# Security — AcadeDoc AcadeDoc security is configured server-side via environment variables. This document covers OIDC single sign-on, personal API keys, and the audit log. ## OIDC Single Sign-On OIDC lets users log in via an external identity provider (Authentik, Keycloak, Auth0, etc.) using the OpenID Connect protocol. ### Environment variables | Variable | Required | Default | Description | |---|---|---|---| | `OIDC_ENABLED` | yes | `false` | Set to `true` to activate OIDC | | `OIDC_ISSUER` | yes | — | Provider discovery URL (e.g. `https://auth.example.com/realms/myrealm`) | | `OIDC_CLIENT_ID` | yes | — | OAuth2 client ID registered at the provider | | `OIDC_CLIENT_SECRET` | yes | — | OAuth2 client secret (not returned by the status API — server-side only) | | `OIDC_REDIRECT_URI` | no | auto | Callback URL. Auto-detected if omitted: `$APP_URL/api/auth/oidc/callback` | | `OIDC_SCOPES` | no | `openid email profile` | Space-separated OAuth2 scopes | | `OIDC_PROVIDER_NAME` | no | `SSO` | Label shown on the login button | | `OIDC_AUTO_PROVISION` | no | `false` | Auto-create user account on first OIDC login | | `OIDC_DEFAULT_WORKSPACE_ID` | no | — | Auto-join workspace UUID on provisioning | ### Authentik example 1. Create an OAuth2/OIDC provider in Authentik with grant type `Authorization Code`. 2. Set the redirect URI to `https://yourdomain.com/api/auth/oidc/callback`. 3. Copy the client ID and secret from the provider page. ```env OIDC_ENABLED=true OIDC_ISSUER=https://authentik.example.com/application/o/acadedoc/ OIDC_CLIENT_ID=acadedoc OIDC_CLIENT_SECRET= OIDC_PROVIDER_NAME=Authentik OIDC_AUTO_PROVISION=true ``` ### Keycloak example ```env OIDC_ENABLED=true OIDC_ISSUER=https://keycloak.example.com/realms/myrealm OIDC_CLIENT_ID=acadedoc OIDC_CLIENT_SECRET= OIDC_PROVIDER_NAME=Keycloak OIDC_SCOPES=openid email profile ``` ### Auth0 example ```env OIDC_ENABLED=true OIDC_ISSUER=https://your-tenant.auth0.com/ OIDC_CLIENT_ID= OIDC_CLIENT_SECRET= OIDC_PROVIDER_NAME=Auth0 OIDC_SCOPES=openid email profile ``` ### PKCE AcadeDoc uses PKCE (Proof Key for Code Exchange, RFC 7636) by default. The code verifier is stored in a short-lived signed cookie and cleared immediately after the callback is consumed. No additional configuration is required. --- ## Personal API Keys Personal API keys allow programmatic access to the AcadeDoc API. Each key is scoped to the user account that created it. ### Token format Tokens are prefixed with `acdk_` followed by 64 random hex characters. Example: ``` acdk_8f2a1b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a ``` ### Security - [CLAIM L1] Only a bcrypt hash is stored in the database (source: `apps/server/src/core/acadenice/api-keys/services/api-key.service.ts` — `bcrypt.hash(plain, BCRYPT_ROUNDS)`). The plaintext is shown once at creation and not retained server-side. - Tokens are not included in server logs by design. Verify your log aggregator does not capture request bodies. - The `acdk_` prefix allows ops to identify and redact tokens in log pipelines before forwarding. ### Best practices - Rotate tokens every 90 days. - Use short-lived tokens (30 days) for CI/CD pipelines. - Do not commit tokens to source control. Use a secret manager (Vault, Doppler, GitHub Secrets). - Use separate tokens per application so you can revoke one without disrupting others. - Set `expiresAt` for all tokens except trusted long-term integrations. ### RGPD / GDPR Tokens grant full account access. When a user is deleted, all their tokens are cascade-deleted via the `ON DELETE CASCADE` foreign key on `acadenice_api_key.user_id`. No manual cleanup is required. --- ## Audit Log The audit log records workspace events for compliance and incident investigation. ### What is logged | Event | Trigger | |---|---| | `page.created` | Page creation | | `page.updated` | Page edit | | `page.deleted` | Page deletion | | `space.created` | Space creation | | `space.deleted` | Space deletion | | `user.invited` | Invitation sent | | `user.deleted` | Member removed | | `workspace.updated` | Workspace settings changed | Each entry records: timestamp, actor (user email), event type, resource type and ID, IP address, and a JSONB diff of changes. ### Retention By default, audit entries are kept indefinitely. For GDPR compliance, configure a retention policy by purging rows older than your legal requirement: ```sql -- Example: delete entries older than 365 days DELETE FROM audit WHERE created_at < now() - interval '365 days'; ``` A cron job or `pg_cron` task is recommended for automated purging. ### Access control Only workspace admins and owners can read the audit log (`audit_log:read` permission).