From b43a4eed4c9bb539ad6da6b119ca3d8d70b3cbf7 Mon Sep 17 00:00:00 2001 From: Maxim Lebedev Date: Sun, 26 Dec 2021 14:10:14 +0500 Subject: [PATCH] :label: Created CodeChallengeMethod domain --- internal/domain/code_challenge_method.go | 94 +++++++++++++++++++ internal/domain/code_challenge_method_test.go | 60 ++++++++++++ 2 files changed, 154 insertions(+) create mode 100644 internal/domain/code_challenge_method.go create mode 100644 internal/domain/code_challenge_method_test.go diff --git a/internal/domain/code_challenge_method.go b/internal/domain/code_challenge_method.go new file mode 100644 index 0000000..1178ec9 --- /dev/null +++ b/internal/domain/code_challenge_method.go @@ -0,0 +1,94 @@ +package domain + +import ( + "crypto/md5" + "crypto/sha1" + "crypto/sha256" + "crypto/sha512" + "errors" + "fmt" + "hash" + "strings" +) + +// NOTE(toby3d): Encapsulate enums in structs for extra compile-time safety: +// https://threedots.tech/post/safer-enums-in-go/#struct-based-enums +type CodeChallengeMethod struct { + hash hash.Hash + slug string +} + +//nolint: gochecknoglobals // NOTE(toby3d): structs cannot be constants +var ( + CodeChallengeMethodUndefined = CodeChallengeMethod{ + slug: "", + hash: nil, + } + + CodeChallengeMethodPLAIN = CodeChallengeMethod{ + slug: "PLAIN", + hash: nil, + } + + CodeChallengeMethodMD5 = CodeChallengeMethod{ + slug: "MD5", + hash: md5.New(), + } + + CodeChallengeMethodS1 = CodeChallengeMethod{ + slug: "S1", + hash: sha1.New(), + } + + CodeChallengeMethodS256 = CodeChallengeMethod{ + slug: "S256", + hash: sha256.New(), + } + + CodeChallengeMethodS512 = CodeChallengeMethod{ + slug: "S512", + hash: sha512.New(), + } +) + +var ErrCodeChallengeMethodUnknown = errors.New("unknown code challenge method") + +//nolint: gochecknoglobals // NOTE(toby3d): maps cannot be constants +var slugsMethods = map[string]CodeChallengeMethod{ + CodeChallengeMethodMD5.slug: CodeChallengeMethodMD5, + CodeChallengeMethodPLAIN.slug: CodeChallengeMethodPLAIN, + CodeChallengeMethodS1.slug: CodeChallengeMethodS1, + CodeChallengeMethodS256.slug: CodeChallengeMethodS256, + CodeChallengeMethodS512.slug: CodeChallengeMethodS512, +} + +// ParseCodeChallengeMethod parse string identifier of code challenge method +// into struct enum. +func ParseCodeChallengeMethod(slug string) (CodeChallengeMethod, error) { + if method, ok := slugsMethods[strings.ToUpper(slug)]; ok { + return method, nil + } + + return CodeChallengeMethodUndefined, fmt.Errorf("%w: %s", ErrCodeChallengeMethodUnknown, slug) +} + +// UnmarshalForm implements custom unmarshler for form values. +func (ccm *CodeChallengeMethod) UnmarshalForm(v []byte) error { + method, err := ParseCodeChallengeMethod(string(v)) + if err != nil { + return fmt.Errorf("code_challenge_method: %w", err) + } + + *ccm = method + + return nil +} + +// String returns string representation of code challenge method. +func (ccm CodeChallengeMethod) String() string { + return ccm.slug +} + +func (ccm CodeChallengeMethod) Encoder() hash.Hash { + return ccm.hash +} diff --git a/internal/domain/code_challenge_method_test.go b/internal/domain/code_challenge_method_test.go new file mode 100644 index 0000000..43e709c --- /dev/null +++ b/internal/domain/code_challenge_method_test.go @@ -0,0 +1,60 @@ +package domain_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "source.toby3d.me/website/oauth/internal/domain" +) + +func TestParseCodeChallengeMethod(t *testing.T) { + t.Parallel() + + for _, testCase := range []struct { + output domain.CodeChallengeMethod + name string + input string + expError bool + }{{ + expError: true, + name: "invalid", + input: "und", + output: domain.CodeChallengeMethodUndefined, + }, { + name: "PLAIN", + input: "plain", + output: domain.CodeChallengeMethodPLAIN, + }, { + name: "MD5", + input: "Md5", + output: domain.CodeChallengeMethodMD5, + }, { + name: "S1", + input: "S1", + output: domain.CodeChallengeMethodS1, + }, { + name: "S256", + input: "S256", + output: domain.CodeChallengeMethodS256, + }, { + name: "S512", + input: "S512", + output: domain.CodeChallengeMethodS512, + }} { + testCase := testCase + + t.Run(testCase.name, func(t *testing.T) { + t.Parallel() + + result, err := domain.ParseCodeChallengeMethod(testCase.input) + if testCase.expError { + assert.Error(t, err) + assert.Equal(t, domain.CodeChallengeMethodUndefined, result) + } else { + assert.NoError(t, err) + assert.Equal(t, testCase.output, result) + } + }) + } +}