package user import ( "context" "net/http" "strings" "time" "forge.cadoles.com/Cadoles/emissary/internal/auth" "forge.cadoles.com/Cadoles/emissary/internal/datastore" "forge.cadoles.com/Cadoles/emissary/internal/jwk" "github.com/lestrrat-go/jwx/v2/jwt" "github.com/pkg/errors" "gitlab.com/wpetit/goweb/logger" ) const DefaultAcceptableSkew = 5 * time.Minute type ( GetKeySet func(context.Context) (jwk.Set, error) GetTokenRole func(context.Context, jwt.Token) (string, error) GetTokenTenant func(context.Context, jwt.Token) (string, error) ) type Authenticator struct { getKeySet GetKeySet getTokenRole GetTokenRole getTokenTenant GetTokenTenant acceptableSkew time.Duration } // Authenticate implements auth.Authenticator. func (a *Authenticator) Authenticate(ctx context.Context, r *http.Request) (auth.User, error) { authorization := r.Header.Get("Authorization") if authorization == "" { return nil, errors.WithStack(auth.ErrUnauthenticated) } rawToken := strings.TrimPrefix(authorization, "Bearer ") if rawToken == "" { return nil, errors.WithStack(auth.ErrUnauthenticated) } keys, err := a.getKeySet(ctx) if err != nil { return nil, errors.WithStack(err) } token, err := parseToken(ctx, keys, rawToken, a.acceptableSkew) if err != nil { logger.Debug(ctx, "could not parse jwt token", logger.CapturedE(errors.WithStack(err))) return nil, errors.WithStack(auth.ErrUnauthenticated) } rawRole, err := a.getTokenRole(ctx, token) if err != nil { logger.Debug(ctx, "could not retrieve token role", logger.CapturedE(errors.WithStack(err))) return nil, errors.WithStack(auth.ErrUnauthenticated) } if !isValidRole(rawRole) { return nil, errors.WithStack(auth.ErrUnauthorized) } rawTenantID, err := a.getTokenTenant(ctx, token) if err != nil { logger.Debug(ctx, "could not retrieve token tenant", logger.CapturedE(errors.WithStack(err))) return nil, errors.WithStack(auth.ErrUnauthenticated) } tenantID, err := datastore.ParseTenantID(rawTenantID) if err != nil { logger.Debug(ctx, "could not retrieve token tenant", logger.CapturedE(errors.WithStack(err))) return nil, errors.WithStack(auth.ErrUnauthenticated) } user := &User{ subject: token.Subject(), role: Role(rawRole), tenantID: tenantID, } return user, nil } func NewAuthenticator(getKeySet GetKeySet, getTokenRole GetTokenRole, getTokenTenant GetTokenTenant, acceptableSkew time.Duration) *Authenticator { return &Authenticator{ getTokenRole: getTokenRole, getTokenTenant: getTokenTenant, getKeySet: getKeySet, acceptableSkew: acceptableSkew, } } var _ auth.Authenticator = &Authenticator{}