@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.
- package/lib/copy.ts +1 -1
- package/package.json +1 -1
- package/registry/combos/express-drizzle/shared/src/{auth-core-error.middleware.ts → middleware/auth-core-error.middleware.ts} +17 -4
- package/registry/combos/express-drizzle/shared/src/{auth.middleware.ts → middleware/auth.middleware.ts} +23 -7
- package/registry/combos/{express-prisma/shared/src → express-drizzle/shared/src/middleware}/response-envelope.middleware.ts +15 -3
- package/registry/combos/express-drizzle/shared/src/{oauth.repository.ts → repositories/oauth.repository.ts} +32 -9
- package/registry/combos/express-drizzle/shared/src/repositories/password-reset.repository.ts +59 -0
- package/registry/combos/express-drizzle/shared/src/{session.repository.ts → repositories/session.repository.ts} +52 -14
- package/registry/combos/express-drizzle/shared/src/{two-factor.repository.ts → repositories/two-factor.repository.ts} +20 -6
- package/registry/combos/express-drizzle/shared/src/request-context.ts +1 -1
- package/registry/combos/express-drizzle/shared/src/routers/auth.router.ts +395 -0
- package/registry/combos/express-drizzle/variants/base/src/create-auth-app.ts +52 -19
- package/registry/combos/express-drizzle/variants/base/src/{authz.middleware.ts → middleware/authz.middleware.ts} +15 -7
- package/registry/combos/express-drizzle/variants/base/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +28 -10
- package/registry/combos/express-drizzle/variants/base/src/{rbac.repository.ts → repositories/rbac.repository.ts} +239 -48
- package/registry/combos/express-drizzle/variants/base/src/routers/admin.router.ts +497 -0
- package/registry/combos/express-drizzle/variants/base/src/{auth.service.ts → services/auth.service.ts} +480 -109
- package/registry/combos/express-drizzle/variants/workspaces/src/create-auth-app.ts +69 -23
- package/registry/combos/express-drizzle/variants/workspaces/src/{authz.middleware.ts → middleware/authz.middleware.ts} +32 -12
- package/registry/combos/express-drizzle/variants/workspaces/src/openapi-workspace.ts +147 -28
- package/registry/combos/express-drizzle/variants/workspaces/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +30 -11
- package/registry/combos/express-drizzle/variants/workspaces/src/{rbac.repository.ts → repositories/rbac.repository.ts} +319 -70
- package/registry/combos/express-drizzle/variants/workspaces/src/{workspace.repository.ts → repositories/workspace.repository.ts} +127 -30
- package/registry/combos/express-drizzle/variants/workspaces/src/routers/admin.router.ts +534 -0
- package/registry/combos/express-drizzle/variants/workspaces/src/routers/workspace.router.ts +194 -0
- package/registry/combos/express-drizzle/variants/workspaces/src/{auth.service.ts → services/auth.service.ts} +544 -122
- package/registry/combos/express-prisma/shared/src/{auth-core-error.middleware.ts → middleware/auth-core-error.middleware.ts} +17 -4
- package/registry/combos/express-prisma/shared/src/{auth.middleware.ts → middleware/auth.middleware.ts} +19 -6
- package/registry/combos/{express-drizzle/shared/src → express-prisma/shared/src/middleware}/response-envelope.middleware.ts +15 -3
- package/registry/combos/express-prisma/shared/src/{oauth.repository.ts → repositories/oauth.repository.ts} +27 -9
- package/registry/combos/express-prisma/shared/src/repositories/password-reset.repository.ts +55 -0
- package/registry/combos/express-prisma/shared/src/{session.repository.ts → repositories/session.repository.ts} +30 -9
- package/registry/combos/express-prisma/shared/src/{two-factor.repository.ts → repositories/two-factor.repository.ts} +11 -4
- package/registry/combos/express-prisma/shared/src/request-context.ts +1 -1
- package/registry/combos/express-prisma/shared/src/routers/auth.router.ts +398 -0
- package/registry/combos/express-prisma/variants/base/src/create-auth-app.ts +56 -21
- package/registry/combos/express-prisma/variants/base/src/{authz.middleware.ts → middleware/authz.middleware.ts} +15 -7
- package/registry/combos/express-prisma/variants/base/src/rbac.defaults.ts +66 -18
- package/registry/combos/express-prisma/variants/base/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +16 -5
- package/registry/combos/express-prisma/variants/base/src/{rbac.repository.ts → repositories/rbac.repository.ts} +199 -48
- package/registry/combos/express-prisma/variants/base/src/routers/admin.router.ts +508 -0
- package/registry/combos/express-prisma/variants/base/src/seed.ts +77 -19
- package/registry/combos/express-prisma/variants/base/src/{auth.service.ts → services/auth.service.ts} +454 -105
- package/registry/combos/express-prisma/variants/workspaces/src/create-auth-app.ts +80 -25
- package/registry/combos/express-prisma/variants/workspaces/src/{authz.middleware.ts → middleware/authz.middleware.ts} +32 -12
- package/registry/combos/express-prisma/variants/workspaces/src/openapi-workspace.ts +147 -28
- package/registry/combos/express-prisma/variants/workspaces/src/rbac.defaults.ts +72 -20
- package/registry/combos/express-prisma/variants/workspaces/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +16 -5
- package/registry/combos/express-prisma/variants/workspaces/src/{rbac.repository.ts → repositories/rbac.repository.ts} +272 -64
- package/registry/combos/express-prisma/variants/workspaces/src/{workspace.repository.ts → repositories/workspace.repository.ts} +86 -23
- package/registry/combos/express-prisma/variants/workspaces/src/routers/admin.router.ts +545 -0
- package/registry/combos/express-prisma/variants/workspaces/src/routers/workspace.router.ts +194 -0
- package/registry/combos/express-prisma/variants/workspaces/src/seed.ts +108 -28
- package/registry/combos/express-prisma/variants/workspaces/src/{auth.service.ts → services/auth.service.ts} +518 -119
- package/registry/combos/nestjs-drizzle/shared/src/{auth.controller.ts → controllers/auth.controller.ts} +6 -6
- package/registry/combos/nestjs-drizzle/shared/src/{auth-core-error.filter.ts → filters/auth-core-error.filter.ts} +29 -5
- package/registry/combos/nestjs-drizzle/shared/src/{ability.guard.ts → guards/ability.guard.ts} +20 -7
- package/registry/combos/nestjs-drizzle/shared/src/{auth.guard.ts → guards/auth.guard.ts} +19 -7
- package/registry/combos/{nestjs-prisma/shared/src → nestjs-drizzle/shared/src/interceptors}/response.interceptor.ts +10 -2
- package/registry/combos/nestjs-drizzle/shared/src/repositories/oauth.repository.ts +63 -0
- package/registry/combos/nestjs-drizzle/shared/src/repositories/password-reset.repository.ts +59 -0
- package/registry/combos/nestjs-drizzle/shared/src/{session.repository.ts → repositories/session.repository.ts} +52 -14
- package/registry/combos/nestjs-drizzle/shared/src/{two-factor.repository.ts → repositories/two-factor.repository.ts} +21 -7
- package/registry/combos/nestjs-drizzle/shared/src/request-context.ts +1 -1
- package/registry/combos/nestjs-drizzle/test/prove-cycle.ts +1097 -307
- package/registry/combos/nestjs-drizzle/variants/base/src/auth.module.ts +44 -20
- package/registry/combos/nestjs-drizzle/variants/base/src/{admin.controller.ts → controllers/admin.controller.ts} +228 -62
- package/registry/combos/nestjs-drizzle/variants/base/src/{authz.guard.ts → guards/authz.guard.ts} +12 -5
- package/registry/combos/nestjs-drizzle/variants/base/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +27 -10
- package/registry/combos/nestjs-drizzle/variants/base/src/{rbac.repository.ts → repositories/rbac.repository.ts} +249 -51
- package/registry/combos/nestjs-drizzle/variants/base/src/{auth.service.ts → services/auth.service.ts} +499 -116
- package/registry/combos/nestjs-drizzle/variants/workspaces/src/auth.module.ts +48 -22
- package/registry/combos/nestjs-drizzle/variants/workspaces/src/{admin.controller.ts → controllers/admin.controller.ts} +269 -70
- package/registry/combos/nestjs-drizzle/variants/workspaces/src/{workspace.controller.ts → controllers/workspace.controller.ts} +93 -26
- package/registry/combos/nestjs-drizzle/variants/workspaces/src/{authz.guard.ts → guards/authz.guard.ts} +26 -8
- package/registry/combos/nestjs-drizzle/variants/workspaces/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +29 -11
- package/registry/combos/nestjs-drizzle/variants/workspaces/src/{rbac.repository.ts → repositories/rbac.repository.ts} +323 -71
- package/registry/combos/nestjs-drizzle/variants/workspaces/src/{workspace.repository.ts → repositories/workspace.repository.ts} +131 -30
- package/registry/combos/nestjs-drizzle/variants/workspaces/src/services/auth.service.ts +988 -0
- package/registry/combos/nestjs-prisma/shared/src/{auth.controller.ts → controllers/auth.controller.ts} +6 -6
- package/registry/combos/nestjs-prisma/shared/src/{auth-core-error.filter.ts → filters/auth-core-error.filter.ts} +29 -5
- package/registry/combos/nestjs-prisma/shared/src/{ability.guard.ts → guards/ability.guard.ts} +20 -7
- package/registry/combos/nestjs-prisma/shared/src/{auth.guard.ts → guards/auth.guard.ts} +19 -7
- package/registry/combos/{nestjs-drizzle/shared/src → nestjs-prisma/shared/src/interceptors}/response.interceptor.ts +10 -2
- package/registry/combos/nestjs-prisma/shared/src/{oauth.repository.ts → repositories/oauth.repository.ts} +25 -8
- package/registry/combos/nestjs-prisma/shared/src/repositories/password-reset.repository.ts +55 -0
- package/registry/combos/nestjs-prisma/shared/src/{session.repository.ts → repositories/session.repository.ts} +30 -9
- package/registry/combos/nestjs-prisma/shared/src/{two-factor.repository.ts → repositories/two-factor.repository.ts} +11 -4
- package/registry/combos/nestjs-prisma/shared/src/request-context.ts +1 -1
- package/registry/combos/nestjs-prisma/test/prove-cycle.ts +1097 -307
- package/registry/combos/nestjs-prisma/variants/base/src/auth.module.ts +58 -27
- package/registry/combos/nestjs-prisma/variants/base/src/{admin.controller.ts → controllers/admin.controller.ts} +469 -110
- package/registry/combos/nestjs-prisma/variants/base/src/{audit-log.gateway.ts → gateways/audit-log.gateway.ts} +22 -6
- package/registry/combos/nestjs-prisma/variants/base/src/{authz.guard.ts → guards/authz.guard.ts} +12 -5
- package/registry/combos/nestjs-prisma/variants/base/src/rbac.defaults.ts +97 -26
- package/registry/combos/nestjs-prisma/variants/base/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +16 -5
- package/registry/combos/nestjs-prisma/variants/base/src/{country.repository.ts → repositories/country.repository.ts} +98 -25
- package/registry/combos/nestjs-prisma/variants/base/src/{customer.repository.ts → repositories/customer.repository.ts} +106 -27
- package/registry/combos/nestjs-prisma/variants/base/src/{language.repository.ts → repositories/language.repository.ts} +93 -25
- package/registry/combos/nestjs-prisma/variants/base/src/{rbac.repository.ts → repositories/rbac.repository.ts} +229 -55
- package/registry/combos/nestjs-prisma/variants/base/src/seed.ts +77 -19
- package/registry/combos/nestjs-prisma/variants/base/src/services/auth.service.ts +1064 -0
- package/registry/combos/nestjs-prisma/variants/workspaces/src/auth.module.ts +54 -26
- package/registry/combos/nestjs-prisma/variants/workspaces/src/{admin.controller.ts → controllers/admin.controller.ts} +544 -128
- package/registry/combos/nestjs-prisma/variants/workspaces/src/{workspace.controller.ts → controllers/workspace.controller.ts} +93 -26
- package/registry/combos/nestjs-prisma/variants/workspaces/src/{authz.guard.ts → guards/authz.guard.ts} +26 -8
- package/registry/combos/nestjs-prisma/variants/workspaces/src/rbac.defaults.ts +103 -28
- package/registry/combos/nestjs-prisma/variants/workspaces/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +16 -5
- package/registry/combos/nestjs-prisma/variants/workspaces/src/{country.repository.ts → repositories/country.repository.ts} +127 -26
- package/registry/combos/nestjs-prisma/variants/workspaces/src/{customer.repository.ts → repositories/customer.repository.ts} +137 -28
- package/registry/combos/nestjs-prisma/variants/workspaces/src/repositories/language.repository.ts +243 -0
- package/registry/combos/nestjs-prisma/variants/workspaces/src/{rbac.repository.ts → repositories/rbac.repository.ts} +314 -74
- package/registry/combos/nestjs-prisma/variants/workspaces/src/{workspace.repository.ts → repositories/workspace.repository.ts} +94 -24
- package/registry/combos/nestjs-prisma/variants/workspaces/src/seed.ts +108 -28
- package/registry/combos/nestjs-prisma/variants/workspaces/src/services/auth.service.ts +961 -0
- package/registry.json +4 -4
- package/simple-auth-kit.ts +42 -11
- package/registry/combos/express-drizzle/shared/src/auth.router.ts +0 -266
- package/registry/combos/express-drizzle/shared/src/password-reset.repository.ts +0 -35
- package/registry/combos/express-drizzle/variants/base/src/admin.router.ts +0 -307
- package/registry/combos/express-drizzle/variants/workspaces/src/admin.router.ts +0 -323
- package/registry/combos/express-drizzle/variants/workspaces/src/workspace.router.ts +0 -123
- package/registry/combos/express-prisma/shared/src/auth.router.ts +0 -269
- package/registry/combos/express-prisma/shared/src/password-reset.repository.ts +0 -29
- package/registry/combos/express-prisma/variants/base/src/admin.router.ts +0 -314
- package/registry/combos/express-prisma/variants/workspaces/src/admin.router.ts +0 -330
- package/registry/combos/express-prisma/variants/workspaces/src/workspace.router.ts +0 -123
- package/registry/combos/nestjs-drizzle/shared/src/oauth.repository.ts +0 -36
- package/registry/combos/nestjs-drizzle/shared/src/password-reset.repository.ts +0 -29
- package/registry/combos/nestjs-drizzle/variants/workspaces/src/auth.service.ts +0 -553
- package/registry/combos/nestjs-prisma/shared/src/password-reset.repository.ts +0 -29
- package/registry/combos/nestjs-prisma/variants/base/src/auth.service.ts +0 -618
- package/registry/combos/nestjs-prisma/variants/workspaces/src/auth.service.ts +0 -509
- 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/
|
|
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/
|
|
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/
|
|
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/
|
|
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"
|
package/simple-auth-kit.ts
CHANGED
|
@@ -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
|
|
449
|
-
* `"../generated/prisma/client.js"`
|
|
450
|
-
*
|
|
451
|
-
*
|
|
452
|
-
*
|
|
453
|
-
*
|
|
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?:
|
|
464
|
+
generatedClientImport?: RegExp;
|
|
461
465
|
}[] = [
|
|
462
466
|
{
|
|
463
467
|
configFile: "prisma.config.ts",
|
|
464
468
|
dataDir: "prisma",
|
|
465
|
-
generatedClientImport:
|
|
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:
|
|
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:
|
|
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
|
-
}
|