@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.
Files changed (133) hide show
  1. package/package.json +1 -1
  2. package/registry/combos/express-drizzle/shared/src/{auth-core-error.middleware.ts → middleware/auth-core-error.middleware.ts} +17 -4
  3. package/registry/combos/express-drizzle/shared/src/{auth.middleware.ts → middleware/auth.middleware.ts} +23 -7
  4. package/registry/combos/{express-prisma/shared/src → express-drizzle/shared/src/middleware}/response-envelope.middleware.ts +15 -3
  5. package/registry/combos/express-drizzle/shared/src/{oauth.repository.ts → repositories/oauth.repository.ts} +32 -9
  6. package/registry/combos/express-drizzle/shared/src/repositories/password-reset.repository.ts +59 -0
  7. package/registry/combos/express-drizzle/shared/src/{session.repository.ts → repositories/session.repository.ts} +52 -14
  8. package/registry/combos/express-drizzle/shared/src/{two-factor.repository.ts → repositories/two-factor.repository.ts} +20 -6
  9. package/registry/combos/express-drizzle/shared/src/request-context.ts +1 -1
  10. package/registry/combos/express-drizzle/shared/src/routers/auth.router.ts +395 -0
  11. package/registry/combos/express-drizzle/variants/base/src/create-auth-app.ts +52 -19
  12. package/registry/combos/express-drizzle/variants/base/src/{authz.middleware.ts → middleware/authz.middleware.ts} +15 -7
  13. package/registry/combos/express-drizzle/variants/base/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +28 -10
  14. package/registry/combos/express-drizzle/variants/base/src/{rbac.repository.ts → repositories/rbac.repository.ts} +239 -48
  15. package/registry/combos/express-drizzle/variants/base/src/routers/admin.router.ts +497 -0
  16. package/registry/combos/express-drizzle/variants/base/src/{auth.service.ts → services/auth.service.ts} +480 -109
  17. package/registry/combos/express-drizzle/variants/workspaces/src/create-auth-app.ts +69 -23
  18. package/registry/combos/express-drizzle/variants/workspaces/src/{authz.middleware.ts → middleware/authz.middleware.ts} +32 -12
  19. package/registry/combos/express-drizzle/variants/workspaces/src/openapi-workspace.ts +147 -28
  20. package/registry/combos/express-drizzle/variants/workspaces/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +30 -11
  21. package/registry/combos/express-drizzle/variants/workspaces/src/{rbac.repository.ts → repositories/rbac.repository.ts} +319 -70
  22. package/registry/combos/express-drizzle/variants/workspaces/src/{workspace.repository.ts → repositories/workspace.repository.ts} +127 -30
  23. package/registry/combos/express-drizzle/variants/workspaces/src/routers/admin.router.ts +534 -0
  24. package/registry/combos/express-drizzle/variants/workspaces/src/routers/workspace.router.ts +194 -0
  25. package/registry/combos/express-drizzle/variants/workspaces/src/{auth.service.ts → services/auth.service.ts} +544 -122
  26. package/registry/combos/express-prisma/shared/src/{auth-core-error.middleware.ts → middleware/auth-core-error.middleware.ts} +17 -4
  27. package/registry/combos/express-prisma/shared/src/{auth.middleware.ts → middleware/auth.middleware.ts} +19 -6
  28. package/registry/combos/{express-drizzle/shared/src → express-prisma/shared/src/middleware}/response-envelope.middleware.ts +15 -3
  29. package/registry/combos/express-prisma/shared/src/{oauth.repository.ts → repositories/oauth.repository.ts} +27 -9
  30. package/registry/combos/express-prisma/shared/src/repositories/password-reset.repository.ts +55 -0
  31. package/registry/combos/express-prisma/shared/src/{session.repository.ts → repositories/session.repository.ts} +30 -9
  32. package/registry/combos/express-prisma/shared/src/{two-factor.repository.ts → repositories/two-factor.repository.ts} +11 -4
  33. package/registry/combos/express-prisma/shared/src/request-context.ts +1 -1
  34. package/registry/combos/express-prisma/shared/src/routers/auth.router.ts +398 -0
  35. package/registry/combos/express-prisma/variants/base/src/create-auth-app.ts +57 -22
  36. package/registry/combos/express-prisma/variants/base/src/{authz.middleware.ts → middleware/authz.middleware.ts} +15 -7
  37. package/registry/combos/express-prisma/variants/base/src/rbac.defaults.ts +67 -19
  38. package/registry/combos/express-prisma/variants/base/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +16 -5
  39. package/registry/combos/express-prisma/variants/base/src/{rbac.repository.ts → repositories/rbac.repository.ts} +199 -48
  40. package/registry/combos/express-prisma/variants/base/src/routers/admin.router.ts +508 -0
  41. package/registry/combos/express-prisma/variants/base/src/seed.ts +78 -20
  42. package/registry/combos/express-prisma/variants/base/src/{auth.service.ts → services/auth.service.ts} +454 -105
  43. package/registry/combos/express-prisma/variants/workspaces/src/create-auth-app.ts +81 -26
  44. package/registry/combos/express-prisma/variants/workspaces/src/{authz.middleware.ts → middleware/authz.middleware.ts} +32 -12
  45. package/registry/combos/express-prisma/variants/workspaces/src/openapi-workspace.ts +147 -28
  46. package/registry/combos/express-prisma/variants/workspaces/src/rbac.defaults.ts +73 -21
  47. package/registry/combos/express-prisma/variants/workspaces/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +16 -5
  48. package/registry/combos/express-prisma/variants/workspaces/src/{rbac.repository.ts → repositories/rbac.repository.ts} +272 -64
  49. package/registry/combos/express-prisma/variants/workspaces/src/{workspace.repository.ts → repositories/workspace.repository.ts} +86 -23
  50. package/registry/combos/express-prisma/variants/workspaces/src/routers/admin.router.ts +545 -0
  51. package/registry/combos/express-prisma/variants/workspaces/src/routers/workspace.router.ts +194 -0
  52. package/registry/combos/express-prisma/variants/workspaces/src/seed.ts +109 -29
  53. package/registry/combos/express-prisma/variants/workspaces/src/{auth.service.ts → services/auth.service.ts} +518 -119
  54. package/registry/combos/nestjs-drizzle/shared/src/{auth.controller.ts → controllers/auth.controller.ts} +6 -6
  55. package/registry/combos/nestjs-drizzle/shared/src/{auth-core-error.filter.ts → filters/auth-core-error.filter.ts} +29 -5
  56. package/registry/combos/nestjs-drizzle/shared/src/{ability.guard.ts → guards/ability.guard.ts} +20 -7
  57. package/registry/combos/nestjs-drizzle/shared/src/{auth.guard.ts → guards/auth.guard.ts} +19 -7
  58. package/registry/combos/{nestjs-prisma/shared/src → nestjs-drizzle/shared/src/interceptors}/response.interceptor.ts +10 -2
  59. package/registry/combos/nestjs-drizzle/shared/src/repositories/oauth.repository.ts +63 -0
  60. package/registry/combos/nestjs-drizzle/shared/src/repositories/password-reset.repository.ts +59 -0
  61. package/registry/combos/nestjs-drizzle/shared/src/{session.repository.ts → repositories/session.repository.ts} +52 -14
  62. package/registry/combos/nestjs-drizzle/shared/src/{two-factor.repository.ts → repositories/two-factor.repository.ts} +21 -7
  63. package/registry/combos/nestjs-drizzle/shared/src/request-context.ts +1 -1
  64. package/registry/combos/nestjs-drizzle/test/prove-cycle.ts +1097 -307
  65. package/registry/combos/nestjs-drizzle/variants/base/src/auth.module.ts +44 -20
  66. package/registry/combos/nestjs-drizzle/variants/base/src/{admin.controller.ts → controllers/admin.controller.ts} +228 -62
  67. package/registry/combos/nestjs-drizzle/variants/base/src/{authz.guard.ts → guards/authz.guard.ts} +12 -5
  68. package/registry/combos/nestjs-drizzle/variants/base/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +27 -10
  69. package/registry/combos/nestjs-drizzle/variants/base/src/{rbac.repository.ts → repositories/rbac.repository.ts} +249 -51
  70. package/registry/combos/nestjs-drizzle/variants/base/src/{auth.service.ts → services/auth.service.ts} +499 -116
  71. package/registry/combos/nestjs-drizzle/variants/workspaces/src/auth.module.ts +48 -22
  72. package/registry/combos/nestjs-drizzle/variants/workspaces/src/{admin.controller.ts → controllers/admin.controller.ts} +269 -70
  73. package/registry/combos/nestjs-drizzle/variants/workspaces/src/{workspace.controller.ts → controllers/workspace.controller.ts} +93 -26
  74. package/registry/combos/nestjs-drizzle/variants/workspaces/src/{authz.guard.ts → guards/authz.guard.ts} +26 -8
  75. package/registry/combos/nestjs-drizzle/variants/workspaces/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +29 -11
  76. package/registry/combos/nestjs-drizzle/variants/workspaces/src/{rbac.repository.ts → repositories/rbac.repository.ts} +323 -71
  77. package/registry/combos/nestjs-drizzle/variants/workspaces/src/{workspace.repository.ts → repositories/workspace.repository.ts} +131 -30
  78. package/registry/combos/nestjs-drizzle/variants/workspaces/src/services/auth.service.ts +988 -0
  79. package/registry/combos/nestjs-prisma/shared/src/{auth.controller.ts → controllers/auth.controller.ts} +6 -6
  80. package/registry/combos/nestjs-prisma/shared/src/{auth-core-error.filter.ts → filters/auth-core-error.filter.ts} +29 -5
  81. package/registry/combos/nestjs-prisma/shared/src/{ability.guard.ts → guards/ability.guard.ts} +20 -7
  82. package/registry/combos/nestjs-prisma/shared/src/{auth.guard.ts → guards/auth.guard.ts} +19 -7
  83. package/registry/combos/{nestjs-drizzle/shared/src → nestjs-prisma/shared/src/interceptors}/response.interceptor.ts +10 -2
  84. package/registry/combos/nestjs-prisma/shared/src/{oauth.repository.ts → repositories/oauth.repository.ts} +25 -8
  85. package/registry/combos/nestjs-prisma/shared/src/repositories/password-reset.repository.ts +55 -0
  86. package/registry/combos/nestjs-prisma/shared/src/{session.repository.ts → repositories/session.repository.ts} +30 -9
  87. package/registry/combos/nestjs-prisma/shared/src/{two-factor.repository.ts → repositories/two-factor.repository.ts} +11 -4
  88. package/registry/combos/nestjs-prisma/shared/src/request-context.ts +1 -1
  89. package/registry/combos/nestjs-prisma/test/prove-cycle.ts +1097 -307
  90. package/registry/combos/nestjs-prisma/variants/base/src/auth.module.ts +59 -28
  91. package/registry/combos/nestjs-prisma/variants/base/src/{admin.controller.ts → controllers/admin.controller.ts} +469 -110
  92. package/registry/combos/nestjs-prisma/variants/base/src/{audit-log.gateway.ts → gateways/audit-log.gateway.ts} +22 -6
  93. package/registry/combos/nestjs-prisma/variants/base/src/{authz.guard.ts → guards/authz.guard.ts} +12 -5
  94. package/registry/combos/nestjs-prisma/variants/base/src/rbac.defaults.ts +98 -27
  95. package/registry/combos/nestjs-prisma/variants/base/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +16 -5
  96. package/registry/combos/nestjs-prisma/variants/base/src/{country.repository.ts → repositories/country.repository.ts} +98 -25
  97. package/registry/combos/nestjs-prisma/variants/base/src/{customer.repository.ts → repositories/customer.repository.ts} +106 -27
  98. package/registry/combos/nestjs-prisma/variants/base/src/{language.repository.ts → repositories/language.repository.ts} +93 -25
  99. package/registry/combos/nestjs-prisma/variants/base/src/{rbac.repository.ts → repositories/rbac.repository.ts} +229 -55
  100. package/registry/combos/nestjs-prisma/variants/base/src/seed.ts +78 -20
  101. package/registry/combos/nestjs-prisma/variants/base/src/services/auth.service.ts +1064 -0
  102. package/registry/combos/nestjs-prisma/variants/workspaces/src/auth.module.ts +55 -27
  103. package/registry/combos/nestjs-prisma/variants/workspaces/src/{admin.controller.ts → controllers/admin.controller.ts} +544 -128
  104. package/registry/combos/nestjs-prisma/variants/workspaces/src/{workspace.controller.ts → controllers/workspace.controller.ts} +93 -26
  105. package/registry/combos/nestjs-prisma/variants/workspaces/src/{authz.guard.ts → guards/authz.guard.ts} +26 -8
  106. package/registry/combos/nestjs-prisma/variants/workspaces/src/rbac.defaults.ts +104 -29
  107. package/registry/combos/nestjs-prisma/variants/workspaces/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +16 -5
  108. package/registry/combos/nestjs-prisma/variants/workspaces/src/{country.repository.ts → repositories/country.repository.ts} +127 -26
  109. package/registry/combos/nestjs-prisma/variants/workspaces/src/{customer.repository.ts → repositories/customer.repository.ts} +137 -28
  110. package/registry/combos/nestjs-prisma/variants/workspaces/src/repositories/language.repository.ts +243 -0
  111. package/registry/combos/nestjs-prisma/variants/workspaces/src/{rbac.repository.ts → repositories/rbac.repository.ts} +314 -74
  112. package/registry/combos/nestjs-prisma/variants/workspaces/src/{workspace.repository.ts → repositories/workspace.repository.ts} +94 -24
  113. package/registry/combos/nestjs-prisma/variants/workspaces/src/seed.ts +109 -29
  114. package/registry/combos/nestjs-prisma/variants/workspaces/src/services/auth.service.ts +961 -0
  115. package/registry.json +4 -4
  116. package/simple-auth-kit.ts +29 -2
  117. package/registry/combos/express-drizzle/shared/src/auth.router.ts +0 -266
  118. package/registry/combos/express-drizzle/shared/src/password-reset.repository.ts +0 -35
  119. package/registry/combos/express-drizzle/variants/base/src/admin.router.ts +0 -307
  120. package/registry/combos/express-drizzle/variants/workspaces/src/admin.router.ts +0 -323
  121. package/registry/combos/express-drizzle/variants/workspaces/src/workspace.router.ts +0 -123
  122. package/registry/combos/express-prisma/shared/src/auth.router.ts +0 -269
  123. package/registry/combos/express-prisma/shared/src/password-reset.repository.ts +0 -29
  124. package/registry/combos/express-prisma/variants/base/src/admin.router.ts +0 -314
  125. package/registry/combos/express-prisma/variants/workspaces/src/admin.router.ts +0 -330
  126. package/registry/combos/express-prisma/variants/workspaces/src/workspace.router.ts +0 -123
  127. package/registry/combos/nestjs-drizzle/shared/src/oauth.repository.ts +0 -36
  128. package/registry/combos/nestjs-drizzle/shared/src/password-reset.repository.ts +0 -29
  129. package/registry/combos/nestjs-drizzle/variants/workspaces/src/auth.service.ts +0 -553
  130. package/registry/combos/nestjs-prisma/shared/src/password-reset.repository.ts +0 -29
  131. package/registry/combos/nestjs-prisma/variants/base/src/auth.service.ts +0 -618
  132. package/registry/combos/nestjs-prisma/variants/workspaces/src/auth.service.ts +0 -509
  133. package/registry/combos/nestjs-prisma/variants/workspaces/src/language.repository.ts +0 -143
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@simple-auth-kit/cli",
3
- "version": "1.4.3",
3
+ "version": "1.5.0",
4
4
  "license": "MIT",
