@simple-auth-kit/cli 1.4.2 → 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 +6 -4
- package/simple-auth-kit.ts +582 -124
- 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
|
@@ -27,11 +27,11 @@ import {
|
|
|
27
27
|
AUTH_CONFIG,
|
|
28
28
|
AuthConfig,
|
|
29
29
|
defaultThrottleBuckets,
|
|
30
|
-
} from "
|
|
31
|
-
import { AuthGuard } from "
|
|
32
|
-
import { AuthService } from "
|
|
33
|
-
import { AuthzGuard } from "
|
|
34
|
-
import { Authenticated, Public } from "
|
|
30
|
+
} from "../auth.config.js";
|
|
31
|
+
import { AuthGuard } from "../guards/auth.guard.js";
|
|
32
|
+
import { AuthService } from "../services/auth.service.js";
|
|
33
|
+
import { AuthzGuard } from "../guards/authz.guard.js";
|
|
34
|
+
import { Authenticated, Public } from "../route-tiers.js";
|
|
35
35
|
import {
|
|
36
36
|
AuthTokensDto,
|
|
37
37
|
ChangePasswordDto,
|
|
@@ -51,7 +51,7 @@ import {
|
|
|
51
51
|
SignupDto,
|
|
52
52
|
TwoFactorCodeDto,
|
|
53
53
|
UpdateUserDto,
|
|
54
|
-
} from "
|
|
54
|
+
} from "../dto/auth.dto.js";
|
|
55
55
|
|
|
56
56
|
function requireString(value: unknown, field: string): string {
|
|
57
57
|
if (typeof value !== "string" || value.length === 0)
|
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import {
|
|
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
|
|
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 =
|
|
25
|
-
|
|
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
|
|
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
|
}
|
package/registry/combos/nestjs-prisma/shared/src/{ability.guard.ts → guards/ability.guard.ts}
RENAMED
|
@@ -1,7 +1,13 @@
|
|
|
1
|
-
import {
|
|
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 "
|
|
4
|
-
import { CHECK_ABILITY_KEY } from "
|
|
9
|
+
import { ABILITY_SUBJECT } from "../ability.js";
|
|
10
|
+
import { CHECK_ABILITY_KEY } from "../route-tiers.js";
|
|
5
11
|
|
|
6
12
|
// The authorization boundary for tier-3 routes — no role-based bypass beside it, so a role
|
|
7
13
|
// carrying no permissions confers no authority. Fail-closed: no ability, a missing slug, or a
|
|
@@ -13,16 +19,23 @@ export class AbilityGuard implements CanActivate {
|
|
|
13
19
|
constructor(@Inject(Reflector) private readonly reflector: Reflector) {}
|
|
14
20
|
|
|
15
21
|
canActivate(context: ExecutionContext): boolean {
|
|
16
|
-
const required = this.reflector.getAllAndOverride<string[] | undefined>(
|
|
22
|
+
const required = this.reflector.getAllAndOverride<string[] | undefined>(
|
|
23
|
+
CHECK_ABILITY_KEY,
|
|
24
|
+
[context.getHandler(), context.getClass()],
|
|
25
|
+
);
|
|
17
26
|
if (!required?.length) {
|
|
18
|
-
throw new Error(
|
|
27
|
+
throw new Error(
|
|
28
|
+
`${context.getClass().name}.${context.getHandler().name} is guarded by AbilityGuard but declares no @CheckAbility`,
|
|
29
|
+
);
|
|
19
30
|
}
|
|
20
31
|
|
|
21
32
|
const ability = context.switchToHttp().getRequest().ability;
|
|
22
|
-
if (!ability)
|
|
33
|
+
if (!ability)
|
|
34
|
+
throw new ForbiddenException("no authorization context for this request");
|
|
23
35
|
|
|
24
36
|
for (const permission of required) {
|
|
25
|
-
if (!ability.can(permission, ABILITY_SUBJECT))
|
|
37
|
+
if (!ability.can(permission, ABILITY_SUBJECT))
|
|
38
|
+
throw new ForbiddenException(`missing permission: ${permission}`);
|
|
26
39
|
}
|
|
27
40
|
return true;
|
|
28
41
|
}
|
|
@@ -1,8 +1,14 @@
|
|
|
1
|
-
import {
|
|
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 "
|
|
4
|
-
import "
|
|
5
|
-
import { SessionRepository } from "
|
|
9
|
+
import { KeyProviderService } from "../key-provider.js";
|
|
10
|
+
import "../request-context.js";
|
|
11
|
+
import { SessionRepository } from "../repositories/session.repository.js";
|
|
6
12
|
|
|
7
13
|
// Authentication only — proves who the caller is and populates `req.auth`. Pair with
|
|
8
14
|
// `AuthzGuard` on any route that checks roles or permissions.
|
|
@@ -16,15 +22,21 @@ export class AuthGuard implements CanActivate {
|
|
|
16
22
|
async canActivate(context: ExecutionContext): Promise<boolean> {
|
|
17
23
|
const req = context.switchToHttp().getRequest();
|
|
18
24
|
const header: string | undefined = req.headers["authorization"];
|
|
19
|
-
if (!header?.startsWith("Bearer "))
|
|
25
|
+
if (!header?.startsWith("Bearer "))
|
|
26
|
+
throw new UnauthorizedException("missing bearer token");
|
|
20
27
|
|
|
21
28
|
try {
|
|
22
29
|
req.auth = await verifyAccessToken(
|
|
23
|
-
{
|
|
30
|
+
{
|
|
31
|
+
secret: this.keys.secret,
|
|
32
|
+
isDenylisted: (jti) => this.sessions.isDenylisted(jti),
|
|
33
|
+
},
|
|
24
34
|
header.slice("Bearer ".length),
|
|
25
35
|
);
|
|
26
36
|
} catch (err) {
|
|
27
|
-
throw new UnauthorizedException(
|
|
37
|
+
throw new UnauthorizedException(
|
|
38
|
+
err instanceof Error ? err.message : "invalid access token",
|
|
39
|
+
);
|
|
28
40
|
}
|
|
29
41
|
return true;
|
|
30
42
|
}
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import {
|
|
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(
|
|
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>();
|
|
@@ -1,21 +1,31 @@
|
|
|
1
1
|
import { BadRequestException, Inject, Injectable } from "@nestjs/common";
|
|
2
2
|
import type { OAuthStoreDeps } from "@/lib/auth/core/oauth.js";
|
|
3
|
-
import { PrismaClient } from "
|
|
4
|
-
import { toId } from "
|
|
3
|
+
import { PrismaClient } from "../../generated/prisma/client.js";
|
|
4
|
+
import { toId } from "../id.helper.js";
|
|
5
5
|
|
|
6
6
|
@Injectable()
|
|
7
7
|
export class OAuthRepository implements OAuthStoreDeps {
|
|
8
8
|
constructor(@Inject(PrismaClient) private readonly prisma: PrismaClient) {}
|
|
9
9
|
|
|
10
|
-
async findAccountByProvider(
|
|
10
|
+
async findAccountByProvider(
|
|
11
|
+
provider: string,
|
|
12
|
+
providerAccountId: string,
|
|
13
|
+
): Promise<{ userId: string } | null> {
|
|
11
14
|
const row = await this.prisma.oAuthAccount.findUnique({
|
|
12
15
|
where: { provider_providerAccountId: { provider, providerAccountId } },
|
|
13
16
|
});
|
|
14
17
|
return row ? { userId: row.userId.toString() } : null;
|
|
15
18
|
}
|
|
16
19
|
|
|
17
|
-
async linkAccount(input: {
|
|
18
|
-
|
|
20
|
+
async linkAccount(input: {
|
|
21
|
+
userId: string;
|
|
22
|
+
provider: string;
|
|
23
|
+
providerAccountId: string;
|
|
24
|
+
email?: string;
|
|
25
|
+
}): Promise<void> {
|
|
26
|
+
await this.prisma.oAuthAccount.create({
|
|
27
|
+
data: { ...input, userId: toId(input.userId) },
|
|
28
|
+
});
|
|
19
29
|
}
|
|
20
30
|
|
|
21
31
|
async findUserByVerifiedEmail(email: string): Promise<{ id: string } | null> {
|
|
@@ -24,9 +34,16 @@ export class OAuthRepository implements OAuthStoreDeps {
|
|
|
24
34
|
}
|
|
25
35
|
|
|
26
36
|
// OAuth-only signup: no password is set, which is why `User.passwordHash` is nullable.
|
|
27
|
-
async createUserFromOAuth(input: {
|
|
28
|
-
|
|
29
|
-
|
|
37
|
+
async createUserFromOAuth(input: {
|
|
38
|
+
email?: string;
|
|
39
|
+
}): Promise<{ id: string }> {
|
|
40
|
+
if (!input.email)
|
|
41
|
+
throw new BadRequestException(
|
|
42
|
+
"OAuth provider did not return an email address",
|
|
43
|
+
);
|
|
44
|
+
const row = await this.prisma.user.create({
|
|
45
|
+
data: { email: input.email, passwordHash: null },
|
|
46
|
+
});
|
|
30
47
|
return { id: row.id.toString() };
|
|
31
48
|
}
|
|
32
49
|
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { Inject, Injectable } from "@nestjs/common";
|
|
2
|
+
import type { PasswordResetStoreDeps } from "@/lib/auth/core/password-reset.js";
|
|
3
|
+
import { PrismaClient } from "../../generated/prisma/client.js";
|
|
4
|
+
import { toId } from "../id.helper.js";
|
|
5
|
+
|
|
6
|
+
@Injectable()
|
|
7
|
+
export class PasswordResetRepository implements PasswordResetStoreDeps {
|
|
8
|
+
constructor(@Inject(PrismaClient) private readonly prisma: PrismaClient) {}
|
|
9
|
+
|
|
10
|
+
async saveResetToken(input: {
|
|
11
|
+
userId: string;
|
|
12
|
+
tokenHash: string;
|
|
13
|
+
expiresAt: string;
|
|
14
|
+
}): Promise<void> {
|
|
15
|
+
await this.prisma.passwordResetToken.create({
|
|
16
|
+
data: {
|
|
17
|
+
userId: toId(input.userId),
|
|
18
|
+
tokenHash: input.tokenHash,
|
|
19
|
+
expiresAt: new Date(input.expiresAt),
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async findValidResetToken(
|
|
25
|
+
tokenHash: string,
|
|
26
|
+
): Promise<{
|
|
27
|
+
userId: string;
|
|
28
|
+
expiresAt: string;
|
|
29
|
+
consumedAt: string | null;
|
|
30
|
+
} | null> {
|
|
31
|
+
const row = await this.prisma.passwordResetToken.findUnique({
|
|
32
|
+
where: { tokenHash },
|
|
33
|
+
});
|
|
34
|
+
if (!row) return null;
|
|
35
|
+
return {
|
|
36
|
+
userId: row.userId.toString(),
|
|
37
|
+
expiresAt: row.expiresAt.toISOString(),
|
|
38
|
+
consumedAt: row.consumedAt?.toISOString() ?? null,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async consumeResetToken(tokenHash: string): Promise<void> {
|
|
43
|
+
await this.prisma.passwordResetToken.update({
|
|
44
|
+
where: { tokenHash },
|
|
45
|
+
data: { consumedAt: new Date() },
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async setPasswordHash(userId: string, passwordHash: string): Promise<void> {
|
|
50
|
+
await this.prisma.user.update({
|
|
51
|
+
where: { id: toId(userId) },
|
|
52
|
+
data: { passwordHash },
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { Inject, Injectable } from "@nestjs/common";
|
|
3
3
|
import type { SessionStoreDeps } from "@/lib/auth/core/session-policy.js";
|
|
4
|
-
import type {
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
import type {
|
|
5
|
+
AuditEvent,
|
|
6
|
+
Revoker,
|
|
7
|
+
SessionRecord,
|
|
8
|
+
} from "@/lib/auth/core/types.js";
|
|
9
|
+
import { PrismaClient } from "../../generated/prisma/client.js";
|
|
10
|
+
import { AUTH_CONFIG, AuthConfig } from "../auth.config.js";
|
|
7
11
|
import { AuditLogRepository } from "./audit-log.repository.js";
|
|
8
|
-
import { toId, toIdOrNull } from "
|
|
12
|
+
import { toId, toIdOrNull } from "../id.helper.js";
|
|
9
13
|
|
|
10
14
|
function toSessionRecord(row: {
|
|
11
15
|
id: bigint;
|
|
@@ -60,7 +64,9 @@ export class SessionRepository implements SessionStoreDeps {
|
|
|
60
64
|
}
|
|
61
65
|
|
|
62
66
|
async findSession(id: string): Promise<SessionRecord | null> {
|
|
63
|
-
const row = await this.prisma.session.findUnique({
|
|
67
|
+
const row = await this.prisma.session.findUnique({
|
|
68
|
+
where: { id: toId(id) },
|
|
69
|
+
});
|
|
64
70
|
return row ? toSessionRecord(row) : null;
|
|
65
71
|
}
|
|
66
72
|
|
|
@@ -82,7 +88,12 @@ export class SessionRepository implements SessionStoreDeps {
|
|
|
82
88
|
|
|
83
89
|
// expiresAt is set once here and never rewritten — an absolute cap, not a sliding window.
|
|
84
90
|
// `id` is no longer client-supplied — the bigint PK auto-increments.
|
|
85
|
-
async createSession(input: {
|
|
91
|
+
async createSession(input: {
|
|
92
|
+
userId: string;
|
|
93
|
+
userAgent?: string;
|
|
94
|
+
ip?: string;
|
|
95
|
+
provider?: string;
|
|
96
|
+
}): Promise<SessionRecord> {
|
|
86
97
|
const userIdBig = toId(input.userId);
|
|
87
98
|
const row = await this.prisma.session.create({
|
|
88
99
|
data: {
|
|
@@ -105,9 +116,17 @@ export class SessionRepository implements SessionStoreDeps {
|
|
|
105
116
|
});
|
|
106
117
|
}
|
|
107
118
|
|
|
108
|
-
async revokeAllByUserExcept(
|
|
119
|
+
async revokeAllByUserExcept(
|
|
120
|
+
userId: string,
|
|
121
|
+
keepSessionId: string,
|
|
122
|
+
revoker?: Revoker,
|
|
123
|
+
): Promise<void> {
|
|
109
124
|
await this.prisma.session.updateMany({
|
|
110
|
-
where: {
|
|
125
|
+
where: {
|
|
126
|
+
userId: toId(userId),
|
|
127
|
+
id: { not: toId(keepSessionId) },
|
|
128
|
+
revokedAt: null,
|
|
129
|
+
},
|
|
111
130
|
data: { ...revocationFields(revoker), sessionVersion: { increment: 1 } },
|
|
112
131
|
});
|
|
113
132
|
}
|
|
@@ -121,7 +140,9 @@ export class SessionRepository implements SessionStoreDeps {
|
|
|
121
140
|
}
|
|
122
141
|
|
|
123
142
|
async isDenylisted(jti: string): Promise<boolean> {
|
|
124
|
-
const row = await this.prisma.denylistedAccessToken.findUnique({
|
|
143
|
+
const row = await this.prisma.denylistedAccessToken.findUnique({
|
|
144
|
+
where: { jti },
|
|
145
|
+
});
|
|
125
146
|
if (!row) return false;
|
|
126
147
|
if (row.expiresAt.getTime() < Date.now()) return false; // expired; a cleanup job reaps these, see Phase 6
|
|
127
148
|
return true;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Inject, Injectable } from "@nestjs/common";
|
|
2
2
|
import { verifyBackupCode } from "@/lib/auth/core/two-factor.js";
|
|
3
|
-
import { PrismaClient } from "
|
|
3
|
+
import { PrismaClient } from "../../generated/prisma/client.js";
|
|
4
4
|
|
|
5
5
|
// Takes bigint directly, not string: every caller already has the user row's id in hand from
|
|
6
6
|
// its own Prisma lookup, so there's nothing to parse here.
|
|
@@ -11,15 +11,22 @@ export class TwoFactorRepository {
|
|
|
11
11
|
/** Replaces any prior set — re-confirming enrollment invalidates old backup codes. */
|
|
12
12
|
async saveBackupCodes(userId: bigint, hashes: string[]): Promise<void> {
|
|
13
13
|
await this.prisma.twoFactorBackupCode.deleteMany({ where: { userId } });
|
|
14
|
-
await this.prisma.twoFactorBackupCode.createMany({
|
|
14
|
+
await this.prisma.twoFactorBackupCode.createMany({
|
|
15
|
+
data: hashes.map((codeHash) => ({ userId, codeHash })),
|
|
16
|
+
});
|
|
15
17
|
}
|
|
16
18
|
|
|
17
19
|
async consumeBackupCode(userId: bigint, code: string): Promise<boolean> {
|
|
18
|
-
const unused = await this.prisma.twoFactorBackupCode.findMany({
|
|
20
|
+
const unused = await this.prisma.twoFactorBackupCode.findMany({
|
|
21
|
+
where: { userId, usedAt: null },
|
|
22
|
+
});
|
|
19
23
|
const match = unused.find((row) => verifyBackupCode(code, row.codeHash));
|
|
20
24
|
if (!match) return false;
|
|
21
25
|
|
|
22
|
-
await this.prisma.twoFactorBackupCode.update({
|
|
26
|
+
await this.prisma.twoFactorBackupCode.update({
|
|
27
|
+
where: { id: match.id },
|
|
28
|
+
data: { usedAt: new Date() },
|
|
29
|
+
});
|
|
23
30
|
return true;
|
|
24
31
|
}
|
|
25
32
|
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
// Importing this module for its side effect installs the Express `Request` augmentation.
|
|
9
9
|
import type { AccessTokenClaims } from "@/lib/auth/core/types.js";
|
|
10
10
|
import type { AppAbility } from "./ability.js";
|
|
11
|
-
import type { AuthzContext } from "./authz.guard.js";
|
|
11
|
+
import type { AuthzContext } from "./guards/authz.guard.js";
|
|
12
12
|
|
|
13
13
|
declare module "express" {
|
|
14
14
|
interface Request {
|