Key points
- Every route here is administrator-gated.
- Creating a user answers with the whole user list rather than the created record.
- A token carries its account’s permissions at request time, so rebinding a role takes effect without reissuing tokens.
- Disabling a service account is the fastest kill switch for an integration: one call instead of one per token.
- Local login can only be turned off while external auth is enabled and a provider remains enabled — the platform refuses to lock itself out.
- Changing a live provider’s issuer re-identifies its users, because a subject is scoped by issuer.
- The audit log is where a secret read appears, naming who recovered it.
Operations
GET/v1/admin/usersAdministrator
Lists user accounts.
Notes
external_managed: true means the provider owns the record: password and email changes belong there, not here.
Call it
curl -s -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/admin/users" | jq '.[] | {sub, provider, status, external_managed}'Responses
All user accounts.
[
{
"id": "5b0a...",
"sub": "admin",
"email": "[email protected]",
"provider": "local",
"status": "active",
"roles": [{ "role": "nopsai-admin" }],
"external_managed": false,
"authentication_source": "local"
}
]When it fails
| Status | Cause | What to do |
|---|---|---|
| 500 | The user query failed. | Platform fault. |
Side effects
- None.
Proven by
services/nopsai/admin_roles_compat_test.goservices/nopsai/admin_user_handlers.goservices/nopsai/auth_models.go
POST/v1/admin/usersAdministrator
Creates a local user account.
Notes
It answers with the whole user list, not the new account, so a client that expects a single object gets an array.
Call it
curl -sX POST "$NOPSAI_URL/v1/admin/users" \
-H "Authorization: Bearer $NOPSAI_TOKEN" \
-H "Content-Type: application/json" \
-d '{"sub":"casey","email":"[email protected]","password":"<initial>","role":"pipeline-operator"}' | jqResponses
The user list after the create.
[
{
"id": "5b0a...",
"sub": "admin",
"email": "[email protected]",
"provider": "local",
"status": "active",
"roles": [{ "role": "nopsai-admin" }],
"external_managed": false,
"authentication_source": "local"
}
]When it fails
| Status | Cause | What to do |
|---|---|---|
| 400 | A missing subject, an unusable password, or an unknown role. | The message names the field. |
| 409 | The subject or email already exists. | Subjects are unique; reuse the existing account. |
| 500 | The account or its role binding could not be saved. | The create is transactional: retry, nothing partial was stored. |
Side effects
- Creates the account and any role binding in one transaction.
- Writes an audit record.
Proven by
services/nopsai/admin_roles_compat_test.goservices/nopsai/admin_user_handlers.go
PUT/v1/admin/users/{userID}Administrator
Replaces a user record.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
userID | path | string | Required | User identifier. |
Call it
curl -sX PUT "$NOPSAI_URL/v1/admin/users/$USER_ID" \
-H "Authorization: Bearer $NOPSAI_TOKEN" \
-H "Content-Type: application/json" \
-d '{"email":"[email protected]","status":"active"}' -w "%{http_code}\n"Responses
User updated.
When it fails
| Status | Cause | What to do |
|---|---|---|
| 400 | An unusable status or password. | The message names the field. |
| 404 | No user with that id. | Confirm the id from the list. |
| 409 | The email belongs to another account. | Resolve the duplicate first. |
| 500 | The update failed. | Retry. |
Side effects
- An administrator password reset takes effect immediately and does not require the current password.
- Writes an audit record.
Proven by
services/nopsai/admin_roles_compat_test.goservices/nopsai/admin_user_handlers.go
PATCH/v1/admin/users/{userID}Administrator
Partially updates a user record.
Notes
Disable rather than delete when someone leaves: the audit trail keeps naming a subject that still resolves.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
userID | path | string | Required | User identifier. |
Call it
curl -sX PATCH "$NOPSAI_URL/v1/admin/users/$USER_ID" \
-H "Authorization: Bearer $NOPSAI_TOKEN" \
-H "Content-Type: application/json" \
-d '{"status":"disabled"}' -w "%{http_code}\n"Responses
User updated.
When it fails
| Status | Cause | What to do |
|---|---|---|
| 400 | An unusable field value. | The message names the field. |
| 404 | No user with that id. | Confirm the id. |
| 500 | The update failed. | Retry. |
Side effects
- Disabling stops the account authenticating without deleting its history.
- Writes an audit record.
Proven by
services/nopsai/admin_roles_compat_test.goservices/nopsai/admin_user_handlers.go
DELETE/v1/admin/users/{userID}Administrator
Deletes a user account.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
userID | path | string | Required | User to delete. |
Call it
curl -sX DELETE -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/admin/users/$USER_ID" -w "%{http_code}\n"Responses
User deleted.
When it fails
| Status | Cause | What to do |
|---|---|---|
| 400 | The id is malformed. | Use an id from the list. |
| 404 | No user with that id. | It may already be deleted. |
| 500 | The delete failed. | Retry. |
Side effects
- Removes the account. Audit records naming the subject remain but no longer resolve to an account.
Proven by
services/nopsai/admin_roles_compat_test.goservices/nopsai/admin_user_handlers.go
GET/v1/admin/service-accountsAdministrator
Lists service accounts used for automation.
Notes
last_used_at finds the automation nobody remembers configuring — usually the account worth revoking.
Call it
curl -s -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/admin/service-accounts" | jq '.[] | {sub, token_count, last_used_at}'Responses
All service accounts.
[
{
"id": "8e3c...",
"sub": "release-bot",
"email": "[email protected]",
"provider": "local",
"status": "active",
"token_count": 1,
"roles": [{ "role": "pipeline-operator" }]
}
]When it fails
| Status | Cause | What to do |
|---|---|---|
| 405 | A method other than GET. | Use GET. |
| 500 | The query failed. | Platform fault. |
Side effects
- None.
Proven by
services/nopsai/personal_tokens_test.goservices/nopsai/auth_models.go
POST/v1/admin/service-accountsAdministrator
Creates a service account.
Notes
A service account is how a system gets its own identity. Using a person’s personal token instead works until that person leaves.
Call it
curl -sX POST "$NOPSAI_URL/v1/admin/service-accounts" \
-H "Authorization: Bearer $NOPSAI_TOKEN" \
-H "Content-Type: application/json" \
-d '{"sub":"release-bot","email":"[email protected]","role":"pipeline-operator","token_name":"ci","expires_in_days":365}' | jqResponses
Account created with a token.
{
"service_account": {
"id": "8e3c...",
"sub": "release-bot",
"email": "[email protected]",
"provider": "local",
"status": "active",
"token_count": 1,
"roles": [{ "role": "pipeline-operator" }]
},
"token": {
"id": "1c77...",
"name": "ci",
"token": "nopsat_...only-returned-once",
"token_suffix": "1c77",
"created_at": "2026-08-19T11:20:00Z",
"expires_at": "2027-08-19T11:20:00Z"
}
}When it fails
| Status | Cause | What to do |
|---|---|---|
| 400 | A missing subject or an unusable expiry. | Choose exactly one expiry form. |
| 409 | The subject already exists. | Reuse the account and issue another token instead. |
| 500 | The account could not be created. | Retry. |
Side effects
- Creates the account, its role binding, and one token in a single call.
- Writes an audit record.
Proven by
services/nopsai/personal_tokens_test.goservices/nopsai/auth_models.go
PUT/v1/admin/service-accounts/{serviceAccountID}Administrator
Replaces a service account record.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
serviceAccountID | path | string | Required | Service account identifier. |
Call it
curl -sX PUT "$NOPSAI_URL/v1/admin/service-accounts/$SERVICE_ACCOUNT_ID" \
-H "Authorization: Bearer $NOPSAI_TOKEN" \
-H "Content-Type: application/json" \
-d '{"email":"[email protected]","status":"active"}' -w "%{http_code}\n"Responses
Service account updated.
When it fails
| Status | Cause | What to do |
|---|---|---|
| 400 | An unusable status. | The message names the field. |
| 404 | No service account with that id. | Confirm the id. |
| 500 | The update failed. | Retry. |
Side effects
- Writes an audit record.
Proven by
services/nopsai/personal_tokens_test.goservices/nopsai/auth_models.go
PATCH/v1/admin/service-accounts/{serviceAccountID}Administrator
Partially updates a service account.
Notes
Disabling the account is the fastest kill switch for a misbehaving integration: one call instead of one per token.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
serviceAccountID | path | string | Required | Service account identifier. |
Call it
curl -sX PATCH "$NOPSAI_URL/v1/admin/service-accounts/$SERVICE_ACCOUNT_ID" \
-H "Authorization: Bearer $NOPSAI_TOKEN" \
-H "Content-Type: application/json" \
-d '{"status":"disabled"}' -w "%{http_code}\n"Responses
Service account updated.
When it fails
| Status | Cause | What to do |
|---|---|---|
| 400 | An unusable status. | The message names the field. |
| 404 | No service account with that id. | Confirm the id. |
| 500 | The update failed. | Retry. |
Side effects
- Disabling stops every token the account holds without revoking them individually.
- Writes an audit record.
Proven by
services/nopsai/personal_tokens_test.goservices/nopsai/auth_models.go
DELETE/v1/admin/service-accounts/{serviceAccountID}Administrator
Deletes a service account.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
serviceAccountID | path | string | Required | Service account to delete. |
Call it
curl -sX DELETE -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/admin/service-accounts/$SERVICE_ACCOUNT_ID" -w "%{http_code}\n"Responses
Deleted.
When it fails
| Status | Cause | What to do |
|---|---|---|
| 404 | No service account with that id. | It may already be deleted. |
| 500 | The delete failed. | Retry. |
Side effects
- Removes the account and every token issued to it.
- Writes an audit record.
Proven by
services/nopsai/personal_tokens_test.goservices/nopsai/auth_models.go
GET/v1/admin/service-accounts/{serviceAccountID}/tokensAdministrator
Lists tokens issued to a service account.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
serviceAccountID | path | string | Required | Service account identifier. |
Call it
curl -s -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/admin/service-accounts/$SERVICE_ACCOUNT_ID/tokens" | jqResponses
Token metadata.
[
{
"id": "1c77...",
"name": "ci",
"token_suffix": "1c77",
"created_at": "2026-08-19T11:20:00Z",
"last_used_at": "2026-08-19T12:44:10Z"
}
]When it fails
| Status | Cause | What to do |
|---|---|---|
| 404 | No service account with that id. | Confirm the id. |
| 500 | The query failed. | Platform fault. |
Side effects
- None.
Proven by
services/nopsai/personal_tokens_test.goservices/nopsai/auth_models.go
POST/v1/admin/service-accounts/{serviceAccountID}/tokensAdministrator
Issues a service account token. The value is returned once.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
serviceAccountID | path | string | Required | Service account identifier. |
Call it
curl -sX POST "$NOPSAI_URL/v1/admin/service-accounts/$SERVICE_ACCOUNT_ID/tokens" \
-H "Authorization: Bearer $NOPSAI_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"ci-secondary","expires_in_days":90}' | jq -r .tokenResponses
Token issued.
{
"id": "1c77...",
"name": "ci",
"token": "nopsat_...only-returned-once",
"token_suffix": "1c77",
"created_at": "2026-08-19T11:20:00Z",
"expires_at": "2027-08-19T11:20:00Z"
}When it fails
| Status | Cause | What to do |
|---|---|---|
| 400 | A missing name or conflicting expiry fields. | Choose exactly one expiry form. |
| 404 | No service account with that id. | Confirm the id. |
| 500 | The token could not be stored. | Retry; no token was issued. |
Side effects
- Creates a credential carrying the account’s permissions.
- Writes an audit record.
Proven by
services/nopsai/personal_tokens_test.goservices/nopsai/auth_models.go
DELETE/v1/admin/service-accounts/{serviceAccountID}/tokens/{tokenID}Administrator
Revokes a service account token.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
serviceAccountID | path | string | Required | Service account identifier. |
tokenID | path | string | Required | Token id from the token list — not the token value. |
Call it
curl -sX DELETE -H "Authorization: Bearer $NOPSAI_TOKEN" \
"$NOPSAI_URL/v1/admin/service-accounts/$SERVICE_ACCOUNT_ID/tokens/$TOKEN_ID" -w "%{http_code}\n"Responses
Token revoked.
When it fails
| Status | Cause | What to do |
|---|---|---|
| 404 | No such token for that account. | Confirm both ids. |
| 500 | The revocation failed. | Retry; the token stays valid until it succeeds. |
Side effects
- Invalidates the token immediately.
- Writes an audit record.
Proven by
services/nopsai/personal_tokens_test.goservices/nopsai/personal_tokens.go
GET/v1/admin/rolesAdministrator
Lists product roles.
Call it
curl -s -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/admin/roles" | jqResponses
Product roles.
[
{
"role": "pipeline-operator",
"name": "Pipeline operator",
"obj": "pipeline",
"act": "run",
"effect": "allow"
}
]When it fails
| Status | Cause | What to do |
|---|---|---|
| 405 | A method other than GET. | Use GET. |
| 500 | The query failed. | Platform fault. |
Side effects
- None.
Proven by
services/nopsai/admin_roles_compat_test.goservices/nopsai/admin_role_handlers.go
POST/v1/admin/rolesAdministrator
Creates a product role.
Notes
A role permits an action on a kind of object. Which concrete resources it reaches is decided by grants, not by the role.
Call it
curl -sX POST "$NOPSAI_URL/v1/admin/roles" \
-H "Authorization: Bearer $NOPSAI_TOKEN" \
-H "Content-Type: application/json" \
-d '{"role":"pipeline-operator","name":"Pipeline operator","obj":"pipeline","act":"run","effect":"allow"}' | jqResponses
Role created.
{
"role": "pipeline-operator",
"name": "Pipeline operator",
"obj": "pipeline",
"act": "run",
"effect": "allow"
}When it fails
| Status | Cause | What to do |
|---|---|---|
| 400 | A missing role name, object, action, or effect. | All four are required. |
| 500 | The role could not be saved. | Retry. |
Side effects
- Adds a role subjects can be bound to.
- Writes an audit record.
Proven by
services/nopsai/admin_roles_compat_test.goservices/nopsai/admin_role_handlers.go
DELETE/v1/admin/rolesAdministrator
Deletes a product role.
Notes
Deleting a role silently narrows everyone bound to it. Check bindings before removing one.
Call it
curl -sX DELETE "$NOPSAI_URL/v1/admin/roles" \
-H "Authorization: Bearer $NOPSAI_TOKEN" \
-H "Content-Type: application/json" \
-d '{"role":"pipeline-operator"}' -w "%{http_code}\n"Responses
Role deleted.
When it fails
| Status | Cause | What to do |
|---|---|---|
| 400 | The body names no role. | Send {"role": "..."}. |
| 404 | No such role. | Confirm the name from the list. |
| 500 | The delete failed. | Retry. |
Side effects
- Subjects bound to the role lose what it permitted.
- Writes an audit record.
Proven by
services/nopsai/admin_roles_compat_test.goservices/nopsai/admin_role_handlers.go
POST/v1/admin/user-rolesAdministrator
Grants a role to a user.
Call it
curl -sX POST "$NOPSAI_URL/v1/admin/user-roles" \
-H "Authorization: Bearer $NOPSAI_TOKEN" \
-H "Content-Type: application/json" \
-d '{"user_id":"5b0a...","role":"pipeline-operator"}' | jqResponses
Role bound to the user.
{
"user_id": "5b0a...",
"role": "pipeline-operator"
}When it fails
| Status | Cause | What to do |
|---|---|---|
| 400 | A missing user or role. | Both fields are required. |
| 404 | The user or role does not exist. | Confirm both. |
| 500 | The binding failed. | Retry. |
Side effects
- Changes what the user may do.
- Writes an audit record.
Proven by
services/nopsai/admin_roles_compat_test.goservices/nopsai/admin_role_handlers.go
DELETE/v1/admin/user-rolesAdministrator
Removes a role from a user.
Call it
curl -sX DELETE "$NOPSAI_URL/v1/admin/user-roles" \
-H "Authorization: Bearer $NOPSAI_TOKEN" \
-H "Content-Type: application/json" \
-d '{"user_id":"5b0a...","role":"pipeline-operator"}' -w "%{http_code}\n"Responses
Binding removed.
When it fails
| Status | Cause | What to do |
|---|---|---|
| 400 | A missing user or role. | Both fields are required. |
| 404 | No such binding. | It may already be removed. |
| 500 | The removal failed. | Retry. |
Side effects
- Narrows what the user may do immediately.
- Writes an audit record.
Proven by
services/nopsai/admin_roles_compat_test.goservices/nopsai/admin_role_handlers.go
POST/v1/admin/service-account-rolesAdministrator
Grants a role to a service account.
Notes
A token carries the account’s permissions at request time, not at issue time — which is why rebinding roles does not require rotating tokens.
Call it
curl -sX POST "$NOPSAI_URL/v1/admin/service-account-roles" \
-H "Authorization: Bearer $NOPSAI_TOKEN" \
-H "Content-Type: application/json" \
-d '{"service_account_id":"8e3c...","role":"pipeline-operator"}' | jqResponses
Role bound.
{
"service_account_id": "8e3c...",
"role": "pipeline-operator"
}When it fails
| Status | Cause | What to do |
|---|---|---|
| 400 | A missing account or role. | Both fields are required. |
| 404 | The account or role does not exist. | Confirm both. |
| 500 | The binding failed. | Retry. |
Side effects
- Existing tokens gain the permission without reissue.
- Writes an audit record.
Proven by
services/nopsai/admin_roles_compat_test.goservices/nopsai/admin_role_handlers.go
DELETE/v1/admin/service-account-rolesAdministrator
Removes a role from a service account.
Call it
curl -sX DELETE "$NOPSAI_URL/v1/admin/service-account-roles" \
-H "Authorization: Bearer $NOPSAI_TOKEN" \
-H "Content-Type: application/json" \
-d '{"service_account_id":"8e3c...","role":"pipeline-operator"}' -w "%{http_code}\n"Responses
Binding removed.
When it fails
| Status | Cause | What to do |
|---|---|---|
| 400 | A missing account or role. | Both fields are required. |
| 404 | No such binding. | It may already be removed. |
| 500 | The removal failed. | Retry. |
Side effects
- Narrows every token the account holds.
- Writes an audit record.
Proven by
services/nopsai/admin_roles_compat_test.goservices/nopsai/admin_role_handlers.go
GET/v1/admin/identity-providersAdministrator
Lists configured identity providers.
Notes
has_client_credential is how you check a provider is configured without the API ever returning the secret.
Call it
curl -s -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/admin/identity-providers" | jqResponses
Providers with their settings.
{
"local_enabled": true,
"oidc_enabled": true,
"providers": [
{
"id": "keycloak",
"type": "oidc",
"display_name": "Corporate SSO",
"has_client_credential": true
}
]
}When it fails
| Status | Cause | What to do |
|---|---|---|
| 405 | A method other than GET or PUT. | Use GET to read, PUT to replace. |
Side effects
- None.
Proven by
services/nopsai/auth_oidc_store_test.goservices/nopsai/auth_oidc_models.go
PUT/v1/admin/identity-providersAdministrator
Replaces the whole identity provider configuration.
Notes
local_enabled: false is accepted only when external auth is enabled and at least one provider remains enabled.
Call it
curl -sX PUT "$NOPSAI_URL/v1/admin/identity-providers" \
-H "Authorization: Bearer $NOPSAI_TOKEN" \
-H "Content-Type: application/json" \
--data @providers.json | jqResponses
Configuration replaced.
{
"local_enabled": true,
"oidc_enabled": true,
"providers": []
}When it fails
| Status | Cause | What to do |
|---|---|---|
| 400 | Turning local login off while no identity provider remains enabled. | The platform refuses a configuration that would lock everyone out. |
| 405 | An unsupported method. | Use GET or PUT. |
Side effects
- Changes how everyone signs in.
- Writes an audit record.
Proven by
services/nopsai/auth_oidc_store_test.goservices/nopsai/auth_oidc_handlers.go
PUT/v1/admin/identity-providers/{provider}Administrator
Creates or replaces one identity provider.
Notes
Changing the issuer of a live provider re-identifies its users: the subject is scoped by issuer, so existing accounts stop matching.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
provider | path | string | Required | Provider id. |
Call it
curl -sX PUT "$NOPSAI_URL/v1/admin/identity-providers/keycloak" \
-H "Authorization: Bearer $NOPSAI_TOKEN" \
-H "Content-Type: application/json" \
--data @keycloak.json | jqResponses
Provider stored.
{
"id": "keycloak",
"type": "oidc",
"display_name": "Corporate SSO",
"has_client_credential": true
}When it fails
| Status | Cause | What to do |
|---|---|---|
| 400 | An unreachable issuer, a missing client credential, or an invalid mapping. | The message names the field. |
| 405 | An unsupported method. | Use PUT or DELETE. |
Side effects
- Changes how users of this provider authenticate.
- Writes an audit record.
Proven by
services/nopsai/auth_oidc_store_test.goservices/nopsai/auth_oidc_handlers.go
DELETE/v1/admin/identity-providers/{provider}Administrator
Removes an identity provider.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
provider | path | string | Required | Provider to remove. |
Call it
curl -sX DELETE -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/admin/identity-providers/keycloak" -w "%{http_code}\n"Responses
Provider removed.
When it fails
| Status | Cause | What to do |
|---|---|---|
| 400 | Removing the last enabled provider while local login is off. | Enable local login first, or the install locks itself out. |
| 405 | An unsupported method. | Use PUT or DELETE. |
Side effects
- Users of the provider lose their sign-in path; their accounts and grants remain.
- Writes an audit record.
Proven by
services/nopsai/auth_oidc_store_test.goservices/nopsai/auth_oidc_handlers.go
GET/v1/auditAdministrator
Reads the audit log.
Notes
This is where a secret read shows up. If you want to know who recovered a credential, this is the record that says so.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
limit | query | integer | Optional | How many records to return. |
Call it
curl -s -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/audit?limit=50" | jqResponses
Audit records, newest first.
[
{
"actor_type": "user",
"actor_id": "admin",
"action": "secret.read_value",
"resource_type": "secret",
"resource_id": "platform/production:REGISTRY_TOKEN",
"decision": "allow",
"created_at": "2026-08-19T12:31:02Z"
}
]When it fails
| Status | Cause | What to do |
|---|---|---|
| 500 | The audit query failed. | Platform fault. |
Side effects
- None. Reading the audit log is itself audited by the request chain.
Proven by
services/nopsai/auth_middleware_test.goservices/nopsai/routes.goservices/nopsai/pkg/audit
How it works
Users and service accounts are the same kind of thing to the authorization layer: a subject with roles and grants. The difference is operational — a person leaves, a system does not — which is why an integration should hold a service account token rather than a personal one.
Roles and grants answer different halves of one question. A role permits an action on a kind of object; a grant decides which concrete resources it reaches. Neither alone tells you whether a caller can do something, which is what effective permissions are for.
Prefer disabling to deleting. A disabled account stops authenticating while its audit trail still resolves to a real subject; a deleted one leaves records naming an account nobody can look up.
Implementation evidence
services/nopsai/admin_user_handlers.goUser and service account handlers.
services/nopsai/admin_role_handlers.goRole definitions and bindings.
services/nopsai/auth_oidc_handlers.goIdentity provider configuration.

