🎨 Format source code, rearrange structs fields
This commit is contained in:
parent
e7a128d69e
commit
0585d51f66
|
@ -48,7 +48,7 @@ func NewHandler(opts NewHandlerOptions) *Handler {
|
||||||
func (h *Handler) Handler() http.Handler {
|
func (h *Handler) Handler() http.Handler {
|
||||||
chain := middleware.Chain{
|
chain := middleware.Chain{
|
||||||
middleware.CSRFWithConfig(middleware.CSRFConfig{
|
middleware.CSRFWithConfig(middleware.CSRFConfig{
|
||||||
Skipper: func(w http.ResponseWriter, r *http.Request) bool {
|
Skipper: func(_ http.ResponseWriter, r *http.Request) bool {
|
||||||
head, _ := urlutil.ShiftPath(r.URL.Path)
|
head, _ := urlutil.ShiftPath(r.URL.Path)
|
||||||
|
|
||||||
return head == ""
|
return head == ""
|
||||||
|
@ -65,7 +65,7 @@ func (h *Handler) Handler() http.Handler {
|
||||||
CookieHTTPOnly: true,
|
CookieHTTPOnly: true,
|
||||||
}),
|
}),
|
||||||
middleware.BasicAuthWithConfig(middleware.BasicAuthConfig{
|
middleware.BasicAuthWithConfig(middleware.BasicAuthConfig{
|
||||||
Skipper: func(w http.ResponseWriter, r *http.Request) bool {
|
Skipper: func(_ http.ResponseWriter, r *http.Request) bool {
|
||||||
head, _ := urlutil.ShiftPath(r.URL.Path)
|
head, _ := urlutil.ShiftPath(r.URL.Path)
|
||||||
|
|
||||||
return r.Method != http.MethodPost ||
|
return r.Method != http.MethodPost ||
|
||||||
|
|
|
@ -11,10 +11,6 @@ import (
|
||||||
|
|
||||||
type (
|
type (
|
||||||
AuthAuthorizationRequest struct {
|
AuthAuthorizationRequest struct {
|
||||||
// Indicates to the authorization server that an authorization
|
|
||||||
// code should be returned as the response.
|
|
||||||
ResponseType domain.ResponseType `form:"response_type"` // code
|
|
||||||
|
|
||||||
// The client URL.
|
// The client URL.
|
||||||
ClientID domain.ClientID `form:"client_id"`
|
ClientID domain.ClientID `form:"client_id"`
|
||||||
|
|
||||||
|
@ -28,12 +24,9 @@ type (
|
||||||
// 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 *domain.CodeChallengeMethod `form:"code_challenge_method,omitempty"`
|
||||||
|
|
||||||
// A space-separated list of scopes the client is requesting,
|
// Indicates to the authorization server that an authorization
|
||||||
// e.g. "profile", or "profile create". If the client omits this
|
// code should be returned as the response.
|
||||||
// value, the authorization server MUST NOT issue an access
|
ResponseType domain.ResponseType `form:"response_type"` // code
|
||||||
// token for this authorization code. Only the user's profile
|
|
||||||
// URL may be returned without any scope requested.
|
|
||||||
Scope domain.Scopes `form:"scope,omitempty"`
|
|
||||||
|
|
||||||
// A parameter set by the client which will be included when the
|
// A parameter set by the client which will be included when the
|
||||||
// user is redirected back to the client. This is used to
|
// user is redirected back to the client. This is used to
|
||||||
|
@ -43,19 +36,26 @@ type (
|
||||||
|
|
||||||
// The code challenge as previously described.
|
// The code challenge as previously described.
|
||||||
CodeChallenge string `form:"code_challenge,omitempty"`
|
CodeChallenge string `form:"code_challenge,omitempty"`
|
||||||
|
|
||||||
|
// A space-separated list of scopes the client is requesting,
|
||||||
|
// e.g. "profile", or "profile create". If the client omits this
|
||||||
|
// value, the authorization server MUST NOT issue an access
|
||||||
|
// token for this authorization code. Only the user's profile
|
||||||
|
// URL may be returned without any scope requested.
|
||||||
|
Scope domain.Scopes `form:"scope,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
AuthVerifyRequest struct {
|
AuthVerifyRequest struct {
|
||||||
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"`
|
||||||
ResponseType domain.ResponseType `form:"response_type"`
|
|
||||||
CodeChallengeMethod *domain.CodeChallengeMethod `form:"code_challenge_method,omitempty"`
|
CodeChallengeMethod *domain.CodeChallengeMethod `form:"code_challenge_method,omitempty"`
|
||||||
Scope domain.Scopes `form:"scope[],omitempty"`
|
ResponseType domain.ResponseType `form:"response_type"`
|
||||||
Authorize string `form:"authorize"`
|
Authorize string `form:"authorize"`
|
||||||
CodeChallenge string `form:"code_challenge,omitempty"`
|
CodeChallenge string `form:"code_challenge,omitempty"`
|
||||||
State string `form:"state"`
|
State string `form:"state"`
|
||||||
Provider string `form:"provider"`
|
Provider string `form:"provider"`
|
||||||
|
Scope domain.Scopes `form:"scope[],omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
AuthExchangeRequest struct {
|
AuthExchangeRequest struct {
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
package common
|
package common
|
||||||
|
|
||||||
const charsetUTF8 = "charset=UTF-8"
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
MIMEApplicationForm string = "application/x-www-form-urlencoded"
|
MIMEApplicationForm string = "application/x-www-form-urlencoded"
|
||||||
MIMEApplicationJSON string = "application/json"
|
MIMEApplicationJSON string = "application/json"
|
||||||
|
@ -10,6 +8,8 @@ const (
|
||||||
MIMETextHTMLCharsetUTF8 string = MIMETextHTML + "; " + charsetUTF8
|
MIMETextHTMLCharsetUTF8 string = MIMETextHTML + "; " + charsetUTF8
|
||||||
MIMETextPlain string = "text/plain"
|
MIMETextPlain string = "text/plain"
|
||||||
MIMETextPlainCharsetUTF8 string = MIMETextPlain + "; " + charsetUTF8
|
MIMETextPlainCharsetUTF8 string = MIMETextPlain + "; " + charsetUTF8
|
||||||
|
|
||||||
|
charsetUTF8 = "charset=UTF-8"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
|
@ -12,26 +12,26 @@ import (
|
||||||
// NOTE(toby3d): Encapsulate enums in structs for extra compile-time safety:
|
// NOTE(toby3d): Encapsulate enums in structs for extra compile-time safety:
|
||||||
// https://threedots.tech/post/safer-enums-in-go/#struct-based-enums
|
// https://threedots.tech/post/safer-enums-in-go/#struct-based-enums
|
||||||
type Action struct {
|
type Action struct {
|
||||||
uid string
|
action string
|
||||||
}
|
}
|
||||||
|
|
||||||
//nolint:gochecknoglobals // structs cannot be constants
|
//nolint:gochecknoglobals // structs cannot be constants
|
||||||
var (
|
var (
|
||||||
ActionUnd = Action{uid: ""} // "und"
|
ActionUnd = Action{action: ""} // "und"
|
||||||
|
|
||||||
// ActionRevoke represent action for revoke token.
|
// ActionRevoke represent action for revoke token.
|
||||||
ActionRevoke = Action{uid: "revoke"} // "revoke"
|
ActionRevoke = Action{action: "revoke"} // "revoke"
|
||||||
|
|
||||||
// ActionTicket represent action for TicketAuth extension.
|
// ActionTicket represent action for TicketAuth extension.
|
||||||
ActionTicket = Action{uid: "ticket"} // "ticket"
|
ActionTicket = Action{action: "ticket"} // "ticket"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ErrActionSyntax error = NewError(ErrorCodeInvalidRequest, "unknown action method", "")
|
var ErrActionSyntax error = NewError(ErrorCodeInvalidRequest, "unknown action method", "")
|
||||||
|
|
||||||
//nolint:gochecknoglobals
|
//nolint:gochecknoglobals
|
||||||
var uidsActions = map[string]Action{
|
var uidsActions = map[string]Action{
|
||||||
ActionRevoke.uid: ActionRevoke,
|
ActionRevoke.action: ActionRevoke,
|
||||||
ActionTicket.uid: ActionTicket,
|
ActionTicket.action: ActionTicket,
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseAction parse string identifier of action into struct enum.
|
// ParseAction parse string identifier of action into struct enum.
|
||||||
|
@ -74,8 +74,8 @@ func (a *Action) UnmarshalJSON(v []byte) error {
|
||||||
|
|
||||||
// String returns string representation of action.
|
// String returns string representation of action.
|
||||||
func (a Action) String() string {
|
func (a Action) String() string {
|
||||||
if a.uid != "" {
|
if a.action != "" {
|
||||||
return a.uid
|
return a.action
|
||||||
}
|
}
|
||||||
|
|
||||||
return common.Und
|
return common.Und
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package domain
|
package domain
|
||||||
|
|
||||||
//nolint:gosec // support old clients
|
//nolint:gosec // support old clients
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
"crypto/sha1"
|
"crypto/sha1"
|
||||||
|
@ -20,17 +21,17 @@ import (
|
||||||
// NOTE(toby3d): Encapsulate enums in structs for extra compile-time safety:
|
// NOTE(toby3d): Encapsulate enums in structs for extra compile-time safety:
|
||||||
// https://threedots.tech/post/safer-enums-in-go/#struct-based-enums
|
// https://threedots.tech/post/safer-enums-in-go/#struct-based-enums
|
||||||
type CodeChallengeMethod struct {
|
type CodeChallengeMethod struct {
|
||||||
uid string
|
codeChallengeMethod string
|
||||||
}
|
}
|
||||||
|
|
||||||
//nolint:gochecknoglobals // structs cannot be constants
|
//nolint:gochecknoglobals // structs cannot be constants
|
||||||
var (
|
var (
|
||||||
CodeChallengeMethodUnd = CodeChallengeMethod{uid: ""} // "und"
|
CodeChallengeMethodUnd = CodeChallengeMethod{codeChallengeMethod: ""} // "und"
|
||||||
CodeChallengeMethodPLAIN = CodeChallengeMethod{uid: "plain"} // "PLAIN"
|
CodeChallengeMethodPLAIN = CodeChallengeMethod{codeChallengeMethod: "plain"} // "PLAIN"
|
||||||
CodeChallengeMethodMD5 = CodeChallengeMethod{uid: "md5"} // "MD5"
|
CodeChallengeMethodMD5 = CodeChallengeMethod{codeChallengeMethod: "md5"} // "MD5"
|
||||||
CodeChallengeMethodS1 = CodeChallengeMethod{uid: "s1"} // "S1"
|
CodeChallengeMethodS1 = CodeChallengeMethod{codeChallengeMethod: "s1"} // "S1"
|
||||||
CodeChallengeMethodS256 = CodeChallengeMethod{uid: "s256"} // "S256"
|
CodeChallengeMethodS256 = CodeChallengeMethod{codeChallengeMethod: "s256"} // "S256"
|
||||||
CodeChallengeMethodS512 = CodeChallengeMethod{uid: "s512"} // "S512"
|
CodeChallengeMethodS512 = CodeChallengeMethod{codeChallengeMethod: "s512"} // "S512"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ErrCodeChallengeMethodUnknown error = NewError(
|
var ErrCodeChallengeMethodUnknown error = NewError(
|
||||||
|
@ -41,11 +42,11 @@ var ErrCodeChallengeMethodUnknown error = NewError(
|
||||||
|
|
||||||
//nolint:gochecknoglobals // maps cannot be constants
|
//nolint:gochecknoglobals // maps cannot be constants
|
||||||
var uidsMethods = map[string]CodeChallengeMethod{
|
var uidsMethods = map[string]CodeChallengeMethod{
|
||||||
CodeChallengeMethodMD5.uid: CodeChallengeMethodMD5,
|
CodeChallengeMethodMD5.codeChallengeMethod: CodeChallengeMethodMD5,
|
||||||
CodeChallengeMethodPLAIN.uid: CodeChallengeMethodPLAIN,
|
CodeChallengeMethodPLAIN.codeChallengeMethod: CodeChallengeMethodPLAIN,
|
||||||
CodeChallengeMethodS1.uid: CodeChallengeMethodS1,
|
CodeChallengeMethodS1.codeChallengeMethod: CodeChallengeMethodS1,
|
||||||
CodeChallengeMethodS256.uid: CodeChallengeMethodS256,
|
CodeChallengeMethodS256.codeChallengeMethod: CodeChallengeMethodS256,
|
||||||
CodeChallengeMethodS512.uid: CodeChallengeMethodS512,
|
CodeChallengeMethodS512.codeChallengeMethod: CodeChallengeMethodS512,
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseCodeChallengeMethod parse string identifier of code challenge method
|
// ParseCodeChallengeMethod parse string identifier of code challenge method
|
||||||
|
@ -85,13 +86,13 @@ func (ccm *CodeChallengeMethod) UnmarshalJSON(v []byte) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ccm CodeChallengeMethod) MarshalJSON() ([]byte, error) {
|
func (ccm CodeChallengeMethod) MarshalJSON() ([]byte, error) {
|
||||||
return []byte(strconv.Quote(ccm.uid)), nil
|
return []byte(strconv.Quote(ccm.codeChallengeMethod)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// String returns string representation of code challenge method.
|
// String returns string representation of code challenge method.
|
||||||
func (ccm CodeChallengeMethod) String() string {
|
func (ccm CodeChallengeMethod) String() string {
|
||||||
if ccm.uid != "" {
|
if ccm.codeChallengeMethod != "" {
|
||||||
return strings.ToUpper(ccm.uid)
|
return strings.ToUpper(ccm.codeChallengeMethod)
|
||||||
}
|
}
|
||||||
|
|
||||||
return common.Und
|
return common.Und
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package domain_test
|
package domain_test
|
||||||
|
|
||||||
//nolint:gosec // support old clients
|
//nolint:gosec // support old clients
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
"crypto/sha1"
|
"crypto/sha1"
|
||||||
|
|
|
@ -11,14 +11,14 @@ import (
|
||||||
|
|
||||||
type (
|
type (
|
||||||
Config struct {
|
Config struct {
|
||||||
Code ConfigCode `envPrefix:"CODE_"`
|
|
||||||
Database ConfigDatabase `envPrefix:"DATABASE_"`
|
|
||||||
IndieAuth ConfigIndieAuth `envPrefix:"INDIEAUTH_"`
|
|
||||||
JWT ConfigJWT `envPrefix:"JWT_"`
|
|
||||||
Server ConfigServer `envPrefix:"SERVER_"`
|
Server ConfigServer `envPrefix:"SERVER_"`
|
||||||
TicketAuth ConfigTicketAuth `envPrefix:"TICKETAUTH_"`
|
Database ConfigDatabase `envPrefix:"DATABASE_"`
|
||||||
Name string `env:"NAME" envDefault:"IndieAuth"`
|
Name string `env:"NAME" envDefault:"IndieAuth"`
|
||||||
RunMode string `env:"RUN_MODE" envDefault:"dev"`
|
RunMode string `env:"RUN_MODE" envDefault:"dev"`
|
||||||
|
IndieAuth ConfigIndieAuth `envPrefix:"INDIEAUTH_"`
|
||||||
|
JWT ConfigJWT `envPrefix:"JWT_"`
|
||||||
|
Code ConfigCode `envPrefix:"CODE_"`
|
||||||
|
TicketAuth ConfigTicketAuth `envPrefix:"TICKETAUTH_"`
|
||||||
}
|
}
|
||||||
|
|
||||||
ConfigServer struct {
|
ConfigServer struct {
|
||||||
|
@ -45,10 +45,10 @@ type (
|
||||||
}
|
}
|
||||||
|
|
||||||
ConfigJWT struct {
|
ConfigJWT struct {
|
||||||
Expiry time.Duration `env:"EXPIRY" envDefault:"1h"` // 1h
|
Algorithm string `env:"ALGORITHM" envDefault:"HS256"`
|
||||||
Algorithm string `env:"ALGORITHM" envDefault:"HS256"` // HS256
|
|
||||||
Secret string `env:"SECRET"`
|
Secret string `env:"SECRET"`
|
||||||
NonceLength uint8 `env:"NONCE_LENGTH" envDefault:"22"` // 22
|
Expiry time.Duration `env:"EXPIRY" envDefault:"1h"`
|
||||||
|
NonceLength uint8 `env:"NONCE_LENGTH" envDefault:"22"`
|
||||||
}
|
}
|
||||||
|
|
||||||
ConfigIndieAuth struct {
|
ConfigIndieAuth struct {
|
||||||
|
|
|
@ -15,6 +15,8 @@ type (
|
||||||
// Error describes the format of a typical IndieAuth error.
|
// Error describes the format of a typical IndieAuth error.
|
||||||
//nolint:tagliatelle // RFC 6749 section 5.2
|
//nolint:tagliatelle // RFC 6749 section 5.2
|
||||||
Error struct {
|
Error struct {
|
||||||
|
frame xerrors.Frame `json:"-"`
|
||||||
|
|
||||||
// A single error code.
|
// A single error code.
|
||||||
Code ErrorCode `json:"error"`
|
Code ErrorCode `json:"error"`
|
||||||
|
|
||||||
|
@ -31,25 +33,23 @@ type (
|
||||||
// authorization request. The exact value received from the
|
// authorization request. The exact value received from the
|
||||||
// client.
|
// client.
|
||||||
State string `json:"-"`
|
State string `json:"-"`
|
||||||
|
|
||||||
frame xerrors.Frame `json:"-"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ErrorCode represent error code described in RFC 6749.
|
// ErrorCode represent error code described in RFC 6749.
|
||||||
ErrorCode struct {
|
ErrorCode struct {
|
||||||
uid string
|
errorCode string
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// ErrorCodeUnd describes an unrecognized error code.
|
// ErrorCodeUnd describes an unrecognized error code.
|
||||||
ErrorCodeUnd = ErrorCode{uid: ""} // "und"
|
ErrorCodeUnd = ErrorCode{errorCode: ""} // "und"
|
||||||
|
|
||||||
// ErrorCodeAccessDenied describes the access_denied error code.
|
// ErrorCodeAccessDenied describes the access_denied error code.
|
||||||
//
|
//
|
||||||
// RFC 6749 section 4.1.2.1: The resource owner or authorization server
|
// RFC 6749 section 4.1.2.1: The resource owner or authorization server
|
||||||
// denied the request.
|
// denied the request.
|
||||||
ErrorCodeAccessDenied = ErrorCode{uid: "access_denied"} // "access_denied"
|
ErrorCodeAccessDenied = ErrorCode{errorCode: "access_denied"} // "access_denied"
|
||||||
|
|
||||||
// ErrorCodeInvalidClient describes the invalid_client error code.
|
// ErrorCodeInvalidClient describes the invalid_client error code.
|
||||||
//
|
//
|
||||||
|
@ -65,7 +65,7 @@ var (
|
||||||
// HTTP 401 (Unauthorized) status code and include the
|
// HTTP 401 (Unauthorized) status code and include the
|
||||||
// "WWW-Authenticate" response header field matching the authentication
|
// "WWW-Authenticate" response header field matching the authentication
|
||||||
// scheme used by the client.
|
// scheme used by the client.
|
||||||
ErrorCodeInvalidClient = ErrorCode{uid: "invalid_client"} // "invalid_client"
|
ErrorCodeInvalidClient = ErrorCode{errorCode: "invalid_client"} // "invalid_client"
|
||||||
|
|
||||||
// ErrorCodeInvalidGrant describes the invalid_grant error code.
|
// ErrorCodeInvalidGrant describes the invalid_grant error code.
|
||||||
//
|
//
|
||||||
|
@ -73,7 +73,7 @@ var (
|
||||||
// authorization code, resource owner credentials) or refresh token is
|
// authorization code, resource owner credentials) or refresh token is
|
||||||
// invalid, expired, revoked, does not match the redirection URI used in
|
// invalid, expired, revoked, does not match the redirection URI used in
|
||||||
// the authorization request, or was issued to another client.
|
// the authorization request, or was issued to another client.
|
||||||
ErrorCodeInvalidGrant = ErrorCode{uid: "invalid_grant"} // "invalid_grant"
|
ErrorCodeInvalidGrant = ErrorCode{errorCode: "invalid_grant"} // "invalid_grant"
|
||||||
|
|
||||||
// ErrorCodeInvalidRequest describes the invalid_request error code.
|
// ErrorCodeInvalidRequest describes the invalid_request error code.
|
||||||
//
|
//
|
||||||
|
@ -88,7 +88,7 @@ var (
|
||||||
// repeats a parameter, includes multiple credentials, utilizes more
|
// repeats a parameter, includes multiple credentials, utilizes more
|
||||||
// than one mechanism for authenticating the client, or is otherwise
|
// than one mechanism for authenticating the client, or is otherwise
|
||||||
// malformed.
|
// malformed.
|
||||||
ErrorCodeInvalidRequest = ErrorCode{uid: "invalid_request"} // "invalid_request"
|
ErrorCodeInvalidRequest = ErrorCode{errorCode: "invalid_request"} // "invalid_request"
|
||||||
|
|
||||||
// ErrorCodeInvalidScope describes the invalid_scope error code.
|
// ErrorCodeInvalidScope describes the invalid_scope error code.
|
||||||
//
|
//
|
||||||
|
@ -97,7 +97,7 @@ var (
|
||||||
//
|
//
|
||||||
// RFC 6749 section 5.2: The requested scope is invalid, unknown,
|
// RFC 6749 section 5.2: The requested scope is invalid, unknown,
|
||||||
// malformed, or exceeds the scope granted by the resource owner.
|
// malformed, or exceeds the scope granted by the resource owner.
|
||||||
ErrorCodeInvalidScope = ErrorCode{uid: "invalid_scope"} // "invalid_scope"
|
ErrorCodeInvalidScope = ErrorCode{errorCode: "invalid_scope"} // "invalid_scope"
|
||||||
|
|
||||||
// ErrorCodeServerError describes the server_error error code.
|
// ErrorCodeServerError describes the server_error error code.
|
||||||
//
|
//
|
||||||
|
@ -105,7 +105,7 @@ var (
|
||||||
// unexpected condition that prevented it from fulfilling the request.
|
// unexpected condition that prevented it from fulfilling the request.
|
||||||
// (This error code is needed because a 500 Internal Server Error HTTP
|
// (This error code is needed because a 500 Internal Server Error HTTP
|
||||||
// status code cannot be returned to the client via an HTTP redirect.)
|
// status code cannot be returned to the client via an HTTP redirect.)
|
||||||
ErrorCodeServerError = ErrorCode{uid: "server_error"} // "server_error"
|
ErrorCodeServerError = ErrorCode{errorCode: "server_error"} // "server_error"
|
||||||
|
|
||||||
// ErrorCodeTemporarilyUnavailable describes the temporarily_unavailable error code.
|
// ErrorCodeTemporarilyUnavailable describes the temporarily_unavailable error code.
|
||||||
//
|
//
|
||||||
|
@ -114,7 +114,7 @@ var (
|
||||||
// maintenance of the server. (This error code is needed because a 503
|
// maintenance of the server. (This error code is needed because a 503
|
||||||
// Service Unavailable HTTP status code cannot be returned to the client
|
// Service Unavailable HTTP status code cannot be returned to the client
|
||||||
// via an HTTP redirect.)
|
// via an HTTP redirect.)
|
||||||
ErrorCodeTemporarilyUnavailable = ErrorCode{uid: "temporarily_unavailable"} // "temporarily_unavailable"
|
ErrorCodeTemporarilyUnavailable = ErrorCode{errorCode: "temporarily_unavailable"} // "temporarily_unavailable"
|
||||||
|
|
||||||
// ErrorCodeUnauthorizedClient describes the unauthorized_client error code.
|
// ErrorCodeUnauthorizedClient describes the unauthorized_client error code.
|
||||||
//
|
//
|
||||||
|
@ -123,53 +123,53 @@ var (
|
||||||
//
|
//
|
||||||
// RFC 6749 section 5.2: The authenticated client is not authorized to
|
// RFC 6749 section 5.2: The authenticated client is not authorized to
|
||||||
// use this authorization grant type.
|
// use this authorization grant type.
|
||||||
ErrorCodeUnauthorizedClient = ErrorCode{uid: "unauthorized_client"} // "unauthorized_client"
|
ErrorCodeUnauthorizedClient = ErrorCode{errorCode: "unauthorized_client"} // "unauthorized_client"
|
||||||
|
|
||||||
// ErrorCodeUnsupportedGrantType describes the unsupported_grant_type error code.
|
// ErrorCodeUnsupportedGrantType describes the unsupported_grant_type error code.
|
||||||
//
|
//
|
||||||
// RFC 6749 section 5.2: The authorization grant type is not supported
|
// RFC 6749 section 5.2: The authorization grant type is not supported
|
||||||
// by the authorization server.
|
// by the authorization server.
|
||||||
ErrorCodeUnsupportedGrantType = ErrorCode{uid: "unsupported_grant_type"} // "unsupported_grant_type"
|
ErrorCodeUnsupportedGrantType = ErrorCode{errorCode: "unsupported_grant_type"} // "unsupported_grant_type"
|
||||||
|
|
||||||
// ErrorCodeUnsupportedResponseType describes the unsupported_response_type error code.
|
// ErrorCodeUnsupportedResponseType describes the unsupported_response_type error code.
|
||||||
//
|
//
|
||||||
// RFC 6749 section 4.1.2.1: The authorization server does not support
|
// RFC 6749 section 4.1.2.1: The authorization server does not support
|
||||||
// obtaining an authorization code using this method.
|
// obtaining an authorization code using this method.
|
||||||
ErrorCodeUnsupportedResponseType = ErrorCode{uid: "unsupported_response_type"} // "unsupported_response_type"
|
ErrorCodeUnsupportedResponseType = ErrorCode{errorCode: "unsupported_response_type"} // "unsupported_response_type"
|
||||||
|
|
||||||
// ErrorCodeInvalidToken describes the invalid_token error code.
|
// ErrorCodeInvalidToken describes the invalid_token error code.
|
||||||
//
|
//
|
||||||
// IndieAuth: The access token provided is expired, revoked, or invalid.
|
// IndieAuth: The access token provided is expired, revoked, or invalid.
|
||||||
ErrorCodeInvalidToken = ErrorCode{uid: "invalid_token"} // "invalid_token"
|
ErrorCodeInvalidToken = ErrorCode{errorCode: "invalid_token"} // "invalid_token"
|
||||||
|
|
||||||
// ErrorCodeInsufficientScope describes the insufficient_scope error code.
|
// ErrorCodeInsufficientScope describes the insufficient_scope error code.
|
||||||
//
|
//
|
||||||
// IndieAuth: The request requires higher privileges than provided.
|
// IndieAuth: The request requires higher privileges than provided.
|
||||||
ErrorCodeInsufficientScope = ErrorCode{uid: "insufficient_scope"} // "insufficient_scope"
|
ErrorCodeInsufficientScope = ErrorCode{errorCode: "insufficient_scope"} // "insufficient_scope"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ErrErrorCodeUnknown error = NewError(ErrorCodeInvalidRequest, "unknown error code", "")
|
var ErrErrorCodeUnknown error = NewError(ErrorCodeInvalidRequest, "unknown error code", "")
|
||||||
|
|
||||||
//nolint:gochecknoglobals // maps cannot be constants
|
//nolint:gochecknoglobals // maps cannot be constants
|
||||||
var uidsErrorCodes = map[string]ErrorCode{
|
var uidsErrorCodes = map[string]ErrorCode{
|
||||||
ErrorCodeAccessDenied.uid: ErrorCodeAccessDenied,
|
ErrorCodeAccessDenied.errorCode: ErrorCodeAccessDenied,
|
||||||
ErrorCodeInsufficientScope.uid: ErrorCodeInsufficientScope,
|
ErrorCodeInsufficientScope.errorCode: ErrorCodeInsufficientScope,
|
||||||
ErrorCodeInvalidClient.uid: ErrorCodeInvalidClient,
|
ErrorCodeInvalidClient.errorCode: ErrorCodeInvalidClient,
|
||||||
ErrorCodeInvalidGrant.uid: ErrorCodeInvalidGrant,
|
ErrorCodeInvalidGrant.errorCode: ErrorCodeInvalidGrant,
|
||||||
ErrorCodeInvalidRequest.uid: ErrorCodeInvalidRequest,
|
ErrorCodeInvalidRequest.errorCode: ErrorCodeInvalidRequest,
|
||||||
ErrorCodeInvalidScope.uid: ErrorCodeInvalidScope,
|
ErrorCodeInvalidScope.errorCode: ErrorCodeInvalidScope,
|
||||||
ErrorCodeInvalidToken.uid: ErrorCodeInvalidToken,
|
ErrorCodeInvalidToken.errorCode: ErrorCodeInvalidToken,
|
||||||
ErrorCodeServerError.uid: ErrorCodeServerError,
|
ErrorCodeServerError.errorCode: ErrorCodeServerError,
|
||||||
ErrorCodeTemporarilyUnavailable.uid: ErrorCodeTemporarilyUnavailable,
|
ErrorCodeTemporarilyUnavailable.errorCode: ErrorCodeTemporarilyUnavailable,
|
||||||
ErrorCodeUnauthorizedClient.uid: ErrorCodeUnauthorizedClient,
|
ErrorCodeUnauthorizedClient.errorCode: ErrorCodeUnauthorizedClient,
|
||||||
ErrorCodeUnsupportedGrantType.uid: ErrorCodeUnsupportedGrantType,
|
ErrorCodeUnsupportedGrantType.errorCode: ErrorCodeUnsupportedGrantType,
|
||||||
ErrorCodeUnsupportedResponseType.uid: ErrorCodeUnsupportedResponseType,
|
ErrorCodeUnsupportedResponseType.errorCode: ErrorCodeUnsupportedResponseType,
|
||||||
}
|
}
|
||||||
|
|
||||||
// String returns a string representation of the error code.
|
// String returns a string representation of the error code.
|
||||||
func (ec ErrorCode) String() string {
|
func (ec ErrorCode) String() string {
|
||||||
if ec.uid != "" {
|
if ec.errorCode != "" {
|
||||||
return ec.uid
|
return ec.errorCode
|
||||||
}
|
}
|
||||||
|
|
||||||
return common.Und
|
return common.Und
|
||||||
|
@ -193,7 +193,7 @@ func (ec *ErrorCode) UnmarshalForm(v []byte) error {
|
||||||
|
|
||||||
// MarshalJSON encodes the error code into its string representation in JSON.
|
// MarshalJSON encodes the error code into its string representation in JSON.
|
||||||
func (ec ErrorCode) MarshalJSON() ([]byte, error) {
|
func (ec ErrorCode) MarshalJSON() ([]byte, error) {
|
||||||
return []byte(strconv.QuoteToASCII(ec.uid)), nil
|
return []byte(strconv.QuoteToASCII(ec.errorCode)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Error returns a string representation of the error, satisfying the error
|
// Error returns a string representation of the error, satisfying the error
|
||||||
|
@ -231,6 +231,8 @@ func (e Error) SetReirectURI(u *url.URL) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
q := u.Query()
|
||||||
|
|
||||||
for key, val := range map[string]string{
|
for key, val := range map[string]string{
|
||||||
"error": e.Code.String(),
|
"error": e.Code.String(),
|
||||||
"error_description": e.Description,
|
"error_description": e.Description,
|
||||||
|
@ -241,8 +243,10 @@ func (e Error) SetReirectURI(u *url.URL) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
u.Query().Set(key, val)
|
q.Set(key, val)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
u.RawQuery = q.Encode()
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewError creates a new Error with the stack pointing to the function call
|
// NewError creates a new Error with the stack pointing to the function call
|
||||||
|
|
|
@ -13,17 +13,17 @@ import (
|
||||||
// NOTE(toby3d): Encapsulate enums in structs for extra compile-time safety:
|
// NOTE(toby3d): Encapsulate enums in structs for extra compile-time safety:
|
||||||
// https://threedots.tech/post/safer-enums-in-go/#struct-based-enums
|
// https://threedots.tech/post/safer-enums-in-go/#struct-based-enums
|
||||||
type GrantType struct {
|
type GrantType struct {
|
||||||
uid string
|
grantType string
|
||||||
}
|
}
|
||||||
|
|
||||||
//nolint:gochecknoglobals // structs cannot be constants
|
//nolint:gochecknoglobals // structs cannot be constants
|
||||||
var (
|
var (
|
||||||
GrantTypeUnd = GrantType{uid: ""} // "und"
|
GrantTypeUnd = GrantType{grantType: ""} // "und"
|
||||||
GrantTypeAuthorizationCode = GrantType{uid: "authorization_code"} // "authorization_code"
|
GrantTypeAuthorizationCode = GrantType{grantType: "authorization_code"} // "authorization_code"
|
||||||
GrantTypeRefreshToken = GrantType{uid: "refresh_token"} // "refresh_token"
|
GrantTypeRefreshToken = GrantType{grantType: "refresh_token"} // "refresh_token"
|
||||||
|
|
||||||
// TicketAuth extension.
|
// TicketAuth extension.
|
||||||
GrantTypeTicket = GrantType{uid: "ticket"}
|
GrantTypeTicket = GrantType{grantType: "ticket"}
|
||||||
)
|
)
|
||||||
|
|
||||||
var ErrGrantTypeUnknown error = NewError(
|
var ErrGrantTypeUnknown error = NewError(
|
||||||
|
@ -34,9 +34,9 @@ var ErrGrantTypeUnknown error = NewError(
|
||||||
|
|
||||||
//nolint:gochecknoglobals // maps cannot be constants
|
//nolint:gochecknoglobals // maps cannot be constants
|
||||||
var uidsGrantTypes = map[string]GrantType{
|
var uidsGrantTypes = map[string]GrantType{
|
||||||
GrantTypeAuthorizationCode.uid: GrantTypeAuthorizationCode,
|
GrantTypeAuthorizationCode.grantType: GrantTypeAuthorizationCode,
|
||||||
GrantTypeRefreshToken.uid: GrantTypeRefreshToken,
|
GrantTypeRefreshToken.grantType: GrantTypeRefreshToken,
|
||||||
GrantTypeTicket.uid: GrantTypeTicket,
|
GrantTypeTicket.grantType: GrantTypeTicket,
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseGrantType parse grant_type value as GrantType struct enum.
|
// ParseGrantType parse grant_type value as GrantType struct enum.
|
||||||
|
@ -78,13 +78,13 @@ func (gt *GrantType) UnmarshalJSON(v []byte) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gt GrantType) MarshalJSON() ([]byte, error) {
|
func (gt GrantType) MarshalJSON() ([]byte, error) {
|
||||||
return []byte(strconv.Quote(gt.uid)), nil
|
return []byte(strconv.Quote(gt.grantType)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// String returns string representation of grant type.
|
// String returns string representation of grant type.
|
||||||
func (gt GrantType) String() string {
|
func (gt GrantType) String() string {
|
||||||
if gt.uid != "" {
|
if gt.grantType != "" {
|
||||||
return gt.uid
|
return gt.grantType
|
||||||
}
|
}
|
||||||
|
|
||||||
return common.Und
|
return common.Und
|
||||||
|
|
|
@ -11,14 +11,14 @@ import (
|
||||||
|
|
||||||
// Me is a URL user identifier.
|
// Me is a URL user identifier.
|
||||||
type Me struct {
|
type Me struct {
|
||||||
id *url.URL
|
me *url.URL
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseMe parse string as me URL identifier.
|
// ParseMe parse string as me URL identifier.
|
||||||
//
|
//
|
||||||
//nolint:funlen,cyclop
|
//nolint:funlen,cyclop
|
||||||
func ParseMe(raw string) (*Me, error) {
|
func ParseMe(raw string) (*Me, error) {
|
||||||
id, err := url.Parse(raw)
|
me, err := url.Parse(raw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, NewError(
|
return nil, NewError(
|
||||||
ErrorCodeInvalidRequest,
|
ErrorCodeInvalidRequest,
|
||||||
|
@ -28,57 +28,57 @@ func ParseMe(raw string) (*Me, error) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if id.Scheme != "http" && id.Scheme != "https" {
|
if me.Scheme != "http" && me.Scheme != "https" {
|
||||||
return nil, NewError(
|
return nil, NewError(
|
||||||
ErrorCodeInvalidRequest,
|
ErrorCodeInvalidRequest,
|
||||||
"profile URL MUST have either an https or http scheme, got '"+id.Scheme+"'",
|
"profile URL MUST have either an https or http scheme, got '"+me.Scheme+"'",
|
||||||
"https://indieauth.net/source/#user-profile-url",
|
"https://indieauth.net/source/#user-profile-url",
|
||||||
"",
|
"",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if id.Path == "" {
|
if me.Path == "" {
|
||||||
id.Path = "/"
|
me.Path = "/"
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.Contains(id.Path, "/.") || strings.Contains(id.Path, "/..") {
|
if strings.Contains(me.Path, "/.") || strings.Contains(me.Path, "/..") {
|
||||||
return nil, NewError(
|
return nil, NewError(
|
||||||
ErrorCodeInvalidRequest,
|
ErrorCodeInvalidRequest,
|
||||||
"profile URL MUST contain a path component (/ is a valid path), MUST NOT contain single-dot "+
|
"profile URL MUST contain a path component (/ is a valid path), MUST NOT contain single-dot "+
|
||||||
"or double-dot path segments, got '"+id.Path+"'",
|
"or double-dot path segments, got '"+me.Path+"'",
|
||||||
"https://indieauth.net/source/#user-profile-url",
|
"https://indieauth.net/source/#user-profile-url",
|
||||||
"",
|
"",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if id.Fragment != "" {
|
if me.Fragment != "" {
|
||||||
return nil, NewError(
|
return nil, NewError(
|
||||||
ErrorCodeInvalidRequest,
|
ErrorCodeInvalidRequest,
|
||||||
"profile URL MUST NOT contain a fragment component, got '"+id.Fragment+"'",
|
"profile URL MUST NOT contain a fragment component, got '"+me.Fragment+"'",
|
||||||
"https://indieauth.net/source/#user-profile-url",
|
"https://indieauth.net/source/#user-profile-url",
|
||||||
"",
|
"",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if id.User != nil {
|
if me.User != nil {
|
||||||
return nil, NewError(
|
return nil, NewError(
|
||||||
ErrorCodeInvalidRequest,
|
ErrorCodeInvalidRequest,
|
||||||
"profile URL MUST NOT contain a username or password component, got '"+id.User.String()+"'",
|
"profile URL MUST NOT contain a username or password component, got '"+me.User.String()+"'",
|
||||||
"https://indieauth.net/source/#user-profile-url",
|
"https://indieauth.net/source/#user-profile-url",
|
||||||
"",
|
"",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if id.Host == "" {
|
if me.Host == "" {
|
||||||
return nil, NewError(
|
return nil, NewError(
|
||||||
ErrorCodeInvalidRequest,
|
ErrorCodeInvalidRequest,
|
||||||
"profile host name MUST be a domain name, got '"+id.Host+"'",
|
"profile host name MUST be a domain name, got '"+me.Host+"'",
|
||||||
"https://indieauth.net/source/#user-profile-url",
|
"https://indieauth.net/source/#user-profile-url",
|
||||||
"",
|
"",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, port, _ := net.SplitHostPort(id.Host); port != "" {
|
if _, port, _ := net.SplitHostPort(me.Host); port != "" {
|
||||||
return nil, NewError(
|
return nil, NewError(
|
||||||
ErrorCodeInvalidRequest,
|
ErrorCodeInvalidRequest,
|
||||||
"profile MUST NOT contain a port, got '"+port+"'",
|
"profile MUST NOT contain a port, got '"+port+"'",
|
||||||
|
@ -87,7 +87,7 @@ func ParseMe(raw string) (*Me, error) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if out := net.ParseIP(id.Host); out != nil {
|
if out := net.ParseIP(me.Host); out != nil {
|
||||||
return nil, NewError(
|
return nil, NewError(
|
||||||
ErrorCodeInvalidRequest,
|
ErrorCodeInvalidRequest,
|
||||||
"profile MUST NOT be ipv4 or ipv6 addresses, got '"+out.String()+"'",
|
"profile MUST NOT be ipv4 or ipv6 addresses, got '"+out.String()+"'",
|
||||||
|
@ -96,7 +96,7 @@ func ParseMe(raw string) (*Me, error) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Me{id: id}, nil
|
return &Me{me: me}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestMe returns valid random generated me for tests.
|
// TestMe returns valid random generated me for tests.
|
||||||
|
@ -108,7 +108,7 @@ func TestMe(tb testing.TB, src string) *Me {
|
||||||
tb.Fatal(err)
|
tb.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Me{id: u}
|
return &Me{me: u}
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalForm implements custom unmarshler for form values.
|
// UnmarshalForm implements custom unmarshler for form values.
|
||||||
|
@ -147,19 +147,19 @@ func (m Me) MarshalJSON() ([]byte, error) {
|
||||||
|
|
||||||
// URL returns copy of parsed me in *url.URL representation.
|
// URL returns copy of parsed me in *url.URL representation.
|
||||||
func (m Me) URL() *url.URL {
|
func (m Me) URL() *url.URL {
|
||||||
if m.id == nil {
|
if m.me == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
out, _ := url.Parse(m.id.String())
|
out, _ := url.Parse(m.me.String())
|
||||||
|
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
// String returns string representation of me.
|
// String returns string representation of me.
|
||||||
func (m Me) String() string {
|
func (m Me) String() string {
|
||||||
if m.id != nil {
|
if m.me != nil {
|
||||||
return m.id.String()
|
return m.me.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
return ""
|
return ""
|
||||||
|
|
|
@ -11,23 +11,23 @@ import (
|
||||||
// NOTE(toby3d): Encapsulate enums in structs for extra compile-time safety:
|
// NOTE(toby3d): Encapsulate enums in structs for extra compile-time safety:
|
||||||
// https://threedots.tech/post/safer-enums-in-go/#struct-based-enums
|
// https://threedots.tech/post/safer-enums-in-go/#struct-based-enums
|
||||||
type ResponseType struct {
|
type ResponseType struct {
|
||||||
uid string
|
responseType string
|
||||||
}
|
}
|
||||||
|
|
||||||
//nolint:gochecknoglobals // structs cannot be constants
|
//nolint:gochecknoglobals // structs cannot be constants
|
||||||
var (
|
var (
|
||||||
ResponseTypeUnd = ResponseType{uid: ""} // "und"
|
ResponseTypeUnd = ResponseType{responseType: ""} // "und"
|
||||||
|
|
||||||
// Deprecated(toby3d): Only accept response_type=code requests, and for
|
// Deprecated(toby3d): Only accept response_type=code requests, and for
|
||||||
// backwards-compatible support, treat response_type=id requests as
|
// backwards-compatible support, treat response_type=id requests as
|
||||||
// response_type=code requests:
|
// response_type=code requests:
|
||||||
// https://aaronparecki.com/2020/12/03/1/indieauth-2020#response-type
|
// https://aaronparecki.com/2020/12/03/1/indieauth-2020#response-type
|
||||||
ResponseTypeID = ResponseType{uid: "id"} // "id"
|
ResponseTypeID = ResponseType{responseType: "id"} // "id"
|
||||||
|
|
||||||
// Indicates to the authorization server that an authorization code
|
// Indicates to the authorization server that an authorization code
|
||||||
// should be returned as the response:
|
// should be returned as the response:
|
||||||
// https://indieauth.net/source/#authorization-request-li-1
|
// https://indieauth.net/source/#authorization-request-li-1
|
||||||
ResponseTypeCode = ResponseType{uid: "code"} // "code"
|
ResponseTypeCode = ResponseType{responseType: "code"} // "code"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ErrResponseTypeUnknown error = NewError(
|
var ErrResponseTypeUnknown error = NewError(
|
||||||
|
@ -39,9 +39,9 @@ var ErrResponseTypeUnknown error = NewError(
|
||||||
// ParseResponseType parse string as response type struct enum.
|
// ParseResponseType parse string as response type struct enum.
|
||||||
func ParseResponseType(uid string) (ResponseType, error) {
|
func ParseResponseType(uid string) (ResponseType, error) {
|
||||||
switch strings.ToLower(uid) {
|
switch strings.ToLower(uid) {
|
||||||
case ResponseTypeCode.uid:
|
case ResponseTypeCode.responseType:
|
||||||
return ResponseTypeCode, nil
|
return ResponseTypeCode, nil
|
||||||
case ResponseTypeID.uid:
|
case ResponseTypeID.responseType:
|
||||||
return ResponseTypeID, nil
|
return ResponseTypeID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,13 +78,13 @@ func (rt *ResponseType) UnmarshalJSON(v []byte) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rt ResponseType) MarshalJSON() ([]byte, error) {
|
func (rt ResponseType) MarshalJSON() ([]byte, error) {
|
||||||
return []byte(strconv.Quote(rt.uid)), nil
|
return []byte(strconv.Quote(rt.responseType)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// String returns string representation of response type.
|
// String returns string representation of response type.
|
||||||
func (rt ResponseType) String() string {
|
func (rt ResponseType) String() string {
|
||||||
if rt.uid != "" {
|
if rt.responseType != "" {
|
||||||
return rt.uid
|
return rt.responseType
|
||||||
}
|
}
|
||||||
|
|
||||||
return common.Und
|
return common.Und
|
||||||
|
|
|
@ -13,35 +13,35 @@ import (
|
||||||
// NOTE(toby3d): Encapsulate enums in structs for extra compile-time safety:
|
// NOTE(toby3d): Encapsulate enums in structs for extra compile-time safety:
|
||||||
// https://threedots.tech/post/safer-enums-in-go/#struct-based-enums
|
// https://threedots.tech/post/safer-enums-in-go/#struct-based-enums
|
||||||
type Scope struct {
|
type Scope struct {
|
||||||
uid string
|
scope string
|
||||||
}
|
}
|
||||||
|
|
||||||
var ErrScopeUnknown error = NewError(ErrorCodeInvalidRequest, "unknown scope", "https://indieweb.org/scope")
|
var ErrScopeUnknown error = NewError(ErrorCodeInvalidRequest, "unknown scope", "https://indieweb.org/scope")
|
||||||
|
|
||||||
//nolint:gochecknoglobals // structs cannot be constants
|
//nolint:gochecknoglobals // structs cannot be constants
|
||||||
var (
|
var (
|
||||||
ScopeUnd = Scope{uid: ""} // "und"
|
ScopeUnd = Scope{scope: ""} // "und"
|
||||||
|
|
||||||
// https://indieweb.org/scope#Micropub_Scopes
|
// https://indieweb.org/scope#Micropub_Scopes
|
||||||
ScopeCreate = Scope{uid: "create"} // "create"
|
ScopeCreate = Scope{scope: "create"} // "create"
|
||||||
ScopeDelete = Scope{uid: "delete"} // "delete"
|
ScopeDelete = Scope{scope: "delete"} // "delete"
|
||||||
ScopeDraft = Scope{uid: "draft"} // "draft"
|
ScopeDraft = Scope{scope: "draft"} // "draft"
|
||||||
ScopeMedia = Scope{uid: "media"} // "media"
|
ScopeMedia = Scope{scope: "media"} // "media"
|
||||||
ScopeUndelete = Scope{uid: "undelete"} // "undelete"
|
ScopeUndelete = Scope{scope: "undelete"} // "undelete"
|
||||||
ScopeUpdate = Scope{uid: "update"} // "update"
|
ScopeUpdate = Scope{scope: "update"} // "update"
|
||||||
|
|
||||||
// https://indieweb.org/scope#Microsub_Scopes
|
// https://indieweb.org/scope#Microsub_Scopes
|
||||||
ScopeBlock = Scope{uid: "block"} // "block"
|
ScopeBlock = Scope{scope: "block"} // "block"
|
||||||
ScopeChannels = Scope{uid: "channels"} // "channels"
|
ScopeChannels = Scope{scope: "channels"} // "channels"
|
||||||
ScopeFollow = Scope{uid: "follow"} // "follow"
|
ScopeFollow = Scope{scope: "follow"} // "follow"
|
||||||
ScopeMute = Scope{uid: "mute"} // "mute"
|
ScopeMute = Scope{scope: "mute"} // "mute"
|
||||||
ScopeRead = Scope{uid: "read"} // "read"
|
ScopeRead = Scope{scope: "read"} // "read"
|
||||||
|
|
||||||
// This scope requests access to the user's default profile information
|
// This scope requests access to the user's default profile information
|
||||||
// which include the following properties: name, photo, url.
|
// which include the following properties: name, photo, url.
|
||||||
//
|
//
|
||||||
// NOTE(toby3d): https://indieauth.net/source/#profile-information
|
// NOTE(toby3d): https://indieauth.net/source/#profile-information
|
||||||
ScopeProfile = Scope{uid: "profile"} // "profile"
|
ScopeProfile = Scope{scope: "profile"} // "profile"
|
||||||
|
|
||||||
// This scope requests access to the user's email address in the
|
// This scope requests access to the user's email address in the
|
||||||
// following property: email.
|
// following property: email.
|
||||||
|
@ -51,24 +51,24 @@ var (
|
||||||
// and must be requested along with the profile scope if desired.
|
// and must be requested along with the profile scope if desired.
|
||||||
//
|
//
|
||||||
// NOTE(toby3d): https://indieauth.net/source/#profile-information
|
// NOTE(toby3d): https://indieauth.net/source/#profile-information
|
||||||
ScopeEmail = Scope{uid: "email"} // "email"
|
ScopeEmail = Scope{scope: "email"} // "email"
|
||||||
)
|
)
|
||||||
|
|
||||||
//nolint:gochecknoglobals // maps cannot be constants
|
//nolint:gochecknoglobals // maps cannot be constants
|
||||||
var uidsScopes = map[string]Scope{
|
var uidsScopes = map[string]Scope{
|
||||||
ScopeBlock.uid: ScopeBlock,
|
ScopeBlock.scope: ScopeBlock,
|
||||||
ScopeChannels.uid: ScopeChannels,
|
ScopeChannels.scope: ScopeChannels,
|
||||||
ScopeCreate.uid: ScopeCreate,
|
ScopeCreate.scope: ScopeCreate,
|
||||||
ScopeDelete.uid: ScopeDelete,
|
ScopeDelete.scope: ScopeDelete,
|
||||||
ScopeDraft.uid: ScopeDraft,
|
ScopeDraft.scope: ScopeDraft,
|
||||||
ScopeEmail.uid: ScopeEmail,
|
ScopeEmail.scope: ScopeEmail,
|
||||||
ScopeFollow.uid: ScopeFollow,
|
ScopeFollow.scope: ScopeFollow,
|
||||||
ScopeMedia.uid: ScopeMedia,
|
ScopeMedia.scope: ScopeMedia,
|
||||||
ScopeMute.uid: ScopeMute,
|
ScopeMute.scope: ScopeMute,
|
||||||
ScopeProfile.uid: ScopeProfile,
|
ScopeProfile.scope: ScopeProfile,
|
||||||
ScopeRead.uid: ScopeRead,
|
ScopeRead.scope: ScopeRead,
|
||||||
ScopeUndelete.uid: ScopeUndelete,
|
ScopeUndelete.scope: ScopeUndelete,
|
||||||
ScopeUpdate.uid: ScopeUpdate,
|
ScopeUpdate.scope: ScopeUpdate,
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseScope parses scope slug into Scope domain.
|
// ParseScope parses scope slug into Scope domain.
|
||||||
|
@ -97,13 +97,13 @@ func (s *Scope) UnmarshalJSON(v []byte) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s Scope) MarshalJSON() ([]byte, error) {
|
func (s Scope) MarshalJSON() ([]byte, error) {
|
||||||
return []byte(strconv.Quote(s.uid)), nil
|
return []byte(strconv.Quote(s.scope)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// String returns string representation of scope.
|
// String returns string representation of scope.
|
||||||
func (s Scope) String() string {
|
func (s Scope) String() string {
|
||||||
if s.uid != "" {
|
if s.scope != "" {
|
||||||
return s.uid
|
return s.scope
|
||||||
}
|
}
|
||||||
|
|
||||||
return common.Und
|
return common.Und
|
||||||
|
|
|
@ -2,8 +2,9 @@ package http
|
||||||
|
|
||||||
//nolint:tagliatelle // https://indieauth.net/source/#indieauth-server-metadata
|
//nolint:tagliatelle // https://indieauth.net/source/#indieauth-server-metadata
|
||||||
type MetadataResponse struct {
|
type MetadataResponse struct {
|
||||||
// The server's issuer identifier.
|
// URL of a page containing human-readable information that
|
||||||
Issuer string `json:"issuer"`
|
// developers might need to know when using the server.
|
||||||
|
ServiceDocumentation string `json:"service_documentation,omitempty"`
|
||||||
|
|
||||||
// The Authorization Endpoint.
|
// The Authorization Endpoint.
|
||||||
AuthorizationEndpoint string `json:"authorization_endpoint"`
|
AuthorizationEndpoint string `json:"authorization_endpoint"`
|
||||||
|
@ -14,37 +15,36 @@ type MetadataResponse struct {
|
||||||
// The Introspection Endpoint.
|
// The Introspection Endpoint.
|
||||||
IntrospectionEndpoint string `json:"introspection_endpoint"`
|
IntrospectionEndpoint string `json:"introspection_endpoint"`
|
||||||
|
|
||||||
// JSON array containing a list of client authentication methods
|
// The User Info Endpoint.
|
||||||
// supported by this introspection endpoint.
|
UserinfoEndpoint string `json:"userinfo_endpoint,omitempty"`
|
||||||
IntrospectionEndpointAuthMethodsSupported []string `json:"introspection_endpoint_auth_methods_supported,omitempty"` //nolint:lll
|
|
||||||
|
|
||||||
// The Revocation Endpoint.
|
// The Revocation Endpoint.
|
||||||
RevocationEndpoint string `json:"revocation_endpoint,omitempty"`
|
RevocationEndpoint string `json:"revocation_endpoint,omitempty"`
|
||||||
|
|
||||||
|
// The server's issuer identifier.
|
||||||
|
Issuer string `json:"issuer"`
|
||||||
|
|
||||||
// JSON array containing the value "none".
|
// JSON array containing the value "none".
|
||||||
RevocationEndpointAuthMethodsSupported []string `json:"revocation_endpoint_auth_methods_supported,omitempty"` //nolint:lll
|
RevocationEndpointAuthMethodsSupported []string `json:"revocation_endpoint_auth_methods_supported,omitempty"` //nolint:lll
|
||||||
|
|
||||||
// JSON array containing scope values supported by the
|
|
||||||
// IndieAuth server.
|
|
||||||
ScopesSupported []string `json:"scopes_supported,omitempty"`
|
|
||||||
|
|
||||||
// JSON array containing the response_type values supported.
|
// JSON array containing the response_type values supported.
|
||||||
ResponseTypesSupported []string `json:"response_types_supported,omitempty"`
|
ResponseTypesSupported []string `json:"response_types_supported,omitempty"`
|
||||||
|
|
||||||
// JSON array containing grant type values supported.
|
// JSON array containing grant type values supported.
|
||||||
GrantTypesSupported []string `json:"grant_types_supported,omitempty"`
|
GrantTypesSupported []string `json:"grant_types_supported,omitempty"`
|
||||||
|
|
||||||
// URL of a page containing human-readable information that
|
// JSON array containing scope values supported by the
|
||||||
// developers might need to know when using the server.
|
// IndieAuth server.
|
||||||
ServiceDocumentation string `json:"service_documentation,omitempty"`
|
ScopesSupported []string `json:"scopes_supported,omitempty"`
|
||||||
|
|
||||||
// JSON array containing the methods supported for PKCE.
|
// JSON array containing the methods supported for PKCE.
|
||||||
CodeChallengeMethodsSupported []string `json:"code_challenge_methods_supported"`
|
CodeChallengeMethodsSupported []string `json:"code_challenge_methods_supported"`
|
||||||
|
|
||||||
|
// JSON array containing a list of client authentication methods
|
||||||
|
// supported by this introspection endpoint.
|
||||||
|
IntrospectionEndpointAuthMethodsSupported []string `json:"introspection_endpoint_auth_methods_supported,omitempty"` //nolint:lll
|
||||||
|
|
||||||
// Boolean parameter indicating whether the authorization server
|
// Boolean parameter indicating whether the authorization server
|
||||||
// provides the iss parameter.
|
// provides the iss parameter.
|
||||||
AuthorizationResponseIssParameterSupported bool `json:"authorization_response_iss_parameter_supported,omitempty"` //nolint:lll
|
AuthorizationResponseIssParameterSupported bool `json:"authorization_response_iss_parameter_supported,omitempty"` //nolint:lll
|
||||||
|
|
||||||
// The User Info Endpoint.
|
|
||||||
UserinfoEndpoint string `json:"userinfo_endpoint,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,25 +18,23 @@ import (
|
||||||
type (
|
type (
|
||||||
//nolint:tagliatelle,lll
|
//nolint:tagliatelle,lll
|
||||||
Response struct {
|
Response struct {
|
||||||
Issuer domain.URL `json:"issuer"`
|
TicketEndpoint domain.URL `json:"ticket_endpoint"`
|
||||||
AuthorizationEndpoint domain.URL `json:"authorization_endpoint"`
|
AuthorizationEndpoint domain.URL `json:"authorization_endpoint"`
|
||||||
IntrospectionEndpoint domain.URL `json:"introspection_endpoint"`
|
IntrospectionEndpoint domain.URL `json:"introspection_endpoint"`
|
||||||
RevocationEndpoint domain.URL `json:"revocation_endpoint,omitempty"`
|
RevocationEndpoint domain.URL `json:"revocation_endpoint,omitempty"`
|
||||||
ServiceDocumentation domain.URL `json:"service_documentation,omitempty"`
|
ServiceDocumentation domain.URL `json:"service_documentation,omitempty"`
|
||||||
TokenEndpoint domain.URL `json:"token_endpoint"`
|
TokenEndpoint domain.URL `json:"token_endpoint"`
|
||||||
UserinfoEndpoint domain.URL `json:"userinfo_endpoint,omitempty"`
|
UserinfoEndpoint domain.URL `json:"userinfo_endpoint,omitempty"`
|
||||||
CodeChallengeMethodsSupported []domain.CodeChallengeMethod `json:"code_challenge_methods_supported"`
|
Microsub domain.URL `json:"microsub"`
|
||||||
|
Issuer domain.URL `json:"issuer"`
|
||||||
|
Micropub domain.URL `json:"micropub"`
|
||||||
GrantTypesSupported []domain.GrantType `json:"grant_types_supported,omitempty"`
|
GrantTypesSupported []domain.GrantType `json:"grant_types_supported,omitempty"`
|
||||||
ResponseTypesSupported []domain.ResponseType `json:"response_types_supported,omitempty"`
|
|
||||||
ScopesSupported []domain.Scope `json:"scopes_supported,omitempty"`
|
|
||||||
IntrospectionEndpointAuthMethodsSupported []string `json:"introspection_endpoint_auth_methods_supported,omitempty"`
|
IntrospectionEndpointAuthMethodsSupported []string `json:"introspection_endpoint_auth_methods_supported,omitempty"`
|
||||||
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"`
|
||||||
|
ResponseTypesSupported []domain.ResponseType `json:"response_types_supported,omitempty"`
|
||||||
|
CodeChallengeMethodsSupported []domain.CodeChallengeMethod `json:"code_challenge_methods_supported"`
|
||||||
AuthorizationResponseIssParameterSupported bool `json:"authorization_response_iss_parameter_supported,omitempty"`
|
AuthorizationResponseIssParameterSupported bool `json:"authorization_response_iss_parameter_supported,omitempty"`
|
||||||
|
|
||||||
// Extensions
|
|
||||||
TicketEndpoint domain.URL `json:"ticket_endpoint"`
|
|
||||||
Micropub domain.URL `json:"micropub"`
|
|
||||||
Microsub domain.URL `json:"microsub"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
httpMetadataRepository struct {
|
httpMetadataRepository struct {
|
||||||
|
@ -150,29 +148,16 @@ func (r Response) populate(dst *domain.Metadata) {
|
||||||
dst.TicketEndpoint = r.TicketEndpoint.URL
|
dst.TicketEndpoint = r.TicketEndpoint.URL
|
||||||
dst.TokenEndpoint = r.TokenEndpoint.URL
|
dst.TokenEndpoint = r.TokenEndpoint.URL
|
||||||
dst.UserinfoEndpoint = r.UserinfoEndpoint.URL
|
dst.UserinfoEndpoint = r.UserinfoEndpoint.URL
|
||||||
|
dst.RevocationEndpointAuthMethodsSupported = append(dst.RevocationEndpointAuthMethodsSupported,
|
||||||
|
r.RevocationEndpointAuthMethodsSupported...)
|
||||||
|
dst.ResponseTypesSupported = append(dst.ResponseTypesSupported, r.ResponseTypesSupported...)
|
||||||
|
dst.IntrospectionEndpointAuthMethodsSupported = append(dst.IntrospectionEndpointAuthMethodsSupported,
|
||||||
|
r.IntrospectionEndpointAuthMethodsSupported...)
|
||||||
|
dst.GrantTypesSupported = append(dst.GrantTypesSupported, r.GrantTypesSupported...)
|
||||||
|
dst.CodeChallengeMethodsSupported = append(dst.CodeChallengeMethodsSupported,
|
||||||
|
r.CodeChallengeMethodsSupported...)
|
||||||
|
|
||||||
for _, scope := range r.ScopesSupported {
|
for _, scope := range r.ScopesSupported {
|
||||||
dst.ScopesSupported = append(dst.ScopesSupported, scope)
|
dst.ScopesSupported = append(dst.ScopesSupported, scope)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, method := range r.RevocationEndpointAuthMethodsSupported {
|
|
||||||
dst.RevocationEndpointAuthMethodsSupported = append(dst.RevocationEndpointAuthMethodsSupported, method)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, responseType := range r.ResponseTypesSupported {
|
|
||||||
dst.ResponseTypesSupported = append(dst.ResponseTypesSupported, responseType)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, method := range r.IntrospectionEndpointAuthMethodsSupported {
|
|
||||||
dst.IntrospectionEndpointAuthMethodsSupported = append(dst.IntrospectionEndpointAuthMethodsSupported,
|
|
||||||
method)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, grantType := range r.GrantTypesSupported {
|
|
||||||
dst.GrantTypesSupported = append(dst.GrantTypesSupported, grantType)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, method := range r.CodeChallengeMethodsSupported {
|
|
||||||
dst.CodeChallengeMethodsSupported = append(dst.CodeChallengeMethodsSupported, method)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,22 +19,22 @@ import (
|
||||||
|
|
||||||
//nolint:lll,tagliatelle
|
//nolint:lll,tagliatelle
|
||||||
type Response struct {
|
type Response struct {
|
||||||
CodeChallengeMethodsSupported []string `json:"code_challenge_methods_supported"`
|
UserinfoEndpoint string `json:"userinfo_endpoint,omitempty"`
|
||||||
GrantTypesSupported []string `json:"grant_types_supported,omitempty"`
|
|
||||||
ResponseTypesSupported []string `json:"response_types_supported,omitempty"`
|
|
||||||
ScopesSupported []string `json:"scopes_supported,omitempty"`
|
|
||||||
IntrospectionEndpointAuthMethodsSupported []string `json:"introspection_endpoint_auth_methods_supported,omitempty"`
|
|
||||||
RevocationEndpointAuthMethodsSupported []string `json:"revocation_endpoint_auth_methods_supported,omitempty"`
|
|
||||||
Issuer string `json:"issuer"`
|
|
||||||
AuthorizationEndpoint string `json:"authorization_endpoint"`
|
AuthorizationEndpoint string `json:"authorization_endpoint"`
|
||||||
IntrospectionEndpoint string `json:"introspection_endpoint"`
|
IntrospectionEndpoint string `json:"introspection_endpoint"`
|
||||||
RevocationEndpoint string `json:"revocation_endpoint,omitempty"`
|
|
||||||
ServiceDocumentation string `json:"service_documentation,omitempty"`
|
|
||||||
TokenEndpoint string `json:"token_endpoint"`
|
|
||||||
UserinfoEndpoint string `json:"userinfo_endpoint,omitempty"`
|
|
||||||
TicketEndpoint string `json:"ticket_endpoint"`
|
|
||||||
Micropub string `json:"micropub"`
|
|
||||||
Microsub string `json:"microsub"`
|
Microsub string `json:"microsub"`
|
||||||
|
RevocationEndpoint string `json:"revocation_endpoint,omitempty"`
|
||||||
|
Micropub string `json:"micropub"`
|
||||||
|
Issuer string `json:"issuer"`
|
||||||
|
ServiceDocumentation string `json:"service_documentation,omitempty"`
|
||||||
|
TicketEndpoint string `json:"ticket_endpoint"`
|
||||||
|
TokenEndpoint string `json:"token_endpoint"`
|
||||||
|
RevocationEndpointAuthMethodsSupported []string `json:"revocation_endpoint_auth_methods_supported,omitempty"`
|
||||||
|
IntrospectionEndpointAuthMethodsSupported []string `json:"introspection_endpoint_auth_methods_supported,omitempty"`
|
||||||
|
CodeChallengeMethodsSupported []string `json:"code_challenge_methods_supported"`
|
||||||
|
ResponseTypesSupported []string `json:"response_types_supported,omitempty"`
|
||||||
|
GrantTypesSupported []string `json:"grant_types_supported,omitempty"`
|
||||||
|
ScopesSupported []string `json:"scopes_supported,omitempty"`
|
||||||
AuthorizationResponseIssParameterSupported bool `json:"authorization_response_iss_parameter_supported,omitempty"`
|
AuthorizationResponseIssParameterSupported bool `json:"authorization_response_iss_parameter_supported,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,9 +17,8 @@ type (
|
||||||
// Skipper defines a function to skip middleware.
|
// Skipper defines a function to skip middleware.
|
||||||
Skipper Skipper
|
Skipper Skipper
|
||||||
|
|
||||||
// TokenLength is the length of the generated token.
|
// ErrorHandler defines a function which is executed for returning custom errors.
|
||||||
TokenLength uint8
|
ErrorHandler CSRFErrorHandler
|
||||||
// Optional. Default value 32.
|
|
||||||
|
|
||||||
// TokenLookup is a string in the form of "<source>:<name>" or "<source>:<name>,<source>:<name>" that
|
// TokenLookup is a string in the form of "<source>:<name>" or "<source>:<name>,<source>:<name>" that
|
||||||
// is used to extract token from the request.
|
// is used to extract token from the request.
|
||||||
|
@ -52,6 +51,14 @@ type (
|
||||||
// Optional. Default value 86400 (24hr).
|
// Optional. Default value 86400 (24hr).
|
||||||
CookieMaxAge int
|
CookieMaxAge int
|
||||||
|
|
||||||
|
// Indicates SameSite mode of the CSRF cookie.
|
||||||
|
// Optional. Default value SameSiteDefaultMode.
|
||||||
|
CookieSameSite http.SameSite
|
||||||
|
|
||||||
|
// TokenLength is the length of the generated token.
|
||||||
|
// Optional. Default value 32.
|
||||||
|
TokenLength uint8
|
||||||
|
|
||||||
// Indicates if CSRF cookie is secure.
|
// Indicates if CSRF cookie is secure.
|
||||||
// Optional. Default value false.
|
// Optional. Default value false.
|
||||||
CookieSecure bool
|
CookieSecure bool
|
||||||
|
@ -59,13 +66,6 @@ type (
|
||||||
// Indicates if CSRF cookie is HTTP only.
|
// Indicates if CSRF cookie is HTTP only.
|
||||||
// Optional. Default value false.
|
// Optional. Default value false.
|
||||||
CookieHTTPOnly bool
|
CookieHTTPOnly bool
|
||||||
|
|
||||||
// Indicates SameSite mode of the CSRF cookie.
|
|
||||||
// Optional. Default value SameSiteDefaultMode.
|
|
||||||
CookieSameSite http.SameSite
|
|
||||||
|
|
||||||
// ErrorHandler defines a function which is executed for returning custom errors.
|
|
||||||
ErrorHandler CSRFErrorHandler
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CSRFErrorHandler is a function which is executed for creating custom errors.
|
// CSRFErrorHandler is a function which is executed for creating custom errors.
|
||||||
|
|
|
@ -15,25 +15,6 @@ import (
|
||||||
type (
|
type (
|
||||||
// JWTConfig defines the config for JWT middleware.
|
// JWTConfig defines the config for JWT middleware.
|
||||||
JWTConfig struct {
|
JWTConfig struct {
|
||||||
// Skipper defines a function to skip middleware.
|
|
||||||
Skipper Skipper
|
|
||||||
|
|
||||||
// BeforeFunc defines a function which is executed just before
|
|
||||||
// the middleware.
|
|
||||||
BeforeFunc BeforeFunc
|
|
||||||
|
|
||||||
// SuccessHandler defines a function which is executed for a
|
|
||||||
// valid token.
|
|
||||||
SuccessHandler JWTSuccessHandler
|
|
||||||
|
|
||||||
// ErrorHandler defines a function which is executed for an
|
|
||||||
// invalid token. It may be used to define a custom JWT error.
|
|
||||||
ErrorHandler JWTErrorHandler
|
|
||||||
|
|
||||||
// ErrorHandlerWithContext is almost identical to ErrorHandler,
|
|
||||||
// but it's passed the current context.
|
|
||||||
ErrorHandlerWithContext JWTErrorHandlerWithContext
|
|
||||||
|
|
||||||
// Signing key to validate token.
|
// Signing key to validate token.
|
||||||
// This is one of the three options to provide a token
|
// This is one of the three options to provide a token
|
||||||
// validation key. The order of precedence is a user-defined
|
// validation key. The order of precedence is a user-defined
|
||||||
|
@ -52,49 +33,24 @@ type (
|
||||||
// provided.
|
// provided.
|
||||||
SigningKeys map[string]any
|
SigningKeys map[string]any
|
||||||
|
|
||||||
// Signing method used to check the token's signing algorithm.
|
// SuccessHandler defines a function which is executed for a
|
||||||
//
|
// valid token.
|
||||||
// Optional. Default value HS256.
|
SuccessHandler JWTSuccessHandler
|
||||||
SigningMethod jwa.SignatureAlgorithm
|
|
||||||
|
|
||||||
// Context key to store user information from the token into
|
// ErrorHandler defines a function which is executed for an
|
||||||
// context.
|
// invalid token. It may be used to define a custom JWT error.
|
||||||
//
|
ErrorHandler JWTErrorHandler
|
||||||
// Optional. Default value "user".
|
|
||||||
ContextKey string
|
|
||||||
|
|
||||||
// Claims are extendable claims data defining token content.
|
// ErrorHandlerWithContext is almost identical to ErrorHandler,
|
||||||
// Used by default ParseTokenFunc implementation. Not used if
|
// but it's passed the current context.
|
||||||
// custom ParseTokenFunc is set.
|
ErrorHandlerWithContext JWTErrorHandlerWithContext
|
||||||
//
|
|
||||||
// Optional. Default value []jwt.ClaimPair
|
|
||||||
Claims []jwt.ClaimPair
|
|
||||||
|
|
||||||
// TokenLookup is a string in the form of "<source>:<name>" or
|
// BeforeFunc defines a function which is executed just before
|
||||||
// "<source>:<name>,<source>:<name>" that is used to extract
|
// the middleware.
|
||||||
// token from the request.
|
BeforeFunc BeforeFunc
|
||||||
// Optional. Default value "header:Authorization".
|
|
||||||
// Possible values:
|
|
||||||
// - "header:<name>"
|
|
||||||
// - "query:<name>"
|
|
||||||
// - "cookie:<name>"
|
|
||||||
// - "form:<name>"
|
|
||||||
// Multiply sources example:
|
|
||||||
// - "header: Authorization,cookie: myowncookie"
|
|
||||||
TokenLookup string
|
|
||||||
|
|
||||||
// TokenLookupFuncs defines a list of user-defined functions
|
// Skipper defines a function to skip middleware.
|
||||||
// that extract JWT token from the given context.
|
Skipper Skipper
|
||||||
// This is one of the two options to provide a token extractor.
|
|
||||||
// The order of precedence is user-defined TokenLookupFuncs, and
|
|
||||||
// TokenLookup.
|
|
||||||
// You can also provide both if you want.
|
|
||||||
TokenLookupFuncs []ValuesExtractor
|
|
||||||
|
|
||||||
// AuthScheme to be used in the Authorization header.
|
|
||||||
//
|
|
||||||
// Optional. Default value "Bearer".
|
|
||||||
AuthScheme string
|
|
||||||
|
|
||||||
// KeyFunc defines a user-defined function that supplies the
|
// KeyFunc defines a user-defined function that supplies the
|
||||||
// public key for a token validation. The function shall take
|
// public key for a token validation. The function shall take
|
||||||
|
@ -119,6 +75,50 @@ type (
|
||||||
// using `github.com/golang-jwt/jwt` as JWT implementation library
|
// using `github.com/golang-jwt/jwt` as JWT implementation library
|
||||||
ParseTokenFunc func(auth []byte, w http.ResponseWriter, r *http.Request) (any, error)
|
ParseTokenFunc func(auth []byte, w http.ResponseWriter, r *http.Request) (any, error)
|
||||||
|
|
||||||
|
// Context key to store user information from the token into
|
||||||
|
// context.
|
||||||
|
//
|
||||||
|
// Optional. Default value "user".
|
||||||
|
ContextKey string
|
||||||
|
|
||||||
|
// TokenLookup is a string in the form of "<source>:<name>" or
|
||||||
|
// "<source>:<name>,<source>:<name>" that is used to extract
|
||||||
|
// token from the request.
|
||||||
|
// Optional. Default value "header:Authorization".
|
||||||
|
// Possible values:
|
||||||
|
// - "header:<name>"
|
||||||
|
// - "query:<name>"
|
||||||
|
// - "cookie:<name>"
|
||||||
|
// - "form:<name>"
|
||||||
|
// Multiply sources example:
|
||||||
|
// - "header: Authorization,cookie: myowncookie"
|
||||||
|
TokenLookup string
|
||||||
|
|
||||||
|
// AuthScheme to be used in the Authorization header.
|
||||||
|
//
|
||||||
|
// Optional. Default value "Bearer".
|
||||||
|
AuthScheme string
|
||||||
|
|
||||||
|
// Signing method used to check the token's signing algorithm.
|
||||||
|
//
|
||||||
|
// Optional. Default value HS256.
|
||||||
|
SigningMethod jwa.SignatureAlgorithm
|
||||||
|
|
||||||
|
// Claims are extendable claims data defining token content.
|
||||||
|
// Used by default ParseTokenFunc implementation. Not used if
|
||||||
|
// custom ParseTokenFunc is set.
|
||||||
|
//
|
||||||
|
// Optional. Default value []jwt.ClaimPair
|
||||||
|
Claims []jwt.ClaimPair
|
||||||
|
|
||||||
|
// TokenLookupFuncs defines a list of user-defined functions
|
||||||
|
// that extract JWT token from the given context.
|
||||||
|
// This is one of the two options to provide a token extractor.
|
||||||
|
// The order of precedence is user-defined TokenLookupFuncs, and
|
||||||
|
// TokenLookup.
|
||||||
|
// You can also provide both if you want.
|
||||||
|
TokenLookupFuncs []ValuesExtractor
|
||||||
|
|
||||||
// ContinueOnIgnoredError allows the next middleware/handler to
|
// ContinueOnIgnoredError allows the next middleware/handler to
|
||||||
// be called when ErrorHandlerWithContext decides to ignore the
|
// be called when ErrorHandlerWithContext decides to ignore the
|
||||||
// error (by returning `nil`). This is useful when parts of your
|
// error (by returning `nil`). This is useful when parts of your
|
||||||
|
|
|
@ -17,9 +17,9 @@ type (
|
||||||
}
|
}
|
||||||
|
|
||||||
memorySessionRepository struct {
|
memorySessionRepository struct {
|
||||||
config domain.Config
|
|
||||||
mutex *sync.RWMutex
|
mutex *sync.RWMutex
|
||||||
sessions map[string]Session
|
sessions map[string]Session
|
||||||
|
config domain.Config
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -17,9 +17,9 @@ import (
|
||||||
|
|
||||||
type (
|
type (
|
||||||
Session struct {
|
Session struct {
|
||||||
CreatedAt sql.NullTime `db:"created_at"`
|
|
||||||
Code string `db:"code"`
|
Code string `db:"code"`
|
||||||
Data string `db:"data"`
|
Data string `db:"data"`
|
||||||
|
CreatedAt sql.NullTime `db:"created_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
sqlite3SessionRepository struct {
|
sqlite3SessionRepository struct {
|
||||||
|
|
|
@ -19,7 +19,7 @@ func New(tb testing.TB) (*bolt.DB, func()) {
|
||||||
|
|
||||||
filePath := tempFile.Name()
|
filePath := tempFile.Name()
|
||||||
|
|
||||||
if err := tempFile.Close(); err != nil {
|
if err = tempFile.Close(); err != nil {
|
||||||
tb.Fatal(err)
|
tb.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,9 +19,9 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Handler struct {
|
type Handler struct {
|
||||||
config domain.Config
|
|
||||||
matcher language.Matcher
|
matcher language.Matcher
|
||||||
tickets ticket.UseCase
|
tickets ticket.UseCase
|
||||||
|
config domain.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHandler(tickets ticket.UseCase, matcher language.Matcher, config domain.Config) *Handler {
|
func NewHandler(tickets ticket.UseCase, matcher language.Matcher, config domain.Config) *Handler {
|
||||||
|
@ -36,7 +36,7 @@ func (h *Handler) Handler() http.Handler {
|
||||||
//nolint:exhaustivestruct
|
//nolint:exhaustivestruct
|
||||||
chain := middleware.Chain{
|
chain := middleware.Chain{
|
||||||
middleware.CSRFWithConfig(middleware.CSRFConfig{
|
middleware.CSRFWithConfig(middleware.CSRFConfig{
|
||||||
Skipper: func(w http.ResponseWriter, r *http.Request) bool {
|
Skipper: func(_ http.ResponseWriter, r *http.Request) bool {
|
||||||
head, _ := urlutil.ShiftPath(r.URL.Path)
|
head, _ := urlutil.ShiftPath(r.URL.Path)
|
||||||
|
|
||||||
return r.Method == http.MethodPost && head == "ticket"
|
return r.Method == http.MethodPost && head == "ticket"
|
||||||
|
|
|
@ -16,9 +16,9 @@ type (
|
||||||
}
|
}
|
||||||
|
|
||||||
memoryTicketRepository struct {
|
memoryTicketRepository struct {
|
||||||
config domain.Config
|
|
||||||
mutex *sync.RWMutex
|
mutex *sync.RWMutex
|
||||||
tickets map[string]Ticket
|
tickets map[string]Ticket
|
||||||
|
config domain.Config
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -65,7 +65,7 @@ func (repo *memoryTicketRepository) GC() {
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
|
|
||||||
for ts := range ticker.C {
|
for ts := range ticker.C {
|
||||||
ts := ts.UTC()
|
ts = ts.UTC()
|
||||||
|
|
||||||
repo.mutex.RLock()
|
repo.mutex.RLock()
|
||||||
|
|
||||||
|
|
|
@ -16,10 +16,10 @@ import (
|
||||||
|
|
||||||
type (
|
type (
|
||||||
Ticket struct {
|
Ticket struct {
|
||||||
CreatedAt sql.NullTime `db:"created_at"`
|
|
||||||
Resource string `db:"resource"`
|
Resource string `db:"resource"`
|
||||||
Subject string `db:"subject"`
|
Subject string `db:"subject"`
|
||||||
Ticket string `db:"ticket"`
|
Ticket string `db:"ticket"`
|
||||||
|
CreatedAt sql.NullTime `db:"created_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
sqlite3TicketRepository struct {
|
sqlite3TicketRepository struct {
|
||||||
|
|
|
@ -18,18 +18,18 @@ type (
|
||||||
}
|
}
|
||||||
|
|
||||||
TokenRefreshRequest struct {
|
TokenRefreshRequest struct {
|
||||||
GrantType domain.GrantType `form:"grant_type"` // refresh_token
|
|
||||||
|
|
||||||
// The client ID that was used when the refresh token was issued.
|
// The client ID that was used when the refresh token was issued.
|
||||||
ClientID domain.ClientID `form:"client_id"`
|
ClientID domain.ClientID `form:"client_id"`
|
||||||
|
|
||||||
|
GrantType domain.GrantType `form:"grant_type"` // refresh_token
|
||||||
|
|
||||||
|
// The refresh token previously offered to the client.
|
||||||
|
RefreshToken string `form:"refresh_token"`
|
||||||
|
|
||||||
// The client may request a token with the same or fewer scopes
|
// The client may request a token with the same or fewer scopes
|
||||||
// than the original access token. If omitted, is treated as
|
// than the original access token. If omitted, is treated as
|
||||||
// equal to the original scopes granted.
|
// equal to the original scopes granted.
|
||||||
Scope domain.Scopes `form:"scope"`
|
Scope domain.Scopes `form:"scope"`
|
||||||
|
|
||||||
// The refresh token previously offered to the client.
|
|
||||||
RefreshToken string `form:"refresh_token"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TokenRevocationRequest struct {
|
TokenRevocationRequest struct {
|
||||||
|
|
|
@ -16,11 +16,11 @@ import (
|
||||||
|
|
||||||
type (
|
type (
|
||||||
Token struct {
|
Token struct {
|
||||||
CreatedAt sql.NullTime `db:"created_at"`
|
|
||||||
AccessToken string `db:"access_token"`
|
AccessToken string `db:"access_token"`
|
||||||
ClientID string `db:"client_id"`
|
ClientID string `db:"client_id"`
|
||||||
Me string `db:"me"`
|
Me string `db:"me"`
|
||||||
Scope string `db:"scope"`
|
Scope string `db:"scope"`
|
||||||
|
CreatedAt sql.NullTime `db:"created_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
sqlite3TokenRepository struct {
|
sqlite3TokenRepository struct {
|
||||||
|
|
|
@ -3,10 +3,10 @@ package http
|
||||||
import "source.toby3d.me/toby3d/auth/internal/domain"
|
import "source.toby3d.me/toby3d/auth/internal/domain"
|
||||||
|
|
||||||
type UserInformationResponse struct {
|
type UserInformationResponse struct {
|
||||||
Name string `json:"name,omitempty"`
|
|
||||||
URL *domain.URL `json:"url,omitempty"`
|
URL *domain.URL `json:"url,omitempty"`
|
||||||
Photo *domain.URL `json:"photo,omitempty"`
|
Photo *domain.URL `json:"photo,omitempty"`
|
||||||
Email *domain.Email `json:"email,omitempty"`
|
Email *domain.Email `json:"email,omitempty"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewUserInformationResponse(in *domain.Profile, hasEmail bool) *UserInformationResponse {
|
func NewUserInformationResponse(in *domain.Profile, hasEmail bool) *UserInformationResponse {
|
||||||
|
|
6
main.go
6
main.go
|
@ -164,7 +164,7 @@ func main() {
|
||||||
case "sqlite3":
|
case "sqlite3":
|
||||||
store, err := sqlx.Open("sqlite", config.Database.Path)
|
store, err := sqlx.Open("sqlite", config.Database.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
logger.Fatalln(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = store.Ping(); err != nil {
|
if err = store.Ping(); err != nil {
|
||||||
|
@ -226,7 +226,7 @@ func main() {
|
||||||
logger.Printf("started at %s, available at %s", config.Server.GetAddress(),
|
logger.Printf("started at %s, available at %s", config.Server.GetAddress(),
|
||||||
config.Server.GetRootURL())
|
config.Server.GetRootURL())
|
||||||
|
|
||||||
err := server.ListenAndServe()
|
err = server.ListenAndServe()
|
||||||
if err != nil && !errors.Is(err, http.ErrServerClosed) {
|
if err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||||
logger.Fatalln("cannot listen and serve:", err)
|
logger.Fatalln("cannot listen and serve:", err)
|
||||||
}
|
}
|
||||||
|
@ -234,7 +234,7 @@ func main() {
|
||||||
|
|
||||||
<-done
|
<-done
|
||||||
|
|
||||||
if err := server.Shutdown(ctx); err != nil {
|
if err = server.Shutdown(ctx); err != nil {
|
||||||
logger.Fatalln("failed shutdown of server:", err)
|
logger.Fatalln("failed shutdown of server:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue