diff --git a/internal/auth/delivery/http/auth_http.go b/internal/auth/delivery/http/auth_http.go index 2c3b19f..dcf15e6 100644 --- a/internal/auth/delivery/http/auth_http.go +++ b/internal/auth/delivery/http/auth_http.go @@ -48,7 +48,7 @@ func NewHandler(opts NewHandlerOptions) *Handler { func (h *Handler) Handler() http.Handler { chain := middleware.Chain{ 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) return head == "" @@ -65,7 +65,7 @@ func (h *Handler) Handler() http.Handler { CookieHTTPOnly: true, }), 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) return r.Method != http.MethodPost || diff --git a/internal/auth/delivery/http/auth_http_schema.go b/internal/auth/delivery/http/auth_http_schema.go index 6e7a6ec..314ac1a 100644 --- a/internal/auth/delivery/http/auth_http_schema.go +++ b/internal/auth/delivery/http/auth_http_schema.go @@ -11,10 +11,6 @@ import ( type ( 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. ClientID domain.ClientID `form:"client_id"` @@ -28,12 +24,9 @@ type ( // The hashing method used to calculate the code challenge. CodeChallengeMethod *domain.CodeChallengeMethod `form:"code_challenge_method,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"` + // Indicates to the authorization server that an authorization + // code should be returned as the response. + ResponseType domain.ResponseType `form:"response_type"` // code // A parameter set by the client which will be included when the // user is redirected back to the client. This is used to @@ -43,19 +36,26 @@ type ( // The code challenge as previously described. 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 { ClientID domain.ClientID `form:"client_id"` Me domain.Me `form:"me"` RedirectURI domain.URL `form:"redirect_uri"` - ResponseType domain.ResponseType `form:"response_type"` CodeChallengeMethod *domain.CodeChallengeMethod `form:"code_challenge_method,omitempty"` - Scope domain.Scopes `form:"scope[],omitempty"` + ResponseType domain.ResponseType `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 { diff --git a/internal/common/common.go b/internal/common/common.go index 9a5a60f..ab0f875 100644 --- a/internal/common/common.go +++ b/internal/common/common.go @@ -1,7 +1,5 @@ package common -const charsetUTF8 = "charset=UTF-8" - const ( MIMEApplicationForm string = "application/x-www-form-urlencoded" MIMEApplicationJSON string = "application/json" @@ -10,6 +8,8 @@ const ( MIMETextHTMLCharsetUTF8 string = MIMETextHTML + "; " + charsetUTF8 MIMETextPlain string = "text/plain" MIMETextPlainCharsetUTF8 string = MIMETextPlain + "; " + charsetUTF8 + + charsetUTF8 = "charset=UTF-8" ) const ( diff --git a/internal/domain/action.go b/internal/domain/action.go index 34ce890..3a201a5 100644 --- a/internal/domain/action.go +++ b/internal/domain/action.go @@ -12,26 +12,26 @@ import ( // NOTE(toby3d): Encapsulate enums in structs for extra compile-time safety: // https://threedots.tech/post/safer-enums-in-go/#struct-based-enums type Action struct { - uid string + action string } //nolint:gochecknoglobals // structs cannot be constants var ( - ActionUnd = Action{uid: ""} // "und" + ActionUnd = Action{action: ""} // "und" // ActionRevoke represent action for revoke token. - ActionRevoke = Action{uid: "revoke"} // "revoke" + ActionRevoke = Action{action: "revoke"} // "revoke" // ActionTicket represent action for TicketAuth extension. - ActionTicket = Action{uid: "ticket"} // "ticket" + ActionTicket = Action{action: "ticket"} // "ticket" ) var ErrActionSyntax error = NewError(ErrorCodeInvalidRequest, "unknown action method", "") //nolint:gochecknoglobals var uidsActions = map[string]Action{ - ActionRevoke.uid: ActionRevoke, - ActionTicket.uid: ActionTicket, + ActionRevoke.action: ActionRevoke, + ActionTicket.action: ActionTicket, } // 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. func (a Action) String() string { - if a.uid != "" { - return a.uid + if a.action != "" { + return a.action } return common.Und diff --git a/internal/domain/code_challenge_method.go b/internal/domain/code_challenge_method.go index f2618a0..2721b43 100644 --- a/internal/domain/code_challenge_method.go +++ b/internal/domain/code_challenge_method.go @@ -1,6 +1,7 @@ package domain //nolint:gosec // support old clients + import ( "crypto/md5" "crypto/sha1" @@ -20,17 +21,17 @@ import ( // 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 { - uid string + codeChallengeMethod string } //nolint:gochecknoglobals // structs cannot be constants var ( - CodeChallengeMethodUnd = CodeChallengeMethod{uid: ""} // "und" - CodeChallengeMethodPLAIN = CodeChallengeMethod{uid: "plain"} // "PLAIN" - CodeChallengeMethodMD5 = CodeChallengeMethod{uid: "md5"} // "MD5" - CodeChallengeMethodS1 = CodeChallengeMethod{uid: "s1"} // "S1" - CodeChallengeMethodS256 = CodeChallengeMethod{uid: "s256"} // "S256" - CodeChallengeMethodS512 = CodeChallengeMethod{uid: "s512"} // "S512" + 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( @@ -41,11 +42,11 @@ var ErrCodeChallengeMethodUnknown error = NewError( //nolint:gochecknoglobals // maps cannot be constants var uidsMethods = map[string]CodeChallengeMethod{ - CodeChallengeMethodMD5.uid: CodeChallengeMethodMD5, - CodeChallengeMethodPLAIN.uid: CodeChallengeMethodPLAIN, - CodeChallengeMethodS1.uid: CodeChallengeMethodS1, - CodeChallengeMethodS256.uid: CodeChallengeMethodS256, - CodeChallengeMethodS512.uid: CodeChallengeMethodS512, + CodeChallengeMethodMD5.codeChallengeMethod: CodeChallengeMethodMD5, + CodeChallengeMethodPLAIN.codeChallengeMethod: CodeChallengeMethodPLAIN, + CodeChallengeMethodS1.codeChallengeMethod: CodeChallengeMethodS1, + CodeChallengeMethodS256.codeChallengeMethod: CodeChallengeMethodS256, + CodeChallengeMethodS512.codeChallengeMethod: CodeChallengeMethodS512, } // 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) { - return []byte(strconv.Quote(ccm.uid)), nil + return []byte(strconv.Quote(ccm.codeChallengeMethod)), nil } // String returns string representation of code challenge method. func (ccm CodeChallengeMethod) String() string { - if ccm.uid != "" { - return strings.ToUpper(ccm.uid) + if ccm.codeChallengeMethod != "" { + return strings.ToUpper(ccm.codeChallengeMethod) } return common.Und diff --git a/internal/domain/code_challenge_method_test.go b/internal/domain/code_challenge_method_test.go index e196766..c3045cd 100644 --- a/internal/domain/code_challenge_method_test.go +++ b/internal/domain/code_challenge_method_test.go @@ -1,6 +1,7 @@ package domain_test //nolint:gosec // support old clients + import ( "crypto/md5" "crypto/sha1" diff --git a/internal/domain/config.go b/internal/domain/config.go index 6e3295b..dc0205e 100644 --- a/internal/domain/config.go +++ b/internal/domain/config.go @@ -11,14 +11,14 @@ import ( type ( Config struct { - Code ConfigCode `envPrefix:"CODE_"` - Database ConfigDatabase `envPrefix:"DATABASE_"` - IndieAuth ConfigIndieAuth `envPrefix:"INDIEAUTH_"` - JWT ConfigJWT `envPrefix:"JWT_"` Server ConfigServer `envPrefix:"SERVER_"` - TicketAuth ConfigTicketAuth `envPrefix:"TICKETAUTH_"` + Database ConfigDatabase `envPrefix:"DATABASE_"` Name string `env:"NAME" envDefault:"IndieAuth"` 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 { @@ -45,10 +45,10 @@ type ( } ConfigJWT struct { - Expiry time.Duration `env:"EXPIRY" envDefault:"1h"` // 1h - Algorithm string `env:"ALGORITHM" envDefault:"HS256"` // HS256 + Algorithm string `env:"ALGORITHM" envDefault:"HS256"` 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 { diff --git a/internal/domain/error.go b/internal/domain/error.go index b0d5748..48b2434 100644 --- a/internal/domain/error.go +++ b/internal/domain/error.go @@ -15,6 +15,8 @@ type ( // Error describes the format of a typical IndieAuth error. //nolint:tagliatelle // RFC 6749 section 5.2 Error struct { + frame xerrors.Frame `json:"-"` + // A single error code. Code ErrorCode `json:"error"` @@ -31,25 +33,23 @@ type ( // authorization request. The exact value received from the // client. State string `json:"-"` - - frame xerrors.Frame `json:"-"` } // ErrorCode represent error code described in RFC 6749. ErrorCode struct { - uid string + errorCode string } ) var ( // ErrorCodeUnd describes an unrecognized error code. - ErrorCodeUnd = ErrorCode{uid: ""} // "und" + ErrorCodeUnd = ErrorCode{errorCode: ""} // "und" // ErrorCodeAccessDenied describes the access_denied error code. // // RFC 6749 section 4.1.2.1: The resource owner or authorization server // denied the request. - ErrorCodeAccessDenied = ErrorCode{uid: "access_denied"} // "access_denied" + ErrorCodeAccessDenied = ErrorCode{errorCode: "access_denied"} // "access_denied" // ErrorCodeInvalidClient describes the invalid_client error code. // @@ -65,7 +65,7 @@ var ( // HTTP 401 (Unauthorized) status code and include the // "WWW-Authenticate" response header field matching the authentication // 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. // @@ -73,7 +73,7 @@ var ( // authorization code, resource owner credentials) or refresh token is // invalid, expired, revoked, does not match the redirection URI used in // 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. // @@ -88,7 +88,7 @@ var ( // repeats a parameter, includes multiple credentials, utilizes more // than one mechanism for authenticating the client, or is otherwise // malformed. - ErrorCodeInvalidRequest = ErrorCode{uid: "invalid_request"} // "invalid_request" + ErrorCodeInvalidRequest = ErrorCode{errorCode: "invalid_request"} // "invalid_request" // ErrorCodeInvalidScope describes the invalid_scope error code. // @@ -97,7 +97,7 @@ var ( // // RFC 6749 section 5.2: The requested scope is invalid, unknown, // 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. // @@ -105,7 +105,7 @@ var ( // unexpected condition that prevented it from fulfilling the request. // (This error code is needed because a 500 Internal Server Error HTTP // 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. // @@ -114,7 +114,7 @@ var ( // maintenance of the server. (This error code is needed because a 503 // Service Unavailable HTTP status code cannot be returned to the client // 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. // @@ -123,53 +123,53 @@ var ( // // RFC 6749 section 5.2: The authenticated client is not authorized to // 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. // // RFC 6749 section 5.2: The authorization grant type is not supported // 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. // // RFC 6749 section 4.1.2.1: The authorization server does not support // 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. // // 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. // // 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", "") //nolint:gochecknoglobals // maps cannot be constants var uidsErrorCodes = map[string]ErrorCode{ - ErrorCodeAccessDenied.uid: ErrorCodeAccessDenied, - ErrorCodeInsufficientScope.uid: ErrorCodeInsufficientScope, - ErrorCodeInvalidClient.uid: ErrorCodeInvalidClient, - ErrorCodeInvalidGrant.uid: ErrorCodeInvalidGrant, - ErrorCodeInvalidRequest.uid: ErrorCodeInvalidRequest, - ErrorCodeInvalidScope.uid: ErrorCodeInvalidScope, - ErrorCodeInvalidToken.uid: ErrorCodeInvalidToken, - ErrorCodeServerError.uid: ErrorCodeServerError, - ErrorCodeTemporarilyUnavailable.uid: ErrorCodeTemporarilyUnavailable, - ErrorCodeUnauthorizedClient.uid: ErrorCodeUnauthorizedClient, - ErrorCodeUnsupportedGrantType.uid: ErrorCodeUnsupportedGrantType, - ErrorCodeUnsupportedResponseType.uid: ErrorCodeUnsupportedResponseType, + ErrorCodeAccessDenied.errorCode: ErrorCodeAccessDenied, + ErrorCodeInsufficientScope.errorCode: ErrorCodeInsufficientScope, + ErrorCodeInvalidClient.errorCode: ErrorCodeInvalidClient, + ErrorCodeInvalidGrant.errorCode: ErrorCodeInvalidGrant, + ErrorCodeInvalidRequest.errorCode: ErrorCodeInvalidRequest, + ErrorCodeInvalidScope.errorCode: ErrorCodeInvalidScope, + ErrorCodeInvalidToken.errorCode: ErrorCodeInvalidToken, + ErrorCodeServerError.errorCode: ErrorCodeServerError, + ErrorCodeTemporarilyUnavailable.errorCode: ErrorCodeTemporarilyUnavailable, + ErrorCodeUnauthorizedClient.errorCode: ErrorCodeUnauthorizedClient, + ErrorCodeUnsupportedGrantType.errorCode: ErrorCodeUnsupportedGrantType, + ErrorCodeUnsupportedResponseType.errorCode: ErrorCodeUnsupportedResponseType, } // String returns a string representation of the error code. func (ec ErrorCode) String() string { - if ec.uid != "" { - return ec.uid + if ec.errorCode != "" { + return ec.errorCode } 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. 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 @@ -231,6 +231,8 @@ func (e Error) SetReirectURI(u *url.URL) { return } + q := u.Query() + for key, val := range map[string]string{ "error": e.Code.String(), "error_description": e.Description, @@ -241,8 +243,10 @@ func (e Error) SetReirectURI(u *url.URL) { 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 diff --git a/internal/domain/grant_type.go b/internal/domain/grant_type.go index 899c28d..556deb4 100644 --- a/internal/domain/grant_type.go +++ b/internal/domain/grant_type.go @@ -13,17 +13,17 @@ import ( // NOTE(toby3d): Encapsulate enums in structs for extra compile-time safety: // https://threedots.tech/post/safer-enums-in-go/#struct-based-enums type GrantType struct { - uid string + grantType string } //nolint:gochecknoglobals // structs cannot be constants var ( - GrantTypeUnd = GrantType{uid: ""} // "und" - GrantTypeAuthorizationCode = GrantType{uid: "authorization_code"} // "authorization_code" - GrantTypeRefreshToken = GrantType{uid: "refresh_token"} // "refresh_token" + GrantTypeUnd = GrantType{grantType: ""} // "und" + GrantTypeAuthorizationCode = GrantType{grantType: "authorization_code"} // "authorization_code" + GrantTypeRefreshToken = GrantType{grantType: "refresh_token"} // "refresh_token" // TicketAuth extension. - GrantTypeTicket = GrantType{uid: "ticket"} + GrantTypeTicket = GrantType{grantType: "ticket"} ) var ErrGrantTypeUnknown error = NewError( @@ -34,9 +34,9 @@ var ErrGrantTypeUnknown error = NewError( //nolint:gochecknoglobals // maps cannot be constants var uidsGrantTypes = map[string]GrantType{ - GrantTypeAuthorizationCode.uid: GrantTypeAuthorizationCode, - GrantTypeRefreshToken.uid: GrantTypeRefreshToken, - GrantTypeTicket.uid: GrantTypeTicket, + GrantTypeAuthorizationCode.grantType: GrantTypeAuthorizationCode, + GrantTypeRefreshToken.grantType: GrantTypeRefreshToken, + GrantTypeTicket.grantType: GrantTypeTicket, } // 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) { - return []byte(strconv.Quote(gt.uid)), nil + return []byte(strconv.Quote(gt.grantType)), nil } // String returns string representation of grant type. func (gt GrantType) String() string { - if gt.uid != "" { - return gt.uid + if gt.grantType != "" { + return gt.grantType } return common.Und diff --git a/internal/domain/me.go b/internal/domain/me.go index cb6f146..6070179 100644 --- a/internal/domain/me.go +++ b/internal/domain/me.go @@ -11,14 +11,14 @@ import ( // Me is a URL user identifier. type Me struct { - id *url.URL + me *url.URL } // ParseMe parse string as me URL identifier. // //nolint:funlen,cyclop func ParseMe(raw string) (*Me, error) { - id, err := url.Parse(raw) + me, err := url.Parse(raw) if err != nil { return nil, NewError( 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( 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", "", ) } - if id.Path == "" { - id.Path = "/" + if me.Path == "" { + me.Path = "/" } - if strings.Contains(id.Path, "/.") || strings.Contains(id.Path, "/..") { + if strings.Contains(me.Path, "/.") || strings.Contains(me.Path, "/..") { return nil, NewError( ErrorCodeInvalidRequest, "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", "", ) } - if id.Fragment != "" { + if me.Fragment != "" { return nil, NewError( 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", "", ) } - if id.User != nil { + if me.User != nil { return nil, NewError( 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", "", ) } - if id.Host == "" { + if me.Host == "" { return nil, NewError( 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", "", ) } - if _, port, _ := net.SplitHostPort(id.Host); port != "" { + if _, port, _ := net.SplitHostPort(me.Host); port != "" { return nil, NewError( ErrorCodeInvalidRequest, "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( ErrorCodeInvalidRequest, "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. @@ -108,7 +108,7 @@ func TestMe(tb testing.TB, src string) *Me { tb.Fatal(err) } - return &Me{id: u} + return &Me{me: u} } // 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. func (m Me) URL() *url.URL { - if m.id == nil { + if m.me == nil { return nil } - out, _ := url.Parse(m.id.String()) + out, _ := url.Parse(m.me.String()) return out } // String returns string representation of me. func (m Me) String() string { - if m.id != nil { - return m.id.String() + if m.me != nil { + return m.me.String() } return "" diff --git a/internal/domain/response_type.go b/internal/domain/response_type.go index c610a88..b597234 100644 --- a/internal/domain/response_type.go +++ b/internal/domain/response_type.go @@ -11,23 +11,23 @@ import ( // NOTE(toby3d): Encapsulate enums in structs for extra compile-time safety: // https://threedots.tech/post/safer-enums-in-go/#struct-based-enums type ResponseType struct { - uid string + responseType string } //nolint:gochecknoglobals // structs cannot be constants var ( - ResponseTypeUnd = ResponseType{uid: ""} // "und" + ResponseTypeUnd = ResponseType{responseType: ""} // "und" // Deprecated(toby3d): Only accept response_type=code requests, and for // backwards-compatible support, treat response_type=id requests as // response_type=code requests: // 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 // should be returned as the response: // https://indieauth.net/source/#authorization-request-li-1 - ResponseTypeCode = ResponseType{uid: "code"} // "code" + ResponseTypeCode = ResponseType{responseType: "code"} // "code" ) var ErrResponseTypeUnknown error = NewError( @@ -39,9 +39,9 @@ var ErrResponseTypeUnknown error = NewError( // ParseResponseType parse string as response type struct enum. func ParseResponseType(uid string) (ResponseType, error) { switch strings.ToLower(uid) { - case ResponseTypeCode.uid: + case ResponseTypeCode.responseType: return ResponseTypeCode, nil - case ResponseTypeID.uid: + case ResponseTypeID.responseType: return ResponseTypeID, nil } @@ -78,13 +78,13 @@ func (rt *ResponseType) UnmarshalJSON(v []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. func (rt ResponseType) String() string { - if rt.uid != "" { - return rt.uid + if rt.responseType != "" { + return rt.responseType } return common.Und diff --git a/internal/domain/scope.go b/internal/domain/scope.go index 126a52d..9a54125 100644 --- a/internal/domain/scope.go +++ b/internal/domain/scope.go @@ -13,35 +13,35 @@ import ( // NOTE(toby3d): Encapsulate enums in structs for extra compile-time safety: // https://threedots.tech/post/safer-enums-in-go/#struct-based-enums type Scope struct { - uid string + scope string } var ErrScopeUnknown error = NewError(ErrorCodeInvalidRequest, "unknown scope", "https://indieweb.org/scope") //nolint:gochecknoglobals // structs cannot be constants var ( - ScopeUnd = Scope{uid: ""} // "und" + ScopeUnd = Scope{scope: ""} // "und" // https://indieweb.org/scope#Micropub_Scopes - ScopeCreate = Scope{uid: "create"} // "create" - ScopeDelete = Scope{uid: "delete"} // "delete" - ScopeDraft = Scope{uid: "draft"} // "draft" - ScopeMedia = Scope{uid: "media"} // "media" - ScopeUndelete = Scope{uid: "undelete"} // "undelete" - ScopeUpdate = Scope{uid: "update"} // "update" + ScopeCreate = Scope{scope: "create"} // "create" + ScopeDelete = Scope{scope: "delete"} // "delete" + ScopeDraft = Scope{scope: "draft"} // "draft" + ScopeMedia = Scope{scope: "media"} // "media" + ScopeUndelete = Scope{scope: "undelete"} // "undelete" + ScopeUpdate = Scope{scope: "update"} // "update" // https://indieweb.org/scope#Microsub_Scopes - ScopeBlock = Scope{uid: "block"} // "block" - ScopeChannels = Scope{uid: "channels"} // "channels" - ScopeFollow = Scope{uid: "follow"} // "follow" - ScopeMute = Scope{uid: "mute"} // "mute" - ScopeRead = Scope{uid: "read"} // "read" + ScopeBlock = Scope{scope: "block"} // "block" + ScopeChannels = Scope{scope: "channels"} // "channels" + ScopeFollow = Scope{scope: "follow"} // "follow" + ScopeMute = Scope{scope: "mute"} // "mute" + ScopeRead = Scope{scope: "read"} // "read" // This scope requests access to the user's default profile information // which include the following properties: name, photo, url. // // 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 // following property: email. @@ -51,24 +51,24 @@ var ( // and must be requested along with the profile scope if desired. // // NOTE(toby3d): https://indieauth.net/source/#profile-information - ScopeEmail = Scope{uid: "email"} // "email" + ScopeEmail = Scope{scope: "email"} // "email" ) //nolint:gochecknoglobals // maps cannot be constants var uidsScopes = map[string]Scope{ - ScopeBlock.uid: ScopeBlock, - ScopeChannels.uid: ScopeChannels, - ScopeCreate.uid: ScopeCreate, - ScopeDelete.uid: ScopeDelete, - ScopeDraft.uid: ScopeDraft, - ScopeEmail.uid: ScopeEmail, - ScopeFollow.uid: ScopeFollow, - ScopeMedia.uid: ScopeMedia, - ScopeMute.uid: ScopeMute, - ScopeProfile.uid: ScopeProfile, - ScopeRead.uid: ScopeRead, - ScopeUndelete.uid: ScopeUndelete, - ScopeUpdate.uid: ScopeUpdate, + ScopeBlock.scope: ScopeBlock, + ScopeChannels.scope: ScopeChannels, + ScopeCreate.scope: ScopeCreate, + ScopeDelete.scope: ScopeDelete, + ScopeDraft.scope: ScopeDraft, + ScopeEmail.scope: ScopeEmail, + ScopeFollow.scope: ScopeFollow, + ScopeMedia.scope: ScopeMedia, + ScopeMute.scope: ScopeMute, + ScopeProfile.scope: ScopeProfile, + ScopeRead.scope: ScopeRead, + ScopeUndelete.scope: ScopeUndelete, + ScopeUpdate.scope: ScopeUpdate, } // ParseScope parses scope slug into Scope domain. @@ -97,13 +97,13 @@ func (s *Scope) UnmarshalJSON(v []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. func (s Scope) String() string { - if s.uid != "" { - return s.uid + if s.scope != "" { + return s.scope } return common.Und diff --git a/internal/metadata/delivery/http/metadata_http_schema.go b/internal/metadata/delivery/http/metadata_http_schema.go index 737ea79..77905a5 100644 --- a/internal/metadata/delivery/http/metadata_http_schema.go +++ b/internal/metadata/delivery/http/metadata_http_schema.go @@ -2,8 +2,9 @@ package http //nolint:tagliatelle // https://indieauth.net/source/#indieauth-server-metadata type MetadataResponse struct { - // The server's issuer identifier. - Issuer string `json:"issuer"` + // URL of a page containing human-readable information that + // developers might need to know when using the server. + ServiceDocumentation string `json:"service_documentation,omitempty"` // The Authorization Endpoint. AuthorizationEndpoint string `json:"authorization_endpoint"` @@ -14,37 +15,36 @@ type MetadataResponse struct { // The Introspection Endpoint. IntrospectionEndpoint string `json:"introspection_endpoint"` - // 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 + // The User Info Endpoint. + UserinfoEndpoint string `json:"userinfo_endpoint,omitempty"` // The Revocation Endpoint. RevocationEndpoint string `json:"revocation_endpoint,omitempty"` + // The server's issuer identifier. + Issuer string `json:"issuer"` + // JSON array containing the value "none". 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. ResponseTypesSupported []string `json:"response_types_supported,omitempty"` // JSON array containing grant type values supported. GrantTypesSupported []string `json:"grant_types_supported,omitempty"` - // URL of a page containing human-readable information that - // developers might need to know when using the server. - ServiceDocumentation string `json:"service_documentation,omitempty"` + // JSON array containing scope values supported by the + // IndieAuth server. + ScopesSupported []string `json:"scopes_supported,omitempty"` // JSON array containing the methods supported for PKCE. 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 // provides the iss parameter. AuthorizationResponseIssParameterSupported bool `json:"authorization_response_iss_parameter_supported,omitempty"` //nolint:lll - - // The User Info Endpoint. - UserinfoEndpoint string `json:"userinfo_endpoint,omitempty"` } diff --git a/internal/metadata/repository/http/http_metadata.go b/internal/metadata/repository/http/http_metadata.go index bea3b6c..011a2cc 100644 --- a/internal/metadata/repository/http/http_metadata.go +++ b/internal/metadata/repository/http/http_metadata.go @@ -18,25 +18,23 @@ import ( type ( //nolint:tagliatelle,lll Response struct { - Issuer domain.URL `json:"issuer"` + 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"` - 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"` - ResponseTypesSupported []domain.ResponseType `json:"response_types_supported,omitempty"` - ScopesSupported []domain.Scope `json:"scopes_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 []domain.ResponseType `json:"response_types_supported,omitempty"` + CodeChallengeMethodsSupported []domain.CodeChallengeMethod `json:"code_challenge_methods_supported"` 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 { @@ -150,29 +148,16 @@ func (r Response) populate(dst *domain.Metadata) { dst.TicketEndpoint = r.TicketEndpoint.URL dst.TokenEndpoint = r.TokenEndpoint.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 { 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) - } } diff --git a/internal/metadata/repository/http/http_metadata_test.go b/internal/metadata/repository/http/http_metadata_test.go index 2b4a791..3da05e4 100644 --- a/internal/metadata/repository/http/http_metadata_test.go +++ b/internal/metadata/repository/http/http_metadata_test.go @@ -19,22 +19,22 @@ import ( //nolint:lll,tagliatelle type Response struct { - CodeChallengeMethodsSupported []string `json:"code_challenge_methods_supported"` - 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"` + UserinfoEndpoint string `json:"userinfo_endpoint,omitempty"` AuthorizationEndpoint string `json:"authorization_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"` + 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"` } diff --git a/internal/middleware/basic_auth.go b/internal/middleware/basic_auth.go index 569fff0..43ecad8 100644 --- a/internal/middleware/basic_auth.go +++ b/internal/middleware/basic_auth.go @@ -23,7 +23,7 @@ const DefaultRealm string = "Restricted" const basic string = "basic" -//nolint: gochecknoglobals +// nolint: gochecknoglobals var DefaultBasicAuthConfig = BasicAuthConfig{ Skipper: DefaultSkipper, Realm: DefaultRealm, diff --git a/internal/middleware/csrf.go b/internal/middleware/csrf.go index c957d4d..5b18c9c 100644 --- a/internal/middleware/csrf.go +++ b/internal/middleware/csrf.go @@ -17,9 +17,8 @@ type ( // Skipper defines a function to skip middleware. Skipper Skipper - // TokenLength is the length of the generated token. - TokenLength uint8 - // Optional. Default value 32. + // ErrorHandler defines a function which is executed for returning custom errors. + ErrorHandler CSRFErrorHandler // TokenLookup is a string in the form of ":" or ":,:" that // is used to extract token from the request. @@ -52,6 +51,14 @@ type ( // Optional. Default value 86400 (24hr). 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. // Optional. Default value false. CookieSecure bool @@ -59,13 +66,6 @@ type ( // Indicates if CSRF cookie is HTTP only. // Optional. Default value false. 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. diff --git a/internal/middleware/jwt.go b/internal/middleware/jwt.go index 27fe40d..eb80626 100644 --- a/internal/middleware/jwt.go +++ b/internal/middleware/jwt.go @@ -15,25 +15,6 @@ import ( type ( // JWTConfig defines the config for JWT middleware. 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. // This is one of the three options to provide a token // validation key. The order of precedence is a user-defined @@ -52,49 +33,24 @@ type ( // provided. SigningKeys map[string]any - // Signing method used to check the token's signing algorithm. - // - // Optional. Default value HS256. - SigningMethod jwa.SignatureAlgorithm + // SuccessHandler defines a function which is executed for a + // valid token. + SuccessHandler JWTSuccessHandler - // Context key to store user information from the token into - // context. - // - // Optional. Default value "user". - ContextKey string + // ErrorHandler defines a function which is executed for an + // invalid token. It may be used to define a custom JWT error. + ErrorHandler JWTErrorHandler - // 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 + // ErrorHandlerWithContext is almost identical to ErrorHandler, + // but it's passed the current context. + ErrorHandlerWithContext JWTErrorHandlerWithContext - // TokenLookup is a string in the form of ":" or - // ":,:" that is used to extract - // token from the request. - // Optional. Default value "header:Authorization". - // Possible values: - // - "header:" - // - "query:" - // - "cookie:" - // - "form:" - // Multiply sources example: - // - "header: Authorization,cookie: myowncookie" - TokenLookup string + // BeforeFunc defines a function which is executed just before + // the middleware. + BeforeFunc BeforeFunc - // 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 - - // AuthScheme to be used in the Authorization header. - // - // Optional. Default value "Bearer". - AuthScheme string + // Skipper defines a function to skip middleware. + Skipper Skipper // KeyFunc defines a user-defined function that supplies the // 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 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 ":" or + // ":,:" that is used to extract + // token from the request. + // Optional. Default value "header:Authorization". + // Possible values: + // - "header:" + // - "query:" + // - "cookie:" + // - "form:" + // 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 // be called when ErrorHandlerWithContext decides to ignore the // error (by returning `nil`). This is useful when parts of your diff --git a/internal/session/repository/memory/memory_session.go b/internal/session/repository/memory/memory_session.go index e2bdb50..e6a460c 100644 --- a/internal/session/repository/memory/memory_session.go +++ b/internal/session/repository/memory/memory_session.go @@ -17,9 +17,9 @@ type ( } memorySessionRepository struct { - config domain.Config mutex *sync.RWMutex sessions map[string]Session + config domain.Config } ) diff --git a/internal/session/repository/sqlite3/sqlite3_session.go b/internal/session/repository/sqlite3/sqlite3_session.go index ea07e66..1f62953 100644 --- a/internal/session/repository/sqlite3/sqlite3_session.go +++ b/internal/session/repository/sqlite3/sqlite3_session.go @@ -17,9 +17,9 @@ import ( type ( Session struct { - CreatedAt sql.NullTime `db:"created_at"` Code string `db:"code"` Data string `db:"data"` + CreatedAt sql.NullTime `db:"created_at"` } sqlite3SessionRepository struct { diff --git a/internal/session/repository/sqlite3/sqlite3_session_test.go b/internal/session/repository/sqlite3/sqlite3_session_test.go index 8ed11a1..b55e81f 100644 --- a/internal/session/repository/sqlite3/sqlite3_session_test.go +++ b/internal/session/repository/sqlite3/sqlite3_session_test.go @@ -12,7 +12,7 @@ import ( "source.toby3d.me/toby3d/auth/internal/testing/sqltest" ) -// nolint: gochecknoglobals // slices cannot be contants +//nolint: gochecknoglobals // slices cannot be contants var tableColumns = []string{"created_at", "code", "data"} func TestCreate(t *testing.T) { diff --git a/internal/testing/bolttest/bolttest.go b/internal/testing/bolttest/bolttest.go index 778e5f2..518ef0a 100644 --- a/internal/testing/bolttest/bolttest.go +++ b/internal/testing/bolttest/bolttest.go @@ -19,7 +19,7 @@ func New(tb testing.TB) (*bolt.DB, func()) { filePath := tempFile.Name() - if err := tempFile.Close(); err != nil { + if err = tempFile.Close(); err != nil { tb.Fatal(err) } diff --git a/internal/ticket/delivery/http/ticket_http.go b/internal/ticket/delivery/http/ticket_http.go index e20cc41..486dc93 100644 --- a/internal/ticket/delivery/http/ticket_http.go +++ b/internal/ticket/delivery/http/ticket_http.go @@ -19,9 +19,9 @@ import ( ) type Handler struct { - config domain.Config matcher language.Matcher tickets ticket.UseCase + config domain.Config } func NewHandler(tickets ticket.UseCase, matcher language.Matcher, config domain.Config) *Handler { @@ -36,7 +36,7 @@ func (h *Handler) Handler() http.Handler { //nolint:exhaustivestruct chain := middleware.Chain{ 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) return r.Method == http.MethodPost && head == "ticket" diff --git a/internal/ticket/repository/memory/memory_ticket.go b/internal/ticket/repository/memory/memory_ticket.go index 08e12d8..c114b52 100644 --- a/internal/ticket/repository/memory/memory_ticket.go +++ b/internal/ticket/repository/memory/memory_ticket.go @@ -16,9 +16,9 @@ type ( } memoryTicketRepository struct { - config domain.Config mutex *sync.RWMutex tickets map[string]Ticket + config domain.Config } ) @@ -65,7 +65,7 @@ func (repo *memoryTicketRepository) GC() { defer ticker.Stop() for ts := range ticker.C { - ts := ts.UTC() + ts = ts.UTC() repo.mutex.RLock() diff --git a/internal/ticket/repository/sqlite3/sqlite3_ticket.go b/internal/ticket/repository/sqlite3/sqlite3_ticket.go index 7e823b4..7d259e1 100644 --- a/internal/ticket/repository/sqlite3/sqlite3_ticket.go +++ b/internal/ticket/repository/sqlite3/sqlite3_ticket.go @@ -16,10 +16,10 @@ import ( type ( Ticket struct { - CreatedAt sql.NullTime `db:"created_at"` Resource string `db:"resource"` Subject string `db:"subject"` Ticket string `db:"ticket"` + CreatedAt sql.NullTime `db:"created_at"` } sqlite3TicketRepository struct { diff --git a/internal/ticket/repository/sqlite3/sqlite3_ticket_test.go b/internal/ticket/repository/sqlite3/sqlite3_ticket_test.go index 7dee99e..ca2a426 100644 --- a/internal/ticket/repository/sqlite3/sqlite3_ticket_test.go +++ b/internal/ticket/repository/sqlite3/sqlite3_ticket_test.go @@ -12,7 +12,7 @@ import ( repository "source.toby3d.me/toby3d/auth/internal/ticket/repository/sqlite3" ) -//nolint: gochecknoglobals // slices cannot be contants +// nolint: gochecknoglobals // slices cannot be contants var tableColumns = []string{"created_at", "resource", "subject", "ticket"} func TestCreate(t *testing.T) { diff --git a/internal/token/delivery/http/token_http_schema.go b/internal/token/delivery/http/token_http_schema.go index da7b872..5449aa6 100644 --- a/internal/token/delivery/http/token_http_schema.go +++ b/internal/token/delivery/http/token_http_schema.go @@ -18,18 +18,18 @@ type ( } TokenRefreshRequest struct { - GrantType domain.GrantType `form:"grant_type"` // refresh_token - // The client ID that was used when the refresh token was issued. 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 // than the original access token. If omitted, is treated as // equal to the original scopes granted. Scope domain.Scopes `form:"scope"` - - // The refresh token previously offered to the client. - RefreshToken string `form:"refresh_token"` } TokenRevocationRequest struct { diff --git a/internal/token/repository/sqlite3/sqlite3_token.go b/internal/token/repository/sqlite3/sqlite3_token.go index 25b268b..6d35e41 100644 --- a/internal/token/repository/sqlite3/sqlite3_token.go +++ b/internal/token/repository/sqlite3/sqlite3_token.go @@ -16,11 +16,11 @@ import ( type ( Token struct { - CreatedAt sql.NullTime `db:"created_at"` AccessToken string `db:"access_token"` ClientID string `db:"client_id"` Me string `db:"me"` Scope string `db:"scope"` + CreatedAt sql.NullTime `db:"created_at"` } sqlite3TokenRepository struct { diff --git a/internal/user/delivery/http/user_http_schema.go b/internal/user/delivery/http/user_http_schema.go index 358d097..c6a27ff 100644 --- a/internal/user/delivery/http/user_http_schema.go +++ b/internal/user/delivery/http/user_http_schema.go @@ -3,10 +3,10 @@ package http import "source.toby3d.me/toby3d/auth/internal/domain" type UserInformationResponse struct { - Name string `json:"name,omitempty"` URL *domain.URL `json:"url,omitempty"` Photo *domain.URL `json:"photo,omitempty"` Email *domain.Email `json:"email,omitempty"` + Name string `json:"name,omitempty"` } func NewUserInformationResponse(in *domain.Profile, hasEmail bool) *UserInformationResponse { diff --git a/main.go b/main.go index 129c65f..cef8571 100644 --- a/main.go +++ b/main.go @@ -164,7 +164,7 @@ func main() { case "sqlite3": store, err := sqlx.Open("sqlite", config.Database.Path) if err != nil { - panic(err) + logger.Fatalln(err) } if err = store.Ping(); err != nil { @@ -226,7 +226,7 @@ func main() { logger.Printf("started at %s, available at %s", config.Server.GetAddress(), config.Server.GetRootURL()) - err := server.ListenAndServe() + err = server.ListenAndServe() if err != nil && !errors.Is(err, http.ErrServerClosed) { logger.Fatalln("cannot listen and serve:", err) } @@ -234,7 +234,7 @@ func main() { <-done - if err := server.Shutdown(ctx); err != nil { + if err = server.Shutdown(ctx); err != nil { logger.Fatalln("failed shutdown of server:", err) }