Server (NestJS): - AcadeniceAuditLogModule: GET /api/acadenice/audit-log (admin/owner, Kysely, paginated + filtered) - AcadeniceApiKeysModule: GET/POST/DELETE /api/acadenice/api-keys (JWT, bcrypt hash, acdk_ prefix) - AcadeniceSecurityModule: GET /api/acadenice/security/oidc-status (admin, no secrets exposed) - Migration 20260510T100000: acadenice_api_key table with token_hash + bcrypt - Permissions catalog: added audit_log:read Client (React 18 + Mantine v7): - Audit log page: paginated table with filters (event, userId, date range) - API keys page: list/create/revoke personal tokens, one-time display modal - Security/OIDC status page: read-only, env-var config reference - Sidebar rewired: Security & SSO, API keys, Audit log -> acadenice/* routes (no EE feature gates) - Prefetch functions for new routes Tests: 36 server (Jest) + client typecheck clean Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
4.7 KiB
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
- Create an OAuth2/OIDC provider in Authentik with grant type
Authorization Code. - Set the redirect URI to
https://yourdomain.com/api/auth/oidc/callback. - Copy the client ID and secret from the provider page.
OIDC_ENABLED=true
OIDC_ISSUER=https://authentik.example.com/application/o/acadedoc/
OIDC_CLIENT_ID=acadedoc
OIDC_CLIENT_SECRET=<secret>
OIDC_PROVIDER_NAME=Authentik
OIDC_AUTO_PROVISION=true
Keycloak example
OIDC_ENABLED=true
OIDC_ISSUER=https://keycloak.example.com/realms/myrealm
OIDC_CLIENT_ID=acadedoc
OIDC_CLIENT_SECRET=<secret>
OIDC_PROVIDER_NAME=Keycloak
OIDC_SCOPES=openid email profile
Auth0 example
OIDC_ENABLED=true
OIDC_ISSUER=https://your-tenant.auth0.com/
OIDC_CLIENT_ID=<client_id>
OIDC_CLIENT_SECRET=<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
expiresAtfor 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:
-- 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).