AcadeDoc/docs/security.md
Corentin 5b512e6324 feat(acadedoc): replace EE Settings with open source UI (audit log, API keys, OIDC status) — R4.5
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>
2026-05-08 12:24:00 +02:00

130 lines
4.7 KiB
Markdown

# 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=<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=<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=<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 `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).