@simple-auth-kit/cli 1.4.3 → 1.5.1

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 (134) hide show
  1. package/lib/copy.ts +1 -1
  2. package/package.json +1 -1
  3. package/registry/combos/express-drizzle/shared/src/{auth-core-error.middleware.ts → middleware/auth-core-error.middleware.ts} +17 -4
  4. package/registry/combos/express-drizzle/shared/src/{auth.middleware.ts → middleware/auth.middleware.ts} +23 -7
  5. package/registry/combos/{express-prisma/shared/src → express-drizzle/shared/src/middleware}/response-envelope.middleware.ts +15 -3
  6. package/registry/combos/express-drizzle/shared/src/{oauth.repository.ts → repositories/oauth.repository.ts} +32 -9
  7. package/registry/combos/express-drizzle/shared/src/repositories/password-reset.repository.ts +59 -0
  8. package/registry/combos/express-drizzle/shared/src/{session.repository.ts → repositories/session.repository.ts} +52 -14
  9. package/registry/combos/express-drizzle/shared/src/{two-factor.repository.ts → repositories/two-factor.repository.ts} +20 -6
  10. package/registry/combos/express-drizzle/shared/src/request-context.ts +1 -1
  11. package/registry/combos/express-drizzle/shared/src/routers/auth.router.ts +395 -0
  12. package/registry/combos/express-drizzle/variants/base/src/create-auth-app.ts +52 -19
  13. package/registry/combos/express-drizzle/variants/base/src/{authz.middleware.ts → middleware/authz.middleware.ts} +15 -7
  14. package/registry/combos/express-drizzle/variants/base/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +28 -10
  15. package/registry/combos/express-drizzle/variants/base/src/{rbac.repository.ts → repositories/rbac.repository.ts} +239 -48
  16. package/registry/combos/express-drizzle/variants/base/src/routers/admin.router.ts +497 -0
  17. package/registry/combos/express-drizzle/variants/base/src/{auth.service.ts → services/auth.service.ts} +480 -109
  18. package/registry/combos/express-drizzle/variants/workspaces/src/create-auth-app.ts +69 -23
  19. package/registry/combos/express-drizzle/variants/workspaces/src/{authz.middleware.ts → middleware/authz.middleware.ts} +32 -12
  20. package/registry/combos/express-drizzle/variants/workspaces/src/openapi-workspace.ts +147 -28
  21. package/registry/combos/express-drizzle/variants/workspaces/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +30 -11
  22. package/registry/combos/express-drizzle/variants/workspaces/src/{rbac.repository.ts → repositories/rbac.repository.ts} +319 -70
  23. package/registry/combos/express-drizzle/variants/workspaces/src/{workspace.repository.ts → repositories/workspace.repository.ts} +127 -30
  24. package/registry/combos/express-drizzle/variants/workspaces/src/routers/admin.router.ts +534 -0
  25. package/registry/combos/express-drizzle/variants/workspaces/src/routers/workspace.router.ts +194 -0
  26. package/registry/combos/express-drizzle/variants/workspaces/src/{auth.service.ts → services/auth.service.ts} +544 -122
  27. package/registry/combos/express-prisma/shared/src/{auth-core-error.middleware.ts → middleware/auth-core-error.middleware.ts} +17 -4
  28. package/registry/combos/express-prisma/shared/src/{auth.middleware.ts → middleware/auth.middleware.ts} +19 -6
  29. package/registry/combos/{express-drizzle/shared/src → express-prisma/shared/src/middleware}/response-envelope.middleware.ts +15 -3
  30. package/registry/combos/express-prisma/shared/src/{oauth.repository.ts → repositories/oauth.repository.ts} +27 -9
  31. package/registry/combos/express-prisma/shared/src/repositories/password-reset.repository.ts +55 -0
  32. package/registry/combos/express-prisma/shared/src/{session.repository.ts → repositories/session.repository.ts} +30 -9
  33. package/registry/combos/express-prisma/shared/src/{two-factor.repository.ts → repositories/two-factor.repository.ts} +11 -4
  34. package/registry/combos/express-prisma/shared/src/request-context.ts +1 -1
  35. package/registry/combos/express-prisma/shared/src/routers/auth.router.ts +398 -0
  36. package/registry/combos/express-prisma/variants/base/src/create-auth-app.ts +56 -21
  37. package/registry/combos/express-prisma/variants/base/src/{authz.middleware.ts → middleware/authz.middleware.ts} +15 -7
  38. package/registry/combos/express-prisma/variants/base/src/rbac.defaults.ts +66 -18
  39. package/registry/combos/express-prisma/variants/base/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +16 -5
  40. package/registry/combos/express-prisma/variants/base/src/{rbac.repository.ts → repositories/rbac.repository.ts} +199 -48
  41. package/registry/combos/express-prisma/variants/base/src/routers/admin.router.ts +508 -0
  42. package/registry/combos/express-prisma/variants/base/src/seed.ts +77 -19
  43. package/registry/combos/express-prisma/variants/base/src/{auth.service.ts → services/auth.service.ts} +454 -105
  44. package/registry/combos/express-prisma/variants/workspaces/src/create-auth-app.ts +80 -25
  45. package/registry/combos/express-prisma/variants/workspaces/src/{authz.middleware.ts → middleware/authz.middleware.ts} +32 -12
  46. package/registry/combos/express-prisma/variants/workspaces/src/openapi-workspace.ts +147 -28
  47. package/registry/combos/express-prisma/variants/workspaces/src/rbac.defaults.ts +72 -20
  48. package/registry/combos/express-prisma/variants/workspaces/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +16 -5
  49. package/registry/combos/express-prisma/variants/workspaces/src/{rbac.repository.ts → repositories/rbac.repository.ts} +272 -64
  50. package/registry/combos/express-prisma/variants/workspaces/src/{workspace.repository.ts → repositories/workspace.repository.ts} +86 -23
  51. package/registry/combos/express-prisma/variants/workspaces/src/routers/admin.router.ts +545 -0
  52. package/registry/combos/express-prisma/variants/workspaces/src/routers/workspace.router.ts +194 -0
  53. package/registry/combos/express-prisma/variants/workspaces/src/seed.ts +108 -28
  54. package/registry/combos/express-prisma/variants/workspaces/src/{auth.service.ts → services/auth.service.ts} +518 -119
  55. package/registry/combos/nestjs-drizzle/shared/src/{auth.controller.ts → controllers/auth.controller.ts} +6 -6
  56. package/registry/combos/nestjs-drizzle/shared/src/{auth-core-error.filter.ts → filters/auth-core-error.filter.ts} +29 -5
  57. package/registry/combos/nestjs-drizzle/shared/src/{ability.guard.ts → guards/ability.guard.ts} +20 -7
  58. package/registry/combos/nestjs-drizzle/shared/src/{auth.guard.ts → guards/auth.guard.ts} +19 -7
  59. package/registry/combos/{nestjs-prisma/shared/src → nestjs-drizzle/shared/src/interceptors}/response.interceptor.ts +10 -2
  60. package/registry/combos/nestjs-drizzle/shared/src/repositories/oauth.repository.ts +63 -0
  61. package/registry/combos/nestjs-drizzle/shared/src/repositories/password-reset.repository.ts +59 -0
  62. package/registry/combos/nestjs-drizzle/shared/src/{session.repository.ts → repositories/session.repository.ts} +52 -14
  63. package/registry/combos/nestjs-drizzle/shared/src/{two-factor.repository.ts → repositories/two-factor.repository.ts} +21 -7
  64. package/registry/combos/nestjs-drizzle/shared/src/request-context.ts +1 -1
  65. package/registry/combos/nestjs-drizzle/test/prove-cycle.ts +1097 -307
  66. package/registry/combos/nestjs-drizzle/variants/base/src/auth.module.ts +44 -20
  67. package/registry/combos/nestjs-drizzle/variants/base/src/{admin.controller.ts → controllers/admin.controller.ts} +228 -62
  68. package/registry/combos/nestjs-drizzle/variants/base/src/{authz.guard.ts → guards/authz.guard.ts} +12 -5
  69. package/registry/combos/nestjs-drizzle/variants/base/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +27 -10
  70. package/registry/combos/nestjs-drizzle/variants/base/src/{rbac.repository.ts → repositories/rbac.repository.ts} +249 -51
  71. package/registry/combos/nestjs-drizzle/variants/base/src/{auth.service.ts → services/auth.service.ts} +499 -116
  72. package/registry/combos/nestjs-drizzle/variants/workspaces/src/auth.module.ts +48 -22
  73. package/registry/combos/nestjs-drizzle/variants/workspaces/src/{admin.controller.ts → controllers/admin.controller.ts} +269 -70
  74. package/registry/combos/nestjs-drizzle/variants/workspaces/src/{workspace.controller.ts → controllers/workspace.controller.ts} +93 -26
  75. package/registry/combos/nestjs-drizzle/variants/workspaces/src/{authz.guard.ts → guards/authz.guard.ts} +26 -8
  76. package/registry/combos/nestjs-drizzle/variants/workspaces/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +29 -11
  77. package/registry/combos/nestjs-drizzle/variants/workspaces/src/{rbac.repository.ts → repositories/rbac.repository.ts} +323 -71
  78. package/registry/combos/nestjs-drizzle/variants/workspaces/src/{workspace.repository.ts → repositories/workspace.repository.ts} +131 -30
  79. package/registry/combos/nestjs-drizzle/variants/workspaces/src/services/auth.service.ts +988 -0
  80. package/registry/combos/nestjs-prisma/shared/src/{auth.controller.ts → controllers/auth.controller.ts} +6 -6
  81. package/registry/combos/nestjs-prisma/shared/src/{auth-core-error.filter.ts → filters/auth-core-error.filter.ts} +29 -5
  82. package/registry/combos/nestjs-prisma/shared/src/{ability.guard.ts → guards/ability.guard.ts} +20 -7
  83. package/registry/combos/nestjs-prisma/shared/src/{auth.guard.ts → guards/auth.guard.ts} +19 -7
  84. package/registry/combos/{nestjs-drizzle/shared/src → nestjs-prisma/shared/src/interceptors}/response.interceptor.ts +10 -2
  85. package/registry/combos/nestjs-prisma/shared/src/{oauth.repository.ts → repositories/oauth.repository.ts} +25 -8
  86. package/registry/combos/nestjs-prisma/shared/src/repositories/password-reset.repository.ts +55 -0
  87. package/registry/combos/nestjs-prisma/shared/src/{session.repository.ts → repositories/session.repository.ts} +30 -9
  88. package/registry/combos/nestjs-prisma/shared/src/{two-factor.repository.ts → repositories/two-factor.repository.ts} +11 -4
  89. package/registry/combos/nestjs-prisma/shared/src/request-context.ts +1 -1
  90. package/registry/combos/nestjs-prisma/test/prove-cycle.ts +1097 -307
  91. package/registry/combos/nestjs-prisma/variants/base/src/auth.module.ts +58 -27
  92. package/registry/combos/nestjs-prisma/variants/base/src/{admin.controller.ts → controllers/admin.controller.ts} +469 -110
  93. package/registry/combos/nestjs-prisma/variants/base/src/{audit-log.gateway.ts → gateways/audit-log.gateway.ts} +22 -6
  94. package/registry/combos/nestjs-prisma/variants/base/src/{authz.guard.ts → guards/authz.guard.ts} +12 -5
  95. package/registry/combos/nestjs-prisma/variants/base/src/rbac.defaults.ts +97 -26
  96. package/registry/combos/nestjs-prisma/variants/base/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +16 -5
  97. package/registry/combos/nestjs-prisma/variants/base/src/{country.repository.ts → repositories/country.repository.ts} +98 -25
  98. package/registry/combos/nestjs-prisma/variants/base/src/{customer.repository.ts → repositories/customer.repository.ts} +106 -27
  99. package/registry/combos/nestjs-prisma/variants/base/src/{language.repository.ts → repositories/language.repository.ts} +93 -25
  100. package/registry/combos/nestjs-prisma/variants/base/src/{rbac.repository.ts → repositories/rbac.repository.ts} +229 -55
  101. package/registry/combos/nestjs-prisma/variants/base/src/seed.ts +77 -19
  102. package/registry/combos/nestjs-prisma/variants/base/src/services/auth.service.ts +1064 -0
  103. package/registry/combos/nestjs-prisma/variants/workspaces/src/auth.module.ts +54 -26
  104. package/registry/combos/nestjs-prisma/variants/workspaces/src/{admin.controller.ts → controllers/admin.controller.ts} +544 -128
  105. package/registry/combos/nestjs-prisma/variants/workspaces/src/{workspace.controller.ts → controllers/workspace.controller.ts} +93 -26
  106. package/registry/combos/nestjs-prisma/variants/workspaces/src/{authz.guard.ts → guards/authz.guard.ts} +26 -8
  107. package/registry/combos/nestjs-prisma/variants/workspaces/src/rbac.defaults.ts +103 -28
  108. package/registry/combos/nestjs-prisma/variants/workspaces/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +16 -5
  109. package/registry/combos/nestjs-prisma/variants/workspaces/src/{country.repository.ts → repositories/country.repository.ts} +127 -26
  110. package/registry/combos/nestjs-prisma/variants/workspaces/src/{customer.repository.ts → repositories/customer.repository.ts} +137 -28
  111. package/registry/combos/nestjs-prisma/variants/workspaces/src/repositories/language.repository.ts +243 -0
  112. package/registry/combos/nestjs-prisma/variants/workspaces/src/{rbac.repository.ts → repositories/rbac.repository.ts} +314 -74
  113. package/registry/combos/nestjs-prisma/variants/workspaces/src/{workspace.repository.ts → repositories/workspace.repository.ts} +94 -24
  114. package/registry/combos/nestjs-prisma/variants/workspaces/src/seed.ts +108 -28
  115. package/registry/combos/nestjs-prisma/variants/workspaces/src/services/auth.service.ts +961 -0
  116. package/registry.json +4 -4
  117. package/simple-auth-kit.ts +42 -11
  118. package/registry/combos/express-drizzle/shared/src/auth.router.ts +0 -266
  119. package/registry/combos/express-drizzle/shared/src/password-reset.repository.ts +0 -35
  120. package/registry/combos/express-drizzle/variants/base/src/admin.router.ts +0 -307
  121. package/registry/combos/express-drizzle/variants/workspaces/src/admin.router.ts +0 -323
  122. package/registry/combos/express-drizzle/variants/workspaces/src/workspace.router.ts +0 -123
  123. package/registry/combos/express-prisma/shared/src/auth.router.ts +0 -269
  124. package/registry/combos/express-prisma/shared/src/password-reset.repository.ts +0 -29
  125. package/registry/combos/express-prisma/variants/base/src/admin.router.ts +0 -314
  126. package/registry/combos/express-prisma/variants/workspaces/src/admin.router.ts +0 -330
  127. package/registry/combos/express-prisma/variants/workspaces/src/workspace.router.ts +0 -123
  128. package/registry/combos/nestjs-drizzle/shared/src/oauth.repository.ts +0 -36
  129. package/registry/combos/nestjs-drizzle/shared/src/password-reset.repository.ts +0 -29
  130. package/registry/combos/nestjs-drizzle/variants/workspaces/src/auth.service.ts +0 -553
  131. package/registry/combos/nestjs-prisma/shared/src/password-reset.repository.ts +0 -29
  132. package/registry/combos/nestjs-prisma/variants/base/src/auth.service.ts +0 -618
  133. package/registry/combos/nestjs-prisma/variants/workspaces/src/auth.service.ts +0 -509
  134. package/registry/combos/nestjs-prisma/variants/workspaces/src/language.repository.ts +0 -143
