Error codes
Odudu reports errors in three places: the admin API, the protocol endpoints and the server or command that fails to start. Each has its own vocabulary, listed below.
Admin API problems
A problem with type about:blank carries no more meaning than its HTTP status and title: detail says what was wrong with this request. Most refusals are of this kind.
These refusals have a type of their own, which a client can switch on:
| Type | Status | Meaning |
|---|---|---|
about:blank#audit-query-too-broad | 400 | The audit read ran past its five second bound because its filters match too many rows to serve cheaply. Narrow the time range with from and to. |
about:blank#builtin-scope-confirmation | 422 | A built-in scope is deleted only when the request’s confirm names it again. detail names the scope and how many clients and roles would lose it. Repeat the request with confirm set to the scope name. |
about:blank#export-too-large | 413 | An audit export or a tenant export would be larger than the cap, and is refused whole rather than cut short. Narrow the audit export with from, to or another filter. |
about:blank#flow-duplicate-authenticator | 400 | A replacement authentication flow names the same authenticator in two steps. A step is addressed by its authenticator, so a repeat has no unambiguous meaning. |
about:blank#flow-empty | 400 | A replacement authentication flow has no steps. A tenant with no flow cannot be logged into. |
about:blank#flow-no-enabled-step | 400 | Every step of a replacement authentication flow is disabled, so nobody could log in. |
about:blank#flow-no-first-challenge | 400 | No step of a replacement authentication flow would run at the start of a login, when no subject is bound yet. |
about:blank#flow-unknown-authenticator | 400 | A step of a replacement authentication flow names an authenticator that is not registered. detail lists the ones that are. |
about:blank#key-active | 409 | The signing key is the active one and cannot be retired. Promote another key first. |
about:blank#key-algorithm-needed | 409 | Retiring the signing key would leave no key for an algorithm a client still requires for its ID tokens or UserInfo responses. detail names the clients. |
about:blank#key-not-retired | 409 | Only a retired signing key can be deleted. Retire it first. |
about:blank#last-administrator | 409 | The write would leave the tenant with no enabled administrator. detail names who would be the last. |
about:blank#logout-deliveries-pending | 409 | The tenant still has back-channel logout tokens to send, and deleting it would discard them. Wait for them to be delivered. |
about:blank#no-email | 409 | The subject has no email address to send the message to. |
about:blank#no-mail-relay | 409 | The tenant has no SMTP relay and the deployment no sender, so the mail would only be logged. |
about:blank#not-found | 404 | Nothing is served at the path, whichever API or tenant it names. detail is "No resource at this path"; it named the path and the route before. A browser that asks for a page is shown one instead. |
about:blank#reset-password-off | 409 | The tenant has reset_password_allowed off, so it would refuse the link. Turn the setting on first. |
about:blank#service-account | 409 | A subject that is a client’s service account is not deleted on its own, since the client would be left with none. detail names the client. Delete the client instead, which deletes its account. |
about:blank#sessions-live | 409 | Deleting the tenant was refused because sessions are still live. Disable the tenant again to end them and tell their relying parties. |
about:blank#sessions-not-ended | 500 | The tenant was disabled, but ending its sessions did not finish. Repeat the request to finish it. |
about:blank#tenant-enabled | 409 | A tenant must be disabled before it is deleted, which ends its sessions and tells their relying parties. |
A 400 that names fields carries an errors list; an entry has a code when the refusal has one, such as bidi_control_refused for a bidirectional control character in a typed field. The shape of the problem document is under problem details.
An address nothing serves answers 404 with the type about:blank#not-found, and an unhandled failure 500 (every failure of status 500 or above answers 500). Both are problem documents whose instance is the request id. A browser that asks for a page, outside the admin, console API, health and protocol paths, is shown a branded page instead: the same page for every unknown address, with the request id on the failure page and nothing the request or the failure carried.
OAuth and OpenID Connect errors
The protocol endpoints answer with the error values RFC 6749, RFC 6750, RFC 7591, RFC 8707 and OpenID Connect Core define. At the authorization endpoint a refusal that can be reported to the client is a redirect carrying error; one that cannot is a page. The token endpoint answers a JSON body. The authorization and token endpoints add an error_description, one plain sentence that is never built from the request. Each code has one fixed sentence, and invalid_grant names no cause. The authorization endpoint adds a few that name the parameter at fault, listed after the table.
| Code | Answered by | Meaning | error_description |
|---|---|---|---|
access_denied | authorization endpoint | The user refused consent, or the sign-in the request was waiting on expired, so the client is sent back without a code. | The user did not allow the request. |
account_selection_required | authorization endpoint | The request asked for no interaction (prompt=none) but the user must choose between accounts. | The user must choose an account first. |
consent_required | authorization endpoint | The request asked for no interaction (prompt=none) but the user has not consented to what the client asks for. | The user has not agreed to this request yet. |
insufficient_scope | UserInfo endpoint | The access token does not carry the openid scope the UserInfo endpoint requires. | |
invalid_client | authorization endpoint, token endpoint | Client authentication failed: the client is unknown or its credential is wrong. | Client authentication failed. |
invalid_client_metadata | client registration | A value in the registration request is not acceptable client metadata. error_description names the field. | |
invalid_grant | token endpoint | The authorization code or refresh token is unknown, expired, already used, issued to another client, or does not match the redirect URI or the PKCE verifier. The response never says which, by design. | The code or token is not valid. |
invalid_redirect_uri | client registration | A redirect URI in the registration request is not one the server accepts. | |
invalid_request | authorization endpoint, token endpoint, UserInfo endpoint | The request is malformed: a required parameter is missing, repeated or has an unacceptable value. | The request is missing a parameter or has a wrong one. |
invalid_scope | authorization endpoint, token endpoint | A requested scope is unknown, not assigned to the client, or malformed. | A requested scope is not available to this client. |
invalid_target | authorization endpoint, token endpoint | A resource indicator (RFC 8707) is malformed or names an audience the client may not request. | A requested resource is not available to this client. |
invalid_token | UserInfo endpoint, client registration | The access token, or the initial access token of a dynamic registration, is missing, expired, revoked, spent or otherwise not valid for this endpoint. UserInfo answers with 401 and a WWW-Authenticate challenge. | |
login_required | authorization endpoint | The request asked for no interaction (prompt=none) but the user is not signed in. | The user is not signed in. |
request_not_supported | authorization endpoint | The request carried a request object, which this server does not support. | The request parameter is not supported. |
request_uri_not_supported | authorization endpoint | The request carried a request_uri, which this server does not support. | The request_uri parameter is not supported. |
unauthorized_client | token endpoint | The client is not allowed to use the grant type it asked for. | This client may not use this grant type. |
unsupported_grant_type | token endpoint | The grant_type is not one this server supports. | This grant type is not supported. |
unsupported_response_type | authorization endpoint | The response_type is not one this server supports. | Only response_type=code is supported. |
At the authorization endpoint, an invalid_request can say which parameter was wrong:
A parameter was sent more than oncecode_challenge is requiredcode_challenge is not validcode_challenge_method must be S256prompt is not validprompt=create is not supported: registration is offprompt=create cannot be sent with id_token_hint or a claims submax_age is not validclaims is not validid_token_hint is not valid
Server and command errors
A command or the server that stops with an error prints its code and a message. A command that stops this way exits with a non-zero status.
Startup and configuration
| Code | Meaning |
|---|---|
config_invalid | The environment does not describe a valid configuration. The message names every variable at fault, or the production rule that was broken. |
module_cycle | The server modules depend on each other in a cycle, so none can start first. |
module_duplicate | Two server modules registered under the same name. |
module_stop_failed | One or more modules failed while the server was shutting down. The failures are in the cause. |
module_unknown_dependency | A server module depends on a module that was not registered. |
smtp_destination_refused | The SMTP relay address is one the server refuses to connect to: a loopback, link-local or multicast address, or a private one while ODUDU_ALLOW_PRIVATE_SMTP_HOSTS is off. |
static_tree_too_large | A directory of static files, such as ODUDU_INSTRUMENT_DIR, holds more entries, files or bytes than the server will serve, so it is probably not the build. |
Commands
| Code | Meaning |
|---|---|
admin_client_list_full | The built-in admin client already holds as many redirect URIs as a client may, so the console’s address cannot be added. |
admin_client_not_builtin | A client with the id of the built-in admin client already exists in the tenant but is not it, so the console cannot be registered. |
cli_unknown_command | The first argument of odudu is not a command. The exit status is 2 and the commands are printed. |
client_origins_unknown_command | odudu client-origins was given a subcommand it does not have. |
console_invalid_options | The options of an odudu console command are missing or unusable. |
console_unknown_command | odudu console was given a subcommand it does not have. |
init_invalid_options | The options or environment of odudu init are not usable, for example a public base URL that is not a loopback address. |
init_roles_without_secrets | odudu init found the service role in the database but not the state file holding its password. Restore the file, or drop the role if the database is disposable. |
seed_admin_exists | odudu seed admin found a subject with that name already in the system tenant. |
seed_conflict | The thing odudu seed was asked to create exists already in a form that disagrees with the request, such as a client of the other type. |
seed_invalid_options | The options of an odudu seed command are missing, contradictory or have an unacceptable value. The message names them. |
seed_not_found | odudu seed was told to act on something that does not exist. |
seed_system_tenant_conflict | A tenant named like the system tenant exists under another id, so the system tenant cannot be created. |
seed_unknown_command | odudu seed was given a subcommand it does not have. |
seed_unknown_grant_type | --grant-type names a grant type this server does not implement. |
seed_unknown_setting | A --set names a tenant setting that does not exist. The message lists the ones that do. |
Background passes
| Code | Meaning |
|---|---|
logout_sender_cannot_enumerate_tenants | The logout sender must list every tenant, which needs a connection that bypasses row-level security, and ODUDU_DATABASE_URL names a role that does not. |
logout_sender_requires_app_database_url | The logout sender needs ODUDU_APP_DATABASE_URL to claim deliveries under the tenant policy. |
logout_sender_serving_role_bypasses_rls | ODUDU_APP_DATABASE_URL names a superuser or BYPASSRLS role, so the logout sender would not claim deliveries under the tenant policy. |
outbox_cannot_enumerate_tenants | The mail sender must list every tenant, which needs a connection that bypasses row-level security, and ODUDU_DATABASE_URL names a role that does not. |
outbox_requires_app_database_url | The mail sender needs ODUDU_APP_DATABASE_URL to claim messages under the tenant policy. |
outbox_serving_role_bypasses_rls | ODUDU_APP_DATABASE_URL names a superuser or BYPASSRLS role, so the mail sender would not claim messages under the tenant policy. |
reap_cannot_enumerate_tenants | odudu reap must list every tenant, which needs a connection that bypasses row-level security, and ODUDU_DATABASE_URL names a role that neither is a superuser nor has BYPASSRLS. |
reap_requires_app_database_url | odudu reap deletes under the tenant policy and so needs ODUDU_APP_DATABASE_URL; the owner role escapes the policy. |
reap_serving_role_bypasses_rls | ODUDU_APP_DATABASE_URL names a superuser or BYPASSRLS role, so odudu reap would not delete under the tenant policy. |
Signing, tokens and encryption
| Code | Meaning |
|---|---|
client_key_invalid | The console client key is not base64-encoded JSON, or is not an EC P-256 key with its public coordinates. |
insert_returned_no_row | A signing key insert returned no row, which the database should never do. |
jwe_alg_unsupported | An encrypted response was asked for with a key-management algorithm that is not one of the permitted ones. |
jwt_alg_mismatch | A token header’s alg is not the algorithm of the key its kid names. |
jwt_header_invalid | A token has no readable header. |
jwt_kid_missing | A token header carries no kid, so no key can be chosen to verify it. |
jwt_typ_mismatch | A token’s typ is one that must not be accepted at this endpoint. |
jwt_unknown_key | A token header names a kid that is not one of the tenant’s keys. |
kek_invalid | The key-encryption key is not 32 bytes. |
signing_key_not_found | The tenant has no active signing key to sign with. |
Data and validation
| Code | Meaning |
|---|---|
bidi_control_refused | A typed field holds a bidirectional control character (U+202A to U+202E, U+2066 to U+2069). |
claim_mapper_duplicate | Two claim mappers registered under the same name. |
client_scope_not_found | There is no client scope with that id. |
credential_not_found | There is no credential with that id. |
group_not_found | There is no group with that id. |
group_reparent_cycle | Moving the group under that parent would make it its own ancestor. |
invalid_email | An email address is not well formed. |
role_composite_cycle | Adding the composite role would make a role contain itself. |
role_not_found | A role that was expected to exist does not. |
subject_not_found | There is no subject with that id. |
tenant_context_missing | A tenant-scoped query was started without a valid tenant id, so it was refused rather than run unscoped. |
tenant_not_found | There is no tenant with that id in the current context. |
user_not_found | There is no user for that subject. |