package agent import ( "context" "net/http" "strings" "time" "forge.cadoles.com/Cadoles/emissary/internal/auth" "forge.cadoles.com/Cadoles/emissary/internal/datastore" "github.com/lestrrat-go/jwx/v2/jws" "github.com/lestrrat-go/jwx/v2/jwt" "github.com/pkg/errors" "gitlab.com/wpetit/goweb/logger" ) const DefaultAcceptableSkew = 5 * time.Minute type Authenticator struct { repo datastore.AgentRepository 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) } token, err := jwt.Parse([]byte(rawToken), jwt.WithVerify(false)) if err != nil { logger.Debug(ctx, "could not parse jwt token", logger.CapturedE(errors.WithStack(err))) return nil, errors.WithStack(auth.ErrUnauthenticated) } rawThumbprint, exists := token.Get(keyThumbprint) if !exists { return nil, errors.WithStack(auth.ErrUnauthenticated) } thumbrint, ok := rawThumbprint.(string) if !ok { logger.Debug(ctx, "unexpected claim value", logger.F("claim", rawThumbprint), logger.F("value", rawThumbprint)) return nil, errors.WithStack(auth.ErrUnauthenticated) } agents, _, err := a.repo.Query( ctx, datastore.WithAgentQueryThumbprints(thumbrint), datastore.WithAgentQueryLimit(1), ) if err != nil { return nil, errors.WithStack(err) } if len(agents) != 1 { logger.Debug(ctx, "unexpected number of found agents", logger.F("total", len(agents))) return nil, errors.WithStack(auth.ErrUnauthenticated) } agent, err := a.repo.Get( ctx, agents[0].ID, ) if err != nil { return nil, errors.WithStack(err) } _, err = jwt.Parse( []byte(rawToken), jwt.WithKeySet(agent.KeySet.Set, jws.WithRequireKid(false)), jwt.WithValidate(true), jwt.WithAcceptableSkew(a.acceptableSkew), ) if err != nil { logger.Error(ctx, "could not parse jwt", logger.CapturedE(errors.WithStack(err))) return nil, errors.WithStack(auth.ErrUnauthenticated) } contactedAt := time.Now().UTC() agent, err = a.repo.Update(ctx, agent.ID, datastore.WithAgentUpdateContactedAt(contactedAt)) if err != nil { return nil, errors.WithStack(auth.ErrUnauthenticated) } user := &User{ agent: agent, } return user, nil } func NewAuthenticator(repo datastore.AgentRepository, acceptableSkew time.Duration) *Authenticator { return &Authenticator{ repo: repo, acceptableSkew: acceptableSkew, } } var _ auth.Authenticator = &Authenticator{}