package/registry.json CHANGED
@@ -39,7 +39,7 @@
39
39
  "Add \"@/prisma/*\": [\"./generated/prisma/*\"] to your tsconfig.json's compilerOptions.paths — the copied source imports the generated Prisma client via this alias, since prisma.config.ts/prisma/ (and the client it generates) install at the project root regardless of where --path put everything else",
40
40
  "Run: npx prisma generate",
41
41
  "Run: npx prisma migrate deploy",
42
- "Seed RBAC: add \"seed\": \"tsx src/lib/auth/src/seed.ts\" to your package.json scripts (adjust the path if you installed elsewhere), then run: npm run seed — it is idempotent, and seeds the permission catalog and the default admin/member roles. Nothing is authorized until it has run at least once.",
42
+ "Seed RBAC: add \"seed\": \"tsx src/lib/auth/seed.ts\" to your package.json scripts (adjust the path if you installed elsewhere), then run: npm run seed — it is idempotent, and seeds the permission catalog and the default admin/member roles. Nothing is authorized until it has run at least once.",
43
43
  "Optional: set SEED_ADMIN_EMAIL and SEED_ADMIN_PASSWORD before seeding to also create an initial admin user. Without them the seeder skips the admin rather than inventing a password.",
44
44
  "Import AuthModule.forRoot({...}) into your root app module",
