create-authenik8-app 2.4.14 → 2.4.15
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 +34 -516
- package/dist/src/bin/cli.d.ts +3 -0
- package/dist/src/bin/cli.d.ts.map +1 -0
- package/dist/src/bin/cli.js +112 -0
- package/dist/src/bin/cli.js.map +1 -0
- package/dist/src/bin/index.js +108 -23
- package/dist/src/bin/index.js.map +1 -1
- package/dist/src/commands/add/apply.d.ts +6 -0
- package/dist/src/commands/add/apply.d.ts.map +1 -0
- package/dist/src/commands/add/apply.js +123 -0
- package/dist/src/commands/add/apply.js.map +1 -0
- package/dist/src/commands/add/ci.d.ts +6 -0
- package/dist/src/commands/add/ci.d.ts.map +1 -0
- package/dist/src/commands/add/ci.js +108 -0
- package/dist/src/commands/add/ci.js.map +1 -0
- package/dist/src/commands/add/context.d.ts +5 -0
- package/dist/src/commands/add/context.d.ts.map +1 -0
- package/dist/src/commands/add/context.js +19 -0
- package/dist/src/commands/add/context.js.map +1 -0
- package/dist/src/commands/add/diff.d.ts +4 -0
- package/dist/src/commands/add/diff.d.ts.map +1 -0
- package/dist/src/commands/add/diff.js +114 -0
- package/dist/src/commands/add/diff.js.map +1 -0
- package/dist/src/commands/add/index.d.ts +6 -0
- package/dist/src/commands/add/index.d.ts.map +1 -0
- package/dist/src/commands/add/index.js +61 -0
- package/dist/src/commands/add/index.js.map +1 -0
- package/dist/src/commands/add/output.d.ts +5 -0
- package/dist/src/commands/add/output.d.ts.map +1 -0
- package/dist/src/commands/add/output.js +58 -0
- package/dist/src/commands/add/output.js.map +1 -0
- package/dist/src/commands/add/plan.d.ts +6 -0
- package/dist/src/commands/add/plan.d.ts.map +1 -0
- package/dist/src/commands/add/plan.js +93 -0
- package/dist/src/commands/add/plan.js.map +1 -0
- package/dist/src/commands/add/registry.d.ts +4 -0
- package/dist/src/commands/add/registry.d.ts.map +1 -0
- package/dist/src/commands/add/registry.js +37 -0
- package/dist/src/commands/add/registry.js.map +1 -0
- package/dist/src/commands/add/types.d.ts +37 -0
- package/dist/src/commands/add/types.d.ts.map +1 -0
- package/dist/src/commands/add/types.js +2 -0
- package/dist/src/commands/add/types.js.map +1 -0
- package/dist/src/commands/doctor/checks.d.ts +3 -0
- package/dist/src/commands/doctor/checks.d.ts.map +1 -0
- package/dist/src/commands/doctor/checks.js +353 -0
- package/dist/src/commands/doctor/checks.js.map +1 -0
- package/dist/src/commands/doctor/context.d.ts +5 -0
- package/dist/src/commands/doctor/context.d.ts.map +1 -0
- package/dist/src/commands/doctor/context.js +134 -0
- package/dist/src/commands/doctor/context.js.map +1 -0
- package/dist/src/commands/doctor/index.d.ts +9 -0
- package/dist/src/commands/doctor/index.d.ts.map +1 -0
- package/dist/src/commands/doctor/index.js +80 -0
- package/dist/src/commands/doctor/index.js.map +1 -0
- package/dist/src/commands/doctor/output.d.ts +4 -0
- package/dist/src/commands/doctor/output.d.ts.map +1 -0
- package/dist/src/commands/doctor/output.js +53 -0
- package/dist/src/commands/doctor/output.js.map +1 -0
- package/dist/src/commands/doctor/postGeneration.d.ts +7 -0
- package/dist/src/commands/doctor/postGeneration.d.ts.map +1 -0
- package/dist/src/commands/doctor/postGeneration.js +22 -0
- package/dist/src/commands/doctor/postGeneration.js.map +1 -0
- package/dist/src/commands/doctor/services.d.ts +5 -0
- package/dist/src/commands/doctor/services.d.ts.map +1 -0
- package/dist/src/commands/doctor/services.js +80 -0
- package/dist/src/commands/doctor/services.js.map +1 -0
- package/dist/src/commands/doctor/types.d.ts +69 -0
- package/dist/src/commands/doctor/types.d.ts.map +1 -0
- package/dist/src/commands/doctor/types.js +2 -0
- package/dist/src/commands/doctor/types.js.map +1 -0
- package/dist/src/commands/upgrade/context.d.ts +5 -0
- package/dist/src/commands/upgrade/context.d.ts.map +1 -0
- package/dist/src/commands/upgrade/context.js +57 -0
- package/dist/src/commands/upgrade/context.js.map +1 -0
- package/dist/src/commands/upgrade/index.d.ts +7 -0
- package/dist/src/commands/upgrade/index.d.ts.map +1 -0
- package/dist/src/commands/upgrade/index.js +35 -0
- package/dist/src/commands/upgrade/index.js.map +1 -0
- package/dist/src/commands/upgrade/migrations.d.ts +4 -0
- package/dist/src/commands/upgrade/migrations.d.ts.map +1 -0
- package/dist/src/commands/upgrade/migrations.js +22 -0
- package/dist/src/commands/upgrade/migrations.js.map +1 -0
- package/dist/src/commands/upgrade/output.d.ts +4 -0
- package/dist/src/commands/upgrade/output.d.ts.map +1 -0
- package/dist/src/commands/upgrade/output.js +52 -0
- package/dist/src/commands/upgrade/output.js.map +1 -0
- package/dist/src/commands/upgrade/plan.d.ts +3 -0
- package/dist/src/commands/upgrade/plan.d.ts.map +1 -0
- package/dist/src/commands/upgrade/plan.js +119 -0
- package/dist/src/commands/upgrade/plan.js.map +1 -0
- package/dist/src/commands/upgrade/types.d.ts +48 -0
- package/dist/src/commands/upgrade/types.d.ts.map +1 -0
- package/dist/src/commands/upgrade/types.js +2 -0
- package/dist/src/commands/upgrade/types.js.map +1 -0
- package/dist/src/lib/args.d.ts +21 -0
- package/dist/src/lib/args.d.ts.map +1 -1
- package/dist/src/lib/args.js +119 -1
- package/dist/src/lib/args.js.map +1 -1
- package/dist/src/lib/constants.d.ts +1 -1
- package/dist/src/lib/constants.d.ts.map +1 -1
- package/dist/src/lib/constants.js +3 -1
- package/dist/src/lib/constants.js.map +1 -1
- package/dist/src/lib/expressOAuth.d.ts +10 -0
- package/dist/src/lib/expressOAuth.d.ts.map +1 -0
- package/dist/src/lib/expressOAuth.js +164 -0
- package/dist/src/lib/expressOAuth.js.map +1 -0
- package/dist/src/lib/interruption.d.ts +14 -0
- package/dist/src/lib/interruption.d.ts.map +1 -0
- package/dist/src/lib/interruption.js +16 -0
- package/dist/src/lib/interruption.js.map +1 -0
- package/dist/src/lib/nonInteractive.d.ts +8 -0
- package/dist/src/lib/nonInteractive.d.ts.map +1 -0
- package/dist/src/lib/nonInteractive.js +111 -0
- package/dist/src/lib/nonInteractive.js.map +1 -0
- package/dist/src/lib/oauth.d.ts +9 -0
- package/dist/src/lib/oauth.d.ts.map +1 -0
- package/dist/src/lib/oauth.js +62 -0
- package/dist/src/lib/oauth.js.map +1 -0
- package/dist/src/lib/onboarding.d.ts +8 -0
- package/dist/src/lib/onboarding.d.ts.map +1 -0
- package/dist/src/lib/onboarding.js +35 -0
- package/dist/src/lib/onboarding.js.map +1 -0
- package/dist/src/lib/packageManagerCommands.d.ts +11 -0
- package/dist/src/lib/packageManagerCommands.d.ts.map +1 -0
- package/dist/src/lib/packageManagerCommands.js +34 -0
- package/dist/src/lib/packageManagerCommands.js.map +1 -0
- package/dist/src/lib/process.d.ts.map +1 -1
- package/dist/src/lib/process.js +9 -0
- package/dist/src/lib/process.js.map +1 -1
- package/dist/src/lib/projectManifest.d.ts +67 -0
- package/dist/src/lib/projectManifest.d.ts.map +1 -0
- package/dist/src/lib/projectManifest.js +134 -0
- package/dist/src/lib/projectManifest.js.map +1 -0
- package/dist/src/lib/release.d.ts +9 -0
- package/dist/src/lib/release.d.ts.map +1 -0
- package/dist/src/lib/release.js +48 -0
- package/dist/src/lib/release.js.map +1 -0
- package/dist/src/lib/rootCommand.d.ts +15 -0
- package/dist/src/lib/rootCommand.d.ts.map +1 -0
- package/dist/src/lib/rootCommand.js +17 -0
- package/dist/src/lib/rootCommand.js.map +1 -0
- package/dist/src/lib/schemas.d.ts +15 -6
- package/dist/src/lib/schemas.d.ts.map +1 -1
- package/dist/src/lib/schemas.js +8 -1
- package/dist/src/lib/schemas.js.map +1 -1
- package/dist/src/lib/semver.d.ts +10 -0
- package/dist/src/lib/semver.d.ts.map +1 -0
- package/dist/src/lib/semver.js +51 -0
- package/dist/src/lib/semver.js.map +1 -0
- package/dist/src/lib/types.d.ts +5 -1
- package/dist/src/lib/types.d.ts.map +1 -1
- package/dist/src/steps/configurePrisma.d.ts.map +1 -1
- package/dist/src/steps/configurePrisma.js +18 -23
- package/dist/src/steps/configurePrisma.js.map +1 -1
- package/dist/src/steps/configureReadme.d.ts +4 -0
- package/dist/src/steps/configureReadme.d.ts.map +1 -0
- package/dist/src/steps/configureReadme.js +22 -0
- package/dist/src/steps/configureReadme.js.map +1 -0
- package/dist/src/steps/createProject.d.ts.map +1 -1
- package/dist/src/steps/createProject.js +16 -170
- package/dist/src/steps/createProject.js.map +1 -1
- package/dist/src/steps/finalSetup.d.ts +3 -2
- package/dist/src/steps/finalSetup.d.ts.map +1 -1
- package/dist/src/steps/finalSetup.js +14 -9
- package/dist/src/steps/finalSetup.js.map +1 -1
- package/dist/src/steps/installDeps.d.ts +1 -0
- package/dist/src/steps/installDeps.d.ts.map +1 -1
- package/dist/src/steps/installDeps.js +11 -0
- package/dist/src/steps/installDeps.js.map +1 -1
- package/dist/src/steps/prompts.d.ts.map +1 -1
- package/dist/src/steps/prompts.js +32 -13
- package/dist/src/steps/prompts.js.map +1 -1
- package/dist/src/utils/output.d.ts.map +1 -1
- package/dist/src/utils/output.js +23 -11
- package/dist/src/utils/output.js.map +1 -1
- package/package.json +5 -4
- package/templates/express-auth/README.md +21 -12
- package/templates/express-auth/package.json +11 -7
- package/templates/express-auth/src/config/redis.ts +109 -0
- package/templates/express-auth/src/server.ts +2 -0
- package/templates/express-auth+/README.md +25 -18
- package/templates/express-auth+/package.json +11 -7
- package/templates/express-auth+/src/auth/auth.ts +2 -0
- package/templates/express-auth+/src/auth/controllers/password.controller.ts +12 -1
- package/templates/express-auth+/src/auth/routes/password.route.ts +1 -0
- package/templates/express-auth+/src/config/redis.ts +109 -0
- package/templates/express-auth+/src/utils/security.ts +12 -0
- package/templates/express-base/README.md +21 -12
- package/templates/express-base/package.json +11 -8
- package/templates/express-base/src/.env.example +1 -1
- package/templates/express-base/src/config/redis.ts +109 -0
- package/templates/express-base/src/server.ts +2 -0
- package/templates/fullstack/.env.example +3 -2
- package/templates/fullstack/PRESET_CONTRACT.md +7 -3
- package/templates/fullstack/README.md +45 -7
- package/templates/fullstack/apps/api/package.json +6 -4
- package/templates/fullstack/apps/api/prisma/migrations/20260721000000_init/migration.sql +163 -0
- package/templates/fullstack/apps/api/prisma/migrations/migration_lock.toml +3 -0
- package/templates/fullstack/apps/api/src/auth/authenik8.ts +13 -4
- package/templates/fullstack/apps/api/src/config/env.ts +9 -1
- package/templates/fullstack/apps/api/src/config/logger.ts +21 -11
- package/templates/fullstack/apps/api/tests/logger.test.ts +43 -0
- package/templates/fullstack/apps/web/package.json +3 -1
- package/templates/fullstack/apps/web/src/App.tsx +1 -1
- package/templates/fullstack/apps/web/src/auth/RouteGuards.tsx +1 -1
- package/templates/fullstack/apps/web/src/components/AppShell.tsx +90 -45
- package/templates/fullstack/apps/web/src/components/AuthShell.tsx +10 -7
- package/templates/fullstack/apps/web/src/components/Page.tsx +6 -3
- package/templates/fullstack/apps/web/src/features/projects/ProjectPages.tsx +1 -1
- package/templates/fullstack/apps/web/src/main.tsx +13 -6
- package/templates/fullstack/apps/web/src/pages/DashboardPage.tsx +163 -23
- package/templates/fullstack/apps/web/src/pages/StatusPages.tsx +1 -1
- package/templates/fullstack/apps/web/src/pages/auth/LoginPage.tsx +93 -17
- package/templates/fullstack/apps/web/src/pages/auth/OAuthCallbackPage.tsx +1 -1
- package/templates/fullstack/apps/web/src/pages/auth/RecoveryPages.tsx +1 -1
- package/templates/fullstack/apps/web/src/pages/auth/RegisterPage.tsx +1 -1
- package/templates/fullstack/apps/web/src/pages/auth/VerifyEmailPage.tsx +1 -1
- package/templates/fullstack/apps/web/src/styles.css +119 -43
- package/templates/fullstack/apps/web/vite.config.ts +12 -0
- package/templates/fullstack/docker-compose.yml +2 -2
- package/templates/fullstack/gitignore.template +1 -0
- package/templates/fullstack/package.json +17 -8
- package/templates/fullstack/scripts/run-local.mjs +157 -0
- package/templates/prisma/postgresql/.env.example +1 -2
- package/templates/prisma/sqlite/.env.example +1 -2
- package/templates/prisma/sqlite/client.ts +2 -2
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import Redis, { type Redis as RedisClient } from "ioredis";
|
|
2
|
+
|
|
3
|
+
const localRedisUrl = "memory://";
|
|
4
|
+
const redisOptions = {
|
|
5
|
+
connectTimeout: 5_000,
|
|
6
|
+
enableReadyCheck: true,
|
|
7
|
+
maxRetriesPerRequest: 2,
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
function externalRedisUrl(source: string): string {
|
|
11
|
+
let url: URL;
|
|
12
|
+
try {
|
|
13
|
+
url = new URL(source);
|
|
14
|
+
} catch {
|
|
15
|
+
throw new Error("REDIS_URL must be memory://, redis://, or rediss://");
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (url.protocol !== "redis:" && url.protocol !== "rediss:") {
|
|
19
|
+
throw new Error("REDIS_URL must be memory://, redis://, or rediss://");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return source;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function redisPort(): number {
|
|
26
|
+
const port = Number(process.env.REDIS_PORT?.trim() || "6379");
|
|
27
|
+
if (!Number.isInteger(port) || port < 1 || port > 65_535) {
|
|
28
|
+
throw new Error("REDIS_PORT must be an integer from 1 to 65535");
|
|
29
|
+
}
|
|
30
|
+
return port;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function waitForRedis(client: RedisClient): Promise<RedisClient> {
|
|
34
|
+
return new Promise((resolve, reject) => {
|
|
35
|
+
let settled = false;
|
|
36
|
+
let readinessCheckStarted = false;
|
|
37
|
+
const cleanup = () => {
|
|
38
|
+
client.off("ready", handleReady);
|
|
39
|
+
client.off("error", handleError);
|
|
40
|
+
};
|
|
41
|
+
const finish = (error?: Error) => {
|
|
42
|
+
if (settled) return;
|
|
43
|
+
settled = true;
|
|
44
|
+
cleanup();
|
|
45
|
+
if (error) {
|
|
46
|
+
client.disconnect();
|
|
47
|
+
reject(error);
|
|
48
|
+
} else {
|
|
49
|
+
resolve(client);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
const handleReady = () => {
|
|
53
|
+
if (readinessCheckStarted) return;
|
|
54
|
+
readinessCheckStarted = true;
|
|
55
|
+
void client.ping().then(() => finish(), (error: Error) => finish(error));
|
|
56
|
+
};
|
|
57
|
+
const handleError = (error: Error) => finish(error);
|
|
58
|
+
|
|
59
|
+
client.once("ready", handleReady);
|
|
60
|
+
client.once("error", handleError);
|
|
61
|
+
if (client.status === "ready") handleReady();
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export async function createRedisClient(): Promise<RedisClient> {
|
|
66
|
+
const redisUrl = process.env.REDIS_URL?.trim();
|
|
67
|
+
const nodeEnv = process.env.NODE_ENV?.trim() || "development";
|
|
68
|
+
|
|
69
|
+
const hasExternalRedisConfig = Boolean(
|
|
70
|
+
redisUrl ||
|
|
71
|
+
process.env.REDIS_HOST?.trim() ||
|
|
72
|
+
process.env.REDIS_PORT?.trim() ||
|
|
73
|
+
process.env.REDIS_PASSWORD?.trim(),
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
if (redisUrl === localRedisUrl && nodeEnv === "production") {
|
|
77
|
+
throw new Error(
|
|
78
|
+
"REDIS_URL=memory:// is for local development only; use redis:// or rediss:// in production",
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const shouldUseMemoryRedis =
|
|
83
|
+
redisUrl === localRedisUrl ||
|
|
84
|
+
(!hasExternalRedisConfig && nodeEnv !== "production");
|
|
85
|
+
|
|
86
|
+
if (shouldUseMemoryRedis) {
|
|
87
|
+
const { default: RedisMock } = await import("ioredis-mock");
|
|
88
|
+
return new RedisMock() as unknown as RedisClient;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (nodeEnv === "production" && !hasExternalRedisConfig) {
|
|
92
|
+
throw new Error(
|
|
93
|
+
"REDIS_URL=memory:// is for local development only; use redis:// or rediss:// in production",
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (redisUrl) {
|
|
98
|
+
return waitForRedis(new Redis(externalRedisUrl(redisUrl), redisOptions));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return waitForRedis(new Redis({
|
|
102
|
+
...redisOptions,
|
|
103
|
+
host: process.env.REDIS_HOST?.trim() || "127.0.0.1",
|
|
104
|
+
port: redisPort(),
|
|
105
|
+
...(process.env.REDIS_PASSWORD?.trim()
|
|
106
|
+
? { password: process.env.REDIS_PASSWORD.trim() }
|
|
107
|
+
: {}),
|
|
108
|
+
}));
|
|
109
|
+
}
|
|
@@ -13,6 +13,10 @@ export const credentialsSchema = z.strictObject({
|
|
|
13
13
|
password: passwordSchema,
|
|
14
14
|
});
|
|
15
15
|
|
|
16
|
+
export const refreshTokenBodySchema = z.strictObject({
|
|
17
|
+
refreshToken: z.string().trim().min(16, "Refresh token is required").max(4096),
|
|
18
|
+
});
|
|
19
|
+
|
|
16
20
|
const secretSchema = z.string().trim().min(32);
|
|
17
21
|
const environmentValueSchema = z.string().trim().min(1);
|
|
18
22
|
const portSchema = z.coerce.number().int().min(1).max(65535);
|
|
@@ -121,6 +125,14 @@ export function parseCredentials(body: unknown): z.infer<typeof credentialsSchem
|
|
|
121
125
|
return result.data;
|
|
122
126
|
}
|
|
123
127
|
|
|
128
|
+
export function parseRefreshToken(body: unknown): string {
|
|
129
|
+
const result = refreshTokenBodySchema.safeParse(body);
|
|
130
|
+
if (!result.success) {
|
|
131
|
+
throw new InputValidationError(validationMessage(result.error, "Refresh token is required"));
|
|
132
|
+
}
|
|
133
|
+
return result.data.refreshToken;
|
|
134
|
+
}
|
|
135
|
+
|
|
124
136
|
export function parseIdentifier(value: unknown, label: string): string {
|
|
125
137
|
const result = identifierSchema.safeParse(value);
|
|
126
138
|
if (!result.success) throw new InputValidationError(`${label} is invalid`);
|
|
@@ -6,12 +6,21 @@ Generated by `create-authenik8-app`.
|
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
8
|
npm install
|
|
9
|
-
npm run
|
|
10
|
-
npm run prisma:migrate
|
|
9
|
+
npm run db:migrate
|
|
11
10
|
npm run dev
|
|
12
11
|
```
|
|
13
12
|
|
|
14
|
-
|
|
13
|
+
The generated local environment uses an in-process Redis-compatible store, so Redis and Docker are optional during development. If you selected PostgreSQL, start it with `npm run docker:up` or point `DATABASE_URL` to an existing instance before migrating.
|
|
14
|
+
|
|
15
|
+
Validate the generated auth configuration and Redis connection at any time:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npx create-authenik8-app@latest doctor
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
`authenik8.json` records the generated architecture and Authenik8 engine version. It contains no secrets and should be committed with the project.
|
|
22
|
+
|
|
23
|
+
After committing the package-manager lockfile, run `npx create-authenik8-app@latest add ci-github` to add the pinned Doctor and upgrade-policy workflow. Preview it first with `--dry-run`.
|
|
15
24
|
|
|
16
25
|
## Environment
|
|
17
26
|
|
|
@@ -27,8 +36,7 @@ AUTHENIK8_ISSUER=http://localhost:3000
|
|
|
27
36
|
AUTHENIK8_AUDIENCE=your-app-api
|
|
28
37
|
AUTHENIK8_AGENTS={}
|
|
29
38
|
REFRESH_SECRET=<generated-random-secret>
|
|
30
|
-
|
|
31
|
-
REDIS_PORT=6379
|
|
39
|
+
REDIS_URL=memory://
|
|
32
40
|
```
|
|
33
41
|
|
|
34
42
|
## Routes
|
|
@@ -51,8 +59,7 @@ Authorization: Bearer <accessToken>
|
|
|
51
59
|
Start the API in one terminal:
|
|
52
60
|
|
|
53
61
|
```bash
|
|
54
|
-
npm run
|
|
55
|
-
npm run prisma:migrate
|
|
62
|
+
npm run db:migrate
|
|
56
63
|
npm run dev
|
|
57
64
|
```
|
|
58
65
|
|
|
@@ -78,8 +85,8 @@ Expected result: the route should reject the request because no access token was
|
|
|
78
85
|
- `AUTHENIK8_ISSUER` / `AUTHENIK8_AUDIENCE`: required claims checked during verification.
|
|
79
86
|
- `AUTHENIK8_AGENTS`: optional JSON map of agent IDs to exact `resource:action` scopes; keep `{}` to disable agent identity. See `AGENT_IDENTITY.md`.
|
|
80
87
|
- `REFRESH_SECRET`: signs refresh tokens. Use a different long random value in production.
|
|
81
|
-
- `
|
|
82
|
-
- `REDIS_PORT`: Redis
|
|
88
|
+
- `REDIS_URL`: use `memory://` for local development or `redis://` / `rediss://` for an external service. Production rejects `memory://`.
|
|
89
|
+
- `REDIS_HOST` / `REDIS_PORT` / `REDIS_PASSWORD`: optional external Redis settings used when `REDIS_URL` is omitted.
|
|
83
90
|
|
|
84
91
|
## Frontend Fetch Example
|
|
85
92
|
|
|
@@ -101,9 +108,11 @@ export async function getProtected(accessToken: string) {
|
|
|
101
108
|
|
|
102
109
|
## Troubleshooting
|
|
103
110
|
|
|
104
|
-
`Redis connection refused`:
|
|
111
|
+
`Redis connection refused`: verify `REDIS_URL`, or omit it and check `REDIS_HOST`, `REDIS_PORT`, and `REDIS_PASSWORD`.
|
|
112
|
+
|
|
113
|
+
`Prisma Client did not initialize`: run `npm run db:migrate`, then restart `npm run dev`.
|
|
105
114
|
|
|
106
|
-
`
|
|
115
|
+
`REDIS_URL=memory:// is for local development only`: configure a `redis://` or `rediss://` endpoint before running in production.
|
|
107
116
|
|
|
108
117
|
`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`.
|
|
109
118
|
|
|
@@ -117,4 +126,4 @@ Read `THREAT_MODEL.md` before deploying. It explains what Authenik8 protects, wh
|
|
|
117
126
|
|
|
118
127
|
## Deploy
|
|
119
128
|
|
|
120
|
-
Use `npm run build`, run `npx prisma migrate deploy` for production databases, set real secrets in your host, and point Redis/Postgres
|
|
129
|
+
Use `npm run build`, run `npx prisma migrate deploy` for production databases, set `NODE_ENV=production`, store real secrets in your host, and point Redis/Postgres environment variables at managed services.
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "authenik8-app",
|
|
3
3
|
"scripts": {
|
|
4
|
-
"dev": "
|
|
4
|
+
"dev": "tsx watch ./src/server.ts",
|
|
5
5
|
"build": "tsc",
|
|
6
6
|
"start": "node dist/src/server.js",
|
|
7
|
-
"
|
|
7
|
+
"db:migrate": "prisma db push && prisma generate",
|
|
8
|
+
"prisma:migrate": "prisma db push && prisma generate",
|
|
8
9
|
"docker:up": "docker compose up -d --wait",
|
|
9
10
|
"docker:down": "docker compose down"
|
|
10
11
|
},
|
|
@@ -12,19 +13,21 @@
|
|
|
12
13
|
"authenik8-core": "2.0.3",
|
|
13
14
|
"dotenv": "^16.0.0",
|
|
14
15
|
"express": "^4.18.2",
|
|
15
|
-
"@prisma/client": "7.
|
|
16
|
+
"@prisma/client": "7.9.0",
|
|
17
|
+
"ioredis": "^5.8.1",
|
|
16
18
|
"zod": "^4.4.3"
|
|
17
19
|
},
|
|
18
20
|
"devDependencies": {
|
|
19
21
|
"@types/express": "^4.17.21",
|
|
20
22
|
"@types/node": "^22.10.2",
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
"ioredis-mock": "8.13.1",
|
|
24
|
+
"prisma": "7.9.0",
|
|
25
|
+
"tsx": "^4.22.4",
|
|
26
|
+
"typescript": "^5.0.0"
|
|
25
27
|
},
|
|
26
28
|
"overrides": {
|
|
27
|
-
"
|
|
29
|
+
"find-my-way": "9.7.0",
|
|
30
|
+
"valibot": "1.4.2"
|
|
28
31
|
},
|
|
29
32
|
"type": "commonjs"
|
|
30
33
|
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import Redis, { type Redis as RedisClient } from "ioredis";
|
|
2
|
+
|
|
3
|
+
const localRedisUrl = "memory://";
|
|
4
|
+
const redisOptions = {
|
|
5
|
+
connectTimeout: 5_000,
|
|
6
|
+
enableReadyCheck: true,
|
|
7
|
+
maxRetriesPerRequest: 2,
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
function externalRedisUrl(source: string): string {
|
|
11
|
+
let url: URL;
|
|
12
|
+
try {
|
|
13
|
+
url = new URL(source);
|
|
14
|
+
} catch {
|
|
15
|
+
throw new Error("REDIS_URL must be memory://, redis://, or rediss://");
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (url.protocol !== "redis:" && url.protocol !== "rediss:") {
|
|
19
|
+
throw new Error("REDIS_URL must be memory://, redis://, or rediss://");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return source;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function redisPort(): number {
|
|
26
|
+
const port = Number(process.env.REDIS_PORT?.trim() || "6379");
|
|
27
|
+
if (!Number.isInteger(port) || port < 1 || port > 65_535) {
|
|
28
|
+
throw new Error("REDIS_PORT must be an integer from 1 to 65535");
|
|
29
|
+
}
|
|
30
|
+
return port;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function waitForRedis(client: RedisClient): Promise<RedisClient> {
|
|
34
|
+
return new Promise((resolve, reject) => {
|
|
35
|
+
let settled = false;
|
|
36
|
+
let readinessCheckStarted = false;
|
|
37
|
+
const cleanup = () => {
|
|
38
|
+
client.off("ready", handleReady);
|
|
39
|
+
client.off("error", handleError);
|
|
40
|
+
};
|
|
41
|
+
const finish = (error?: Error) => {
|
|
42
|
+
if (settled) return;
|
|
43
|
+
settled = true;
|
|
44
|
+
cleanup();
|
|
45
|
+
if (error) {
|
|
46
|
+
client.disconnect();
|
|
47
|
+
reject(error);
|
|
48
|
+
} else {
|
|
49
|
+
resolve(client);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
const handleReady = () => {
|
|
53
|
+
if (readinessCheckStarted) return;
|
|
54
|
+
readinessCheckStarted = true;
|
|
55
|
+
void client.ping().then(() => finish(), (error: Error) => finish(error));
|
|
56
|
+
};
|
|
57
|
+
const handleError = (error: Error) => finish(error);
|
|
58
|
+
|
|
59
|
+
client.once("ready", handleReady);
|
|
60
|
+
client.once("error", handleError);
|
|
61
|
+
if (client.status === "ready") handleReady();
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export async function createRedisClient(): Promise<RedisClient> {
|
|
66
|
+
const redisUrl = process.env.REDIS_URL?.trim();
|
|
67
|
+
const nodeEnv = process.env.NODE_ENV?.trim() || "development";
|
|
68
|
+
|
|
69
|
+
const hasExternalRedisConfig = Boolean(
|
|
70
|
+
redisUrl ||
|
|
71
|
+
process.env.REDIS_HOST?.trim() ||
|
|
72
|
+
process.env.REDIS_PORT?.trim() ||
|
|
73
|
+
process.env.REDIS_PASSWORD?.trim(),
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
if (redisUrl === localRedisUrl && nodeEnv === "production") {
|
|
77
|
+
throw new Error(
|
|
78
|
+
"REDIS_URL=memory:// is for local development only; use redis:// or rediss:// in production",
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const shouldUseMemoryRedis =
|
|
83
|
+
redisUrl === localRedisUrl ||
|
|
84
|
+
(!hasExternalRedisConfig && nodeEnv !== "production");
|
|
85
|
+
|
|
86
|
+
if (shouldUseMemoryRedis) {
|
|
87
|
+
const { default: RedisMock } = await import("ioredis-mock");
|
|
88
|
+
return new RedisMock() as unknown as RedisClient;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (nodeEnv === "production" && !hasExternalRedisConfig) {
|
|
92
|
+
throw new Error(
|
|
93
|
+
"REDIS_URL=memory:// is for local development only; use redis:// or rediss:// in production",
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (redisUrl) {
|
|
98
|
+
return waitForRedis(new Redis(externalRedisUrl(redisUrl), redisOptions));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return waitForRedis(new Redis({
|
|
102
|
+
...redisOptions,
|
|
103
|
+
host: process.env.REDIS_HOST?.trim() || "127.0.0.1",
|
|
104
|
+
port: redisPort(),
|
|
105
|
+
...(process.env.REDIS_PASSWORD?.trim()
|
|
106
|
+
? { password: process.env.REDIS_PASSWORD.trim() }
|
|
107
|
+
: {}),
|
|
108
|
+
}));
|
|
109
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import dotenv from "dotenv";
|
|
2
2
|
import { createAuthenik8 } from "authenik8-core";
|
|
3
3
|
import { createApp } from "../app";
|
|
4
|
+
import { createRedisClient } from "./config/redis";
|
|
4
5
|
import { agentIdentityConfig, authJwkConfig, requiredPort, requiredSecret } from "../utils/security";
|
|
5
6
|
|
|
6
7
|
dotenv.config();
|
|
@@ -10,6 +11,7 @@ async function start() {
|
|
|
10
11
|
jwt: authJwkConfig(),
|
|
11
12
|
refreshSecret: requiredSecret("REFRESH_SECRET"),
|
|
12
13
|
agent: agentIdentityConfig(),
|
|
14
|
+
redis: await createRedisClient(),
|
|
13
15
|
});
|
|
14
16
|
|
|
15
17
|
const app = createApp(auth);
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
NODE_ENV=development
|
|
2
2
|
PORT=3000
|
|
3
3
|
WEB_ORIGIN=http://localhost:5173
|
|
4
|
-
DATABASE_URL=postgresql://postgres:postgres@localhost:
|
|
5
|
-
REDIS_URL=
|
|
4
|
+
DATABASE_URL=postgresql://postgres:postgres@localhost:55432/authenik8?schema=public
|
|
5
|
+
REDIS_URL=memory://
|
|
6
|
+
AUTHENIK8_LOCAL_DATABASE=embedded
|
|
6
7
|
AUTHENIK8_SIGNING_JWKS=
|
|
7
8
|
AUTHENIK8_ACTIVE_KID=
|
|
8
9
|
AUTHENIK8_ISSUER=http://localhost:3000
|
|
@@ -15,7 +15,8 @@ This document is the testable contract for the `fullstack` preset.
|
|
|
15
15
|
|
|
16
16
|
| Command | Guarantee |
|
|
17
17
|
| --- | --- |
|
|
18
|
-
| `npm run dev` |
|
|
18
|
+
| `npm run dev` | Starts project-local PostgreSQL, prepares data, then starts API and web together. |
|
|
19
|
+
| `npm run setup` | Starts project-local PostgreSQL for the command, applies migrations, and creates idempotent seed data. |
|
|
19
20
|
| `npm run build` | Produces the API and frontend production bundles. |
|
|
20
21
|
| `npm start` | Starts Express, which serves `/api` and the built SPA. |
|
|
21
22
|
| `npm run db:migrate` | Applies the Prisma PostgreSQL development migration. |
|
|
@@ -26,8 +27,11 @@ This document is the testable contract for the `fullstack` preset.
|
|
|
26
27
|
|
|
27
28
|
- Vite: `http://localhost:5173`, proxying `/api` to Express.
|
|
28
29
|
- Express: `http://localhost:3000`.
|
|
29
|
-
- PostgreSQL:
|
|
30
|
-
- Redis:
|
|
30
|
+
- PostgreSQL: project-local PGlite, bound to loopback only while local commands run.
|
|
31
|
+
- Redis: an in-process Redis-compatible store for local development.
|
|
32
|
+
- Docker Compose is optional and explicit through `npm run docker:up`.
|
|
33
|
+
- Set `AUTHENIK8_LOCAL_DATABASE=external` to use `DATABASE_URL` directly.
|
|
34
|
+
- Production requires external PostgreSQL and Redis services.
|
|
31
35
|
- Production uses one origin. Express serves `apps/web/dist` after `npm run build`.
|
|
32
36
|
|
|
33
37
|
## Environment contract
|
|
@@ -8,18 +8,56 @@ agent access without exposing an unsafe public token-minting route.
|
|
|
8
8
|
|
|
9
9
|
## Local development
|
|
10
10
|
|
|
11
|
-
Requirements: Node.js 20.19+, 22.12+, or 24
|
|
11
|
+
Requirements: Node.js 20.19+, 22.12+, or 24+ and npm. Docker is optional.
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
|
-
docker compose up -d
|
|
15
|
-
npm run db:migrate
|
|
16
|
-
npm run db:seed
|
|
17
14
|
npm run dev
|
|
18
15
|
```
|
|
19
16
|
|
|
20
|
-
|
|
17
|
+
That one command starts project-local PostgreSQL, applies the shipped migration,
|
|
18
|
+
creates the seed administrator, and starts every development watcher.
|
|
19
|
+
Redis-compatible auth state runs inside the API process during local
|
|
20
|
+
development. Setup is idempotent, so subsequent runs reuse the same database. Open
|
|
21
|
+
`http://localhost:5173`. The API runs on `http://localhost:3000/api`.
|
|
21
22
|
|
|
22
|
-
|
|
23
|
+
Run setup without starting the watchers when needed:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm run setup
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
The embedded PostgreSQL server listens only on loopback while a local command is
|
|
30
|
+
running and stores data under `.authenik8/`. No global PostgreSQL, Redis, or
|
|
31
|
+
Docker installation is required. If you provide your own PostgreSQL, set
|
|
32
|
+
`AUTHENIK8_LOCAL_DATABASE=external` and update `DATABASE_URL`.
|
|
33
|
+
|
|
34
|
+
Docker Compose remains available as an explicit option:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npm run docker:up
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
When using the containers, set `AUTHENIK8_LOCAL_DATABASE=external` and
|
|
41
|
+
`REDIS_URL=redis://localhost:56379`. Production must always use external
|
|
42
|
+
PostgreSQL and a real `redis://` or `rediss://` service. The API rejects the
|
|
43
|
+
in-process Redis setting in production.
|
|
44
|
+
|
|
45
|
+
The generated `.env` contains development values; replace every secret before deploying.
|
|
46
|
+
|
|
47
|
+
Validate the workspace auth configuration and selected Redis mode at any time:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
npx create-authenik8-app@latest doctor
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
`authenik8.json` records the generated architecture and Authenik8 engine version. It contains no secrets and should be committed with the workspace.
|
|
54
|
+
|
|
55
|
+
After committing `package-lock.json`, run `npx create-authenik8-app@latest add ci-github` to add the pinned Doctor and upgrade-policy workflow. Preview it first with `--dry-run`.
|
|
56
|
+
|
|
57
|
+
With an unchanged generated `.env`, sign in with `admin@example.com` and
|
|
58
|
+
`ChangeMe123!`. These values come from `SEED_ADMIN_EMAIL` and
|
|
59
|
+
`SEED_ADMIN_PASSWORD`. Change the password immediately, including for local
|
|
60
|
+
environments shared by multiple people.
|
|
23
61
|
|
|
24
62
|
## Application map
|
|
25
63
|
|
|
@@ -62,4 +100,4 @@ Read [docs/PRODUCTION.md](docs/PRODUCTION.md) and [THREAT_MODEL.md](THREAT_MODEL
|
|
|
62
100
|
- `GET /api/health/ready` checks PostgreSQL and Redis.
|
|
63
101
|
- `GET /api/docs/openapi.json` returns the generated OpenAPI 3.1 contract.
|
|
64
102
|
- `npm test` covers ownership/admin policies, encrypted cookies, origin and CSRF defenses, and the browser storage rule.
|
|
65
|
-
- `npm run typecheck` checks each workspace.
|
|
103
|
+
- `npm run typecheck` builds internal packages, generates Prisma Client, and checks each workspace from a clean checkout.
|
|
@@ -11,16 +11,17 @@
|
|
|
11
11
|
"build": "tsc -p tsconfig.json",
|
|
12
12
|
"start": "node dist/server.js",
|
|
13
13
|
"test": "vitest run",
|
|
14
|
+
"pretypecheck": "prisma generate",
|
|
14
15
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
15
16
|
"prisma:generate": "prisma generate",
|
|
16
|
-
"prisma:migrate": "prisma migrate
|
|
17
|
+
"prisma:migrate": "prisma migrate deploy && prisma generate",
|
|
17
18
|
"prisma:seed": "prisma generate && prisma db seed",
|
|
18
19
|
"prisma:studio": "prisma studio"
|
|
19
20
|
},
|
|
20
21
|
"dependencies": {
|
|
21
22
|
"@authenik8/contracts": "0.1.0",
|
|
22
|
-
"@prisma/adapter-pg": "7.
|
|
23
|
-
"@prisma/client": "7.
|
|
23
|
+
"@prisma/adapter-pg": "7.9.0",
|
|
24
|
+
"@prisma/client": "7.9.0",
|
|
24
25
|
"authenik8-core": "2.0.3",
|
|
25
26
|
"bcryptjs": "^2.4.3",
|
|
26
27
|
"cookie-parser": "^1.4.7",
|
|
@@ -28,6 +29,7 @@
|
|
|
28
29
|
"dotenv": "^17.3.1",
|
|
29
30
|
"express": "^5.2.1",
|
|
30
31
|
"ioredis": "^5.10.0",
|
|
32
|
+
"ioredis-mock": "8.13.1",
|
|
31
33
|
"pino": "^9.7.0",
|
|
32
34
|
"pino-http": "^10.4.0",
|
|
33
35
|
"zod": "^4.4.3"
|
|
@@ -39,7 +41,7 @@
|
|
|
39
41
|
"@types/express": "^5.0.6",
|
|
40
42
|
"@types/node": "^25.0.0",
|
|
41
43
|
"@types/supertest": "^7.2.0",
|
|
42
|
-
"prisma": "7.
|
|
44
|
+
"prisma": "7.9.0",
|
|
43
45
|
"supertest": "^7.2.2",
|
|
44
46
|
"tsx": "^4.22.4",
|
|
45
47
|
"typescript": "^6.0.3",
|