5
5
  "description": "simple-auth-kit add <combo> — copies registry source (backend combos, admin consoles, mobile apps) into a consumer repo, shadcn-CLI-style. Nothing is ever installed as a runtime dependency of the consumer.",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  import type { NextFunction, Request, Response } from "express";
2
2
  import { AuthCoreError } from "@/lib/auth/core/types.js";
3
- import { HttpError } from "./http-error.js";
3
+ import { HttpError } from "../http-error.js";
4
4
 
5
5
  /**
6
6
  * Replaces the reference combo's `AuthCoreErrorFilter` (a Nest global `@Catch` exception
@@ -11,19 +11,32 @@ import { HttpError } from "./http-error.js";
11
11
  * maps this combo's own `HttpError` to its intended status code — the plain-Express stand-in
12
12
  * for the NestJS exception classes the reference throws.
13
13
  */
14
- export function authCoreErrorMiddleware(err: unknown, _req: Request, res: Response, next: NextFunction): void {
14
+ export function authCoreErrorMiddleware(
15
+ err: unknown,
16
+ _req: Request,
17
+ res: Response,
18
+ next: NextFunction,
19
+ ): void {
15
20
  if (res.headersSent) {
16
21
  next(err);
17
22
  return;
18
23
  }
19
24
 
20
25
  if (err instanceof AuthCoreError) {
21
- res.status(401).json({ statusCode: 401, code: err.code, message: err.message });
26
+ res
27
+ .status(401)
28
+ .json({ statusCode: 401, code: err.code, message: err.message });
22
29
  return;
23
30
  }
24
31
 
25
32
  if (err instanceof HttpError) {
26
- res.status(err.statusCode).json({ statusCode: err.statusCode, code: err.name, message: err.message });
33
+ res
34
+ .status(err.statusCode)
35
+ .json({
36
+ statusCode: err.statusCode,
37
+ code: err.name,
38
+ message: err.message,
39
+ });
27
40
  return;
28
41
  }
29
42
 
@@ -1,8 +1,8 @@
1
1
  import type { NextFunction, Request, Response } from "express";
2
2
  import { verifyAccessToken } from "@/lib/auth/core/token-service.js";
3
- import { KeyProviderService } from "./key-provider.js";
4
- import "./request-context.js";
5
- import { SessionRepository } from "./session.repository.js";
3
+ import { KeyProviderService } from "../key-provider.js";
4
+ import "../request-context.js";
5
+ import { SessionRepository } from "../repositories/session.repository.js";
6
6
 
7
7
  /**
8
8
  * Authentication only — proves who the caller is and populates `req.auth`. Authorization
@@ -13,17 +13,33 @@ import { SessionRepository } from "./session.repository.js";
13
13
  * `CanActivate`). Express has no guard/decorator system, so this is a plain factory function
14
14
  * returning a request handler — mount it directly on whichever routes need it.
15
15
  */
16
- export function createAuthMiddleware(keys: KeyProviderService, sessions: SessionRepository) {
17
- return async function authMiddleware(req: Request, res: Response, next: NextFunction): Promise<void> {
16
+ export function createAuthMiddleware(
17
+ keys: KeyProviderService,
18
+ sessions: SessionRepository,
19
+ ) {
20
+ return async function authMiddleware(
21
+ req: Request,
22
+ res: Response,
23
+ next: NextFunction,
24
+ ): Promise<void> {
18
25
  const header = req.headers["authorization"];
19
26
  if (!header?.startsWith("Bearer ")) {
20
- res.status(401).json({ statusCode: 401, code: "UNAUTHORIZED", message: "missing bearer token" });
27
+ res
28
+ .status(401)
29
+ .json({
30
+ statusCode: 401,
31
+ code: "UNAUTHORIZED",
32
+ message: "missing bearer token",
33
+ });
21
34
  return;
22
35
  }
23
36
 
24
37
  try {
25
38
  req.auth = await verifyAccessToken(
26
- { secret: keys.secret, isDenylisted: (jti) => sessions.isDenylisted(jti) },
39
+ {
40
+ secret: keys.secret,
41
+ isDenylisted: (jti) => sessions.isDenylisted(jti),
42
+ },
27
43
  header.slice("Bearer ".length),
28
44
  );
29
45
  next();
@@ -36,10 +36,22 @@ export function responseEnvelope(): RequestHandler {
36
36
  const originalJson = res.json.bind(res);
37
37
  res.json = ((body: unknown) => {
38
38
  if (res.statusCode >= 400) {
39
- const shaped = body && typeof body === "object" ? (body as Record<string, unknown>) : { message: body };
40
- return originalJson({ success: false, statusCode: res.statusCode, ...shaped });
39
+ const shaped =
40
+ body && typeof body === "object"
41
+ ? (body as Record<string, unknown>)
42
+ : { message: body };
43
+ return originalJson({
44
+ success: false,
45
+ statusCode: res.statusCode,
46
+ ...shaped,
47
+ });
41
48
  }
42
- return originalJson({ success: true, statusCode: res.statusCode, message: deriveMessage(req.method), data: body });
49
+ return originalJson({
50
+ success: true,
51
+ statusCode: res.statusCode,
52
+ message: deriveMessage(req.method),
53
+ data: body,
54
+ });
43
55
  }) as typeof res.json;
44
56
  next();
45
57
  };
@@ -1,9 +1,9 @@
1
1
  import { and, eq } from "drizzle-orm";
2
2
  import type { NodePgDatabase } from "drizzle-orm/node-postgres";
3
3
  import type { OAuthStoreDeps } from "@/lib/auth/core/oauth.js";
4
- import { HttpError } from "./http-error.js";
5
- import * as schema from "./schema.js";
6
- import { toId } from "./id.helper.js";
4
+ import { HttpError } from "../http-error.js";
5
+ import * as schema from "../schema.js";
6
+ import { toId } from "../id.helper.js";
7
7
 
8
8
  /**
9
9
  * Plain class, no decorators — Drizzle-specific implementation of `OAuthStoreDeps`
@@ -12,16 +12,29 @@ import { toId } from "./id.helper.js";
12
12
  export class OAuthRepository implements OAuthStoreDeps {
13
13
  constructor(private readonly db: NodePgDatabase<typeof schema>) {}
14
14
 
15
- async findAccountByProvider(provider: string, providerAccountId: string): Promise<{ userId: string } | null> {
15
+ async findAccountByProvider(
16
+ provider: string,
17
+ providerAccountId: string,
18
+ ): Promise<{ userId: string } | null> {
16
19
  const [row] = await this.db
17
20
  .select()
18
21
  .from(schema.oauthAccounts)
19
- .where(and(eq(schema.oauthAccounts.provider, provider), eq(schema.oauthAccounts.providerAccountId, providerAccountId)))
22
+ .where(
23
+ and(
24
+ eq(schema.oauthAccounts.provider, provider),
25
+ eq(schema.oauthAccounts.providerAccountId, providerAccountId),
26
+ ),
27
+ )
20
28
  .limit(1);
21
29
  return row ? { userId: row.userId.toString() } : null;
22
30
  }
23
31
 
24
- async linkAccount(input: { userId: string; provider: string; providerAccountId: string; email?: string }): Promise<void> {
32
+ async linkAccount(input: {
33
+ userId: string;
34
+ provider: string;
35
+ providerAccountId: string;
36
+ email?: string;
37
+ }): Promise<void> {
25
38
  await this.db.insert(schema.oauthAccounts).values({
26
39
  userId: toId(input.userId),
27
40
  provider: input.provider,
@@ -31,13 +44,23 @@ export class OAuthRepository implements OAuthStoreDeps {
31
44
  }
32
45
 
33
46
  async findUserByVerifiedEmail(email: string): Promise<{ id: string } | null> {
34
- const [row] = await this.db.select().from(schema.users).where(eq(schema.users.email, email)).limit(1);
47
+ const [row] = await this.db
48
+ .select()
49
+ .from(schema.users)
50
+ .where(eq(schema.users.email, email))
51
+ .limit(1);
35
52
  return row ? { id: row.id.toString() } : null;
36
53
  }
37
54
 
38
55
  /** OAuth-only signup: no password is ever set, which is why `User.passwordHash` is nullable. */
39
- async createUserFromOAuth(input: { email?: string }): Promise<{ id: string }> {
40
- if (!input.email) throw new HttpError(400, "OAuth provider did not return an email address");
56
+ async createUserFromOAuth(input: {
57
+ email?: string;
58
+ }): Promise<{ id: string }> {
59
+ if (!input.email)
60
+ throw new HttpError(
61
+ 400,
62
+ "OAuth provider did not return an email address",
63
+ );
41
64
  const [row] = await this.db
42
65
  .insert(schema.users)
43
66
  .values({ email: input.email, passwordHash: null })
@@ -0,0 +1,59 @@
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: {
15
+ userId: string;
16
+ tokenHash: string;
17
+ expiresAt: string;
18
+ }): Promise<void> {
19
+ await this.db.insert(schema.passwordResetTokens).values({
20
+ userId: toId(input.userId),
21
+ tokenHash: input.tokenHash,
22
+ expiresAt: new Date(input.expiresAt),
23
+ });
24
+ }
25
+
26
+ async findValidResetToken(
27
+ tokenHash: string,
28
+ ): Promise<{
29
+ userId: string;
30
+ expiresAt: string;
31
+ consumedAt: string | null;
32
+ } | null> {
33
+ const [row] = await this.db
34
+ .select()
35
+ .from(schema.passwordResetTokens)
36
+ .where(eq(schema.passwordResetTokens.tokenHash, tokenHash))
37
+ .limit(1);
38
+ if (!row) return null;
39
+ return {
40
+ userId: row.userId.toString(),
41
+ expiresAt: row.expiresAt.toISOString(),
42
+ consumedAt: row.consumedAt?.toISOString() ?? null,
43
+ };
44
+ }
45
+
46
+ async consumeResetToken(tokenHash: string): Promise<void> {
47
+ await this.db
48
+ .update(schema.passwordResetTokens)
49
+ .set({ consumedAt: new Date() })
50
+ .where(eq(schema.passwordResetTokens.tokenHash, tokenHash));
51
+ }
52
+
53
+ async setPasswordHash(userId: string, passwordHash: string): Promise<void> {
54
+ await this.db
55
+ .update(schema.users)
56
+ .set({ passwordHash })
57
+ .where(eq(schema.users.id, toId(userId)));
58
+ }
59
+ }
@@ -1,12 +1,16 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import { and, eq, isNull, ne, sql } from "drizzle-orm";
3
3
  import type { SessionStoreDeps } from "@/lib/auth/core/session-policy.js";
4
- import type { AuditEvent, Revoker, SessionRecord } from "@/lib/auth/core/types.js";
4
+ import type {
5
+ AuditEvent,
6
+ Revoker,
7
+ SessionRecord,
8
+ } from "@/lib/auth/core/types.js";
5
9
  import { AuditLogRepository } from "./audit-log.repository.js";
6
- import type { AuthConfig } from "./auth.config.js";
7
- import type { Database } from "./db.js";
8
- import { denylistedAccessTokens, sessions } from "./schema.js";
9
- import { toId, toIdOrNull } from "./id.helper.js";
10
+ import type { AuthConfig } from "../auth.config.js";
11
+ import type { Database } from "../db.js";
12
+ import { denylistedAccessTokens, sessions } from "../schema.js";
13
+ import { toId, toIdOrNull } from "../id.helper.js";
10
14
 
11
15
  function toSessionRecord(row: typeof sessions.$inferSelect): SessionRecord {
12
16
  return {
@@ -53,7 +57,11 @@ export class SessionRepository implements SessionStoreDeps {
53
57
  }
54
58
 
55
59
  async findSession(id: string): Promise<SessionRecord | null> {
56
- const [row] = await this.db.select().from(sessions).where(eq(sessions.id, toId(id))).limit(1);
60
+ const [row] = await this.db
61
+ .select()
62
+ .from(sessions)
63
+ .where(eq(sessions.id, toId(id)))
64
+ .limit(1);
57
65
  return row ? toSessionRecord(row) : null;
58
66
  }
59
67
 
@@ -81,7 +89,12 @@ export class SessionRepository implements SessionStoreDeps {
81
89
  * `createdBy` is the user themselves. A session is only ever created by the person logging in,
82
90
  * so recording it costs nothing and leaves the column meaningful rather than always null.
83
91
  */
84
- async createSession(input: { userId: string; userAgent?: string; ip?: string; provider?: string }): Promise<SessionRecord> {
92
+ async createSession(input: {
93
+ userId: string;
94
+ userAgent?: string;
95
+ ip?: string;
96
+ provider?: string;
97
+ }): Promise<SessionRecord> {
85
98
  const userIdBig = toId(input.userId);
86
99
  const [row] = await this.db
87
100
  .insert(sessions)
@@ -102,15 +115,33 @@ export class SessionRepository implements SessionStoreDeps {
102
115
  async revokeAllByUser(userId: string, revoker?: Revoker): Promise<void> {
103
116
  await this.db
104
117
  .update(sessions)
105
- .set({ ...revocationFields(revoker), sessionVersion: sql`${sessions.sessionVersion} + 1` })
106
- .where(and(eq(sessions.userId, toId(userId)), isNull(sessions.revokedAt)));
118
+ .set({
119
+ ...revocationFields(revoker),
120
+ sessionVersion: sql`${sessions.sessionVersion} + 1`,
121
+ })
122
+ .where(
123
+ and(eq(sessions.userId, toId(userId)), isNull(sessions.revokedAt)),
124
+ );
107
125
  }
108
126
 
109
- async revokeAllByUserExcept(userId: string, keepSessionId: string, revoker?: Revoker): Promise<void> {
127
+ async revokeAllByUserExcept(
128
+ userId: string,
129
+ keepSessionId: string,
130
+ revoker?: Revoker,
131
+ ): Promise<void> {
110
132
  await this.db
111
133
  .update(sessions)
112
- .set({ ...revocationFields(revoker), sessionVersion: sql`${sessions.sessionVersion} + 1` })
113
- .where(and(eq(sessions.userId, toId(userId)), ne(sessions.id, toId(keepSessionId)), isNull(sessions.revokedAt)));
134
+ .set({
135
+ ...revocationFields(revoker),
136
+ sessionVersion: sql`${sessions.sessionVersion} + 1`,
137
+ })
138
+ .where(
139
+ and(
140
+ eq(sessions.userId, toId(userId)),
141
+ ne(sessions.id, toId(keepSessionId)),
142
+ isNull(sessions.revokedAt),
143
+ ),
144
+ );
114
145
  }
115
146
 
116
147
  async denylistJti(jti: string, ttlSeconds: number): Promise<void> {
@@ -118,11 +149,18 @@ export class SessionRepository implements SessionStoreDeps {
118
149
  await this.db
119
150
  .insert(denylistedAccessTokens)
120
151
  .values({ jti, expiresAt })
121
- .onConflictDoUpdate({ target: denylistedAccessTokens.jti, set: { expiresAt } });
152
+ .onConflictDoUpdate({
153
+ target: denylistedAccessTokens.jti,
154
+ set: { expiresAt },
155
+ });
122
156
  }
123
157
 
124
158
  async isDenylisted(jti: string): Promise<boolean> {
125
- const [row] = await this.db.select().from(denylistedAccessTokens).where(eq(denylistedAccessTokens.jti, jti)).limit(1);
159
+ const [row] = await this.db
160
+ .select()
161
+ .from(denylistedAccessTokens)
162
+ .where(eq(denylistedAccessTokens.jti, jti))
163
+ .limit(1);
126
164
  if (!row) return false;
127
165
  if (row.expiresAt.getTime() < Date.now()) return false; // expired; a cleanup job reaps these, see Phase 6
128
166
  return true;
@@ -1,7 +1,7 @@
1
1
  import { and, eq, isNull } from "drizzle-orm";
2
2
  import type { NodePgDatabase } from "drizzle-orm/node-postgres";
3
3
  import { verifyBackupCode } from "@/lib/auth/core/two-factor.js";
4
- import * as schema from "./schema.js";
4
+ import * as schema from "../schema.js";
5
5
 
6
6
  /**
7
7
  * Plain class, no decorators — Drizzle-specific implementation mirroring the reference
@@ -15,24 +15,38 @@ export class TwoFactorRepository {
15
15
 
16
16
  /** Replaces any prior set — re-confirming enrollment invalidates old backup codes. */
17
17
  async saveBackupCodes(userId: bigint, hashes: string[]): Promise<void> {
18
- await this.db.delete(schema.twoFactorBackupCodes).where(eq(schema.twoFactorBackupCodes.userId, userId));
18
+ await this.db
19
+ .delete(schema.twoFactorBackupCodes)
20
+ .where(eq(schema.twoFactorBackupCodes.userId, userId));
19
21
  if (hashes.length === 0) return;
20
- await this.db.insert(schema.twoFactorBackupCodes).values(hashes.map((codeHash) => ({ userId, codeHash })));
22
+ await this.db
23
+ .insert(schema.twoFactorBackupCodes)
24
+ .values(hashes.map((codeHash) => ({ userId, codeHash })));
21
25
  }
22
26
 
23
27
  async consumeBackupCode(userId: bigint, code: string): Promise<boolean> {
24
28
  const unused = await this.db
25
29
  .select()
26
30
  .from(schema.twoFactorBackupCodes)
27
- .where(and(eq(schema.twoFactorBackupCodes.userId, userId), isNull(schema.twoFactorBackupCodes.usedAt)));
31
+ .where(
32
+ and(
33
+ eq(schema.twoFactorBackupCodes.userId, userId),
34
+ isNull(schema.twoFactorBackupCodes.usedAt),
35
+ ),
36
+ );
28
37
  const match = unused.find((row) => verifyBackupCode(code, row.codeHash));
29
38
  if (!match) return false;
30
39
 
31
- await this.db.update(schema.twoFactorBackupCodes).set({ usedAt: new Date() }).where(eq(schema.twoFactorBackupCodes.id, match.id));
40
+ await this.db
41
+ .update(schema.twoFactorBackupCodes)
42
+ .set({ usedAt: new Date() })
43
+ .where(eq(schema.twoFactorBackupCodes.id, match.id));
32
44
  return true;
33
45
  }
34
46
 
35
47
  async clearBackupCodes(userId: bigint): Promise<void> {
36
- await this.db.delete(schema.twoFactorBackupCodes).where(eq(schema.twoFactorBackupCodes.userId, userId));
48
+ await this.db
49
+ .delete(schema.twoFactorBackupCodes)
50
+ .where(eq(schema.twoFactorBackupCodes.userId, userId));
37
51
  }
38
52
  }
@@ -20,7 +20,7 @@
20
20
  // `declare module "express"`.
21
21
  import type { AccessTokenClaims } from "@/lib/auth/core/types.js";
22
22
  import type { AppAbility } from "./ability.js";
23
- import type { AuthzContext } from "./authz.middleware.js";
23
+ import type { AuthzContext } from "./middleware/authz.middleware.js";
24
24
 
25
25
  declare global {
26
26
  // Express's own declaration-merging style for augmenting its Request type requires a