Extend OAuth2 CIMD for compliance portal clients

Teach CIMD registration and discovery about per-portal client
metadata, and carry portal context through token and ID token
issuance for downstream session creation.

Signed-off-by: Bryan Frimin <bryan@probo.com>
This commit is contained in:
Bryan Frimin
2026-07-15 10:58:53 +02:00
parent ec91106063
commit 6d5217ae6e
8 changed files with 539 additions and 53 deletions

View File

@@ -68,7 +68,7 @@ type (
logger *log.Logger
gc *GarbageCollector
cimd *cimdFetcher
cimdAllowedClientIDs []string
cimdAllow CIMDAllowFunc
registry *oauth2scope.Registry
accessTokenDuration time.Duration
refreshTokenDuration time.Duration
@@ -171,12 +171,16 @@ func WithRegistry(registry *oauth2scope.Registry) Option {
}
}
func WithCIMDAllowedClientIDs(clientIDs []string) Option {
func WithCIMDAllow(fn CIMDAllowFunc) Option {
return func(s *Service) {
s.cimdAllowedClientIDs = clientIDs
s.cimdAllow = fn
}
}
func (s *Service) SetCIMDAllow(fn CIMDAllowFunc) {
s.cimdAllow = fn
}
func NewService(
pgClient *pg.Client,
signingKeys SigningKeys,
@@ -303,9 +307,14 @@ func (s *Service) GetClientByID(ctx context.Context, clientID gid.GID) (*coredat
func (s *Service) ExchangeAuthorizationCode(
ctx context.Context,
client *coredata.OAuth2Client,
clientIDRaw string,
codeValue, redirectURI, codeVerifier string,
) (*TokenResult, error) {
client, err := s.resolveClient(ctx, nil, clientIDRaw)
if err != nil {
return nil, err
}
var (
code = coredata.OAuth2AuthorizationCode{}
identity = coredata.Identity{}
@@ -1497,37 +1506,49 @@ func (s *Service) Authorize(
codeChallengeMethod = coredata.OAuth2CodeChallengeMethodS256
}
// RFC 6819 §5.2.3.2 / §5.2.4.1: public clients must always require
// explicit user consent since they cannot be strongly authenticated.
if client.TokenEndpointAuthMethod != coredata.OAuth2ClientTokenEndpointAuthMethodNone {
skipConsent := false
if client.TokenEndpointAuthMethod == coredata.OAuth2ClientTokenEndpointAuthMethodNone {
// RFC 6819 §5.2.3.2 / §5.2.4.1: public clients must always
// require explicit user consent since they cannot be strongly
// authenticated.
allowance, allowanceErr := s.cimdAllowance(ctx, client.ExternalClientID)
if allowanceErr != nil {
s.logger.WarnCtx(ctx, "cannot check cimd client allowance", log.Error(allowanceErr))
} else {
skipConsent = allowance.SkipsConsent()
}
} else {
var existingConsent coredata.OAuth2Consent
if err := existingConsent.LoadMatchingConsent(
skipConsent = existingConsent.LoadMatchingConsent(
ctx,
tx,
req.IdentityID,
client.ID,
requestedScopes,
); err == nil {
var err error
) == nil
}
code, err = s.issueAuthorizationCode(
ctx,
tx,
client,
req.IdentityID,
uri.URI(req.RedirectURI),
requestedScopes,
req.CodeChallenge,
codeChallengeMethod,
req.Nonce,
req.AuthTime,
)
if err != nil {
return fmt.Errorf("cannot issue authorization code: %w", err)
}
if skipConsent {
var err error
return nil
code, err = s.issueAuthorizationCode(
ctx,
tx,
client,
req.IdentityID,
uri.URI(req.RedirectURI),
requestedScopes,
req.CodeChallenge,
codeChallengeMethod,
req.Nonce,
req.AuthTime,
)
if err != nil {
return fmt.Errorf("cannot issue authorization code: %w", err)
}
return nil
}
now := time.Now()