@simple-auth-kit/cli 1.4.3 → 1.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. package/lib/copy.ts +1 -1
  2. package/package.json +1 -1
  3. package/registry/combos/express-drizzle/shared/src/{auth-core-error.middleware.ts → middleware/auth-core-error.middleware.ts} +17 -4
  4. package/registry/combos/express-drizzle/shared/src/{auth.middleware.ts → middleware/auth.middleware.ts} +23 -7
  5. package/registry/combos/{express-prisma/shared/src → express-drizzle/shared/src/middleware}/response-envelope.middleware.ts +15 -3
  6. package/registry/combos/express-drizzle/shared/src/{oauth.repository.ts → repositories/oauth.repository.ts} +32 -9
  7. package/registry/combos/express-drizzle/shared/src/repositories/password-reset.repository.ts +59 -0
  8. package/registry/combos/express-drizzle/shared/src/{session.repository.ts → repositories/session.repository.ts} +52 -14
  9. package/registry/combos/express-drizzle/shared/src/{two-factor.repository.ts → repositories/two-factor.repository.ts} +20 -6
  10. package/registry/combos/express-drizzle/shared/src/request-context.ts +1 -1
  11. package/registry/combos/express-drizzle/shared/src/routers/auth.router.ts +395 -0
  12. package/registry/combos/express-drizzle/variants/base/src/create-auth-app.ts +52 -19
  13. package/registry/combos/express-drizzle/variants/base/src/{authz.middleware.ts → middleware/authz.middleware.ts} +15 -7
  14. package/registry/combos/express-drizzle/variants/base/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +28 -10
  15. package/registry/combos/express-drizzle/variants/base/src/{rbac.repository.ts → repositories/rbac.repository.ts} +239 -48
  16. package/registry/combos/express-drizzle/variants/base/src/routers/admin.router.ts +497 -0
  17. package/registry/combos/express-drizzle/variants/base/src/{auth.service.ts → services/auth.service.ts} +480 -109
  18. package/registry/combos/express-drizzle/variants/workspaces/src/create-auth-app.ts +69 -23
  19. package/registry/combos/express-drizzle/variants/workspaces/src/{authz.middleware.ts → middleware/authz.middleware.ts} +32 -12
  20. package/registry/combos/express-drizzle/variants/workspaces/src/openapi-workspace.ts +147 -28
  21. package/registry/combos/express-drizzle/variants/workspaces/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +30 -11
  22. package/registry/combos/express-drizzle/variants/workspaces/src/{rbac.repository.ts → repositories/rbac.repository.ts} +319 -70
  23. package/registry/combos/express-drizzle/variants/workspaces/src/{workspace.repository.ts → repositories/workspace.repository.ts} +127 -30
  24. package/registry/combos/express-drizzle/variants/workspaces/src/routers/admin.router.ts +534 -0
  25. package/registry/combos/express-drizzle/variants/workspaces/src/routers/workspace.router.ts +194 -0
  26. package/registry/combos/express-drizzle/variants/workspaces/src/{auth.service.ts → services/auth.service.ts} +544 -122
  27. package/registry/combos/express-prisma/shared/src/{auth-core-error.middleware.ts → middleware/auth-core-error.middleware.ts} +17 -4
  28. package/registry/combos/express-prisma/shared/src/{auth.middleware.ts → middleware/auth.middleware.ts} +19 -6
  29. package/registry/combos/{express-drizzle/shared/src → express-prisma/shared/src/middleware}/response-envelope.middleware.ts +15 -3
  30. package/registry/combos/express-prisma/shared/src/{oauth.repository.ts → repositories/oauth.repository.ts} +27 -9
  31. package/registry/combos/express-prisma/shared/src/repositories/password-reset.repository.ts +55 -0
  32. package/registry/combos/express-prisma/shared/src/{session.repository.ts → repositories/session.repository.ts} +30 -9
  33. package/registry/combos/express-prisma/shared/src/{two-factor.repository.ts → repositories/two-factor.repository.ts} +11 -4
  34. package/registry/combos/express-prisma/shared/src/request-context.ts +1 -1
  35. package/registry/combos/express-prisma/shared/src/routers/auth.router.ts +398 -0
  36. package/registry/combos/express-prisma/variants/base/src/create-auth-app.ts +56 -21
  37. package/registry/combos/express-prisma/variants/base/src/{authz.middleware.ts → middleware/authz.middleware.ts} +15 -7
  38. package/registry/combos/express-prisma/variants/base/src/rbac.defaults.ts +66 -18
  39. package/registry/combos/express-prisma/variants/base/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +16 -5
  40. package/registry/combos/express-prisma/variants/base/src/{rbac.repository.ts → repositories/rbac.repository.ts} +199 -48
  41. package/registry/combos/express-prisma/variants/base/src/routers/admin.router.ts +508 -0
  42. package/registry/combos/express-prisma/variants/base/src/seed.ts +77 -19
  43. package/registry/combos/express-prisma/variants/base/src/{auth.service.ts → services/auth.service.ts} +454 -105
  44. package/registry/combos/express-prisma/variants/workspaces/src/create-auth-app.ts +80 -25
  45. package/registry/combos/express-prisma/variants/workspaces/src/{authz.middleware.ts → middleware/authz.middleware.ts} +32 -12
  46. package/registry/combos/express-prisma/variants/workspaces/src/openapi-workspace.ts +147 -28
  47. package/registry/combos/express-prisma/variants/workspaces/src/rbac.defaults.ts +72 -20
  48. package/registry/combos/express-prisma/variants/workspaces/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +16 -5
  49. package/registry/combos/express-prisma/variants/workspaces/src/{rbac.repository.ts → repositories/rbac.repository.ts} +272 -64
  50. package/registry/combos/express-prisma/variants/workspaces/src/{workspace.repository.ts → repositories/workspace.repository.ts} +86 -23
  51. package/registry/combos/express-prisma/variants/workspaces/src/routers/admin.router.ts +545 -0
  52. package/registry/combos/express-prisma/variants/workspaces/src/routers/workspace.router.ts +194 -0
  53. package/registry/combos/express-prisma/variants/workspaces/src/seed.ts +108 -28
  54. package/registry/combos/express-prisma/variants/workspaces/src/{auth.service.ts → services/auth.service.ts} +518 -119
  55. package/registry/combos/nestjs-drizzle/shared/src/{auth.controller.ts → controllers/auth.controller.ts} +6 -6
  56. package/registry/combos/nestjs-drizzle/shared/src/{auth-core-error.filter.ts → filters/auth-core-error.filter.ts} +29 -5
  57. package/registry/combos/nestjs-drizzle/shared/src/{ability.guard.ts → guards/ability.guard.ts} +20 -7
  58. package/registry/combos/nestjs-drizzle/shared/src/{auth.guard.ts → guards/auth.guard.ts} +19 -7
  59. package/registry/combos/{nestjs-prisma/shared/src → nestjs-drizzle/shared/src/interceptors}/response.interceptor.ts +10 -2
  60. package/registry/combos/nestjs-drizzle/shared/src/repositories/oauth.repository.ts +63 -0
  61. package/registry/combos/nestjs-drizzle/shared/src/repositories/password-reset.repository.ts +59 -0
  62. package/registry/combos/nestjs-drizzle/shared/src/{session.repository.ts → repositories/session.repository.ts} +52 -14
  63. package/registry/combos/nestjs-drizzle/shared/src/{two-factor.repository.ts → repositories/two-factor.repository.ts} +21 -7
  64. package/registry/combos/nestjs-drizzle/shared/src/request-context.ts +1 -1
  65. package/registry/combos/nestjs-drizzle/test/prove-cycle.ts +1097 -307
  66. package/registry/combos/nestjs-drizzle/variants/base/src/auth.module.ts +44 -20
  67. package/registry/combos/nestjs-drizzle/variants/base/src/{admin.controller.ts → controllers/admin.controller.ts} +228 -62
  68. package/registry/combos/nestjs-drizzle/variants/base/src/{authz.guard.ts → guards/authz.guard.ts} +12 -5
  69. package/registry/combos/nestjs-drizzle/variants/base/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +27 -10
  70. package/registry/combos/nestjs-drizzle/variants/base/src/{rbac.repository.ts → repositories/rbac.repository.ts} +249 -51
  71. package/registry/combos/nestjs-drizzle/variants/base/src/{auth.service.ts → services/auth.service.ts} +499 -116
  72. package/registry/combos/nestjs-drizzle/variants/workspaces/src/auth.module.ts +48 -22
  73. package/registry/combos/nestjs-drizzle/variants/workspaces/src/{admin.controller.ts → controllers/admin.controller.ts} +269 -70
  74. package/registry/combos/nestjs-drizzle/variants/workspaces/src/{workspace.controller.ts → controllers/workspace.controller.ts} +93 -26
  75. package/registry/combos/nestjs-drizzle/variants/workspaces/src/{authz.guard.ts → guards/authz.guard.ts} +26 -8
  76. package/registry/combos/nestjs-drizzle/variants/workspaces/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +29 -11
  77. package/registry/combos/nestjs-drizzle/variants/workspaces/src/{rbac.repository.ts → repositories/rbac.repository.ts} +323 -71
  78. package/registry/combos/nestjs-drizzle/variants/workspaces/src/{workspace.repository.ts → repositories/workspace.repository.ts} +131 -30
  79. package/registry/combos/nestjs-drizzle/variants/workspaces/src/services/auth.service.ts +988 -0
  80. package/registry/combos/nestjs-prisma/shared/src/{auth.controller.ts → controllers/auth.controller.ts} +6 -6
  81. package/registry/combos/nestjs-prisma/shared/src/{auth-core-error.filter.ts → filters/auth-core-error.filter.ts} +29 -5
  82. package/registry/combos/nestjs-prisma/shared/src/{ability.guard.ts → guards/ability.guard.ts} +20 -7
  83. package/registry/combos/nestjs-prisma/shared/src/{auth.guard.ts → guards/auth.guard.ts} +19 -7
  84. package/registry/combos/{nestjs-drizzle/shared/src → nestjs-prisma/shared/src/interceptors}/response.interceptor.ts +10 -2
  85. package/registry/combos/nestjs-prisma/shared/src/{oauth.repository.ts → repositories/oauth.repository.ts} +25 -8
  86. package/registry/combos/nestjs-prisma/shared/src/repositories/password-reset.repository.ts +55 -0
  87. package/registry/combos/nestjs-prisma/shared/src/{session.repository.ts → repositories/session.repository.ts} +30 -9
  88. package/registry/combos/nestjs-prisma/shared/src/{two-factor.repository.ts → repositories/two-factor.repository.ts} +11 -4
  89. package/registry/combos/nestjs-prisma/shared/src/request-context.ts +1 -1
  90. package/registry/combos/nestjs-prisma/test/prove-cycle.ts +1097 -307
  91. package/registry/combos/nestjs-prisma/variants/base/src/auth.module.ts +58 -27
  92. package/registry/combos/nestjs-prisma/variants/base/src/{admin.controller.ts → controllers/admin.controller.ts} +469 -110
  93. package/registry/combos/nestjs-prisma/variants/base/src/{audit-log.gateway.ts → gateways/audit-log.gateway.ts} +22 -6
  94. package/registry/combos/nestjs-prisma/variants/base/src/{authz.guard.ts → guards/authz.guard.ts} +12 -5
  95. package/registry/combos/nestjs-prisma/variants/base/src/rbac.defaults.ts +97 -26
  96. package/registry/combos/nestjs-prisma/variants/base/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +16 -5
  97. package/registry/combos/nestjs-prisma/variants/base/src/{country.repository.ts → repositories/country.repository.ts} +98 -25
  98. package/registry/combos/nestjs-prisma/variants/base/src/{customer.repository.ts → repositories/customer.repository.ts} +106 -27
  99. package/registry/combos/nestjs-prisma/variants/base/src/{language.repository.ts → repositories/language.repository.ts} +93 -25
  100. package/registry/combos/nestjs-prisma/variants/base/src/{rbac.repository.ts → repositories/rbac.repository.ts} +229 -55
  101. package/registry/combos/nestjs-prisma/variants/base/src/seed.ts +77 -19
  102. package/registry/combos/nestjs-prisma/variants/base/src/services/auth.service.ts +1064 -0
  103. package/registry/combos/nestjs-prisma/variants/workspaces/src/auth.module.ts +54 -26
  104. package/registry/combos/nestjs-prisma/variants/workspaces/src/{admin.controller.ts → controllers/admin.controller.ts} +544 -128
  105. package/registry/combos/nestjs-prisma/variants/workspaces/src/{workspace.controller.ts → controllers/workspace.controller.ts} +93 -26
  106. package/registry/combos/nestjs-prisma/variants/workspaces/src/{authz.guard.ts → guards/authz.guard.ts} +26 -8
  107. package/registry/combos/nestjs-prisma/variants/workspaces/src/rbac.defaults.ts +103 -28
  108. package/registry/combos/nestjs-prisma/variants/workspaces/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +16 -5
  109. package/registry/combos/nestjs-prisma/variants/workspaces/src/{country.repository.ts → repositories/country.repository.ts} +127 -26
  110. package/registry/combos/nestjs-prisma/variants/workspaces/src/{customer.repository.ts → repositories/customer.repository.ts} +137 -28
  111. package/registry/combos/nestjs-prisma/variants/workspaces/src/repositories/language.repository.ts +243 -0
  112. package/registry/combos/nestjs-prisma/variants/workspaces/src/{rbac.repository.ts → repositories/rbac.repository.ts} +314 -74
  113. package/registry/combos/nestjs-prisma/variants/workspaces/src/{workspace.repository.ts → repositories/workspace.repository.ts} +94 -24
  114. package/registry/combos/nestjs-prisma/variants/workspaces/src/seed.ts +108 -28
  115. package/registry/combos/nestjs-prisma/variants/workspaces/src/services/auth.service.ts +961 -0
  116. package/registry.json +4 -4
  117. package/simple-auth-kit.ts +42 -11
  118. package/registry/combos/express-drizzle/shared/src/auth.router.ts +0 -266
  119. package/registry/combos/express-drizzle/shared/src/password-reset.repository.ts +0 -35
  120. package/registry/combos/express-drizzle/variants/base/src/admin.router.ts +0 -307
  121. package/registry/combos/express-drizzle/variants/workspaces/src/admin.router.ts +0 -323
  122. package/registry/combos/express-drizzle/variants/workspaces/src/workspace.router.ts +0 -123
  123. package/registry/combos/express-prisma/shared/src/auth.router.ts +0 -269
  124. package/registry/combos/express-prisma/shared/src/password-reset.repository.ts +0 -29
  125. package/registry/combos/express-prisma/variants/base/src/admin.router.ts +0 -314
  126. package/registry/combos/express-prisma/variants/workspaces/src/admin.router.ts +0 -330
  127. package/registry/combos/express-prisma/variants/workspaces/src/workspace.router.ts +0 -123
  128. package/registry/combos/nestjs-drizzle/shared/src/oauth.repository.ts +0 -36
  129. package/registry/combos/nestjs-drizzle/shared/src/password-reset.repository.ts +0 -29
  130. package/registry/combos/nestjs-drizzle/variants/workspaces/src/auth.service.ts +0 -553
  131. package/registry/combos/nestjs-prisma/shared/src/password-reset.repository.ts +0 -29
  132. package/registry/combos/nestjs-prisma/variants/base/src/auth.service.ts +0 -618
  133. package/registry/combos/nestjs-prisma/variants/workspaces/src/auth.service.ts +0 -509
  134. package/registry/combos/nestjs-prisma/variants/workspaces/src/language.repository.ts +0 -143
