🚚 Moved code challenge method into separated package

This commit is contained in:
Maxim Lebedev 2024-05-06 20:58:14 +05:00
parent cba81b5ac4
commit 71c8373eb4
Signed by: toby3d
GPG Key ID: 1F14E25B7C119FC5
18 changed files with 559 additions and 550 deletions

View File

@ -6,6 +6,7 @@ import (
"strings"
"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/response"
"source.toby3d.me/toby3d/form"
@ -24,7 +25,7 @@ type (
Me domain.Me `form:"me"`
// 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
// code should be returned as the response.
@ -48,16 +49,16 @@ type (
}
AuthVerifyRequest struct {
ClientID domain.ClientID `form:"client_id"`
Me domain.Me `form:"me"`
RedirectURI domain.URL `form:"redirect_uri"`
CodeChallengeMethod domain.CodeChallengeMethod `form:"code_challenge_method,omitempty"`
ResponseType response.Type `form:"response_type"`
Authorize string `form:"authorize"`
CodeChallenge string `form:"code_challenge,omitempty"`
State string `form:"state"`
Provider string `form:"provider"`
Scope domain.Scopes `form:"scope[],omitempty"`
ClientID domain.ClientID `form:"client_id"`
Me domain.Me `form:"me"`
RedirectURI domain.URL `form:"redirect_uri"`
CodeChallengeMethod challenge.Method `form:"code_challenge_method,omitempty"`
ResponseType response.Type `form:"response_type"`
Authorize string `form:"authorize"`
CodeChallenge string `form:"code_challenge,omitempty"`
State string `form:"state"`
Provider string `form:"provider"`
Scope domain.Scopes `form:"scope[],omitempty"`
}
AuthExchangeRequest struct {
@ -97,7 +98,7 @@ func NewAuthAuthorizationRequest() *AuthAuthorizationRequest {
return &AuthAuthorizationRequest{
ClientID: domain.ClientID{},
CodeChallenge: "",
CodeChallengeMethod: domain.CodeChallengeMethodUnd,
CodeChallengeMethod: challenge.Und,
Me: domain.Me{},
RedirectURI: domain.URL{},
ResponseType: response.Und,
@ -131,7 +132,7 @@ func NewAuthVerifyRequest() *AuthVerifyRequest {
Authorize: "",
ClientID: domain.ClientID{},
CodeChallenge: "",
CodeChallengeMethod: domain.CodeChallengeMethodUnd,
CodeChallengeMethod: challenge.Und,
Me: domain.Me{},
Provider: "",
RedirectURI: domain.URL{},

View File

@ -19,6 +19,7 @@ import (
clientrepo "source.toby3d.me/toby3d/auth/internal/client/repository/memory"
clientucase "source.toby3d.me/toby3d/auth/internal/client/usecase"
"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/profile"
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{
"client_id": client.ID.String(),
"code_challenge": "OfYAxt8zU2dAPDWQxTAUIteRzMsoj9QBdMIVEDOErUo",
"code_challenge_method": domain.CodeChallengeMethodS256.String(),
"code_challenge_method": challenge.S256.String(),
"me": me.String(),
"redirect_uri": client.RedirectURI[0].String(),
"response_type": response.Code.String(),

View File

@ -5,6 +5,7 @@ import (
"net/url"
"source.toby3d.me/toby3d/auth/internal/domain"
"source.toby3d.me/toby3d/auth/internal/domain/challenge"
)
type (
@ -12,7 +13,7 @@ type (
ClientID domain.ClientID
Me domain.Me
RedirectURI *url.URL
CodeChallengeMethod domain.CodeChallengeMethod
CodeChallengeMethod challenge.Method
CodeChallenge string
Scope domain.Scopes
}

View File

@ -6,6 +6,7 @@ import (
"source.toby3d.me/toby3d/auth/internal/auth"
"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/random"
"source.toby3d.me/toby3d/auth/internal/session"
@ -76,7 +77,7 @@ func (uc *authUseCase) Exchange(ctx context.Context, opts auth.ExchangeOptions)
}
if session.CodeChallenge != "" &&
session.CodeChallengeMethod != domain.CodeChallengeMethodUnd &&
session.CodeChallengeMethod != challenge.Und &&
!session.CodeChallengeMethod.Validate(session.CodeChallenge, opts.CodeVerifier) {
return nil, nil, auth.ErrMismatchPKCE
}

View File

@ -15,6 +15,7 @@ import (
"source.toby3d.me/toby3d/auth/internal/client"
"source.toby3d.me/toby3d/auth/internal/common"
"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/response"
)
@ -22,23 +23,23 @@ import (
type (
//nolint:tagliatelle,lll
Response struct {
TicketEndpoint domain.URL `json:"ticket_endpoint"`
AuthorizationEndpoint domain.URL `json:"authorization_endpoint"`
IntrospectionEndpoint domain.URL `json:"introspection_endpoint"`
RevocationEndpoint domain.URL `json:"revocation_endpoint,omitempty"`
ServiceDocumentation domain.URL `json:"service_documentation,omitempty"`
TokenEndpoint domain.URL `json:"token_endpoint"`
UserinfoEndpoint domain.URL `json:"userinfo_endpoint,omitempty"`
Microsub domain.URL `json:"microsub"`
Issuer domain.URL `json:"issuer"`
Micropub domain.URL `json:"micropub"`
GrantTypesSupported []grant.Type `json:"grant_types_supported,omitempty"`
IntrospectionEndpointAuthMethodsSupported []string `json:"introspection_endpoint_auth_methods_supported,omitempty"`
RevocationEndpointAuthMethodsSupported []string `json:"revocation_endpoint_auth_methods_supported,omitempty"`
ScopesSupported []domain.Scope `json:"scopes_supported,omitempty"`
ResponseTypesSupported []response.Type `json:"response_types_supported,omitempty"`
CodeChallengeMethodsSupported []domain.CodeChallengeMethod `json:"code_challenge_methods_supported"`
AuthorizationResponseIssParameterSupported bool `json:"authorization_response_iss_parameter_supported,omitempty"`
TicketEndpoint domain.URL `json:"ticket_endpoint"`
AuthorizationEndpoint domain.URL `json:"authorization_endpoint"`
IntrospectionEndpoint domain.URL `json:"introspection_endpoint"`
RevocationEndpoint domain.URL `json:"revocation_endpoint,omitempty"`
ServiceDocumentation domain.URL `json:"service_documentation,omitempty"`
TokenEndpoint domain.URL `json:"token_endpoint"`
UserinfoEndpoint domain.URL `json:"userinfo_endpoint,omitempty"`
Microsub domain.URL `json:"microsub"`
Issuer domain.URL `json:"issuer"`
Micropub domain.URL `json:"micropub"`
GrantTypesSupported []grant.Type `json:"grant_types_supported,omitempty"`
IntrospectionEndpointAuthMethodsSupported []string `json:"introspection_endpoint_auth_methods_supported,omitempty"`
RevocationEndpointAuthMethodsSupported []string `json:"revocation_endpoint_auth_methods_supported,omitempty"`
ScopesSupported []domain.Scope `json:"scopes_supported,omitempty"`
ResponseTypesSupported []response.Type `json:"response_types_supported,omitempty"`
CodeChallengeMethodsSupported []challenge.Method `json:"code_challenge_methods_supported"`
AuthorizationResponseIssParameterSupported bool `json:"authorization_response_iss_parameter_supported,omitempty"`
}
httpClientRepository struct {

View 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))
}

View 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)
}
}

View File

@ -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))
}

View File

@ -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)
}
}

