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
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { csrfTokenSchema, registerSchema } from "@authenik8/contracts";
|
|
2
|
+
import type {
|
|
3
|
+
AdminUserUpdateInput,
|
|
4
|
+
AuditEvent,
|
|
5
|
+
AuthResponse,
|
|
6
|
+
ChangePasswordInput,
|
|
7
|
+
LinkedProvider,
|
|
8
|
+
LoginInput,
|
|
9
|
+
Page,
|
|
10
|
+
ProfileInput,
|
|
11
|
+
Project,
|
|
12
|
+
ProjectCreateInput,
|
|
13
|
+
ProjectUpdateInput,
|
|
14
|
+
RegisterInput,
|
|
15
|
+
Session,
|
|
16
|
+
User,
|
|
17
|
+
} from "@authenik8/contracts";
|
|
18
|
+
|
|
19
|
+
export class ApiError extends Error {
|
|
20
|
+
constructor(
|
|
21
|
+
public readonly status: number,
|
|
22
|
+
public readonly code: string,
|
|
23
|
+
message: string,
|
|
24
|
+
public readonly fields?: Record<string, string[]>,
|
|
25
|
+
) {
|
|
26
|
+
super(message);
|
|
27
|
+
this.name = "ApiError";
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
let accessToken: string | null = null;
|
|
32
|
+
let refreshRequest: Promise<boolean> | null = null;
|
|
33
|
+
let csrfToken: string | null = null;
|
|
34
|
+
let csrfRequest: Promise<string> | null = null;
|
|
35
|
+
|
|
36
|
+
export function setAccessToken(token: string | null): void {
|
|
37
|
+
accessToken = token;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function hasAccessToken(): boolean {
|
|
41
|
+
return accessToken !== null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function parseResponse<T>(response: Response): Promise<T> {
|
|
45
|
+
const body = await response.json().catch(() => ({}));
|
|
46
|
+
if (!response.ok) {
|
|
47
|
+
const error = body?.error;
|
|
48
|
+
throw new ApiError(
|
|
49
|
+
response.status,
|
|
50
|
+
error?.code ?? "REQUEST_FAILED",
|
|
51
|
+
error?.message ?? "The request could not be completed",
|
|
52
|
+
error?.fields,
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
return body as T;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function getCsrfToken(force = false): Promise<string> {
|
|
59
|
+
if (force) {
|
|
60
|
+
csrfToken = null;
|
|
61
|
+
csrfRequest = null;
|
|
62
|
+
}
|
|
63
|
+
if (csrfToken) return csrfToken;
|
|
64
|
+
|
|
65
|
+
if (!csrfRequest) {
|
|
66
|
+
csrfRequest = fetch("/api/auth/csrf", {
|
|
67
|
+
credentials: "include",
|
|
68
|
+
headers: { Accept: "application/json" },
|
|
69
|
+
})
|
|
70
|
+
.then((response) => parseResponse<{ csrfToken: unknown }>(response))
|
|
71
|
+
.then((body) => {
|
|
72
|
+
const token = csrfTokenSchema.parse(body.csrfToken);
|
|
73
|
+
csrfToken = token;
|
|
74
|
+
return token;
|
|
75
|
+
})
|
|
76
|
+
.finally(() => {
|
|
77
|
+
csrfRequest = null;
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return await csrfRequest;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function isUnsafeMethod(method: string): boolean {
|
|
85
|
+
return !["GET", "HEAD", "OPTIONS"].includes(method);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function isCsrfRejection(response: Response): Promise<boolean> {
|
|
89
|
+
if (response.status !== 403) return false;
|
|
90
|
+
const body = await response.clone().json().catch(() => null);
|
|
91
|
+
return body?.error?.code === "CSRF_REJECTED";
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async function requestTokenRefresh(forceCsrf = false): Promise<Response> {
|
|
95
|
+
const token = await getCsrfToken(forceCsrf);
|
|
96
|
+
const response = await fetch("/api/auth/refresh", {
|
|
97
|
+
method: "POST",
|
|
98
|
+
credentials: "include",
|
|
99
|
+
headers: { Accept: "application/json", "X-CSRF-Token": token },
|
|
100
|
+
});
|
|
101
|
+
if (!forceCsrf && await isCsrfRejection(response)) return requestTokenRefresh(true);
|
|
102
|
+
return response;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async function refreshAccessToken(): Promise<boolean> {
|
|
106
|
+
if (!refreshRequest) {
|
|
107
|
+
refreshRequest = requestTokenRefresh()
|
|
108
|
+
.then(async (response) => {
|
|
109
|
+
if (!response.ok) return false;
|
|
110
|
+
const result = await response.json() as AuthResponse;
|
|
111
|
+
accessToken = result.accessToken;
|
|
112
|
+
return true;
|
|
113
|
+
})
|
|
114
|
+
.catch(() => false)
|
|
115
|
+
.finally(() => {
|
|
116
|
+
refreshRequest = null;
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
return refreshRequest;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export async function apiFetch<T>(path: string, init: RequestInit = {}, retry = true): Promise<T> {
|
|
123
|
+
const headers = new Headers(init.headers);
|
|
124
|
+
const method = (init.method ?? "GET").toUpperCase();
|
|
125
|
+
headers.set("Accept", "application/json");
|
|
126
|
+
if (init.body && !(init.body instanceof FormData)) headers.set("Content-Type", "application/json");
|
|
127
|
+
if (accessToken) headers.set("Authorization", `Bearer ${accessToken}`);
|
|
128
|
+
if (isUnsafeMethod(method)) headers.set("X-CSRF-Token", await getCsrfToken());
|
|
129
|
+
|
|
130
|
+
const response = await fetch(`/api${path}`, { ...init, headers, credentials: "include" });
|
|
131
|
+
if (retry && isUnsafeMethod(method) && await isCsrfRejection(response)) {
|
|
132
|
+
await getCsrfToken(true);
|
|
133
|
+
return apiFetch<T>(path, init, false);
|
|
134
|
+
}
|
|
135
|
+
if (response.status === 401 && retry && path !== "/auth/refresh") {
|
|
136
|
+
if (await refreshAccessToken()) return apiFetch<T>(path, init, false);
|
|
137
|
+
accessToken = null;
|
|
138
|
+
}
|
|
139
|
+
return parseResponse<T>(response);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const json = (value: unknown) => JSON.stringify(value);
|
|
143
|
+
|
|
144
|
+
export const authApi = {
|
|
145
|
+
register: (input: RegisterInput) => apiFetch<{ message: string; devVerificationToken?: string }>("/auth/register", {
|
|
146
|
+
method: "POST",
|
|
147
|
+
body: json(registerSchema.parse(input)),
|
|
148
|
+
}),
|
|
149
|
+
login: async (input: LoginInput) => {
|
|
150
|
+
const result = await apiFetch<AuthResponse>("/auth/login", { method: "POST", body: json(input) });
|
|
151
|
+
setAccessToken(result.accessToken);
|
|
152
|
+
return result;
|
|
153
|
+
},
|
|
154
|
+
restore: async () => {
|
|
155
|
+
const ok = await refreshAccessToken();
|
|
156
|
+
if (!ok) return null;
|
|
157
|
+
return apiFetch<{ user: User }>("/auth/me", {}, false);
|
|
158
|
+
},
|
|
159
|
+
logout: async () => {
|
|
160
|
+
try {
|
|
161
|
+
await apiFetch<{ message: string }>("/auth/logout", { method: "POST" }, false);
|
|
162
|
+
} finally {
|
|
163
|
+
setAccessToken(null);
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
exchangeOAuth: async (code: string) => {
|
|
167
|
+
const result = await apiFetch<AuthResponse>("/auth/oauth/exchange", { method: "POST", body: json({ code }) });
|
|
168
|
+
setAccessToken(result.accessToken);
|
|
169
|
+
return result;
|
|
170
|
+
},
|
|
171
|
+
forgotPassword: (email: string) => apiFetch<{ message: string; devResetToken?: string }>("/auth/forgot-password", { method: "POST", body: json({ email }) }),
|
|
172
|
+
resetPassword: (token: string, password: string) => apiFetch<{ message: string }>("/auth/reset-password", { method: "POST", body: json({ token, password }) }),
|
|
173
|
+
verifyEmail: (token: string) => apiFetch<{ message: string }>("/auth/verify-email", { method: "POST", body: json({ token }) }),
|
|
174
|
+
resendVerification: () => apiFetch<{ message: string; devVerificationToken?: string }>("/auth/resend-verification", { method: "POST" }),
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
export const accountApi = {
|
|
178
|
+
updateProfile: (input: ProfileInput) => apiFetch<{ user: User }>("/account/profile", { method: "PATCH", body: json(input) }),
|
|
179
|
+
changePassword: (input: ChangePasswordInput) => apiFetch<{ message: string }>("/account/password", { method: "PUT", body: json(input) }),
|
|
180
|
+
sessions: () => apiFetch<{ sessions: Session[] }>("/account/sessions"),
|
|
181
|
+
revokeSession: (id: string) => apiFetch<{ message: string }>(`/account/sessions/${id}`, { method: "DELETE" }),
|
|
182
|
+
providers: () => apiFetch<{ providers: LinkedProvider[] }>("/account/providers"),
|
|
183
|
+
startProviderLink: (provider: "google" | "github") => apiFetch<{ url: string }>(`/auth/oauth/${provider}/link-intent`, { method: "POST" }),
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
export const projectApi = {
|
|
187
|
+
list: () => apiFetch<{ projects: Project[] }>("/projects"),
|
|
188
|
+
get: (id: string) => apiFetch<{ project: Project }>(`/projects/${id}`),
|
|
189
|
+
create: (input: ProjectCreateInput) => apiFetch<{ project: Project }>("/projects", { method: "POST", body: json(input) }),
|
|
190
|
+
update: (id: string, input: ProjectUpdateInput) => apiFetch<{ project: Project }>(`/projects/${id}`, { method: "PATCH", body: json(input) }),
|
|
191
|
+
remove: (id: string) => apiFetch<void>(`/projects/${id}`, { method: "DELETE" }),
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
export const adminApi = {
|
|
195
|
+
users: (page = 1) => apiFetch<Page<User>>(`/admin/users?page=${page}`),
|
|
196
|
+
updateUser: (id: string, input: AdminUserUpdateInput) => apiFetch<{ user: User }>(`/admin/users/${id}`, { method: "PATCH", body: json(input) }),
|
|
197
|
+
revokeSessions: (id: string) => apiFetch<{ message: string }>(`/admin/users/${id}/sessions`, { method: "DELETE" }),
|
|
198
|
+
audit: () => apiFetch<{ events: AuditEvent[] }>("/admin/audit"),
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
export const healthApi = {
|
|
202
|
+
status: () => apiFetch<{ status: string; database: string; redis: string }>("/health/ready"),
|
|
203
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"rootDir": "src",
|
|
7
|
+
"outDir": "dist",
|
|
8
|
+
"declaration": true,
|
|
9
|
+
"strict": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"lib": ["ES2022", "DOM"]
|
|
12
|
+
},
|
|
13
|
+
"include": ["src"]
|
|
14
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@authenik8/contracts",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"files": ["dist"],
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsc -p tsconfig.json",
|
|
11
|
+
"dev": "tsc -p tsconfig.json --watch --preserveWatchOutput",
|
|
12
|
+
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"zod": "^4.4.3"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"typescript": "^6.0.3"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
const normalizedSingleLine = (minimum: number, maximum: number) => z.string()
|
|
4
|
+
.transform((value) => value.normalize("NFKC").trim().replace(/\s+/g, " "))
|
|
5
|
+
.pipe(z.string().min(minimum).max(maximum))
|
|
6
|
+
.refine((value) => !/[\u0000-\u001f\u007f]/.test(value), "Remove unsupported control characters");
|
|
7
|
+
|
|
8
|
+
const normalizedDescription = z.string()
|
|
9
|
+
.transform((value) => value.normalize("NFKC").replace(/\r\n?/g, "\n").trim())
|
|
10
|
+
.pipe(z.string().max(2000))
|
|
11
|
+
.refine((value) => !/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/.test(value), "Remove unsupported control characters");
|
|
12
|
+
|
|
13
|
+
export const roleSchema = z.enum(["USER", "ADMIN"]);
|
|
14
|
+
export const userStatusSchema = z.enum(["ACTIVE", "SUSPENDED"]);
|
|
15
|
+
export const projectStatusSchema = z.enum(["DRAFT", "ACTIVE", "ARCHIVED"]);
|
|
16
|
+
export const oauthProviderSchema = z.enum(["google", "github"]);
|
|
17
|
+
export const identifierSchema = z.string().trim().uuid("A valid identifier is required");
|
|
18
|
+
export const pageSchema = z.coerce.number().int().min(1).max(10_000).default(1);
|
|
19
|
+
export const csrfTokenSchema = z.string()
|
|
20
|
+
.regex(/^[A-Za-z0-9_-]{43}\.[A-Za-z0-9_-]{43}$/, "A valid CSRF token is required");
|
|
21
|
+
|
|
22
|
+
const emailSchema = z.string()
|
|
23
|
+
.trim()
|
|
24
|
+
.toLowerCase()
|
|
25
|
+
.email("Enter a valid email address")
|
|
26
|
+
.max(254);
|
|
27
|
+
|
|
28
|
+
const passwordSchema = z.string()
|
|
29
|
+
.min(10, "Use at least 10 characters")
|
|
30
|
+
.max(128)
|
|
31
|
+
.regex(/[a-z]/, "Include a lowercase letter")
|
|
32
|
+
.regex(/[A-Z]/, "Include an uppercase letter")
|
|
33
|
+
.regex(/[0-9]/, "Include a number")
|
|
34
|
+
.refine((value) => !/[\u0000-\u001f\u007f]/.test(value), "Password contains unsupported control characters");
|
|
35
|
+
|
|
36
|
+
export const loginSchema = z.strictObject({
|
|
37
|
+
email: emailSchema,
|
|
38
|
+
password: z.string().min(1).max(128),
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
export const registerSchema = z.strictObject({
|
|
42
|
+
name: normalizedSingleLine(2, 80),
|
|
43
|
+
email: emailSchema,
|
|
44
|
+
password: passwordSchema,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
export const forgotPasswordSchema = z.strictObject({ email: emailSchema });
|
|
48
|
+
|
|
49
|
+
export const resetPasswordSchema = z.strictObject({
|
|
50
|
+
token: z.string().trim().min(32).max(256),
|
|
51
|
+
password: passwordSchema,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
export const verificationSchema = z.strictObject({ token: z.string().trim().min(32).max(256) });
|
|
55
|
+
export const oauthExchangeSchema = z.strictObject({ code: z.string().trim().min(32).max(256) });
|
|
56
|
+
export const oauthLinkQuerySchema = z.strictObject({ ticket: z.string().trim().min(32).max(256) });
|
|
57
|
+
|
|
58
|
+
export const profileSchema = z.strictObject({
|
|
59
|
+
name: normalizedSingleLine(2, 80),
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
export const changePasswordSchema = z.strictObject({
|
|
63
|
+
currentPassword: z.string().min(1).max(128),
|
|
64
|
+
newPassword: passwordSchema,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
export const projectCreateSchema = z.strictObject({
|
|
68
|
+
name: normalizedSingleLine(2, 120),
|
|
69
|
+
description: normalizedDescription.default(""),
|
|
70
|
+
status: projectStatusSchema.default("DRAFT"),
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
export const projectUpdateSchema = projectCreateSchema.partial().refine(
|
|
74
|
+
(value) => Object.keys(value).length > 0,
|
|
75
|
+
"Provide at least one field",
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
export const adminUserUpdateSchema = z.strictObject({
|
|
79
|
+
role: roleSchema.optional(),
|
|
80
|
+
status: userStatusSchema.optional(),
|
|
81
|
+
}).refine((value) => value.role !== undefined || value.status !== undefined, "Provide a role or status");
|
|
82
|
+
|
|
83
|
+
export type Role = z.infer<typeof roleSchema>;
|
|
84
|
+
export type UserStatus = z.infer<typeof userStatusSchema>;
|
|
85
|
+
export type ProjectStatus = z.infer<typeof projectStatusSchema>;
|
|
86
|
+
export type OAuthProvider = z.infer<typeof oauthProviderSchema>;
|
|
87
|
+
export type CsrfToken = z.infer<typeof csrfTokenSchema>;
|
|
88
|
+
export type LoginInput = z.infer<typeof loginSchema>;
|
|
89
|
+
export type RegisterInput = z.infer<typeof registerSchema>;
|
|
90
|
+
export type ProfileInput = z.infer<typeof profileSchema>;
|
|
91
|
+
export type ChangePasswordInput = z.infer<typeof changePasswordSchema>;
|
|
92
|
+
export type ProjectCreateInput = z.infer<typeof projectCreateSchema>;
|
|
93
|
+
export type ProjectUpdateInput = z.infer<typeof projectUpdateSchema>;
|
|
94
|
+
export type AdminUserUpdateInput = z.infer<typeof adminUserUpdateSchema>;
|
|
95
|
+
|
|
96
|
+
export type User = {
|
|
97
|
+
id: string;
|
|
98
|
+
email: string;
|
|
99
|
+
name: string;
|
|
100
|
+
role: Role;
|
|
101
|
+
status: UserStatus;
|
|
102
|
+
verified: boolean;
|
|
103
|
+
createdAt: string;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
export type Project = {
|
|
107
|
+
id: string;
|
|
108
|
+
name: string;
|
|
109
|
+
description: string;
|
|
110
|
+
status: ProjectStatus;
|
|
111
|
+
ownerId: string;
|
|
112
|
+
createdAt: string;
|
|
113
|
+
updatedAt: string;
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
export type Session = {
|
|
117
|
+
id: string;
|
|
118
|
+
userAgent: string;
|
|
119
|
+
ipAddress: string;
|
|
120
|
+
createdAt: string;
|
|
121
|
+
lastUsedAt: string;
|
|
122
|
+
expiresAt: string;
|
|
123
|
+
current: boolean;
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
export type LinkedProvider = {
|
|
127
|
+
provider: OAuthProvider;
|
|
128
|
+
providerEmail: string;
|
|
129
|
+
linkedAt: string;
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
export type AuditEvent = {
|
|
133
|
+
id: string;
|
|
134
|
+
action: string;
|
|
135
|
+
actorEmail: string | null;
|
|
136
|
+
targetType: string;
|
|
137
|
+
targetId: string | null;
|
|
138
|
+
createdAt: string;
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
export type AuthResponse = { accessToken: string; user: User };
|
|
142
|
+
export type Page<T> = { items: T[]; total: number; page: number; pageSize: number };
|
|
143
|
+
export type ApiErrorBody = {
|
|
144
|
+
error: { code: string; message: string; fields?: Record<string, string[]> };
|
|
145
|
+
requestId?: string;
|
|
146
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@authenik8/ui",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"files": ["dist"],
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsc -p tsconfig.json",
|
|
11
|
+
"dev": "tsc -p tsconfig.json --watch --preserveWatchOutput",
|
|
12
|
+
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
13
|
+
},
|
|
14
|
+
"peerDependencies": {
|
|
15
|
+
"react": "^19.2.0"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"@types/react": "^19.2.0",
|
|
19
|
+
"react": "^19.2.0",
|
|
20
|
+
"typescript": "^6.0.3"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { ButtonHTMLAttributes, InputHTMLAttributes, ReactNode } from "react";
|
|
2
|
+
import { forwardRef } from "react";
|
|
3
|
+
|
|
4
|
+
export function Button({ className = "", variant = "primary", ...props }: ButtonHTMLAttributes<HTMLButtonElement> & { variant?: "primary" | "secondary" | "danger" | "ghost" }) {
|
|
5
|
+
return <button className={`button button-${variant} ${className}`.trim()} {...props} />;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const Input = forwardRef<HTMLInputElement, InputHTMLAttributes<HTMLInputElement>>(function Input({ className = "", ...props }, ref) {
|
|
9
|
+
return <input ref={ref} className={`input ${className}`.trim()} {...props} />;
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
export function Card({ children, className = "" }: { children: ReactNode; className?: string }) {
|
|
13
|
+
return <section className={`card ${className}`.trim()}>{children}</section>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function Badge({ children, tone = "neutral" }: { children: ReactNode; tone?: "neutral" | "success" | "warning" | "danger" | "accent" }) {
|
|
17
|
+
return <span className={`badge badge-${tone}`}>{children}</span>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function Spinner({ label = "Loading" }: { label?: string }) {
|
|
21
|
+
return <span className="spinner" role="status" aria-label={label} />;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function EmptyState({ icon, title, children, action }: { icon: ReactNode; title: string; children: ReactNode; action?: ReactNode }) {
|
|
25
|
+
return (
|
|
26
|
+
<div className="empty-state">
|
|
27
|
+
<div className="empty-icon" aria-hidden="true">{icon}</div>
|
|
28
|
+
<h2>{title}</h2>
|
|
29
|
+
<p>{children}</p>
|
|
30
|
+
{action}
|
|
31
|
+
</div>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"rootDir": "src",
|
|
7
|
+
"outDir": "dist",
|
|
8
|
+
"declaration": true,
|
|
9
|
+
"strict": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"jsx": "react-jsx"
|
|
12
|
+
},
|
|
13
|
+
"include": ["src"]
|
|
14
|
+
}
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/authenik8?schema=public"
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
AUTHENIK8_SIGNING_JWKS=
|
|
3
|
+
AUTHENIK8_ACTIVE_KID=
|
|
4
|
+
AUTHENIK8_ISSUER="http://localhost:3000"
|
|
5
|
+
AUTHENIK8_AUDIENCE="authenik8-app-api"
|
|
6
|
+
AUTHENIK8_AGENTS={}
|
|
7
|
+
REFRESH_SECRET="replace-with-at-least-32-random-characters"
|
|
4
8
|
REDIS_HOST="127.0.0.1"
|
|
5
9
|
REDIS_PORT="6379"
|
|
6
10
|
GOOGLE_CLIENT_ID="change-me-google-client-id"
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import "dotenv/config";
|
|
2
|
+
import { PrismaPg } from "@prisma/adapter-pg";
|
|
3
|
+
import { PrismaClient } from "@prisma/client";
|
|
4
|
+
|
|
5
|
+
const globalForPrisma = globalThis as unknown as {
|
|
6
|
+
prisma: PrismaClient | undefined;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const databaseUrl = process.env.DATABASE_URL;
|
|
10
|
+
if (!databaseUrl) throw new Error("DATABASE_URL is required");
|
|
11
|
+
|
|
12
|
+
const adapter = new PrismaPg({ connectionString: databaseUrl });
|
|
13
|
+
export const prisma = globalForPrisma.prisma ?? new PrismaClient({ adapter });
|
|
14
|
+
|
|
15
|
+
if (process.env.NODE_ENV !== "production") {
|
|
16
|
+
globalForPrisma.prisma = prisma;
|
|
17
|
+
}
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
DATABASE_URL="file:./dev.db"
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
AUTHENIK8_SIGNING_JWKS=
|
|
3
|
+
AUTHENIK8_ACTIVE_KID=
|
|
4
|
+
AUTHENIK8_ISSUER="http://localhost:3000"
|
|
5
|
+
AUTHENIK8_AUDIENCE="authenik8-app-api"
|
|
6
|
+
AUTHENIK8_AGENTS={}
|
|
7
|
+
REFRESH_SECRET="replace-with-at-least-32-random-characters"
|
|
4
8
|
REDIS_HOST="127.0.0.1"
|
|
5
9
|
REDIS_PORT="6379"
|
|
6
10
|
GOOGLE_CLIENT_ID="change-me-google-client-id"
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import "dotenv/config";
|
|
2
|
+
import { PrismaBetterSqlite3 } from "@prisma/adapter-better-sqlite3";
|
|
3
|
+
import { PrismaClient } from "@prisma/client";
|
|
4
|
+
|
|
5
|
+
const globalForPrisma = globalThis as unknown as {
|
|
6
|
+
prisma: PrismaClient | undefined;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const adapter = new PrismaBetterSqlite3({
|
|
10
|
+
url: process.env.DATABASE_URL ?? "file:./dev.db",
|
|
11
|
+
});
|
|
12
|
+
export const prisma = globalForPrisma.prisma ?? new PrismaClient({ adapter });
|
|
13
|
+
|
|
14
|
+
if (process.env.NODE_ENV !== "production") {
|
|
15
|
+
globalForPrisma.prisma = prisma;
|
|
16
|
+
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"templated-routes.test.d.ts","sourceRoot":"","sources":["../../../src/tests/templated-routes.test.ts"],"names":[],"mappings":""}
|