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.
Files changed (227) hide show
  1. package/README.md +34 -516
  2. package/dist/src/bin/cli.d.ts +3 -0
  3. package/dist/src/bin/cli.d.ts.map +1 -0
  4. package/dist/src/bin/cli.js +112 -0
  5. package/dist/src/bin/cli.js.map +1 -0
  6. package/dist/src/bin/index.js +108 -23
  7. package/dist/src/bin/index.js.map +1 -1
  8. package/dist/src/commands/add/apply.d.ts +6 -0
  9. package/dist/src/commands/add/apply.d.ts.map +1 -0
  10. package/dist/src/commands/add/apply.js +123 -0
  11. package/dist/src/commands/add/apply.js.map +1 -0
  12. package/dist/src/commands/add/ci.d.ts +6 -0
  13. package/dist/src/commands/add/ci.d.ts.map +1 -0
  14. package/dist/src/commands/add/ci.js +108 -0
  15. package/dist/src/commands/add/ci.js.map +1 -0
  16. package/dist/src/commands/add/context.d.ts +5 -0
  17. package/dist/src/commands/add/context.d.ts.map +1 -0
  18. package/dist/src/commands/add/context.js +19 -0
  19. package/dist/src/commands/add/context.js.map +1 -0
  20. package/dist/src/commands/add/diff.d.ts +4 -0
  21. package/dist/src/commands/add/diff.d.ts.map +1 -0
  22. package/dist/src/commands/add/diff.js +114 -0
  23. package/dist/src/commands/add/diff.js.map +1 -0
  24. package/dist/src/commands/add/index.d.ts +6 -0
  25. package/dist/src/commands/add/index.d.ts.map +1 -0
  26. package/dist/src/commands/add/index.js +61 -0
  27. package/dist/src/commands/add/index.js.map +1 -0
  28. package/dist/src/commands/add/output.d.ts +5 -0
  29. package/dist/src/commands/add/output.d.ts.map +1 -0
  30. package/dist/src/commands/add/output.js +58 -0
  31. package/dist/src/commands/add/output.js.map +1 -0
  32. package/dist/src/commands/add/plan.d.ts +6 -0
  33. package/dist/src/commands/add/plan.d.ts.map +1 -0
  34. package/dist/src/commands/add/plan.js +93 -0
  35. package/dist/src/commands/add/plan.js.map +1 -0
  36. package/dist/src/commands/add/registry.d.ts +4 -0
  37. package/dist/src/commands/add/registry.d.ts.map +1 -0
  38. package/dist/src/commands/add/registry.js +37 -0
  39. package/dist/src/commands/add/registry.js.map +1 -0
  40. package/dist/src/commands/add/types.d.ts +37 -0
  41. package/dist/src/commands/add/types.d.ts.map +1 -0
  42. package/dist/src/commands/add/types.js +2 -0
  43. package/dist/src/commands/add/types.js.map +1 -0
  44. package/dist/src/commands/doctor/checks.d.ts +3 -0
  45. package/dist/src/commands/doctor/checks.d.ts.map +1 -0
  46. package/dist/src/commands/doctor/checks.js +353 -0
  47. package/dist/src/commands/doctor/checks.js.map +1 -0
  48. package/dist/src/commands/doctor/context.d.ts +5 -0
  49. package/dist/src/commands/doctor/context.d.ts.map +1 -0
  50. package/dist/src/commands/doctor/context.js +134 -0
  51. package/dist/src/commands/doctor/context.js.map +1 -0
  52. package/dist/src/commands/doctor/index.d.ts +9 -0
  53. package/dist/src/commands/doctor/index.d.ts.map +1 -0
  54. package/dist/src/commands/doctor/index.js +80 -0
  55. package/dist/src/commands/doctor/index.js.map +1 -0
  56. package/dist/src/commands/doctor/output.d.ts +4 -0
  57. package/dist/src/commands/doctor/output.d.ts.map +1 -0
  58. package/dist/src/commands/doctor/output.js +53 -0
  59. package/dist/src/commands/doctor/output.js.map +1 -0
  60. package/dist/src/commands/doctor/postGeneration.d.ts +7 -0
  61. package/dist/src/commands/doctor/postGeneration.d.ts.map +1 -0
  62. package/dist/src/commands/doctor/postGeneration.js +22 -0
  63. package/dist/src/commands/doctor/postGeneration.js.map +1 -0
  64. package/dist/src/commands/doctor/services.d.ts +5 -0
  65. package/dist/src/commands/doctor/services.d.ts.map +1 -0
  66. package/dist/src/commands/doctor/services.js +80 -0
  67. package/dist/src/commands/doctor/services.js.map +1 -0
  68. package/dist/src/commands/doctor/types.d.ts +69 -0
  69. package/dist/src/commands/doctor/types.d.ts.map +1 -0
  70. package/dist/src/commands/doctor/types.js +2 -0
  71. package/dist/src/commands/doctor/types.js.map +1 -0
  72. package/dist/src/commands/upgrade/context.d.ts +5 -0
  73. package/dist/src/commands/upgrade/context.d.ts.map +1 -0
  74. package/dist/src/commands/upgrade/context.js +57 -0
  75. package/dist/src/commands/upgrade/context.js.map +1 -0
  76. package/dist/src/commands/upgrade/index.d.ts +7 -0
  77. package/dist/src/commands/upgrade/index.d.ts.map +1 -0
  78. package/dist/src/commands/upgrade/index.js +35 -0
  79. package/dist/src/commands/upgrade/index.js.map +1 -0
  80. package/dist/src/commands/upgrade/migrations.d.ts +4 -0
  81. package/dist/src/commands/upgrade/migrations.d.ts.map +1 -0
  82. package/dist/src/commands/upgrade/migrations.js +22 -0
  83. package/dist/src/commands/upgrade/migrations.js.map +1 -0
  84. package/dist/src/commands/upgrade/output.d.ts +4 -0
  85. package/dist/src/commands/upgrade/output.d.ts.map +1 -0
  86. package/dist/src/commands/upgrade/output.js +52 -0
  87. package/dist/src/commands/upgrade/output.js.map +1 -0
  88. package/dist/src/commands/upgrade/plan.d.ts +3 -0
  89. package/dist/src/commands/upgrade/plan.d.ts.map +1 -0
  90. package/dist/src/commands/upgrade/plan.js +119 -0
  91. package/dist/src/commands/upgrade/plan.js.map +1 -0
  92. package/dist/src/commands/upgrade/types.d.ts +48 -0
  93. package/dist/src/commands/upgrade/types.d.ts.map +1 -0
  94. package/dist/src/commands/upgrade/types.js +2 -0
  95. package/dist/src/commands/upgrade/types.js.map +1 -0
  96. package/dist/src/lib/args.d.ts +21 -0
  97. package/dist/src/lib/args.d.ts.map +1 -1
  98. package/dist/src/lib/args.js +119 -1
  99. package/dist/src/lib/args.js.map +1 -1
  100. package/dist/src/lib/constants.d.ts +1 -1
  101. package/dist/src/lib/constants.d.ts.map +1 -1
  102. package/dist/src/lib/constants.js +3 -1
  103. package/dist/src/lib/constants.js.map +1 -1
  104. package/dist/src/lib/expressOAuth.d.ts +10 -0
  105. package/dist/src/lib/expressOAuth.d.ts.map +1 -0
  106. package/dist/src/lib/expressOAuth.js +164 -0
  107. package/dist/src/lib/expressOAuth.js.map +1 -0
  108. package/dist/src/lib/interruption.d.ts +14 -0
  109. package/dist/src/lib/interruption.d.ts.map +1 -0
  110. package/dist/src/lib/interruption.js +16 -0
  111. package/dist/src/lib/interruption.js.map +1 -0
  112. package/dist/src/lib/nonInteractive.d.ts +8 -0
  113. package/dist/src/lib/nonInteractive.d.ts.map +1 -0
  114. package/dist/src/lib/nonInteractive.js +111 -0
  115. package/dist/src/lib/nonInteractive.js.map +1 -0
  116. package/dist/src/lib/oauth.d.ts +9 -0
  117. package/dist/src/lib/oauth.d.ts.map +1 -0
  118. package/dist/src/lib/oauth.js +62 -0
  119. package/dist/src/lib/oauth.js.map +1 -0
  120. package/dist/src/lib/onboarding.d.ts +8 -0
  121. package/dist/src/lib/onboarding.d.ts.map +1 -0
  122. package/dist/src/lib/onboarding.js +35 -0
  123. package/dist/src/lib/onboarding.js.map +1 -0
  124. package/dist/src/lib/packageManagerCommands.d.ts +11 -0
  125. package/dist/src/lib/packageManagerCommands.d.ts.map +1 -0
  126. package/dist/src/lib/packageManagerCommands.js +34 -0
  127. package/dist/src/lib/packageManagerCommands.js.map +1 -0
  128. package/dist/src/lib/process.d.ts.map +1 -1
  129. package/dist/src/lib/process.js +9 -0
  130. package/dist/src/lib/process.js.map +1 -1
  131. package/dist/src/lib/projectManifest.d.ts +67 -0
  132. package/dist/src/lib/projectManifest.d.ts.map +1 -0
  133. package/dist/src/lib/projectManifest.js +134 -0
  134. package/dist/src/lib/projectManifest.js.map +1 -0
  135. package/dist/src/lib/release.d.ts +9 -0
  136. package/dist/src/lib/release.d.ts.map +1 -0
  137. package/dist/src/lib/release.js +48 -0
  138. package/dist/src/lib/release.js.map +1 -0
  139. package/dist/src/lib/rootCommand.d.ts +15 -0
  140. package/dist/src/lib/rootCommand.d.ts.map +1 -0
  141. package/dist/src/lib/rootCommand.js +17 -0
  142. package/dist/src/lib/rootCommand.js.map +1 -0
  143. package/dist/src/lib/schemas.d.ts +15 -6
  144. package/dist/src/lib/schemas.d.ts.map +1 -1
  145. package/dist/src/lib/schemas.js +8 -1
  146. package/dist/src/lib/schemas.js.map +1 -1
  147. package/dist/src/lib/semver.d.ts +10 -0
  148. package/dist/src/lib/semver.d.ts.map +1 -0
  149. package/dist/src/lib/semver.js +51 -0
  150. package/dist/src/lib/semver.js.map +1 -0
  151. package/dist/src/lib/types.d.ts +5 -1
  152. package/dist/src/lib/types.d.ts.map +1 -1
  153. package/dist/src/steps/configurePrisma.d.ts.map +1 -1
  154. package/dist/src/steps/configurePrisma.js +18 -23
  155. package/dist/src/steps/configurePrisma.js.map +1 -1
  156. package/dist/src/steps/configureReadme.d.ts +4 -0
  157. package/dist/src/steps/configureReadme.d.ts.map +1 -0
  158. package/dist/src/steps/configureReadme.js +22 -0
  159. package/dist/src/steps/configureReadme.js.map +1 -0
  160. package/dist/src/steps/createProject.d.ts.map +1 -1
  161. package/dist/src/steps/createProject.js +16 -170
  162. package/dist/src/steps/createProject.js.map +1 -1
  163. package/dist/src/steps/finalSetup.d.ts +3 -2
  164. package/dist/src/steps/finalSetup.d.ts.map +1 -1
  165. package/dist/src/steps/finalSetup.js +14 -9
  166. package/dist/src/steps/finalSetup.js.map +1 -1
  167. package/dist/src/steps/installDeps.d.ts +1 -0
  168. package/dist/src/steps/installDeps.d.ts.map +1 -1
  169. package/dist/src/steps/installDeps.js +11 -0
  170. package/dist/src/steps/installDeps.js.map +1 -1
  171. package/dist/src/steps/prompts.d.ts.map +1 -1
  172. package/dist/src/steps/prompts.js +32 -13
  173. package/dist/src/steps/prompts.js.map +1 -1
  174. package/dist/src/utils/output.d.ts.map +1 -1
  175. package/dist/src/utils/output.js +23 -11
  176. package/dist/src/utils/output.js.map +1 -1
  177. package/package.json +5 -4
  178. package/templates/express-auth/README.md +21 -12
  179. package/templates/express-auth/package.json +11 -7
  180. package/templates/express-auth/src/config/redis.ts +109 -0
  181. package/templates/express-auth/src/server.ts +2 -0
  182. package/templates/express-auth+/README.md +25 -18
  183. package/templates/express-auth+/package.json +11 -7
  184. package/templates/express-auth+/src/auth/auth.ts +2 -0
  185. package/templates/express-auth+/src/auth/controllers/password.controller.ts +12 -1
  186. package/templates/express-auth+/src/auth/routes/password.route.ts +1 -0
  187. package/templates/express-auth+/src/config/redis.ts +109 -0
  188. package/templates/express-auth+/src/utils/security.ts +12 -0
  189. package/templates/express-base/README.md +21 -12
  190. package/templates/express-base/package.json +11 -8
  191. package/templates/express-base/src/.env.example +1 -1
  192. package/templates/express-base/src/config/redis.ts +109 -0
  193. package/templates/express-base/src/server.ts +2 -0
  194. package/templates/fullstack/.env.example +3 -2
  195. package/templates/fullstack/PRESET_CONTRACT.md +7 -3
  196. package/templates/fullstack/README.md +45 -7
  197. package/templates/fullstack/apps/api/package.json +6 -4
  198. package/templates/fullstack/apps/api/prisma/migrations/20260721000000_init/migration.sql +163 -0
  199. package/templates/fullstack/apps/api/prisma/migrations/migration_lock.toml +3 -0
  200. package/templates/fullstack/apps/api/src/auth/authenik8.ts +13 -4
  201. package/templates/fullstack/apps/api/src/config/env.ts +9 -1
  202. package/templates/fullstack/apps/api/src/config/logger.ts +21 -11
  203. package/templates/fullstack/apps/api/tests/logger.test.ts +43 -0
  204. package/templates/fullstack/apps/web/package.json +3 -1
  205. package/templates/fullstack/apps/web/src/App.tsx +1 -1
  206. package/templates/fullstack/apps/web/src/auth/RouteGuards.tsx +1 -1
  207. package/templates/fullstack/apps/web/src/components/AppShell.tsx +90 -45
  208. package/templates/fullstack/apps/web/src/components/AuthShell.tsx +10 -7
  209. package/templates/fullstack/apps/web/src/components/Page.tsx +6 -3
  210. package/templates/fullstack/apps/web/src/features/projects/ProjectPages.tsx +1 -1
  211. package/templates/fullstack/apps/web/src/main.tsx +13 -6
  212. package/templates/fullstack/apps/web/src/pages/DashboardPage.tsx +163 -23
  213. package/templates/fullstack/apps/web/src/pages/StatusPages.tsx +1 -1
  214. package/templates/fullstack/apps/web/src/pages/auth/LoginPage.tsx +93 -17
  215. package/templates/fullstack/apps/web/src/pages/auth/OAuthCallbackPage.tsx +1 -1
  216. package/templates/fullstack/apps/web/src/pages/auth/RecoveryPages.tsx +1 -1
  217. package/templates/fullstack/apps/web/src/pages/auth/RegisterPage.tsx +1 -1
  218. package/templates/fullstack/apps/web/src/pages/auth/VerifyEmailPage.tsx +1 -1
  219. package/templates/fullstack/apps/web/src/styles.css +119 -43
  220. package/templates/fullstack/apps/web/vite.config.ts +12 -0
  221. package/templates/fullstack/docker-compose.yml +2 -2
  222. package/templates/fullstack/gitignore.template +1 -0
  223. package/templates/fullstack/package.json +17 -8
  224. package/templates/fullstack/scripts/run-local.mjs +157 -0
  225. package/templates/prisma/postgresql/.env.example +1 -2
  226. package/templates/prisma/sqlite/.env.example +1 -2
  227. package/templates/prisma/sqlite/client.ts +2 -2
