diff --git a/auth/auth.go b/auth/auth.go index de413b5a..3d1537bf 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -90,6 +90,19 @@ func New[T User](config Configuration) (*Auth[T], error) { return s, nil } +// WithProviderAndErrorHandler registers a provider (if not nil) and +// an error handler (if not nil) and returns this "s" Auth instance. +// It's the same as calling AddProvider and SetErrorHandler at once. +// It's really useful when registering an Auth instance using the iris.Party.PartyConfigure +// method when a Provider of T and ErrorHandler is available through the registered Party's dependencies. +// +// Usage Example: +// api := app.Party("/api") +// api.EnsureStaticBindings().RegisterDependency( +// NewAuthProviderErrorHandler(), +// NewAuthCustomerProvider, +// auth.Must(auth.New[Customer](authConfig)).WithProviderAndErrorHandler, +// ) func (s *Auth[T]) WithProviderAndErrorHandler(provider Provider[T], errHandler ErrorHandler) *Auth[T] { if provider != nil { for i := range s.providers { diff --git a/auth/provider.go b/auth/provider.go index 1c1ccf9a..5e2171bb 100644 --- a/auth/provider.go +++ b/auth/provider.go @@ -18,13 +18,15 @@ type VerifiedToken = jwt.VerifiedToken // by a custom value type to provide user information to the Auth's // JWT Token Signer and Verifier. // -// A provider can implement Transformer and ErrorHandler and ClaimsProvider as well. +// A provider can optionally complete the Transformer, ClaimsProvider and +// ErrorHandler all in once when necessary. +// Set a provider using the AddProvider method of Auth type. type Provider[T User] interface { // Signin accepts a username (or email) and a password and should // return a valid T value or an error describing // the user authentication or verification reason of failure. // - // It's called on auth.SigninHandler + // It's called on Auth.SigninHandler Signin(ctx stdContext.Context, username, password string) (T, error) // ValidateToken accepts the standard JWT claims and the T value obtained @@ -34,7 +36,7 @@ type Provider[T User] interface { // the standard claim's (e.g. origin jwt token id). // It can be an empty method too which returns a nil error. // - // It's caleld on auth.VerifyHandler. + // It's caleld on Auth.VerifyHandler. ValidateToken(ctx stdContext.Context, standardClaims StandardClaims, t T) error // InvalidateToken is optional and can be used to allow tokens to be invalidated @@ -42,13 +44,13 @@ type Provider[T User] interface { // on a persistence storage and server can decide which token is valid or invalid. // It can be an empty method too which returns a nil error. // - // It's called on auth.SignoutHandler. + // It's called on Auth.SignoutHandler. InvalidateToken(ctx stdContext.Context, standardClaims StandardClaims, t T) error // InvalidateTokens is like InvalidateToken but it should invalidate // all tokens generated for a specific T value. // It can be an empty method too which returns a nil error. // - // It's called on auth.SignoutAllHandler. + // It's called on Auth.SignoutAllHandler. InvalidateTokens(ctx stdContext.Context, t T) error } @@ -60,27 +62,51 @@ type ClaimsProvider interface { GetRefreshTokenClaims(accessClaims StandardClaims) StandardClaims } +// Transformer is an optional interface which can be implemented by a Provider as well. +// Set a Transformer through Auth.SetTransformer or Auth.SetTransformerFunc or by implementing +// the Transform method inside a Provider which can be registered through the Auth.AddProvider +// method. +// +// A transformer is called on Auth.VerifyHandler before Provider.ValidateToken and it can +// be used to modify the T value based on the token's contents. It is mostly used +// to convert the json claims to T value manually, when they differ. type Transformer[T User] interface { Transform(ctx stdContext.Context, tok *VerifiedToken) (T, error) } +// TransformerFunc completes the Transformer interface. type TransformerFunc[T User] func(ctx stdContext.Context, tok *VerifiedToken) (T, error) +// Transform calls itself. func (fn TransformerFunc[T]) Transform(ctx stdContext.Context, tok *VerifiedToken) (T, error) { return fn(ctx, tok) } +// ErrorHandler is an optional interface which can be implemented by a Provider as well. +// +// ErrorHandler is the interface which controls the HTTP errors on +// Auth.SigninHandler, Auth.VerifyHandler, Auth.SignoutHandler and +// Auth.SignoutAllHandler handelrs. type ErrorHandler interface { + // InvalidArgument should handle any 400 (bad request) errors, + // e.g. invalid request body. InvalidArgument(ctx *context.Context, err error) + // Unauthenticated should handle any 401 (unauthenticated) errors, + // e.g. user not found or invalid credentials. Unauthenticated(ctx *context.Context, err error) } +// DefaultErrorHandler is the default registered ErrorHandler which can be +// replaced through the Auth.SetErrorHandler method. type DefaultErrorHandler struct{} +// InvalidArgument sends 400 (bad request) with "unable to parse body" as its message +// and the "err" value as its details. func (e *DefaultErrorHandler) InvalidArgument(ctx *context.Context, err error) { errors.InvalidArgument.Details(ctx, "unable to parse body", err.Error()) } +// Unauthenticated sends 401 (unauthenticated) with the "err" value as its message. func (e *DefaultErrorHandler) Unauthenticated(ctx *context.Context, err error) { errors.Unauthenticated.Err(ctx, err) } diff --git a/auth/user.go b/auth/user.go index f08b18ab..885afd20 100644 --- a/auth/user.go +++ b/auth/user.go @@ -9,18 +9,25 @@ import ( ) type ( + // StandardClaims is an alias of jwt.Claims, it holds the standard JWT claims. StandardClaims = jwt.Claims - User = interface{} // any type. + // User is an alias of an empty interface, it's here to declare the typeof T, + // which can be any custom struct type. + User = interface{} ) const accessTokenContextKey = "iris.auth.context.access_token" +// GetAccessToken accepts the iris Context and returns the raw access token value. +// It's only available after Auth.VerifyHandler is executed. func GetAccessToken(ctx *context.Context) string { return ctx.Values().GetString(accessTokenContextKey) } const standardClaimsContextKey = "iris.auth.context.standard_claims" +// GetStandardClaims accepts the iris Context and returns the standard token's claims. +// It's only available after Auth.VerifyHandler is executed. func GetStandardClaims(ctx *context.Context) StandardClaims { if v := ctx.Values().Get(standardClaimsContextKey); v != nil { if c, ok := v.(StandardClaims); ok { @@ -31,12 +38,10 @@ func GetStandardClaims(ctx *context.Context) StandardClaims { return StandardClaims{} } -func (s *Auth[T]) GetStandardClaims(ctx *context.Context) StandardClaims { - return GetStandardClaims(ctx) -} - const userContextKey = "iris.auth.context.user" +// GetUser is the package-level function of the Auth.GetUser method. +// It returns the T user value after Auth.VerifyHandler is executed. func GetUser[T User](ctx *context.Context) T { if v := ctx.Values().Get(userContextKey); v != nil { if t, ok := v.(T); ok { @@ -48,6 +53,8 @@ func GetUser[T User](ctx *context.Context) T { return empty } +// GetUser accepts the iris Context and returns the T custom user/claims struct value. +// It's only available after Auth.VerifyHandler is executed. func (s *Auth[T]) GetUser(ctx *context.Context) T { return GetUser[T](ctx) }