45
45
  "Optional: wire SwaggerModule.setup('docs', app, document) in your main.ts to serve the OpenAPI spec this combo's controller is already decorated for — see examples/nestjs-prisma-app/src/main.ts for a working example"
@@ -74,7 +74,7 @@
74
74
  "Set DATABASE_URL in .env (see .env.example)",
75
75
  "Set AUTH_JWT_SECRET in .env — a base64-encoded 256-bit (or longer) secret, e.g.: openssl rand -base64 32. The app refuses to start without it; there is no default and nothing is generated for you. Changing it invalidates every outstanding token.",
76
76
  "Run: npx drizzle-kit migrate — drizzle.config.ts installs at the project root, alongside drizzle/, so no cd is needed",
77
- "Seed RBAC: add \"seed\": \"tsx src/lib/auth/src/seed.ts\" to your package.json scripts (adjust the path if you installed elsewhere), then run: npm run seed — it is idempotent, and seeds the permission catalog and the default admin/member roles. Nothing is authorized until it has run at least once.",
77
+ "Seed RBAC: add \"seed\": \"tsx src/lib/auth/seed.ts\" to your package.json scripts (adjust the path if you installed elsewhere), then run: npm run seed — it is idempotent, and seeds the permission catalog and the default admin/member roles. Nothing is authorized until it has run at least once.",
78
78
  "Optional: set SEED_ADMIN_EMAIL and SEED_ADMIN_PASSWORD before seeding to also create an initial admin user. Without them the seeder skips the admin rather than inventing a password.",
