create-authenik8-app 2.4.11 → 2.4.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +160 -57
- package/assets/logo.svg +53 -0
- package/dist/src/bin/index.js +199 -125
- package/dist/src/bin/index.js.map +1 -1
- package/dist/src/lib/args.d.ts +18 -0
- package/dist/src/lib/args.d.ts.map +1 -0
- package/dist/src/lib/args.js +82 -0
- package/dist/src/lib/args.js.map +1 -0
- package/dist/src/lib/constants.d.ts +1 -0
- package/dist/src/lib/constants.d.ts.map +1 -1
- package/dist/src/lib/constants.js +10 -11
- package/dist/src/lib/constants.js.map +1 -1
- package/dist/src/lib/preflight.d.ts +3 -0
- package/dist/src/lib/preflight.d.ts.map +1 -0
- package/dist/src/lib/preflight.js +21 -0
- package/dist/src/lib/preflight.js.map +1 -0
- package/dist/src/lib/process.d.ts +6 -2
- package/dist/src/lib/process.d.ts.map +1 -1
- package/dist/src/lib/process.js +66 -7
- package/dist/src/lib/process.js.map +1 -1
- package/dist/src/lib/projectName.d.ts +2 -0
- package/dist/src/lib/projectName.d.ts.map +1 -0
- package/dist/src/lib/projectName.js +6 -0
- package/dist/src/lib/projectName.js.map +1 -0
- package/dist/src/lib/schemas.d.ts +192 -0
- package/dist/src/lib/schemas.d.ts.map +1 -0
- package/dist/src/lib/schemas.js +171 -0
- package/dist/src/lib/schemas.js.map +1 -0
- package/dist/src/lib/state.d.ts.map +1 -1
- package/dist/src/lib/state.js +20 -3
- package/dist/src/lib/state.js.map +1 -1
- package/dist/src/lib/types.d.ts +6 -13
- package/dist/src/lib/types.d.ts.map +1 -1
- package/dist/src/lib/ui.d.ts +8 -3
- package/dist/src/lib/ui.d.ts.map +1 -1
- package/dist/src/lib/ui.js +76 -112
- package/dist/src/lib/ui.js.map +1 -1
- package/dist/src/steps/configurePrisma.d.ts.map +1 -1
- package/dist/src/steps/configurePrisma.js +83 -9
- package/dist/src/steps/configurePrisma.js.map +1 -1
- package/dist/src/steps/createProject.d.ts +2 -1
- package/dist/src/steps/createProject.d.ts.map +1 -1
- package/dist/src/steps/createProject.js +149 -28
- package/dist/src/steps/createProject.js.map +1 -1
- package/dist/src/steps/finalSetup.d.ts +3 -3
- package/dist/src/steps/finalSetup.d.ts.map +1 -1
- package/dist/src/steps/finalSetup.js +24 -28
- package/dist/src/steps/finalSetup.js.map +1 -1
- package/dist/src/steps/installAuth.d.ts +1 -1
- package/dist/src/steps/installAuth.d.ts.map +1 -1
- package/dist/src/steps/installAuth.js +5 -4
- package/dist/src/steps/installAuth.js.map +1 -1
- package/dist/src/steps/installDeps.d.ts +11 -4
- package/dist/src/steps/installDeps.d.ts.map +1 -1
- package/dist/src/steps/installDeps.js +45 -77
- package/dist/src/steps/installDeps.js.map +1 -1
- package/dist/src/steps/prompts.d.ts +1 -1
- package/dist/src/steps/prompts.d.ts.map +1 -1
- package/dist/src/steps/prompts.js +42 -30
- package/dist/src/steps/prompts.js.map +1 -1
- package/dist/src/utils/jwk.d.ts +2 -0
- package/dist/src/utils/jwk.d.ts.map +1 -0
- package/dist/src/utils/jwk.js +44 -0
- package/dist/src/utils/jwk.js.map +1 -0
- package/dist/src/utils/output.d.ts +1 -1
- package/dist/src/utils/output.d.ts.map +1 -1
- package/dist/src/utils/output.js +75 -68
- package/dist/src/utils/output.js.map +1 -1
- package/package.json +17 -9
- package/templates/AGENT_IDENTITY.md +112 -0
- package/templates/THREAT_MODEL.md +27 -5
- package/templates/express-auth/README.md +13 -10
- package/templates/express-auth/docker-compose.yml +10 -0
- package/templates/express-auth/gitignore.template +6 -0
- package/templates/express-auth/package.json +13 -6
- package/templates/express-auth/src/app.ts +2 -0
- package/templates/express-auth/src/controllers/auth.controller.ts +2 -6
- package/templates/express-auth/src/controllers/protected.controller.ts +25 -10
- package/templates/express-auth/src/middleware/auth.middleware.ts +1 -0
- package/templates/express-auth/src/routes/protected.routes.ts +3 -1
- package/templates/express-auth/src/server.ts +6 -12
- package/templates/express-auth/src/utils/security.ts +119 -32
- package/templates/express-auth+/README.md +13 -10
- package/templates/express-auth+/docker-compose.yml +10 -0
- package/templates/express-auth+/gitignore.template +6 -0
- package/templates/express-auth+/package.json +13 -6
- package/templates/express-auth+/src/auth/auth.ts +3 -2
- package/templates/express-auth+/src/auth/controllers/password.controller.ts +3 -7
- package/templates/express-auth+/src/auth/controllers/protected.controller.ts +26 -11
- package/templates/express-auth+/src/auth/identity.adapter.ts +90 -0
- package/templates/express-auth+/src/auth/middleware/auth.middleware.ts +3 -4
- package/templates/express-auth+/src/auth/routes/protected.routes.ts +2 -2
- package/templates/express-auth+/src/oauth-providers/github/src/auth/auth.ts +3 -2
- package/templates/express-auth+/src/oauth-providers/google/src/auth/auth.ts +3 -2
- package/templates/express-auth+/src/oauth-providers/google-github/src/auth/auth.ts +3 -2
- package/templates/express-auth+/src/server.ts +5 -10
- package/templates/express-auth+/src/utils/security.ts +109 -28
- package/templates/express-base/README.md +13 -6
- package/templates/express-base/app.ts +2 -0
- package/templates/express-base/controllers/base.controller.ts +33 -19
- package/templates/express-base/docker-compose.yml +10 -0
- package/templates/express-base/gitignore.template +6 -0
- package/templates/express-base/package.json +11 -5
- package/templates/express-base/routes/base.routes.ts +1 -1
- package/templates/express-base/src/.env.example +7 -2
- package/templates/express-base/src/server.ts +6 -12
- package/templates/express-base/tsconfig.json +7 -40
- package/templates/express-base/utils/security.ts +111 -15
- package/templates/fullstack/.env.example +24 -0
- package/templates/fullstack/PRESET_CONTRACT.md +46 -0
- package/templates/fullstack/README.md +65 -0
- package/templates/fullstack/THREAT_MODEL.md +30 -0
- package/templates/fullstack/apps/api/package.json +48 -0
- package/templates/fullstack/apps/api/prisma/schema.prisma +124 -0
- package/templates/fullstack/apps/api/prisma/seed.ts +36 -0
- package/templates/fullstack/apps/api/prisma.config.ts +18 -0
- package/templates/fullstack/apps/api/src/app.ts +68 -0
- package/templates/fullstack/apps/api/src/auth/auth.controller.ts +125 -0
- package/templates/fullstack/apps/api/src/auth/auth.routes.ts +50 -0
- package/templates/fullstack/apps/api/src/auth/auth.service.ts +257 -0
- package/templates/fullstack/apps/api/src/auth/authenik8.ts +67 -0
- package/templates/fullstack/apps/api/src/auth/cookies.ts +36 -0
- package/templates/fullstack/apps/api/src/config/env.ts +72 -0
- package/templates/fullstack/apps/api/src/config/logger.ts +17 -0
- package/templates/fullstack/apps/api/src/config/mailer.ts +32 -0
- package/templates/fullstack/apps/api/src/config/origins.ts +27 -0
- package/templates/fullstack/apps/api/src/config/prisma.ts +9 -0
- package/templates/fullstack/apps/api/src/middleware/authenticate.ts +35 -0
- package/templates/fullstack/apps/api/src/middleware/csrf.ts +60 -0
- package/templates/fullstack/apps/api/src/middleware/origin.ts +12 -0
- package/templates/fullstack/apps/api/src/middleware/request-id.ts +9 -0
- package/templates/fullstack/apps/api/src/modules/admin/admin.controller.ts +17 -0
- package/templates/fullstack/apps/api/src/modules/admin/admin.routes.ts +14 -0
- package/templates/fullstack/apps/api/src/modules/admin/admin.service.ts +54 -0
- package/templates/fullstack/apps/api/src/modules/projects/project.controller.ts +22 -0
- package/templates/fullstack/apps/api/src/modules/projects/project.policy.ts +14 -0
- package/templates/fullstack/apps/api/src/modules/projects/project.repository.ts +20 -0
- package/templates/fullstack/apps/api/src/modules/projects/project.routes.ts +21 -0
- package/templates/fullstack/apps/api/src/modules/projects/project.service.ts +41 -0
- package/templates/fullstack/apps/api/src/modules/users/user.controller.ts +26 -0
- package/templates/fullstack/apps/api/src/modules/users/user.presenter.ts +13 -0
- package/templates/fullstack/apps/api/src/modules/users/user.routes.ts +21 -0
- package/templates/fullstack/apps/api/src/modules/users/user.service.ts +71 -0
- package/templates/fullstack/apps/api/src/openapi.ts +32 -0
- package/templates/fullstack/apps/api/src/server.ts +29 -0
- package/templates/fullstack/apps/api/src/types.ts +15 -0
- package/templates/fullstack/apps/api/src/utils/crypto.ts +4 -0
- package/templates/fullstack/apps/api/src/utils/http.ts +33 -0
- package/templates/fullstack/apps/api/src/utils/sealed-value.ts +37 -0
- package/templates/fullstack/apps/api/tests/project.policy.test.ts +26 -0
- package/templates/fullstack/apps/api/tests/registration.test.ts +69 -0
- package/templates/fullstack/apps/api/tests/security.test.ts +98 -0
- package/templates/fullstack/apps/api/tests/validation.test.ts +37 -0
- package/templates/fullstack/apps/api/tsconfig.json +16 -0
- package/templates/fullstack/apps/api/vitest.config.ts +9 -0
- package/templates/fullstack/apps/web/index.html +14 -0
- package/templates/fullstack/apps/web/package.json +31 -0
- package/templates/fullstack/apps/web/public/authenik8-logo.svg +53 -0
- package/templates/fullstack/apps/web/src/App.tsx +45 -0
- package/templates/fullstack/apps/web/src/auth/AuthProvider.tsx +53 -0
- package/templates/fullstack/apps/web/src/auth/RouteGuards.tsx +16 -0
- package/templates/fullstack/apps/web/src/auth/providers.ts +3 -0
- package/templates/fullstack/apps/web/src/components/AppShell.tsx +80 -0
- package/templates/fullstack/apps/web/src/components/AuthShell.tsx +26 -0
- package/templates/fullstack/apps/web/src/components/Page.tsx +30 -0
- package/templates/fullstack/apps/web/src/features/projects/ProjectPages.tsx +79 -0
- package/templates/fullstack/apps/web/src/main.tsx +24 -0
- package/templates/fullstack/apps/web/src/pages/DashboardPage.tsx +48 -0
- package/templates/fullstack/apps/web/src/pages/StatusPages.tsx +8 -0
- package/templates/fullstack/apps/web/src/pages/admin/AuditPage.tsx +19 -0
- package/templates/fullstack/apps/web/src/pages/admin/UsersPage.tsx +24 -0
- package/templates/fullstack/apps/web/src/pages/auth/LoginPage.tsx +51 -0
- package/templates/fullstack/apps/web/src/pages/auth/OAuthCallbackPage.tsx +26 -0
- package/templates/fullstack/apps/web/src/pages/auth/RecoveryPages.tsx +53 -0
- package/templates/fullstack/apps/web/src/pages/auth/RegisterPage.tsx +86 -0
- package/templates/fullstack/apps/web/src/pages/auth/VerifyEmailPage.tsx +24 -0
- package/templates/fullstack/apps/web/src/pages/settings/ProfilePage.tsx +34 -0
- package/templates/fullstack/apps/web/src/pages/settings/SecurityPage.tsx +57 -0
- package/templates/fullstack/apps/web/src/styles.css +289 -0
- package/templates/fullstack/apps/web/src/token-storage.test.ts +12 -0
- package/templates/fullstack/apps/web/tsconfig.app.json +19 -0
- package/templates/fullstack/apps/web/tsconfig.json +7 -0
- package/templates/fullstack/apps/web/tsconfig.node.json +10 -0
- package/templates/fullstack/apps/web/vite.config.ts +18 -0
- package/templates/fullstack/docker-compose.yml +35 -0
- package/templates/fullstack/docs/PRODUCTION.md +30 -0
- package/templates/fullstack/gitignore.template +8 -0
- package/templates/fullstack/package.json +39 -0
- package/templates/fullstack/packages/api-client/package.json +20 -0
- package/templates/fullstack/packages/api-client/src/index.ts +203 -0
- package/templates/fullstack/packages/api-client/tsconfig.json +14 -0
- package/templates/fullstack/packages/contracts/package.json +20 -0
- package/templates/fullstack/packages/contracts/src/index.ts +146 -0
- package/templates/fullstack/packages/contracts/tsconfig.json +13 -0
- package/templates/fullstack/packages/ui/package.json +22 -0
- package/templates/fullstack/packages/ui/src/index.tsx +33 -0
- package/templates/fullstack/packages/ui/tsconfig.json +14 -0
- package/templates/prisma/postgresql/.env.example +6 -2
- package/templates/prisma/postgresql/client.ts +17 -0
- package/templates/prisma/postgresql/prisma.config.ts +12 -0
- package/templates/prisma/postgresql/schema.prisma +0 -1
- package/templates/prisma/sqlite/.env.example +6 -2
- package/templates/prisma/sqlite/client.ts +16 -0
- package/templates/prisma/sqlite/prisma.config.ts +12 -0
- package/templates/prisma/sqlite/schema.prisma +0 -1
- package/dist/src/tests/templated-routes.test.d.ts +0 -2
- package/dist/src/tests/templated-routes.test.d.ts.map +0 -1
- package/dist/src/tests/templated-routes.test.js +0 -325
- package/dist/src/tests/templated-routes.test.js.map +0 -1
- package/dist/templates/THREAT_MODEL.md +0 -138
- package/dist/templates/express-auth/src/controllers/protected.controller.d.ts +0 -8
- package/dist/templates/express-auth/src/controllers/protected.controller.d.ts.map +0 -1
- package/dist/templates/express-auth/src/controllers/protected.controller.js +0 -50
- package/dist/templates/express-auth/src/controllers/protected.controller.js.map +0 -1
- package/dist/templates/express-auth/src/routes/protected.routes.d.ts +0 -2
- package/dist/templates/express-auth/src/routes/protected.routes.d.ts.map +0 -1
- package/dist/templates/express-auth/src/routes/protected.routes.js +0 -16
- package/dist/templates/express-auth/src/routes/protected.routes.js.map +0 -1
- package/dist/templates/express-auth/src/utils/security.d.ts +0 -8
- package/dist/templates/express-auth/src/utils/security.d.ts.map +0 -1
- package/dist/templates/express-auth/src/utils/security.js +0 -55
- package/dist/templates/express-auth/src/utils/security.js.map +0 -1
- package/dist/templates/express-base/controllers/base.controller.d.ts +0 -12
- package/dist/templates/express-base/controllers/base.controller.d.ts.map +0 -1
- package/dist/templates/express-base/controllers/base.controller.js +0 -77
- package/dist/templates/express-base/controllers/base.controller.js.map +0 -1
- package/dist/templates/express-base/routes/base.routes.d.ts +0 -2
- package/dist/templates/express-base/routes/base.routes.d.ts.map +0 -1
- package/dist/templates/express-base/routes/base.routes.js +0 -20
- package/dist/templates/express-base/routes/base.routes.js.map +0 -1
- package/dist/templates/express-base/utils/security.d.ts +0 -5
- package/dist/templates/express-base/utils/security.d.ts.map +0 -1
- package/dist/templates/express-base/utils/security.js +0 -40
- package/dist/templates/express-base/utils/security.js.map +0 -1
- package/dist/templates/package.json +0 -27
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-authenik8-app",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.13",
|
|
4
4
|
"description": " Fast Express + TypeScript auth starter with secure JWT, refresh rotation, Redis, RBAC, OAuth & Prisma.\nPowered by the Authenik8 Identity Engine.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"create-authenik8-app": "dist/src/bin/index.js"
|
|
@@ -28,27 +28,31 @@
|
|
|
28
28
|
"license": "MIT",
|
|
29
29
|
"author": "Karabo Seeisa",
|
|
30
30
|
"type": "module",
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": "^20.19 || ^22.12 || >=24"
|
|
33
|
+
},
|
|
31
34
|
"dependencies": {
|
|
32
|
-
"
|
|
33
|
-
"bun": "^1.3.12",
|
|
35
|
+
"@inquirer/core": "^10.3.2",
|
|
34
36
|
"chalk": "^5.6.2",
|
|
35
37
|
"fs-extra": "^11.3.4",
|
|
36
|
-
"inquirer": "^
|
|
37
|
-
"ora": "^
|
|
38
|
-
"
|
|
39
|
-
"typescript": "^6.0.3"
|
|
38
|
+
"inquirer": "^12.11.1",
|
|
39
|
+
"ora": "^8.2.0",
|
|
40
|
+
"zod": "^4.4.3"
|
|
40
41
|
},
|
|
41
42
|
"files": [
|
|
43
|
+
"assets/logo.svg",
|
|
42
44
|
"dist/",
|
|
43
45
|
"templates/"
|
|
44
46
|
],
|
|
45
47
|
"scripts": {
|
|
46
|
-
"
|
|
48
|
+
"clean": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"",
|
|
49
|
+
"build": "npm run clean && tsc",
|
|
47
50
|
"prepublishOnly": "npm run build",
|
|
48
51
|
"test": "vitest run",
|
|
49
52
|
"test:watch": "vitest",
|
|
50
53
|
"test:coverage": "vitest run --coverage",
|
|
51
|
-
"test:templates": "node --import tsx --test tests/template-servers.test.mjs"
|
|
54
|
+
"test:templates": "node --import tsx --test tests/template-servers.test.mjs",
|
|
55
|
+
"test:fresh": "node scripts/verify-fresh-project.mjs"
|
|
52
56
|
},
|
|
53
57
|
"devDependencies": {
|
|
54
58
|
"@types/express": "^5.0.6",
|
|
@@ -56,8 +60,12 @@
|
|
|
56
60
|
"@types/node": "^25.9.3",
|
|
57
61
|
"@types/supertest": "^7.2.0",
|
|
58
62
|
"@vitest/coverage-v8": "^4.1.8",
|
|
63
|
+
"authenik8-core": "2.0.3",
|
|
59
64
|
"fast-check": "^4.8.0",
|
|
65
|
+
"jose": "6.2.3",
|
|
60
66
|
"supertest": "^7.2.2",
|
|
67
|
+
"tsx": "^4.22.4",
|
|
68
|
+
"typescript": "^6.0.3",
|
|
61
69
|
"vitest": "^4.1.8"
|
|
62
70
|
},
|
|
63
71
|
"repository": {
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# Agent and service identity
|
|
2
|
+
|
|
3
|
+
Authenik8 separates human access tokens from AI agents, workers, bots, and
|
|
4
|
+
machine-to-machine callers. Agent tokens use a distinct token type, exact
|
|
5
|
+
`resource:action` scopes, an actor chain, and Redis-backed sessions.
|
|
6
|
+
|
|
7
|
+
## Enable a development registry
|
|
8
|
+
|
|
9
|
+
Agent identity is disabled when `AUTHENIK8_AGENTS` is empty or `{}`. For local
|
|
10
|
+
development, map each agent ID to its maximum grant:
|
|
11
|
+
|
|
12
|
+
```dotenv
|
|
13
|
+
AUTHENIK8_AGENTS='{"build-worker":["tasks:read","tasks:write"]}'
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
The generated configuration validates the IDs and scopes before startup. For
|
|
17
|
+
production, replace this static environment registry with a database-backed
|
|
18
|
+
`resolveAgent` callback so deactivation and scope removal take effect without a
|
|
19
|
+
deployment.
|
|
20
|
+
|
|
21
|
+
## Issue an M2M token from a trusted exchange
|
|
22
|
+
|
|
23
|
+
`issueToken` is a privileged SDK operation, not an authentication endpoint.
|
|
24
|
+
Call it only after authenticating the workload using a mechanism appropriate to
|
|
25
|
+
your infrastructure, such as mTLS, a cloud workload identity, or a signed client
|
|
26
|
+
assertion. Do not expose it as an unauthenticated HTTP route.
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
const agent = auth.agent;
|
|
30
|
+
if (!agent) throw new Error("Agent identity is not configured");
|
|
31
|
+
|
|
32
|
+
const token = await agent.issueToken({
|
|
33
|
+
agentId: "build-worker",
|
|
34
|
+
scopes: ["tasks:read"],
|
|
35
|
+
label: "production queue worker",
|
|
36
|
+
ip: request.ip,
|
|
37
|
+
});
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Requested scopes must be an exact subset of the registry grant. Tokens are
|
|
41
|
+
short-lived and stored under a separate `agent-sessions:<agentId>` namespace.
|
|
42
|
+
Human middleware rejects them.
|
|
43
|
+
|
|
44
|
+
## Protect an agent route
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
import type { AgentAuthenticatedRequest } from "authenik8-core";
|
|
48
|
+
|
|
49
|
+
router.post(
|
|
50
|
+
"/internal/tasks",
|
|
51
|
+
auth.agent!.requireScopes("tasks:write"),
|
|
52
|
+
(req, res) => {
|
|
53
|
+
const actor = (req as AgentAuthenticatedRequest).agent;
|
|
54
|
+
res.json({ accepted: true, actor: actor.agentId });
|
|
55
|
+
},
|
|
56
|
+
);
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Every required scope must be present. Scope matching is exact; wildcard scope
|
|
60
|
+
expansion is intentionally not inferred.
|
|
61
|
+
|
|
62
|
+
## Delegate a human action to an agent
|
|
63
|
+
|
|
64
|
+
Delegation is disabled unless the application supplies `authorizeDelegation`.
|
|
65
|
+
The callback should check the user, tenant, requested operation, consent, and
|
|
66
|
+
any application permission model.
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
const auth = await createAuthenik8({
|
|
70
|
+
// jwt, Redis, and refresh configuration omitted
|
|
71
|
+
agent: {
|
|
72
|
+
resolveAgent: async (agentId) => agentRepository.findActive(agentId),
|
|
73
|
+
authorizeDelegation: async ({ user, agent, requestedScopes }) =>
|
|
74
|
+
user.role === "admin" &&
|
|
75
|
+
agent.agentId === "build-worker" &&
|
|
76
|
+
requestedScopes.every((scope) => scope === "tasks:read"),
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const delegated = await auth.agent!.issueDelegatedToken({
|
|
81
|
+
agentId: "build-worker",
|
|
82
|
+
userAccessToken,
|
|
83
|
+
scopes: ["tasks:read"],
|
|
84
|
+
});
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
The token identifies the user as the subject and the agent as the acting party.
|
|
88
|
+
Its `actorChain` records both actors. Revoking the originating human session
|
|
89
|
+
immediately invalidates the delegated token.
|
|
90
|
+
|
|
91
|
+
## Revoke agent access
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
await auth.agent!.revokeSession("build-worker", sessionId); // one workload session
|
|
95
|
+
await auth.agent!.revokeAgent("build-worker"); // all sessions + issuance block
|
|
96
|
+
await auth.agent!.activateAgent("build-worker"); // clear the core revocation block
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
The external registry must still report the agent as active. Removing an agent
|
|
100
|
+
or reducing its scopes in that registry also invalidates existing tokens during
|
|
101
|
+
verification.
|
|
102
|
+
|
|
103
|
+
## Security boundaries
|
|
104
|
+
|
|
105
|
+
- Agent tokens are bearer credentials; protect them in memory and transport.
|
|
106
|
+
- Never place agent tokens in browser storage, URLs, logs, or source control.
|
|
107
|
+
- Keep token lifetimes short and re-authenticate the workload to obtain another.
|
|
108
|
+
- Public JWKS verification proves the signature but cannot by itself observe
|
|
109
|
+
Redis revocation. Security-sensitive APIs should use the session-aware SDK
|
|
110
|
+
middleware or a trusted introspection boundary.
|
|
111
|
+
- A delegated token never outlives the authority of its originating human
|
|
112
|
+
session.
|
|
@@ -13,6 +13,7 @@ A generated app includes:
|
|
|
13
13
|
- Optional Google/GitHub OAuth.
|
|
14
14
|
- Authenik8 security middleware: Helmet, rate limiting, and optional IP whitelist.
|
|
15
15
|
- Optional PM2 production process config.
|
|
16
|
+
- Optional scoped identities for agents, workers, bots, and M2M callers.
|
|
16
17
|
|
|
17
18
|
External systems are outside the generated app boundary:
|
|
18
19
|
|
|
@@ -33,26 +34,28 @@ External systems are outside the generated app boundary:
|
|
|
33
34
|
- User IDs, emails, roles, and provider links.
|
|
34
35
|
- Redis-backed session state.
|
|
35
36
|
- Admin-only routes.
|
|
37
|
+
- Agent registry grants, delegated actor chains, and M2M sessions.
|
|
36
38
|
|
|
37
39
|
## Trust Assumptions
|
|
38
40
|
|
|
39
|
-
-
|
|
41
|
+
- The active ES256 private JWK and `REFRESH_SECRET` are random, private, and stored outside source control.
|
|
40
42
|
- Redis is private to the app and not exposed to the public internet.
|
|
41
43
|
- Database credentials are private.
|
|
42
44
|
- OAuth callback URLs match provider dashboard settings exactly.
|
|
43
45
|
- Production traffic uses HTTPS.
|
|
44
46
|
- Reverse proxy headers are trusted only when you control the proxy.
|
|
45
47
|
- Developers validate and authorize their own business-domain routes.
|
|
48
|
+
- Applications authenticate workloads before calling privileged agent-token issuance and explicitly authorize every user delegation.
|
|
46
49
|
|
|
47
50
|
## Threats Addressed
|
|
48
51
|
|
|
49
52
|
### Refresh-token replay
|
|
50
53
|
|
|
51
|
-
Refresh tokens are stateful. The currently valid
|
|
54
|
+
Refresh tokens are stateful. The currently valid token for each session is stored under `refresh:<userId>:<sessionId>`. When a refresh succeeds, Authenik8-core atomically replaces it. Reusing an old refresh token fails and revokes that refresh family.
|
|
52
55
|
|
|
53
56
|
### Concurrent refresh abuse
|
|
54
57
|
|
|
55
|
-
Refresh requests acquire a Redis lock under `lock:<userId>`. Two simultaneous refresh attempts for the same
|
|
58
|
+
Refresh requests acquire a Redis lock under `lock:<userId>:<sessionId>`. Two simultaneous refresh attempts for the same session cannot both rotate successfully.
|
|
56
59
|
|
|
57
60
|
### Stateless JWT logout limitations
|
|
58
61
|
|
|
@@ -76,12 +79,21 @@ OAuth profiles are normalized into provider, provider ID, email, name, and email
|
|
|
76
79
|
|
|
77
80
|
### Unverified OAuth email token issuance
|
|
78
81
|
|
|
79
|
-
|
|
82
|
+
Provider callbacks verify the provider email before the Identity Engine issues an application session.
|
|
80
83
|
|
|
81
84
|
### Admin-route access
|
|
82
85
|
|
|
83
86
|
`auth.requireAdmin` checks for a valid JWT with `role: "admin"`.
|
|
84
87
|
|
|
88
|
+
### Agent identity and revocation
|
|
89
|
+
|
|
90
|
+
Human and agent tokens have distinct purposes and middleware. Agent scopes must
|
|
91
|
+
be an exact subset of the live registry grant. M2M sessions are stored under
|
|
92
|
+
`agent-sessions:<agentId>`, and removing a grant, revoking one session, or
|
|
93
|
+
revoking the whole agent invalidates its tokens. Delegated tokens record the
|
|
94
|
+
human and agent actor chain and become invalid when the originating human
|
|
95
|
+
session is revoked.
|
|
96
|
+
|
|
85
97
|
## Threats Not Fully Addressed
|
|
86
98
|
|
|
87
99
|
### XSS in your frontend
|
|
@@ -94,7 +106,7 @@ Generated examples use bearer tokens. If you move tokens into cookies, add CSRF
|
|
|
94
106
|
|
|
95
107
|
### Weak or leaked secrets
|
|
96
108
|
|
|
97
|
-
Authenik8 cannot protect tokens if
|
|
109
|
+
Authenik8 cannot protect tokens if the private signing JWK or `REFRESH_SECRET` is committed, logged, reused, or leaked. Public keys from `/.well-known/jwks.json` are safe to distribute.
|
|
98
110
|
|
|
99
111
|
### Public Redis exposure
|
|
100
112
|
|
|
@@ -120,6 +132,15 @@ Global rate limiting is included. Add stricter per-email or per-account login th
|
|
|
120
132
|
|
|
121
133
|
Short-lived access tokens reduce exposure, but a stolen access token can be used until it expires or is rejected by your session policy.
|
|
122
134
|
|
|
135
|
+
### Workload authentication and offline agent verification
|
|
136
|
+
|
|
137
|
+
The SDK issues agent tokens only after trusted application code calls it; your
|
|
138
|
+
application must authenticate the workload using mTLS, cloud workload identity,
|
|
139
|
+
a signed assertion, or another suitable credential. Never expose issuance as an
|
|
140
|
+
unauthenticated route. Agent tokens remain bearer credentials. Public JWKS
|
|
141
|
+
verification cannot observe Redis revocation without a trusted introspection or
|
|
142
|
+
session-aware middleware boundary.
|
|
143
|
+
|
|
123
144
|
## Production Checklist
|
|
124
145
|
|
|
125
146
|
- Replace generated development secrets with long random values.
|
|
@@ -131,6 +152,7 @@ Short-lived access tokens reduce exposure, but a stolen access token can be used
|
|
|
131
152
|
- Review CORS policy before connecting a frontend.
|
|
132
153
|
- Add business-level authorization checks to every protected resource route.
|
|
133
154
|
- Add logging and alerting for refresh failures, OAuth failures, and admin actions.
|
|
155
|
+
- Keep `AUTHENIK8_AGENTS={}` unless agent identity is intentionally configured; review every agent grant and delegation policy.
|
|
134
156
|
- Keep `authenik8-core` and generated dependencies updated.
|
|
135
157
|
|
|
136
158
|
## Security Reporting
|
|
@@ -15,14 +15,18 @@ For SQLite, Postgres in `docker-compose.yml` is optional. Redis is required for
|
|
|
15
15
|
|
|
16
16
|
## Environment
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
The generator writes a unique signing key and refresh secret to git-ignored `.env`. Move them into your deployment secret manager and never commit the private JWK.
|
|
19
19
|
|
|
20
20
|
Required:
|
|
21
21
|
|
|
22
22
|
```bash
|
|
23
23
|
DATABASE_URL=file:./dev.db
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
AUTHENIK8_SIGNING_JWKS='[{"kty":"EC","crv":"P-256","kid":"<generated>","x":"...","y":"...","d":"...","alg":"ES256"}]'
|
|
25
|
+
AUTHENIK8_ACTIVE_KID=<generated>
|
|
26
|
+
AUTHENIK8_ISSUER=http://localhost:3000
|
|
27
|
+
AUTHENIK8_AUDIENCE=your-app-api
|
|
28
|
+
AUTHENIK8_AGENTS={}
|
|
29
|
+
REFRESH_SECRET=<generated-random-secret>
|
|
26
30
|
REDIS_HOST=127.0.0.1
|
|
27
31
|
REDIS_PORT=6379
|
|
28
32
|
```
|
|
@@ -101,10 +105,6 @@ Expected shape:
|
|
|
101
105
|
|
|
102
106
|
```json
|
|
103
107
|
{
|
|
104
|
-
"user": {
|
|
105
|
-
"id": "user-id",
|
|
106
|
-
"email": "dev@example.com"
|
|
107
|
-
},
|
|
108
108
|
"accessToken": "access-token",
|
|
109
109
|
"refreshToken": "refresh-token"
|
|
110
110
|
}
|
|
@@ -128,7 +128,10 @@ curl -s -X POST http://localhost:3000/auth/refresh \
|
|
|
128
128
|
## Environment Variables
|
|
129
129
|
|
|
130
130
|
- `DATABASE_URL`: Prisma database connection. SQLite uses `file:./dev.db`; Postgres uses a `postgresql://...` URL.
|
|
131
|
-
- `
|
|
131
|
+
- `AUTHENIK8_SIGNING_JWKS`: JSON array containing the active private ES256 key and any previous public verification keys.
|
|
132
|
+
- `AUTHENIK8_ACTIVE_KID`: selects the private key used to sign new access tokens.
|
|
133
|
+
- `AUTHENIK8_ISSUER` / `AUTHENIK8_AUDIENCE`: required claims checked during verification.
|
|
134
|
+
- `AUTHENIK8_AGENTS`: optional JSON map of agent IDs to exact `resource:action` scopes; keep `{}` to disable agent identity. See `AGENT_IDENTITY.md`.
|
|
132
135
|
- `REFRESH_SECRET`: signs refresh tokens. Use a different long random value in production.
|
|
133
136
|
- `REDIS_HOST`: Redis host for refresh-token/session security.
|
|
134
137
|
- `REDIS_PORT`: Redis port, usually `6379` locally.
|
|
@@ -177,13 +180,13 @@ export async function getProtected() {
|
|
|
177
180
|
|
|
178
181
|
`Prisma Client did not initialize`: run `npm run prisma:migrate`, then restart `npm run dev`.
|
|
179
182
|
|
|
180
|
-
`
|
|
183
|
+
`AUTHENIK8_SIGNING_JWKS must be a valid JSON array`: restore the generated `.env` value or provide an ES256 P-256 key ring and matching `AUTHENIK8_ACTIVE_KID`.
|
|
181
184
|
|
|
182
185
|
`Cannot POST /auth/login`: confirm the server is running and you generated the password auth template, not the JWT-only base template.
|
|
183
186
|
|
|
184
187
|
`Invalid email or password`: register the user first, then login with the same email/password.
|
|
185
188
|
|
|
186
|
-
`Port 3000 already in use`: stop the other process or
|
|
189
|
+
`Port 3000 already in use`: stop the other process or set `PORT` in `.env`.
|
|
187
190
|
|
|
188
191
|
`DATABASE_URL is wrong`: for SQLite use `file:./dev.db`; for local Docker Postgres use `postgresql://postgres:postgres@localhost:5432/authenik8?schema=public`.
|
|
189
192
|
|
|
@@ -6,6 +6,11 @@ services:
|
|
|
6
6
|
command: ["redis-server", "--appendonly", "yes"]
|
|
7
7
|
volumes:
|
|
8
8
|
- redis-data:/data
|
|
9
|
+
healthcheck:
|
|
10
|
+
test: ["CMD", "redis-cli", "ping"]
|
|
11
|
+
interval: 5s
|
|
12
|
+
timeout: 3s
|
|
13
|
+
retries: 10
|
|
9
14
|
|
|
10
15
|
postgres:
|
|
11
16
|
image: postgres:16-alpine
|
|
@@ -17,6 +22,11 @@ services:
|
|
|
17
22
|
POSTGRES_DB: authenik8
|
|
18
23
|
volumes:
|
|
19
24
|
- postgres-data:/var/lib/postgresql/data
|
|
25
|
+
healthcheck:
|
|
26
|
+
test: ["CMD-SHELL", "pg_isready -U postgres -d authenik8"]
|
|
27
|
+
interval: 5s
|
|
28
|
+
timeout: 3s
|
|
29
|
+
retries: 10
|
|
20
30
|
|
|
21
31
|
volumes:
|
|
22
32
|
redis-data:
|
|
@@ -3,25 +3,32 @@
|
|
|
3
3
|
"description": "Authenik8 generated Express auth app",
|
|
4
4
|
"main": "dist/server.js",
|
|
5
5
|
"type": "commonjs",
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": "^20.19 || ^22.12 || >=24"
|
|
8
|
+
},
|
|
6
9
|
"scripts": {
|
|
7
10
|
"dev": "ts-node-dev --respawn --transpile-only src/server.ts",
|
|
8
11
|
"build": "tsc",
|
|
9
12
|
"start": "node dist/server.js",
|
|
10
|
-
"prisma:migrate": "prisma migrate dev",
|
|
11
|
-
"docker:up": "docker compose up -d",
|
|
13
|
+
"prisma:migrate": "prisma migrate dev && prisma generate",
|
|
14
|
+
"docker:up": "docker compose up -d --wait",
|
|
12
15
|
"docker:down": "docker compose down"
|
|
13
16
|
},
|
|
14
17
|
"dependencies": {
|
|
15
|
-
"authenik8-core": "
|
|
18
|
+
"authenik8-core": "2.0.3",
|
|
16
19
|
"dotenv": "^16.0.0",
|
|
17
20
|
"express": "^4.19.2",
|
|
18
|
-
"@prisma/client": "
|
|
21
|
+
"@prisma/client": "7.8.0",
|
|
22
|
+
"zod": "^4.4.3"
|
|
19
23
|
},
|
|
20
24
|
"devDependencies": {
|
|
21
25
|
"@types/express": "^4.17.21",
|
|
22
26
|
"@types/node": "^26.0.1",
|
|
23
27
|
"ts-node-dev": "^2.0.0",
|
|
24
|
-
"typescript": "^
|
|
25
|
-
"prisma": "
|
|
28
|
+
"typescript": "^7.0.2",
|
|
29
|
+
"prisma": "7.8.0"
|
|
30
|
+
},
|
|
31
|
+
"overrides": {
|
|
32
|
+
"@hono/node-server": "1.19.13"
|
|
26
33
|
}
|
|
27
34
|
}
|
|
@@ -10,6 +10,8 @@ export const createApp = (auth: any) => {
|
|
|
10
10
|
app.use(auth.helmet);
|
|
11
11
|
app.use(auth.rateLimit);
|
|
12
12
|
|
|
13
|
+
app.get("/.well-known/jwks.json", (_req, res) => res.json(auth.getJwks()));
|
|
14
|
+
|
|
13
15
|
app.use("/auth", createAuthRoutes(auth));
|
|
14
16
|
app.use("/", createProtectedRoutes(auth));
|
|
15
17
|
|
|
@@ -21,14 +21,10 @@ export const createAuthController = (auth: any) => ({
|
|
|
21
21
|
|
|
22
22
|
const user = await AuthService.login(email, password);
|
|
23
23
|
|
|
24
|
-
const accessToken = auth.
|
|
25
|
-
userId: user.id,
|
|
26
|
-
email: user.email,
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
const refreshToken = await auth.generateRefreshToken({
|
|
24
|
+
const { accessToken, refreshToken } = await auth.issueTokens({
|
|
30
25
|
userId: user.id,
|
|
31
26
|
email: user.email,
|
|
27
|
+
role: String(user.role).toLowerCase(),
|
|
32
28
|
});
|
|
33
29
|
|
|
34
30
|
res.json({ accessToken, refreshToken });
|
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
import { Request, Response } from "express";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
InputValidationError,
|
|
4
|
+
parseIdentifier,
|
|
5
|
+
sanitizeSessionResponse,
|
|
6
|
+
} from "../utils/security";
|
|
7
|
+
|
|
8
|
+
function sessionError(res: Response, error: unknown, fallback: string) {
|
|
9
|
+
if (error instanceof InputValidationError) {
|
|
10
|
+
return res.status(400).json({ success: false, message: error.message });
|
|
11
|
+
}
|
|
12
|
+
return res.status(500).json({ success: false, message: fallback });
|
|
13
|
+
}
|
|
3
14
|
|
|
4
15
|
export const createProtectedController = () => ({
|
|
5
16
|
protected(req: Request, res: Response) {
|
|
@@ -13,10 +24,11 @@ export const createProtectedController = () => ({
|
|
|
13
24
|
return res.status(503).json({ success: false, message: "Session management unavailable" });
|
|
14
25
|
}
|
|
15
26
|
|
|
16
|
-
const
|
|
27
|
+
const userId = parseIdentifier(req.params.userId, "User ID");
|
|
28
|
+
const sessions = await actions.listSessions(userId);
|
|
17
29
|
res.json({ sessions: sanitizeSessionResponse(sessions) });
|
|
18
|
-
} catch {
|
|
19
|
-
res
|
|
30
|
+
} catch (error) {
|
|
31
|
+
sessionError(res, error, "Failed to retrieve sessions");
|
|
20
32
|
}
|
|
21
33
|
},
|
|
22
34
|
|
|
@@ -27,10 +39,12 @@ export const createProtectedController = () => ({
|
|
|
27
39
|
return res.status(503).json({ success: false, message: "Session management unavailable" });
|
|
28
40
|
}
|
|
29
41
|
|
|
30
|
-
|
|
42
|
+
const userId = parseIdentifier(req.params.userId, "User ID");
|
|
43
|
+
const sessionId = parseIdentifier(req.params.sessionId, "Session ID");
|
|
44
|
+
await actions.revokeSession(userId, sessionId);
|
|
31
45
|
res.json({ success: true, message: "Session revoked" });
|
|
32
|
-
} catch {
|
|
33
|
-
res
|
|
46
|
+
} catch (error) {
|
|
47
|
+
sessionError(res, error, "Failed to revoke session");
|
|
34
48
|
}
|
|
35
49
|
},
|
|
36
50
|
|
|
@@ -41,10 +55,11 @@ export const createProtectedController = () => ({
|
|
|
41
55
|
return res.status(503).json({ success: false, message: "Session management unavailable" });
|
|
42
56
|
}
|
|
43
57
|
|
|
44
|
-
|
|
58
|
+
const userId = parseIdentifier(req.params.userId, "User ID");
|
|
59
|
+
await actions.revokeAllSessions(userId);
|
|
45
60
|
res.json({ success: true, message: "All sessions revoked" });
|
|
46
|
-
} catch {
|
|
47
|
-
res
|
|
61
|
+
} catch (error) {
|
|
62
|
+
sessionError(res, error, "Failed to revoke sessions");
|
|
48
63
|
}
|
|
49
64
|
},
|
|
50
65
|
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const createAuthMiddleware = (auth: any) => auth.requireAuth;
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { Router } from "express";
|
|
2
2
|
import { createProtectedController } from "../controllers/protected.controller";
|
|
3
|
+
import { createAuthMiddleware } from "../middleware/auth.middleware";
|
|
3
4
|
|
|
4
5
|
export const createProtectedRoutes = (auth: any) => {
|
|
5
6
|
const router = Router();
|
|
6
7
|
const controller = createProtectedController();
|
|
8
|
+
const requireAuth = createAuthMiddleware(auth);
|
|
7
9
|
|
|
8
|
-
router.get("/protected",
|
|
10
|
+
router.get("/protected", requireAuth, controller.protected);
|
|
9
11
|
router.get("/admin/sessions/:userId", auth.requireAdmin, controller.listSessions);
|
|
10
12
|
router.delete("/admin/sessions/:userId/:sessionId", auth.requireAdmin, controller.revokeSession);
|
|
11
13
|
router.delete("/admin/sessions/:userId", auth.requireAdmin, controller.revokeAllSessions);
|
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
import dotenv from "dotenv";
|
|
2
2
|
import { createAuthenik8 } from "authenik8-core";
|
|
3
3
|
import { createApp } from "./app";
|
|
4
|
-
import { requiredSecret } from "./utils/security";
|
|
4
|
+
import { agentIdentityConfig, authJwkConfig, requiredPort, requiredSecret } from "./utils/security";
|
|
5
5
|
|
|
6
6
|
dotenv.config();
|
|
7
7
|
|
|
8
8
|
async function start() {
|
|
9
9
|
const auth = await createAuthenik8({
|
|
10
|
-
|
|
10
|
+
jwt: authJwkConfig(),
|
|
11
11
|
refreshSecret: requiredSecret("REFRESH_SECRET"),
|
|
12
|
+
agent: agentIdentityConfig(),
|
|
12
13
|
});
|
|
13
14
|
|
|
14
15
|
const app = createApp(auth);
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
const port = requiredPort();
|
|
18
|
+
app.listen(port, () => {
|
|
19
|
+
console.log(` Server running on http://localhost:${port}`);
|
|
18
20
|
});
|
|
19
21
|
}
|
|
20
22
|
process.on("uncaughtException", (err) => {
|
|
@@ -27,12 +29,4 @@ process.on("unhandledRejection", (err) => {
|
|
|
27
29
|
process.exit(1);
|
|
28
30
|
|
|
29
31
|
});
|
|
30
|
-
setInterval(() => {
|
|
31
|
-
const used = process.memoryUsage().heapUsed / 1024 / 1024;
|
|
32
|
-
|
|
33
|
-
if (used > 300) {
|
|
34
|
-
console.error(`Memory exceeded: ${used.toFixed(2)} MB`);
|
|
35
|
-
process.exit(1);
|
|
36
|
-
}
|
|
37
|
-
}, 10000);
|
|
38
32
|
start();
|