package oidc import ( "slices" "time" "github.com/ory/fosite" "github.com/pocket-id/pocket-id/backend/internal/model" ) type Client struct { model.OidcClient apiScopes []string apiAudiences []string } func (c Client) GetID() string { return c.ID } // GetHashedSecret returns the hash of the client's most recently created secret that is still active // Fosite checks this one first and then falls back to GetRotatedHashes for the others func (c Client) GetHashedSecret() []byte { active := c.Credentials.ActiveSecrets() if len(active) == 0 { return nil } return active[0].EncodedHash() } // GetRotatedHashes returns the hashes of every active secret except the one returned by GetHashedSecret func (c Client) GetRotatedHashes() [][]byte { active := c.Credentials.ActiveSecrets() if len(active) <= 1 { return nil } hashes := make([][]byte, len(active)-1) for i, secret := range active[1:] { hashes[i] = secret.EncodedHash() } return hashes } func (c Client) GetRedirectURIs() []string { return c.CallbackURLs } func (c Client) GetGrantTypes() fosite.Arguments { grantTypes := fosite.Arguments{ string(fosite.GrantTypeAuthorizationCode), string(fosite.GrantTypeRefreshToken), string(fosite.GrantTypeDeviceCode), } if !c.IsPublic() { grantTypes = append(grantTypes, string(fosite.GrantTypeClientCredentials)) } if !c.IsMetadataDocument() { return grantTypes } if len(c.MetadataGrantTypes) == 0 { return fosite.Arguments{string(fosite.GrantTypeAuthorizationCode)} } // If the client is a CIMD client, we need to filter the grant types based on the metadata document. allowed := make(fosite.Arguments, 0, len(c.MetadataGrantTypes)) for _, value := range c.MetadataGrantTypes { if slices.Contains([]string(grantTypes), value) { allowed = append(allowed, value) } } return allowed } func (c Client) GetResponseTypes() fosite.Arguments { return fosite.Arguments{"code"} } func (c Client) GetScopes() fosite.Arguments { scopes := make(fosite.Arguments, 5, 5+len(c.apiScopes)) scopes[0] = "openid" scopes[1] = "profile" scopes[2] = "email" scopes[3] = "groups" scopes[4] = "offline_access" scopes = append(scopes, c.apiScopes...) return scopes } func (c Client) IsPublic() bool { return c.OidcClient.IsPublic } func (c Client) GetAudience() fosite.Arguments { audience := make(fosite.Arguments, 0, len(c.apiAudiences)+1) audience = append(audience, c.ID) audience = append(audience, c.apiAudiences...) return audience } func (c Client) GetResponseModes() []fosite.ResponseModeType { return []fosite.ResponseModeType{ fosite.ResponseModeQuery, fosite.ResponseModeFragment, fosite.ResponseModeFormPost, } } func (c Client) GetEffectiveLifespan(grantType fosite.GrantType, tokenType fosite.TokenType, fallback time.Duration) time.Duration { var minutes int64 switch tokenType { case fosite.AccessToken: switch grantType { case fosite.GrantTypeAuthorizationCode, fosite.GrantTypeRefreshToken, fosite.GrantTypeDeviceCode, fosite.GrantTypeClientCredentials: minutes = c.AccessTokenDurationMinutes case fosite.GrantTypeImplicit, fosite.GrantTypePassword, fosite.GrantTypeJWTBearer: return fallback default: return fallback } case fosite.RefreshToken: switch grantType { case fosite.GrantTypeAuthorizationCode, fosite.GrantTypeRefreshToken, fosite.GrantTypeDeviceCode: minutes = c.RefreshTokenDurationMinutes case fosite.GrantTypeImplicit, fosite.GrantTypePassword, fosite.GrantTypeClientCredentials, fosite.GrantTypeJWTBearer: return fallback default: return fallback } case fosite.AuthorizeCode, fosite.IDToken, fosite.UserCode, fosite.DeviceCode, fosite.PushedAuthorizeRequestContext: return fallback default: return fallback } if !model.IsValidTokenDurationMinutes(minutes) { return fallback } return time.Duration(minutes) * time.Minute }