79
79
  "Import AuthModule.forRoot({...}) into your root app module",
80
80
  "Optional: wire SwaggerModule.setup('docs', app, document) in your main.ts to serve the OpenAPI spec this combo's controller is already decorated for — see examples/nestjs-drizzle-app/src/main.ts for a working example"
@@ -106,7 +106,7 @@
106
106
  "Add \"@/prisma/*\": [\"./generated/prisma/*\"] to your tsconfig.json's compilerOptions.paths — the copied source imports the generated Prisma client via this alias, since prisma.config.ts/prisma/ (and the client it generates) install at the project root regardless of where --path put everything else",
107
107
  "Run: npx prisma generate",
108
108
  "Run: npx prisma migrate deploy",
109
- "Seed RBAC: add \"seed\": \"tsx src/lib/auth/src/seed.ts\" to your package.json scripts (adjust the path if you installed elsewhere), then run: npm run seed — it is idempotent, and seeds the permission catalog and the default admin/member roles. Nothing is authorized until it has run at least once.",
109
+ "Seed RBAC: add \"seed\": \"tsx src/lib/auth/seed.ts\" to your package.json scripts (adjust the path if you installed elsewhere), then run: npm run seed — it is idempotent, and seeds the permission catalog and the default admin/member roles. Nothing is authorized until it has run at least once.",
110
110
  "Optional: set SEED_ADMIN_EMAIL and SEED_ADMIN_PASSWORD before seeding to also create an initial admin user. Without them the seeder skips the admin rather than inventing a password.",
111
111
  "Call createAuthApp({...}) and mount or listen on the Express app it returns",
