create-authenik8-app 2.4.12 → 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 -8
- 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 +12 -5
- 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
|
@@ -1,49 +1,130 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { AgentIdentityConfig, Authenik8JwkConfig } from "authenik8-core";
|
|
3
|
+
|
|
1
4
|
const sensitiveKeys = new Set(["token", "accessToken", "refreshToken"]);
|
|
2
5
|
|
|
3
|
-
|
|
4
|
-
|
|
6
|
+
const passwordSchema = z.string()
|
|
7
|
+
.min(8, "Password must be between 8 and 1024 characters")
|
|
8
|
+
.max(1024, "Password must be between 8 and 1024 characters")
|
|
9
|
+
.refine((password) => !/[\u0000-\u001f\u007f]/.test(password), "Password contains unsupported control characters");
|
|
10
|
+
|
|
11
|
+
export const credentialsSchema = z.strictObject({
|
|
12
|
+
email: z.string().trim().toLowerCase().email("A valid email is required").max(254),
|
|
13
|
+
password: passwordSchema,
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
const secretSchema = z.string().trim().min(32);
|
|
17
|
+
const environmentValueSchema = z.string().trim().min(1);
|
|
18
|
+
const portSchema = z.coerce.number().int().min(1).max(65535);
|
|
19
|
+
const signingJwkSchema = z.object({
|
|
20
|
+
kty: z.literal("EC"),
|
|
21
|
+
crv: z.literal("P-256"),
|
|
22
|
+
x: z.string().min(1),
|
|
23
|
+
y: z.string().min(1),
|
|
24
|
+
d: z.string().min(1).optional(),
|
|
25
|
+
kid: z.string().min(1),
|
|
26
|
+
alg: z.literal("ES256").optional(),
|
|
27
|
+
use: z.literal("sig").optional(),
|
|
28
|
+
});
|
|
29
|
+
const identifierSchema = z.string()
|
|
30
|
+
.trim()
|
|
31
|
+
.min(1)
|
|
32
|
+
.max(128)
|
|
33
|
+
.regex(/^[A-Za-z0-9._:-]+$/);
|
|
34
|
+
const agentIdSchema = z.string().regex(/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/);
|
|
35
|
+
const agentScopeSchema = z.string().max(128).regex(/^[a-z][a-z0-9._/-]*(?::[a-z][a-z0-9._/-]*)+$/);
|
|
36
|
+
const agentRegistrySchema = z.record(
|
|
37
|
+
agentIdSchema,
|
|
38
|
+
z.array(agentScopeSchema).min(1).max(64),
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
export class InputValidationError extends Error {}
|
|
42
|
+
|
|
43
|
+
function validationMessage(error: z.ZodError, fallback: string): string {
|
|
44
|
+
return error.issues[0]?.message ?? fallback;
|
|
45
|
+
}
|
|
5
46
|
|
|
6
|
-
|
|
7
|
-
|
|
47
|
+
export function requiredSecret(name: string): string {
|
|
48
|
+
const result = secretSchema.safeParse(process.env[name]);
|
|
49
|
+
if (!result.success) {
|
|
50
|
+
throw new InputValidationError(`${name} must be set to at least 32 characters`);
|
|
8
51
|
}
|
|
9
|
-
|
|
10
|
-
return value;
|
|
52
|
+
return result.data;
|
|
11
53
|
}
|
|
12
54
|
|
|
13
55
|
export function requiredEnv(name: string): string {
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
56
|
+
const result = environmentValueSchema.safeParse(process.env[name]);
|
|
57
|
+
if (!result.success) throw new InputValidationError(`${name} must be set`);
|
|
58
|
+
return result.data;
|
|
59
|
+
}
|
|
19
60
|
|
|
20
|
-
|
|
61
|
+
export function requiredPort(): number {
|
|
62
|
+
const result = portSchema.safeParse(process.env.PORT ?? 3000);
|
|
63
|
+
if (!result.success) throw new InputValidationError("PORT must be between 1 and 65535");
|
|
64
|
+
return result.data;
|
|
21
65
|
}
|
|
22
66
|
|
|
23
|
-
export function
|
|
24
|
-
|
|
25
|
-
|
|
67
|
+
export function authJwkConfig(): Authenik8JwkConfig {
|
|
68
|
+
let parsed: unknown;
|
|
69
|
+
try {
|
|
70
|
+
parsed = JSON.parse(requiredEnv("AUTHENIK8_SIGNING_JWKS"));
|
|
71
|
+
} catch {
|
|
72
|
+
throw new InputValidationError("AUTHENIK8_SIGNING_JWKS must be a valid JSON array");
|
|
26
73
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
if (typeof email !== "string" || typeof password !== "string") {
|
|
31
|
-
throw new Error("Email and password are required");
|
|
74
|
+
const result = z.array(signingJwkSchema).min(1).safeParse(parsed);
|
|
75
|
+
if (!result.success) {
|
|
76
|
+
throw new InputValidationError("AUTHENIK8_SIGNING_JWKS must contain ES256 P-256 JWKs");
|
|
32
77
|
}
|
|
78
|
+
const activeKid = requiredEnv("AUTHENIK8_ACTIVE_KID");
|
|
79
|
+
const activeKey = result.data.find((key) => key.kid === activeKid);
|
|
80
|
+
if (!activeKey?.d) throw new InputValidationError("AUTHENIK8_ACTIVE_KID must select a private signing JWK");
|
|
81
|
+
return {
|
|
82
|
+
keys: result.data,
|
|
83
|
+
activeKid,
|
|
84
|
+
issuer: requiredEnv("AUTHENIK8_ISSUER"),
|
|
85
|
+
audience: requiredEnv("AUTHENIK8_AUDIENCE"),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
33
88
|
|
|
34
|
-
|
|
35
|
-
const
|
|
36
|
-
|
|
89
|
+
export function agentIdentityConfig(): AgentIdentityConfig | undefined {
|
|
90
|
+
const source = process.env.AUTHENIK8_AGENTS?.trim();
|
|
91
|
+
if (!source) return undefined;
|
|
37
92
|
|
|
38
|
-
|
|
39
|
-
|
|
93
|
+
let parsed: unknown;
|
|
94
|
+
try {
|
|
95
|
+
parsed = JSON.parse(source);
|
|
96
|
+
} catch {
|
|
97
|
+
throw new InputValidationError("AUTHENIK8_AGENTS must be a JSON object of agent scope arrays");
|
|
40
98
|
}
|
|
99
|
+
const result = agentRegistrySchema.safeParse(parsed);
|
|
100
|
+
if (!result.success) {
|
|
101
|
+
throw new InputValidationError("AUTHENIK8_AGENTS must map valid agent IDs to resource:action scopes");
|
|
102
|
+
}
|
|
103
|
+
if (!Object.keys(result.data).length) return undefined;
|
|
104
|
+
|
|
105
|
+
const registry = result.data;
|
|
106
|
+
return {
|
|
107
|
+
resolveAgent: async (agentId) => {
|
|
108
|
+
const scopes = Object.prototype.hasOwnProperty.call(registry, agentId)
|
|
109
|
+
? registry[agentId]
|
|
110
|
+
: undefined;
|
|
111
|
+
return scopes ? { agentId, scopes, active: true } : null;
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
}
|
|
41
115
|
|
|
42
|
-
|
|
43
|
-
|
|
116
|
+
export function parseCredentials(body: unknown): z.infer<typeof credentialsSchema> {
|
|
117
|
+
const result = credentialsSchema.safeParse(body);
|
|
118
|
+
if (!result.success) {
|
|
119
|
+
throw new InputValidationError(validationMessage(result.error, "Email and password are required"));
|
|
44
120
|
}
|
|
121
|
+
return result.data;
|
|
122
|
+
}
|
|
45
123
|
|
|
46
|
-
|
|
124
|
+
export function parseIdentifier(value: unknown, label: string): string {
|
|
125
|
+
const result = identifierSchema.safeParse(value);
|
|
126
|
+
if (!result.success) throw new InputValidationError(`${label} is invalid`);
|
|
127
|
+
return result.data;
|
|
47
128
|
}
|
|
48
129
|
|
|
49
130
|
export function sanitizeSessionResponse(value: unknown): unknown {
|
|
@@ -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
|
```
|
|
@@ -69,7 +73,10 @@ Expected result: the route should reject the request because no access token was
|
|
|
69
73
|
## Environment Variables
|
|
70
74
|
|
|
71
75
|
- `DATABASE_URL`: Prisma database connection. SQLite uses `file:./dev.db`; Postgres uses a `postgresql://...` URL.
|
|
72
|
-
- `
|
|
76
|
+
- `AUTHENIK8_SIGNING_JWKS`: JSON array containing the active private ES256 key and any previous public verification keys.
|
|
77
|
+
- `AUTHENIK8_ACTIVE_KID`: selects the private key used to sign new access tokens.
|
|
78
|
+
- `AUTHENIK8_ISSUER` / `AUTHENIK8_AUDIENCE`: required claims checked during verification.
|
|
79
|
+
- `AUTHENIK8_AGENTS`: optional JSON map of agent IDs to exact `resource:action` scopes; keep `{}` to disable agent identity. See `AGENT_IDENTITY.md`.
|
|
73
80
|
- `REFRESH_SECRET`: signs refresh tokens. Use a different long random value in production.
|
|
74
81
|
- `REDIS_HOST`: Redis host for refresh-token/session security.
|
|
75
82
|
- `REDIS_PORT`: Redis port, usually `6379` locally.
|
|
@@ -98,9 +105,9 @@ export async function getProtected(accessToken: string) {
|
|
|
98
105
|
|
|
99
106
|
`Prisma Client did not initialize`: run `npm run prisma:migrate`, then restart `npm run dev`.
|
|
100
107
|
|
|
101
|
-
`
|
|
108
|
+
`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`.
|
|
102
109
|
|
|
103
|
-
`Port 3000 already in use`: stop the other process or
|
|
110
|
+
`Port 3000 already in use`: stop the other process or set `PORT` in `.env`.
|
|
104
111
|
|
|
105
112
|
`DATABASE_URL is wrong`: for SQLite use `file:./dev.db`; for local Docker Postgres use `postgresql://postgres:postgres@localhost:5432/authenik8?schema=public`.
|
|
106
113
|
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { Request, Response } from "express";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
InputValidationError,
|
|
4
|
+
parseIdentifier,
|
|
5
|
+
parseRefreshToken,
|
|
6
|
+
sanitizeSessionResponse,
|
|
7
|
+
} from "../utils/security";
|
|
3
8
|
|
|
4
9
|
export const createBaseController = (auth: any) => ({
|
|
5
10
|
publicRoute(req: Request, res: Response) {
|
|
@@ -11,15 +16,8 @@ export const createBaseController = (auth: any) => ({
|
|
|
11
16
|
res.json({ token });
|
|
12
17
|
},
|
|
13
18
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
try {
|
|
18
|
-
const decoded = await auth.verifyToken(token);
|
|
19
|
-
res.json({ message: "Protected data", user: decoded });
|
|
20
|
-
} catch {
|
|
21
|
-
res.status(401).json({ error: "Unauthorized" });
|
|
22
|
-
}
|
|
19
|
+
protected(req: Request, res: Response) {
|
|
20
|
+
res.json({ message: "Protected data", user: (req as any).user });
|
|
23
21
|
},
|
|
24
22
|
|
|
25
23
|
async refresh(req: Request, res: Response) {
|
|
@@ -43,10 +41,15 @@ export const createBaseController = (auth: any) => ({
|
|
|
43
41
|
return res.status(503).json({ success: false, message: "Session management unavailable" });
|
|
44
42
|
}
|
|
45
43
|
|
|
46
|
-
const
|
|
44
|
+
const userId = parseIdentifier(req.params.userId, "User ID");
|
|
45
|
+
const sessions = await actions.listSessions(userId);
|
|
47
46
|
res.json({ sessions: sanitizeSessionResponse(sessions) });
|
|
48
|
-
} catch {
|
|
49
|
-
|
|
47
|
+
} catch (error) {
|
|
48
|
+
const validationError = error instanceof InputValidationError;
|
|
49
|
+
res.status(validationError ? 400 : 500).json({
|
|
50
|
+
success: false,
|
|
51
|
+
message: validationError ? error.message : "Failed to retrieve sessions",
|
|
52
|
+
});
|
|
50
53
|
}
|
|
51
54
|
},
|
|
52
55
|
|
|
@@ -57,10 +60,16 @@ export const createBaseController = (auth: any) => ({
|
|
|
57
60
|
return res.status(503).json({ success: false, message: "Session management unavailable" });
|
|
58
61
|
}
|
|
59
62
|
|
|
60
|
-
|
|
63
|
+
const userId = parseIdentifier(req.params.userId, "User ID");
|
|
64
|
+
const sessionId = parseIdentifier(req.params.sessionId, "Session ID");
|
|
65
|
+
await actions.revokeSession(userId, sessionId);
|
|
61
66
|
res.json({ success: true, message: "Session revoked" });
|
|
62
|
-
} catch {
|
|
63
|
-
|
|
67
|
+
} catch (error) {
|
|
68
|
+
const validationError = error instanceof InputValidationError;
|
|
69
|
+
res.status(validationError ? 400 : 500).json({
|
|
70
|
+
success: false,
|
|
71
|
+
message: validationError ? error.message : "Failed to revoke session",
|
|
72
|
+
});
|
|
64
73
|
}
|
|
65
74
|
},
|
|
66
75
|
|
|
@@ -71,10 +80,15 @@ export const createBaseController = (auth: any) => ({
|
|
|
71
80
|
return res.status(503).json({ success: false, message: "Session management unavailable" });
|
|
72
81
|
}
|
|
73
82
|
|
|
74
|
-
|
|
83
|
+
const userId = parseIdentifier(req.params.userId, "User ID");
|
|
84
|
+
await actions.revokeAllSessions(userId);
|
|
75
85
|
res.json({ success: true, message: "All sessions revoked" });
|
|
76
|
-
} catch {
|
|
77
|
-
|
|
86
|
+
} catch (error) {
|
|
87
|
+
const validationError = error instanceof InputValidationError;
|
|
88
|
+
res.status(validationError ? 400 : 500).json({
|
|
89
|
+
success: false,
|
|
90
|
+
message: validationError ? error.message : "Failed to revoke sessions",
|
|
91
|
+
});
|
|
78
92
|
}
|
|
79
93
|
},
|
|
80
94
|
});
|
|
@@ -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,22 +3,28 @@
|
|
|
3
3
|
"scripts": {
|
|
4
4
|
"dev": "ts-node-dev --respawn --transpile-only ./src/server.ts",
|
|
5
5
|
"build": "tsc",
|
|
6
|
-
"start": "node dist/server.js",
|
|
6
|
+
"start": "node dist/src/server.js",
|
|
7
7
|
"prisma:migrate": "prisma migrate",
|
|
8
|
-
"docker:up": "docker compose up -d",
|
|
8
|
+
"docker:up": "docker compose up -d --wait",
|
|
9
9
|
"docker:down": "docker compose down"
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"authenik8-core": "
|
|
12
|
+
"authenik8-core": "2.0.3",
|
|
13
13
|
"dotenv": "^16.0.0",
|
|
14
14
|
"express": "^4.18.2",
|
|
15
|
-
"@prisma/client": "
|
|
15
|
+
"@prisma/client": "7.8.0",
|
|
16
|
+
"zod": "^4.4.3"
|
|
16
17
|
},
|
|
17
18
|
"devDependencies": {
|
|
19
|
+
"@types/express": "^4.17.21",
|
|
20
|
+
"@types/node": "^22.10.2",
|
|
18
21
|
"ts-node-dev": "^2.0.0",
|
|
19
22
|
"typescript": "^5.0.0",
|
|
20
|
-
"prisma": "
|
|
23
|
+
"prisma": "7.8.0"
|
|
21
24
|
|
|
22
25
|
},
|
|
26
|
+
"overrides": {
|
|
27
|
+
"@hono/node-server": "1.19.13"
|
|
28
|
+
},
|
|
23
29
|
"type": "commonjs"
|
|
24
30
|
}
|
|
@@ -7,7 +7,7 @@ export const createBaseRoutes = (auth: any) => {
|
|
|
7
7
|
|
|
8
8
|
router.get("/public", controller.publicRoute);
|
|
9
9
|
router.get("/guest", controller.guest);
|
|
10
|
-
router.get("/protected", controller.protected);
|
|
10
|
+
router.get("/protected", auth.requireAuth, controller.protected);
|
|
11
11
|
router.post("/refresh", controller.refresh);
|
|
12
12
|
|
|
13
13
|
router.get("/admin", auth.requireAdmin, controller.admin);
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
AUTHENIK8_SIGNING_JWKS=
|
|
2
|
+
AUTHENIK8_ACTIVE_KID=
|
|
3
|
+
AUTHENIK8_ISSUER=http://localhost:3000
|
|
4
|
+
AUTHENIK8_AUDIENCE=authenik8-app-api
|
|
5
|
+
AUTHENIK8_AGENTS={}
|
|
6
|
+
REFRESH_SECRET=replace-with-at-least-32-random-characters
|
|
7
|
+
PORT=3000
|
|
3
8
|
REDIS_URL=redis://localhost:6379
|
|
@@ -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) => {
|
|
@@ -26,12 +28,4 @@ process.on("unhandledRejection", (err) => {
|
|
|
26
28
|
console.error(" Unhandled Rejection:", err);
|
|
27
29
|
process.exit(1);
|
|
28
30
|
});
|
|
29
|
-
setInterval(() => {
|
|
30
|
-
const used = process.memoryUsage().heapUsed / 1024 / 1024;
|
|
31
|
-
|
|
32
|
-
if (used > 300) {
|
|
33
|
-
console.error(`Memory exceeded: ${used.toFixed(2)} MB`);
|
|
34
|
-
process.exit(1);
|
|
35
|
-
}
|
|
36
|
-
}, 10000);
|
|
37
31
|
start();
|
|
@@ -1,47 +1,14 @@
|
|
|
1
1
|
{
|
|
2
|
-
// Visit https://aka.ms/tsconfig to read more about this file
|
|
3
2
|
"compilerOptions": {
|
|
4
|
-
// File Layout
|
|
5
|
-
"rootDir": "./src",
|
|
6
|
-
"outDir": "./dist",
|
|
7
|
-
|
|
8
|
-
// Environment Settings
|
|
9
|
-
// See also https://aka.ms/tsconfig/module
|
|
10
|
-
"module": "nodenext",
|
|
11
3
|
"target": "ES2020",
|
|
12
|
-
"
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
// "types": ["node"],
|
|
16
|
-
// and npm install -D @types/node
|
|
17
|
-
|
|
18
|
-
// Other Outputs
|
|
19
|
-
"sourceMap": true,
|
|
20
|
-
"declaration": true,
|
|
21
|
-
"declarationMap": true,
|
|
22
|
-
|
|
23
|
-
// Stricter Typechecking Options
|
|
24
|
-
"noUncheckedIndexedAccess": true,
|
|
25
|
-
"exactOptionalPropertyTypes": true,
|
|
26
|
-
|
|
27
|
-
// Style Options
|
|
28
|
-
// "noImplicitReturns": true,
|
|
29
|
-
// "noImplicitOverride": true,
|
|
30
|
-
// "noUnusedLocals": true,
|
|
31
|
-
// "noUnusedParameters": true,
|
|
32
|
-
// "noFallthroughCasesInSwitch": true,
|
|
33
|
-
// "noPropertyAccessFromIndexSignature": true,
|
|
34
|
-
|
|
35
|
-
// Recommended Options
|
|
4
|
+
"module": "commonjs",
|
|
5
|
+
"rootDir": ".",
|
|
6
|
+
"outDir": "./dist",
|
|
36
7
|
"strict": true,
|
|
37
|
-
"
|
|
38
|
-
"verbatimModuleSyntax": true,
|
|
39
|
-
"isolatedModules": true,
|
|
40
|
-
"noUncheckedSideEffectImports": true,
|
|
41
|
-
"moduleDetection": "force",
|
|
8
|
+
"esModuleInterop": true,
|
|
42
9
|
"skipLibCheck": true,
|
|
43
|
-
"
|
|
44
|
-
//"moduleResolution":Nodenext,
|
|
10
|
+
"forceConsistentCasingInFileNames": true
|
|
45
11
|
},
|
|
46
|
-
"
|
|
12
|
+
"include": ["src", "app.ts", "controllers", "routes", "utils"],
|
|
13
|
+
"exclude": ["node_modules", "dist"]
|
|
47
14
|
}
|
|
@@ -1,32 +1,128 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { AgentIdentityConfig, Authenik8JwkConfig } from "authenik8-core";
|
|
3
|
+
|
|
1
4
|
const sensitiveKeys = new Set(["token", "accessToken", "refreshToken"]);
|
|
2
5
|
|
|
3
|
-
|
|
4
|
-
|
|
6
|
+
const secretSchema = z.string().trim().min(32);
|
|
7
|
+
const environmentValueSchema = z.string().trim().min(1);
|
|
8
|
+
const portSchema = z.coerce.number().int().min(1).max(65535);
|
|
9
|
+
const signingJwkSchema = z.object({
|
|
10
|
+
kty: z.literal("EC"),
|
|
11
|
+
crv: z.literal("P-256"),
|
|
12
|
+
x: z.string().min(1),
|
|
13
|
+
y: z.string().min(1),
|
|
14
|
+
d: z.string().min(1).optional(),
|
|
15
|
+
kid: z.string().min(1),
|
|
16
|
+
alg: z.literal("ES256").optional(),
|
|
17
|
+
use: z.literal("sig").optional(),
|
|
18
|
+
});
|
|
19
|
+
export const refreshTokenBodySchema = z.strictObject({
|
|
20
|
+
refreshToken: z.string().trim().min(16, "Refresh token is required").max(4096),
|
|
21
|
+
});
|
|
22
|
+
const identifierSchema = z.string()
|
|
23
|
+
.trim()
|
|
24
|
+
.min(1)
|
|
25
|
+
.max(128)
|
|
26
|
+
.regex(/^[A-Za-z0-9._:-]+$/);
|
|
27
|
+
const agentIdSchema = z.string().regex(/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/);
|
|
28
|
+
const agentScopeSchema = z.string().max(128).regex(/^[a-z][a-z0-9._/-]*(?::[a-z][a-z0-9._/-]*)+$/);
|
|
29
|
+
const agentRegistrySchema = z.record(
|
|
30
|
+
agentIdSchema,
|
|
31
|
+
z.array(agentScopeSchema).min(1).max(64),
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
export class InputValidationError extends Error {}
|
|
35
|
+
|
|
36
|
+
function validationMessage(error: z.ZodError, fallback: string): string {
|
|
37
|
+
return error.issues[0]?.message ?? fallback;
|
|
38
|
+
}
|
|
5
39
|
|
|
6
|
-
|
|
7
|
-
|
|
40
|
+
export function requiredSecret(name: string): string {
|
|
41
|
+
const result = secretSchema.safeParse(process.env[name]);
|
|
42
|
+
if (!result.success) {
|
|
43
|
+
throw new InputValidationError(`${name} must be set to at least 32 characters`);
|
|
8
44
|
}
|
|
45
|
+
return result.data;
|
|
46
|
+
}
|
|
9
47
|
|
|
10
|
-
|
|
48
|
+
export function requiredEnv(name: string): string {
|
|
49
|
+
const result = environmentValueSchema.safeParse(process.env[name]);
|
|
50
|
+
if (!result.success) throw new InputValidationError(`${name} must be set`);
|
|
51
|
+
return result.data;
|
|
11
52
|
}
|
|
12
53
|
|
|
13
|
-
export function
|
|
14
|
-
const
|
|
15
|
-
|
|
54
|
+
export function requiredPort(): number {
|
|
55
|
+
const result = portSchema.safeParse(process.env.PORT ?? 3000);
|
|
56
|
+
if (!result.success) throw new InputValidationError("PORT must be between 1 and 65535");
|
|
57
|
+
return result.data;
|
|
16
58
|
}
|
|
17
59
|
|
|
18
|
-
export function
|
|
19
|
-
|
|
20
|
-
|
|
60
|
+
export function authJwkConfig(): Authenik8JwkConfig {
|
|
61
|
+
let parsed: unknown;
|
|
62
|
+
try {
|
|
63
|
+
parsed = JSON.parse(requiredEnv("AUTHENIK8_SIGNING_JWKS"));
|
|
64
|
+
} catch {
|
|
65
|
+
throw new InputValidationError("AUTHENIK8_SIGNING_JWKS must be a valid JSON array");
|
|
21
66
|
}
|
|
67
|
+
const result = z.array(signingJwkSchema).min(1).safeParse(parsed);
|
|
68
|
+
if (!result.success) {
|
|
69
|
+
throw new InputValidationError("AUTHENIK8_SIGNING_JWKS must contain ES256 P-256 JWKs");
|
|
70
|
+
}
|
|
71
|
+
const activeKid = requiredEnv("AUTHENIK8_ACTIVE_KID");
|
|
72
|
+
const activeKey = result.data.find((key) => key.kid === activeKid);
|
|
73
|
+
if (!activeKey?.d) throw new InputValidationError("AUTHENIK8_ACTIVE_KID must select a private signing JWK");
|
|
74
|
+
return {
|
|
75
|
+
keys: result.data,
|
|
76
|
+
activeKid,
|
|
77
|
+
issuer: requiredEnv("AUTHENIK8_ISSUER"),
|
|
78
|
+
audience: requiredEnv("AUTHENIK8_AUDIENCE"),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
22
81
|
|
|
23
|
-
|
|
82
|
+
export function agentIdentityConfig(): AgentIdentityConfig | undefined {
|
|
83
|
+
const source = process.env.AUTHENIK8_AGENTS?.trim();
|
|
84
|
+
if (!source) return undefined;
|
|
24
85
|
|
|
25
|
-
|
|
26
|
-
|
|
86
|
+
let parsed: unknown;
|
|
87
|
+
try {
|
|
88
|
+
parsed = JSON.parse(source);
|
|
89
|
+
} catch {
|
|
90
|
+
throw new InputValidationError("AUTHENIK8_AGENTS must be a JSON object of agent scope arrays");
|
|
27
91
|
}
|
|
92
|
+
const result = agentRegistrySchema.safeParse(parsed);
|
|
93
|
+
if (!result.success) {
|
|
94
|
+
throw new InputValidationError("AUTHENIK8_AGENTS must map valid agent IDs to resource:action scopes");
|
|
95
|
+
}
|
|
96
|
+
if (!Object.keys(result.data).length) return undefined;
|
|
97
|
+
|
|
98
|
+
const registry = result.data;
|
|
99
|
+
return {
|
|
100
|
+
resolveAgent: async (agentId) => {
|
|
101
|
+
const scopes = Object.prototype.hasOwnProperty.call(registry, agentId)
|
|
102
|
+
? registry[agentId]
|
|
103
|
+
: undefined;
|
|
104
|
+
return scopes ? { agentId, scopes, active: true } : null;
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function getBearerToken(authorizationHeader: string | undefined): string | undefined {
|
|
110
|
+
const result = z.string().regex(/^Bearer\s+\S+$/).safeParse(authorizationHeader);
|
|
111
|
+
return result.success ? result.data.replace(/^Bearer\s+/, "") : undefined;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function parseRefreshToken(body: unknown): string {
|
|
115
|
+
const result = refreshTokenBodySchema.safeParse(body);
|
|
116
|
+
if (!result.success) {
|
|
117
|
+
throw new InputValidationError(validationMessage(result.error, "Refresh token is required"));
|
|
118
|
+
}
|
|
119
|
+
return result.data.refreshToken;
|
|
120
|
+
}
|
|
28
121
|
|
|
29
|
-
|
|
122
|
+
export function parseIdentifier(value: unknown, label: string): string {
|
|
123
|
+
const result = identifierSchema.safeParse(value);
|
|
124
|
+
if (!result.success) throw new InputValidationError(`${label} is invalid`);
|
|
125
|
+
return result.data;
|
|
30
126
|
}
|
|
31
127
|
|
|
32
128
|
export function sanitizeSessionResponse(value: unknown): unknown {
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
NODE_ENV=development
|
|
2
|
+
PORT=3000
|
|
3
|
+
WEB_ORIGIN=http://localhost:5173
|
|
4
|
+
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/authenik8?schema=public
|
|
5
|
+
REDIS_URL=redis://localhost:6379
|
|
6
|
+
AUTHENIK8_SIGNING_JWKS=
|
|
7
|
+
AUTHENIK8_ACTIVE_KID=
|
|
8
|
+
AUTHENIK8_ISSUER=http://localhost:3000
|
|
9
|
+
AUTHENIK8_AUDIENCE=authenik8-fullstack-api
|
|
10
|
+
AUTHENIK8_AGENTS={}
|
|
11
|
+
REFRESH_SECRET=replace-with-another-32-random-characters
|
|
12
|
+
COOKIE_SECURE=false
|
|
13
|
+
TRUST_PROXY=false
|
|
14
|
+
LOG_LEVEL=info
|
|
15
|
+
EMAIL_FROM=Authenik8 <auth@example.com>
|
|
16
|
+
RESEND_API_KEY=
|
|
17
|
+
GOOGLE_CLIENT_ID=
|
|
18
|
+
GOOGLE_CLIENT_SECRET=
|
|
19
|
+
GOOGLE_REDIRECT_URI=http://localhost:3000/api/auth/oauth/google/callback
|
|
20
|
+
GITHUB_CLIENT_ID=
|
|
21
|
+
GITHUB_CLIENT_SECRET=
|
|
22
|
+
GITHUB_REDIRECT_URI=http://localhost:3000/api/auth/oauth/github/callback
|
|
23
|
+
SEED_ADMIN_EMAIL=admin@example.com
|
|
24
|
+
SEED_ADMIN_PASSWORD=ChangeMe123!
|