External authentication (ADR 0021) built on jose (#188's vetted library) plus fetch — no new dependency enters the supply chain for a security base function. Discovery-configured; ID tokens validate against the IdP's JWKS under an explicit RS256/ES256 allowlist with issuer, audience, expiry and nonce binding. State, nonce and the PKCE verifier travel in a signed HttpOnly Lax cookie keyed by a dedicated HKDF purpose (oidc-state, ADR 0020). Deploy-level configuration (OIDC_ISSUER/CLIENT_ID/CLIENT_SECRET/SCOPES/ PROVIDER_LABEL): who authenticates users is a platform decision. The login page discovers the provider via GET /auth/methods and renders the SSO button (i18n de+en). Identities use the existing slot (provider oidc:<issuer>, subject from the token). First login creates the account just-in-time — ACTIVE and mail-verified only when the IdP asserts a verified address. An existing local account is NEVER adopted silently by e-mail (account-takeover path): login refuses with oidc_link_required and the owner links explicitly via GET /auth/oidc/link (audited auth.identity_linked, catalogue v1.3). Sessions come from the one existing session service. Tests run the full flow against a protocol-faithful fake IdP: PKCE verifier at the token endpoint, JIT creation incl. personal pond, invalid state/nonce/signature/issuer/audience/expiry each rejected, the linking refusal and the explicit link flow. Verified end-to-end against a real Keycloak 26.0 (repeatable procedure documented in security.md §External authentication). Refs #214. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AUtYMxwTCMHG9mVHnwbFg8
72 lines
2.0 KiB
JSON
72 lines
2.0 KiB
JSON
{
|
|
"name": "@dorfteich/api",
|
|
"version": "0.0.0",
|
|
"private": true,
|
|
"description": "Dorfteich REST API server",
|
|
"license": "MIT",
|
|
"scripts": {
|
|
"build": "prisma generate && nest build",
|
|
"start": "node dist/main.js",
|
|
"start:dev": "prisma generate && nest start --watch",
|
|
"typecheck": "tsc --noEmit",
|
|
"test": "vitest run --passWithNoTests",
|
|
"db:migrate:dev": "prisma migrate dev",
|
|
"db:seed": "tsx prisma/seed.ts",
|
|
"fixtures:regenerate": "tsx prisma/fixtures/regenerate.ts",
|
|
"search:reindex": "tsx src/search/reindex.cli.ts"
|
|
},
|
|
"dependencies": {
|
|
"@dorfteich/plugin-sdk": "workspace:*",
|
|
"@dorfteich/shared": "workspace:*",
|
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
"@nestjs/common": "^11.0.0",
|
|
"@nestjs/core": "^11.0.0",
|
|
"@nestjs/platform-express": "^11.0.0",
|
|
"@prisma/client": "^6.3.0",
|
|
"archiver": "^7.0.1",
|
|
"argon2": "^0.44.0",
|
|
"cookie-parser": "^1.4.7",
|
|
"dompurify": "^3.4.11",
|
|
"fflate": "^0.8.3",
|
|
"fractional-indexing": "^4.0.0",
|
|
"i18next": "^26.3.4",
|
|
"jose": "^6.2.4",
|
|
"jsdom": "^26.1.0",
|
|
"multer": "^2.1.1",
|
|
"nestjs-pino": "^4.3.0",
|
|
"nodemailer": "^9.0.3",
|
|
"pg": "^8.22.0",
|
|
"pino": "^9.6.0",
|
|
"pino-http": "^10.4.0",
|
|
"prisma": "^6.3.0",
|
|
"prosemirror-model": "^1.25.9",
|
|
"prosemirror-state": "^1.4.4",
|
|
"prosemirror-view": "^1.42.0",
|
|
"reflect-metadata": "^0.2.2",
|
|
"rxjs": "^7.8.0",
|
|
"y-prosemirror": "^1.3.7",
|
|
"y-protocols": "^1.0.7",
|
|
"yjs": "^13.6.31",
|
|
"zod": "^3.25.76"
|
|
},
|
|
"devDependencies": {
|
|
"@nestjs/cli": "^11.0.0",
|
|
"@nestjs/testing": "^11.0.0",
|
|
"@swc/core": "^1.10.0",
|
|
"@types/archiver": "^6.0.4",
|
|
"@types/cookie-parser": "^1.4.10",
|
|
"@types/express": "^5.0.0",
|
|
"@types/jsdom": "^28.0.3",
|
|
"@types/multer": "^2.0.0",
|
|
"@types/nodemailer": "^8.0.1",
|
|
"@types/pg": "^8.20.0",
|
|
"@types/supertest": "^6.0.0",
|
|
"pdf-parse": "^2.4.5",
|
|
"pino-pretty": "^13.0.0",
|
|
"supertest": "^7.0.0",
|
|
"tsx": "^4.19.0",
|
|
"unplugin-swc": "^1.5.0",
|
|
"vitest": "^3.0.0"
|
|
}
|
|
}
|