@simple-auth-kit/cli 1.4.3 → 1.5.0
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/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 +57 -22
- 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 +67 -19
- 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 +78 -20
- 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 +81 -26
- 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 +73 -21
- 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 +109 -29
- 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 +59 -28
- 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 +98 -27
- 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 +78 -20
- 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 +55 -27
- 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 +104 -29
- 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 +109 -29
- package/registry/combos/nestjs-prisma/variants/workspaces/src/services/auth.service.ts +961 -0
- package/registry.json +4 -4
- package/simple-auth-kit.ts +29 -2
- 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
|
@@ -588,17 +588,44 @@ async function runMergeCopy(
|
|
|
588
588
|
plan.destRoot,
|
|
589
589
|
result,
|
|
590
590
|
);
|
|
591
|
+
|
|
592
|
+
// shared/src/ and variants/<variant>/src/ are the registry's own internal source layout
|
|
593
|
+
// (parallel to core/, prisma.config.ts, etc.) — copied flat into destRoot rather than
|
|
594
|
+
// preserving that extra "src" level, so a consumer who installed at the default
|
|
595
|
+
// "src/lib/auth" doesn't end up with a redundant "src/lib/auth/src/...". Order matters:
|
|
596
|
+
// shared fully applied (its own src/, then everything else in shared/), then the variant
|
|
597
|
+
// overlaid the same way, so a variant file with the same name still wins.
|
|
598
|
+
const skipShared = new Set([...plan.skipFromShared, "src"]);
|
|
599
|
+
const skipVariant = new Set([...plan.skipFromVariant, "src"]);
|
|
600
|
+
if (await pathExists(join(plan.sharedDir, "src"))) {
|
|
601
|
+
await copyDir(
|
|
602
|
+
join(plan.sharedDir, "src"),
|
|
603
|
+
plan.destRoot,
|
|
604
|
+
{ ...copyOpts, neverCopy: plan.skipFromShared },
|
|
605
|
+
plan.destRoot,
|
|
606
|
+
result,
|
|
607
|
+
);
|
|
608
|
+
}
|
|
591
609
|
await copyDir(
|
|
592
610
|
plan.sharedDir,
|
|
593
611
|
plan.destRoot,
|
|
594
|
-
{ ...copyOpts, neverCopy:
|
|
612
|
+
{ ...copyOpts, neverCopy: skipShared },
|
|
595
613
|
plan.destRoot,
|
|
596
614
|
result,
|
|
597
615
|
);
|
|
616
|
+
if (await pathExists(join(plan.variantDir, "src"))) {
|
|
617
|
+
await copyDir(
|
|
618
|
+
join(plan.variantDir, "src"),
|
|
619
|
+
plan.destRoot,
|
|
620
|
+
{ ...copyOpts, neverCopy: plan.skipFromVariant },
|
|
621
|
+
plan.destRoot,
|
|
622
|
+
result,
|
|
623
|
+
);
|
|
624
|
+
}
|
|
598
625
|
await copyDir(
|
|
599
626
|
plan.variantDir,
|
|
600
627
|
plan.destRoot,
|
|
601
|
-
{ ...copyOpts, neverCopy:
|
|
628
|
+
{ ...copyOpts, neverCopy: skipVariant },
|
|
602
629
|
plan.destRoot,
|
|
603
630
|
result,
|
|
604
631
|
);
|
|
@@ -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
|
-
}
|