@@ -22,11 +22,11 @@ import {
22
22
  ApiTags,
23
23
  } from "@nestjs/swagger";
24
24
  import type { Request } from "express";
25
- import { AUTH_CONFIG, AuthConfig } from "./auth.config.js";
26
- import { AuthGuard } from "./auth.guard.js";
27
- import { AuthService } from "./auth.service.js";
28
- import { AuthzGuard } from "./authz.guard.js";
29
- import { Authenticated, Public } from "./route-tiers.js";
25
+ import { AUTH_CONFIG, AuthConfig } from "../auth.config.js";
26
+ import { AuthGuard } from "../guards/auth.guard.js";
27
+ import { AuthService } from "../services/auth.service.js";
28
+ import { AuthzGuard } from "../guards/authz.guard.js";
29
+ import { Authenticated, Public } from "../route-tiers.js";
30
30
  import {
31
31
  AuthTokensDto,
32
32
  ChangePasswordDto,
@@ -46,7 +46,7 @@ import {
46
46
  SignupDto,
47
47
  TwoFactorCodeDto,
48
48
  UpdateUserDto,
49
- } from "./dto/auth.dto.js";
49
+ } from "../dto/auth.dto.js";
50
50
 
51
51
  function requireString(value: unknown, field: string): string {
52
52
  if (typeof value !== "string" || value.length === 0)
@@ -1,4 +1,10 @@
1
- import { ArgumentsHost, Catch, ExceptionFilter, HttpException, HttpStatus } from "@nestjs/common";
1
+ import {
2
+ ArgumentsHost,
3
+ Catch,
4
+ ExceptionFilter,
5
+ HttpException,
6
+ HttpStatus,
7
+ } from "@nestjs/common";
2
8
  import { AuthCoreError } from "@/lib/auth/core/types.js";
3
9
 
4
10
  /**
@@ -14,19 +20,37 @@ export class AuthCoreErrorFilter implements ExceptionFilter {
14
20
  const res = host.switchToHttp().getResponse();
15
21
 
16
22
  if (exception instanceof AuthCoreError) {
17
- res.status(HttpStatus.UNAUTHORIZED).json({ success: false, statusCode: HttpStatus.UNAUTHORIZED, code: exception.code, message: exception.message });
23
+ res
24
+ .status(HttpStatus.UNAUTHORIZED)
25
+ .json({
26
+ success: false,
27
+ statusCode: HttpStatus.UNAUTHORIZED,
28
+ code: exception.code,
29
+ message: exception.message,
30
+ });
18
31
  return;
19
32
  }
20
33
 
21
34
  if (exception instanceof HttpException) {
22
35
  const status = exception.getStatus();
23
36
  const body = exception.getResponse();
24
- const shaped = typeof body === "string" ? { message: body } : (body as Record<string, unknown>);
25
- res.status(status).json({ success: false, statusCode: status, ...shaped });
37
+ const shaped =
38
+ typeof body === "string"
39
+ ? { message: body }
40
+ : (body as Record<string, unknown>);
41
+ res
42
+ .status(status)
43
+ .json({ success: false, statusCode: status, ...shaped });
26
44
  return;
27
45
  }
28
46
 
29
47
  // Anything not thrown deliberately — don't leak internals in the response.
30
- res.status(HttpStatus.INTERNAL_SERVER_ERROR).json({ success: false, statusCode: HttpStatus.INTERNAL_SERVER_ERROR, message: "internal server error" });
48
+ res
49
+ .status(HttpStatus.INTERNAL_SERVER_ERROR)
50
+ .json({
51
+ success: false,
52
+ statusCode: HttpStatus.INTERNAL_SERVER_ERROR,
53
+ message: "internal server error",
54
+ });
31
55
  }
32
56
  }
@@ -1,7 +1,13 @@
1
- import { CanActivate, ExecutionContext, ForbiddenException, Inject, Injectable } from "@nestjs/common";
1
+ import {
2
+ CanActivate,
3
+ ExecutionContext,
4
+ ForbiddenException,
5
+ Inject,
6
+ Injectable,
7
+ } from "@nestjs/common";
2
8
  import { Reflector } from "@nestjs/core";
3
- import { ABILITY_SUBJECT } from "./ability.js";
4
- import { CHECK_ABILITY_KEY } from "./route-tiers.js";
9
+ import { ABILITY_SUBJECT } from "../ability.js";
10
+ import { CHECK_ABILITY_KEY } from "../route-tiers.js";
5
11
 
6
12
  /**
7
13
  * The authorization boundary for tier-3 routes. This is the *only* thing standing between a
@@ -30,16 +36,23 @@ export class AbilityGuard implements CanActivate {
30
36
  constructor(@Inject(Reflector) private readonly reflector: Reflector) {}
31
37
 
32
38
  canActivate(context: ExecutionContext): boolean {
33
- const required = this.reflector.getAllAndOverride<string[] | undefined>(CHECK_ABILITY_KEY, [context.getHandler(), context.getClass()]);
39
+ const required = this.reflector.getAllAndOverride<string[] | undefined>(
40
+ CHECK_ABILITY_KEY,
41
+ [context.getHandler(), context.getClass()],
42
+ );
34
43
  if (!required?.length) {
35
- throw new Error(`${context.getClass().name}.${context.getHandler().name} is guarded by AbilityGuard but declares no @CheckAbility`);
44
+ throw new Error(
45
+ `${context.getClass().name}.${context.getHandler().name} is guarded by AbilityGuard but declares no @CheckAbility`,
46
+ );
36
47
  }
37
48
 
38
49
  const ability = context.switchToHttp().getRequest().ability;
39
- if (!ability) throw new ForbiddenException("no authorization context for this request");
50
+ if (!ability)
51
+ throw new ForbiddenException("no authorization context for this request");
40
52
 
41
53
  for (const permission of required) {
42
- if (!ability.can(permission, ABILITY_SUBJECT)) throw new ForbiddenException(`missing permission: ${permission}`);
54
+ if (!ability.can(permission, ABILITY_SUBJECT))
55
+ throw new ForbiddenException(`missing permission: ${permission}`);
43
56
  }
44
57
  return true;
45
58
  }
@@ -1,8 +1,14 @@
1
- import { CanActivate, ExecutionContext, Inject, Injectable, UnauthorizedException } from "@nestjs/common";
1
+ import {
2
+ CanActivate,
3
+ ExecutionContext,
4
+ Inject,
5
+ Injectable,
6
+ UnauthorizedException,
7
+ } from "@nestjs/common";
2
8
  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";
9
+ import { KeyProviderService } from "../key-provider.js";
10
+ import "../request-context.js";
11
+ import { SessionRepository } from "../repositories/session.repository.js";
6
12
 
7
13
  /**
8
14
  * Authentication only — proves who the caller is and populates `req.auth`. Authorization
@@ -19,15 +25,21 @@ export class AuthGuard implements CanActivate {
19
25
  async canActivate(context: ExecutionContext): Promise<boolean> {
20
26
  const req = context.switchToHttp().getRequest();
21
27
  const header: string | undefined = req.headers["authorization"];
22
- if (!header?.startsWith("Bearer ")) throw new UnauthorizedException("missing bearer token");
28
+ if (!header?.startsWith("Bearer "))
29
+ throw new UnauthorizedException("missing bearer token");
23
30
 
24
31
  try {
25
32
  req.auth = await verifyAccessToken(
26
- { secret: this.keys.secret, isDenylisted: (jti) => this.sessions.isDenylisted(jti) },
33
+ {
34
+ secret: this.keys.secret,
35
+ isDenylisted: (jti) => this.sessions.isDenylisted(jti),
36
+ },
27
37
  header.slice("Bearer ".length),
28
38
  );
29
39
  } catch (err) {
30
- throw new UnauthorizedException(err instanceof Error ? err.message : "invalid access token");
40
+ throw new UnauthorizedException(
41
+ err instanceof Error ? err.message : "invalid access token",
42
+ );
31
43
  }
32
44
  return true;
33
45
  }
@@ -1,4 +1,9 @@
1
- import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from "@nestjs/common";
1
+ import {
2
+ CallHandler,
3
+ ExecutionContext,
4
+ Injectable,
5
+ NestInterceptor,
6
+ } from "@nestjs/common";
2
7
  import { Observable, map } from "rxjs";
3
8
  import type { Request, Response } from "express";
4
9
 
@@ -32,7 +37,10 @@ function deriveMessage(method: string): string {
32
37
 
33
38
  @Injectable()
34
39
  export class ResponseInterceptor<T> implements NestInterceptor<T, Envelope<T>> {
35
- intercept(context: ExecutionContext, next: CallHandler<T>): Observable<Envelope<T>> {
40
+ intercept(
41
+ context: ExecutionContext,
42
+ next: CallHandler<T>,
43
+ ): Observable<Envelope<T>> {
36
44
  const http = context.switchToHttp();
37
45
  const req = http.getRequest<Request>();
38
46
  const res = http.getResponse<Response>();
@@ -0,0 +1,63 @@
1
+ import { BadRequestException, Inject, Injectable } from "@nestjs/common";
2
+ import { and, eq } from "drizzle-orm";
3
+ import type { OAuthStoreDeps } from "@/lib/auth/core/oauth.js";
4
+ import { DRIZZLE_DB, type Database } from "../db.js";
5
+ import { oauthAccounts, users } from "../schema.js";
6
+ import { toId } from "../id.helper.js";
7
+
8
+ @Injectable()
9
+ export class OAuthRepository implements OAuthStoreDeps {
10
+ constructor(@Inject(DRIZZLE_DB) private readonly db: Database) {}
11
+
12
+ async findAccountByProvider(
13
+ provider: string,
14
+ providerAccountId: string,
15
+ ): Promise<{ userId: string } | null> {
16
+ const [row] = await this.db
17
+ .select()
18
+ .from(oauthAccounts)
19
+ .where(
20
+ and(
21
+ eq(oauthAccounts.provider, provider),
22
+ eq(oauthAccounts.providerAccountId, providerAccountId),
23
+ ),
24
+ )
25
+ .limit(1);
26
+ return row ? { userId: row.userId.toString() } : null;
27
+ }
28
+
29
+ async linkAccount(input: {
30
+ userId: string;
31
+ provider: string;
32
+ providerAccountId: string;
33
+ email?: string;
34
+ }): Promise<void> {
35
+ await this.db
36
+ .insert(oauthAccounts)
37
+ .values({ ...input, userId: toId(input.userId) });
38
+ }
39
+
40
+ async findUserByVerifiedEmail(email: string): Promise<{ id: string } | null> {
41
+ const [row] = await this.db
42
+ .select()
43
+ .from(users)
44
+ .where(eq(users.email, email))
45
+ .limit(1);
46
+ return row ? { id: row.id.toString() } : null;
47
+ }
48
+
49
+ /** OAuth-only signup: no password is ever set, which is why `User.passwordHash` is nullable. */
50
+ async createUserFromOAuth(input: {
51
+ email?: string;
52
+ }): Promise<{ id: string }> {
53
+ if (!input.email)
54
+ throw new BadRequestException(
55
+ "OAuth provider did not return an email address",
56
+ );
57
+ const [row] = await this.db
58
+ .insert(users)
59
+ .values({ email: input.email, passwordHash: null })
60
+ .returning();
61
+ return { id: row.id.toString() };
62
+ }
63
+ }
@@ -0,0 +1,59 @@
1
+ import { Inject, Injectable } from "@nestjs/common";
2
+ import { eq } from "drizzle-orm";
3
+ import type { PasswordResetStoreDeps } from "@/lib/auth/core/password-reset.js";
4
+ import { DRIZZLE_DB, type Database } from "../db.js";
5
+ import { passwordResetTokens, users } from "../schema.js";
6
+ import { toId } from "../id.helper.js";
7
+
8
+ @Injectable()
9
+ export class PasswordResetRepository implements PasswordResetStoreDeps {
10
+ constructor(@Inject(DRIZZLE_DB) private readonly db: Database) {}
11
+
12
+ async saveResetToken(input: {
13
+ userId: string;
14
+ tokenHash: string;
15
+ expiresAt: string;
16
+ }): Promise<void> {
17
+ await this.db
18
+ .insert(passwordResetTokens)
19
+ .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(passwordResetTokens)
36
+ .where(eq(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(passwordResetTokens)
49
+ .set({ consumedAt: new Date() })
50
+ .where(eq(passwordResetTokens.tokenHash, tokenHash));
51
+ }
52
+
53
+ async setPasswordHash(userId: string, passwordHash: string): Promise<void> {
54
+ await this.db
55
+ .update(users)
56
+ .set({ passwordHash })
57
+ .where(eq(users.id, toId(userId)));
58
+ }
59
+ }
@@ -2,12 +2,16 @@ import { randomUUID } from "node:crypto";
2
2
  import { Inject, Injectable } from "@nestjs/common";
3
3
  import { and, eq, isNull, ne, sql } from "drizzle-orm";
4
4
  import type { SessionStoreDeps } from "@/lib/auth/core/session-policy.js";
5
- import type { AuditEvent, Revoker, SessionRecord } from "@/lib/auth/core/types.js";
5
+ import type {
6
+ AuditEvent,
7
+ Revoker,
8
+ SessionRecord,
9
+ } from "@/lib/auth/core/types.js";
6
10
  import { AuditLogRepository } from "./audit-log.repository.js";
7
- import { AUTH_CONFIG, AuthConfig } from "./auth.config.js";
8
- import { DRIZZLE_DB, type Database } from "./db.js";
9
- import { denylistedAccessTokens, sessions } from "./schema.js";
10
- import { toId, toIdOrNull } from "./id.helper.js";
11
+ import { AUTH_CONFIG, AuthConfig } from "../auth.config.js";
12
+ import { DRIZZLE_DB, type Database } from "../db.js";
13
+ import { denylistedAccessTokens, sessions } from "../schema.js";
14
+ import { toId, toIdOrNull } from "../id.helper.js";
11
15
 
12
16
  function toSessionRecord(row: typeof sessions.$inferSelect): SessionRecord {
13
17
  return {
@@ -55,7 +59,11 @@ export class SessionRepository implements SessionStoreDeps {
55
59
  }
56
60
 
57
61
  async findSession(id: string): Promise<SessionRecord | null> {
58
- const [row] = await this.db.select().from(sessions).where(eq(sessions.id, toId(id))).limit(1);
62
+ const [row] = await this.db
63
+ .select()
64
+ .from(sessions)
65
+ .where(eq(sessions.id, toId(id)))
66
+ .limit(1);
59
67
  return row ? toSessionRecord(row) : null;
60
68
  }
61
69
 
@@ -83,7 +91,12 @@ export class SessionRepository implements SessionStoreDeps {
83
91
  * `createdBy` is the user themselves. A session is only ever created by the person logging in,
84
92
  * so recording it costs nothing and leaves the column meaningful rather than always null.
85
93
  */
86
- async createSession(input: { userId: string; userAgent?: string; ip?: string; provider?: string }): Promise<SessionRecord> {
94
+ async createSession(input: {
95
+ userId: string;
96
+ userAgent?: string;
97
+ ip?: string;
98
+ provider?: string;
99
+ }): Promise<SessionRecord> {
87
100
  const userIdBig = toId(input.userId);
88
101
  const [row] = await this.db
89
102
  .insert(sessions)
@@ -104,15 +117,33 @@ export class SessionRepository implements SessionStoreDeps {
104
117
  async revokeAllByUser(userId: string, revoker?: Revoker): Promise<void> {
105
118
  await this.db
106
119
  .update(sessions)
107
- .set({ ...revocationFields(revoker), sessionVersion: sql`${sessions.sessionVersion} + 1` })
108
- .where(and(eq(sessions.userId, toId(userId)), isNull(sessions.revokedAt)));
120
+ .set({
121
+ ...revocationFields(revoker),
122
+ sessionVersion: sql`${sessions.sessionVersion} + 1`,
123
+ })
124
+ .where(
125
+ and(eq(sessions.userId, toId(userId)), isNull(sessions.revokedAt)),
126
+ );
109
127
  }
110
128
 
111
- async revokeAllByUserExcept(userId: string, keepSessionId: string, revoker?: Revoker): Promise<void> {
129
+ async revokeAllByUserExcept(
130
+ userId: string,
131
+ keepSessionId: string,
132
+ revoker?: Revoker,
133
+ ): Promise<void> {
112
134
  await this.db
113
135
  .update(sessions)
114
- .set({ ...revocationFields(revoker), sessionVersion: sql`${sessions.sessionVersion} + 1` })
115
- .where(and(eq(sessions.userId, toId(userId)), ne(sessions.id, toId(keepSessionId)), isNull(sessions.revokedAt)));
136
+ .set({
137
+ ...revocationFields(revoker),
138
+ sessionVersion: sql`${sessions.sessionVersion} + 1`,
139
+ })
140
+ .where(
141
+ and(
142
+ eq(sessions.userId, toId(userId)),
143
+ ne(sessions.id, toId(keepSessionId)),
144
+ isNull(sessions.revokedAt),
145
+ ),
146
+ );
116
147
  }
117
148
 
118
149
  async denylistJti(jti: string, ttlSeconds: number): Promise<void> {
@@ -120,11 +151,18 @@ export class SessionRepository implements SessionStoreDeps {
120
151
  await this.db
121
152
  .insert(denylistedAccessTokens)
122
153
  .values({ jti, expiresAt })
123
- .onConflictDoUpdate({ target: denylistedAccessTokens.jti, set: { expiresAt } });
154
+ .onConflictDoUpdate({
155
+ target: denylistedAccessTokens.jti,
156
+ set: { expiresAt },
157
+ });
124
158
  }
125
159
 
126
160
  async isDenylisted(jti: string): Promise<boolean> {
127
- const [row] = await this.db.select().from(denylistedAccessTokens).where(eq(denylistedAccessTokens.jti, jti)).limit(1);
161
+ const [row] = await this.db
162
+ .select()
163
+ .from(denylistedAccessTokens)
164
+ .where(eq(denylistedAccessTokens.jti, jti))
165
+ .limit(1);
128
166
  if (!row) return false;
129
167
  if (row.expiresAt.getTime() < Date.now()) return false; // expired; a cleanup job reaps these, see Phase 6
130
168
  return true;
@@ -1,8 +1,8 @@
1
1
  import { Inject, Injectable } from "@nestjs/common";
2
2
  import { and, eq, isNull } from "drizzle-orm";
3
3
  import { verifyBackupCode } from "@/lib/auth/core/two-factor.js";
4
- import { DRIZZLE_DB, type Database } from "./db.js";
5
- import { twoFactorBackupCodes } from "./schema.js";
4
+ import { DRIZZLE_DB, type Database } from "../db.js";
5
+ import { twoFactorBackupCodes } from "../schema.js";
6
6
 
7
7
  // Takes bigint directly, not string: every caller already has the user row's id in hand from
8
8
  // its own database lookup, so there's nothing to parse here.
@@ -12,24 +12,38 @@ export class TwoFactorRepository {
12
12
 
13
13
  /** Replaces any prior set — re-confirming enrollment invalidates old backup codes. */
14
14
  async saveBackupCodes(userId: bigint, hashes: string[]): Promise<void> {
15
- await this.db.delete(twoFactorBackupCodes).where(eq(twoFactorBackupCodes.userId, userId));
15
+ await this.db
16
+ .delete(twoFactorBackupCodes)
17
+ .where(eq(twoFactorBackupCodes.userId, userId));
16
18
  if (hashes.length === 0) return;
17
- await this.db.insert(twoFactorBackupCodes).values(hashes.map((codeHash) => ({ userId, codeHash })));
19
+ await this.db
20
+ .insert(twoFactorBackupCodes)
21
+ .values(hashes.map((codeHash) => ({ userId, codeHash })));
18
22
  }
19
23
 
20
24
  async consumeBackupCode(userId: bigint, code: string): Promise<boolean> {
21
25
  const unused = await this.db
22
26
  .select()
23
27
  .from(twoFactorBackupCodes)
24
- .where(and(eq(twoFactorBackupCodes.userId, userId), isNull(twoFactorBackupCodes.usedAt)));
28
+ .where(
29
+ and(
30
+ eq(twoFactorBackupCodes.userId, userId),
31
+ isNull(twoFactorBackupCodes.usedAt),
32
+ ),
33
+ );
25
34
  const match = unused.find((row) => verifyBackupCode(code, row.codeHash));
26
35
  if (!match) return false;
27
36
 
28
- await this.db.update(twoFactorBackupCodes).set({ usedAt: new Date() }).where(eq(twoFactorBackupCodes.id, match.id));
37
+ await this.db
38
+ .update(twoFactorBackupCodes)
39
+ .set({ usedAt: new Date() })
40
+ .where(eq(twoFactorBackupCodes.id, match.id));
29
41
  return true;
30
42
  }
31
43
 
32
44
  async clearBackupCodes(userId: bigint): Promise<void> {
33
- await this.db.delete(twoFactorBackupCodes).where(eq(twoFactorBackupCodes.userId, userId));
45
+ await this.db
46
+ .delete(twoFactorBackupCodes)
47
+ .where(eq(twoFactorBackupCodes.userId, userId));
34
48
  }
35
49
  }
@@ -14,7 +14,7 @@
14
14
  // for its side effect installs the Express `Request` augmentation.
15
15
  import type { AccessTokenClaims } from "@/lib/auth/core/types.js";
16
16
  import type { AppAbility } from "./ability.js";
17
- import type { AuthzContext } from "./authz.guard.js";
17
+ import type { AuthzContext } from "./guards/authz.guard.js";
18
18
 
19
19
  declare module "express" {
20
20
  interface Request {