@shipfox/api-auth 2.0.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/.turbo/turbo-build.log +2 -0
- package/.turbo/turbo-type$colon$emit.log +1 -0
- package/.turbo/turbo-type.log +1 -0
- package/CHANGELOG.md +116 -0
- package/LICENSE +21 -0
- package/README.md +336 -0
- package/dist/config.d.ts +22 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +95 -0
- package/dist/config.js.map +1 -0
- package/dist/core/auth.d.ts +108 -0
- package/dist/core/auth.d.ts.map +1 -0
- package/dist/core/auth.js +476 -0
- package/dist/core/auth.js.map +1 -0
- package/dist/core/entities/email-verification.d.ts +9 -0
- package/dist/core/entities/email-verification.d.ts.map +1 -0
- package/dist/core/entities/email-verification.js +3 -0
- package/dist/core/entities/email-verification.js.map +1 -0
- package/dist/core/entities/password-reset.d.ts +9 -0
- package/dist/core/entities/password-reset.d.ts.map +1 -0
- package/dist/core/entities/password-reset.js +3 -0
- package/dist/core/entities/password-reset.js.map +1 -0
- package/dist/core/entities/refresh-token.d.ts +12 -0
- package/dist/core/entities/refresh-token.d.ts.map +1 -0
- package/dist/core/entities/refresh-token.js +3 -0
- package/dist/core/entities/refresh-token.js.map +1 -0
- package/dist/core/entities/user.d.ts +12 -0
- package/dist/core/entities/user.d.ts.map +1 -0
- package/dist/core/entities/user.js +3 -0
- package/dist/core/entities/user.js.map +1 -0
- package/dist/core/errors.d.ts +54 -0
- package/dist/core/errors.d.ts.map +1 -0
- package/dist/core/errors.js +106 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/job-lease-token.d.ts +12 -0
- package/dist/core/job-lease-token.d.ts.map +1 -0
- package/dist/core/job-lease-token.js +58 -0
- package/dist/core/job-lease-token.js.map +1 -0
- package/dist/core/jwt.d.ts +37 -0
- package/dist/core/jwt.d.ts.map +1 -0
- package/dist/core/jwt.js +46 -0
- package/dist/core/jwt.js.map +1 -0
- package/dist/core/password.d.ts +10 -0
- package/dist/core/password.d.ts.map +1 -0
- package/dist/core/password.js +13 -0
- package/dist/core/password.js.map +1 -0
- package/dist/core/rate-limit.d.ts +34 -0
- package/dist/core/rate-limit.d.ts.map +1 -0
- package/dist/core/rate-limit.js +76 -0
- package/dist/core/rate-limit.js.map +1 -0
- package/dist/core/runner-session-token.d.ts +6 -0
- package/dist/core/runner-session-token.d.ts.map +1 -0
- package/dist/core/runner-session-token.js +37 -0
- package/dist/core/runner-session-token.js.map +1 -0
- package/dist/db/db.d.ts +1723 -0
- package/dist/db/db.d.ts.map +1 -0
- package/dist/db/db.js +28 -0
- package/dist/db/db.js.map +1 -0
- package/dist/db/email-verifications.d.ts +39 -0
- package/dist/db/email-verifications.d.ts.map +1 -0
- package/dist/db/email-verifications.js +95 -0
- package/dist/db/email-verifications.js.map +1 -0
- package/dist/db/migrations.d.ts +2 -0
- package/dist/db/migrations.d.ts.map +1 -0
- package/dist/db/migrations.js +5 -0
- package/dist/db/migrations.js.map +1 -0
- package/dist/db/password-resets.d.ts +23 -0
- package/dist/db/password-resets.d.ts.map +1 -0
- package/dist/db/password-resets.js +37 -0
- package/dist/db/password-resets.js.map +1 -0
- package/dist/db/rate-limits.d.ts +18 -0
- package/dist/db/rate-limits.d.ts.map +1 -0
- package/dist/db/rate-limits.js +44 -0
- package/dist/db/rate-limits.js.map +1 -0
- package/dist/db/refresh-tokens.d.ts +44 -0
- package/dist/db/refresh-tokens.d.ts.map +1 -0
- package/dist/db/refresh-tokens.js +71 -0
- package/dist/db/refresh-tokens.js.map +1 -0
- package/dist/db/schema/common.d.ts +2 -0
- package/dist/db/schema/common.d.ts.map +1 -0
- package/dist/db/schema/common.js +4 -0
- package/dist/db/schema/common.js.map +1 -0
- package/dist/db/schema/email-verifications.d.ts +114 -0
- package/dist/db/schema/email-verifications.d.ts.map +1 -0
- package/dist/db/schema/email-verifications.js +35 -0
- package/dist/db/schema/email-verifications.js.map +1 -0
- package/dist/db/schema/outbox.d.ts +195 -0
- package/dist/db/schema/outbox.d.ts.map +1 -0
- package/dist/db/schema/outbox.js +5 -0
- package/dist/db/schema/outbox.js.map +1 -0
- package/dist/db/schema/password-resets.d.ts +114 -0
- package/dist/db/schema/password-resets.d.ts.map +1 -0
- package/dist/db/schema/password-resets.js +35 -0
- package/dist/db/schema/password-resets.js.map +1 -0
- package/dist/db/schema/rate-limits.d.ts +145 -0
- package/dist/db/schema/rate-limits.d.ts.map +1 -0
- package/dist/db/schema/rate-limits.js +25 -0
- package/dist/db/schema/rate-limits.js.map +1 -0
- package/dist/db/schema/refresh-tokens.d.ts +165 -0
- package/dist/db/schema/refresh-tokens.d.ts.map +1 -0
- package/dist/db/schema/refresh-tokens.js +47 -0
- package/dist/db/schema/refresh-tokens.js.map +1 -0
- package/dist/db/schema/users.d.ts +149 -0
- package/dist/db/schema/users.d.ts.map +1 -0
- package/dist/db/schema/users.js +40 -0
- package/dist/db/schema/users.js.map +1 -0
- package/dist/db/users.d.ts +28 -0
- package/dist/db/users.d.ts.map +1 -0
- package/dist/db/users.js +83 -0
- package/dist/db/users.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +52 -0
- package/dist/index.js.map +1 -0
- package/dist/metrics/index.d.ts +2 -0
- package/dist/metrics/index.d.ts.map +1 -0
- package/dist/metrics/index.js +3 -0
- package/dist/metrics/index.js.map +1 -0
- package/dist/metrics/instance.d.ts +16 -0
- package/dist/metrics/instance.d.ts.map +1 -0
- package/dist/metrics/instance.js +52 -0
- package/dist/metrics/instance.js.map +1 -0
- package/dist/presentation/auth/bearer-token-auth.d.ts +15 -0
- package/dist/presentation/auth/bearer-token-auth.d.ts.map +1 -0
- package/dist/presentation/auth/bearer-token-auth.js +31 -0
- package/dist/presentation/auth/bearer-token-auth.js.map +1 -0
- package/dist/presentation/auth/jwt-auth.d.ts +10 -0
- package/dist/presentation/auth/jwt-auth.d.ts.map +1 -0
- package/dist/presentation/auth/jwt-auth.js +31 -0
- package/dist/presentation/auth/jwt-auth.js.map +1 -0
- package/dist/presentation/auth/lease-token-auth.d.ts +15 -0
- package/dist/presentation/auth/lease-token-auth.d.ts.map +1 -0
- package/dist/presentation/auth/lease-token-auth.js +27 -0
- package/dist/presentation/auth/lease-token-auth.js.map +1 -0
- package/dist/presentation/auth/refresh-cookie.d.ts +7 -0
- package/dist/presentation/auth/refresh-cookie.d.ts.map +1 -0
- package/dist/presentation/auth/refresh-cookie.js +29 -0
- package/dist/presentation/auth/refresh-cookie.js.map +1 -0
- package/dist/presentation/auth/runner-session-auth.d.ts +3 -0
- package/dist/presentation/auth/runner-session-auth.d.ts.map +1 -0
- package/dist/presentation/auth/runner-session-auth.js +16 -0
- package/dist/presentation/auth/runner-session-auth.js.map +1 -0
- package/dist/presentation/dto/user.d.ts +8 -0
- package/dist/presentation/dto/user.d.ts.map +1 -0
- package/dist/presentation/dto/user.js +19 -0
- package/dist/presentation/dto/user.js.map +1 -0
- package/dist/presentation/e2eRoutes/create-session.d.ts +2 -0
- package/dist/presentation/e2eRoutes/create-session.d.ts.map +1 -0
- package/dist/presentation/e2eRoutes/create-session.js +49 -0
- package/dist/presentation/e2eRoutes/create-session.js.map +1 -0
- package/dist/presentation/e2eRoutes/create-user.d.ts +2 -0
- package/dist/presentation/e2eRoutes/create-user.d.ts.map +1 -0
- package/dist/presentation/e2eRoutes/create-user.js +42 -0
- package/dist/presentation/e2eRoutes/create-user.js.map +1 -0
- package/dist/presentation/e2eRoutes/index.d.ts +3 -0
- package/dist/presentation/e2eRoutes/index.d.ts.map +1 -0
- package/dist/presentation/e2eRoutes/index.js +15 -0
- package/dist/presentation/e2eRoutes/index.js.map +1 -0
- package/dist/presentation/routes/email-verification/verify-email-confirm.d.ts +2 -0
- package/dist/presentation/routes/email-verification/verify-email-confirm.d.ts.map +1 -0
- package/dist/presentation/routes/email-verification/verify-email-confirm.js +35 -0
- package/dist/presentation/routes/email-verification/verify-email-confirm.js.map +1 -0
- package/dist/presentation/routes/email-verification/verify-email-resend.d.ts +2 -0
- package/dist/presentation/routes/email-verification/verify-email-resend.d.ts.map +1 -0
- package/dist/presentation/routes/email-verification/verify-email-resend.js +27 -0
- package/dist/presentation/routes/email-verification/verify-email-resend.js.map +1 -0
- package/dist/presentation/routes/index.d.ts +3 -0
- package/dist/presentation/routes/index.d.ts.map +1 -0
- package/dist/presentation/routes/index.js +31 -0
- package/dist/presentation/routes/index.js.map +1 -0
- package/dist/presentation/routes/password/change-password.d.ts +2 -0
- package/dist/presentation/routes/password/change-password.d.ts.map +1 -0
- package/dist/presentation/routes/password/change-password.js +50 -0
- package/dist/presentation/routes/password/change-password.js.map +1 -0
- package/dist/presentation/routes/password/password-reset-confirm.d.ts +2 -0
- package/dist/presentation/routes/password/password-reset-confirm.d.ts.map +1 -0
- package/dist/presentation/routes/password/password-reset-confirm.js +36 -0
- package/dist/presentation/routes/password/password-reset-confirm.js.map +1 -0
- package/dist/presentation/routes/password/password-reset-request.d.ts +2 -0
- package/dist/presentation/routes/password/password-reset-request.d.ts.map +1 -0
- package/dist/presentation/routes/password/password-reset-request.js +26 -0
- package/dist/presentation/routes/password/password-reset-request.js.map +1 -0
- package/dist/presentation/routes/rate-limit.d.ts +10 -0
- package/dist/presentation/routes/rate-limit.d.ts.map +1 -0
- package/dist/presentation/routes/rate-limit.js +101 -0
- package/dist/presentation/routes/rate-limit.js.map +1 -0
- package/dist/presentation/routes/registration/signup.d.ts +2 -0
- package/dist/presentation/routes/registration/signup.d.ts.map +1 -0
- package/dist/presentation/routes/registration/signup.js +82 -0
- package/dist/presentation/routes/registration/signup.js.map +1 -0
- package/dist/presentation/routes/session/login.d.ts +2 -0
- package/dist/presentation/routes/session/login.d.ts.map +1 -0
- package/dist/presentation/routes/session/login.js +48 -0
- package/dist/presentation/routes/session/login.js.map +1 -0
- package/dist/presentation/routes/session/logout.d.ts +2 -0
- package/dist/presentation/routes/session/logout.d.ts.map +1 -0
- package/dist/presentation/routes/session/logout.js +25 -0
- package/dist/presentation/routes/session/logout.js.map +1 -0
- package/dist/presentation/routes/session/me.d.ts +2 -0
- package/dist/presentation/routes/session/me.d.ts.map +1 -0
- package/dist/presentation/routes/session/me.js +42 -0
- package/dist/presentation/routes/session/me.js.map +1 -0
- package/dist/presentation/routes/session/refresh.d.ts +2 -0
- package/dist/presentation/routes/session/refresh.d.ts.map +1 -0
- package/dist/presentation/routes/session/refresh.js +49 -0
- package/dist/presentation/routes/session/refresh.js.map +1 -0
- package/dist/presentation/subscribers/index.d.ts +3 -0
- package/dist/presentation/subscribers/index.d.ts.map +1 -0
- package/dist/presentation/subscribers/index.js +4 -0
- package/dist/presentation/subscribers/index.js.map +1 -0
- package/dist/presentation/subscribers/on-email-verification-send-requested.d.ts +3 -0
- package/dist/presentation/subscribers/on-email-verification-send-requested.d.ts.map +1 -0
- package/dist/presentation/subscribers/on-email-verification-send-requested.js +13 -0
- package/dist/presentation/subscribers/on-email-verification-send-requested.js.map +1 -0
- package/dist/presentation/subscribers/on-password-reset-send-requested.d.ts +3 -0
- package/dist/presentation/subscribers/on-password-reset-send-requested.d.ts.map +1 -0
- package/dist/presentation/subscribers/on-password-reset-send-requested.js +14 -0
- package/dist/presentation/subscribers/on-password-reset-send-requested.js.map +1 -0
- package/dist/tsconfig.test.tsbuildinfo +1 -0
- package/drizzle/0000_initial.sql +81 -0
- package/drizzle/meta/0000_snapshot.json +643 -0
- package/drizzle/meta/_journal.json +13 -0
- package/drizzle.config.ts +7 -0
- package/package.json +88 -0
- package/src/config.ts +88 -0
- package/src/core/auth.test.ts +675 -0
- package/src/core/auth.ts +560 -0
- package/src/core/entities/email-verification.ts +8 -0
- package/src/core/entities/password-reset.ts +8 -0
- package/src/core/entities/refresh-token.ts +11 -0
- package/src/core/entities/user.ts +12 -0
- package/src/core/errors.ts +123 -0
- package/src/core/job-lease-token.test.ts +201 -0
- package/src/core/job-lease-token.ts +83 -0
- package/src/core/jwt.test.ts +180 -0
- package/src/core/jwt.ts +66 -0
- package/src/core/password.test.ts +28 -0
- package/src/core/password.ts +21 -0
- package/src/core/rate-limit.test.ts +462 -0
- package/src/core/rate-limit.ts +130 -0
- package/src/core/runner-session-token.test.ts +126 -0
- package/src/core/runner-session-token.ts +49 -0
- package/src/db/db.ts +28 -0
- package/src/db/email-verifications.test.ts +61 -0
- package/src/db/email-verifications.ts +160 -0
- package/src/db/migrations.ts +4 -0
- package/src/db/password-resets.test.ts +61 -0
- package/src/db/password-resets.ts +69 -0
- package/src/db/rate-limits.ts +69 -0
- package/src/db/refresh-tokens.test.ts +164 -0
- package/src/db/refresh-tokens.ts +149 -0
- package/src/db/schema/common.ts +3 -0
- package/src/db/schema/email-verifications.ts +37 -0
- package/src/db/schema/outbox.ts +4 -0
- package/src/db/schema/password-resets.ts +37 -0
- package/src/db/schema/rate-limits.ts +27 -0
- package/src/db/schema/refresh-tokens.ts +43 -0
- package/src/db/schema/users.ts +37 -0
- package/src/db/users.test.ts +75 -0
- package/src/db/users.ts +121 -0
- package/src/index.test.ts +39 -0
- package/src/index.ts +69 -0
- package/src/metrics/index.ts +13 -0
- package/src/metrics/instance.test.ts +69 -0
- package/src/metrics/instance.ts +77 -0
- package/src/presentation/auth/bearer-token-auth.test.ts +99 -0
- package/src/presentation/auth/bearer-token-auth.ts +51 -0
- package/src/presentation/auth/jwt-auth.test.ts +136 -0
- package/src/presentation/auth/jwt-auth.ts +39 -0
- package/src/presentation/auth/lease-token-auth.test.ts +56 -0
- package/src/presentation/auth/lease-token-auth.ts +25 -0
- package/src/presentation/auth/refresh-cookie.test.ts +39 -0
- package/src/presentation/auth/refresh-cookie.ts +34 -0
- package/src/presentation/auth/runner-session-auth.test.ts +54 -0
- package/src/presentation/auth/runner-session-auth.ts +13 -0
- package/src/presentation/dto/user.ts +21 -0
- package/src/presentation/e2eRoutes/create-session.ts +39 -0
- package/src/presentation/e2eRoutes/create-user.ts +38 -0
- package/src/presentation/e2eRoutes/index.test.ts +102 -0
- package/src/presentation/e2eRoutes/index.ts +10 -0
- package/src/presentation/routes/email-verification/verify-email-confirm.test.ts +66 -0
- package/src/presentation/routes/email-verification/verify-email-confirm.ts +37 -0
- package/src/presentation/routes/email-verification/verify-email-resend.test.ts +100 -0
- package/src/presentation/routes/email-verification/verify-email-resend.ts +26 -0
- package/src/presentation/routes/index.ts +29 -0
- package/src/presentation/routes/password/change-password.test.ts +106 -0
- package/src/presentation/routes/password/change-password.ts +45 -0
- package/src/presentation/routes/password/password-reset-confirm.test.ts +63 -0
- package/src/presentation/routes/password/password-reset-confirm.ts +35 -0
- package/src/presentation/routes/password/password-reset-request.test.ts +56 -0
- package/src/presentation/routes/password/password-reset-request.ts +25 -0
- package/src/presentation/routes/rate-limit.test.ts +341 -0
- package/src/presentation/routes/rate-limit.ts +119 -0
- package/src/presentation/routes/registration/signup.test.ts +284 -0
- package/src/presentation/routes/registration/signup.ts +81 -0
- package/src/presentation/routes/session/login.test.ts +77 -0
- package/src/presentation/routes/session/login.ts +50 -0
- package/src/presentation/routes/session/logout.test.ts +55 -0
- package/src/presentation/routes/session/logout.ts +23 -0
- package/src/presentation/routes/session/me.test.ts +56 -0
- package/src/presentation/routes/session/me.ts +35 -0
- package/src/presentation/routes/session/refresh.test.ts +89 -0
- package/src/presentation/routes/session/refresh.ts +54 -0
- package/src/presentation/subscribers/email-send-requested.test.ts +77 -0
- package/src/presentation/subscribers/index.ts +2 -0
- package/src/presentation/subscribers/on-email-verification-send-requested.ts +10 -0
- package/src/presentation/subscribers/on-password-reset-send-requested.ts +13 -0
- package/test/env.ts +10 -0
- package/test/factories/index.ts +1 -0
- package/test/factories/user.ts +36 -0
- package/test/globalSetup.ts +18 -0
- package/test/index.ts +1 -0
- package/test/routes.ts +275 -0
- package/test/setup.ts +17 -0
- package/tsconfig.build.json +9 -0
- package/tsconfig.build.tsbuildinfo +1 -0
- package/tsconfig.json +4 -0
- package/tsconfig.test.json +8 -0
- package/vitest.config.ts +11 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../../src/db/db.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,cAAc,EAAC,MAAM,uBAAuB,CAAC;AASnE,eAAO,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAOlB,CAAC;AAIF,wBAAgB,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAGjB;AAED,wBAAgB,OAAO,IAAI,IAAI,CAE9B"}
|
package/dist/db/db.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { drizzle } from '@shipfox/node-drizzle';
|
|
2
|
+
import { pgClient } from '@shipfox/node-postgres';
|
|
3
|
+
import { emailVerifications } from './schema/email-verifications.js';
|
|
4
|
+
import { authOutbox } from './schema/outbox.js';
|
|
5
|
+
import { passwordResets } from './schema/password-resets.js';
|
|
6
|
+
import { authRateLimits } from './schema/rate-limits.js';
|
|
7
|
+
import { refreshTokens } from './schema/refresh-tokens.js';
|
|
8
|
+
import { users } from './schema/users.js';
|
|
9
|
+
export const schema = {
|
|
10
|
+
users,
|
|
11
|
+
passwordResets,
|
|
12
|
+
refreshTokens,
|
|
13
|
+
emailVerifications,
|
|
14
|
+
authOutbox,
|
|
15
|
+
authRateLimits
|
|
16
|
+
};
|
|
17
|
+
let _db;
|
|
18
|
+
export function db() {
|
|
19
|
+
if (!_db) _db = drizzle(pgClient(), {
|
|
20
|
+
schema
|
|
21
|
+
});
|
|
22
|
+
return _db;
|
|
23
|
+
}
|
|
24
|
+
export function closeDb() {
|
|
25
|
+
_db = undefined;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
//# sourceMappingURL=db.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/db/db.ts"],"sourcesContent":["import {drizzle, type NodePgDatabase} from '@shipfox/node-drizzle';\nimport {pgClient} from '@shipfox/node-postgres';\nimport {emailVerifications} from './schema/email-verifications.js';\nimport {authOutbox} from './schema/outbox.js';\nimport {passwordResets} from './schema/password-resets.js';\nimport {authRateLimits} from './schema/rate-limits.js';\nimport {refreshTokens} from './schema/refresh-tokens.js';\nimport {users} from './schema/users.js';\n\nexport const schema = {\n users,\n passwordResets,\n refreshTokens,\n emailVerifications,\n authOutbox,\n authRateLimits,\n};\n\nlet _db: NodePgDatabase<typeof schema> | undefined;\n\nexport function db() {\n if (!_db) _db = drizzle(pgClient(), {schema});\n return _db;\n}\n\nexport function closeDb(): void {\n _db = undefined;\n}\n"],"names":["drizzle","pgClient","emailVerifications","authOutbox","passwordResets","authRateLimits","refreshTokens","users","schema","_db","db","closeDb","undefined"],"mappings":"AAAA,SAAQA,OAAO,QAA4B,wBAAwB;AACnE,SAAQC,QAAQ,QAAO,yBAAyB;AAChD,SAAQC,kBAAkB,QAAO,kCAAkC;AACnE,SAAQC,UAAU,QAAO,qBAAqB;AAC9C,SAAQC,cAAc,QAAO,8BAA8B;AAC3D,SAAQC,cAAc,QAAO,0BAA0B;AACvD,SAAQC,aAAa,QAAO,6BAA6B;AACzD,SAAQC,KAAK,QAAO,oBAAoB;AAExC,OAAO,MAAMC,SAAS;IACpBD;IACAH;IACAE;IACAJ;IACAC;IACAE;AACF,EAAE;AAEF,IAAII;AAEJ,OAAO,SAASC;IACd,IAAI,CAACD,KAAKA,MAAMT,QAAQC,YAAY;QAACO;IAAM;IAC3C,OAAOC;AACT;AAEA,OAAO,SAASE;IACdF,MAAMG;AACR"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { EmailVerification } from '#core/entities/email-verification.js';
|
|
2
|
+
import type { User } from '#core/entities/user.js';
|
|
3
|
+
interface CreateEmailVerificationBaseParams {
|
|
4
|
+
userId: string;
|
|
5
|
+
hashedToken: string;
|
|
6
|
+
expiresAt: Date;
|
|
7
|
+
}
|
|
8
|
+
export type CreateEmailVerificationParams = CreateEmailVerificationBaseParams & ({
|
|
9
|
+
sendEmail: {
|
|
10
|
+
email: string;
|
|
11
|
+
verifyLink: string;
|
|
12
|
+
};
|
|
13
|
+
skipEmail?: never;
|
|
14
|
+
} | {
|
|
15
|
+
sendEmail?: never;
|
|
16
|
+
skipEmail: true;
|
|
17
|
+
});
|
|
18
|
+
export declare function createEmailVerification(params: CreateEmailVerificationParams): Promise<EmailVerification>;
|
|
19
|
+
export interface CreateResendEmailVerificationParams {
|
|
20
|
+
email: string;
|
|
21
|
+
hashedToken: string;
|
|
22
|
+
expiresAt: Date;
|
|
23
|
+
cooldownSeconds: number;
|
|
24
|
+
sendEmail: {
|
|
25
|
+
verifyLink: string;
|
|
26
|
+
};
|
|
27
|
+
now?: Date | undefined;
|
|
28
|
+
}
|
|
29
|
+
export interface CreateResendEmailVerificationResult {
|
|
30
|
+
user?: User | undefined;
|
|
31
|
+
verification?: EmailVerification | undefined;
|
|
32
|
+
nextResendAvailableAt: Date;
|
|
33
|
+
}
|
|
34
|
+
export declare function createResendEmailVerification(params: CreateResendEmailVerificationParams): Promise<CreateResendEmailVerificationResult>;
|
|
35
|
+
export declare function consumeEmailVerification(params: {
|
|
36
|
+
hashedToken: string;
|
|
37
|
+
}): Promise<EmailVerification | undefined>;
|
|
38
|
+
export {};
|
|
39
|
+
//# sourceMappingURL=email-verifications.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"email-verifications.d.ts","sourceRoot":"","sources":["../../src/db/email-verifications.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,sCAAsC,CAAC;AAC5E,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,wBAAwB,CAAC;AAMjD,UAAU,iCAAiC;IACzC,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,IAAI,CAAC;CACjB;AAED,MAAM,MAAM,6BAA6B,GAAG,iCAAiC,GAC3E,CACI;IAAC,SAAS,EAAE;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAC,CAAC;IAAC,SAAS,CAAC,EAAE,KAAK,CAAA;CAAC,GACnE;IAAC,SAAS,CAAC,EAAE,KAAK,CAAC;IAAC,SAAS,EAAE,IAAI,CAAA;CAAC,CACvC,CAAC;AAEJ,wBAAsB,uBAAuB,CAC3C,MAAM,EAAE,6BAA6B,GACpC,OAAO,CAAC,iBAAiB,CAAC,CA0B5B;AAED,MAAM,WAAW,mCAAmC;IAClD,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,IAAI,CAAC;IAChB,eAAe,EAAE,MAAM,CAAC;IACxB,SAAS,EAAE;QAAC,UAAU,EAAE,MAAM,CAAA;KAAC,CAAC;IAChC,GAAG,CAAC,EAAE,IAAI,GAAG,SAAS,CAAC;CACxB;AAED,MAAM,WAAW,mCAAmC;IAClD,IAAI,CAAC,EAAE,IAAI,GAAG,SAAS,CAAC;IACxB,YAAY,CAAC,EAAE,iBAAiB,GAAG,SAAS,CAAC;IAC7C,qBAAqB,EAAE,IAAI,CAAC;CAC7B;AAMD,wBAAsB,6BAA6B,CACjD,MAAM,EAAE,mCAAmC,GAC1C,OAAO,CAAC,mCAAmC,CAAC,CAkE9C;AAED,wBAAsB,wBAAwB,CAAC,MAAM,EAAE;IACrD,WAAW,EAAE,MAAM,CAAC;CACrB,GAAG,OAAO,CAAC,iBAAiB,GAAG,SAAS,CAAC,CAgBzC"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { AUTH_EMAIL_VERIFICATION_SEND_REQUESTED } from '@shipfox/api-auth-dto';
|
|
2
|
+
import { writeOutboxEvent } from '@shipfox/node-outbox';
|
|
3
|
+
import { and, desc, eq, gt, isNull, sql } from 'drizzle-orm';
|
|
4
|
+
import { db } from './db.js';
|
|
5
|
+
import { emailVerifications, toEmailVerification } from './schema/email-verifications.js';
|
|
6
|
+
import { authOutbox } from './schema/outbox.js';
|
|
7
|
+
import { toUser, users } from './schema/users.js';
|
|
8
|
+
export async function createEmailVerification(params) {
|
|
9
|
+
return await db().transaction(async (tx)=>{
|
|
10
|
+
await tx.update(emailVerifications).set({
|
|
11
|
+
usedAt: sql`now()`
|
|
12
|
+
}).where(and(eq(emailVerifications.userId, params.userId), isNull(emailVerifications.usedAt)));
|
|
13
|
+
const rows = await tx.insert(emailVerifications).values({
|
|
14
|
+
userId: params.userId,
|
|
15
|
+
hashedToken: params.hashedToken,
|
|
16
|
+
expiresAt: params.expiresAt
|
|
17
|
+
}).returning();
|
|
18
|
+
const row = rows[0];
|
|
19
|
+
if (!row) throw new Error('Insert returned no rows');
|
|
20
|
+
if (params.sendEmail) {
|
|
21
|
+
await writeOutboxEvent(tx, authOutbox, {
|
|
22
|
+
type: AUTH_EMAIL_VERIFICATION_SEND_REQUESTED,
|
|
23
|
+
payload: params.sendEmail
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
return toEmailVerification(row);
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
function secondsAfter(date, seconds) {
|
|
30
|
+
return new Date(date.getTime() + seconds * 1000);
|
|
31
|
+
}
|
|
32
|
+
export async function createResendEmailVerification(params) {
|
|
33
|
+
const now = params.now ?? new Date();
|
|
34
|
+
const genericNextResendAvailableAt = secondsAfter(now, params.cooldownSeconds);
|
|
35
|
+
return await db().transaction(async (tx)=>{
|
|
36
|
+
const userRows = await tx.select().from(users).where(eq(users.email, params.email)).limit(1).for('update');
|
|
37
|
+
const userRow = userRows[0];
|
|
38
|
+
if (!userRow) {
|
|
39
|
+
return {
|
|
40
|
+
nextResendAvailableAt: genericNextResendAvailableAt
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
const user = toUser(userRow);
|
|
44
|
+
if (user.status !== 'active' || user.emailVerifiedAt !== null) {
|
|
45
|
+
return {
|
|
46
|
+
nextResendAvailableAt: genericNextResendAvailableAt
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
const latestRows = await tx.select().from(emailVerifications).where(and(eq(emailVerifications.userId, user.id), isNull(emailVerifications.usedAt))).orderBy(desc(emailVerifications.createdAt)).limit(1);
|
|
50
|
+
const latest = latestRows[0] ? toEmailVerification(latestRows[0]) : undefined;
|
|
51
|
+
if (latest && secondsAfter(latest.createdAt, params.cooldownSeconds) > now) {
|
|
52
|
+
// Return the same public retry estimate as no-op cases so the endpoint does
|
|
53
|
+
// not expose existing account state or prior verification timing.
|
|
54
|
+
return {
|
|
55
|
+
user,
|
|
56
|
+
nextResendAvailableAt: genericNextResendAvailableAt
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
await tx.update(emailVerifications).set({
|
|
60
|
+
usedAt: sql`now()`
|
|
61
|
+
}).where(and(eq(emailVerifications.userId, user.id), isNull(emailVerifications.usedAt)));
|
|
62
|
+
const rows = await tx.insert(emailVerifications).values({
|
|
63
|
+
userId: user.id,
|
|
64
|
+
hashedToken: params.hashedToken,
|
|
65
|
+
expiresAt: params.expiresAt
|
|
66
|
+
}).returning();
|
|
67
|
+
const row = rows[0];
|
|
68
|
+
if (!row) throw new Error('Insert returned no rows');
|
|
69
|
+
const verification = toEmailVerification(row);
|
|
70
|
+
if (params.sendEmail) {
|
|
71
|
+
await writeOutboxEvent(tx, authOutbox, {
|
|
72
|
+
type: AUTH_EMAIL_VERIFICATION_SEND_REQUESTED,
|
|
73
|
+
payload: {
|
|
74
|
+
email: user.email,
|
|
75
|
+
verifyLink: params.sendEmail.verifyLink
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
user,
|
|
81
|
+
verification,
|
|
82
|
+
nextResendAvailableAt: secondsAfter(verification.createdAt, params.cooldownSeconds)
|
|
83
|
+
};
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
export async function consumeEmailVerification(params) {
|
|
87
|
+
const rows = await db().update(emailVerifications).set({
|
|
88
|
+
usedAt: sql`now()`
|
|
89
|
+
}).where(and(eq(emailVerifications.hashedToken, params.hashedToken), isNull(emailVerifications.usedAt), gt(emailVerifications.expiresAt, sql`now()`))).returning();
|
|
90
|
+
const row = rows[0];
|
|
91
|
+
if (!row) return undefined;
|
|
92
|
+
return toEmailVerification(row);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
//# sourceMappingURL=email-verifications.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/db/email-verifications.ts"],"sourcesContent":["import {AUTH_EMAIL_VERIFICATION_SEND_REQUESTED, type AuthEventMap} from '@shipfox/api-auth-dto';\nimport {writeOutboxEvent} from '@shipfox/node-outbox';\nimport {and, desc, eq, gt, isNull, sql} from 'drizzle-orm';\nimport type {EmailVerification} from '#core/entities/email-verification.js';\nimport type {User} from '#core/entities/user.js';\nimport {db} from './db.js';\nimport {emailVerifications, toEmailVerification} from './schema/email-verifications.js';\nimport {authOutbox} from './schema/outbox.js';\nimport {toUser, users} from './schema/users.js';\n\ninterface CreateEmailVerificationBaseParams {\n userId: string;\n hashedToken: string;\n expiresAt: Date;\n}\n\nexport type CreateEmailVerificationParams = CreateEmailVerificationBaseParams &\n (\n | {sendEmail: {email: string; verifyLink: string}; skipEmail?: never}\n | {sendEmail?: never; skipEmail: true}\n );\n\nexport async function createEmailVerification(\n params: CreateEmailVerificationParams,\n): Promise<EmailVerification> {\n return await db().transaction(async (tx) => {\n await tx\n .update(emailVerifications)\n .set({usedAt: sql`now()`})\n .where(and(eq(emailVerifications.userId, params.userId), isNull(emailVerifications.usedAt)));\n\n const rows = await tx\n .insert(emailVerifications)\n .values({\n userId: params.userId,\n hashedToken: params.hashedToken,\n expiresAt: params.expiresAt,\n })\n .returning();\n\n const row = rows[0];\n if (!row) throw new Error('Insert returned no rows');\n if (params.sendEmail) {\n await writeOutboxEvent<AuthEventMap>(tx, authOutbox, {\n type: AUTH_EMAIL_VERIFICATION_SEND_REQUESTED,\n payload: params.sendEmail,\n });\n }\n return toEmailVerification(row);\n });\n}\n\nexport interface CreateResendEmailVerificationParams {\n email: string;\n hashedToken: string;\n expiresAt: Date;\n cooldownSeconds: number;\n sendEmail: {verifyLink: string};\n now?: Date | undefined;\n}\n\nexport interface CreateResendEmailVerificationResult {\n user?: User | undefined;\n verification?: EmailVerification | undefined;\n nextResendAvailableAt: Date;\n}\n\nfunction secondsAfter(date: Date, seconds: number): Date {\n return new Date(date.getTime() + seconds * 1000);\n}\n\nexport async function createResendEmailVerification(\n params: CreateResendEmailVerificationParams,\n): Promise<CreateResendEmailVerificationResult> {\n const now = params.now ?? new Date();\n const genericNextResendAvailableAt = secondsAfter(now, params.cooldownSeconds);\n\n return await db().transaction(async (tx) => {\n const userRows = await tx\n .select()\n .from(users)\n .where(eq(users.email, params.email))\n .limit(1)\n .for('update');\n const userRow = userRows[0];\n if (!userRow) {\n return {nextResendAvailableAt: genericNextResendAvailableAt};\n }\n\n const user = toUser(userRow);\n if (user.status !== 'active' || user.emailVerifiedAt !== null) {\n return {nextResendAvailableAt: genericNextResendAvailableAt};\n }\n\n const latestRows = await tx\n .select()\n .from(emailVerifications)\n .where(and(eq(emailVerifications.userId, user.id), isNull(emailVerifications.usedAt)))\n .orderBy(desc(emailVerifications.createdAt))\n .limit(1);\n const latest = latestRows[0] ? toEmailVerification(latestRows[0]) : undefined;\n if (latest && secondsAfter(latest.createdAt, params.cooldownSeconds) > now) {\n // Return the same public retry estimate as no-op cases so the endpoint does\n // not expose existing account state or prior verification timing.\n return {user, nextResendAvailableAt: genericNextResendAvailableAt};\n }\n\n await tx\n .update(emailVerifications)\n .set({usedAt: sql`now()`})\n .where(and(eq(emailVerifications.userId, user.id), isNull(emailVerifications.usedAt)));\n\n const rows = await tx\n .insert(emailVerifications)\n .values({\n userId: user.id,\n hashedToken: params.hashedToken,\n expiresAt: params.expiresAt,\n })\n .returning();\n\n const row = rows[0];\n if (!row) throw new Error('Insert returned no rows');\n const verification = toEmailVerification(row);\n if (params.sendEmail) {\n await writeOutboxEvent<AuthEventMap>(tx, authOutbox, {\n type: AUTH_EMAIL_VERIFICATION_SEND_REQUESTED,\n payload: {\n email: user.email,\n verifyLink: params.sendEmail.verifyLink,\n },\n });\n }\n return {\n user,\n verification,\n nextResendAvailableAt: secondsAfter(verification.createdAt, params.cooldownSeconds),\n };\n });\n}\n\nexport async function consumeEmailVerification(params: {\n hashedToken: string;\n}): Promise<EmailVerification | undefined> {\n const rows = await db()\n .update(emailVerifications)\n .set({usedAt: sql`now()`})\n .where(\n and(\n eq(emailVerifications.hashedToken, params.hashedToken),\n isNull(emailVerifications.usedAt),\n gt(emailVerifications.expiresAt, sql`now()`),\n ),\n )\n .returning();\n\n const row = rows[0];\n if (!row) return undefined;\n return toEmailVerification(row);\n}\n"],"names":["AUTH_EMAIL_VERIFICATION_SEND_REQUESTED","writeOutboxEvent","and","desc","eq","gt","isNull","sql","db","emailVerifications","toEmailVerification","authOutbox","toUser","users","createEmailVerification","params","transaction","tx","update","set","usedAt","where","userId","rows","insert","values","hashedToken","expiresAt","returning","row","Error","sendEmail","type","payload","secondsAfter","date","seconds","Date","getTime","createResendEmailVerification","now","genericNextResendAvailableAt","cooldownSeconds","userRows","select","from","email","limit","for","userRow","nextResendAvailableAt","user","status","emailVerifiedAt","latestRows","id","orderBy","createdAt","latest","undefined","verification","verifyLink","consumeEmailVerification"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migrations.d.ts","sourceRoot":"","sources":["../../src/db/migrations.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,cAAc,QAAoE,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/db/migrations.ts"],"sourcesContent":["import {dirname, resolve} from 'node:path';\nimport {fileURLToPath} from 'node:url';\n\nexport const migrationsPath = resolve(dirname(fileURLToPath(import.meta.url)), '../../drizzle');\n"],"names":["dirname","resolve","fileURLToPath","migrationsPath","url"],"mappings":"AAAA,SAAQA,OAAO,EAAEC,OAAO,QAAO,YAAY;AAC3C,SAAQC,aAAa,QAAO,WAAW;AAEvC,OAAO,MAAMC,iBAAiBF,QAAQD,QAAQE,cAAc,YAAYE,GAAG,IAAI,iBAAiB"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { PasswordReset } from '#core/entities/password-reset.js';
|
|
2
|
+
interface CreatePasswordResetBaseParams {
|
|
3
|
+
userId: string;
|
|
4
|
+
hashedToken: string;
|
|
5
|
+
expiresAt: Date;
|
|
6
|
+
}
|
|
7
|
+
export type CreatePasswordResetParams = CreatePasswordResetBaseParams & ({
|
|
8
|
+
sendEmail: {
|
|
9
|
+
email: string;
|
|
10
|
+
resetLink: string;
|
|
11
|
+
expiresInHours: number;
|
|
12
|
+
};
|
|
13
|
+
skipEmail?: never;
|
|
14
|
+
} | {
|
|
15
|
+
sendEmail?: never;
|
|
16
|
+
skipEmail: true;
|
|
17
|
+
});
|
|
18
|
+
export declare function createPasswordReset(params: CreatePasswordResetParams): Promise<PasswordReset>;
|
|
19
|
+
export declare function consumePasswordReset(params: {
|
|
20
|
+
hashedToken: string;
|
|
21
|
+
}): Promise<PasswordReset | undefined>;
|
|
22
|
+
export {};
|
|
23
|
+
//# sourceMappingURL=password-resets.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"password-resets.d.ts","sourceRoot":"","sources":["../../src/db/password-resets.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,kCAAkC,CAAC;AAKpE,UAAU,6BAA6B;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,IAAI,CAAC;CACjB;AAED,MAAM,MAAM,yBAAyB,GAAG,6BAA6B,GACnE,CACI;IAAC,SAAS,EAAE;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,cAAc,EAAE,MAAM,CAAA;KAAC,CAAC;IAAC,SAAS,CAAC,EAAE,KAAK,CAAA;CAAC,GAC1F;IAAC,SAAS,CAAC,EAAE,KAAK,CAAC;IAAC,SAAS,EAAE,IAAI,CAAA;CAAC,CACvC,CAAC;AAEJ,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,yBAAyB,GAChC,OAAO,CAAC,aAAa,CAAC,CA0BxB;AAED,wBAAsB,oBAAoB,CAAC,MAAM,EAAE;IACjD,WAAW,EAAE,MAAM,CAAC;CACrB,GAAG,OAAO,CAAC,aAAa,GAAG,SAAS,CAAC,CAgBrC"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { AUTH_PASSWORD_RESET_SEND_REQUESTED } from '@shipfox/api-auth-dto';
|
|
2
|
+
import { writeOutboxEvent } from '@shipfox/node-outbox';
|
|
3
|
+
import { and, eq, gt, isNull, sql } from 'drizzle-orm';
|
|
4
|
+
import { db } from './db.js';
|
|
5
|
+
import { authOutbox } from './schema/outbox.js';
|
|
6
|
+
import { passwordResets, toPasswordReset } from './schema/password-resets.js';
|
|
7
|
+
export async function createPasswordReset(params) {
|
|
8
|
+
return await db().transaction(async (tx)=>{
|
|
9
|
+
await tx.update(passwordResets).set({
|
|
10
|
+
usedAt: sql`now()`
|
|
11
|
+
}).where(and(eq(passwordResets.userId, params.userId), isNull(passwordResets.usedAt)));
|
|
12
|
+
const rows = await tx.insert(passwordResets).values({
|
|
13
|
+
userId: params.userId,
|
|
14
|
+
hashedToken: params.hashedToken,
|
|
15
|
+
expiresAt: params.expiresAt
|
|
16
|
+
}).returning();
|
|
17
|
+
const row = rows[0];
|
|
18
|
+
if (!row) throw new Error('Insert returned no rows');
|
|
19
|
+
if (params.sendEmail) {
|
|
20
|
+
await writeOutboxEvent(tx, authOutbox, {
|
|
21
|
+
type: AUTH_PASSWORD_RESET_SEND_REQUESTED,
|
|
22
|
+
payload: params.sendEmail
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
return toPasswordReset(row);
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
export async function consumePasswordReset(params) {
|
|
29
|
+
const rows = await db().update(passwordResets).set({
|
|
30
|
+
usedAt: sql`now()`
|
|
31
|
+
}).where(and(eq(passwordResets.hashedToken, params.hashedToken), isNull(passwordResets.usedAt), gt(passwordResets.expiresAt, sql`now()`))).returning();
|
|
32
|
+
const row = rows[0];
|
|
33
|
+
if (!row) return undefined;
|
|
34
|
+
return toPasswordReset(row);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
//# sourceMappingURL=password-resets.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/db/password-resets.ts"],"sourcesContent":["import {AUTH_PASSWORD_RESET_SEND_REQUESTED, type AuthEventMap} from '@shipfox/api-auth-dto';\nimport {writeOutboxEvent} from '@shipfox/node-outbox';\nimport {and, eq, gt, isNull, sql} from 'drizzle-orm';\nimport type {PasswordReset} from '#core/entities/password-reset.js';\nimport {db} from './db.js';\nimport {authOutbox} from './schema/outbox.js';\nimport {passwordResets, toPasswordReset} from './schema/password-resets.js';\n\ninterface CreatePasswordResetBaseParams {\n userId: string;\n hashedToken: string;\n expiresAt: Date;\n}\n\nexport type CreatePasswordResetParams = CreatePasswordResetBaseParams &\n (\n | {sendEmail: {email: string; resetLink: string; expiresInHours: number}; skipEmail?: never}\n | {sendEmail?: never; skipEmail: true}\n );\n\nexport async function createPasswordReset(\n params: CreatePasswordResetParams,\n): Promise<PasswordReset> {\n return await db().transaction(async (tx) => {\n await tx\n .update(passwordResets)\n .set({usedAt: sql`now()`})\n .where(and(eq(passwordResets.userId, params.userId), isNull(passwordResets.usedAt)));\n\n const rows = await tx\n .insert(passwordResets)\n .values({\n userId: params.userId,\n hashedToken: params.hashedToken,\n expiresAt: params.expiresAt,\n })\n .returning();\n\n const row = rows[0];\n if (!row) throw new Error('Insert returned no rows');\n if (params.sendEmail) {\n await writeOutboxEvent<AuthEventMap>(tx, authOutbox, {\n type: AUTH_PASSWORD_RESET_SEND_REQUESTED,\n payload: params.sendEmail,\n });\n }\n return toPasswordReset(row);\n });\n}\n\nexport async function consumePasswordReset(params: {\n hashedToken: string;\n}): Promise<PasswordReset | undefined> {\n const rows = await db()\n .update(passwordResets)\n .set({usedAt: sql`now()`})\n .where(\n and(\n eq(passwordResets.hashedToken, params.hashedToken),\n isNull(passwordResets.usedAt),\n gt(passwordResets.expiresAt, sql`now()`),\n ),\n )\n .returning();\n\n const row = rows[0];\n if (!row) return undefined;\n return toPasswordReset(row);\n}\n"],"names":["AUTH_PASSWORD_RESET_SEND_REQUESTED","writeOutboxEvent","and","eq","gt","isNull","sql","db","authOutbox","passwordResets","toPasswordReset","createPasswordReset","params","transaction","tx","update","set","usedAt","where","userId","rows","insert","values","hashedToken","expiresAt","returning","row","Error","sendEmail","type","payload","consumePasswordReset","undefined"],"mappings":"AAAA,SAAQA,kCAAkC,QAA0B,wBAAwB;AAC5F,SAAQC,gBAAgB,QAAO,uBAAuB;AACtD,SAAQC,GAAG,EAAEC,EAAE,EAAEC,EAAE,EAAEC,MAAM,EAAEC,GAAG,QAAO,cAAc;AAErD,SAAQC,EAAE,QAAO,UAAU;AAC3B,SAAQC,UAAU,QAAO,qBAAqB;AAC9C,SAAQC,cAAc,EAAEC,eAAe,QAAO,8BAA8B;AAc5E,OAAO,eAAeC,oBACpBC,MAAiC;IAEjC,OAAO,MAAML,KAAKM,WAAW,CAAC,OAAOC;QACnC,MAAMA,GACHC,MAAM,CAACN,gBACPO,GAAG,CAAC;YAACC,QAAQX,GAAG,CAAC,KAAK,CAAC;QAAA,GACvBY,KAAK,CAAChB,IAAIC,GAAGM,eAAeU,MAAM,EAAEP,OAAOO,MAAM,GAAGd,OAAOI,eAAeQ,MAAM;QAEnF,MAAMG,OAAO,MAAMN,GAChBO,MAAM,CAACZ,gBACPa,MAAM,CAAC;YACNH,QAAQP,OAAOO,MAAM;YACrBI,aAAaX,OAAOW,WAAW;YAC/BC,WAAWZ,OAAOY,SAAS;QAC7B,GACCC,SAAS;QAEZ,MAAMC,MAAMN,IAAI,CAAC,EAAE;QACnB,IAAI,CAACM,KAAK,MAAM,IAAIC,MAAM;QAC1B,IAAIf,OAAOgB,SAAS,EAAE;YACpB,MAAM3B,iBAA+Ba,IAAIN,YAAY;gBACnDqB,MAAM7B;gBACN8B,SAASlB,OAAOgB,SAAS;YAC3B;QACF;QACA,OAAOlB,gBAAgBgB;IACzB;AACF;AAEA,OAAO,eAAeK,qBAAqBnB,MAE1C;IACC,MAAMQ,OAAO,MAAMb,KAChBQ,MAAM,CAACN,gBACPO,GAAG,CAAC;QAACC,QAAQX,GAAG,CAAC,KAAK,CAAC;IAAA,GACvBY,KAAK,CACJhB,IACEC,GAAGM,eAAec,WAAW,EAAEX,OAAOW,WAAW,GACjDlB,OAAOI,eAAeQ,MAAM,GAC5Bb,GAAGK,eAAee,SAAS,EAAElB,GAAG,CAAC,KAAK,CAAC,IAG1CmB,SAAS;IAEZ,MAAMC,MAAMN,IAAI,CAAC,EAAE;IACnB,IAAI,CAACM,KAAK,OAAOM;IACjB,OAAOtB,gBAAgBgB;AACzB"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface ConsumeAuthRateLimitParams {
|
|
2
|
+
action: string;
|
|
3
|
+
scope: string;
|
|
4
|
+
identifierHmac: string;
|
|
5
|
+
windowStart: Date;
|
|
6
|
+
expiresAt: Date;
|
|
7
|
+
timeoutMs: number;
|
|
8
|
+
}
|
|
9
|
+
export interface ConsumeAuthRateLimitResult {
|
|
10
|
+
count: number;
|
|
11
|
+
expiresAt: Date;
|
|
12
|
+
}
|
|
13
|
+
export declare function consumeAuthRateLimit(params: ConsumeAuthRateLimitParams): Promise<ConsumeAuthRateLimitResult>;
|
|
14
|
+
export declare function pruneExpiredAuthRateLimits(params?: {
|
|
15
|
+
now?: Date | undefined;
|
|
16
|
+
minIntervalMs?: number | undefined;
|
|
17
|
+
}): Promise<number | undefined>;
|
|
18
|
+
//# sourceMappingURL=rate-limits.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limits.d.ts","sourceRoot":"","sources":["../../src/db/rate-limits.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,0BAA0B;IACzC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,IAAI,CAAC;IAClB,SAAS,EAAE,IAAI,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,0BAA0B;IACzC,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,IAAI,CAAC;CACjB;AAED,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,0BAA0B,GACjC,OAAO,CAAC,0BAA0B,CAAC,CAgCrC;AAID,wBAAsB,0BAA0B,CAC9C,MAAM,GAAE;IAAC,GAAG,CAAC,EAAE,IAAI,GAAG,SAAS,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAAM,GACxE,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAU7B"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { lt, sql } from 'drizzle-orm';
|
|
2
|
+
import { db } from './db.js';
|
|
3
|
+
import { authRateLimits } from './schema/rate-limits.js';
|
|
4
|
+
export async function consumeAuthRateLimit(params) {
|
|
5
|
+
return await db().transaction(async (tx)=>{
|
|
6
|
+
await tx.execute(sql`select set_config('statement_timeout', ${`${params.timeoutMs}ms`}, true)`);
|
|
7
|
+
const rows = await tx.insert(authRateLimits).values({
|
|
8
|
+
action: params.action,
|
|
9
|
+
scope: params.scope,
|
|
10
|
+
identifierHmac: params.identifierHmac,
|
|
11
|
+
windowStart: params.windowStart,
|
|
12
|
+
count: 1,
|
|
13
|
+
expiresAt: params.expiresAt
|
|
14
|
+
}).onConflictDoUpdate({
|
|
15
|
+
target: [
|
|
16
|
+
authRateLimits.action,
|
|
17
|
+
authRateLimits.scope,
|
|
18
|
+
authRateLimits.identifierHmac,
|
|
19
|
+
authRateLimits.windowStart
|
|
20
|
+
],
|
|
21
|
+
set: {
|
|
22
|
+
count: sql`${authRateLimits.count} + 1`,
|
|
23
|
+
updatedAt: sql`now()`
|
|
24
|
+
}
|
|
25
|
+
}).returning({
|
|
26
|
+
count: authRateLimits.count,
|
|
27
|
+
expiresAt: authRateLimits.expiresAt
|
|
28
|
+
});
|
|
29
|
+
const row = rows[0];
|
|
30
|
+
if (!row) throw new Error('Rate limit upsert returned no rows');
|
|
31
|
+
return row;
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
let nextPruneAt = 0;
|
|
35
|
+
export async function pruneExpiredAuthRateLimits(params = {}) {
|
|
36
|
+
const now = params.now ?? new Date();
|
|
37
|
+
const minIntervalMs = params.minIntervalMs ?? 60_000;
|
|
38
|
+
if (minIntervalMs > 0 && now.getTime() < nextPruneAt) return undefined;
|
|
39
|
+
nextPruneAt = now.getTime() + minIntervalMs;
|
|
40
|
+
const result = await db().delete(authRateLimits).where(lt(authRateLimits.expiresAt, now));
|
|
41
|
+
return result.rowCount ?? 0;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
//# sourceMappingURL=rate-limits.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/db/rate-limits.ts"],"sourcesContent":["import {lt, sql} from 'drizzle-orm';\nimport {db} from './db.js';\nimport {authRateLimits} from './schema/rate-limits.js';\n\nexport interface ConsumeAuthRateLimitParams {\n action: string;\n scope: string;\n identifierHmac: string;\n windowStart: Date;\n expiresAt: Date;\n timeoutMs: number;\n}\n\nexport interface ConsumeAuthRateLimitResult {\n count: number;\n expiresAt: Date;\n}\n\nexport async function consumeAuthRateLimit(\n params: ConsumeAuthRateLimitParams,\n): Promise<ConsumeAuthRateLimitResult> {\n return await db().transaction(async (tx) => {\n await tx.execute(sql`select set_config('statement_timeout', ${`${params.timeoutMs}ms`}, true)`);\n\n const rows = await tx\n .insert(authRateLimits)\n .values({\n action: params.action,\n scope: params.scope,\n identifierHmac: params.identifierHmac,\n windowStart: params.windowStart,\n count: 1,\n expiresAt: params.expiresAt,\n })\n .onConflictDoUpdate({\n target: [\n authRateLimits.action,\n authRateLimits.scope,\n authRateLimits.identifierHmac,\n authRateLimits.windowStart,\n ],\n set: {\n count: sql`${authRateLimits.count} + 1`,\n updatedAt: sql`now()`,\n },\n })\n .returning({count: authRateLimits.count, expiresAt: authRateLimits.expiresAt});\n\n const row = rows[0];\n if (!row) throw new Error('Rate limit upsert returned no rows');\n return row;\n });\n}\n\nlet nextPruneAt = 0;\n\nexport async function pruneExpiredAuthRateLimits(\n params: {now?: Date | undefined; minIntervalMs?: number | undefined} = {},\n): Promise<number | undefined> {\n const now = params.now ?? new Date();\n const minIntervalMs = params.minIntervalMs ?? 60_000;\n if (minIntervalMs > 0 && now.getTime() < nextPruneAt) return undefined;\n\n nextPruneAt = now.getTime() + minIntervalMs;\n\n const result = await db().delete(authRateLimits).where(lt(authRateLimits.expiresAt, now));\n\n return result.rowCount ?? 0;\n}\n"],"names":["lt","sql","db","authRateLimits","consumeAuthRateLimit","params","transaction","tx","execute","timeoutMs","rows","insert","values","action","scope","identifierHmac","windowStart","count","expiresAt","onConflictDoUpdate","target","set","updatedAt","returning","row","Error","nextPruneAt","pruneExpiredAuthRateLimits","now","Date","minIntervalMs","getTime","undefined","result","delete","where","rowCount"],"mappings":"AAAA,SAAQA,EAAE,EAAEC,GAAG,QAAO,cAAc;AACpC,SAAQC,EAAE,QAAO,UAAU;AAC3B,SAAQC,cAAc,QAAO,0BAA0B;AAgBvD,OAAO,eAAeC,qBACpBC,MAAkC;IAElC,OAAO,MAAMH,KAAKI,WAAW,CAAC,OAAOC;QACnC,MAAMA,GAAGC,OAAO,CAACP,GAAG,CAAC,uCAAuC,EAAE,GAAGI,OAAOI,SAAS,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC;QAE9F,MAAMC,OAAO,MAAMH,GAChBI,MAAM,CAACR,gBACPS,MAAM,CAAC;YACNC,QAAQR,OAAOQ,MAAM;YACrBC,OAAOT,OAAOS,KAAK;YACnBC,gBAAgBV,OAAOU,cAAc;YACrCC,aAAaX,OAAOW,WAAW;YAC/BC,OAAO;YACPC,WAAWb,OAAOa,SAAS;QAC7B,GACCC,kBAAkB,CAAC;YAClBC,QAAQ;gBACNjB,eAAeU,MAAM;gBACrBV,eAAeW,KAAK;gBACpBX,eAAeY,cAAc;gBAC7BZ,eAAea,WAAW;aAC3B;YACDK,KAAK;gBACHJ,OAAOhB,GAAG,CAAC,EAAEE,eAAec,KAAK,CAAC,IAAI,CAAC;gBACvCK,WAAWrB,GAAG,CAAC,KAAK,CAAC;YACvB;QACF,GACCsB,SAAS,CAAC;YAACN,OAAOd,eAAec,KAAK;YAAEC,WAAWf,eAAee,SAAS;QAAA;QAE9E,MAAMM,MAAMd,IAAI,CAAC,EAAE;QACnB,IAAI,CAACc,KAAK,MAAM,IAAIC,MAAM;QAC1B,OAAOD;IACT;AACF;AAEA,IAAIE,cAAc;AAElB,OAAO,eAAeC,2BACpBtB,SAAuE,CAAC,CAAC;IAEzE,MAAMuB,MAAMvB,OAAOuB,GAAG,IAAI,IAAIC;IAC9B,MAAMC,gBAAgBzB,OAAOyB,aAAa,IAAI;IAC9C,IAAIA,gBAAgB,KAAKF,IAAIG,OAAO,KAAKL,aAAa,OAAOM;IAE7DN,cAAcE,IAAIG,OAAO,KAAKD;IAE9B,MAAMG,SAAS,MAAM/B,KAAKgC,MAAM,CAAC/B,gBAAgBgC,KAAK,CAACnC,GAAGG,eAAee,SAAS,EAAEU;IAEpF,OAAOK,OAAOG,QAAQ,IAAI;AAC5B"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { RefreshToken } from '#core/entities/refresh-token.js';
|
|
2
|
+
export interface CreateRefreshTokenParams {
|
|
3
|
+
userId: string;
|
|
4
|
+
hashedToken: string;
|
|
5
|
+
expiresAt: Date;
|
|
6
|
+
}
|
|
7
|
+
export declare function createRefreshToken(params: CreateRefreshTokenParams): Promise<RefreshToken>;
|
|
8
|
+
/**
|
|
9
|
+
* Looks up the live session token by hash — one that can still authenticate as
|
|
10
|
+
* the current session. Returns `undefined` for revoked, expired, or already
|
|
11
|
+
* rotated tokens; rotated rows survive in the table only for the grace window
|
|
12
|
+
* and are not "active".
|
|
13
|
+
*/
|
|
14
|
+
export declare function findActiveRefreshTokenByHash(params: {
|
|
15
|
+
hashedToken: string;
|
|
16
|
+
}): Promise<RefreshToken | undefined>;
|
|
17
|
+
/**
|
|
18
|
+
* Looks up a non-revoked, non-expired token by hash, including ones already
|
|
19
|
+
* rotated. Rotation reads {@link RefreshToken.rotatedAt} to decide whether to
|
|
20
|
+
* rotate, honour the grace window, or reject reuse, so — unlike
|
|
21
|
+
* {@link findActiveRefreshTokenByHash} — it must still see rotated rows.
|
|
22
|
+
*/
|
|
23
|
+
export declare function findRefreshTokenByHash(params: {
|
|
24
|
+
hashedToken: string;
|
|
25
|
+
}): Promise<RefreshToken | undefined>;
|
|
26
|
+
/**
|
|
27
|
+
* Atomically claims rotation and inserts the successor token. The predecessor is
|
|
28
|
+
* marked rather than deleted so later reuse of the retired token can be detected.
|
|
29
|
+
* If the successor insert fails, the predecessor rotation rolls back.
|
|
30
|
+
*/
|
|
31
|
+
export declare function rotateRefreshToken(params: {
|
|
32
|
+
id: string;
|
|
33
|
+
currentHashedToken: string;
|
|
34
|
+
nextHashedToken: string;
|
|
35
|
+
expiresAt: Date;
|
|
36
|
+
}): Promise<RefreshToken | undefined>;
|
|
37
|
+
export declare function revokeRefreshTokenByHash(params: {
|
|
38
|
+
hashedToken: string;
|
|
39
|
+
}): Promise<void>;
|
|
40
|
+
export declare function revokeRefreshTokensForUser(params: {
|
|
41
|
+
userId: string;
|
|
42
|
+
exceptRefreshTokenId?: string | undefined;
|
|
43
|
+
}): Promise<void>;
|
|
44
|
+
//# sourceMappingURL=refresh-tokens.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"refresh-tokens.d.ts","sourceRoot":"","sources":["../../src/db/refresh-tokens.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,iCAAiC,CAAC;AAIlE,MAAM,WAAW,wBAAwB;IACvC,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,IAAI,CAAC;CACjB;AAED,wBAAsB,kBAAkB,CAAC,MAAM,EAAE,wBAAwB,GAAG,OAAO,CAAC,YAAY,CAAC,CAahG;AAED;;;;;GAKG;AACH,wBAAsB,4BAA4B,CAAC,MAAM,EAAE;IACzD,WAAW,EAAE,MAAM,CAAC;CACrB,GAAG,OAAO,CAAC,YAAY,GAAG,SAAS,CAAC,CAiBpC;AAED;;;;;GAKG;AACH,wBAAsB,sBAAsB,CAAC,MAAM,EAAE;IACnD,WAAW,EAAE,MAAM,CAAC;CACrB,GAAG,OAAO,CAAC,YAAY,GAAG,SAAS,CAAC,CAgBpC;AAED;;;;GAIG;AACH,wBAAsB,kBAAkB,CAAC,MAAM,EAAE;IAC/C,EAAE,EAAE,MAAM,CAAC;IACX,kBAAkB,EAAE,MAAM,CAAC;IAC3B,eAAe,EAAE,MAAM,CAAC;IACxB,SAAS,EAAE,IAAI,CAAC;CACjB,GAAG,OAAO,CAAC,YAAY,GAAG,SAAS,CAAC,CAoCpC;AAED,wBAAsB,wBAAwB,CAAC,MAAM,EAAE;IAAC,WAAW,EAAE,MAAM,CAAA;CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAK3F;AAED,wBAAsB,0BAA0B,CAAC,MAAM,EAAE;IACvD,MAAM,EAAE,MAAM,CAAC;IACf,oBAAoB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC3C,GAAG,OAAO,CAAC,IAAI,CAAC,CAWhB"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { and, eq, gt, isNull, ne, sql } from 'drizzle-orm';
|
|
2
|
+
import { db } from './db.js';
|
|
3
|
+
import { refreshTokens, toRefreshToken } from './schema/refresh-tokens.js';
|
|
4
|
+
export async function createRefreshToken(params) {
|
|
5
|
+
const rows = await db().insert(refreshTokens).values({
|
|
6
|
+
userId: params.userId,
|
|
7
|
+
hashedToken: params.hashedToken,
|
|
8
|
+
expiresAt: params.expiresAt
|
|
9
|
+
}).returning();
|
|
10
|
+
const row = rows[0];
|
|
11
|
+
if (!row) throw new Error('Insert returned no rows');
|
|
12
|
+
return toRefreshToken(row);
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Looks up the live session token by hash — one that can still authenticate as
|
|
16
|
+
* the current session. Returns `undefined` for revoked, expired, or already
|
|
17
|
+
* rotated tokens; rotated rows survive in the table only for the grace window
|
|
18
|
+
* and are not "active".
|
|
19
|
+
*/ export async function findActiveRefreshTokenByHash(params) {
|
|
20
|
+
const rows = await db().select().from(refreshTokens).where(and(eq(refreshTokens.hashedToken, params.hashedToken), isNull(refreshTokens.revokedAt), isNull(refreshTokens.rotatedAt), gt(refreshTokens.expiresAt, sql`now()`))).limit(1);
|
|
21
|
+
const row = rows[0];
|
|
22
|
+
if (!row) return undefined;
|
|
23
|
+
return toRefreshToken(row);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Looks up a non-revoked, non-expired token by hash, including ones already
|
|
27
|
+
* rotated. Rotation reads {@link RefreshToken.rotatedAt} to decide whether to
|
|
28
|
+
* rotate, honour the grace window, or reject reuse, so — unlike
|
|
29
|
+
* {@link findActiveRefreshTokenByHash} — it must still see rotated rows.
|
|
30
|
+
*/ export async function findRefreshTokenByHash(params) {
|
|
31
|
+
const rows = await db().select().from(refreshTokens).where(and(eq(refreshTokens.hashedToken, params.hashedToken), isNull(refreshTokens.revokedAt), gt(refreshTokens.expiresAt, sql`now()`))).limit(1);
|
|
32
|
+
const row = rows[0];
|
|
33
|
+
if (!row) return undefined;
|
|
34
|
+
return toRefreshToken(row);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Atomically claims rotation and inserts the successor token. The predecessor is
|
|
38
|
+
* marked rather than deleted so later reuse of the retired token can be detected.
|
|
39
|
+
* If the successor insert fails, the predecessor rotation rolls back.
|
|
40
|
+
*/ export async function rotateRefreshToken(params) {
|
|
41
|
+
return await db().transaction(async (tx)=>{
|
|
42
|
+
const rotatedRows = await tx.update(refreshTokens).set({
|
|
43
|
+
rotatedAt: sql`now()`,
|
|
44
|
+
lastUsedAt: sql`now()`,
|
|
45
|
+
updatedAt: sql`now()`
|
|
46
|
+
}).where(and(eq(refreshTokens.id, params.id), eq(refreshTokens.hashedToken, params.currentHashedToken), isNull(refreshTokens.revokedAt), isNull(refreshTokens.rotatedAt), gt(refreshTokens.expiresAt, sql`now()`))).returning();
|
|
47
|
+
const rotated = rotatedRows[0];
|
|
48
|
+
if (!rotated) return undefined;
|
|
49
|
+
const successorRows = await tx.insert(refreshTokens).values({
|
|
50
|
+
userId: rotated.userId,
|
|
51
|
+
hashedToken: params.nextHashedToken,
|
|
52
|
+
expiresAt: params.expiresAt
|
|
53
|
+
}).returning();
|
|
54
|
+
if (!successorRows[0]) throw new Error('Insert returned no rows');
|
|
55
|
+
return toRefreshToken(rotated);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
export async function revokeRefreshTokenByHash(params) {
|
|
59
|
+
await db().update(refreshTokens).set({
|
|
60
|
+
revokedAt: sql`now()`,
|
|
61
|
+
updatedAt: sql`now()`
|
|
62
|
+
}).where(and(eq(refreshTokens.hashedToken, params.hashedToken), isNull(refreshTokens.revokedAt)));
|
|
63
|
+
}
|
|
64
|
+
export async function revokeRefreshTokensForUser(params) {
|
|
65
|
+
await db().update(refreshTokens).set({
|
|
66
|
+
revokedAt: sql`now()`,
|
|
67
|
+
updatedAt: sql`now()`
|
|
68
|
+
}).where(and(eq(refreshTokens.userId, params.userId), isNull(refreshTokens.revokedAt), params.exceptRefreshTokenId ? ne(refreshTokens.id, params.exceptRefreshTokenId) : undefined));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
//# sourceMappingURL=refresh-tokens.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/db/refresh-tokens.ts"],"sourcesContent":["import {and, eq, gt, isNull, ne, sql} from 'drizzle-orm';\nimport type {RefreshToken} from '#core/entities/refresh-token.js';\nimport {db} from './db.js';\nimport {refreshTokens, toRefreshToken} from './schema/refresh-tokens.js';\n\nexport interface CreateRefreshTokenParams {\n userId: string;\n hashedToken: string;\n expiresAt: Date;\n}\n\nexport async function createRefreshToken(params: CreateRefreshTokenParams): Promise<RefreshToken> {\n const rows = await db()\n .insert(refreshTokens)\n .values({\n userId: params.userId,\n hashedToken: params.hashedToken,\n expiresAt: params.expiresAt,\n })\n .returning();\n\n const row = rows[0];\n if (!row) throw new Error('Insert returned no rows');\n return toRefreshToken(row);\n}\n\n/**\n * Looks up the live session token by hash — one that can still authenticate as\n * the current session. Returns `undefined` for revoked, expired, or already\n * rotated tokens; rotated rows survive in the table only for the grace window\n * and are not \"active\".\n */\nexport async function findActiveRefreshTokenByHash(params: {\n hashedToken: string;\n}): Promise<RefreshToken | undefined> {\n const rows = await db()\n .select()\n .from(refreshTokens)\n .where(\n and(\n eq(refreshTokens.hashedToken, params.hashedToken),\n isNull(refreshTokens.revokedAt),\n isNull(refreshTokens.rotatedAt),\n gt(refreshTokens.expiresAt, sql`now()`),\n ),\n )\n .limit(1);\n\n const row = rows[0];\n if (!row) return undefined;\n return toRefreshToken(row);\n}\n\n/**\n * Looks up a non-revoked, non-expired token by hash, including ones already\n * rotated. Rotation reads {@link RefreshToken.rotatedAt} to decide whether to\n * rotate, honour the grace window, or reject reuse, so — unlike\n * {@link findActiveRefreshTokenByHash} — it must still see rotated rows.\n */\nexport async function findRefreshTokenByHash(params: {\n hashedToken: string;\n}): Promise<RefreshToken | undefined> {\n const rows = await db()\n .select()\n .from(refreshTokens)\n .where(\n and(\n eq(refreshTokens.hashedToken, params.hashedToken),\n isNull(refreshTokens.revokedAt),\n gt(refreshTokens.expiresAt, sql`now()`),\n ),\n )\n .limit(1);\n\n const row = rows[0];\n if (!row) return undefined;\n return toRefreshToken(row);\n}\n\n/**\n * Atomically claims rotation and inserts the successor token. The predecessor is\n * marked rather than deleted so later reuse of the retired token can be detected.\n * If the successor insert fails, the predecessor rotation rolls back.\n */\nexport async function rotateRefreshToken(params: {\n id: string;\n currentHashedToken: string;\n nextHashedToken: string;\n expiresAt: Date;\n}): Promise<RefreshToken | undefined> {\n return await db().transaction(async (tx) => {\n const rotatedRows = await tx\n .update(refreshTokens)\n .set({\n rotatedAt: sql`now()`,\n lastUsedAt: sql`now()`,\n updatedAt: sql`now()`,\n })\n .where(\n and(\n eq(refreshTokens.id, params.id),\n eq(refreshTokens.hashedToken, params.currentHashedToken),\n isNull(refreshTokens.revokedAt),\n isNull(refreshTokens.rotatedAt),\n gt(refreshTokens.expiresAt, sql`now()`),\n ),\n )\n .returning();\n\n const rotated = rotatedRows[0];\n if (!rotated) return undefined;\n\n const successorRows = await tx\n .insert(refreshTokens)\n .values({\n userId: rotated.userId,\n hashedToken: params.nextHashedToken,\n expiresAt: params.expiresAt,\n })\n .returning();\n\n if (!successorRows[0]) throw new Error('Insert returned no rows');\n\n return toRefreshToken(rotated);\n });\n}\n\nexport async function revokeRefreshTokenByHash(params: {hashedToken: string}): Promise<void> {\n await db()\n .update(refreshTokens)\n .set({revokedAt: sql`now()`, updatedAt: sql`now()`})\n .where(and(eq(refreshTokens.hashedToken, params.hashedToken), isNull(refreshTokens.revokedAt)));\n}\n\nexport async function revokeRefreshTokensForUser(params: {\n userId: string;\n exceptRefreshTokenId?: string | undefined;\n}): Promise<void> {\n await db()\n .update(refreshTokens)\n .set({revokedAt: sql`now()`, updatedAt: sql`now()`})\n .where(\n and(\n eq(refreshTokens.userId, params.userId),\n isNull(refreshTokens.revokedAt),\n params.exceptRefreshTokenId ? ne(refreshTokens.id, params.exceptRefreshTokenId) : undefined,\n ),\n );\n}\n"],"names":["and","eq","gt","isNull","ne","sql","db","refreshTokens","toRefreshToken","createRefreshToken","params","rows","insert","values","userId","hashedToken","expiresAt","returning","row","Error","findActiveRefreshTokenByHash","select","from","where","revokedAt","rotatedAt","limit","undefined","findRefreshTokenByHash","rotateRefreshToken","transaction","tx","rotatedRows","update","set","lastUsedAt","updatedAt","id","currentHashedToken","rotated","successorRows","nextHashedToken","revokeRefreshTokenByHash","revokeRefreshTokensForUser","exceptRefreshTokenId"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"common.d.ts","sourceRoot":"","sources":["../../../src/db/schema/common.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,OAAO,oDAA2C,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/db/schema/common.ts"],"sourcesContent":["import {pgTableCreator} from 'drizzle-orm/pg-core';\n\nexport const pgTable = pgTableCreator((name) => `auth_${name}`);\n"],"names":["pgTableCreator","pgTable","name"],"mappings":"AAAA,SAAQA,cAAc,QAAO,sBAAsB;AAEnD,OAAO,MAAMC,UAAUD,eAAe,CAACE,OAAS,CAAC,KAAK,EAAEA,MAAM,EAAE"}
|