@simple-auth-kit/cli 1.4.3 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/copy.ts +1 -1
- package/package.json +1 -1
- package/registry/combos/express-drizzle/shared/src/{auth-core-error.middleware.ts → middleware/auth-core-error.middleware.ts} +17 -4
- package/registry/combos/express-drizzle/shared/src/{auth.middleware.ts → middleware/auth.middleware.ts} +23 -7
- package/registry/combos/{express-prisma/shared/src → express-drizzle/shared/src/middleware}/response-envelope.middleware.ts +15 -3
- package/registry/combos/express-drizzle/shared/src/{oauth.repository.ts → repositories/oauth.repository.ts} +32 -9
- package/registry/combos/express-drizzle/shared/src/repositories/password-reset.repository.ts +59 -0
- package/registry/combos/express-drizzle/shared/src/{session.repository.ts → repositories/session.repository.ts} +52 -14
- package/registry/combos/express-drizzle/shared/src/{two-factor.repository.ts → repositories/two-factor.repository.ts} +20 -6
- package/registry/combos/express-drizzle/shared/src/request-context.ts +1 -1
- package/registry/combos/express-drizzle/shared/src/routers/auth.router.ts +395 -0
- package/registry/combos/express-drizzle/variants/base/src/create-auth-app.ts +52 -19
- package/registry/combos/express-drizzle/variants/base/src/{authz.middleware.ts → middleware/authz.middleware.ts} +15 -7
- package/registry/combos/express-drizzle/variants/base/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +28 -10
- package/registry/combos/express-drizzle/variants/base/src/{rbac.repository.ts → repositories/rbac.repository.ts} +239 -48
- package/registry/combos/express-drizzle/variants/base/src/routers/admin.router.ts +497 -0
- package/registry/combos/express-drizzle/variants/base/src/{auth.service.ts → services/auth.service.ts} +480 -109
- package/registry/combos/express-drizzle/variants/workspaces/src/create-auth-app.ts +69 -23
- package/registry/combos/express-drizzle/variants/workspaces/src/{authz.middleware.ts → middleware/authz.middleware.ts} +32 -12
- package/registry/combos/express-drizzle/variants/workspaces/src/openapi-workspace.ts +147 -28
- package/registry/combos/express-drizzle/variants/workspaces/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +30 -11
- package/registry/combos/express-drizzle/variants/workspaces/src/{rbac.repository.ts → repositories/rbac.repository.ts} +319 -70
- package/registry/combos/express-drizzle/variants/workspaces/src/{workspace.repository.ts → repositories/workspace.repository.ts} +127 -30
- package/registry/combos/express-drizzle/variants/workspaces/src/routers/admin.router.ts +534 -0
- package/registry/combos/express-drizzle/variants/workspaces/src/routers/workspace.router.ts +194 -0
- package/registry/combos/express-drizzle/variants/workspaces/src/{auth.service.ts → services/auth.service.ts} +544 -122
- package/registry/combos/express-prisma/shared/src/{auth-core-error.middleware.ts → middleware/auth-core-error.middleware.ts} +17 -4
- package/registry/combos/express-prisma/shared/src/{auth.middleware.ts → middleware/auth.middleware.ts} +19 -6
- package/registry/combos/{express-drizzle/shared/src → express-prisma/shared/src/middleware}/response-envelope.middleware.ts +15 -3
- package/registry/combos/express-prisma/shared/src/{oauth.repository.ts → repositories/oauth.repository.ts} +27 -9
- package/registry/combos/express-prisma/shared/src/repositories/password-reset.repository.ts +55 -0
- package/registry/combos/express-prisma/shared/src/{session.repository.ts → repositories/session.repository.ts} +30 -9
- package/registry/combos/express-prisma/shared/src/{two-factor.repository.ts → repositories/two-factor.repository.ts} +11 -4
- package/registry/combos/express-prisma/shared/src/request-context.ts +1 -1
- package/registry/combos/express-prisma/shared/src/routers/auth.router.ts +398 -0
- package/registry/combos/express-prisma/variants/base/src/create-auth-app.ts +56 -21
- package/registry/combos/express-prisma/variants/base/src/{authz.middleware.ts → middleware/authz.middleware.ts} +15 -7
- package/registry/combos/express-prisma/variants/base/src/rbac.defaults.ts +66 -18
- package/registry/combos/express-prisma/variants/base/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +16 -5
- package/registry/combos/express-prisma/variants/base/src/{rbac.repository.ts → repositories/rbac.repository.ts} +199 -48
- package/registry/combos/express-prisma/variants/base/src/routers/admin.router.ts +508 -0
- package/registry/combos/express-prisma/variants/base/src/seed.ts +77 -19
- package/registry/combos/express-prisma/variants/base/src/{auth.service.ts → services/auth.service.ts} +454 -105
- package/registry/combos/express-prisma/variants/workspaces/src/create-auth-app.ts +80 -25
- package/registry/combos/express-prisma/variants/workspaces/src/{authz.middleware.ts → middleware/authz.middleware.ts} +32 -12
- package/registry/combos/express-prisma/variants/workspaces/src/openapi-workspace.ts +147 -28
- package/registry/combos/express-prisma/variants/workspaces/src/rbac.defaults.ts +72 -20
- package/registry/combos/express-prisma/variants/workspaces/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +16 -5
- package/registry/combos/express-prisma/variants/workspaces/src/{rbac.repository.ts → repositories/rbac.repository.ts} +272 -64
- package/registry/combos/express-prisma/variants/workspaces/src/{workspace.repository.ts → repositories/workspace.repository.ts} +86 -23
- package/registry/combos/express-prisma/variants/workspaces/src/routers/admin.router.ts +545 -0
- package/registry/combos/express-prisma/variants/workspaces/src/routers/workspace.router.ts +194 -0
- package/registry/combos/express-prisma/variants/workspaces/src/seed.ts +108 -28
- package/registry/combos/express-prisma/variants/workspaces/src/{auth.service.ts → services/auth.service.ts} +518 -119
- package/registry/combos/nestjs-drizzle/shared/src/{auth.controller.ts → controllers/auth.controller.ts} +6 -6
- package/registry/combos/nestjs-drizzle/shared/src/{auth-core-error.filter.ts → filters/auth-core-error.filter.ts} +29 -5
- package/registry/combos/nestjs-drizzle/shared/src/{ability.guard.ts → guards/ability.guard.ts} +20 -7
- package/registry/combos/nestjs-drizzle/shared/src/{auth.guard.ts → guards/auth.guard.ts} +19 -7
- package/registry/combos/{nestjs-prisma/shared/src → nestjs-drizzle/shared/src/interceptors}/response.interceptor.ts +10 -2
- package/registry/combos/nestjs-drizzle/shared/src/repositories/oauth.repository.ts +63 -0
- package/registry/combos/nestjs-drizzle/shared/src/repositories/password-reset.repository.ts +59 -0
- package/registry/combos/nestjs-drizzle/shared/src/{session.repository.ts → repositories/session.repository.ts} +52 -14
- package/registry/combos/nestjs-drizzle/shared/src/{two-factor.repository.ts → repositories/two-factor.repository.ts} +21 -7
- package/registry/combos/nestjs-drizzle/shared/src/request-context.ts +1 -1
- package/registry/combos/nestjs-drizzle/test/prove-cycle.ts +1097 -307
- package/registry/combos/nestjs-drizzle/variants/base/src/auth.module.ts +44 -20
- package/registry/combos/nestjs-drizzle/variants/base/src/{admin.controller.ts → controllers/admin.controller.ts} +228 -62
- package/registry/combos/nestjs-drizzle/variants/base/src/{authz.guard.ts → guards/authz.guard.ts} +12 -5
- package/registry/combos/nestjs-drizzle/variants/base/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +27 -10
- package/registry/combos/nestjs-drizzle/variants/base/src/{rbac.repository.ts → repositories/rbac.repository.ts} +249 -51
- package/registry/combos/nestjs-drizzle/variants/base/src/{auth.service.ts → services/auth.service.ts} +499 -116
- package/registry/combos/nestjs-drizzle/variants/workspaces/src/auth.module.ts +48 -22
- package/registry/combos/nestjs-drizzle/variants/workspaces/src/{admin.controller.ts → controllers/admin.controller.ts} +269 -70
- package/registry/combos/nestjs-drizzle/variants/workspaces/src/{workspace.controller.ts → controllers/workspace.controller.ts} +93 -26
- package/registry/combos/nestjs-drizzle/variants/workspaces/src/{authz.guard.ts → guards/authz.guard.ts} +26 -8
- package/registry/combos/nestjs-drizzle/variants/workspaces/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +29 -11
- package/registry/combos/nestjs-drizzle/variants/workspaces/src/{rbac.repository.ts → repositories/rbac.repository.ts} +323 -71
- package/registry/combos/nestjs-drizzle/variants/workspaces/src/{workspace.repository.ts → repositories/workspace.repository.ts} +131 -30
- package/registry/combos/nestjs-drizzle/variants/workspaces/src/services/auth.service.ts +988 -0
- package/registry/combos/nestjs-prisma/shared/src/{auth.controller.ts → controllers/auth.controller.ts} +6 -6
- package/registry/combos/nestjs-prisma/shared/src/{auth-core-error.filter.ts → filters/auth-core-error.filter.ts} +29 -5
- package/registry/combos/nestjs-prisma/shared/src/{ability.guard.ts → guards/ability.guard.ts} +20 -7
- package/registry/combos/nestjs-prisma/shared/src/{auth.guard.ts → guards/auth.guard.ts} +19 -7
- package/registry/combos/{nestjs-drizzle/shared/src → nestjs-prisma/shared/src/interceptors}/response.interceptor.ts +10 -2
- package/registry/combos/nestjs-prisma/shared/src/{oauth.repository.ts → repositories/oauth.repository.ts} +25 -8
- package/registry/combos/nestjs-prisma/shared/src/repositories/password-reset.repository.ts +55 -0
- package/registry/combos/nestjs-prisma/shared/src/{session.repository.ts → repositories/session.repository.ts} +30 -9
- package/registry/combos/nestjs-prisma/shared/src/{two-factor.repository.ts → repositories/two-factor.repository.ts} +11 -4
- package/registry/combos/nestjs-prisma/shared/src/request-context.ts +1 -1
- package/registry/combos/nestjs-prisma/test/prove-cycle.ts +1097 -307
- package/registry/combos/nestjs-prisma/variants/base/src/auth.module.ts +58 -27
- package/registry/combos/nestjs-prisma/variants/base/src/{admin.controller.ts → controllers/admin.controller.ts} +469 -110
- package/registry/combos/nestjs-prisma/variants/base/src/{audit-log.gateway.ts → gateways/audit-log.gateway.ts} +22 -6
- package/registry/combos/nestjs-prisma/variants/base/src/{authz.guard.ts → guards/authz.guard.ts} +12 -5
- package/registry/combos/nestjs-prisma/variants/base/src/rbac.defaults.ts +97 -26
- package/registry/combos/nestjs-prisma/variants/base/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +16 -5
- package/registry/combos/nestjs-prisma/variants/base/src/{country.repository.ts → repositories/country.repository.ts} +98 -25
- package/registry/combos/nestjs-prisma/variants/base/src/{customer.repository.ts → repositories/customer.repository.ts} +106 -27
- package/registry/combos/nestjs-prisma/variants/base/src/{language.repository.ts → repositories/language.repository.ts} +93 -25
- package/registry/combos/nestjs-prisma/variants/base/src/{rbac.repository.ts → repositories/rbac.repository.ts} +229 -55
- package/registry/combos/nestjs-prisma/variants/base/src/seed.ts +77 -19
- package/registry/combos/nestjs-prisma/variants/base/src/services/auth.service.ts +1064 -0
- package/registry/combos/nestjs-prisma/variants/workspaces/src/auth.module.ts +54 -26
- package/registry/combos/nestjs-prisma/variants/workspaces/src/{admin.controller.ts → controllers/admin.controller.ts} +544 -128
- package/registry/combos/nestjs-prisma/variants/workspaces/src/{workspace.controller.ts → controllers/workspace.controller.ts} +93 -26
- package/registry/combos/nestjs-prisma/variants/workspaces/src/{authz.guard.ts → guards/authz.guard.ts} +26 -8
- package/registry/combos/nestjs-prisma/variants/workspaces/src/rbac.defaults.ts +103 -28
- package/registry/combos/nestjs-prisma/variants/workspaces/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +16 -5
- package/registry/combos/nestjs-prisma/variants/workspaces/src/{country.repository.ts → repositories/country.repository.ts} +127 -26
- package/registry/combos/nestjs-prisma/variants/workspaces/src/{customer.repository.ts → repositories/customer.repository.ts} +137 -28
- package/registry/combos/nestjs-prisma/variants/workspaces/src/repositories/language.repository.ts +243 -0
- package/registry/combos/nestjs-prisma/variants/workspaces/src/{rbac.repository.ts → repositories/rbac.repository.ts} +314 -74
- package/registry/combos/nestjs-prisma/variants/workspaces/src/{workspace.repository.ts → repositories/workspace.repository.ts} +94 -24
- package/registry/combos/nestjs-prisma/variants/workspaces/src/seed.ts +108 -28
- package/registry/combos/nestjs-prisma/variants/workspaces/src/services/auth.service.ts +961 -0
- package/registry.json +4 -4
- package/simple-auth-kit.ts +42 -11
- package/registry/combos/express-drizzle/shared/src/auth.router.ts +0 -266
- package/registry/combos/express-drizzle/shared/src/password-reset.repository.ts +0 -35
- package/registry/combos/express-drizzle/variants/base/src/admin.router.ts +0 -307
- package/registry/combos/express-drizzle/variants/workspaces/src/admin.router.ts +0 -323
- package/registry/combos/express-drizzle/variants/workspaces/src/workspace.router.ts +0 -123
- package/registry/combos/express-prisma/shared/src/auth.router.ts +0 -269
- package/registry/combos/express-prisma/shared/src/password-reset.repository.ts +0 -29
- package/registry/combos/express-prisma/variants/base/src/admin.router.ts +0 -314
- package/registry/combos/express-prisma/variants/workspaces/src/admin.router.ts +0 -330
- package/registry/combos/express-prisma/variants/workspaces/src/workspace.router.ts +0 -123
- package/registry/combos/nestjs-drizzle/shared/src/oauth.repository.ts +0 -36
- package/registry/combos/nestjs-drizzle/shared/src/password-reset.repository.ts +0 -29
- package/registry/combos/nestjs-drizzle/variants/workspaces/src/auth.service.ts +0 -553
- package/registry/combos/nestjs-prisma/shared/src/password-reset.repository.ts +0 -29
- package/registry/combos/nestjs-prisma/variants/base/src/auth.service.ts +0 -618
- package/registry/combos/nestjs-prisma/variants/workspaces/src/auth.service.ts +0 -509
- package/registry/combos/nestjs-prisma/variants/workspaces/src/language.repository.ts +0 -143
package/lib/copy.ts
CHANGED
|
@@ -52,7 +52,7 @@ export interface CopyOptions {
|
|
|
52
52
|
* `cli/simple-auth-kit.ts`) — the registry source itself keeps the path that's correct for its
|
|
53
53
|
* own dev/typecheck loop, where nothing has moved.
|
|
54
54
|
*/
|
|
55
|
-
extraRewrites?: { from: string; to: string }[];
|
|
55
|
+
extraRewrites?: { from: string | RegExp; to: string }[];
|
|
56
56
|
/**
|
|
57
57
|
* The previous install's sha256-by-path manifest. A destination file whose current content
|
|
58
58
|
* no longer matches what was recorded is a file the user edited, and is left alone.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@simple-auth-kit/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.1",
|
|
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 "
|
|
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(
|
|
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
|
|
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
|
|
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 "
|
|
4
|
-
import "
|
|
5
|
-
import { SessionRepository } from "
|
|
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(
|
|
17
|
-
|
|
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
|
|
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
|
-
{
|
|
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 =
|
|
40
|
-
|
|
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({
|
|
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 "
|
|
5
|
-
import * as schema from "
|
|
6
|
-
import { toId } from "
|
|
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(
|
|
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(
|
|
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: {
|
|
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
|
|
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: {
|
|
40
|
-
|
|
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 {
|
|
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 "
|
|
7
|
-
import type { Database } from "
|
|
8
|
-
import { denylistedAccessTokens, sessions } from "
|
|
9
|
-
import { toId, toIdOrNull } from "
|
|
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
|
|
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: {
|
|
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({
|
|
106
|
-
|
|
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(
|
|
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({
|
|
113
|
-
|
|
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({
|
|
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
|
|
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 "
|
|
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
|
|
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
|
|
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(
|
|
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
|
|
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
|
|
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
|