feat: add support for "select_account" prompt (#1453)

Co-authored-by: Elias Schneider <login@eliasschneider.com>
This commit is contained in:
Alessandro (Ale) Segala
2026-04-26 10:26:21 -07:00
committed by GitHub
parent e33a9b8c88
commit f4706cd6cc
9 changed files with 365 additions and 314 deletions

View File

@@ -8,21 +8,28 @@ import (
type AppError interface {
error
HttpStatusCode() int
}
type AppErrorDescription interface {
AppError
Description() string
}
// Custom error types for various conditions
type AlreadyInUseError struct {
Property string
}
func (e *AlreadyInUseError) Error() string {
func (e AlreadyInUseError) Error() string {
return e.Property + " is already in use"
}
func (e *AlreadyInUseError) HttpStatusCode() int { return http.StatusBadRequest }
func (e AlreadyInUseError) HttpStatusCode() int { return http.StatusBadRequest }
func (e *AlreadyInUseError) Is(target error) bool {
func (e AlreadyInUseError) Is(target error) bool {
// Ignore the field property when checking if an error is of the type AlreadyInUseError
x := &AlreadyInUseError{}
return errors.As(target, &x)
@@ -30,445 +37,357 @@ func (e *AlreadyInUseError) Is(target error) bool {
type SetupNotAvailableError struct{}
func (e *SetupNotAvailableError) Error() string { return "not found" }
func (e *SetupNotAvailableError) HttpStatusCode() int { return http.StatusNotFound }
func (e SetupNotAvailableError) Error() string { return "not found" }
func (e SetupNotAvailableError) HttpStatusCode() int { return http.StatusNotFound }
type TokenInvalidOrExpiredError struct{}
func (e *TokenInvalidOrExpiredError) Error() string { return "token is invalid or expired" }
func (e *TokenInvalidOrExpiredError) HttpStatusCode() int { return http.StatusUnauthorized }
func (e TokenInvalidOrExpiredError) Error() string { return "token is invalid or expired" }
func (e TokenInvalidOrExpiredError) HttpStatusCode() int { return http.StatusUnauthorized }
type DeviceCodeInvalid struct{}
func (e *DeviceCodeInvalid) Error() string {
func (e DeviceCodeInvalid) Error() string {
return "one time access code must be used on the device it was generated for"
}
func (e *DeviceCodeInvalid) HttpStatusCode() int { return http.StatusUnauthorized }
func (e DeviceCodeInvalid) HttpStatusCode() int { return http.StatusUnauthorized }
type TokenInvalidError struct{}
func (e *TokenInvalidError) Error() string {
return "Token is invalid"
}
func (e *TokenInvalidError) HttpStatusCode() int { return http.StatusUnauthorized }
func (e TokenInvalidError) Error() string { return "Token is invalid" }
func (e TokenInvalidError) HttpStatusCode() int { return http.StatusUnauthorized }
type OidcMissingAuthorizationError struct{}
func (e *OidcMissingAuthorizationError) Error() string { return "missing authorization" }
func (e *OidcMissingAuthorizationError) HttpStatusCode() int { return http.StatusForbidden }
func (e OidcMissingAuthorizationError) Error() string { return "missing authorization" }
func (e OidcMissingAuthorizationError) HttpStatusCode() int { return http.StatusForbidden }
type OidcGrantTypeNotSupportedError struct{}
func (e *OidcGrantTypeNotSupportedError) Error() string { return "grant type not supported" }
func (e *OidcGrantTypeNotSupportedError) HttpStatusCode() int { return http.StatusBadRequest }
func (e OidcGrantTypeNotSupportedError) Error() string { return "grant type not supported" }
func (e OidcGrantTypeNotSupportedError) HttpStatusCode() int { return http.StatusBadRequest }
type OidcMissingClientCredentialsError struct{}
func (e *OidcMissingClientCredentialsError) Error() string { return "client id or secret not provided" }
func (e *OidcMissingClientCredentialsError) HttpStatusCode() int { return http.StatusBadRequest }
func (e OidcMissingClientCredentialsError) Error() string { return "client id or secret not provided" }
func (e OidcMissingClientCredentialsError) HttpStatusCode() int { return http.StatusBadRequest }
type OidcClientSecretInvalidError struct{}
func (e *OidcClientSecretInvalidError) Error() string { return "invalid client secret" }
func (e *OidcClientSecretInvalidError) HttpStatusCode() int { return http.StatusUnauthorized }
func (e OidcClientSecretInvalidError) Error() string { return "invalid client secret" }
func (e OidcClientSecretInvalidError) HttpStatusCode() int { return http.StatusUnauthorized }
type OidcClientAssertionInvalidError struct{}
func (e *OidcClientAssertionInvalidError) Error() string { return "invalid client assertion" }
func (e *OidcClientAssertionInvalidError) HttpStatusCode() int { return http.StatusUnauthorized }
func (e OidcClientAssertionInvalidError) Error() string { return "invalid client assertion" }
func (e OidcClientAssertionInvalidError) HttpStatusCode() int { return http.StatusUnauthorized }
type OidcInvalidAuthorizationCodeError struct{}
func (e *OidcInvalidAuthorizationCodeError) Error() string { return "invalid authorization code" }
func (e *OidcInvalidAuthorizationCodeError) HttpStatusCode() int { return http.StatusBadRequest }
func (e OidcInvalidAuthorizationCodeError) Error() string { return "invalid authorization code" }
func (e OidcInvalidAuthorizationCodeError) HttpStatusCode() int { return http.StatusBadRequest }
type OidcClientNotFoundError struct{}
func (e *OidcClientNotFoundError) Error() string { return "client not found" }
func (e *OidcClientNotFoundError) HttpStatusCode() int { return http.StatusNotFound }
func (e OidcClientNotFoundError) Error() string { return "client not found" }
func (e OidcClientNotFoundError) HttpStatusCode() int { return http.StatusNotFound }
type OidcMissingCallbackURLError struct{}
func (e *OidcMissingCallbackURLError) Error() string {
func (e OidcMissingCallbackURLError) Error() string {
return "unable to detect callback url, it might be necessary for an admin to fix this"
}
func (e *OidcMissingCallbackURLError) HttpStatusCode() int { return http.StatusBadRequest }
func (e OidcMissingCallbackURLError) HttpStatusCode() int { return http.StatusBadRequest }
type OidcInvalidCallbackURLError struct{}
func (e *OidcInvalidCallbackURLError) Error() string {
func (e OidcInvalidCallbackURLError) Error() string {
return "invalid callback URL, it might be necessary for an admin to fix this"
}
func (e *OidcInvalidCallbackURLError) HttpStatusCode() int { return http.StatusBadRequest }
func (e OidcInvalidCallbackURLError) HttpStatusCode() int { return http.StatusBadRequest }
type FileTypeNotSupportedError struct{}
func (e *FileTypeNotSupportedError) Error() string { return "file type not supported" }
func (e *FileTypeNotSupportedError) HttpStatusCode() int { return http.StatusBadRequest }
func (e FileTypeNotSupportedError) Error() string { return "file type not supported" }
func (e FileTypeNotSupportedError) HttpStatusCode() int { return http.StatusBadRequest }
type FileTooLargeError struct {
MaxSize string
}
func (e *FileTooLargeError) Error() string {
func (e FileTooLargeError) Error() string {
return fmt.Sprintf("The file can't be larger than %s", e.MaxSize)
}
func (e *FileTooLargeError) HttpStatusCode() int { return http.StatusRequestEntityTooLarge }
func (e FileTooLargeError) HttpStatusCode() int { return http.StatusRequestEntityTooLarge }
type NotSignedInError struct{}
func (e *NotSignedInError) Error() string { return "You are not signed in" }
func (e *NotSignedInError) HttpStatusCode() int { return http.StatusUnauthorized }
func (e NotSignedInError) Error() string { return "You are not signed in" }
func (e NotSignedInError) HttpStatusCode() int { return http.StatusUnauthorized }
type MissingAccessToken struct{}
func (e *MissingAccessToken) Error() string { return "Missing access token" }
func (e *MissingAccessToken) HttpStatusCode() int { return http.StatusUnauthorized }
func (e MissingAccessToken) Error() string { return "Missing access token" }
func (e MissingAccessToken) HttpStatusCode() int { return http.StatusUnauthorized }
type MissingPermissionError struct{}
func (e *MissingPermissionError) Error() string {
func (e MissingPermissionError) Error() string {
return "You don't have permission to perform this action"
}
func (e *MissingPermissionError) HttpStatusCode() int { return http.StatusForbidden }
func (e MissingPermissionError) HttpStatusCode() int { return http.StatusForbidden }
type TooManyRequestsError struct{}
func (e *TooManyRequestsError) Error() string {
return "Too many requests"
}
func (e *TooManyRequestsError) HttpStatusCode() int { return http.StatusTooManyRequests }
func (e TooManyRequestsError) Error() string { return "Too many requests" }
func (e TooManyRequestsError) HttpStatusCode() int { return http.StatusTooManyRequests }
type UserIdNotProvidedError struct{}
func (e *UserIdNotProvidedError) Error() string {
return "User id not provided"
}
func (e *UserIdNotProvidedError) HttpStatusCode() int { return http.StatusBadRequest }
func (e UserIdNotProvidedError) Error() string { return "User id not provided" }
func (e UserIdNotProvidedError) HttpStatusCode() int { return http.StatusBadRequest }
type UserNotFoundError struct{}
func (e *UserNotFoundError) Error() string {
return "User not found"
}
func (e *UserNotFoundError) HttpStatusCode() int { return http.StatusNotFound }
func (e UserNotFoundError) Error() string { return "User not found" }
func (e UserNotFoundError) HttpStatusCode() int { return http.StatusNotFound }
type ClientIdOrSecretNotProvidedError struct{}
func (e *ClientIdOrSecretNotProvidedError) Error() string {
return "Client id or secret not provided"
}
func (e *ClientIdOrSecretNotProvidedError) HttpStatusCode() int { return http.StatusBadRequest }
func (e ClientIdOrSecretNotProvidedError) Error() string { return "Client id or secret not provided" }
func (e ClientIdOrSecretNotProvidedError) HttpStatusCode() int { return http.StatusBadRequest }
type WrongFileTypeError struct {
ExpectedFileType string
}
func (e *WrongFileTypeError) Error() string {
func (e WrongFileTypeError) Error() string {
return fmt.Sprintf("File must be of type %s", e.ExpectedFileType)
}
func (e *WrongFileTypeError) HttpStatusCode() int { return http.StatusBadRequest }
func (e WrongFileTypeError) HttpStatusCode() int { return http.StatusBadRequest }
type MissingSessionIdError struct{}
func (e *MissingSessionIdError) Error() string {
return "Missing session id"
}
func (e *MissingSessionIdError) HttpStatusCode() int { return http.StatusBadRequest }
func (e MissingSessionIdError) Error() string { return "Missing session id" }
func (e MissingSessionIdError) HttpStatusCode() int { return http.StatusBadRequest }
type ReservedClaimError struct {
Key string
}
func (e *ReservedClaimError) Error() string {
func (e ReservedClaimError) Error() string {
return fmt.Sprintf("Claim %s is reserved and can't be used", e.Key)
}
func (e *ReservedClaimError) HttpStatusCode() int { return http.StatusBadRequest }
func (e ReservedClaimError) HttpStatusCode() int { return http.StatusBadRequest }
type DuplicateClaimError struct {
Key string
}
func (e *DuplicateClaimError) Error() string {
func (e DuplicateClaimError) Error() string {
return fmt.Sprintf("Claim %s is already defined", e.Key)
}
func (e *DuplicateClaimError) HttpStatusCode() int { return http.StatusBadRequest }
func (e DuplicateClaimError) HttpStatusCode() int { return http.StatusBadRequest }
type OidcInvalidCodeVerifierError struct{}
func (e *OidcInvalidCodeVerifierError) Error() string {
return "Invalid code verifier"
}
func (e *OidcInvalidCodeVerifierError) HttpStatusCode() int { return http.StatusBadRequest }
func (e OidcInvalidCodeVerifierError) Error() string { return "Invalid code verifier" }
func (e OidcInvalidCodeVerifierError) HttpStatusCode() int { return http.StatusBadRequest }
type OidcMissingCodeChallengeError struct{}
func (e *OidcMissingCodeChallengeError) Error() string {
return "Missing code challenge"
}
func (e *OidcMissingCodeChallengeError) HttpStatusCode() int { return http.StatusBadRequest }
func (e OidcMissingCodeChallengeError) Error() string { return "Missing code challenge" }
func (e OidcMissingCodeChallengeError) HttpStatusCode() int { return http.StatusBadRequest }
type LdapUserUpdateError struct{}
func (e *LdapUserUpdateError) Error() string {
return "LDAP users can't be updated"
}
func (e *LdapUserUpdateError) HttpStatusCode() int { return http.StatusForbidden }
func (e LdapUserUpdateError) Error() string { return "LDAP users can't be updated" }
func (e LdapUserUpdateError) HttpStatusCode() int { return http.StatusForbidden }
type LdapUserGroupUpdateError struct{}
func (e *LdapUserGroupUpdateError) Error() string {
return "LDAP user groups can't be updated"
}
func (e *LdapUserGroupUpdateError) HttpStatusCode() int { return http.StatusForbidden }
func (e LdapUserGroupUpdateError) Error() string { return "LDAP user groups can't be updated" }
func (e LdapUserGroupUpdateError) HttpStatusCode() int { return http.StatusForbidden }
type OidcAccessDeniedError struct{}
func (e *OidcAccessDeniedError) Error() string {
return "You're not allowed to access this service"
}
func (e *OidcAccessDeniedError) HttpStatusCode() int { return http.StatusForbidden }
func (e OidcAccessDeniedError) Error() string { return "You're not allowed to access this service" }
func (e OidcAccessDeniedError) HttpStatusCode() int { return http.StatusForbidden }
type OidcClientIdNotMatchingError struct{}
func (e *OidcClientIdNotMatchingError) Error() string {
func (e OidcClientIdNotMatchingError) Error() string {
return "Client id in request doesn't match client id in token"
}
func (e *OidcClientIdNotMatchingError) HttpStatusCode() int { return http.StatusBadRequest }
func (e OidcClientIdNotMatchingError) HttpStatusCode() int { return http.StatusBadRequest }
type OidcNoCallbackURLError struct{}
func (e *OidcNoCallbackURLError) Error() string {
func (e OidcNoCallbackURLError) Error() string {
return "No callback URL provided"
}
func (e *OidcNoCallbackURLError) HttpStatusCode() int { return http.StatusBadRequest }
func (e OidcNoCallbackURLError) HttpStatusCode() int { return http.StatusBadRequest }
type UiConfigDisabledError struct{}
func (e *UiConfigDisabledError) Error() string {
func (e UiConfigDisabledError) Error() string {
return "The configuration can't be changed since the UI configuration is disabled"
}
func (e *UiConfigDisabledError) HttpStatusCode() int { return http.StatusForbidden }
func (e UiConfigDisabledError) HttpStatusCode() int { return http.StatusForbidden }
type InvalidUUIDError struct{}
func (e *InvalidUUIDError) Error() string {
return "Invalid UUID"
}
func (e *InvalidUUIDError) HttpStatusCode() int { return http.StatusBadRequest }
func (e InvalidUUIDError) Error() string { return "Invalid UUID" }
func (e InvalidUUIDError) HttpStatusCode() int { return http.StatusBadRequest }
type OneTimeAccessDisabledError struct{}
func (e *OneTimeAccessDisabledError) Error() string {
return "One-time access is disabled"
}
func (e *OneTimeAccessDisabledError) HttpStatusCode() int { return http.StatusBadRequest }
func (e OneTimeAccessDisabledError) Error() string { return "One-time access is disabled" }
func (e OneTimeAccessDisabledError) HttpStatusCode() int { return http.StatusBadRequest }
type InvalidAPIKeyError struct{}
func (e *InvalidAPIKeyError) Error() string {
return "Invalid Api Key"
}
func (e *InvalidAPIKeyError) HttpStatusCode() int { return http.StatusUnauthorized }
func (e InvalidAPIKeyError) Error() string { return "Invalid Api Key" }
func (e InvalidAPIKeyError) HttpStatusCode() int { return http.StatusUnauthorized }
type NoAPIKeyProvidedError struct{}
func (e *NoAPIKeyProvidedError) Error() string {
return "No API Key Provided"
}
func (e *NoAPIKeyProvidedError) HttpStatusCode() int { return http.StatusUnauthorized }
func (e NoAPIKeyProvidedError) Error() string { return "No API Key Provided" }
func (e NoAPIKeyProvidedError) HttpStatusCode() int { return http.StatusUnauthorized }
type APIKeyNotFoundError struct{}
func (e *APIKeyNotFoundError) Error() string {
return "API Key Not Found"
}
func (e *APIKeyNotFoundError) HttpStatusCode() int { return http.StatusUnauthorized }
func (e APIKeyNotFoundError) Error() string { return "API Key Not Found" }
func (e APIKeyNotFoundError) HttpStatusCode() int { return http.StatusUnauthorized }
type APIKeyNotExpiredError struct{}
func (e *APIKeyNotExpiredError) Error() string {
return "API Key is not expired yet"
}
func (e *APIKeyNotExpiredError) HttpStatusCode() int { return http.StatusBadRequest }
func (e APIKeyNotExpiredError) Error() string { return "API Key is not expired yet" }
func (e APIKeyNotExpiredError) HttpStatusCode() int { return http.StatusBadRequest }
type APIKeyExpirationDateError struct{}
func (e *APIKeyExpirationDateError) Error() string {
func (e APIKeyExpirationDateError) Error() string {
return "API Key expiration time must be in the future"
}
func (e *APIKeyExpirationDateError) HttpStatusCode() int { return http.StatusBadRequest }
func (e APIKeyExpirationDateError) HttpStatusCode() int { return http.StatusBadRequest }
type APIKeyAuthNotAllowedError struct{}
func (e *APIKeyAuthNotAllowedError) Error() string {
func (e APIKeyAuthNotAllowedError) Error() string {
return "API key authentication is not allowed for this endpoint"
}
func (e *APIKeyAuthNotAllowedError) HttpStatusCode() int { return http.StatusForbidden }
func (e APIKeyAuthNotAllowedError) HttpStatusCode() int { return http.StatusForbidden }
type OidcInvalidRefreshTokenError struct{}
func (e *OidcInvalidRefreshTokenError) Error() string {
return "refresh token is invalid or expired"
}
func (e *OidcInvalidRefreshTokenError) HttpStatusCode() int {
return http.StatusBadRequest
}
func (e OidcInvalidRefreshTokenError) Error() string { return "refresh token is invalid or expired" }
func (e OidcInvalidRefreshTokenError) HttpStatusCode() int { return http.StatusBadRequest }
type OidcMissingRefreshTokenError struct{}
func (e *OidcMissingRefreshTokenError) Error() string {
return "refresh token is required"
}
func (e *OidcMissingRefreshTokenError) HttpStatusCode() int {
return http.StatusBadRequest
}
func (e OidcMissingRefreshTokenError) Error() string { return "refresh token is required" }
func (e OidcMissingRefreshTokenError) HttpStatusCode() int { return http.StatusBadRequest }
type OidcMissingAuthorizationCodeError struct{}
func (e *OidcMissingAuthorizationCodeError) Error() string {
return "authorization code is required"
}
func (e *OidcMissingAuthorizationCodeError) HttpStatusCode() int {
return http.StatusBadRequest
}
func (e OidcMissingAuthorizationCodeError) Error() string { return "authorization code is required" }
func (e OidcMissingAuthorizationCodeError) HttpStatusCode() int { return http.StatusBadRequest }
type UserDisabledError struct{}
func (e *UserDisabledError) Error() string {
return "User account is disabled"
}
func (e *UserDisabledError) HttpStatusCode() int {
return http.StatusForbidden
}
func (e UserDisabledError) Error() string { return "User account is disabled" }
func (e UserDisabledError) HttpStatusCode() int { return http.StatusForbidden }
type ValidationError struct {
Message string
}
type ValidationError struct{ Message string }
func (e *ValidationError) Error() string {
return e.Message
}
func (e ValidationError) Error() string { return e.Message }
func (e *ValidationError) HttpStatusCode() int {
return http.StatusBadRequest
}
func (e ValidationError) HttpStatusCode() int { return http.StatusBadRequest }
type OidcDeviceCodeExpiredError struct{}
func (e *OidcDeviceCodeExpiredError) Error() string {
return "device code has expired"
}
func (e *OidcDeviceCodeExpiredError) HttpStatusCode() int {
return http.StatusBadRequest
}
func (e OidcDeviceCodeExpiredError) Error() string { return "device code has expired" }
func (e OidcDeviceCodeExpiredError) HttpStatusCode() int { return http.StatusBadRequest }
type OidcInvalidDeviceCodeError struct{}
func (e *OidcInvalidDeviceCodeError) Error() string {
return "invalid device code"
}
func (e *OidcInvalidDeviceCodeError) HttpStatusCode() int {
return http.StatusBadRequest
}
func (e OidcInvalidDeviceCodeError) Error() string { return "invalid device code" }
func (e OidcInvalidDeviceCodeError) HttpStatusCode() int { return http.StatusBadRequest }
type OidcSlowDownError struct{}
func (e *OidcSlowDownError) Error() string {
return "polling too frequently"
}
func (e *OidcSlowDownError) HttpStatusCode() int {
return http.StatusTooManyRequests
}
func (e OidcSlowDownError) Error() string { return "polling too frequently" }
func (e OidcSlowDownError) HttpStatusCode() int { return http.StatusTooManyRequests }
type OidcAuthorizationPendingError struct{}
func (e *OidcAuthorizationPendingError) Error() string {
return "authorization is still pending"
}
func (e *OidcAuthorizationPendingError) HttpStatusCode() int {
return http.StatusBadRequest
}
func (e OidcAuthorizationPendingError) Error() string { return "authorization is still pending" }
func (e OidcAuthorizationPendingError) HttpStatusCode() int { return http.StatusBadRequest }
type ReauthenticationRequiredError struct{}
func (e *ReauthenticationRequiredError) Error() string {
return "reauthentication required"
}
func (e *ReauthenticationRequiredError) HttpStatusCode() int {
return http.StatusUnauthorized
}
func (e ReauthenticationRequiredError) Error() string { return "reauthentication required" }
func (e ReauthenticationRequiredError) HttpStatusCode() int { return http.StatusUnauthorized }
type OpenSignupDisabledError struct{}
func (e *OpenSignupDisabledError) Error() string {
return "Open user signup is not enabled"
}
func (e OpenSignupDisabledError) Error() string { return "Open user signup is not enabled" }
func (e *OpenSignupDisabledError) HttpStatusCode() int {
return http.StatusForbidden
}
func (e OpenSignupDisabledError) HttpStatusCode() int { return http.StatusForbidden }
type ClientIdAlreadyExistsError struct{}
func (e *ClientIdAlreadyExistsError) Error() string {
return "Client ID already in use"
}
func (e ClientIdAlreadyExistsError) Error() string { return "Client ID already in use" }
func (e *ClientIdAlreadyExistsError) HttpStatusCode() int {
return http.StatusBadRequest
}
func (e ClientIdAlreadyExistsError) HttpStatusCode() int { return http.StatusBadRequest }
type UserEmailNotSetError struct{}
func (e *UserEmailNotSetError) Error() string {
return "The user does not have an email address set"
}
func (e UserEmailNotSetError) Error() string { return "The user does not have an email address set" }
func (e *UserEmailNotSetError) HttpStatusCode() int {
return http.StatusBadRequest
}
func (e UserEmailNotSetError) HttpStatusCode() int { return http.StatusBadRequest }
type ImageNotFoundError struct{}
func (e *ImageNotFoundError) Error() string {
return "Image not found"
}
func (e ImageNotFoundError) Error() string { return "Image not found" }
func (e *ImageNotFoundError) HttpStatusCode() int {
return http.StatusNotFound
}
func (e ImageNotFoundError) HttpStatusCode() int { return http.StatusNotFound }
type InvalidEmailVerificationTokenError struct{}
func (e *InvalidEmailVerificationTokenError) Error() string {
return "Invalid email verification token"
}
func (e InvalidEmailVerificationTokenError) Error() string { return "Invalid email verification token" }
func (e *InvalidEmailVerificationTokenError) HttpStatusCode() int {
return http.StatusBadRequest
}
func (e InvalidEmailVerificationTokenError) HttpStatusCode() int { return http.StatusBadRequest }
// OIDC prompt parameter errors - used for redirect error responses
type OidcLoginRequiredError struct{}
func (e *OidcLoginRequiredError) Error() string { return "login_required" }
func (e *OidcLoginRequiredError) HttpStatusCode() int { return http.StatusBadRequest }
func (e OidcLoginRequiredError) Error() string { return "login_required" }
func (e OidcLoginRequiredError) HttpStatusCode() int { return http.StatusBadRequest }
type OidcConsentRequiredError struct{}
func (e *OidcConsentRequiredError) Error() string { return "consent_required" }
func (e *OidcConsentRequiredError) HttpStatusCode() int { return http.StatusBadRequest }
func (e OidcConsentRequiredError) Error() string { return "consent_required" }
func (e OidcConsentRequiredError) HttpStatusCode() int { return http.StatusBadRequest }
type OidcInteractionRequiredError struct{}
func (e *OidcInteractionRequiredError) Error() string { return "interaction_required" }
func (e *OidcInteractionRequiredError) HttpStatusCode() int { return http.StatusBadRequest }
func (e OidcInteractionRequiredError) Error() string { return "interaction_required" }
func (e OidcInteractionRequiredError) HttpStatusCode() int { return http.StatusBadRequest }
type OidcInvalidRequestError struct{ description string }
func NewOidcInvalidRequestError(description string) *OidcInvalidRequestError {
return &OidcInvalidRequestError{description: description}
}
func (e OidcInvalidRequestError) Error() string { return "invalid_request" }
func (e OidcInvalidRequestError) HttpStatusCode() int { return http.StatusBadRequest }
func (e OidcInvalidRequestError) Description() string { return e.description }
type OidcAccountSelectionRequiredError struct{}
func (e *OidcAccountSelectionRequiredError) Error() string {
return "account_selection_required"
}
func (e *OidcAccountSelectionRequiredError) HttpStatusCode() int { return http.StatusBadRequest }
func (e OidcAccountSelectionRequiredError) Error() string { return "account_selection_required" }
func (e OidcAccountSelectionRequiredError) HttpStatusCode() int { return http.StatusBadRequest }

View File

@@ -91,7 +91,7 @@ func (wkc *WellKnownController) computeOIDCConfiguration() ([]byte, error) {
"id_token_signing_alg_values_supported": []string{alg.String()},
"authorization_response_iss_parameter_supported": true,
"code_challenge_methods_supported": []string{"plain", "S256"},
"prompt_values_supported": []string{"none", "login", "consent"},
"prompt_values_supported": []string{"none", "login", "consent", "select_account"},
"token_endpoint_auth_methods_supported": []string{"client_secret_basic", "client_secret_post", "none"},
}
return json.Marshal(config)

View File

@@ -23,7 +23,6 @@ func (m *ErrorHandlerMiddleware) Add() gin.HandlerFunc {
return func(c *gin.Context) {
c.Next()
for _, err := range c.Errors {
// Check for record not found errors
if errors.Is(err, gorm.ErrRecordNotFound) {
errorResponse(c, http.StatusNotFound, "Record not found")
@@ -39,30 +38,56 @@ func (m *ErrorHandlerMiddleware) Add() gin.HandlerFunc {
}
// Check for slice validation errors
var sliceValidationErrors binding.SliceValidationError
if errors.As(err, &sliceValidationErrors) {
if errors.As(sliceValidationErrors[0], &validationErrors) {
svErr, ok := errors.AsType[binding.SliceValidationError](err)
if ok {
if errors.As(svErr[0], &validationErrors) {
message := handleValidationError(validationErrors)
errorResponse(c, http.StatusBadRequest, message)
return
}
}
var appErr common.AppError
if errors.As(err, &appErr) {
// AppError with description
appDescErr, ok := errors.AsType[common.AppErrorDescription](err)
if ok {
errorResponseWithDescription(c, appDescErr.HttpStatusCode(), appDescErr.Error(), appDescErr.Description())
return
}
// AppError (without description)
appErr, ok := errors.AsType[common.AppError](err)
if ok {
errorResponse(c, appErr.HttpStatusCode(), appErr.Error())
return
}
c.JSON(http.StatusInternalServerError, gin.H{"error": "Something went wrong"})
c.JSON(http.StatusInternalServerError, errorResponseBody{
Error: "Something went wrong",
})
}
}
}
type errorResponseBody struct {
Error string `json:"error"`
ErrorDescription string `json:"error_description,omitempty"`
}
func errorResponse(c *gin.Context, statusCode int, message string) {
// Capitalize the first letter of the message
message = strings.ToUpper(message[:1]) + message[1:]
c.JSON(statusCode, gin.H{"error": message})
c.JSON(statusCode, errorResponseBody{
Error: message,
})
}
func errorResponseWithDescription(c *gin.Context, statusCode int, message string, description string) {
// Capitalize the first letter of the message
message = strings.ToUpper(message[:1]) + message[1:]
c.JSON(statusCode, errorResponseBody{
Error: message,
ErrorDescription: description,
})
}
func handleValidationError(validationErrors validator.ValidationErrors) string {

View File

@@ -151,22 +151,20 @@ func (s *OidcService) Authorize(ctx context.Context, input dto.AuthorizeOidcClie
// Parse prompt parameter (space-delimited list per OIDC spec)
promptValues := parsePromptParameter(input.Prompt)
hasPromptNone := contains(promptValues, "none")
hasPromptLogin := contains(promptValues, "login")
hasPromptConsent := contains(promptValues, "consent")
hasPromptSelectAccount := contains(promptValues, "select_account")
hasPromptNone := slices.Contains(promptValues, "none")
hasPromptLogin := slices.Contains(promptValues, "login")
hasPromptConsent := slices.Contains(promptValues, "consent")
hasPromptSelectAccount := slices.Contains(promptValues, "select_account")
// Validate prompt parameter conflicts early.
// Per OIDC Core §3.1.2.6, prompt=none must not be combined with any
// value that requires user interaction.
if hasPromptNone && (hasPromptConsent || hasPromptLogin || hasPromptSelectAccount) {
return "", "", &common.OidcInteractionRequiredError{}
return "", "", common.NewOidcInvalidRequestError("prompt type 'none' cannot be combined with others")
}
// Handle prompt=select_account early (not supported)
if hasPromptSelectAccount {
return "", "", &common.OidcInteractionRequiredError{}
}
// prompt=select_account is handled entirely in the UI
// Pocket ID holds one session per browser, so the frontend renders the current user as the sole selectable account and then calls Authorize normally.
// If prompt=login is specified or the client requires reauthentication, check the reauthentication token
if hasPromptLogin || client.RequiresReauthentication {
@@ -219,14 +217,24 @@ func (s *OidcService) Authorize(ctx context.Context, input dto.AuthorizeOidcClie
// Log the authorization event
if hasAlreadyAuthorizedClient {
s.auditLogService.Create(ctx, model.AuditLogEventClientAuthorization, ipAddress, userAgent, userID, model.AuditLogData{"clientName": client.Name}, tx)
s.auditLogService.Create(
ctx, model.AuditLogEventClientAuthorization,
ipAddress, userAgent, userID,
model.AuditLogData{"clientName": client.Name},
tx,
)
} else {
s.auditLogService.Create(ctx, model.AuditLogEventNewClientAuthorization, ipAddress, userAgent, userID, model.AuditLogData{"clientName": client.Name}, tx)
s.auditLogService.Create(
ctx, model.AuditLogEventNewClientAuthorization,
ipAddress, userAgent, userID,
model.AuditLogData{"clientName": client.Name},
tx,
)
}
err = tx.Commit().Error
if err != nil {
return "", "", err
return "", "", fmt.Errorf("failed to commit transaction: %w", err)
}
return code, callbackURL, nil
@@ -2219,13 +2227,3 @@ func parsePromptParameter(prompt string) []string {
}
return strings.Fields(prompt)
}
// contains checks if a string slice contains a specific value
func contains(slice []string, value string) bool {
for _, item := range slice {
if item == value {
return true
}
}
return false
}

View File

@@ -10,6 +10,7 @@ import (
"encoding/json"
"io"
"net/http"
"slices"
"strconv"
"strings"
"testing"
@@ -1238,30 +1239,13 @@ func TestPromptParameterConflicts(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
values := parsePromptParameter(tt.prompt)
hasNone := contains(values, "none")
hasConsent := contains(values, "consent")
hasLogin := contains(values, "login")
hasSelectAccount := contains(values, "select_account")
hasNone := slices.Contains(values, "none")
hasConsent := slices.Contains(values, "consent")
hasLogin := slices.Contains(values, "login")
hasSelectAccount := slices.Contains(values, "select_account")
conflict := hasNone && (hasConsent || hasLogin || hasSelectAccount)
assert.Equal(t, tt.expectConflict, conflict)
})
}
}
func TestContains(t *testing.T) {
t.Run("finds value in slice", func(t *testing.T) {
slice := []string{"none", "login", "consent"}
assert.True(t, contains(slice, "login"))
})
t.Run("returns false for missing value", func(t *testing.T) {
slice := []string{"none", "login"}
assert.False(t, contains(slice, "consent"))
})
t.Run("returns false for empty slice", func(t *testing.T) {
slice := []string{}
assert.False(t, contains(slice, "none"))
})
}