112
112
  "The OpenAPI spec this combo hand-authors is already served at /docs and /docs-json — see examples/express-prisma-app/src/main.ts for a working example"
@@ -136,7 +136,7 @@
136
136
  "Set DATABASE_URL in .env (see .env.example)",
137
137
  "Set AUTH_JWT_SECRET in .env — a base64-encoded 256-bit (or longer) secret, e.g.: openssl rand -base64 32. The app refuses to start without it; there is no default and nothing is generated for you. Changing it invalidates every outstanding token.",
138
138
  "Run: npx drizzle-kit migrate — drizzle.config.ts installs at the project root, alongside drizzle/, so no cd is needed",
139
- "Seed RBAC: add \"seed\": \"tsx src/lib/auth/src/seed.ts\" to your package.json scripts (adjust the path if you installed elsewhere), then run: npm run seed — it is idempotent, and seeds the permission catalog and the default admin/member roles. Nothing is authorized until it has run at least once.",
139
+ "Seed RBAC: add \"seed\": \"tsx src/lib/auth/seed.ts\" to your package.json scripts (adjust the path if you installed elsewhere), then run: npm run seed — it is idempotent, and seeds the permission catalog and the default admin/member roles. Nothing is authorized until it has run at least once.",
140
140
  "Optional: set SEED_ADMIN_EMAIL and SEED_ADMIN_PASSWORD before seeding to also create an initial admin user. Without them the seeder skips the admin rather than inventing a password.",
141
141
  "Call createAuthApp({...}) and mount or listen on the Express app it returns",
142
142
  "The OpenAPI spec this combo hand-authors is already served at /docs and /docs-json — see examples/express-drizzle-app/src/main.ts for a working example"
@@ -445,24 +445,28 @@ function printPostInstallNotes(combo: ComboEntry, variant: string) {
445
445
  *
446
446
  * `generatedClientImport`: only Prisma has this — its schema's `generator { output }` is relative
447
447
  * to schema.prisma's own location, so moving prisma.config.ts/prisma/ to the project root also
448
- * moves the generated client. The registry source's `src/*.ts` files import it as
449
- * `"../generated/prisma/client.js"` — correct for the registry's own dev/typecheck loop (where
450
- * nothing has moved) and installMerge rewrites that literal string in every copied file to
451
- * `@/prisma/client.js` instead of a computed relative path: the generated client always lands at
452
- * the project root regardless of how deep the importing file sits (unlike `@/lib/auth/core`,
453
- * whose depth actually varies with `--path`), so a fixed alias is both simpler and stable across
448
+ * moves the generated client. The registry source's `src/**\/*.ts` files import it with whatever
449
+ * relative depth is correct from their own location — `"../generated/prisma/client.js"` for a
450
+ * top-level file, `"../../generated/prisma/client.js"` for one nested a level deeper in
451
+ * controllers/services/repositories/etc all correct for the registry's own dev/typecheck loop
452
+ * (where nothing has moved). installMerge rewrites *any* such relative import (matched by regex,
453
+ * not a fixed-depth literal string a real bug here once: a plain string match only ever
454
+ * matched the top-level depth, leaving a stray leading "../" on every nested file's rewritten
455
+ * import) to the fixed alias `@/prisma/client.js`: the generated client always lands at the
456
+ * project root regardless of how deep the importing file sits (unlike `@/lib/auth/core`, whose
457
+ * depth actually varies with `--path`), so a fixed alias is both simpler and stable across
454
458
  * installs. The consumer adds `"@/prisma/*": ["./generated/prisma/*"]` to their tsconfig `paths`
455
459
  * once (see postInstall) — the CLI never writes tsconfig itself, same as `@/lib/auth`. Drizzle has
456
460
  * no equivalent generated artifact to redirect. */
457
461
  const ORM_LAYOUTS: {
458
462
  configFile: string;
459
463
  dataDir: string;
460
- generatedClientImport?: string;
464
+ generatedClientImport?: RegExp;
461
465
  }[] = [
462
466
  {
463
467
  configFile: "prisma.config.ts",
464
468
  dataDir: "prisma",
465
- generatedClientImport: "../generated/prisma/client.js",
469
+ generatedClientImport: /(?:\.\/)?(?:\.\.\/)+generated\/prisma\/client\.js/g,
466
470
  },
467
471
  { configFile: "drizzle.config.ts", dataDir: "drizzle" },
468
472
  ];
@@ -476,7 +480,7 @@ interface MergePlan {
476
480
  orm: (typeof ORM_LAYOUTS)[number] | null;
477
481
  skipFromShared: Set<string>;
478
482
  skipFromVariant: Set<string>;
479
- extraRewrites: { from: string; to: string }[] | undefined;
483
+ extraRewrites: { from: string | RegExp; to: string }[] | undefined;
480
484
  config: SimpleAuthKitConfig;
481
485
  previous: Record<string, string>;
482
486
  }
@@ -588,17 +592,44 @@ async function runMergeCopy(
588
592
  plan.destRoot,
589
593
  result,
590
594
  );