@@ -0,0 +1,163 @@
1
+ -- CreateEnum
2
+ CREATE TYPE "Role" AS ENUM ('USER', 'ADMIN');
3
+
4
+ -- CreateEnum
5
+ CREATE TYPE "UserStatus" AS ENUM ('ACTIVE', 'SUSPENDED');
6
+
7
+ -- CreateEnum
8
+ CREATE TYPE "ProjectStatus" AS ENUM ('DRAFT', 'ACTIVE', 'ARCHIVED');
9
+
10
+ -- CreateTable
11
+ CREATE TABLE "User" (
12
+ "id" TEXT NOT NULL,
13
+ "email" TEXT NOT NULL,
14
+ "passwordHash" TEXT,
15
+ "name" TEXT NOT NULL,
16
+ "role" "Role" NOT NULL DEFAULT 'USER',
17
+ "status" "UserStatus" NOT NULL DEFAULT 'ACTIVE',
18
+ "emailVerifiedAt" TIMESTAMP(3),
19
+ "passwordUpdatedAt" TIMESTAMP(3),
20
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
21
+ "updatedAt" TIMESTAMP(3) NOT NULL,
22
+
23
+ CONSTRAINT "User_pkey" PRIMARY KEY ("id")
24
+ );
25
+
26
+ -- CreateTable
27
+ CREATE TABLE "OAuthAccount" (
28
+ "id" TEXT NOT NULL,
29
+ "userId" TEXT NOT NULL,
30
+ "provider" TEXT NOT NULL,
31
+ "providerAccountId" TEXT NOT NULL,
32
+ "providerEmail" TEXT NOT NULL,
33
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
34
+
35
+ CONSTRAINT "OAuthAccount_pkey" PRIMARY KEY ("id")
36
+ );
37
+
38
+ -- CreateTable
39
+ CREATE TABLE "Session" (
40
+ "id" TEXT NOT NULL,
41
+ "userId" TEXT NOT NULL,
42
+ "coreSessionId" TEXT NOT NULL,
43
+ "refreshHash" TEXT NOT NULL,
44
+ "userAgent" TEXT NOT NULL,
45
+ "ipAddress" TEXT NOT NULL,
46
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
47
+ "lastUsedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
48
+ "expiresAt" TIMESTAMP(3) NOT NULL,
49
+ "revokedAt" TIMESTAMP(3),
50
+
51
+ CONSTRAINT "Session_pkey" PRIMARY KEY ("id")
52
+ );
53
+
54
+ -- CreateTable
55
+ CREATE TABLE "Project" (
56
+ "id" TEXT NOT NULL,
57
+ "name" TEXT NOT NULL,
58
+ "description" TEXT NOT NULL DEFAULT '',
59
+ "status" "ProjectStatus" NOT NULL DEFAULT 'DRAFT',
60
+ "ownerId" TEXT NOT NULL,
61
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
62
+ "updatedAt" TIMESTAMP(3) NOT NULL,
63
+
64
+ CONSTRAINT "Project_pkey" PRIMARY KEY ("id")
65
+ );
66
+
67
+ -- CreateTable
68
+ CREATE TABLE "PasswordResetToken" (
69
+ "id" TEXT NOT NULL,
70
+ "tokenHash" TEXT NOT NULL,
71
+ "userId" TEXT NOT NULL,
72
+ "expiresAt" TIMESTAMP(3) NOT NULL,
73
+ "usedAt" TIMESTAMP(3),
74
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
75
+
76
+ CONSTRAINT "PasswordResetToken_pkey" PRIMARY KEY ("id")
77
+ );
78
+
79
+ -- CreateTable
80
+ CREATE TABLE "EmailVerificationToken" (
81
+ "id" TEXT NOT NULL,
82
+ "tokenHash" TEXT NOT NULL,
83
+ "userId" TEXT NOT NULL,
84
+ "expiresAt" TIMESTAMP(3) NOT NULL,
85
+ "usedAt" TIMESTAMP(3),
86
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
87
+
88
+ CONSTRAINT "EmailVerificationToken_pkey" PRIMARY KEY ("id")
89
+ );
90
+
91
+ -- CreateTable
92
+ CREATE TABLE "AuditEvent" (
93
+ "id" TEXT NOT NULL,
94
+ "actorId" TEXT,
95
+ "action" TEXT NOT NULL,
96
+ "targetType" TEXT NOT NULL,
97
+ "targetId" TEXT,
98
+ "metadata" JSONB,
99
+ "ipAddress" TEXT,
100
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
101
+
102
+ CONSTRAINT "AuditEvent_pkey" PRIMARY KEY ("id")
103
+ );
104
+
105
+ -- CreateIndex
106
+ CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
107
+
108
+ -- CreateIndex
109
+ CREATE UNIQUE INDEX "OAuthAccount_provider_providerAccountId_key" ON "OAuthAccount"("provider", "providerAccountId");
110
+
111
+ -- CreateIndex
112
+ CREATE UNIQUE INDEX "OAuthAccount_userId_provider_key" ON "OAuthAccount"("userId", "provider");
113
+
114
+ -- CreateIndex
115
+ CREATE INDEX "OAuthAccount_userId_idx" ON "OAuthAccount"("userId");
116
+
117
+ -- CreateIndex
118
+ CREATE UNIQUE INDEX "Session_coreSessionId_key" ON "Session"("coreSessionId");
119
+
120
+ -- CreateIndex
121
+ CREATE UNIQUE INDEX "Session_refreshHash_key" ON "Session"("refreshHash");
122
+
123
+ -- CreateIndex
124
+ CREATE INDEX "Session_userId_revokedAt_idx" ON "Session"("userId", "revokedAt");
125
+
126
+ -- CreateIndex
127
+ CREATE INDEX "Project_ownerId_updatedAt_idx" ON "Project"("ownerId", "updatedAt");
128
+
129
+ -- CreateIndex
130
+ CREATE UNIQUE INDEX "PasswordResetToken_tokenHash_key" ON "PasswordResetToken"("tokenHash");
131
+
132
+ -- CreateIndex
133
+ CREATE INDEX "PasswordResetToken_userId_expiresAt_idx" ON "PasswordResetToken"("userId", "expiresAt");
134
+
135
+ -- CreateIndex
136
+ CREATE UNIQUE INDEX "EmailVerificationToken_tokenHash_key" ON "EmailVerificationToken"("tokenHash");
137
+
138
+ -- CreateIndex
139
+ CREATE INDEX "EmailVerificationToken_userId_expiresAt_idx" ON "EmailVerificationToken"("userId", "expiresAt");
140
+
141
+ -- CreateIndex
142
+ CREATE INDEX "AuditEvent_createdAt_idx" ON "AuditEvent"("createdAt");
143
+
144
+ -- CreateIndex
145
+ CREATE INDEX "AuditEvent_actorId_idx" ON "AuditEvent"("actorId");
146
+
147
+ -- AddForeignKey
148
+ ALTER TABLE "OAuthAccount" ADD CONSTRAINT "OAuthAccount_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
149
+
150
+ -- AddForeignKey
151
+ ALTER TABLE "Session" ADD CONSTRAINT "Session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
152
+
153
+ -- AddForeignKey
154
+ ALTER TABLE "Project" ADD CONSTRAINT "Project_ownerId_fkey" FOREIGN KEY ("ownerId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
155
+
156
+ -- AddForeignKey
157
+ ALTER TABLE "PasswordResetToken" ADD CONSTRAINT "PasswordResetToken_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
158
+
159
+ -- AddForeignKey
160
+ ALTER TABLE "EmailVerificationToken" ADD CONSTRAINT "EmailVerificationToken_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
161
+
162
+ -- AddForeignKey
163
+ ALTER TABLE "AuditEvent" ADD CONSTRAINT "AuditEvent_actorId_fkey" FOREIGN KEY ("actorId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
@@ -0,0 +1,3 @@
1
+ # Please do not edit this file manually
2
+ # It should be added to your version-control system
3
+ provider = "postgresql"
@@ -1,11 +1,20 @@
1
1
  import { createAuthenik8 } from "authenik8-core";
2
2
  import { Redis } from "ioredis";
3
+ import RedisMock from "ioredis-mock";
3
4
  import { env } from "../config/env.js";
4
5
 
5
- export const redis = new Redis(env.REDIS_URL, {
6
- maxRetriesPerRequest: 2,
7
- enableReadyCheck: true,
8
- });
6
+ function createRedisClient(): Redis {
7
+ if (env.REDIS_URL === "memory://") {
8
+ return new RedisMock() as unknown as Redis;
9
+ }
10
+
11
+ return new Redis(env.REDIS_URL, {
12
+ maxRetriesPerRequest: 2,
13
+ enableReadyCheck: true,
14
+ });
15
+ }
16
+
17
+ export const redis = createRedisClient();
9
18
 
10
19
  type AuthInstance = Awaited<ReturnType<typeof createAuthenik8>>;
11
20
  let instance: AuthInstance | undefined;
@@ -43,7 +43,7 @@ const schema = z.object({
43
43
  PORT: z.coerce.number().int().positive().default(3000),
44
44
  WEB_ORIGIN: z.string().url().default("http://localhost:5173"),
45
45
  DATABASE_URL: z.string().min(1),
46
- REDIS_URL: z.string().min(1).default("redis://localhost:6379"),
46
+ REDIS_URL: z.string().min(1).default("memory://"),
47
47
  AUTHENIK8_SIGNING_JWKS: signingJwks,
48
48
  AUTHENIK8_ACTIVE_KID: z.string().min(1),
49
49
  AUTHENIK8_ISSUER: z.string().url(),
@@ -61,6 +61,14 @@ const schema = z.object({
61
61
  GITHUB_CLIENT_ID: z.string().optional(),
62
62
  GITHUB_CLIENT_SECRET: z.string().optional(),
63
63
  GITHUB_REDIRECT_URI: z.string().url().optional().or(z.literal("")),
64
+ }).superRefine((environment, context) => {
65
+ if (environment.NODE_ENV === "production" && environment.REDIS_URL === "memory://") {
66
+ context.addIssue({
67
+ code: "custom",
68
+ path: ["REDIS_URL"],
69
+ message: "must use redis:// or rediss:// in production",
70
+ });
71
+ }
64
72
  });
65
73
 
66
74
  const result = schema.safeParse(process.env);
@@ -1,17 +1,27 @@
1
- import pino from "pino";
1
+ import pino, { type DestinationStream, type LoggerOptions } from "pino";
2
2
  import { env } from "./env.js";
3
3
 
4
- export const logger = pino({
4
+ export const redactedLogPaths = [
5
+ "req.headers.authorization",
6
+ "req.headers.cookie",
7
+ 'req.headers["x-csrf-token"]',
8
+ 'res.headers["set-cookie"]',
9
+ "password",
10
+ "passwordHash",
11
+ "accessToken",
12
+ "refreshToken",
13
+ ];
14
+
15
+ const loggerOptions: LoggerOptions = {
5
16
  level: env.LOG_LEVEL,
6
17
  redact: {
7
- paths: [
8
- "req.headers.authorization",
9
- "req.headers.cookie",
10
- "password",
11
- "passwordHash",
12
- "accessToken",
13
- "refreshToken",
14
- ],
18
+ paths: redactedLogPaths,
15
19
  censor: "[REDACTED]",
16
20
  },
17
- });
21
+ };
22
+
23
+ export function createLogger(destination?: DestinationStream) {
24
+ return destination ? pino(loggerOptions, destination) : pino(loggerOptions);
25
+ }
26
+
27
+ export const logger = createLogger();
@@ -0,0 +1,43 @@
1
+ import { Writable } from "node:stream";
2
+ import express from "express";
3
+ import { pinoHttp } from "pino-http";
4
+ import request from "supertest";
5
+ import { describe, expect, it, vi } from "vitest";
6
+
7
+ vi.mock("../src/config/env.js", () => ({
8
+ env: { LOG_LEVEL: "info" },
9
+ }));
10
+
11
+ import { createLogger } from "../src/config/logger.js";
12
+
13
+ describe("HTTP log redaction", () => {
14
+ it("does not record bearer, CSRF, or cookie credentials", async () => {
15
+ let output = "";
16
+ const destination = new Writable({
17
+ write(chunk, _encoding, callback) {
18
+ output += chunk.toString();
19
+ callback();
20
+ },
21
+ });
22
+ const app = express();
23
+
24
+ app.use(pinoHttp({ logger: createLogger(destination) }));
25
+ app.get("/session", (_req, res) => {
26
+ res.cookie("authenik8_refresh", "sealed-refresh-secret", { httpOnly: true });
27
+ res.sendStatus(204);
28
+ });
29
+
30
+ await request(app)
31
+ .get("/session")
32
+ .set("authorization", "Bearer access-secret")
33
+ .set("cookie", "authenik8_refresh=incoming-cookie-secret")
34
+ .set("x-csrf-token", "csrf-secret")
35
+ .expect(204);
36
+
37
+ expect(output).toContain("[REDACTED]");
38
+ expect(output).not.toContain("access-secret");
39
+ expect(output).not.toContain("incoming-cookie-secret");
40
+ expect(output).not.toContain("csrf-secret");
41
+ expect(output).not.toContain("sealed-refresh-secret");
42
+ });
43
+ });
@@ -10,6 +10,8 @@
10
10
  "typecheck": "tsc -b --pretty false"
11
11
  },
12
12
  "dependencies": {
13
+ "@astryxdesign/core": "0.1.7",
14
+ "@astryxdesign/theme-neutral": "0.1.7",
13
15
  "@authenik8/api-client": "0.1.0",
14
16
  "@authenik8/contracts": "0.1.0",
15
17
  "@authenik8/ui": "0.1.0",
@@ -17,7 +19,7 @@
17
19
  "lucide-react": "^0.468.0",
18
20
  "react": "^19.2.0",
19
21
  "react-dom": "^19.2.0",
20
- "react-router-dom": "^7.9.0"
22
+ "react-router": "8.3.0"
21
23
  },
22
24
  "devDependencies": {
23
25
  "@types/node": "^25.0.0",
@@ -1,4 +1,4 @@
1
- import { Route, Routes } from "react-router-dom";
1
+ import { Route, Routes } from "react-router";
2
2
  import { AdminRoute, ProtectedRoute } from "./auth/RouteGuards";
3
3
  import { AppShell } from "./components/AppShell";
4
4
  import { LoginPage } from "./pages/auth/LoginPage";
@@ -1,4 +1,4 @@
1
- import { Navigate, Outlet, useLocation } from "react-router-dom";
1
+ import { Navigate, Outlet, useLocation } from "react-router";
2
2
  import { Spinner } from "@authenik8/ui";
3
3
  import { useAuth } from "./AuthProvider";
4
4
 
@@ -3,15 +3,16 @@ import {
3
3
  FolderKanban,
4
4
  LayoutDashboard,
5
5
  LogOut,
6
- Menu,
7
6
  Settings,
8
7
  ShieldCheck,
9
8
  Users,
10
- X,
11
9
  } from "lucide-react";
12
- import { useState } from "react";
13
- import { NavLink, Outlet, useLocation } from "react-router-dom";
14
- import { Button } from "@authenik8/ui";
10
+ import { AppShell as AstryxAppShell } from "@astryxdesign/core/AppShell";
11
+ import { Avatar } from "@astryxdesign/core/Avatar";
12
+ import { Button } from "@astryxdesign/core/Button";
13
+ import { SideNav, SideNavItem, SideNavSection } from "@astryxdesign/core/SideNav";
14
+ import { TopNav, TopNavHeading } from "@astryxdesign/core/TopNav";
15
+ import { Outlet, useLocation, useNavigate } from "react-router";
15
16
  import { useAuth } from "../auth/AuthProvider";
16
17
 
17
18
  const mainLinks = [
@@ -22,53 +23,97 @@ const mainLinks = [
22
23
 
23
24
  export function AppShell() {
24
25
  const { user, logout } = useAuth();
25
- const [open, setOpen] = useState(false);
26
26
  const location = useLocation();
27
+ const navigate = useNavigate();
27
28
 
28
- const nav = (
29
- <>
30
- <NavLink className="brand" to="/" onClick={() => setOpen(false)}>
31
- <img src="/authenik8-logo.svg" alt="" />
32
- <span>Authenik8 <small>Workspace</small></span>
33
- </NavLink>
34
- <nav className="side-nav" aria-label="Primary navigation">
35
- <p className="nav-label">Workspace</p>
29
+ const navigateTo = (path: string) => () => navigate(path);
30
+ const isSelected = (path: string, exact = false) =>
31
+ exact ? location.pathname === path : location.pathname.startsWith(path);
32
+
33
+ const sideNav = (
34
+ <SideNav
35
+ className="workspace-sidebar"
36
+ footer={
37
+ <div className="sidebar-account">
38
+ <Avatar name={user?.name} size="medium" />
39
+ <div>
40
+ <strong>{user?.name}</strong>
41
+ <span>{user?.email}</span>
42
+ </div>
43
+ </div>
44
+ }
45
+ >
46
+ <SideNavSection title="Workspace">
36
47
  {mainLinks.map(({ icon: Icon, ...link }) => (
37
- <NavLink key={link.to} to={link.to} end={link.end} onClick={() => setOpen(false)}>
38
- <Icon size={18} /> <span>{link.label}</span>
39
- </NavLink>
48
+ <SideNavItem
49
+ key={link.to}
50
+ label={link.label}
51
+ icon={<Icon size={18} />}
52
+ isSelected={isSelected(link.to, link.end)}
53
+ onClick={navigateTo(link.to)}
54
+ />
40
55
  ))}
41
- {user?.role === "ADMIN" && (
42
- <>
43
- <p className="nav-label">Administration</p>
44
- <NavLink to="/admin/users" onClick={() => setOpen(false)}><Users size={18} /> <span>Users</span></NavLink>
45
- <NavLink to="/admin/audit" onClick={() => setOpen(false)}><Activity size={18} /> <span>Audit trail</span></NavLink>
46
- </>
47
- )}
48
- </nav>
49
- <div className="sidebar-account">
50
- <div className="avatar">{user?.name.slice(0, 2).toUpperCase()}</div>
51
- <div><strong>{user?.name}</strong><span>{user?.email}</span></div>
52
- </div>
53
- </>
56
+ </SideNavSection>
57
+ {user?.role === "ADMIN" && (
58
+ <SideNavSection title="Administration">
59
+ <SideNavItem
60
+ label="Users"
61
+ icon={<Users size={18} />}
62
+ isSelected={isSelected("/admin/users")}
63
+ onClick={navigateTo("/admin/users")}
64
+ />
65
+ <SideNavItem
66
+ label="Audit trail"
67
+ icon={<Activity size={18} />}
68
+ isSelected={isSelected("/admin/audit")}
69
+ onClick={navigateTo("/admin/audit")}
70
+ />
71
+ </SideNavSection>
72
+ )}
73
+ </SideNav>
74
+ );
75
+
76
+ const topNav = (
77
+ <TopNav
78
+ className="workspace-topbar"
79
+ label="Workspace navigation"
80
+ heading={
81
+ <TopNavHeading
82
+ logo={<img className="brand-logo" src="/authenik8-logo.svg" alt="" />}
83
+ heading="Authenik8"
84
+ subheading="Workspace"
85
+ />
86
+ }
87
+ startContent={
88
+ <div className="current-section">
89
+ <ShieldCheck size={16} aria-hidden="true" />
90
+ <span>{titleForPath(location.pathname)}</span>
91
+ </div>
92
+ }
93
+ endContent={
94
+ <Button
95
+ label="Sign out"
96
+ variant="ghost"
97
+ size="sm"
98
+ icon={<LogOut size={17} />}
99
+ onClick={() => void logout()}
100
+ />
101
+ }
102
+ />
54
103
  );
55
104
 
56
105
  return (
57
- <div className="app-layout">
58
- <aside className="sidebar">{nav}</aside>
59
- {open && <div className="mobile-scrim" onClick={() => setOpen(false)} />}
60
- <aside className={`mobile-sidebar ${open ? "is-open" : ""}`} aria-hidden={!open}>{nav}</aside>
61
- <div className="app-main">
62
- <header className="topbar">
63
- <Button className="icon-button menu-button" variant="ghost" onClick={() => setOpen(!open)} aria-label={open ? "Close navigation" : "Open navigation"} title={open ? "Close navigation" : "Open navigation"}>
64
- {open ? <X size={20} /> : <Menu size={20} />}
65
- </Button>
66
- <div className="breadcrumb"><ShieldCheck size={17} /><span>{titleForPath(location.pathname)}</span></div>
67
- <Button className="icon-button" variant="ghost" onClick={() => void logout()} aria-label="Sign out" title="Sign out"><LogOut size={19} /></Button>
68
- </header>
69
- <main className="page"><Outlet /></main>
70
- </div>
71
- </div>
106
+ <AstryxAppShell
107
+ className="workspace-shell"
108
+ variant="elevated"
109
+ height="fill"
110
+ contentPadding={0}
111
+ mobileNav={{ breakpoint: "md" }}
112
+ topNav={topNav}
113
+ sideNav={sideNav}
114
+ >
115
+ <div className="page"><Outlet /></div>
116
+ </AstryxAppShell>
72
117
  );
73
118
  }
74
119
 
@@ -1,11 +1,12 @@
1
1
  import type { ReactNode } from "react";
2
- import { Link } from "react-router-dom";
2
+ import { Card } from "@astryxdesign/core/Card";
3
+ import { Link } from "react-router";
3
4
 
4
5
  export function AuthShell({ title, subtitle, children, footer }: { title: string; subtitle: string; children: ReactNode; footer: ReactNode }) {
5
6
  return (
6
7
  <main className="auth-layout">
7
8
  <section className="auth-brand-panel">
8
- <Link className="brand" to="/"><img src="/authenik8-logo.svg" alt="" /><span>Authenik8 <small>Workspace</small></span></Link>
9
+ <Link className="auth-top-brand" to="/"><img src="/authenik8-logo.svg" alt="" /><span>Authenik8 <small>Workspace</small></span></Link>
9
10
  <div className="auth-brand-copy">
10
11
  <img className="auth-logo" src="/authenik8-logo.svg" alt="Authenik8" />
11
12
  <span className="security-mark">A8 / IDENTITY</span>
@@ -15,11 +16,13 @@ export function AuthShell({ title, subtitle, children, footer }: { title: string
15
16
  <div className="auth-signal"><span className="signal-dot" /> Identity services operational</div>
16
17
  </section>
17
18
  <section className="auth-form-panel">
18
- <div className="auth-form-wrap">
19
- <header><h2>{title}</h2><p>{subtitle}</p></header>
20
- {children}
21
- <footer>{footer}</footer>
22
- </div>
19
+ <Card padding={0} className="auth-form-card">
20
+ <div className="auth-form-wrap">
21
+ <header><h2>{title}</h2><p>{subtitle}</p></header>
22
+ {children}
23
+ <footer>{footer}</footer>
24
+ </div>
25
+ </Card>
23
26
  </section>
24
27
  </main>
25
28
  );
@@ -11,14 +11,17 @@ export function PageHeader({ title, description, action }: { title: string; desc
11
11
  );
12
12
  }
13
13
 
14
- export function ErrorNotice({ error }: { error: unknown }) {
14
+ export function getErrorMessage(error: unknown) {
15
15
  const fieldMessage = error instanceof ApiError
16
16
  ? Object.values(error.fields ?? {}).flat()[0]
17
17
  : undefined;
18
- const message = error instanceof ApiError
18
+ return error instanceof ApiError
19
19
  ? fieldMessage ?? error.message
20
20
  : "Something went wrong. Try again.";
21
- return <div className="notice notice-error" role="alert"><AlertTriangle size={17} /><span>{message}</span></div>;
21
+ }
22
+
23
+ export function ErrorNotice({ error }: { error: unknown }) {
24
+ return <div className="notice notice-error" role="alert"><AlertTriangle size={17} /><span>{getErrorMessage(error)}</span></div>;
22
25
  }
23
26
 
24
27
  export function SuccessNotice({ children }: { children: ReactNode }) {
@@ -1,7 +1,7 @@
1
1
  import { useEffect, useState, type FormEvent } from "react";
2
2
  import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
3
3
  import { ArrowLeft, Edit3, FolderKanban, Plus, Save, Trash2 } from "lucide-react";
4
- import { Link, useNavigate, useParams } from "react-router-dom";
4
+ import { Link, useNavigate, useParams } from "react-router";
5
5
  import { Badge, Button, EmptyState, Input, Spinner } from "@authenik8/ui";
6
6
  import { projectApi } from "@authenik8/api-client";
7
7
  import type { ProjectStatus } from "@authenik8/contracts";
@@ -1,9 +1,14 @@
1
1
  import { StrictMode } from "react";
2
2
  import { createRoot } from "react-dom/client";
3
3
  import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
4
- import { BrowserRouter } from "react-router-dom";
4
+ import { BrowserRouter } from "react-router";
5
+ import { Theme } from "@astryxdesign/core/theme";
6
+ import { neutralTheme } from "@astryxdesign/theme-neutral/built";
5
7
  import { AuthProvider } from "./auth/AuthProvider";
6
8
  import { App } from "./App";
9
+ import "@astryxdesign/core/reset.css";
10
+ import "@astryxdesign/core/astryx.css";
11
+ import "@astryxdesign/theme-neutral/theme.css";
7
12
  import "./styles.css";
8
13
 
9
14
  const queryClient = new QueryClient({
@@ -15,10 +20,12 @@ const queryClient = new QueryClient({
15
20
 
16
21
  createRoot(document.getElementById("root")!).render(
17
22
  <StrictMode>
18
- <QueryClientProvider client={queryClient}>
19
- <BrowserRouter>
20
- <AuthProvider><App /></AuthProvider>
21
- </BrowserRouter>
22
- </QueryClientProvider>
23
+ <Theme theme={neutralTheme} mode="light">
24
+ <QueryClientProvider client={queryClient}>
25
+ <BrowserRouter>
26
+ <AuthProvider><App /></AuthProvider>
27
+ </BrowserRouter>
28
+ </QueryClientProvider>
29
+ </Theme>
23
30
  </StrictMode>,
24
31
  );