diff --git a/core/authenticate/authenticators.go b/core/authenticate/authenticators.go index 5dd361d36..0050ff71a 100644 --- a/core/authenticate/authenticators.go +++ b/core/authenticate/authenticators.go @@ -133,6 +133,30 @@ func authenticateWithAccessToken(ctx context.Context, s *Service) (Principal, er }, nil } + if claims[token.SubTypeClaimsKey] == schema.PATPrincipal { + patID := userID // sub = PAT ID + pat, err := s.userPATService.GetByID(ctx, patID) + if err != nil { + s.log.Debug("failed to get PAT", "err", err) + return Principal{}, errors.ErrUnauthenticated + } + if pat.ExpiresAt.Before(s.Now()) { + s.log.Debug("PAT has expired", "pat_id", patID) + return Principal{}, errors.ErrUnauthenticated + } + currentUser, err := s.userService.GetByID(ctx, pat.UserID) + if err != nil { + s.log.Debug("failed to get PAT owner", "err", err) + return Principal{}, errors.ErrUnauthenticated + } + return Principal{ + ID: pat.ID, + Type: schema.PATPrincipal, + PAT: &pat, + User: ¤tUser, + }, nil + } + currentUser, err := s.userService.GetByID(ctx, userID) if err != nil { s.log.Debug("failed to get user", "err", err) diff --git a/core/authenticate/service.go b/core/authenticate/service.go index 45a7754be..f963d3302 100644 --- a/core/authenticate/service.go +++ b/core/authenticate/service.go @@ -90,6 +90,7 @@ type TokenService interface { type UserPATService interface { Validate(ctx context.Context, value string) (patModels.PAT, error) + GetByID(ctx context.Context, id string) (patModels.PAT, error) } type Service struct { @@ -691,6 +692,9 @@ func (s Service) BuildToken(ctx context.Context, principal Principal, metadata m if principal.Type == schema.UserPrincipal && s.config.Token.Claims.AddUserEmailClaim { metadata[token.SubEmailClaimsKey] = principal.User.Email } + if principal.Type == schema.PATPrincipal && principal.User != nil { + metadata[token.UserIDClaimKey] = principal.User.ID + } return s.internalTokenService.Build(principal.ID, metadata) } diff --git a/core/authenticate/token/service.go b/core/authenticate/token/service.go index 6a568614d..dcd0fc418 100644 --- a/core/authenticate/token/service.go +++ b/core/authenticate/token/service.go @@ -24,6 +24,7 @@ const ( SubTypeClaimsKey = "sub_type" SubEmailClaimsKey = "email" SessionIDClaimKey = "sid" + UserIDClaimKey = "user_id" ) type Service struct { diff --git a/core/userpat/validator.go b/core/userpat/validator.go index f62d3972d..39c51a3af 100644 --- a/core/userpat/validator.go +++ b/core/userpat/validator.go @@ -67,3 +67,9 @@ func (v *Validator) Validate(ctx context.Context, value string) (models.PAT, err return pat, nil } + +// GetByID retrieves a PAT by ID. Used by the access token authenticator +// to reconstruct the PAT principal from JWT claims. +func (v *Validator) GetByID(ctx context.Context, id string) (models.PAT, error) { + return v.repo.GetByID(ctx, id) +}