View File

@ -4,6 +4,7 @@ import (
"net/url"
"testing"
"source.toby3d.me/toby3d/auth/internal/domain/challenge"
"source.toby3d.me/toby3d/auth/internal/domain/grant"
"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
// 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.
IntrospectionEndpointAuthMethodsSupported []string // ["Bearer"]
@ -119,12 +120,12 @@ func TestMetadata(tb testing.TB) *Metadata {
grant.AuthorizationCode,
grant.Ticket,
},
CodeChallengeMethodsSupported: []CodeChallengeMethod{
CodeChallengeMethodMD5,
CodeChallengeMethodPLAIN,
CodeChallengeMethodS1,
CodeChallengeMethodS256,
CodeChallengeMethodS512,
CodeChallengeMethodsSupported: []challenge.Method{
challenge.MD5,
challenge.PLAIN,
challenge.S1,
challenge.S256,
challenge.S512,
},
IntrospectionEndpointAuthMethodsSupported: []string{"Bearer"},
RevocationEndpointAuthMethodsSupported: []string{"none"},

View File

@ -4,19 +4,20 @@ import (
"net/url"
"testing"
"source.toby3d.me/toby3d/auth/internal/domain/challenge"
"source.toby3d.me/toby3d/auth/internal/random"
)
//nolint:tagliatelle
type Session struct {
ClientID ClientID `json:"client_id"`
RedirectURI *url.URL `json:"redirect_uri"`
Me Me `json:"me"`
Profile *Profile `json:"profile,omitempty"`
CodeChallengeMethod CodeChallengeMethod `json:"code_challenge_method,omitempty"`
CodeChallenge string `json:"code_challenge,omitempty"`
Code string `json:"-"`
Scope Scopes `json:"scope"`
ClientID ClientID `json:"client_id"`
RedirectURI *url.URL `json:"redirect_uri"`
Me Me `json:"me"`
Profile *Profile `json:"profile,omitempty"`
CodeChallengeMethod challenge.Method `json:"code_challenge_method,omitempty"`
CodeChallenge string `json:"code_challenge,omitempty"`
Code string `json:"-"`
Scope Scopes `json:"scope"`
}
// TestSession returns valid random generated session for tests.
@ -34,7 +35,7 @@ func TestSession(tb testing.TB) *Session {
ClientID: *TestClientID(tb),
Code: code,
CodeChallenge: "hackme",
CodeChallengeMethod: CodeChallengeMethodPLAIN,
CodeChallengeMethod: challenge.PLAIN,
Profile: TestProfile(tb),
Me: *TestMe(tb, "https://user.example.net/"),
RedirectURI: &url.URL{Scheme: "https", Host: "example.com", Path: "/callback"},

View File

@ -12,6 +12,7 @@ import (
"source.toby3d.me/toby3d/auth/internal/common"
"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/response"
"source.toby3d.me/toby3d/auth/internal/metadata"
@ -20,23 +21,23 @@ import (
type (
//nolint:tagliatelle,lll
Response struct {
TicketEndpoint domain.URL `json:"ticket_endpoint"`
AuthorizationEndpoint domain.URL `json:"authorization_endpoint"`
IntrospectionEndpoint domain.URL `json:"introspection_endpoint"`
RevocationEndpoint domain.URL `json:"revocation_endpoint,omitempty"`
ServiceDocumentation domain.URL `json:"service_documentation,omitempty"`
TokenEndpoint domain.URL `json:"token_endpoint"`
UserinfoEndpoint domain.URL `json:"userinfo_endpoint,omitempty"`
Microsub domain.URL `json:"microsub"`
Issuer domain.URL `json:"issuer"`
Micropub domain.URL `json:"micropub"`
GrantTypesSupported []grant.Type `json:"grant_types_supported,omitempty"`
IntrospectionEndpointAuthMethodsSupported []string `json:"introspection_endpoint_auth_methods_supported,omitempty"`
RevocationEndpointAuthMethodsSupported []string `json:"revocation_endpoint_auth_methods_supported,omitempty"`
ScopesSupported []domain.Scope `json:"scopes_supported,omitempty"`
ResponseTypesSupported []response.Type `json:"response_types_supported,omitempty"`
CodeChallengeMethodsSupported []domain.CodeChallengeMethod `json:"code_challenge_methods_supported"`
AuthorizationResponseIssParameterSupported bool `json:"authorization_response_iss_parameter_supported,omitempty"`
TicketEndpoint domain.URL `json:"ticket_endpoint"`
AuthorizationEndpoint domain.URL `json:"authorization_endpoint"`
IntrospectionEndpoint domain.URL `json:"introspection_endpoint"`
RevocationEndpoint domain.URL `json:"revocation_endpoint,omitempty"`
ServiceDocumentation domain.URL `json:"service_documentation,omitempty"`
TokenEndpoint domain.URL `json:"token_endpoint"`
UserinfoEndpoint domain.URL `json:"userinfo_endpoint,omitempty"`
Microsub domain.URL `json:"microsub"`
Issuer domain.URL `json:"issuer"`
Micropub domain.URL `json:"micropub"`
GrantTypesSupported []grant.Type `json:"grant_types_supported,omitempty"`
IntrospectionEndpointAuthMethodsSupported []string `json:"introspection_endpoint_auth_methods_supported,omitempty"`
RevocationEndpointAuthMethodsSupported []string `json:"revocation_endpoint_auth_methods_supported,omitempty"`
ScopesSupported []domain.Scope `json:"scopes_supported,omitempty"`
ResponseTypesSupported []response.Type `json:"response_types_supported,omitempty"`
CodeChallengeMethodsSupported []challenge.Method `json:"code_challenge_methods_supported"`
AuthorizationResponseIssParameterSupported bool `json:"authorization_response_iss_parameter_supported,omitempty"`
}
httpMetadataRepository struct {
@ -119,7 +120,7 @@ func populateBuffer(dst map[string][]string, rel, u string) {
func NewResponse() *Response {
return &Response{
CodeChallengeMethodsSupported: make([]domain.CodeChallengeMethod, 0),
CodeChallengeMethodsSupported: make([]challenge.Method, 0),
GrantTypesSupported: make([]grant.Type, 0),
ResponseTypesSupported: make([]response.Type, 0),
ScopesSupported: make([]domain.Scope, 0),

View File

@ -14,6 +14,7 @@ import (
"source.toby3d.me/toby3d/auth/internal/common"
"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/response"
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(
domain.ClientID{},
domain.CodeChallengeMethod{},
challenge.Und,
grant.Und,
response.Und,
domain.Scope{},

View File

@ -9,6 +9,7 @@ import (
"github.com/lestrrat-go/jwx/v2/jwt"
"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/session"
"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
}
if session.CodeChallenge != "" && session.CodeChallengeMethod != domain.CodeChallengeMethodUnd &&
if session.CodeChallenge != "" && session.CodeChallengeMethod != challenge.Und &&
!session.CodeChallengeMethod.Validate(session.CodeChallenge, opts.CodeVerifier) {
return nil, nil, token.ErrMismatchPKCE
}

View File

@ -15,6 +15,7 @@ import (
"source.toby3d.me/toby3d/auth/internal/common"
"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/response"
"source.toby3d.me/toby3d/auth/internal/user"
@ -23,23 +24,23 @@ import (
type (
//nolint:tagliatelle,lll
MetadataResponse struct {
TicketEndpoint domain.URL `json:"ticket_endpoint"`
AuthorizationEndpoint domain.URL `json:"authorization_endpoint"`
IntrospectionEndpoint domain.URL `json:"introspection_endpoint"`
RevocationEndpoint domain.URL `json:"revocation_endpoint,omitempty"`
ServiceDocumentation domain.URL `json:"service_documentation,omitempty"`
TokenEndpoint domain.URL `json:"token_endpoint"`
UserinfoEndpoint domain.URL `json:"userinfo_endpoint,omitempty"`
Microsub domain.URL `json:"microsub"`
Issuer domain.URL `json:"issuer"`
Micropub domain.URL `json:"micropub"`
GrantTypesSupported []grant.Type `json:"grant_types_supported,omitempty"`
IntrospectionEndpointAuthMethodsSupported []string `json:"introspection_endpoint_auth_methods_supported,omitempty"`
RevocationEndpointAuthMethodsSupported []string `json:"revocation_endpoint_auth_methods_supported,omitempty"`
ScopesSupported []domain.Scope `json:"scopes_supported,omitempty"`
ResponseTypesSupported []response.Type `json:"response_types_supported,omitempty"`
CodeChallengeMethodsSupported []domain.CodeChallengeMethod `json:"code_challenge_methods_supported"`
AuthorizationResponseIssParameterSupported bool `json:"authorization_response_iss_parameter_supported,omitempty"`
TicketEndpoint domain.URL `json:"ticket_endpoint"`
AuthorizationEndpoint domain.URL `json:"authorization_endpoint"`
IntrospectionEndpoint domain.URL `json:"introspection_endpoint"`
RevocationEndpoint domain.URL `json:"revocation_endpoint,omitempty"`
ServiceDocumentation domain.URL `json:"service_documentation,omitempty"`
TokenEndpoint domain.URL `json:"token_endpoint"`
UserinfoEndpoint domain.URL `json:"userinfo_endpoint,omitempty"`
Microsub domain.URL `json:"microsub"`
Issuer domain.URL `json:"issuer"`
Micropub domain.URL `json:"micropub"`
GrantTypesSupported []grant.Type `json:"grant_types_supported,omitempty"`
IntrospectionEndpointAuthMethodsSupported []string `json:"introspection_endpoint_auth_methods_supported,omitempty"`
RevocationEndpointAuthMethodsSupported []string `json:"revocation_endpoint_auth_methods_supported,omitempty"`
ScopesSupported []domain.Scope `json:"scopes_supported,omitempty"`
ResponseTypesSupported []response.Type `json:"response_types_supported,omitempty"`
CodeChallengeMethodsSupported []challenge.Method `json:"code_challenge_methods_supported"`
AuthorizationResponseIssParameterSupported bool `json:"authorization_response_iss_parameter_supported,omitempty"`
}
httpUserRepository struct {

13
main.go
View File

@ -37,6 +37,7 @@ import (
clienthttprepo "source.toby3d.me/toby3d/auth/internal/client/repository/http"
clientucase "source.toby3d.me/toby3d/auth/internal/client/usecase"
"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/response"
healthhttpdelivery "source.toby3d.me/toby3d/auth/internal/health/delivery/http"
@ -295,12 +296,12 @@ func (app *App) Handler() http.Handler {
grant.AuthorizationCode,
grant.Ticket,
},
CodeChallengeMethodsSupported: []domain.CodeChallengeMethod{
domain.CodeChallengeMethodMD5,
domain.CodeChallengeMethodPLAIN,
domain.CodeChallengeMethodS1,
domain.CodeChallengeMethodS256,
domain.CodeChallengeMethodS512,
CodeChallengeMethodsSupported: []challenge.Method{
challenge.MD5,
challenge.PLAIN,
challenge.S1,
challenge.S256,
challenge.S512,
},
AuthorizationResponseIssParameterSupported: true,
})

View File

@ -2,6 +2,7 @@
{% import (
"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/web/template/layout"
) %}
@ -9,7 +10,7 @@
{% code type Authorize struct {
layout.BaseOf
Scope domain.Scopes
CodeChallengeMethod domain.CodeChallengeMethod
CodeChallengeMethod challenge.Method
ResponseType response.Type
Client *domain.Client
Me *domain.Me
@ -60,7 +61,7 @@
<main>
<aside>
{% if p.CodeChallengeMethod != domain.CodeChallengeMethodUnd && p.CodeChallenge != "" %}
{% if p.CodeChallengeMethod != challenge.Und && p.CodeChallenge != "" %}
<p class="with-icon">
<span class="icon"
role="img"

View File

@ -7,28 +7,29 @@ package template
//line web/template/authorize.qtpl:3
import (
"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/web/template/layout"
)
//line web/template/authorize.qtpl:9
//line web/template/authorize.qtpl:10
import (
qtio422016 "io"
qt422016 "github.com/valyala/quicktemplate"
)
//line web/template/authorize.qtpl:9
//line web/template/authorize.qtpl:10
var (
_ = qtio422016.Copy
_ = qt422016.AcquireByteBuffer
)
//line web/template/authorize.qtpl:9
//line web/template/authorize.qtpl:10
type Authorize struct {
layout.BaseOf
Scope domain.Scopes
CodeChallengeMethod domain.CodeChallengeMethod
CodeChallengeMethod challenge.Method
ResponseType response.Type
Client *domain.Client
Me *domain.Me
@ -39,74 +40,74 @@ type Authorize struct {
State string
}
//line web/template/authorize.qtpl:23
//line web/template/authorize.qtpl:24
func (p *Authorize) StreamTitle(qw422016 *qt422016.Writer) {
//line web/template/authorize.qtpl:23
//line web/template/authorize.qtpl:24
qw422016.N().S(`
`)
//line web/template/authorize.qtpl:24
//line web/template/authorize.qtpl:25
if p.Client.Name != "" {
//line web/template/authorize.qtpl:24
//line web/template/authorize.qtpl:25
qw422016.N().S(`
`)
//line web/template/authorize.qtpl:25
//line web/template/authorize.qtpl:26
p.StreamT(qw422016, "Authorize %s", p.Client.Name)
//line web/template/authorize.qtpl:25
//line web/template/authorize.qtpl:26
qw422016.N().S(`
`)
//line web/template/authorize.qtpl:26
//line web/template/authorize.qtpl:27
} else {
//line web/template/authorize.qtpl:26
//line web/template/authorize.qtpl:27
qw422016.N().S(`
`)
//line web/template/authorize.qtpl:27
//line web/template/authorize.qtpl:28
p.StreamT(qw422016, "Authorize application")
//line web/template/authorize.qtpl:27
//line web/template/authorize.qtpl:28
qw422016.N().S(`
`)
//line web/template/authorize.qtpl:28
//line web/template/authorize.qtpl:29
}
//line web/template/authorize.qtpl:28
//line web/template/authorize.qtpl:29
qw422016.N().S(`
`)
//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) WriteTitle(qq422016 qtio422016.Writer) {
//line web/template/authorize.qtpl:29
//line web/template/authorize.qtpl:30
qw422016 := qt422016.AcquireWriter(qq422016)
//line web/template/authorize.qtpl:29
//line web/template/authorize.qtpl:30
p.StreamTitle(qw422016)
//line web/template/authorize.qtpl:29
//line web/template/authorize.qtpl:30
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 {
//line web/template/authorize.qtpl:29
//line web/template/authorize.qtpl:30
qb422016 := qt422016.AcquireByteBuffer()
//line web/template/authorize.qtpl:29
//line web/template/authorize.qtpl:30
p.WriteTitle(qb422016)
//line web/template/authorize.qtpl:29
//line web/template/authorize.qtpl:30
qs422016 := string(qb422016.B)
//line web/template/authorize.qtpl:29
//line web/template/authorize.qtpl:30
qt422016.ReleaseByteBuffer(qb422016)
//line web/template/authorize.qtpl:29
//line web/template/authorize.qtpl:30
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) {
//line web/template/authorize.qtpl:31
//line web/template/authorize.qtpl:32
qw422016.N().S(`
<header>
`)
//line web/template/authorize.qtpl:33
//line web/template/authorize.qtpl:34
if p.Client.Logo != nil {
//line web/template/authorize.qtpl:33
//line web/template/authorize.qtpl:34
qw422016.N().S(`
<img class=""
crossorigin="anonymous"
@ -116,73 +117,73 @@ func (p *Authorize) StreamBody(qw422016 *qt422016.Writer) {
loading="lazy"
referrerpolicy="no-referrer-when-downgrade"
src="`)
//line web/template/authorize.qtpl:41
//line web/template/authorize.qtpl:42
qw422016.E().S(p.Client.Logo.String())
//line web/template/authorize.qtpl:41
//line web/template/authorize.qtpl:42
qw422016.N().S(`"
alt="`)
//line web/template/authorize.qtpl:42
//line web/template/authorize.qtpl:43
qw422016.E().S(p.Client.Name)
//line web/template/authorize.qtpl:42
//line web/template/authorize.qtpl:43
qw422016.N().S(`"
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(`
<h2>
`)
//line web/template/authorize.qtpl:47
//line web/template/authorize.qtpl:48
if p.Client.URL != nil {
//line web/template/authorize.qtpl:47
//line web/template/authorize.qtpl:48
qw422016.N().S(`
<a href="`)
//line web/template/authorize.qtpl:48
//line web/template/authorize.qtpl:49
qw422016.E().S(p.Client.URL.String())
//line web/template/authorize.qtpl:48
//line web/template/authorize.qtpl:49
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(`
`)
//line web/template/authorize.qtpl:50
//line web/template/authorize.qtpl:51
if p.Client.Name != "" {
//line web/template/authorize.qtpl:50
//line web/template/authorize.qtpl:51
qw422016.N().S(`
`)
//line web/template/authorize.qtpl:51
//line web/template/authorize.qtpl:52
qw422016.E().S(p.Client.Name)
//line web/template/authorize.qtpl:51
//line web/template/authorize.qtpl:52
qw422016.N().S(`
`)
//line web/template/authorize.qtpl:52
//line web/template/authorize.qtpl:53
} else {
//line web/template/authorize.qtpl:52
//line web/template/authorize.qtpl:53
qw422016.N().S(`
`)
//line web/template/authorize.qtpl:53
//line web/template/authorize.qtpl:54
qw422016.E().S(p.Client.ID.String())
//line web/template/authorize.qtpl:53
//line web/template/authorize.qtpl:54
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(`
`)
//line web/template/authorize.qtpl:55
//line web/template/authorize.qtpl:56
if p.Client.URL != nil {
//line web/template/authorize.qtpl:55
//line web/template/authorize.qtpl:56
qw422016.N().S(`
</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(`
</h2>
</header>
@ -190,9 +191,9 @@ func (p *Authorize) StreamBody(qw422016 *qt422016.Writer) {
<main>
<aside>
`)
//line web/template/authorize.qtpl:63
if p.CodeChallengeMethod != domain.CodeChallengeMethodUnd && p.CodeChallenge != "" {
//line web/template/authorize.qtpl:63
//line web/template/authorize.qtpl:64
if p.CodeChallengeMethod != challenge.Und && p.CodeChallenge != "" {
//line web/template/authorize.qtpl:64
qw422016.N().S(`
<p class="with-icon">
<span class="icon"
@ -200,16 +201,16 @@ func (p *Authorize) StreamBody(qw422016 *qt422016.Writer) {
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">`,
`</abbr>`, `<code>`, p.CodeChallengeMethod, `</code>`)
//line web/template/authorize.qtpl:70
//line web/template/authorize.qtpl:71
qw422016.N().S(`
</p>
`)
//line web/template/authorize.qtpl:72
//line web/template/authorize.qtpl:73
} else {
//line web/template/authorize.qtpl:72
//line web/template/authorize.qtpl:73
qw422016.N().S(`
<details>
<summary class="with-icon">
@ -218,26 +219,26 @@ func (p *Authorize) StreamBody(qw422016 *qt422016.Writer) {
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>`)
//line web/template/authorize.qtpl:79
//line web/template/authorize.qtpl:80
qw422016.N().S(`
</summary>
<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 `+
`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 `+
` and contact the developers.`, `<dfn id="PKCE">`, `</dfn>`)
//line web/template/authorize.qtpl:85
//line web/template/authorize.qtpl:86
qw422016.N().S(`
</p>
</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(`
</aside>
@ -251,215 +252,215 @@ func (p *Authorize) StreamBody(qw422016 *qt422016.Writer) {
target="_self">
`)
//line web/template/authorize.qtpl:100
//line web/template/authorize.qtpl:101
if p.CSRF != nil {
//line web/template/authorize.qtpl:100
//line web/template/authorize.qtpl:101
qw422016.N().S(`
<input type="hidden"
name="_csrf"
value="`)
//line web/template/authorize.qtpl:103
//line web/template/authorize.qtpl:104
qw422016.E().Z(p.CSRF)
//line web/template/authorize.qtpl:103
//line web/template/authorize.qtpl:104
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(`
`)
//line web/template/authorize.qtpl:106
//line web/template/authorize.qtpl:107
for key, val := range map[string]string{
"client_id": p.Client.ID.String(),
"redirect_uri": p.RedirectURI.String(),
"response_type": p.ResponseType.String(),
"state": p.State,
} {
//line web/template/authorize.qtpl:111
//line web/template/authorize.qtpl:112
qw422016.N().S(`
<input type="hidden"
name="`)
//line web/template/authorize.qtpl:113
//line web/template/authorize.qtpl:114
qw422016.E().S(key)
//line web/template/authorize.qtpl:113
//line web/template/authorize.qtpl:114
qw422016.N().S(`"
value="`)
//line web/template/authorize.qtpl:114
//line web/template/authorize.qtpl:115
qw422016.E().S(val)
//line web/template/authorize.qtpl:114
//line web/template/authorize.qtpl:115
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(`
`)
//line web/template/authorize.qtpl:117
//line web/template/authorize.qtpl:118
if len(p.Scope) > 0 {
//line web/template/authorize.qtpl:117
//line web/template/authorize.qtpl:118
qw422016.N().S(`
<fieldset>
<legend>`)
//line web/template/authorize.qtpl:119
//line web/template/authorize.qtpl:120
p.StreamT(qw422016, "Scopes")
//line web/template/authorize.qtpl:119
//line web/template/authorize.qtpl:120
qw422016.N().S(`</legend>
`)
//line web/template/authorize.qtpl:121
//line web/template/authorize.qtpl:122
for _, scope := range p.Scope {
//line web/template/authorize.qtpl:121
//line web/template/authorize.qtpl:122
qw422016.N().S(`
<div>
<label>
<input type="checkbox"
name="scope[]"
value="`)
//line web/template/authorize.qtpl:126
//line web/template/authorize.qtpl:127
qw422016.E().S(scope.String())
//line web/template/authorize.qtpl:126
//line web/template/authorize.qtpl:127
qw422016.N().S(`"
checked>
`)
//line web/template/authorize.qtpl:129
//line web/template/authorize.qtpl:130
qw422016.E().S(scope.String())
//line web/template/authorize.qtpl:129
//line web/template/authorize.qtpl:130
qw422016.N().S(`
</label>
</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(`
</fieldset>
`)
//line web/template/authorize.qtpl:134
//line web/template/authorize.qtpl:135
} else {
//line web/template/authorize.qtpl:134
//line web/template/authorize.qtpl:135
qw422016.N().S(`
<aside>
<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.`)
//line web/template/authorize.qtpl:136
//line web/template/authorize.qtpl:137
qw422016.N().S(`</p>
</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(`
`)
//line web/template/authorize.qtpl:140
//line web/template/authorize.qtpl:141
if p.CodeChallenge != "" {
//line web/template/authorize.qtpl:140
//line web/template/authorize.qtpl:141
qw422016.N().S(`
`)
//line web/template/authorize.qtpl:141
//line web/template/authorize.qtpl:142
for key, val := range map[string]string{
"code_challenge": p.CodeChallenge,
"code_challenge_method": p.CodeChallengeMethod.String(),
} {
//line web/template/authorize.qtpl:144
//line web/template/authorize.qtpl:145
qw422016.N().S(`
<input type="hidden"
name="`)
//line web/template/authorize.qtpl:146
//line web/template/authorize.qtpl:147
qw422016.E().S(key)
//line web/template/authorize.qtpl:146
//line web/template/authorize.qtpl:147
qw422016.N().S(`"
value="`)
//line web/template/authorize.qtpl:147
//line web/template/authorize.qtpl:148
qw422016.E().S(val)
//line web/template/authorize.qtpl:147
//line web/template/authorize.qtpl:148
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(`
`)
//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(`
`)
//line web/template/authorize.qtpl:151
//line web/template/authorize.qtpl:152
if p.Me != nil {
//line web/template/authorize.qtpl:151
//line web/template/authorize.qtpl:152
qw422016.N().S(`
<input type="hidden"
name="me"
value="`)
//line web/template/authorize.qtpl:154
//line web/template/authorize.qtpl:155
qw422016.E().S(p.Me.String())
//line web/template/authorize.qtpl:154
//line web/template/authorize.qtpl:155
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(`
`)
//line web/template/authorize.qtpl:157
//line web/template/authorize.qtpl:158
if len(p.Providers) > 0 {
//line web/template/authorize.qtpl:157
//line web/template/authorize.qtpl:158
qw422016.N().S(`
<select name="provider"
autocomplete
required>
`)
//line web/template/authorize.qtpl:162
//line web/template/authorize.qtpl:163
for _, provider := range p.Providers {
//line web/template/authorize.qtpl:162
//line web/template/authorize.qtpl:163
qw422016.N().S(`
<option value="`)
//line web/template/authorize.qtpl:163
//line web/template/authorize.qtpl:164
qw422016.E().S(provider.UID)
//line web/template/authorize.qtpl:163
//line web/template/authorize.qtpl:164
qw422016.N().S(`"
`)
//line web/template/authorize.qtpl:164
//line web/template/authorize.qtpl:165
if provider.UID == "mastodon" {
//line web/template/authorize.qtpl:164
//line web/template/authorize.qtpl:165
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(`>
`)
//line web/template/authorize.qtpl:166
//line web/template/authorize.qtpl:167
qw422016.E().S(provider.Name)
//line web/template/authorize.qtpl:166
//line web/template/authorize.qtpl:167
qw422016.N().S(`
</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(`
</select>
`)
//line web/template/authorize.qtpl:170
//line web/template/authorize.qtpl:171
} else {
//line web/template/authorize.qtpl:170
//line web/template/authorize.qtpl:171
qw422016.N().S(`
<input type="hidden"
name="provider"
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(`
<button type="submit"
@ -467,9 +468,9 @@ func (p *Authorize) StreamBody(qw422016 *qt422016.Writer) {
value="deny">
`)
//line web/template/authorize.qtpl:180
//line web/template/authorize.qtpl:181
p.StreamT(qw422016, "Deny")
//line web/template/authorize.qtpl:180
//line web/template/authorize.qtpl:181
qw422016.N().S(`
</button>
@ -478,47 +479,47 @@ func (p *Authorize) StreamBody(qw422016 *qt422016.Writer) {
value="allow">
`)
//line web/template/authorize.qtpl:187
//line web/template/authorize.qtpl:188
p.StreamT(qw422016, "Allow")
//line web/template/authorize.qtpl:187
//line web/template/authorize.qtpl:188
qw422016.N().S(`
</button>
<aside>
<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>`)
//line web/template/authorize.qtpl:191
//line web/template/authorize.qtpl:192
qw422016.N().S(`</p>
</aside>
</form>
</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) {
//line web/template/authorize.qtpl:195
//line web/template/authorize.qtpl:196
qw422016 := qt422016.AcquireWriter(qq422016)
//line web/template/authorize.qtpl:195
//line web/template/authorize.qtpl:196
p.StreamBody(qw422016)
//line web/template/authorize.qtpl:195
//line web/template/authorize.qtpl:196
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 {
//line web/template/authorize.qtpl:195
//line web/template/authorize.qtpl:196
qb422016 := qt422016.AcquireByteBuffer()
//line web/template/authorize.qtpl:195
//line web/template/authorize.qtpl:196
p.WriteBody(qb422016)
//line web/template/authorize.qtpl:195
//line web/template/authorize.qtpl:196
qs422016 := string(qb422016.B)
//line web/template/authorize.qtpl:195
//line web/template/authorize.qtpl:196
qt422016.ReleaseByteBuffer(qb422016)
//line web/template/authorize.qtpl:195
//line web/template/authorize.qtpl:196
return qs422016
//line web/template/authorize.qtpl:195
//line web/template/authorize.qtpl:196
}