595
+
596
+ // shared/src/ and variants/<variant>/src/ are the registry's own internal source layout
597
+ // (parallel to core/, prisma.config.ts, etc.) — copied flat into destRoot rather than
598
+ // preserving that extra "src" level, so a consumer who installed at the default
599
+ // "src/lib/auth" doesn't end up with a redundant "src/lib/auth/src/...". Order matters:
600
+ // shared fully applied (its own src/, then everything else in shared/), then the variant
601
+ // overlaid the same way, so a variant file with the same name still wins.
602
+ const skipShared = new Set([...plan.skipFromShared, "src"]);
603
+ const skipVariant = new Set([...plan.skipFromVariant, "src"]);
604
+ if (await pathExists(join(plan.sharedDir, "src"))) {
605
+ await copyDir(
606
+ join(plan.sharedDir, "src"),
607
+ plan.destRoot,
608
+ { ...copyOpts, neverCopy: plan.skipFromShared },
609
+ plan.destRoot,
610
+ result,
611
+ );
612
+ }
591
613
  await copyDir(
592
614
  plan.sharedDir,
593
615
  plan.destRoot,
594
- { ...copyOpts, neverCopy: plan.skipFromShared },
616
+ { ...copyOpts, neverCopy: skipShared },
595
617
  plan.destRoot,
596
618
  result,
597
619
  );
620
+ if (await pathExists(join(plan.variantDir, "src"))) {
621
+ await copyDir(
622
+ join(plan.variantDir, "src"),
623
+ plan.destRoot,
624
+ { ...copyOpts, neverCopy: plan.skipFromVariant },
625
+ plan.destRoot,
626
+ result,
627
+ );
628
+ }
598
629
  await copyDir(
599
630
  plan.variantDir,
600
631
  plan.destRoot,
601
- { ...copyOpts, neverCopy: plan.skipFromVariant },
632
+ { ...copyOpts, neverCopy: skipVariant },
602
633
  plan.destRoot,
603
634
  result,
604
635
  );
