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