create-authenik8-app 2.4.12 → 2.4.14
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 +148 -27
- 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 +12 -5
- 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,54 +1,141 @@
|
|
|
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
|
+
export const refreshTokenBodySchema = z.strictObject({
|
|
17
|
+
refreshToken: z.string().trim().min(16, "Refresh token is required").max(4096),
|
|
18
|
+
});
|
|
19
|
+
const secretSchema = z.string().trim().min(32);
|
|
20
|
+
const environmentValueSchema = z.string().trim().min(1);
|
|
21
|
+
const portSchema = z.coerce.number().int().min(1).max(65535);
|
|
22
|
+
const signingJwkSchema = z.object({
|
|
23
|
+
kty: z.literal("EC"),
|
|
24
|
+
crv: z.literal("P-256"),
|
|
25
|
+
x: z.string().min(1),
|
|
26
|
+
y: z.string().min(1),
|
|
27
|
+
d: z.string().min(1).optional(),
|
|
28
|
+
kid: z.string().min(1),
|
|
29
|
+
alg: z.literal("ES256").optional(),
|
|
30
|
+
use: z.literal("sig").optional(),
|
|
31
|
+
});
|
|
32
|
+
const identifierSchema = z.string()
|
|
33
|
+
.trim()
|
|
34
|
+
.min(1)
|
|
35
|
+
.max(128)
|
|
36
|
+
.regex(/^[A-Za-z0-9._:-]+$/);
|
|
37
|
+
const agentIdSchema = z.string().regex(/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/);
|
|
38
|
+
const agentScopeSchema = z.string().max(128).regex(/^[a-z][a-z0-9._/-]*(?::[a-z][a-z0-9._/-]*)+$/);
|
|
39
|
+
const agentRegistrySchema = z.record(
|
|
40
|
+
agentIdSchema,
|
|
41
|
+
z.array(agentScopeSchema).min(1).max(64),
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
export class InputValidationError extends Error {}
|
|
45
|
+
|
|
46
|
+
function validationMessage(error: z.ZodError, fallback: string): string {
|
|
47
|
+
return error.issues[0]?.message ?? fallback;
|
|
48
|
+
}
|
|
5
49
|
|
|
6
|
-
|
|
7
|
-
|
|
50
|
+
export function requiredSecret(name: string): string {
|
|
51
|
+
const result = secretSchema.safeParse(process.env[name]);
|
|
52
|
+
if (!result.success) {
|
|
53
|
+
throw new InputValidationError(`${name} must be set to at least 32 characters`);
|
|
8
54
|
}
|
|
9
|
-
|
|
10
|
-
return value;
|
|
55
|
+
return result.data;
|
|
11
56
|
}
|
|
12
57
|
|
|
13
|
-
export function
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
58
|
+
export function requiredEnv(name: string): string {
|
|
59
|
+
const result = environmentValueSchema.safeParse(process.env[name]);
|
|
60
|
+
if (!result.success) throw new InputValidationError(`${name} must be set`);
|
|
61
|
+
return result.data;
|
|
62
|
+
}
|
|
17
63
|
|
|
18
|
-
|
|
64
|
+
export function requiredPort(): number {
|
|
65
|
+
const result = portSchema.safeParse(process.env.PORT ?? 3000);
|
|
66
|
+
if (!result.success) throw new InputValidationError("PORT must be between 1 and 65535");
|
|
67
|
+
return result.data;
|
|
68
|
+
}
|
|
19
69
|
|
|
20
|
-
|
|
21
|
-
|
|
70
|
+
export function authJwkConfig(): Authenik8JwkConfig {
|
|
71
|
+
let parsed: unknown;
|
|
72
|
+
try {
|
|
73
|
+
parsed = JSON.parse(requiredEnv("AUTHENIK8_SIGNING_JWKS"));
|
|
74
|
+
} catch {
|
|
75
|
+
throw new InputValidationError("AUTHENIK8_SIGNING_JWKS must be a valid JSON array");
|
|
22
76
|
}
|
|
77
|
+
const result = z.array(signingJwkSchema).min(1).safeParse(parsed);
|
|
78
|
+
if (!result.success) {
|
|
79
|
+
throw new InputValidationError("AUTHENIK8_SIGNING_JWKS must contain ES256 P-256 JWKs");
|
|
80
|
+
}
|
|
81
|
+
const activeKid = requiredEnv("AUTHENIK8_ACTIVE_KID");
|
|
82
|
+
const activeKey = result.data.find((key) => key.kid === activeKid);
|
|
83
|
+
if (!activeKey?.d) throw new InputValidationError("AUTHENIK8_ACTIVE_KID must select a private signing JWK");
|
|
84
|
+
return {
|
|
85
|
+
keys: result.data,
|
|
86
|
+
activeKid,
|
|
87
|
+
issuer: requiredEnv("AUTHENIK8_ISSUER"),
|
|
88
|
+
audience: requiredEnv("AUTHENIK8_AUDIENCE"),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
23
91
|
|
|
24
|
-
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
92
|
+
export function agentIdentityConfig(): AgentIdentityConfig | undefined {
|
|
93
|
+
const source = process.env.AUTHENIK8_AGENTS?.trim();
|
|
94
|
+
if (!source) return undefined;
|
|
28
95
|
|
|
29
|
-
|
|
30
|
-
|
|
96
|
+
let parsed: unknown;
|
|
97
|
+
try {
|
|
98
|
+
parsed = JSON.parse(source);
|
|
99
|
+
} catch {
|
|
100
|
+
throw new InputValidationError("AUTHENIK8_AGENTS must be a JSON object of agent scope arrays");
|
|
31
101
|
}
|
|
32
|
-
|
|
33
|
-
if (
|
|
34
|
-
throw new
|
|
102
|
+
const result = agentRegistrySchema.safeParse(parsed);
|
|
103
|
+
if (!result.success) {
|
|
104
|
+
throw new InputValidationError("AUTHENIK8_AGENTS must map valid agent IDs to resource:action scopes");
|
|
35
105
|
}
|
|
36
|
-
|
|
37
|
-
|
|
106
|
+
if (!Object.keys(result.data).length) return undefined;
|
|
107
|
+
|
|
108
|
+
const registry = result.data;
|
|
109
|
+
return {
|
|
110
|
+
resolveAgent: async (agentId) => {
|
|
111
|
+
const scopes = Object.prototype.hasOwnProperty.call(registry, agentId)
|
|
112
|
+
? registry[agentId]
|
|
113
|
+
: undefined;
|
|
114
|
+
return scopes ? { agentId, scopes, active: true } : null;
|
|
115
|
+
},
|
|
116
|
+
};
|
|
38
117
|
}
|
|
39
118
|
|
|
40
|
-
export function
|
|
41
|
-
|
|
42
|
-
|
|
119
|
+
export function parseCredentials(body: unknown): z.infer<typeof credentialsSchema> {
|
|
120
|
+
const result = credentialsSchema.safeParse(body);
|
|
121
|
+
if (!result.success) {
|
|
122
|
+
throw new InputValidationError(validationMessage(result.error, "Email and password are required"));
|
|
43
123
|
}
|
|
124
|
+
return result.data;
|
|
125
|
+
}
|
|
44
126
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
if (
|
|
48
|
-
throw new
|
|
127
|
+
export function parseRefreshToken(body: unknown): string {
|
|
128
|
+
const result = refreshTokenBodySchema.safeParse(body);
|
|
129
|
+
if (!result.success) {
|
|
130
|
+
throw new InputValidationError(validationMessage(result.error, "Refresh token is required"));
|
|
49
131
|
}
|
|
132
|
+
return result.data.refreshToken;
|
|
133
|
+
}
|
|
50
134
|
|
|
51
|
-
|
|
135
|
+
export function parseIdentifier(value: unknown, label: string): string {
|
|
136
|
+
const result = identifierSchema.safeParse(value);
|
|
137
|
+
if (!result.success) throw new InputValidationError(`${label} is invalid`);
|
|
138
|
+
return result.data;
|
|
52
139
|
}
|
|
53
140
|
|
|
54
141
|
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
|
AUTHENIK8_OAUTH_PROVIDERS=google,github
|
|
@@ -124,10 +128,6 @@ Expected shape:
|
|
|
124
128
|
|
|
125
129
|
```json
|
|
126
130
|
{
|
|
127
|
-
"user": {
|
|
128
|
-
"id": "user-id",
|
|
129
|
-
"email": "dev@example.com"
|
|
130
|
-
},
|
|
131
131
|
"accessToken": "access-token",
|
|
132
132
|
"refreshToken": "refresh-token"
|
|
133
133
|
}
|
|
@@ -152,7 +152,10 @@ If a provider is not listed in `AUTHENIK8_OAUTH_PROVIDERS`, that provider route
|
|
|
152
152
|
## Environment Variables
|
|
153
153
|
|
|
154
154
|
- `DATABASE_URL`: Prisma database connection. SQLite uses `file:./dev.db`; Postgres uses a `postgresql://...` URL.
|
|
155
|
-
- `
|
|
155
|
+
- `AUTHENIK8_SIGNING_JWKS`: JSON array containing the active private ES256 key and any previous public verification keys.
|
|
156
|
+
- `AUTHENIK8_ACTIVE_KID`: selects the private key used to sign new access tokens.
|
|
157
|
+
- `AUTHENIK8_ISSUER` / `AUTHENIK8_AUDIENCE`: required claims checked during verification.
|
|
158
|
+
- `AUTHENIK8_AGENTS`: optional JSON map of agent IDs to exact `resource:action` scopes; keep `{}` to disable agent identity. See `AGENT_IDENTITY.md`.
|
|
156
159
|
- `REFRESH_SECRET`: signs refresh tokens. Use a different long random value in production.
|
|
157
160
|
- `REDIS_HOST`: Redis host for refresh-token/session security.
|
|
158
161
|
- `REDIS_PORT`: Redis port, usually `6379` locally.
|
|
@@ -224,7 +227,7 @@ The callback URL in `.env` must match the provider dashboard exactly, including
|
|
|
224
227
|
|
|
225
228
|
`Prisma Client did not initialize`: run `npm run prisma:migrate`, then restart `npm run dev`.
|
|
226
229
|
|
|
227
|
-
`
|
|
230
|
+
`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`.
|
|
228
231
|
|
|
229
232
|
`Cannot POST /auth/login`: confirm the server is running and you generated the password or OAuth auth template.
|
|
230
233
|
|
|
@@ -234,7 +237,7 @@ The callback URL in `.env` must match the provider dashboard exactly, including
|
|
|
234
237
|
|
|
235
238
|
`google OAuth is not configured`: add `google` to `AUTHENIK8_OAUTH_PROVIDERS` and set the Google env vars, or use only the enabled provider route.
|
|
236
239
|
|
|
237
|
-
`Port 3000 already in use`: stop the other process or
|
|
240
|
+
`Port 3000 already in use`: stop the other process or set `PORT` in `.env`.
|
|
238
241
|
|
|
239
242
|
`DATABASE_URL is wrong`: for SQLite use `file:./dev.db`; for local Docker Postgres use `postgresql://postgres:postgres@localhost:5432/authenik8?schema=public`.
|
|
240
243
|
|
|
@@ -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
28
|
"typescript": "^5.0.0",
|
|
25
|
-
"prisma": "
|
|
29
|
+
"prisma": "7.8.0"
|
|
30
|
+
},
|
|
31
|
+
"overrides": {
|
|
32
|
+
"@hono/node-server": "1.19.13"
|
|
26
33
|
}
|
|
27
34
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createAuthenik8 } from "authenik8-core";
|
|
2
2
|
import dotenv from "dotenv";
|
|
3
|
-
import { requiredSecret } from "../utils/security";
|
|
3
|
+
import { agentIdentityConfig, authJwkConfig, requiredSecret } from "../utils/security";
|
|
4
4
|
|
|
5
5
|
dotenv.config();
|
|
6
6
|
|
|
@@ -12,8 +12,9 @@ function oauthConfig() {
|
|
|
12
12
|
|
|
13
13
|
export async function initAuth() {
|
|
14
14
|
authInstance= await createAuthenik8({
|
|
15
|
-
|
|
15
|
+
jwt: authJwkConfig(),
|
|
16
16
|
refreshSecret: requiredSecret("REFRESH_SECRET"),
|
|
17
|
+
agent: agentIdentityConfig(),
|
|
17
18
|
oauth: oauthConfig(),
|
|
18
19
|
});
|
|
19
20
|
|
|
@@ -32,19 +32,15 @@ export const passwordController = {
|
|
|
32
32
|
const { email, password } = parseCredentials(req.body);
|
|
33
33
|
const user = await prisma.user.findUnique({ where: { email } });
|
|
34
34
|
|
|
35
|
-
if (!user || !(await comparePassword(password, user.password))) {
|
|
35
|
+
if (!user?.password || !(await comparePassword(password, user.password))) {
|
|
36
36
|
return res.status(401).json({ error: "Invalid credentials" });
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
const auth = getAuth();
|
|
40
|
-
const accessToken = auth.
|
|
41
|
-
userId: user.id,
|
|
42
|
-
email: user.email,
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
const refreshToken = await auth.generateRefreshToken({
|
|
40
|
+
const { accessToken, refreshToken } = await auth.issueTokens({
|
|
46
41
|
userId: user.id,
|
|
47
42
|
email: user.email,
|
|
43
|
+
role: String(user.role).toLowerCase(),
|
|
48
44
|
});
|
|
49
45
|
|
|
50
46
|
res.json({ accessToken, refreshToken });
|
|
@@ -1,8 +1,19 @@
|
|
|
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 protectedController = {
|
|
5
|
-
protected( res: Response) {
|
|
16
|
+
protected(_req: Request, res: Response) {
|
|
6
17
|
res.json({ message: "Protected route" });
|
|
7
18
|
},
|
|
8
19
|
|
|
@@ -13,10 +24,11 @@ export const protectedController = {
|
|
|
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 protectedController = {
|
|
|
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 protectedController = {
|
|
|
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,90 @@
|
|
|
1
|
+
import { prisma } from "../prisma/client";
|
|
2
|
+
|
|
3
|
+
type ProviderIdentity = {
|
|
4
|
+
provider: string;
|
|
5
|
+
providerId: string;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
type IdentityUser = {
|
|
9
|
+
id: string;
|
|
10
|
+
email: string;
|
|
11
|
+
role?: string;
|
|
12
|
+
providers: ProviderIdentity[];
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const db = prisma as any;
|
|
16
|
+
|
|
17
|
+
function toIdentityUser(user: any): IdentityUser | null {
|
|
18
|
+
if (!user) return null;
|
|
19
|
+
|
|
20
|
+
return {
|
|
21
|
+
id: user.id,
|
|
22
|
+
email: user.email,
|
|
23
|
+
role: typeof user.role === "string" ? user.role.toLowerCase() : undefined,
|
|
24
|
+
providers: (user.identityProviders ?? []).map((identity: ProviderIdentity) => ({
|
|
25
|
+
provider: identity.provider,
|
|
26
|
+
providerId: identity.providerId,
|
|
27
|
+
})),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const includeProviders = { identityProviders: true };
|
|
32
|
+
|
|
33
|
+
export const identityAdapter = {
|
|
34
|
+
async findUserByEmail(email: string) {
|
|
35
|
+
const user = await db.user.findUnique({
|
|
36
|
+
where: { email: email.trim().toLowerCase() },
|
|
37
|
+
include: includeProviders,
|
|
38
|
+
});
|
|
39
|
+
return toIdentityUser(user);
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
async findUserByProvider(provider: string, providerId: string) {
|
|
43
|
+
const identity = await db.identityProvider.findUnique({
|
|
44
|
+
where: { provider_providerId: { provider, providerId } },
|
|
45
|
+
include: { user: { include: includeProviders } },
|
|
46
|
+
});
|
|
47
|
+
return toIdentityUser(identity?.user);
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
async createUser(data: { email: string; provider: string; providerId: string }) {
|
|
51
|
+
const email = data.email.trim().toLowerCase();
|
|
52
|
+
const existing = await this.findUserByEmail(email);
|
|
53
|
+
if (existing) return existing;
|
|
54
|
+
|
|
55
|
+
const user = await db.user.create({
|
|
56
|
+
data: {
|
|
57
|
+
email,
|
|
58
|
+
password: null,
|
|
59
|
+
verified: true,
|
|
60
|
+
identityProviders: {
|
|
61
|
+
create: { provider: data.provider, providerId: data.providerId },
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
include: includeProviders,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
return toIdentityUser(user) as IdentityUser;
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
async linkProvider(userId: string, provider: string, providerId: string) {
|
|
71
|
+
const existing = await db.identityProvider.findUnique({
|
|
72
|
+
where: { provider_providerId: { provider, providerId } },
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
if (existing && existing.userId !== userId) {
|
|
76
|
+
throw new Error("Provider is already linked to another user");
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const user = await db.user.findUnique({ where: { id: userId } });
|
|
80
|
+
if (!user) {
|
|
81
|
+
throw new Error(`User not found: ${userId}`);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
await db.identityProvider.upsert({
|
|
85
|
+
where: { userId_provider: { userId, provider } },
|
|
86
|
+
create: { userId, provider, providerId },
|
|
87
|
+
update: { providerId },
|
|
88
|
+
});
|
|
89
|
+
},
|
|
90
|
+
};
|
|
@@ -2,9 +2,8 @@ import { Request, Response, NextFunction } from "express";
|
|
|
2
2
|
import { getAuth } from "../auth";
|
|
3
3
|
|
|
4
4
|
export const adminMiddleware = (req: Request, res: Response, next: NextFunction) => {
|
|
5
|
-
getAuth().requireAdmin(req, res, next);
|
|
5
|
+
return getAuth().requireAdmin(req, res, next);
|
|
6
6
|
};
|
|
7
7
|
|
|
8
|
-
export const authMiddleware = (req: Request, res: Response, next: NextFunction) =>
|
|
9
|
-
getAuth().
|
|
10
|
-
};
|
|
8
|
+
export const authMiddleware = (req: Request, res: Response, next: NextFunction) =>
|
|
9
|
+
getAuth().requireAuth(req, res, next);
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import express from "express";
|
|
2
|
-
import { adminMiddleware } from "../middleware/auth.middleware";
|
|
2
|
+
import { adminMiddleware, authMiddleware } from "../middleware/auth.middleware";
|
|
3
3
|
import { protectedController } from "../controllers/protected.controller";
|
|
4
4
|
|
|
5
5
|
const router = express.Router();
|
|
6
6
|
|
|
7
|
-
router.get("/protected",
|
|
7
|
+
router.get("/protected", authMiddleware, protectedController.protected);
|
|
8
8
|
router.get("/admin/sessions/:userId", adminMiddleware, protectedController.listSessions);
|
|
9
9
|
router.delete("/admin/sessions/:userId/:sessionId", adminMiddleware, protectedController.revokeSession);
|
|
10
10
|
router.delete("/admin/sessions/:userId", adminMiddleware, protectedController.revokeAllSessions);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createAuthenik8 } from "authenik8-core";
|
|
2
2
|
import dotenv from "dotenv";
|
|
3
|
-
import { requiredEnv, requiredSecret } from "../../../../src/utils/security";
|
|
3
|
+
import { agentIdentityConfig, authJwkConfig, requiredEnv, requiredSecret } from "../../../../src/utils/security";
|
|
4
4
|
|
|
5
5
|
dotenv.config();
|
|
6
6
|
|
|
@@ -18,8 +18,9 @@ function oauthConfig() {
|
|
|
18
18
|
|
|
19
19
|
export async function initAuth() {
|
|
20
20
|
authInstance= await createAuthenik8({
|
|
21
|
-
|
|
21
|
+
jwt: authJwkConfig(),
|
|
22
22
|
refreshSecret: requiredSecret("REFRESH_SECRET"),
|
|
23
|
+
agent: agentIdentityConfig(),
|
|
23
24
|
oauth: oauthConfig(),
|
|
24
25
|
});
|
|
25
26
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createAuthenik8 } from "authenik8-core";
|
|
2
2
|
import dotenv from "dotenv";
|
|
3
|
-
import { requiredEnv, requiredSecret } from "../../../../src/utils/security";
|
|
3
|
+
import { agentIdentityConfig, authJwkConfig, requiredEnv, requiredSecret } from "../../../../src/utils/security";
|
|
4
4
|
|
|
5
5
|
dotenv.config();
|
|
6
6
|
|
|
@@ -18,8 +18,9 @@ function oauthConfig() {
|
|
|
18
18
|
|
|
19
19
|
export async function initAuth() {
|
|
20
20
|
authInstance= await createAuthenik8({
|
|
21
|
-
|
|
21
|
+
jwt: authJwkConfig(),
|
|
22
22
|
refreshSecret: requiredSecret("REFRESH_SECRET"),
|
|
23
|
+
agent: agentIdentityConfig(),
|
|
23
24
|
oauth: oauthConfig(),
|
|
24
25
|
});
|
|
25
26
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createAuthenik8 } from "authenik8-core";
|
|
2
2
|
import dotenv from "dotenv";
|
|
3
|
-
import { requiredEnv, requiredSecret } from "../utils/security";
|
|
3
|
+
import { agentIdentityConfig, authJwkConfig, requiredEnv, requiredSecret } from "../utils/security";
|
|
4
4
|
|
|
5
5
|
dotenv.config();
|
|
6
6
|
|
|
@@ -23,8 +23,9 @@ function oauthConfig() {
|
|
|
23
23
|
|
|
24
24
|
export async function initAuth() {
|
|
25
25
|
authInstance= await createAuthenik8({
|
|
26
|
-
|
|
26
|
+
jwt: authJwkConfig(),
|
|
27
27
|
refreshSecret: requiredSecret("REFRESH_SECRET"),
|
|
28
|
+
agent: agentIdentityConfig(),
|
|
28
29
|
oauth: oauthConfig(),
|
|
29
30
|
});
|
|
30
31
|
|
|
@@ -3,6 +3,7 @@ import passwordRoutes from "./auth/routes/password.route";
|
|
|
3
3
|
import oauthRoutes from "./auth/routes/oauth.routes";
|
|
4
4
|
import protectedRoutes from "./auth/routes/protected.routes";
|
|
5
5
|
import { getAuth, initAuth } from "./auth/auth";
|
|
6
|
+
import { requiredPort } from "./utils/security";
|
|
6
7
|
|
|
7
8
|
async function start(){
|
|
8
9
|
await initAuth();
|
|
@@ -12,13 +13,15 @@ async function start(){
|
|
|
12
13
|
app.use(express.json({ limit: "16kb", strict: true }));
|
|
13
14
|
app.use(auth.helmet);
|
|
14
15
|
app.use(auth.rateLimit);
|
|
16
|
+
app.get("/.well-known/jwks.json", (_req, res) => res.json(auth.getJwks()));
|
|
15
17
|
|
|
16
18
|
app.use("/auth", passwordRoutes);
|
|
17
19
|
app.use("/auth", oauthRoutes);
|
|
18
20
|
app.use("/", protectedRoutes);
|
|
19
21
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
+
const port = requiredPort();
|
|
23
|
+
app.listen(port, () => {
|
|
24
|
+
console.log(`Auth system running on http://localhost:${port}`);
|
|
22
25
|
});
|
|
23
26
|
}
|
|
24
27
|
|
|
@@ -32,11 +35,3 @@ process.on("unhandledRejection", (err) => {
|
|
|
32
35
|
console.error(" Unhandled Rejection:", err);
|
|
33
36
|
process.exit(1);
|
|
34
37
|
});
|
|
35
|
-
setInterval(() => {
|
|
36
|
-
const used = process.memoryUsage().heapUsed / 1024 / 1024;
|
|
37
|
-
|
|
38
|
-
if (used > 300) {
|
|
39
|
-
console.error(` Memory exceeded: ${used.toFixed(2)} MB`);
|
|
40
|
-
process.exit(1);
|
|
41
|
-
}
|
|
42
|
-
}, 10000);
|