@@ -1,266 +0,0 @@
1
- import type { NextFunction, Request, RequestHandler, Response } from "express";
2
- import type { AuthConfig } from "./auth.config.js";
3
- import { AuthService } from "./auth.service.js";
4
- import { HttpError } from "./http-error.js";
5
- import { authenticated, createTieredRouter, publicRoute, type TierMiddleware } from "./route-tiers.js";
6
- import "./request-context.js";
7
-
8
- function requireString(value: unknown, field: string): string {
9
- if (typeof value !== "string" || value.length === 0) throw new HttpError(400, `${field} is required`);
10
- return value;
11
- }
12
-
13
- const optionalString = (value: unknown): string | undefined => (typeof value === "string" ? value : undefined);
14
-
15
- export interface AuthRouterDeps extends TierMiddleware {
16
- auth: AuthService;
17
- config: AuthConfig;
18
- }
19
-
20
- /**
21
- * Identity endpoints — everything that is about *a user*, never about a group of them.
22
- * Administration lives in admin.router.ts.
23
- *
24
- * Replaces the reference combo's `AuthController` (a Nest `@Controller`). Same endpoints, same
25
- * validation, same status codes — just an Express `Router()` instead of a decorated class, and
26
- * middleware passed in where the controller lists `@UseGuards(...)`. Every handler is wrapped in
27
- * try/catch + `next(err)` so a thrown `HttpError`/`AuthCoreError` reaches the error-handling
28
- * middleware mounted in create-auth-app.ts instead of leaking a bare 500.
29
- */
30
- export function createAuthRouter(deps: AuthRouterDeps): RequestHandler {
31
- const { auth, config, authentication, authorization } = deps;
32
- const router = createTieredRouter({ authentication, authorization });
33
-
34
- router.route("post", "/signup", publicRoute(), async (req: Request, res: Response, next: NextFunction) => {
35
- try {
36
- const body = req.body as Record<string, unknown>;
37
- const tokens = await auth.signup({
38
- email: requireString(body.email, "email"),
39
- password: requireString(body.password, "password"),
40
- firstName: optionalString(body.firstName),
41
- lastName: optionalString(body.lastName),
42
- displayName: optionalString(body.displayName),
43
- phone: optionalString(body.phone),
44
- username: optionalString(body.username),
45
- userAgent: req.headers["user-agent"],
46
- ip: req.ip,
47
- });
48
- res.status(201).json(tokens);
49
- } catch (err) {
50
- next(err);
51
- }
52
- });
53
-
54
- router.route("post", "/login", publicRoute(), async (req: Request, res: Response, next: NextFunction) => {
55
- try {
56
- const body = req.body as Record<string, unknown>;
57
- const result = await auth.login({
58
- identifier: requireString(body.identifier, "identifier"),
59
- password: requireString(body.password, "password"),
60
- userAgent: req.headers["user-agent"],
61
- ip: req.ip,
62
- });
63
- res.status(200).json(result);
64
- } catch (err) {
65
- next(err);
66
- }
67
- });
68
-
69
- router.route("post", "/login/2fa", publicRoute(), async (req: Request, res: Response, next: NextFunction) => {
70
- try {
71
- const body = req.body as Record<string, unknown>;
72
- const tokens = await auth.loginTwoFactor({
73
- challengeToken: requireString(body.challengeToken, "challengeToken"),
74
- code: requireString(body.code, "code"),
75
- userAgent: req.headers["user-agent"],
76
- ip: req.ip,
77
- });
78
- res.status(200).json(tokens);
79
- } catch (err) {
80
- next(err);
81
- }
82
- });
83
-
84
- router.route("post", "/refresh", publicRoute(), async (req: Request, res: Response, next: NextFunction) => {
85
- try {
86
- const body = req.body as Record<string, unknown>;
87
- const tokens = await auth.refresh(requireString(body.refreshToken, "refreshToken"));
88
- res.status(200).json(tokens);
89
- } catch (err) {
90
- next(err);
91
- }
92
- });
93
-
94
- // `roles`/`permissions` are whatever applies to this request — see authz.middleware.ts for how
95
- // they are resolved. A request that names no authorization scope gets empty arrays.
96
- //
97
- // `permissions` is the caller's whole authorization: `defineAbilitiesFor(permissions)` from
98
- // ability.ts rebuilds, in the client, the identical CASL ability the server's own route check
99
- // just answered with, so the UI cannot disagree with what the API will allow. The slugs go on
100
- // the wire rather than a serialised ability, so the payload is something a human can read in a
101
- // log — and it is the same `req.authz` the ability was built from, not a re-resolution.
102
- router.route("get", "/me", authenticated(), async (req: Request, res: Response, next: NextFunction) => {
103
- try {
104
- const { sub, sessionId } = req.auth!;
105
- const [{ twoFactorEnabled }, profile] = await Promise.all([auth.getTwoFactorStatus(sub), auth.getProfile(sub)]);
106
- res.status(200).json({
107
- sub,
108
- sessionId,
109
- roles: req.authz?.roles ?? [],
110
- permissions: req.authz?.permissions ?? [],
111
- twoFactorEnabled,
112
- email: profile.email,
113
- firstName: profile.firstName,
114
- lastName: profile.lastName,
115
- displayName: profile.displayName,
116
- phone: profile.phone,
117
- username: profile.username,
118
- photo: profile.photo,
119
- });
120
- } catch (err) {
121
- next(err);
122
- }
123
- });
124
-
125
- router.route("get", "/sessions", authenticated(), async (req: Request, res: Response, next: NextFunction) => {
126
- try {
127
- const sessions = await auth.listActiveSessions(req.auth!.sub);
128
- res.status(200).json(sessions);
129
- } catch (err) {
130
- next(err);
131
- }
132
- });
133
-
134
- router.route("post", "/logout", authenticated(), async (req: Request, res: Response, next: NextFunction) => {
135
- try {
136
- const { sessionId, jti, sub } = req.auth!;
137
- // Recorded so the row can later be told apart from a session an administrator ended —
138
- // see `sessions.revoked_by`.
139
- await auth.logout(sessionId, jti, config.accessTokenTtlSeconds, { userId: sub, ip: req.ip });
140
- res.status(200).json({ ok: true });
141
- } catch (err) {
142
- next(err);
143
- }
144
- });
145
-
146
- router.route("post", "/logout-all", authenticated(), async (req: Request, res: Response, next: NextFunction) => {
147
- try {
148
- await auth.logoutAll(req.auth!.sub, { userId: req.auth!.sub, ip: req.ip });
149
- res.status(200).json({ ok: true });
150
- } catch (err) {
151
- next(err);
152
- }
153
- });
154
-
155
- router.route("post", "/logout-others", authenticated(), async (req: Request, res: Response, next: NextFunction) => {
156
- try {
157
- await auth.logoutOthers(req.auth!.sub, req.auth!.sessionId, { userId: req.auth!.sub, ip: req.ip });
158
- res.status(200).json({ ok: true });
159
- } catch (err) {
160
- next(err);
161
- }
162
- });
163
-
164
- router.route("post", "/password/change", authenticated(), async (req: Request, res: Response, next: NextFunction) => {
165
- try {
166
- const body = req.body as Record<string, unknown>;
167
- await auth.changePassword(req.auth!.sub, req.auth!.sessionId, {
168
- currentPassword: requireString(body.currentPassword, "currentPassword"),
169
- newPassword: requireString(body.newPassword, "newPassword"),
170
- });
171
- res.status(200).json({ ok: true });
172
- } catch (err) {
173
- next(err);
174
- }
175
- });
176
-
177
- router.route("patch", "/me", authenticated(), async (req: Request, res: Response, next: NextFunction) => {
178
- try {
179
- const body = req.body as Record<string, unknown>;
180
- const profile = await auth.updateProfile(req.auth!.sub, {
181
- firstName: body.firstName === null ? null : optionalString(body.firstName),
182
- lastName: body.lastName === null ? null : optionalString(body.lastName),
183
- displayName: body.displayName === null ? null : optionalString(body.displayName),
184
- phone: body.phone === null ? null : optionalString(body.phone),
185
- username: body.username === null ? null : optionalString(body.username),
186
- photo: body.photo === null ? null : optionalString(body.photo),
187
- });
188
- res.status(200).json(profile);
189
- } catch (err) {
190
- next(err);
191
- }
192
- });
193
-
194
- router.route("post", "/2fa/enroll", authenticated(), async (req: Request, res: Response, next: NextFunction) => {
195
- try {
196
- const result = await auth.enrollTwoFactor(req.auth!.sub);
197
- res.status(200).json(result);
198
- } catch (err) {
199
- next(err);
200
- }
201
- });
202
-
203
- router.route("post", "/2fa/confirm", authenticated(), async (req: Request, res: Response, next: NextFunction) => {
204
- try {
205
- const body = req.body as Record<string, unknown>;
206
- const result = await auth.confirmTwoFactor(req.auth!.sub, requireString(body.code, "code"));
207
- res.status(200).json(result);
208
- } catch (err) {
209
- next(err);
210
- }
211
- });
212
-
213
- router.route("post", "/2fa/disable", authenticated(), async (req: Request, res: Response, next: NextFunction) => {
214
- try {
215
- const body = req.body as Record<string, unknown>;
216
- await auth.disableTwoFactor(req.auth!.sub, requireString(body.code, "code"));
217
- res.status(200).json({ ok: true });
218
- } catch (err) {
219
- next(err);
220
- }
221
- });
222
-
223
- router.route("post", "/password/forgot", publicRoute(), async (req: Request, res: Response, next: NextFunction) => {
224
- try {
225
- const body = req.body as Record<string, unknown>;
226
- await auth.requestPasswordReset(requireString(body.email, "email"));
227
- res.status(200).json({ ok: true }); // always 200 — never reveal whether the email exists
228
- } catch (err) {
229
- next(err);
230
- }
231
- });
232
-
233
- router.route("post", "/password/reset", publicRoute(), async (req: Request, res: Response, next: NextFunction) => {
234
- try {
235
- const body = req.body as Record<string, unknown>;
236
- await auth.resetPassword(requireString(body.token, "token"), requireString(body.newPassword, "newPassword"));
237
- res.status(200).json({ ok: true });
238
- } catch (err) {
239
- next(err);
240
- }
241
- });
242
-
243
- router.route("get", "/oauth/:provider/start", publicRoute(), async (req: Request, res: Response, next: NextFunction) => {
244
- try {
245
- const result = await auth.startOAuth(requireString(req.params.provider, "provider"));
246
- res.status(200).json(result);
247
- } catch (err) {
248
- next(err);
249
- }
250
- });
251
-
252
- router.route("get", "/oauth/:provider/callback", publicRoute(), async (req: Request, res: Response, next: NextFunction) => {
253
- try {
254
- const tokens = await auth.completeOAuthCallback(
255
- requireString(req.params.provider, "provider"),
256
- requireString(req.query.code, "code"),
257
- requireString(req.query.state, "state"),
258
- );
259
- res.status(200).json(tokens);
260
- } catch (err) {
261
- next(err);
262
- }
263
- });
264
-
265
- return router.handler;
266
- }
@@ -1,35 +0,0 @@
1
- import { eq } from "drizzle-orm";
2
- import type { NodePgDatabase } from "drizzle-orm/node-postgres";
3
- import type { PasswordResetStoreDeps } from "@/lib/auth/core/password-reset.js";
4
- import * as schema from "./schema.js";
5
- import { toId } from "./id.helper.js";
6
-
7
- /**
8
- * Plain class, no decorators — Drizzle-specific implementation of `PasswordResetStoreDeps`
9
- * (registry/core/password-reset.js), mirroring the reference combo's PasswordResetRepository.
10
- */
11
- export class PasswordResetRepository implements PasswordResetStoreDeps {
12
- constructor(private readonly db: NodePgDatabase<typeof schema>) {}
13
-
14
- async saveResetToken(input: { userId: string; tokenHash: string; expiresAt: string }): Promise<void> {
15
- await this.db.insert(schema.passwordResetTokens).values({
16
- userId: toId(input.userId),
17
- tokenHash: input.tokenHash,
18
- expiresAt: new Date(input.expiresAt),
19
- });
20
- }
21
-
22
- async findValidResetToken(tokenHash: string): Promise<{ userId: string; expiresAt: string; consumedAt: string | null } | null> {
23
- const [row] = await this.db.select().from(schema.passwordResetTokens).where(eq(schema.passwordResetTokens.tokenHash, tokenHash)).limit(1);
24
- if (!row) return null;
25
- return { userId: row.userId.toString(), expiresAt: row.expiresAt.toISOString(), consumedAt: row.consumedAt?.toISOString() ?? null };
26
- }
27
-
28
- async consumeResetToken(tokenHash: string): Promise<void> {
29
- await this.db.update(schema.passwordResetTokens).set({ consumedAt: new Date() }).where(eq(schema.passwordResetTokens.tokenHash, tokenHash));
30
- }
31
-
32
- async setPasswordHash(userId: string, passwordHash: string): Promise<void> {
33
- await this.db.update(schema.users).set({ passwordHash }).where(eq(schema.users.id, toId(userId)));
34
- }
35
- }