🚚 Moved code challenge method into separated package
This commit is contained in:
parent
cba81b5ac4
commit
71c8373eb4
|
@ -6,6 +6,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"source.toby3d.me/toby3d/auth/internal/domain"
|
"source.toby3d.me/toby3d/auth/internal/domain"
|
||||||
|
"source.toby3d.me/toby3d/auth/internal/domain/challenge"
|
||||||
"source.toby3d.me/toby3d/auth/internal/domain/grant"
|
"source.toby3d.me/toby3d/auth/internal/domain/grant"
|
||||||
"source.toby3d.me/toby3d/auth/internal/domain/response"
|
"source.toby3d.me/toby3d/auth/internal/domain/response"
|
||||||
"source.toby3d.me/toby3d/form"
|
"source.toby3d.me/toby3d/form"
|
||||||
|
@ -24,7 +25,7 @@ type (
|
||||||
Me domain.Me `form:"me"`
|
Me domain.Me `form:"me"`
|
||||||
|
|
||||||
// The hashing method used to calculate the code challenge.
|
// The hashing method used to calculate the code challenge.
|
||||||
CodeChallengeMethod domain.CodeChallengeMethod `form:"code_challenge_method,omitempty"`
|
CodeChallengeMethod challenge.Method `form:"code_challenge_method,omitempty"`
|
||||||
|
|
||||||
// Indicates to the authorization server that an authorization
|
// Indicates to the authorization server that an authorization
|
||||||
// code should be returned as the response.
|
// code should be returned as the response.
|
||||||
|
@ -51,7 +52,7 @@ type (
|
||||||
ClientID domain.ClientID `form:"client_id"`
|
ClientID domain.ClientID `form:"client_id"`
|
||||||
Me domain.Me `form:"me"`
|
Me domain.Me `form:"me"`
|
||||||
RedirectURI domain.URL `form:"redirect_uri"`
|
RedirectURI domain.URL `form:"redirect_uri"`
|
||||||
CodeChallengeMethod domain.CodeChallengeMethod `form:"code_challenge_method,omitempty"`
|
CodeChallengeMethod challenge.Method `form:"code_challenge_method,omitempty"`
|
||||||
ResponseType response.Type `form:"response_type"`
|
ResponseType response.Type `form:"response_type"`
|
||||||
Authorize string `form:"authorize"`
|
Authorize string `form:"authorize"`
|
||||||
CodeChallenge string `form:"code_challenge,omitempty"`
|
CodeChallenge string `form:"code_challenge,omitempty"`
|
||||||
|
@ -97,7 +98,7 @@ func NewAuthAuthorizationRequest() *AuthAuthorizationRequest {
|
||||||
return &AuthAuthorizationRequest{
|
return &AuthAuthorizationRequest{
|
||||||
ClientID: domain.ClientID{},
|
ClientID: domain.ClientID{},
|
||||||
CodeChallenge: "",
|
CodeChallenge: "",
|
||||||
CodeChallengeMethod: domain.CodeChallengeMethodUnd,
|
CodeChallengeMethod: challenge.Und,
|
||||||
Me: domain.Me{},
|
Me: domain.Me{},
|
||||||
RedirectURI: domain.URL{},
|
RedirectURI: domain.URL{},
|
||||||
ResponseType: response.Und,
|
ResponseType: response.Und,
|
||||||
|
@ -131,7 +132,7 @@ func NewAuthVerifyRequest() *AuthVerifyRequest {
|
||||||
Authorize: "",
|
Authorize: "",
|
||||||
ClientID: domain.ClientID{},
|
ClientID: domain.ClientID{},
|
||||||
CodeChallenge: "",
|
CodeChallenge: "",
|
||||||
CodeChallengeMethod: domain.CodeChallengeMethodUnd,
|
CodeChallengeMethod: challenge.Und,
|
||||||
Me: domain.Me{},
|
Me: domain.Me{},
|
||||||
Provider: "",
|
Provider: "",
|
||||||
RedirectURI: domain.URL{},
|
RedirectURI: domain.URL{},
|
||||||
|
|
|
@ -19,6 +19,7 @@ import (
|
||||||
clientrepo "source.toby3d.me/toby3d/auth/internal/client/repository/memory"
|
clientrepo "source.toby3d.me/toby3d/auth/internal/client/repository/memory"
|
||||||
clientucase "source.toby3d.me/toby3d/auth/internal/client/usecase"
|
clientucase "source.toby3d.me/toby3d/auth/internal/client/usecase"
|
||||||
"source.toby3d.me/toby3d/auth/internal/domain"
|
"source.toby3d.me/toby3d/auth/internal/domain"
|
||||||
|
"source.toby3d.me/toby3d/auth/internal/domain/challenge"
|
||||||
"source.toby3d.me/toby3d/auth/internal/domain/response"
|
"source.toby3d.me/toby3d/auth/internal/domain/response"
|
||||||
"source.toby3d.me/toby3d/auth/internal/profile"
|
"source.toby3d.me/toby3d/auth/internal/profile"
|
||||||
profilerepo "source.toby3d.me/toby3d/auth/internal/profile/repository/memory"
|
profilerepo "source.toby3d.me/toby3d/auth/internal/profile/repository/memory"
|
||||||
|
@ -66,7 +67,7 @@ func TestAuthorize(t *testing.T) {
|
||||||
for key, val := range map[string]string{
|
for key, val := range map[string]string{
|
||||||
"client_id": client.ID.String(),
|
"client_id": client.ID.String(),
|
||||||
"code_challenge": "OfYAxt8zU2dAPDWQxTAUIteRzMsoj9QBdMIVEDOErUo",
|
"code_challenge": "OfYAxt8zU2dAPDWQxTAUIteRzMsoj9QBdMIVEDOErUo",
|
||||||
"code_challenge_method": domain.CodeChallengeMethodS256.String(),
|
"code_challenge_method": challenge.S256.String(),
|
||||||
"me": me.String(),
|
"me": me.String(),
|
||||||
"redirect_uri": client.RedirectURI[0].String(),
|
"redirect_uri": client.RedirectURI[0].String(),
|
||||||
"response_type": response.Code.String(),
|
"response_type": response.Code.String(),
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
"source.toby3d.me/toby3d/auth/internal/domain"
|
"source.toby3d.me/toby3d/auth/internal/domain"
|
||||||
|
"source.toby3d.me/toby3d/auth/internal/domain/challenge"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
|
@ -12,7 +13,7 @@ type (
|
||||||
ClientID domain.ClientID
|
ClientID domain.ClientID
|
||||||
Me domain.Me
|
Me domain.Me
|
||||||
RedirectURI *url.URL
|
RedirectURI *url.URL
|
||||||
CodeChallengeMethod domain.CodeChallengeMethod
|
CodeChallengeMethod challenge.Method
|
||||||
CodeChallenge string
|
CodeChallenge string
|
||||||
Scope domain.Scopes
|
Scope domain.Scopes
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
|
|
||||||
"source.toby3d.me/toby3d/auth/internal/auth"
|
"source.toby3d.me/toby3d/auth/internal/auth"
|
||||||
"source.toby3d.me/toby3d/auth/internal/domain"
|
"source.toby3d.me/toby3d/auth/internal/domain"
|
||||||
|
"source.toby3d.me/toby3d/auth/internal/domain/challenge"
|
||||||
"source.toby3d.me/toby3d/auth/internal/profile"
|
"source.toby3d.me/toby3d/auth/internal/profile"
|
||||||
"source.toby3d.me/toby3d/auth/internal/random"
|
"source.toby3d.me/toby3d/auth/internal/random"
|
||||||
"source.toby3d.me/toby3d/auth/internal/session"
|
"source.toby3d.me/toby3d/auth/internal/session"
|
||||||
|
@ -76,7 +77,7 @@ func (uc *authUseCase) Exchange(ctx context.Context, opts auth.ExchangeOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
if session.CodeChallenge != "" &&
|
if session.CodeChallenge != "" &&
|
||||||
session.CodeChallengeMethod != domain.CodeChallengeMethodUnd &&
|
session.CodeChallengeMethod != challenge.Und &&
|
||||||
!session.CodeChallengeMethod.Validate(session.CodeChallenge, opts.CodeVerifier) {
|
!session.CodeChallengeMethod.Validate(session.CodeChallenge, opts.CodeVerifier) {
|
||||||
return nil, nil, auth.ErrMismatchPKCE
|
return nil, nil, auth.ErrMismatchPKCE
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ import (
|
||||||
"source.toby3d.me/toby3d/auth/internal/client"
|
"source.toby3d.me/toby3d/auth/internal/client"
|
||||||
"source.toby3d.me/toby3d/auth/internal/common"
|
"source.toby3d.me/toby3d/auth/internal/common"
|
||||||
"source.toby3d.me/toby3d/auth/internal/domain"
|
"source.toby3d.me/toby3d/auth/internal/domain"
|
||||||
|
"source.toby3d.me/toby3d/auth/internal/domain/challenge"
|
||||||
"source.toby3d.me/toby3d/auth/internal/domain/grant"
|
"source.toby3d.me/toby3d/auth/internal/domain/grant"
|
||||||
"source.toby3d.me/toby3d/auth/internal/domain/response"
|
"source.toby3d.me/toby3d/auth/internal/domain/response"
|
||||||
)
|
)
|
||||||
|
@ -37,7 +38,7 @@ type (
|
||||||
RevocationEndpointAuthMethodsSupported []string `json:"revocation_endpoint_auth_methods_supported,omitempty"`
|
RevocationEndpointAuthMethodsSupported []string `json:"revocation_endpoint_auth_methods_supported,omitempty"`
|
||||||
ScopesSupported []domain.Scope `json:"scopes_supported,omitempty"`
|
ScopesSupported []domain.Scope `json:"scopes_supported,omitempty"`
|
||||||
ResponseTypesSupported []response.Type `json:"response_types_supported,omitempty"`
|
ResponseTypesSupported []response.Type `json:"response_types_supported,omitempty"`
|
||||||
CodeChallengeMethodsSupported []domain.CodeChallengeMethod `json:"code_challenge_methods_supported"`
|
CodeChallengeMethodsSupported []challenge.Method `json:"code_challenge_methods_supported"`
|
||||||
AuthorizationResponseIssParameterSupported bool `json:"authorization_response_iss_parameter_supported,omitempty"`
|
AuthorizationResponseIssParameterSupported bool `json:"authorization_response_iss_parameter_supported,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
132
internal/domain/challenge/method.go
Normal file
132
internal/domain/challenge/method.go
Normal file
|
@ -0,0 +1,132 @@
|
||||||
|
package challenge
|
||||||
|
|
||||||
|
//nolint:gosec // support old clients
|
||||||
|
import (
|
||||||
|
"crypto/md5"
|
||||||
|
"crypto/sha1"
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/sha512"
|
||||||
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"hash"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"source.toby3d.me/toby3d/auth/internal/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Method represent a PKCE challenge method for validate verifier.
|
||||||
|
//
|
||||||
|
// NOTE(toby3d): Encapsulate enums in structs for extra compile-time safety:
|
||||||
|
// https://threedots.tech/post/safer-enums-in-go/#struct-based-enums
|
||||||
|
type Method struct {
|
||||||
|
codeChallengeMethod string
|
||||||
|
}
|
||||||
|
|
||||||
|
//nolint:gochecknoglobals // structs cannot be constants
|
||||||
|
var (
|
||||||
|
Und = Method{} // "und"
|
||||||
|
PLAIN = Method{"plain"} // "plain"
|
||||||
|
MD5 = Method{"md5"} // "md5"
|
||||||
|
S1 = Method{"s1"} // "s1"
|
||||||
|
S256 = Method{"s256"} // "s256"
|
||||||
|
S512 = Method{"s512"} // "s512"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ErrCodeChallengeMethodUnknown error = errors.New("unknown code_challenge_method")
|
||||||
|
|
||||||
|
//nolint:gochecknoglobals // maps cannot be constants
|
||||||
|
var uidsMethods = map[string]Method{
|
||||||
|
MD5.codeChallengeMethod: MD5,
|
||||||
|
PLAIN.codeChallengeMethod: PLAIN,
|
||||||
|
S1.codeChallengeMethod: S1,
|
||||||
|
S256.codeChallengeMethod: S256,
|
||||||
|
S512.codeChallengeMethod: S512,
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseMethod parse string identifier of code challenge method into struct enum.
|
||||||
|
func ParseMethod(uid string) (Method, error) {
|
||||||
|
if method, ok := uidsMethods[uid]; ok {
|
||||||
|
return method, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return Und, fmt.Errorf("%w: %s", ErrCodeChallengeMethodUnknown, uid)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalForm implements custom unmarshler for form values.
|
||||||
|
func (m *Method) UnmarshalForm(v []byte) error {
|
||||||
|
parsed, err := ParseMethod(strings.ToLower(string(v)))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("CodeChallengeMethod: UnmarshalForm: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
*m = parsed
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON implements custom unmarshler for JSON.
|
||||||
|
func (m *Method) UnmarshalJSON(v []byte) error {
|
||||||
|
unquoted, err := strconv.Unquote(string(v))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("CodeChallengeMethod: UnmarshalJSON: cannot unquote value '%s': %w", string(v), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
parsed, err := ParseMethod(strings.ToLower(unquoted))
|
||||||
|
if err != nil && !errors.Is(err, ErrCodeChallengeMethodUnknown) {
|
||||||
|
return fmt.Errorf("CodeChallengeMethod: UnmarshalJSON: cannot parse '%s' value: %w", unquoted, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
*m = parsed
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m Method) MarshalJSON() ([]byte, error) {
|
||||||
|
if m == Und {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return []byte(strconv.Quote(m.String())), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns string representation of code challenge method.
|
||||||
|
func (m Method) String() string {
|
||||||
|
if m == Und {
|
||||||
|
return common.Und
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.ToUpper(m.codeChallengeMethod)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m Method) GoString() string {
|
||||||
|
return "challenge.Method(" + m.String() + ")"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate checks for a match to the verifier with the hashed version of the
|
||||||
|
// challenge via the chosen method.
|
||||||
|
func (m Method) Validate(codeChallenge, verifier string) bool {
|
||||||
|
var h hash.Hash
|
||||||
|
|
||||||
|
switch m {
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
case PLAIN:
|
||||||
|
return codeChallenge == verifier
|
||||||
|
case MD5:
|
||||||
|
h = md5.New()
|
||||||
|
case S1:
|
||||||
|
h = sha1.New()
|
||||||
|
case S256:
|
||||||
|
h = sha256.New()
|
||||||
|
case S512:
|
||||||
|
h = sha512.New()
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := h.Write([]byte(verifier)); err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return codeChallenge == base64.RawURLEncoding.EncodeToString(h.Sum(nil))
|
||||||
|
}
|
160
internal/domain/challenge/method_test.go
Normal file
160
internal/domain/challenge/method_test.go
Normal file
|
@ -0,0 +1,160 @@
|
||||||
|
package challenge_test
|
||||||
|
|
||||||
|
//nolint:gosec // support old clients
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/md5"
|
||||||
|
"crypto/sha1"
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/sha512"
|
||||||
|
"encoding/base64"
|
||||||
|
"hash"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/brianvoe/gofakeit/v6"
|
||||||
|
|
||||||
|
"source.toby3d.me/toby3d/auth/internal/domain/challenge"
|
||||||
|
"source.toby3d.me/toby3d/auth/internal/random"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParseMethod(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
for name, tc := range map[string]struct {
|
||||||
|
input string
|
||||||
|
expect challenge.Method
|
||||||
|
}{
|
||||||
|
"PLAIN": {input: "plain", expect: challenge.PLAIN},
|
||||||
|
"MD5": {input: "md5", expect: challenge.MD5},
|
||||||
|
"S1": {input: "s1", expect: challenge.S1},
|
||||||
|
"S256": {input: "s256", expect: challenge.S256},
|
||||||
|
"S512": {input: "s512", expect: challenge.S512},
|
||||||
|
} {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
actual, err := challenge.ParseMethod(tc.input)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if actual != tc.expect {
|
||||||
|
t.Errorf("ParseMethod(%s) = %v, want %v", tc.input, actual, tc.expect)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMethod_UnmarshalForm(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
input := []byte("s256")
|
||||||
|
actual := challenge.Und
|
||||||
|
|
||||||
|
if err := actual.UnmarshalForm(input); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if actual != challenge.S256 {
|
||||||
|
t.Errorf("UnmarshalForm(%s) = %v, want %v", input, actual, challenge.S256)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMethod_UnmarshalJSON(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
input := []byte(`"S256"`)
|
||||||
|
actual := challenge.Und
|
||||||
|
|
||||||
|
if err := actual.UnmarshalJSON(input); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if actual != challenge.S256 {
|
||||||
|
t.Errorf("UnmarshalJSON(%s) = %v, want %v", input, actual, challenge.S256)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMethod_String(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
for name, tc := range map[string]struct {
|
||||||
|
input challenge.Method
|
||||||
|
expect string
|
||||||
|
}{
|
||||||
|
"plain": {input: challenge.PLAIN, expect: "PLAIN"},
|
||||||
|
"md5": {input: challenge.MD5, expect: "MD5"},
|
||||||
|
"s1": {input: challenge.S1, expect: "S1"},
|
||||||
|
"s256": {input: challenge.S256, expect: "S256"},
|
||||||
|
"s512": {input: challenge.S512, expect: "S512"},
|
||||||
|
} {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
if actual := tc.input.String(); actual != tc.expect {
|
||||||
|
t.Errorf("String() = %v, want %v", actual, tc.expect)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//nolint:gosec // support old clients
|
||||||
|
func TestMethod_Validate(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
verifier, err := random.String(uint8(gofakeit.Number(43, 128)))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tc := range map[string]struct {
|
||||||
|
hash hash.Hash
|
||||||
|
input challenge.Method
|
||||||
|
ok bool
|
||||||
|
}{
|
||||||
|
"invalid": {input: challenge.S256, hash: md5.New(), ok: true},
|
||||||
|
"MD5": {input: challenge.MD5, hash: md5.New(), ok: false},
|
||||||
|
"plain": {input: challenge.PLAIN, hash: nil, ok: false},
|
||||||
|
"S1": {input: challenge.S1, hash: sha1.New(), ok: false},
|
||||||
|
"S256": {input: challenge.S256, hash: sha256.New(), ok: false},
|
||||||
|
"S512": {input: challenge.S512, hash: sha512.New(), ok: false},
|
||||||
|
"Und": {input: challenge.Und, hash: nil, ok: true},
|
||||||
|
} {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
var codeChallenge string
|
||||||
|
|
||||||
|
switch tc.input {
|
||||||
|
case challenge.Und, challenge.PLAIN:
|
||||||
|
codeChallenge = verifier
|
||||||
|
default:
|
||||||
|
hash := tc.hash
|
||||||
|
hash.Reset()
|
||||||
|
|
||||||
|
if _, err := hash.Write([]byte(verifier)); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
codeChallenge = base64.RawURLEncoding.EncodeToString(hash.Sum(nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
if actual := tc.input.Validate(codeChallenge, verifier); actual != !tc.ok {
|
||||||
|
t.Errorf("Validate(%s, %s) = %t, want %t", codeChallenge, verifier, actual, tc.ok)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMethod_Validate_IndieAuth(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
if ok := challenge.S256.Validate(
|
||||||
|
"ALiMNf5FvF_LIWLhSkd9tjPKh3PEmai2OrdDBzrVZ3M",
|
||||||
|
"6f535c952339f0670311b4bbec5c41c00805e83291fc7eb15ca4963f82a4d57595787dcc6ee90571fb7789cbd521fe0178ed",
|
||||||
|
); !ok {
|
||||||
|
t.Errorf("Validate(%s, %s) = %t, want %t", "ALiMNf5FvF_LIWLhSkd9tjPKh3PEmai2OrdDBzrVZ3M",
|
||||||
|
"6f535c952339f0670311b4bbec5c41c00805e83291fc7eb15ca4963f82a4d57595787dcc6ee90571fb7789cbd521"+
|
||||||
|
"fe0178ed", ok, true)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,137 +0,0 @@
|
||||||
package domain
|
|
||||||
|
|
||||||
//nolint:gosec // support old clients
|
|
||||||
import (
|
|
||||||
"crypto/md5"
|
|
||||||
"crypto/sha1"
|
|
||||||
"crypto/sha256"
|
|
||||||
"crypto/sha512"
|
|
||||||
"encoding/base64"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"hash"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"source.toby3d.me/toby3d/auth/internal/common"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CodeChallengeMethod represent a PKCE challenge method for validate verifier.
|
|
||||||
//
|
|
||||||
// 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 {
|
|
||||||
codeChallengeMethod string
|
|
||||||
}
|
|
||||||
|
|
||||||
//nolint:gochecknoglobals // structs cannot be constants
|
|
||||||
var (
|
|
||||||
CodeChallengeMethodUnd = CodeChallengeMethod{codeChallengeMethod: ""} // "und"
|
|
||||||
CodeChallengeMethodPLAIN = CodeChallengeMethod{codeChallengeMethod: "plain"} // "plain"
|
|
||||||
CodeChallengeMethodMD5 = CodeChallengeMethod{codeChallengeMethod: "md5"} // "md5"
|
|
||||||
CodeChallengeMethodS1 = CodeChallengeMethod{codeChallengeMethod: "s1"} // "s1"
|
|
||||||
CodeChallengeMethodS256 = CodeChallengeMethod{codeChallengeMethod: "s256"} // "s256"
|
|
||||||
CodeChallengeMethodS512 = CodeChallengeMethod{codeChallengeMethod: "s512"} // "s512"
|
|
||||||
)
|
|
||||||
|
|
||||||
var ErrCodeChallengeMethodUnknown error = NewError(
|
|
||||||
ErrorCodeInvalidRequest,
|
|
||||||
"unknown code_challenge_method",
|
|
||||||
"https://indieauth.net/source/#authorization-request",
|
|
||||||
)
|
|
||||||
|
|
||||||
//nolint:gochecknoglobals // maps cannot be constants
|
|
||||||
var uidsMethods = map[string]CodeChallengeMethod{
|
|
||||||
CodeChallengeMethodMD5.codeChallengeMethod: CodeChallengeMethodMD5,
|
|
||||||
CodeChallengeMethodPLAIN.codeChallengeMethod: CodeChallengeMethodPLAIN,
|
|
||||||
CodeChallengeMethodS1.codeChallengeMethod: CodeChallengeMethodS1,
|
|
||||||
CodeChallengeMethodS256.codeChallengeMethod: CodeChallengeMethodS256,
|
|
||||||
CodeChallengeMethodS512.codeChallengeMethod: CodeChallengeMethodS512,
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseCodeChallengeMethod parse string identifier of code challenge method
|
|
||||||
// into struct enum.
|
|
||||||
func ParseCodeChallengeMethod(uid string) (CodeChallengeMethod, error) {
|
|
||||||
if method, ok := uidsMethods[uid]; ok {
|
|
||||||
return method, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return CodeChallengeMethodUnd, fmt.Errorf("%w: %s", ErrCodeChallengeMethodUnknown, uid)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalForm implements custom unmarshler for form values.
|
|
||||||
func (ccm *CodeChallengeMethod) UnmarshalForm(v []byte) error {
|
|
||||||
parsed, err := ParseCodeChallengeMethod(strings.ToLower(string(v)))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("CodeChallengeMethod: UnmarshalForm: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
*ccm = parsed
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON implements custom unmarshler for JSON.
|
|
||||||
func (ccm *CodeChallengeMethod) UnmarshalJSON(v []byte) error {
|
|
||||||
unquoted, err := strconv.Unquote(string(v))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("CodeChallengeMethod: UnmarshalJSON: cannot unquote value '%s': %w", string(v), err)
|
|
||||||
}
|
|
||||||
|
|
||||||
parsed, err := ParseCodeChallengeMethod(strings.ToLower(unquoted))
|
|
||||||
if err != nil && !errors.Is(err, ErrCodeChallengeMethodUnknown) {
|
|
||||||
return fmt.Errorf("CodeChallengeMethod: UnmarshalJSON: cannot parse '%s' value: %w", unquoted, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
*ccm = parsed
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ccm CodeChallengeMethod) MarshalJSON() ([]byte, error) {
|
|
||||||
if ccm == CodeChallengeMethodUnd {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return []byte(strconv.Quote(ccm.String())), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns string representation of code challenge method.
|
|
||||||
func (ccm CodeChallengeMethod) String() string {
|
|
||||||
if ccm == CodeChallengeMethodUnd {
|
|
||||||
return common.Und
|
|
||||||
}
|
|
||||||
|
|
||||||
return strings.ToUpper(ccm.codeChallengeMethod)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ccm CodeChallengeMethod) GoString() string {
|
|
||||||
return "domain.CodeChallengeMethod(" + ccm.String() + ")"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate checks for a match to the verifier with the hashed version of the
|
|
||||||
// challenge via the chosen method.
|
|
||||||
func (ccm CodeChallengeMethod) Validate(codeChallenge, verifier string) bool {
|
|
||||||
var h hash.Hash
|
|
||||||
|
|
||||||
switch ccm {
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
case CodeChallengeMethodPLAIN:
|
|
||||||
return codeChallenge == verifier
|
|
||||||
case CodeChallengeMethodMD5:
|
|
||||||
h = md5.New()
|
|
||||||
case CodeChallengeMethodS1:
|
|
||||||
h = sha1.New()
|
|
||||||
case CodeChallengeMethodS256:
|
|
||||||
h = sha256.New()
|
|
||||||
case CodeChallengeMethodS512:
|
|
||||||
h = sha512.New()
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := h.Write([]byte(verifier)); err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return codeChallenge == base64.RawURLEncoding.EncodeToString(h.Sum(nil))
|
|
||||||
}
|
|
|
@ -1,160 +0,0 @@
|
||||||
package domain_test
|
|
||||||
|
|
||||||
//nolint:gosec // support old clients
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/md5"
|
|
||||||
"crypto/sha1"
|
|
||||||
"crypto/sha256"
|
|
||||||
"crypto/sha512"
|
|
||||||
"encoding/base64"
|
|
||||||
"hash"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/brianvoe/gofakeit/v6"
|
|
||||||
|
|
||||||
"source.toby3d.me/toby3d/auth/internal/domain"
|
|
||||||
"source.toby3d.me/toby3d/auth/internal/random"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestParseCodeChallengeMethod(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
for name, tc := range map[string]struct {
|
|
||||||
input string
|
|
||||||
expect domain.CodeChallengeMethod
|
|
||||||
}{
|
|
||||||
"PLAIN": {input: "plain", expect: domain.CodeChallengeMethodPLAIN},
|
|
||||||
"MD5": {input: "md5", expect: domain.CodeChallengeMethodMD5},
|
|
||||||
"S1": {input: "s1", expect: domain.CodeChallengeMethodS1},
|
|
||||||
"S256": {input: "s256", expect: domain.CodeChallengeMethodS256},
|
|
||||||
"S512": {input: "s512", expect: domain.CodeChallengeMethodS512},
|
|
||||||
} {
|
|
||||||
t.Run(name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
actual, err := domain.ParseCodeChallengeMethod(tc.input)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if actual != tc.expect {
|
|
||||||
t.Errorf("ParseCodeChallengeMethod(%s) = %v, want %v", tc.input, actual, tc.expect)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCodeChallengeMethod_UnmarshalForm(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
input := []byte("s256")
|
|
||||||
actual := domain.CodeChallengeMethodUnd
|
|
||||||
|
|
||||||
if err := actual.UnmarshalForm(input); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if actual != domain.CodeChallengeMethodS256 {
|
|
||||||
t.Errorf("UnmarshalForm(%s) = %v, want %v", input, actual, domain.CodeChallengeMethodS256)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCodeChallengeMethod_UnmarshalJSON(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
input := []byte(`"S256"`)
|
|
||||||
actual := domain.CodeChallengeMethodUnd
|
|
||||||
|
|
||||||
if err := actual.UnmarshalJSON(input); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if actual != domain.CodeChallengeMethodS256 {
|
|
||||||
t.Errorf("UnmarshalJSON(%s) = %v, want %v", input, actual, domain.CodeChallengeMethodS256)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCodeChallengeMethod_String(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
for name, tc := range map[string]struct {
|
|
||||||
input domain.CodeChallengeMethod
|
|
||||||
expect string
|
|
||||||
}{
|
|
||||||
"plain": {input: domain.CodeChallengeMethodPLAIN, expect: "PLAIN"},
|
|
||||||
"md5": {input: domain.CodeChallengeMethodMD5, expect: "MD5"},
|
|
||||||
"s1": {input: domain.CodeChallengeMethodS1, expect: "S1"},
|
|
||||||
"s256": {input: domain.CodeChallengeMethodS256, expect: "S256"},
|
|
||||||
"s512": {input: domain.CodeChallengeMethodS512, expect: "S512"},
|
|
||||||
} {
|
|
||||||
t.Run(name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
if actual := tc.input.String(); actual != tc.expect {
|
|
||||||
t.Errorf("String() = %v, want %v", actual, tc.expect)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//nolint:gosec // support old clients
|
|
||||||
func TestCodeChallengeMethod_Validate(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
verifier, err := random.String(uint8(gofakeit.Number(43, 128)))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for name, tc := range map[string]struct {
|
|
||||||
hash hash.Hash
|
|
||||||
input domain.CodeChallengeMethod
|
|
||||||
ok bool
|
|
||||||
}{
|
|
||||||
"invalid": {input: domain.CodeChallengeMethodS256, hash: md5.New(), ok: true},
|
|
||||||
"MD5": {input: domain.CodeChallengeMethodMD5, hash: md5.New(), ok: false},
|
|
||||||
"plain": {input: domain.CodeChallengeMethodPLAIN, hash: nil, ok: false},
|
|
||||||
"S1": {input: domain.CodeChallengeMethodS1, hash: sha1.New(), ok: false},
|
|
||||||
"S256": {input: domain.CodeChallengeMethodS256, hash: sha256.New(), ok: false},
|
|
||||||
"S512": {input: domain.CodeChallengeMethodS512, hash: sha512.New(), ok: false},
|
|
||||||
"Und": {input: domain.CodeChallengeMethodUnd, hash: nil, ok: true},
|
|
||||||
} {
|
|
||||||
t.Run(name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
var codeChallenge string
|
|
||||||
|
|
||||||
switch tc.input {
|
|
||||||
case domain.CodeChallengeMethodUnd, domain.CodeChallengeMethodPLAIN:
|
|
||||||
codeChallenge = verifier
|
|
||||||
default:
|
|
||||||
hash := tc.hash
|
|
||||||
hash.Reset()
|
|
||||||
|
|
||||||
if _, err := hash.Write([]byte(verifier)); err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
codeChallenge = base64.RawURLEncoding.EncodeToString(hash.Sum(nil))
|
|
||||||
}
|
|
||||||
|
|
||||||
if actual := tc.input.Validate(codeChallenge, verifier); actual != !tc.ok {
|
|
||||||
t.Errorf("Validate(%s, %s) = %t, want %t", codeChallenge, verifier, actual, tc.ok)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCodeChallengeMethod_Validate_IndieAuth(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
if ok := domain.CodeChallengeMethodS256.Validate(
|
|
||||||
"ALiMNf5FvF_LIWLhSkd9tjPKh3PEmai2OrdDBzrVZ3M",
|
|
||||||
"6f535c952339f0670311b4bbec5c41c00805e83291fc7eb15ca4963f82a4d57595787dcc6ee90571fb7789cbd521fe0178ed",
|
|
||||||
); !ok {
|
|
||||||
t.Errorf("Validate(%s, %s) = %t, want %t", "ALiMNf5FvF_LIWLhSkd9tjPKh3PEmai2OrdDBzrVZ3M",
|
|
||||||
"6f535c952339f0670311b4bbec5c41c00805e83291fc7eb15ca4963f82a4d57595787dcc6ee90571fb7789cbd521"+
|
|
||||||
"fe0178ed", ok, true)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"source.toby3d.me/toby3d/auth/internal/domain/challenge"
|
||||||
"source.toby3d.me/toby3d/auth/internal/domain/grant"
|
"source.toby3d.me/toby3d/auth/internal/domain/grant"
|
||||||
"source.toby3d.me/toby3d/auth/internal/domain/response"
|
"source.toby3d.me/toby3d/auth/internal/domain/response"
|
||||||
)
|
)
|
||||||
|
@ -64,7 +65,7 @@ type Metadata struct {
|
||||||
|
|
||||||
// JSON array containing the methods supported for PKCE. This parameter
|
// JSON array containing the methods supported for PKCE. This parameter
|
||||||
// differs from RFC8414 in that it is not optional as PKCE is REQUIRED.
|
// differs from RFC8414 in that it is not optional as PKCE is REQUIRED.
|
||||||
CodeChallengeMethodsSupported []CodeChallengeMethod
|
CodeChallengeMethodsSupported []challenge.Method
|
||||||
|
|
||||||
// List of client authentication methods supported by this introspection endpoint.
|
// List of client authentication methods supported by this introspection endpoint.
|
||||||
IntrospectionEndpointAuthMethodsSupported []string // ["Bearer"]
|
IntrospectionEndpointAuthMethodsSupported []string // ["Bearer"]
|
||||||
|
@ -119,12 +120,12 @@ func TestMetadata(tb testing.TB) *Metadata {
|
||||||
grant.AuthorizationCode,
|
grant.AuthorizationCode,
|
||||||
grant.Ticket,
|
grant.Ticket,
|
||||||
},
|
},
|
||||||
CodeChallengeMethodsSupported: []CodeChallengeMethod{
|
CodeChallengeMethodsSupported: []challenge.Method{
|
||||||
CodeChallengeMethodMD5,
|
challenge.MD5,
|
||||||
CodeChallengeMethodPLAIN,
|
challenge.PLAIN,
|
||||||
CodeChallengeMethodS1,
|
challenge.S1,
|
||||||
CodeChallengeMethodS256,
|
challenge.S256,
|
||||||
CodeChallengeMethodS512,
|
challenge.S512,
|
||||||
},
|
},
|
||||||
IntrospectionEndpointAuthMethodsSupported: []string{"Bearer"},
|
IntrospectionEndpointAuthMethodsSupported: []string{"Bearer"},
|
||||||
RevocationEndpointAuthMethodsSupported: []string{"none"},
|
RevocationEndpointAuthMethodsSupported: []string{"none"},
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"source.toby3d.me/toby3d/auth/internal/domain/challenge"
|
||||||
"source.toby3d.me/toby3d/auth/internal/random"
|
"source.toby3d.me/toby3d/auth/internal/random"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -13,7 +14,7 @@ type Session struct {
|
||||||
RedirectURI *url.URL `json:"redirect_uri"`
|
RedirectURI *url.URL `json:"redirect_uri"`
|
||||||
Me Me `json:"me"`
|
Me Me `json:"me"`
|
||||||
Profile *Profile `json:"profile,omitempty"`
|
Profile *Profile `json:"profile,omitempty"`
|
||||||
CodeChallengeMethod CodeChallengeMethod `json:"code_challenge_method,omitempty"`
|
CodeChallengeMethod challenge.Method `json:"code_challenge_method,omitempty"`
|
||||||
CodeChallenge string `json:"code_challenge,omitempty"`
|
CodeChallenge string `json:"code_challenge,omitempty"`
|
||||||
Code string `json:"-"`
|
Code string `json:"-"`
|
||||||
Scope Scopes `json:"scope"`
|
Scope Scopes `json:"scope"`
|
||||||
|
@ -34,7 +35,7 @@ func TestSession(tb testing.TB) *Session {
|
||||||
ClientID: *TestClientID(tb),
|
ClientID: *TestClientID(tb),
|
||||||
Code: code,
|
Code: code,
|
||||||
CodeChallenge: "hackme",
|
CodeChallenge: "hackme",
|
||||||
CodeChallengeMethod: CodeChallengeMethodPLAIN,
|
CodeChallengeMethod: challenge.PLAIN,
|
||||||
Profile: TestProfile(tb),
|
Profile: TestProfile(tb),
|
||||||
Me: *TestMe(tb, "https://user.example.net/"),
|
Me: *TestMe(tb, "https://user.example.net/"),
|
||||||
RedirectURI: &url.URL{Scheme: "https", Host: "example.com", Path: "/callback"},
|
RedirectURI: &url.URL{Scheme: "https", Host: "example.com", Path: "/callback"},
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
|
|
||||||
"source.toby3d.me/toby3d/auth/internal/common"
|
"source.toby3d.me/toby3d/auth/internal/common"
|
||||||
"source.toby3d.me/toby3d/auth/internal/domain"
|
"source.toby3d.me/toby3d/auth/internal/domain"
|
||||||
|
"source.toby3d.me/toby3d/auth/internal/domain/challenge"
|
||||||
"source.toby3d.me/toby3d/auth/internal/domain/grant"
|
"source.toby3d.me/toby3d/auth/internal/domain/grant"
|
||||||
"source.toby3d.me/toby3d/auth/internal/domain/response"
|
"source.toby3d.me/toby3d/auth/internal/domain/response"
|
||||||
"source.toby3d.me/toby3d/auth/internal/metadata"
|
"source.toby3d.me/toby3d/auth/internal/metadata"
|
||||||
|
@ -35,7 +36,7 @@ type (
|
||||||
RevocationEndpointAuthMethodsSupported []string `json:"revocation_endpoint_auth_methods_supported,omitempty"`
|
RevocationEndpointAuthMethodsSupported []string `json:"revocation_endpoint_auth_methods_supported,omitempty"`
|
||||||
ScopesSupported []domain.Scope `json:"scopes_supported,omitempty"`
|
ScopesSupported []domain.Scope `json:"scopes_supported,omitempty"`
|
||||||
ResponseTypesSupported []response.Type `json:"response_types_supported,omitempty"`
|
ResponseTypesSupported []response.Type `json:"response_types_supported,omitempty"`
|
||||||
CodeChallengeMethodsSupported []domain.CodeChallengeMethod `json:"code_challenge_methods_supported"`
|
CodeChallengeMethodsSupported []challenge.Method `json:"code_challenge_methods_supported"`
|
||||||
AuthorizationResponseIssParameterSupported bool `json:"authorization_response_iss_parameter_supported,omitempty"`
|
AuthorizationResponseIssParameterSupported bool `json:"authorization_response_iss_parameter_supported,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -119,7 +120,7 @@ func populateBuffer(dst map[string][]string, rel, u string) {
|
||||||
|
|
||||||
func NewResponse() *Response {
|
func NewResponse() *Response {
|
||||||
return &Response{
|
return &Response{
|
||||||
CodeChallengeMethodsSupported: make([]domain.CodeChallengeMethod, 0),
|
CodeChallengeMethodsSupported: make([]challenge.Method, 0),
|
||||||
GrantTypesSupported: make([]grant.Type, 0),
|
GrantTypesSupported: make([]grant.Type, 0),
|
||||||
ResponseTypesSupported: make([]response.Type, 0),
|
ResponseTypesSupported: make([]response.Type, 0),
|
||||||
ScopesSupported: make([]domain.Scope, 0),
|
ScopesSupported: make([]domain.Scope, 0),
|
||||||
|
|
|
@ -14,6 +14,7 @@ import (
|
||||||
|
|
||||||
"source.toby3d.me/toby3d/auth/internal/common"
|
"source.toby3d.me/toby3d/auth/internal/common"
|
||||||
"source.toby3d.me/toby3d/auth/internal/domain"
|
"source.toby3d.me/toby3d/auth/internal/domain"
|
||||||
|
"source.toby3d.me/toby3d/auth/internal/domain/challenge"
|
||||||
"source.toby3d.me/toby3d/auth/internal/domain/grant"
|
"source.toby3d.me/toby3d/auth/internal/domain/grant"
|
||||||
"source.toby3d.me/toby3d/auth/internal/domain/response"
|
"source.toby3d.me/toby3d/auth/internal/domain/response"
|
||||||
repository "source.toby3d.me/toby3d/auth/internal/metadata/repository/http"
|
repository "source.toby3d.me/toby3d/auth/internal/metadata/repository/http"
|
||||||
|
@ -121,7 +122,7 @@ func TestGet(t *testing.T) {
|
||||||
|
|
||||||
if diff := cmp.Diff(tc.out, out, cmp.AllowUnexported(
|
if diff := cmp.Diff(tc.out, out, cmp.AllowUnexported(
|
||||||
domain.ClientID{},
|
domain.ClientID{},
|
||||||
domain.CodeChallengeMethod{},
|
challenge.Und,
|
||||||
grant.Und,
|
grant.Und,
|
||||||
response.Und,
|
response.Und,
|
||||||
domain.Scope{},
|
domain.Scope{},
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"github.com/lestrrat-go/jwx/v2/jwt"
|
"github.com/lestrrat-go/jwx/v2/jwt"
|
||||||
|
|
||||||
"source.toby3d.me/toby3d/auth/internal/domain"
|
"source.toby3d.me/toby3d/auth/internal/domain"
|
||||||
|
"source.toby3d.me/toby3d/auth/internal/domain/challenge"
|
||||||
"source.toby3d.me/toby3d/auth/internal/profile"
|
"source.toby3d.me/toby3d/auth/internal/profile"
|
||||||
"source.toby3d.me/toby3d/auth/internal/session"
|
"source.toby3d.me/toby3d/auth/internal/session"
|
||||||
"source.toby3d.me/toby3d/auth/internal/token"
|
"source.toby3d.me/toby3d/auth/internal/token"
|
||||||
|
@ -58,7 +59,7 @@ func (uc *tokenUseCase) Exchange(ctx context.Context, opts token.ExchangeOptions
|
||||||
return nil, nil, token.ErrMismatchRedirectURI
|
return nil, nil, token.ErrMismatchRedirectURI
|
||||||
}
|
}
|
||||||
|
|
||||||
if session.CodeChallenge != "" && session.CodeChallengeMethod != domain.CodeChallengeMethodUnd &&
|
if session.CodeChallenge != "" && session.CodeChallengeMethod != challenge.Und &&
|
||||||
!session.CodeChallengeMethod.Validate(session.CodeChallenge, opts.CodeVerifier) {
|
!session.CodeChallengeMethod.Validate(session.CodeChallenge, opts.CodeVerifier) {
|
||||||
return nil, nil, token.ErrMismatchPKCE
|
return nil, nil, token.ErrMismatchPKCE
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ import (
|
||||||
|
|
||||||
"source.toby3d.me/toby3d/auth/internal/common"
|
"source.toby3d.me/toby3d/auth/internal/common"
|
||||||
"source.toby3d.me/toby3d/auth/internal/domain"
|
"source.toby3d.me/toby3d/auth/internal/domain"
|
||||||
|
"source.toby3d.me/toby3d/auth/internal/domain/challenge"
|
||||||
"source.toby3d.me/toby3d/auth/internal/domain/grant"
|
"source.toby3d.me/toby3d/auth/internal/domain/grant"
|
||||||
"source.toby3d.me/toby3d/auth/internal/domain/response"
|
"source.toby3d.me/toby3d/auth/internal/domain/response"
|
||||||
"source.toby3d.me/toby3d/auth/internal/user"
|
"source.toby3d.me/toby3d/auth/internal/user"
|
||||||
|
@ -38,7 +39,7 @@ type (
|
||||||
RevocationEndpointAuthMethodsSupported []string `json:"revocation_endpoint_auth_methods_supported,omitempty"`
|
RevocationEndpointAuthMethodsSupported []string `json:"revocation_endpoint_auth_methods_supported,omitempty"`
|
||||||
ScopesSupported []domain.Scope `json:"scopes_supported,omitempty"`
|
ScopesSupported []domain.Scope `json:"scopes_supported,omitempty"`
|
||||||
ResponseTypesSupported []response.Type `json:"response_types_supported,omitempty"`
|
ResponseTypesSupported []response.Type `json:"response_types_supported,omitempty"`
|
||||||
CodeChallengeMethodsSupported []domain.CodeChallengeMethod `json:"code_challenge_methods_supported"`
|
CodeChallengeMethodsSupported []challenge.Method `json:"code_challenge_methods_supported"`
|
||||||
AuthorizationResponseIssParameterSupported bool `json:"authorization_response_iss_parameter_supported,omitempty"`
|
AuthorizationResponseIssParameterSupported bool `json:"authorization_response_iss_parameter_supported,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
13
main.go
13
main.go
|
@ -37,6 +37,7 @@ import (
|
||||||
clienthttprepo "source.toby3d.me/toby3d/auth/internal/client/repository/http"
|
clienthttprepo "source.toby3d.me/toby3d/auth/internal/client/repository/http"
|
||||||
clientucase "source.toby3d.me/toby3d/auth/internal/client/usecase"
|
clientucase "source.toby3d.me/toby3d/auth/internal/client/usecase"
|
||||||
"source.toby3d.me/toby3d/auth/internal/domain"
|
"source.toby3d.me/toby3d/auth/internal/domain"
|
||||||
|
"source.toby3d.me/toby3d/auth/internal/domain/challenge"
|
||||||
"source.toby3d.me/toby3d/auth/internal/domain/grant"
|
"source.toby3d.me/toby3d/auth/internal/domain/grant"
|
||||||
"source.toby3d.me/toby3d/auth/internal/domain/response"
|
"source.toby3d.me/toby3d/auth/internal/domain/response"
|
||||||
healthhttpdelivery "source.toby3d.me/toby3d/auth/internal/health/delivery/http"
|
healthhttpdelivery "source.toby3d.me/toby3d/auth/internal/health/delivery/http"
|
||||||
|
@ -295,12 +296,12 @@ func (app *App) Handler() http.Handler {
|
||||||
grant.AuthorizationCode,
|
grant.AuthorizationCode,
|
||||||
grant.Ticket,
|
grant.Ticket,
|
||||||
},
|
},
|
||||||
CodeChallengeMethodsSupported: []domain.CodeChallengeMethod{
|
CodeChallengeMethodsSupported: []challenge.Method{
|
||||||
domain.CodeChallengeMethodMD5,
|
challenge.MD5,
|
||||||
domain.CodeChallengeMethodPLAIN,
|
challenge.PLAIN,
|
||||||
domain.CodeChallengeMethodS1,
|
challenge.S1,
|
||||||
domain.CodeChallengeMethodS256,
|
challenge.S256,
|
||||||
domain.CodeChallengeMethodS512,
|
challenge.S512,
|
||||||
},
|
},
|
||||||
AuthorizationResponseIssParameterSupported: true,
|
AuthorizationResponseIssParameterSupported: true,
|
||||||
})
|
})
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
{% import (
|
{% import (
|
||||||
"source.toby3d.me/toby3d/auth/internal/domain"
|
"source.toby3d.me/toby3d/auth/internal/domain"
|
||||||
|
"source.toby3d.me/toby3d/auth/internal/domain/challenge"
|
||||||
"source.toby3d.me/toby3d/auth/internal/domain/response"
|
"source.toby3d.me/toby3d/auth/internal/domain/response"
|
||||||
"source.toby3d.me/toby3d/auth/web/template/layout"
|
"source.toby3d.me/toby3d/auth/web/template/layout"
|
||||||
) %}
|
) %}
|
||||||
|
@ -9,7 +10,7 @@
|
||||||
{% code type Authorize struct {
|
{% code type Authorize struct {
|
||||||
layout.BaseOf
|
layout.BaseOf
|
||||||
Scope domain.Scopes
|
Scope domain.Scopes
|
||||||
CodeChallengeMethod domain.CodeChallengeMethod
|
CodeChallengeMethod challenge.Method
|
||||||
ResponseType response.Type
|
ResponseType response.Type
|
||||||
Client *domain.Client
|
Client *domain.Client
|
||||||
Me *domain.Me
|
Me *domain.Me
|
||||||
|
@ -60,7 +61,7 @@
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
<aside>
|
<aside>
|
||||||
{% if p.CodeChallengeMethod != domain.CodeChallengeMethodUnd && p.CodeChallenge != "" %}
|
{% if p.CodeChallengeMethod != challenge.Und && p.CodeChallenge != "" %}
|
||||||
<p class="with-icon">
|
<p class="with-icon">
|
||||||
<span class="icon"
|
<span class="icon"
|
||||||
role="img"
|
role="img"
|
||||||
|
|
|
@ -7,28 +7,29 @@ package template
|
||||||
//line web/template/authorize.qtpl:3
|
//line web/template/authorize.qtpl:3
|
||||||
import (
|
import (
|
||||||
"source.toby3d.me/toby3d/auth/internal/domain"
|
"source.toby3d.me/toby3d/auth/internal/domain"
|
||||||
|
"source.toby3d.me/toby3d/auth/internal/domain/challenge"
|
||||||
"source.toby3d.me/toby3d/auth/internal/domain/response"
|
"source.toby3d.me/toby3d/auth/internal/domain/response"
|
||||||
"source.toby3d.me/toby3d/auth/web/template/layout"
|
"source.toby3d.me/toby3d/auth/web/template/layout"
|
||||||
)
|
)
|
||||||
|
|
||||||
//line web/template/authorize.qtpl:9
|
//line web/template/authorize.qtpl:10
|
||||||
import (
|
import (
|
||||||
qtio422016 "io"
|
qtio422016 "io"
|
||||||
|
|
||||||
qt422016 "github.com/valyala/quicktemplate"
|
qt422016 "github.com/valyala/quicktemplate"
|
||||||
)
|
)
|
||||||
|
|
||||||
//line web/template/authorize.qtpl:9
|
//line web/template/authorize.qtpl:10
|
||||||
var (
|
var (
|
||||||
_ = qtio422016.Copy
|
_ = qtio422016.Copy
|
||||||
_ = qt422016.AcquireByteBuffer
|
_ = qt422016.AcquireByteBuffer
|
||||||
)
|
)
|
||||||
|
|
||||||
//line web/template/authorize.qtpl:9
|
//line web/template/authorize.qtpl:10
|
||||||
type Authorize struct {
|
type Authorize struct {
|
||||||
layout.BaseOf
|
layout.BaseOf
|
||||||
Scope domain.Scopes
|
Scope domain.Scopes
|
||||||
CodeChallengeMethod domain.CodeChallengeMethod
|
CodeChallengeMethod challenge.Method
|
||||||
ResponseType response.Type
|
ResponseType response.Type
|
||||||
Client *domain.Client
|
Client *domain.Client
|
||||||
Me *domain.Me
|
Me *domain.Me
|
||||||
|
@ -39,74 +40,74 @@ type Authorize struct {
|
||||||
State string
|
State string
|
||||||
}
|
}
|
||||||
|
|
||||||
//line web/template/authorize.qtpl:23
|
//line web/template/authorize.qtpl:24
|
||||||
func (p *Authorize) StreamTitle(qw422016 *qt422016.Writer) {
|
func (p *Authorize) StreamTitle(qw422016 *qt422016.Writer) {
|
||||||
//line web/template/authorize.qtpl:23
|
//line web/template/authorize.qtpl:24
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
`)
|
`)
|
||||||
//line web/template/authorize.qtpl:24
|
//line web/template/authorize.qtpl:25
|
||||||
if p.Client.Name != "" {
|
if p.Client.Name != "" {
|
||||||
//line web/template/authorize.qtpl:24
|
//line web/template/authorize.qtpl:25
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
`)
|
`)
|
||||||
//line web/template/authorize.qtpl:25
|
//line web/template/authorize.qtpl:26
|
||||||
p.StreamT(qw422016, "Authorize %s", p.Client.Name)
|
p.StreamT(qw422016, "Authorize %s", p.Client.Name)
|
||||||
//line web/template/authorize.qtpl:25
|
//line web/template/authorize.qtpl:26
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
`)
|
`)
|
||||||
//line web/template/authorize.qtpl:26
|
//line web/template/authorize.qtpl:27
|
||||||
} else {
|
} else {
|
||||||
//line web/template/authorize.qtpl:26
|
//line web/template/authorize.qtpl:27
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
`)
|
`)
|
||||||
//line web/template/authorize.qtpl:27
|
//line web/template/authorize.qtpl:28
|
||||||
p.StreamT(qw422016, "Authorize application")
|
p.StreamT(qw422016, "Authorize application")
|
||||||
//line web/template/authorize.qtpl:27
|
|
||||||
qw422016.N().S(`
|
|
||||||
`)
|
|
||||||
//line web/template/authorize.qtpl:28
|
|
||||||
}
|
|
||||||
//line web/template/authorize.qtpl:28
|
//line web/template/authorize.qtpl:28
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
`)
|
`)
|
||||||
//line web/template/authorize.qtpl:29
|
//line web/template/authorize.qtpl:29
|
||||||
}
|
}
|
||||||
|
//line web/template/authorize.qtpl:29
|
||||||
|
qw422016.N().S(`
|
||||||
|
`)
|
||||||
|
//line web/template/authorize.qtpl:30
|
||||||
|
}
|
||||||
|
|
||||||
//line web/template/authorize.qtpl:29
|
//line web/template/authorize.qtpl:30
|
||||||
func (p *Authorize) WriteTitle(qq422016 qtio422016.Writer) {
|
func (p *Authorize) WriteTitle(qq422016 qtio422016.Writer) {
|
||||||
//line web/template/authorize.qtpl:29
|
//line web/template/authorize.qtpl:30
|
||||||
qw422016 := qt422016.AcquireWriter(qq422016)
|
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||||
//line web/template/authorize.qtpl:29
|
//line web/template/authorize.qtpl:30
|
||||||
p.StreamTitle(qw422016)
|
p.StreamTitle(qw422016)
|
||||||
//line web/template/authorize.qtpl:29
|
//line web/template/authorize.qtpl:30
|
||||||
qt422016.ReleaseWriter(qw422016)
|
qt422016.ReleaseWriter(qw422016)
|
||||||
//line web/template/authorize.qtpl:29
|
//line web/template/authorize.qtpl:30
|
||||||
}
|
}
|
||||||
|
|
||||||
//line web/template/authorize.qtpl:29
|
//line web/template/authorize.qtpl:30
|
||||||
func (p *Authorize) Title() string {
|
func (p *Authorize) Title() string {
|
||||||
//line web/template/authorize.qtpl:29
|
//line web/template/authorize.qtpl:30
|
||||||
qb422016 := qt422016.AcquireByteBuffer()
|
qb422016 := qt422016.AcquireByteBuffer()
|
||||||
//line web/template/authorize.qtpl:29
|
//line web/template/authorize.qtpl:30
|
||||||
p.WriteTitle(qb422016)
|
p.WriteTitle(qb422016)
|
||||||
//line web/template/authorize.qtpl:29
|
//line web/template/authorize.qtpl:30
|
||||||
qs422016 := string(qb422016.B)
|
qs422016 := string(qb422016.B)
|
||||||
//line web/template/authorize.qtpl:29
|
//line web/template/authorize.qtpl:30
|
||||||
qt422016.ReleaseByteBuffer(qb422016)
|
qt422016.ReleaseByteBuffer(qb422016)
|
||||||
//line web/template/authorize.qtpl:29
|
//line web/template/authorize.qtpl:30
|
||||||
return qs422016
|
return qs422016
|
||||||
//line web/template/authorize.qtpl:29
|
//line web/template/authorize.qtpl:30
|
||||||
}
|
}
|
||||||
|
|
||||||
//line web/template/authorize.qtpl:31
|
//line web/template/authorize.qtpl:32
|
||||||
func (p *Authorize) StreamBody(qw422016 *qt422016.Writer) {
|
func (p *Authorize) StreamBody(qw422016 *qt422016.Writer) {
|
||||||
//line web/template/authorize.qtpl:31
|
//line web/template/authorize.qtpl:32
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
<header>
|
<header>
|
||||||
`)
|
`)
|
||||||
//line web/template/authorize.qtpl:33
|
//line web/template/authorize.qtpl:34
|
||||||
if p.Client.Logo != nil {
|
if p.Client.Logo != nil {
|
||||||
//line web/template/authorize.qtpl:33
|
//line web/template/authorize.qtpl:34
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
<img class=""
|
<img class=""
|
||||||
crossorigin="anonymous"
|
crossorigin="anonymous"
|
||||||
|
@ -116,73 +117,73 @@ func (p *Authorize) StreamBody(qw422016 *qt422016.Writer) {
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
referrerpolicy="no-referrer-when-downgrade"
|
referrerpolicy="no-referrer-when-downgrade"
|
||||||
src="`)
|
src="`)
|
||||||
//line web/template/authorize.qtpl:41
|
//line web/template/authorize.qtpl:42
|
||||||
qw422016.E().S(p.Client.Logo.String())
|
qw422016.E().S(p.Client.Logo.String())
|
||||||
//line web/template/authorize.qtpl:41
|
//line web/template/authorize.qtpl:42
|
||||||
qw422016.N().S(`"
|
qw422016.N().S(`"
|
||||||
alt="`)
|
alt="`)
|
||||||
//line web/template/authorize.qtpl:42
|
//line web/template/authorize.qtpl:43
|
||||||
qw422016.E().S(p.Client.Name)
|
qw422016.E().S(p.Client.Name)
|
||||||
//line web/template/authorize.qtpl:42
|
//line web/template/authorize.qtpl:43
|
||||||
qw422016.N().S(`"
|
qw422016.N().S(`"
|
||||||
width="140">
|
width="140">
|
||||||
`)
|
`)
|
||||||
//line web/template/authorize.qtpl:44
|
//line web/template/authorize.qtpl:45
|
||||||
}
|
}
|
||||||
//line web/template/authorize.qtpl:44
|
//line web/template/authorize.qtpl:45
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
|
|
||||||
<h2>
|
<h2>
|
||||||
`)
|
`)
|
||||||
//line web/template/authorize.qtpl:47
|
//line web/template/authorize.qtpl:48
|
||||||
if p.Client.URL != nil {
|
if p.Client.URL != nil {
|
||||||
//line web/template/authorize.qtpl:47
|
//line web/template/authorize.qtpl:48
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
<a href="`)
|
<a href="`)
|
||||||
//line web/template/authorize.qtpl:48
|
//line web/template/authorize.qtpl:49
|
||||||
qw422016.E().S(p.Client.URL.String())
|
qw422016.E().S(p.Client.URL.String())
|
||||||
//line web/template/authorize.qtpl:48
|
//line web/template/authorize.qtpl:49
|
||||||
qw422016.N().S(`">
|
qw422016.N().S(`">
|
||||||
`)
|
`)
|
||||||
//line web/template/authorize.qtpl:49
|
//line web/template/authorize.qtpl:50
|
||||||
}
|
}
|
||||||
//line web/template/authorize.qtpl:49
|
//line web/template/authorize.qtpl:50
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
`)
|
`)
|
||||||
//line web/template/authorize.qtpl:50
|
//line web/template/authorize.qtpl:51
|
||||||
if p.Client.Name != "" {
|
if p.Client.Name != "" {
|
||||||
//line web/template/authorize.qtpl:50
|
//line web/template/authorize.qtpl:51
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
`)
|
`)
|
||||||
//line web/template/authorize.qtpl:51
|
//line web/template/authorize.qtpl:52
|
||||||
qw422016.E().S(p.Client.Name)
|
qw422016.E().S(p.Client.Name)
|
||||||
//line web/template/authorize.qtpl:51
|
//line web/template/authorize.qtpl:52
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
`)
|
`)
|
||||||
//line web/template/authorize.qtpl:52
|
//line web/template/authorize.qtpl:53
|
||||||
} else {
|
} else {
|
||||||
//line web/template/authorize.qtpl:52
|
//line web/template/authorize.qtpl:53
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
`)
|
`)
|
||||||
//line web/template/authorize.qtpl:53
|
//line web/template/authorize.qtpl:54
|
||||||
qw422016.E().S(p.Client.ID.String())
|
qw422016.E().S(p.Client.ID.String())
|
||||||
//line web/template/authorize.qtpl:53
|
//line web/template/authorize.qtpl:54
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
`)
|
`)
|
||||||
//line web/template/authorize.qtpl:54
|
//line web/template/authorize.qtpl:55
|
||||||
}
|
}
|
||||||
//line web/template/authorize.qtpl:54
|
//line web/template/authorize.qtpl:55
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
`)
|
`)
|
||||||
//line web/template/authorize.qtpl:55
|
//line web/template/authorize.qtpl:56
|
||||||
if p.Client.URL != nil {
|
if p.Client.URL != nil {
|
||||||
//line web/template/authorize.qtpl:55
|
//line web/template/authorize.qtpl:56
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
</a>
|
</a>
|
||||||
`)
|
`)
|
||||||
//line web/template/authorize.qtpl:57
|
//line web/template/authorize.qtpl:58
|
||||||
}
|
}
|
||||||
//line web/template/authorize.qtpl:57
|
//line web/template/authorize.qtpl:58
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
</h2>
|
</h2>
|
||||||
</header>
|
</header>
|
||||||
|
@ -190,9 +191,9 @@ func (p *Authorize) StreamBody(qw422016 *qt422016.Writer) {
|
||||||
<main>
|
<main>
|
||||||
<aside>
|
<aside>
|
||||||
`)
|
`)
|
||||||
//line web/template/authorize.qtpl:63
|
//line web/template/authorize.qtpl:64
|
||||||
if p.CodeChallengeMethod != domain.CodeChallengeMethodUnd && p.CodeChallenge != "" {
|
if p.CodeChallengeMethod != challenge.Und && p.CodeChallenge != "" {
|
||||||
//line web/template/authorize.qtpl:63
|
//line web/template/authorize.qtpl:64
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
<p class="with-icon">
|
<p class="with-icon">
|
||||||
<span class="icon"
|
<span class="icon"
|
||||||
|
@ -200,16 +201,16 @@ func (p *Authorize) StreamBody(qw422016 *qt422016.Writer) {
|
||||||
aria-label="closed lock with key">🔐</span>
|
aria-label="closed lock with key">🔐</span>
|
||||||
|
|
||||||
`)
|
`)
|
||||||
//line web/template/authorize.qtpl:69
|
//line web/template/authorize.qtpl:70
|
||||||
p.StreamT(qw422016, `This client uses %sPKCE%s with the %s%s%s method.`, `<abbr title="Proof of Key Code Exchange">`,
|
p.StreamT(qw422016, `This client uses %sPKCE%s with the %s%s%s method.`, `<abbr title="Proof of Key Code Exchange">`,
|
||||||
`</abbr>`, `<code>`, p.CodeChallengeMethod, `</code>`)
|
`</abbr>`, `<code>`, p.CodeChallengeMethod, `</code>`)
|
||||||
//line web/template/authorize.qtpl:70
|
//line web/template/authorize.qtpl:71
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
</p>
|
</p>
|
||||||
`)
|
`)
|
||||||
//line web/template/authorize.qtpl:72
|
//line web/template/authorize.qtpl:73
|
||||||
} else {
|
} else {
|
||||||
//line web/template/authorize.qtpl:72
|
//line web/template/authorize.qtpl:73
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
<details>
|
<details>
|
||||||
<summary class="with-icon">
|
<summary class="with-icon">
|
||||||
|
@ -218,26 +219,26 @@ func (p *Authorize) StreamBody(qw422016 *qt422016.Writer) {
|
||||||
aria-label="unlock">🔓</span>
|
aria-label="unlock">🔓</span>
|
||||||
|
|
||||||
`)
|
`)
|
||||||
//line web/template/authorize.qtpl:79
|
//line web/template/authorize.qtpl:80
|
||||||
p.StreamT(qw422016, `This client does not use %sPKCE%s!`, `<abbr title="Proof of Key Code Exchange">`, `</abbr>`)
|
p.StreamT(qw422016, `This client does not use %sPKCE%s!`, `<abbr title="Proof of Key Code Exchange">`, `</abbr>`)
|
||||||
//line web/template/authorize.qtpl:79
|
//line web/template/authorize.qtpl:80
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
</summary>
|
</summary>
|
||||||
<p>
|
<p>
|
||||||
`)
|
`)
|
||||||
//line web/template/authorize.qtpl:82
|
//line web/template/authorize.qtpl:83
|
||||||
p.StreamT(qw422016, `%sProof of Key Code Exchange%s is a mechanism that protects against attackers in the middle hijacking `+
|
p.StreamT(qw422016, `%sProof of Key Code Exchange%s is a mechanism that protects against attackers in the middle hijacking `+
|
||||||
`your application's authentication process. You can still authorize this application without this protection, `+
|
`your application's authentication process. You can still authorize this application without this protection, `+
|
||||||
`but you must independently verify the security of this connection. If you have any doubts - stop the process `+
|
`but you must independently verify the security of this connection. If you have any doubts - stop the process `+
|
||||||
` and contact the developers.`, `<dfn id="PKCE">`, `</dfn>`)
|
` and contact the developers.`, `<dfn id="PKCE">`, `</dfn>`)
|
||||||
//line web/template/authorize.qtpl:85
|
//line web/template/authorize.qtpl:86
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
</p>
|
</p>
|
||||||
</details>
|
</details>
|
||||||
`)
|
`)
|
||||||
//line web/template/authorize.qtpl:88
|
//line web/template/authorize.qtpl:89
|
||||||
}
|
}
|
||||||
//line web/template/authorize.qtpl:88
|
//line web/template/authorize.qtpl:89
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
|
@ -251,215 +252,215 @@ func (p *Authorize) StreamBody(qw422016 *qt422016.Writer) {
|
||||||
target="_self">
|
target="_self">
|
||||||
|
|
||||||
`)
|
`)
|
||||||
//line web/template/authorize.qtpl:100
|
//line web/template/authorize.qtpl:101
|
||||||
if p.CSRF != nil {
|
if p.CSRF != nil {
|
||||||
//line web/template/authorize.qtpl:100
|
//line web/template/authorize.qtpl:101
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
<input type="hidden"
|
<input type="hidden"
|
||||||
name="_csrf"
|
name="_csrf"
|
||||||
value="`)
|
value="`)
|
||||||
//line web/template/authorize.qtpl:103
|
//line web/template/authorize.qtpl:104
|
||||||
qw422016.E().Z(p.CSRF)
|
qw422016.E().Z(p.CSRF)
|
||||||
//line web/template/authorize.qtpl:103
|
//line web/template/authorize.qtpl:104
|
||||||
qw422016.N().S(`">
|
qw422016.N().S(`">
|
||||||
`)
|
`)
|
||||||
//line web/template/authorize.qtpl:104
|
//line web/template/authorize.qtpl:105
|
||||||
}
|
}
|
||||||
//line web/template/authorize.qtpl:104
|
//line web/template/authorize.qtpl:105
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
|
|
||||||
`)
|
`)
|
||||||
//line web/template/authorize.qtpl:106
|
//line web/template/authorize.qtpl:107
|
||||||
for key, val := range map[string]string{
|
for key, val := range map[string]string{
|
||||||
"client_id": p.Client.ID.String(),
|
"client_id": p.Client.ID.String(),
|
||||||
"redirect_uri": p.RedirectURI.String(),
|
"redirect_uri": p.RedirectURI.String(),
|
||||||
"response_type": p.ResponseType.String(),
|
"response_type": p.ResponseType.String(),
|
||||||
"state": p.State,
|
"state": p.State,
|
||||||
} {
|
} {
|
||||||
//line web/template/authorize.qtpl:111
|
//line web/template/authorize.qtpl:112
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
<input type="hidden"
|
<input type="hidden"
|
||||||
name="`)
|
name="`)
|
||||||
//line web/template/authorize.qtpl:113
|
//line web/template/authorize.qtpl:114
|
||||||
qw422016.E().S(key)
|
qw422016.E().S(key)
|
||||||
//line web/template/authorize.qtpl:113
|
//line web/template/authorize.qtpl:114
|
||||||
qw422016.N().S(`"
|
qw422016.N().S(`"
|
||||||
value="`)
|
value="`)
|
||||||
//line web/template/authorize.qtpl:114
|
//line web/template/authorize.qtpl:115
|
||||||
qw422016.E().S(val)
|
qw422016.E().S(val)
|
||||||
//line web/template/authorize.qtpl:114
|
//line web/template/authorize.qtpl:115
|
||||||
qw422016.N().S(`">
|
qw422016.N().S(`">
|
||||||
`)
|
`)
|
||||||
//line web/template/authorize.qtpl:115
|
//line web/template/authorize.qtpl:116
|
||||||
}
|
}
|
||||||
//line web/template/authorize.qtpl:115
|
//line web/template/authorize.qtpl:116
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
|
|
||||||
`)
|
`)
|
||||||
//line web/template/authorize.qtpl:117
|
//line web/template/authorize.qtpl:118
|
||||||
if len(p.Scope) > 0 {
|
if len(p.Scope) > 0 {
|
||||||
//line web/template/authorize.qtpl:117
|
//line web/template/authorize.qtpl:118
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>`)
|
<legend>`)
|
||||||
//line web/template/authorize.qtpl:119
|
//line web/template/authorize.qtpl:120
|
||||||
p.StreamT(qw422016, "Scopes")
|
p.StreamT(qw422016, "Scopes")
|
||||||
//line web/template/authorize.qtpl:119
|
//line web/template/authorize.qtpl:120
|
||||||
qw422016.N().S(`</legend>
|
qw422016.N().S(`</legend>
|
||||||
|
|
||||||
`)
|
`)
|
||||||
//line web/template/authorize.qtpl:121
|
//line web/template/authorize.qtpl:122
|
||||||
for _, scope := range p.Scope {
|
for _, scope := range p.Scope {
|
||||||
//line web/template/authorize.qtpl:121
|
//line web/template/authorize.qtpl:122
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
<div>
|
<div>
|
||||||
<label>
|
<label>
|
||||||
<input type="checkbox"
|
<input type="checkbox"
|
||||||
name="scope[]"
|
name="scope[]"
|
||||||
value="`)
|
value="`)
|
||||||
//line web/template/authorize.qtpl:126
|
//line web/template/authorize.qtpl:127
|
||||||
qw422016.E().S(scope.String())
|
qw422016.E().S(scope.String())
|
||||||
//line web/template/authorize.qtpl:126
|
//line web/template/authorize.qtpl:127
|
||||||
qw422016.N().S(`"
|
qw422016.N().S(`"
|
||||||
checked>
|
checked>
|
||||||
|
|
||||||
`)
|
`)
|
||||||
//line web/template/authorize.qtpl:129
|
//line web/template/authorize.qtpl:130
|
||||||
qw422016.E().S(scope.String())
|
qw422016.E().S(scope.String())
|
||||||
//line web/template/authorize.qtpl:129
|
//line web/template/authorize.qtpl:130
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
`)
|
`)
|
||||||
//line web/template/authorize.qtpl:132
|
//line web/template/authorize.qtpl:133
|
||||||
}
|
}
|
||||||
//line web/template/authorize.qtpl:132
|
//line web/template/authorize.qtpl:133
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
</fieldset>
|
</fieldset>
|
||||||
`)
|
`)
|
||||||
//line web/template/authorize.qtpl:134
|
//line web/template/authorize.qtpl:135
|
||||||
} else {
|
} else {
|
||||||
//line web/template/authorize.qtpl:134
|
//line web/template/authorize.qtpl:135
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
<aside>
|
<aside>
|
||||||
<p>`)
|
<p>`)
|
||||||
//line web/template/authorize.qtpl:136
|
//line web/template/authorize.qtpl:137
|
||||||
p.StreamT(qw422016, `No scopes is requested: the application will only get your profile URL.`)
|
p.StreamT(qw422016, `No scopes is requested: the application will only get your profile URL.`)
|
||||||
//line web/template/authorize.qtpl:136
|
//line web/template/authorize.qtpl:137
|
||||||
qw422016.N().S(`</p>
|
qw422016.N().S(`</p>
|
||||||
</aside>
|
</aside>
|
||||||
`)
|
`)
|
||||||
//line web/template/authorize.qtpl:138
|
//line web/template/authorize.qtpl:139
|
||||||
}
|
}
|
||||||
//line web/template/authorize.qtpl:138
|
//line web/template/authorize.qtpl:139
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
|
|
||||||
`)
|
`)
|
||||||
//line web/template/authorize.qtpl:140
|
//line web/template/authorize.qtpl:141
|
||||||
if p.CodeChallenge != "" {
|
if p.CodeChallenge != "" {
|
||||||
//line web/template/authorize.qtpl:140
|
//line web/template/authorize.qtpl:141
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
`)
|
`)
|
||||||
//line web/template/authorize.qtpl:141
|
//line web/template/authorize.qtpl:142
|
||||||
for key, val := range map[string]string{
|
for key, val := range map[string]string{
|
||||||
"code_challenge": p.CodeChallenge,
|
"code_challenge": p.CodeChallenge,
|
||||||
"code_challenge_method": p.CodeChallengeMethod.String(),
|
"code_challenge_method": p.CodeChallengeMethod.String(),
|
||||||
} {
|
} {
|
||||||
//line web/template/authorize.qtpl:144
|
//line web/template/authorize.qtpl:145
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
<input type="hidden"
|
<input type="hidden"
|
||||||
name="`)
|
name="`)
|
||||||
//line web/template/authorize.qtpl:146
|
//line web/template/authorize.qtpl:147
|
||||||
qw422016.E().S(key)
|
qw422016.E().S(key)
|
||||||
//line web/template/authorize.qtpl:146
|
//line web/template/authorize.qtpl:147
|
||||||
qw422016.N().S(`"
|
qw422016.N().S(`"
|
||||||
value="`)
|
value="`)
|
||||||
//line web/template/authorize.qtpl:147
|
//line web/template/authorize.qtpl:148
|
||||||
qw422016.E().S(val)
|
qw422016.E().S(val)
|
||||||
//line web/template/authorize.qtpl:147
|
//line web/template/authorize.qtpl:148
|
||||||
qw422016.N().S(`">
|
qw422016.N().S(`">
|
||||||
`)
|
`)
|
||||||
//line web/template/authorize.qtpl:148
|
//line web/template/authorize.qtpl:149
|
||||||
}
|
}
|
||||||
//line web/template/authorize.qtpl:148
|
//line web/template/authorize.qtpl:149
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
`)
|
`)
|
||||||
//line web/template/authorize.qtpl:149
|
//line web/template/authorize.qtpl:150
|
||||||
}
|
}
|
||||||
//line web/template/authorize.qtpl:149
|
//line web/template/authorize.qtpl:150
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
|
|
||||||
`)
|
`)
|
||||||
//line web/template/authorize.qtpl:151
|
//line web/template/authorize.qtpl:152
|
||||||
if p.Me != nil {
|
if p.Me != nil {
|
||||||
//line web/template/authorize.qtpl:151
|
//line web/template/authorize.qtpl:152
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
<input type="hidden"
|
<input type="hidden"
|
||||||
name="me"
|
name="me"
|
||||||
value="`)
|
value="`)
|
||||||
//line web/template/authorize.qtpl:154
|
//line web/template/authorize.qtpl:155
|
||||||
qw422016.E().S(p.Me.String())
|
qw422016.E().S(p.Me.String())
|
||||||
//line web/template/authorize.qtpl:154
|
//line web/template/authorize.qtpl:155
|
||||||
qw422016.N().S(`">
|
qw422016.N().S(`">
|
||||||
`)
|
`)
|
||||||
//line web/template/authorize.qtpl:155
|
//line web/template/authorize.qtpl:156
|
||||||
}
|
}
|
||||||
//line web/template/authorize.qtpl:155
|
//line web/template/authorize.qtpl:156
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
|
|
||||||
`)
|
`)
|
||||||
//line web/template/authorize.qtpl:157
|
//line web/template/authorize.qtpl:158
|
||||||
if len(p.Providers) > 0 {
|
if len(p.Providers) > 0 {
|
||||||
//line web/template/authorize.qtpl:157
|
//line web/template/authorize.qtpl:158
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
<select name="provider"
|
<select name="provider"
|
||||||
autocomplete
|
autocomplete
|
||||||
required>
|
required>
|
||||||
|
|
||||||
`)
|
`)
|
||||||
//line web/template/authorize.qtpl:162
|
//line web/template/authorize.qtpl:163
|
||||||
for _, provider := range p.Providers {
|
for _, provider := range p.Providers {
|
||||||
//line web/template/authorize.qtpl:162
|
//line web/template/authorize.qtpl:163
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
<option value="`)
|
<option value="`)
|
||||||
//line web/template/authorize.qtpl:163
|
//line web/template/authorize.qtpl:164
|
||||||
qw422016.E().S(provider.UID)
|
qw422016.E().S(provider.UID)
|
||||||
//line web/template/authorize.qtpl:163
|
//line web/template/authorize.qtpl:164
|
||||||
qw422016.N().S(`"
|
qw422016.N().S(`"
|
||||||
`)
|
`)
|
||||||
//line web/template/authorize.qtpl:164
|
//line web/template/authorize.qtpl:165
|
||||||
if provider.UID == "mastodon" {
|
if provider.UID == "mastodon" {
|
||||||
//line web/template/authorize.qtpl:164
|
//line web/template/authorize.qtpl:165
|
||||||
qw422016.N().S(`selected`)
|
qw422016.N().S(`selected`)
|
||||||
//line web/template/authorize.qtpl:164
|
//line web/template/authorize.qtpl:165
|
||||||
}
|
}
|
||||||
//line web/template/authorize.qtpl:164
|
//line web/template/authorize.qtpl:165
|
||||||
qw422016.N().S(`>
|
qw422016.N().S(`>
|
||||||
|
|
||||||
`)
|
`)
|
||||||
//line web/template/authorize.qtpl:166
|
//line web/template/authorize.qtpl:167
|
||||||
qw422016.E().S(provider.Name)
|
qw422016.E().S(provider.Name)
|
||||||
//line web/template/authorize.qtpl:166
|
//line web/template/authorize.qtpl:167
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
</option>
|
</option>
|
||||||
`)
|
`)
|
||||||
//line web/template/authorize.qtpl:168
|
//line web/template/authorize.qtpl:169
|
||||||
}
|
}
|
||||||
//line web/template/authorize.qtpl:168
|
//line web/template/authorize.qtpl:169
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
</select>
|
</select>
|
||||||
`)
|
`)
|
||||||
//line web/template/authorize.qtpl:170
|
//line web/template/authorize.qtpl:171
|
||||||
} else {
|
} else {
|
||||||
//line web/template/authorize.qtpl:170
|
//line web/template/authorize.qtpl:171
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
<input type="hidden"
|
<input type="hidden"
|
||||||
name="provider"
|
name="provider"
|
||||||
value="direct">
|
value="direct">
|
||||||
`)
|
`)
|
||||||
//line web/template/authorize.qtpl:174
|
//line web/template/authorize.qtpl:175
|
||||||
}
|
}
|
||||||
//line web/template/authorize.qtpl:174
|
//line web/template/authorize.qtpl:175
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
|
|
||||||
<button type="submit"
|
<button type="submit"
|
||||||
|
@ -467,9 +468,9 @@ func (p *Authorize) StreamBody(qw422016 *qt422016.Writer) {
|
||||||
value="deny">
|
value="deny">
|
||||||
|
|
||||||
`)
|
`)
|
||||||
//line web/template/authorize.qtpl:180
|
//line web/template/authorize.qtpl:181
|
||||||
p.StreamT(qw422016, "Deny")
|
p.StreamT(qw422016, "Deny")
|
||||||
//line web/template/authorize.qtpl:180
|
//line web/template/authorize.qtpl:181
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
@ -478,47 +479,47 @@ func (p *Authorize) StreamBody(qw422016 *qt422016.Writer) {
|
||||||
value="allow">
|
value="allow">
|
||||||
|
|
||||||
`)
|
`)
|
||||||
//line web/template/authorize.qtpl:187
|
//line web/template/authorize.qtpl:188
|
||||||
p.StreamT(qw422016, "Allow")
|
p.StreamT(qw422016, "Allow")
|
||||||
//line web/template/authorize.qtpl:187
|
//line web/template/authorize.qtpl:188
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<aside>
|
<aside>
|
||||||
<p>`)
|
<p>`)
|
||||||
//line web/template/authorize.qtpl:191
|
//line web/template/authorize.qtpl:192
|
||||||
p.StreamT(qw422016, `You will be redirected to %s%s%s`, `<code>`, p.RedirectURI, `</code>`)
|
p.StreamT(qw422016, `You will be redirected to %s%s%s`, `<code>`, p.RedirectURI, `</code>`)
|
||||||
//line web/template/authorize.qtpl:191
|
//line web/template/authorize.qtpl:192
|
||||||
qw422016.N().S(`</p>
|
qw422016.N().S(`</p>
|
||||||
</aside>
|
</aside>
|
||||||
</form>
|
</form>
|
||||||
</main>
|
</main>
|
||||||
`)
|
`)
|
||||||
//line web/template/authorize.qtpl:195
|
//line web/template/authorize.qtpl:196
|
||||||
}
|
}
|
||||||
|
|
||||||
//line web/template/authorize.qtpl:195
|
//line web/template/authorize.qtpl:196
|
||||||
func (p *Authorize) WriteBody(qq422016 qtio422016.Writer) {
|
func (p *Authorize) WriteBody(qq422016 qtio422016.Writer) {
|
||||||
//line web/template/authorize.qtpl:195
|
//line web/template/authorize.qtpl:196
|
||||||
qw422016 := qt422016.AcquireWriter(qq422016)
|
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||||
//line web/template/authorize.qtpl:195
|
//line web/template/authorize.qtpl:196
|
||||||
p.StreamBody(qw422016)
|
p.StreamBody(qw422016)
|
||||||
//line web/template/authorize.qtpl:195
|
//line web/template/authorize.qtpl:196
|
||||||
qt422016.ReleaseWriter(qw422016)
|
qt422016.ReleaseWriter(qw422016)
|
||||||
//line web/template/authorize.qtpl:195
|
//line web/template/authorize.qtpl:196
|
||||||
}
|
}
|
||||||
|
|
||||||
//line web/template/authorize.qtpl:195
|
//line web/template/authorize.qtpl:196
|
||||||
func (p *Authorize) Body() string {
|
func (p *Authorize) Body() string {
|
||||||
//line web/template/authorize.qtpl:195
|
//line web/template/authorize.qtpl:196
|
||||||
qb422016 := qt422016.AcquireByteBuffer()
|
qb422016 := qt422016.AcquireByteBuffer()
|
||||||
//line web/template/authorize.qtpl:195
|
//line web/template/authorize.qtpl:196
|
||||||
p.WriteBody(qb422016)
|
p.WriteBody(qb422016)
|
||||||
//line web/template/authorize.qtpl:195
|
//line web/template/authorize.qtpl:196
|
||||||
qs422016 := string(qb422016.B)
|
qs422016 := string(qb422016.B)
|
||||||
//line web/template/authorize.qtpl:195
|
//line web/template/authorize.qtpl:196
|
||||||
qt422016.ReleaseByteBuffer(qb422016)
|
qt422016.ReleaseByteBuffer(qb422016)
|
||||||
//line web/template/authorize.qtpl:195
|
//line web/template/authorize.qtpl:196
|
||||||
return qs422016
|
return qs422016
|
||||||
//line web/template/authorize.qtpl:195
|
//line web/template/authorize.qtpl:196
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user