♻️ Simplify error usage in auth package
This commit is contained in:
parent
b60aab7be5
commit
6b05c5170f
|
@ -2,7 +2,7 @@ package http
|
|||
|
||||
import (
|
||||
"crypto/subtle"
|
||||
"fmt"
|
||||
"errors"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
|
@ -11,7 +11,6 @@ import (
|
|||
http "github.com/valyala/fasthttp"
|
||||
"golang.org/x/text/language"
|
||||
"golang.org/x/text/message"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"source.toby3d.me/toby3d/form"
|
||||
"source.toby3d.me/toby3d/middleware"
|
||||
|
@ -68,6 +67,7 @@ type (
|
|||
Authorize string `form:"authorize"`
|
||||
CodeChallenge string `form:"code_challenge"`
|
||||
State string `form:"state"`
|
||||
Provider string `form:"provider"`
|
||||
}
|
||||
|
||||
ExchangeRequest struct {
|
||||
|
@ -99,6 +99,7 @@ type (
|
|||
Clients client.UseCase
|
||||
Config *domain.Config
|
||||
Matcher language.Matcher
|
||||
Providers []*domain.Provider
|
||||
}
|
||||
|
||||
RequestHandler struct {
|
||||
|
@ -106,6 +107,7 @@ type (
|
|||
config *domain.Config
|
||||
matcher language.Matcher
|
||||
useCase auth.UseCase
|
||||
providers []*domain.Provider
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -115,6 +117,7 @@ func NewRequestHandler(opts NewRequestHandlerOptions) *RequestHandler {
|
|||
config: opts.Config,
|
||||
matcher: opts.Matcher,
|
||||
useCase: opts.Auth,
|
||||
providers: opts.Providers,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -135,8 +138,10 @@ func (h *RequestHandler) Register(r *router.Router) {
|
|||
middleware.BasicAuthWithConfig(middleware.BasicAuthConfig{
|
||||
Skipper: func(ctx *http.RequestCtx) bool {
|
||||
matched, _ := path.Match("/api/*", string(ctx.Path()))
|
||||
provider := string(ctx.QueryArgs().Peek("provider"))
|
||||
|
||||
return !matched
|
||||
return !ctx.IsPost() || !matched ||
|
||||
(provider != "" && provider != domain.DefaultProviderDirect.UID)
|
||||
},
|
||||
Validator: func(ctx *http.RequestCtx, login, password string) (bool, error) {
|
||||
// TODO(toby3d): change this
|
||||
|
@ -204,11 +209,7 @@ func (h *RequestHandler) handleVerify(ctx *http.RequestCtx) {
|
|||
req := new(VerifyRequest)
|
||||
if err := req.bind(ctx); err != nil {
|
||||
ctx.SetStatusCode(http.StatusBadRequest)
|
||||
encoder.Encode(domain.Error{
|
||||
Code: "invalid_request",
|
||||
Description: err.Error(),
|
||||
Frame: xerrors.Caller(1),
|
||||
})
|
||||
encoder.Encode(err)
|
||||
|
||||
return
|
||||
}
|
||||
|
@ -218,8 +219,8 @@ func (h *RequestHandler) handleVerify(ctx *http.RequestCtx) {
|
|||
req.RedirectURI.CopyTo(u)
|
||||
|
||||
if strings.EqualFold(req.Authorize, "deny") {
|
||||
u.QueryArgs().Set("error", "access_denied")
|
||||
u.QueryArgs().Set("error_description", "user deny authorization request")
|
||||
domain.NewError(domain.ErrorCodeAccessDenied, "user deny authorization request", "", req.State).
|
||||
SetReirectURI(u)
|
||||
ctx.Redirect(u.String(), http.StatusFound)
|
||||
|
||||
return
|
||||
|
@ -235,10 +236,7 @@ func (h *RequestHandler) handleVerify(ctx *http.RequestCtx) {
|
|||
})
|
||||
if err != nil {
|
||||
ctx.SetStatusCode(http.StatusInternalServerError)
|
||||
encoder.Encode(domain.Error{
|
||||
Description: err.Error(),
|
||||
Frame: xerrors.Caller(1),
|
||||
})
|
||||
encoder.Encode(err)
|
||||
|
||||
return
|
||||
}
|
||||
|
@ -286,16 +284,32 @@ func (h *RequestHandler) handleExchange(ctx *http.RequestCtx) {
|
|||
}
|
||||
|
||||
func (r *AuthorizeRequest) bind(ctx *http.RequestCtx) error {
|
||||
indieAuthError := new(domain.Error)
|
||||
if err := form.Unmarshal(ctx.QueryArgs(), r); err != nil {
|
||||
return domain.Error{
|
||||
Code: "invalid_request",
|
||||
Description: err.Error(),
|
||||
Frame: xerrors.Caller(1),
|
||||
if errors.As(err, indieAuthError) {
|
||||
return indieAuthError
|
||||
}
|
||||
|
||||
return domain.NewError(
|
||||
domain.ErrorCodeInvalidRequest,
|
||||
err.Error(),
|
||||
"https://indieauth.net/source/#authorization-request",
|
||||
)
|
||||
}
|
||||
|
||||
r.Scope = make(domain.Scopes, 0)
|
||||
parseScope(r.Scope, ctx.QueryArgs().Peek("scope"))
|
||||
|
||||
if err := parseScope(r.Scope, ctx.QueryArgs().Peek("scope")); err != nil {
|
||||
if errors.As(err, indieAuthError) {
|
||||
return indieAuthError
|
||||
}
|
||||
|
||||
return domain.NewError(
|
||||
domain.ErrorCodeInvalidScope,
|
||||
err.Error(),
|
||||
"https://indieweb.org/scope",
|
||||
)
|
||||
}
|
||||
|
||||
if r.ResponseType == domain.ResponseTypeID {
|
||||
r.ResponseType = domain.ResponseTypeCode
|
||||
|
@ -305,12 +319,18 @@ func (r *AuthorizeRequest) bind(ctx *http.RequestCtx) error {
|
|||
}
|
||||
|
||||
func (r *VerifyRequest) bind(ctx *http.RequestCtx) error {
|
||||
indieAuthError := new(domain.Error)
|
||||
|
||||
if err := form.Unmarshal(ctx.PostArgs(), r); err != nil {
|
||||
return domain.Error{
|
||||
Code: "invalid_request",
|
||||
Description: err.Error(),
|
||||
Frame: xerrors.Caller(1),
|
||||
if errors.As(err, indieAuthError) {
|
||||
return indieAuthError
|
||||
}
|
||||
|
||||
return domain.NewError(
|
||||
domain.ErrorCodeInvalidRequest,
|
||||
err.Error(),
|
||||
"https://indieauth.net/source/#authorization-request",
|
||||
)
|
||||
}
|
||||
|
||||
r.Scope = make(domain.Scopes, 0)
|
||||
|
@ -320,24 +340,31 @@ func (r *VerifyRequest) bind(ctx *http.RequestCtx) error {
|
|||
r.ResponseType = domain.ResponseTypeCode
|
||||
}
|
||||
|
||||
r.Provider = strings.ToLower(r.Provider)
|
||||
|
||||
if !strings.EqualFold(r.Authorize, "allow") && !strings.EqualFold(r.Authorize, "deny") {
|
||||
return domain.Error{
|
||||
Code: "invalid_request",
|
||||
Description: "cannot validate verification request",
|
||||
Frame: xerrors.Caller(1),
|
||||
}
|
||||
return domain.NewError(
|
||||
domain.ErrorCodeInvalidRequest,
|
||||
"cannot validate verification request",
|
||||
"https://indieauth.net/source/#authorization-request",
|
||||
)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *ExchangeRequest) bind(ctx *http.RequestCtx) error {
|
||||
indieAuthError := new(domain.Error)
|
||||
if err := form.Unmarshal(ctx.PostArgs(), r); err != nil {
|
||||
return domain.Error{
|
||||
Code: "invalid_request",
|
||||
Description: err.Error(),
|
||||
Frame: xerrors.Caller(1),
|
||||
if errors.As(err, indieAuthError) {
|
||||
return indieAuthError
|
||||
}
|
||||
|
||||
return domain.NewError(
|
||||
domain.ErrorCodeInvalidRequest,
|
||||
"cannot validate verification request",
|
||||
"https://indieauth.net/source/#redeeming-the-authorization-code",
|
||||
)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -358,11 +385,11 @@ func parseScope(dst domain.Scopes, src ...[]byte) error {
|
|||
for _, rawScope := range scopes {
|
||||
scope, err := domain.ParseScope(string(rawScope))
|
||||
if err != nil {
|
||||
return &domain.Error{
|
||||
Code: "invalid_request",
|
||||
Description: fmt.Sprintf("cannot parse scope: %v", err),
|
||||
Frame: xerrors.Caller(1),
|
||||
}
|
||||
return domain.NewError(
|
||||
domain.ErrorCodeInvalidScope,
|
||||
err.Error(),
|
||||
"https://indieweb.org/scope#IndieAuth_Scopes",
|
||||
)
|
||||
}
|
||||
|
||||
dst = append(dst, scope)
|
||||
|
|
|
@ -19,6 +19,7 @@ import (
|
|||
clientrepo "source.toby3d.me/website/indieauth/internal/client/repository/memory"
|
||||
clientucase "source.toby3d.me/website/indieauth/internal/client/usecase"
|
||||
"source.toby3d.me/website/indieauth/internal/domain"
|
||||
profilerepo "source.toby3d.me/website/indieauth/internal/profile/repository/memory"
|
||||
sessionrepo "source.toby3d.me/website/indieauth/internal/session/repository/memory"
|
||||
"source.toby3d.me/website/indieauth/internal/testing/httptest"
|
||||
userrepo "source.toby3d.me/website/indieauth/internal/user/repository/memory"
|
||||
|
@ -33,19 +34,25 @@ func TestRender(t *testing.T) {
|
|||
s := session.New(session.NewDefaultConfig())
|
||||
require.NoError(t, s.SetProvider(provider))
|
||||
|
||||
me := domain.TestMe(t)
|
||||
me := domain.TestMe(t, "https://user.example.net")
|
||||
c := domain.TestClient(t)
|
||||
config := domain.TestConfig(t)
|
||||
store := new(sync.Map)
|
||||
store.Store(path.Join(userrepo.DefaultPathPrefix, me.String()), domain.TestUser(t))
|
||||
user := domain.TestUser(t)
|
||||
store.Store(path.Join(userrepo.DefaultPathPrefix, me.String()), user)
|
||||
store.Store(path.Join(clientrepo.DefaultPathPrefix, c.ID.String()), c)
|
||||
store.Store(path.Join(profilerepo.DefaultPathPrefix, me.String()), user.Profile)
|
||||
|
||||
r := router.New()
|
||||
delivery.NewRequestHandler(delivery.NewRequestHandlerOptions{
|
||||
Clients: clientucase.NewClientUseCase(clientrepo.NewMemoryClientRepository(store)),
|
||||
Config: config,
|
||||
Matcher: language.NewMatcher(message.DefaultCatalog.Languages()),
|
||||
Auth: ucase.NewAuthUseCase(sessionrepo.NewMemorySessionRepository(config, store), config),
|
||||
Auth: ucase.NewAuthUseCase(
|
||||
sessionrepo.NewMemorySessionRepository(config, store),
|
||||
profilerepo.NewMemoryProfileRepository(store),
|
||||
config,
|
||||
),
|
||||
}).Register(r)
|
||||
|
||||
client, _, cleanup := httptest.New(t, r.Handler)
|
||||
|
|
|
@ -9,17 +9,17 @@ import (
|
|||
type (
|
||||
GenerateOptions struct {
|
||||
ClientID *domain.ClientID
|
||||
Me *domain.Me
|
||||
RedirectURI *domain.URL
|
||||
CodeChallenge string
|
||||
CodeChallengeMethod domain.CodeChallengeMethod
|
||||
Scope domain.Scopes
|
||||
Me *domain.Me
|
||||
CodeChallenge string
|
||||
}
|
||||
|
||||
ExchangeOptions struct {
|
||||
Code string
|
||||
ClientID *domain.ClientID
|
||||
RedirectURI *domain.URL
|
||||
Code string
|
||||
CodeVerifier string
|
||||
}
|
||||
|
||||
|
|
|
@ -4,22 +4,24 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"source.toby3d.me/website/indieauth/internal/auth"
|
||||
"source.toby3d.me/website/indieauth/internal/domain"
|
||||
"source.toby3d.me/website/indieauth/internal/profile"
|
||||
"source.toby3d.me/website/indieauth/internal/random"
|
||||
"source.toby3d.me/website/indieauth/internal/session"
|
||||
)
|
||||
|
||||
type authUseCase struct {
|
||||
config *domain.Config
|
||||
profiles profile.Repository
|
||||
sessions session.Repository
|
||||
}
|
||||
|
||||
func NewAuthUseCase(sessions session.Repository, config *domain.Config) auth.UseCase {
|
||||
// NewAuthUseCase creates a new authentication use case.
|
||||
func NewAuthUseCase(sessions session.Repository, profiles profile.Repository, config *domain.Config) auth.UseCase {
|
||||
return &authUseCase{
|
||||
config: config,
|
||||
profiles: profiles,
|
||||
sessions: sessions,
|
||||
}
|
||||
}
|
||||
|
@ -48,36 +50,33 @@ func (useCase *authUseCase) Generate(ctx context.Context, opts auth.GenerateOpti
|
|||
func (useCase *authUseCase) Exchange(ctx context.Context, opts auth.ExchangeOptions) (*domain.Me, error) {
|
||||
session, err := useCase.sessions.GetAndDelete(ctx, opts.Code)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("cannot find session in store: %w", err)
|
||||
}
|
||||
|
||||
if opts.ClientID.String() != session.ClientID.String() {
|
||||
return nil, domain.Error{
|
||||
Code: "invalid_request",
|
||||
Description: "client's URL MUST match the client_id used in the authentication request",
|
||||
URI: "https://indieauth.net/source/#request",
|
||||
Frame: xerrors.Caller(1),
|
||||
}
|
||||
return nil, domain.NewError(
|
||||
domain.ErrorCodeInvalidRequest,
|
||||
"client's URL MUST match the client_id used in the authentication request",
|
||||
"https://indieauth.net/source/#request",
|
||||
)
|
||||
}
|
||||
|
||||
if opts.RedirectURI.String() != session.RedirectURI.String() {
|
||||
return nil, domain.Error{
|
||||
Code: "invalid_request",
|
||||
Description: "client's redirect URL MUST match the initial authentication request",
|
||||
URI: "https://indieauth.net/source/#request",
|
||||
Frame: xerrors.Caller(1),
|
||||
}
|
||||
return nil, domain.NewError(
|
||||
domain.ErrorCodeInvalidRequest,
|
||||
"client's redirect URL MUST match the initial authentication request",
|
||||
"https://indieauth.net/source/#request",
|
||||
)
|
||||
}
|
||||
|
||||
if session.CodeChallenge != "" &&
|
||||
!session.CodeChallengeMethod.Validate(session.CodeChallenge, opts.CodeVerifier) {
|
||||
return nil, domain.Error{
|
||||
Code: "invalid_request",
|
||||
Description: "code_verifier is not hashes to the same value as given in " +
|
||||
"the code_challenge in the original authorization request",
|
||||
URI: "https://indieauth.net/source/#request",
|
||||
Frame: xerrors.Caller(1),
|
||||
}
|
||||
return nil, domain.NewError(
|
||||
domain.ErrorCodeInvalidRequest,
|
||||
"code_verifier is not hashes to the same value as given in the code_challenge in the original "+
|
||||
"authorization request",
|
||||
"https://indieauth.net/source/#request",
|
||||
)
|
||||
}
|
||||
|
||||
return session.Me, nil
|
||||
|
|
Loading…
Reference in New Issue