@shipfox/api-auth 10.2.0 → 12.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 +1 -1
- package/CHANGELOG.md +40 -0
- package/README.md +48 -32
- package/dist/core/auth.d.ts.map +1 -1
- package/dist/core/auth.js +16 -8
- package/dist/core/auth.js.map +1 -1
- package/dist/core/job-lease-token.d.ts +1 -1
- package/dist/core/job-lease-token.d.ts.map +1 -1
- package/dist/core/job-lease-token.js +1 -1
- package/dist/core/job-lease-token.js.map +1 -1
- package/dist/core/jwt.d.ts +10 -0
- package/dist/core/jwt.d.ts.map +1 -1
- package/dist/core/jwt.js +3 -2
- package/dist/core/jwt.js.map +1 -1
- package/dist/core/runner-session-token.d.ts +1 -1
- package/dist/core/runner-session-token.d.ts.map +1 -1
- package/dist/core/runner-session-token.js +1 -1
- package/dist/core/runner-session-token.js.map +1 -1
- package/dist/db/rate-limits.d.ts +6 -15
- package/dist/db/rate-limits.d.ts.map +1 -1
- package/dist/db/rate-limits.js +17 -18
- package/dist/db/rate-limits.js.map +1 -1
- package/dist/db/refresh-tokens.d.ts +3 -7
- package/dist/db/refresh-tokens.d.ts.map +1 -1
- package/dist/db/refresh-tokens.js +3 -9
- package/dist/db/refresh-tokens.js.map +1 -1
- package/dist/presentation/auth/jwt-auth.d.ts +1 -4
- package/dist/presentation/auth/jwt-auth.d.ts.map +1 -1
- package/dist/presentation/auth/jwt-auth.js +5 -29
- package/dist/presentation/auth/jwt-auth.js.map +1 -1
- package/dist/presentation/auth/lease-token-auth.d.ts +1 -1
- package/dist/presentation/auth/lease-token-auth.js +1 -1
- package/dist/presentation/auth/lease-token-auth.js.map +1 -1
- package/dist/presentation/routes/rate-limit.d.ts.map +1 -1
- package/dist/presentation/routes/rate-limit.js +32 -53
- package/dist/presentation/routes/rate-limit.js.map +1 -1
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/package.json +13 -13
- package/src/core/auth.test.ts +29 -10
- package/src/core/auth.ts +27 -12
- package/src/core/job-lease-token.ts +1 -1
- package/src/core/jwt.test.ts +20 -4
- package/src/core/jwt.ts +2 -1
- package/src/core/runner-session-token.ts +1 -1
- package/src/db/rate-limits.ts +28 -42
- package/src/db/refresh-tokens.test.ts +0 -19
- package/src/db/refresh-tokens.ts +3 -26
- package/src/presentation/auth/jwt-auth.test.ts +33 -8
- package/src/presentation/auth/jwt-auth.ts +3 -26
- package/src/presentation/auth/lease-token-auth.ts +1 -1
- package/src/presentation/routes/administration.test.ts +9 -1
- package/src/presentation/routes/password/change-password.test.ts +6 -0
- package/src/presentation/routes/password/password-reset-confirm.test.ts +14 -0
- package/src/presentation/routes/rate-limit.ts +31 -64
- package/src/presentation/routes/registration/signup.test.ts +2 -2
- package/src/presentation/routes/session/logout.test.ts +6 -0
- package/src/presentation/routes/session/refresh.test.ts +11 -0
- package/tsconfig.build.tsbuildinfo +1 -1
package/dist/core/jwt.d.ts
CHANGED
|
@@ -4,6 +4,11 @@ export declare const tokenMembershipSchema: z.ZodObject<{
|
|
|
4
4
|
role: z.ZodEnum<{
|
|
5
5
|
admin: "admin";
|
|
6
6
|
}>;
|
|
7
|
+
workspaceStatus: z.ZodDefault<z.ZodEnum<{
|
|
8
|
+
active: "active";
|
|
9
|
+
suspended: "suspended";
|
|
10
|
+
deleted: "deleted";
|
|
11
|
+
}>>;
|
|
7
12
|
}, z.core.$strip>;
|
|
8
13
|
export type TokenMembership = z.infer<typeof tokenMembershipSchema>;
|
|
9
14
|
export declare const userTokenClaimsSchema: z.ZodObject<{
|
|
@@ -16,6 +21,11 @@ export declare const userTokenClaimsSchema: z.ZodObject<{
|
|
|
16
21
|
role: z.ZodEnum<{
|
|
17
22
|
admin: "admin";
|
|
18
23
|
}>;
|
|
24
|
+
workspaceStatus: z.ZodDefault<z.ZodEnum<{
|
|
25
|
+
active: "active";
|
|
26
|
+
suspended: "suspended";
|
|
27
|
+
deleted: "deleted";
|
|
28
|
+
}>>;
|
|
19
29
|
}, z.core.$strip>>;
|
|
20
30
|
iat: z.ZodNumber;
|
|
21
31
|
exp: z.ZodNumber;
|
package/dist/core/jwt.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"jwt.d.ts","sourceRoot":"","sources":["../../src/core/jwt.ts"],"names":[],"mappings":"AAEA,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAGtB,eAAO,MAAM,qBAAqB
|
|
1
|
+
{"version":3,"file":"jwt.d.ts","sourceRoot":"","sources":["../../src/core/jwt.ts"],"names":[],"mappings":"AAEA,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAGtB,eAAO,MAAM,qBAAqB;;;;;;;;;;iBAIhC,CAAC;AAEH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAEpE,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;iBAQhC,CAAC;AAEH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAEpE,MAAM,WAAW,mBAAmB;IAClC,gBAAgB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACtC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IACjC,WAAW,EAAE,eAAe,EAAE,CAAC;IAC/B,MAAM,EAAE,MAAM,GAAG,UAAU,CAAC;IAC5B,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,GAAG,UAAU,CAAC;CAC7B;AAED,wBAAsB,aAAa,CAAC,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAAC,MAAM,CAAC,CAchF;AAED,wBAAsB,eAAe,CAAC,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAAC,eAAe,CAAC,CAa7F"}
|
package/dist/core/jwt.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { workspaceRoleSchema } from '@shipfox/api-workspaces-dto';
|
|
1
|
+
import { workspaceRoleSchema, workspaceStatusSchema } from '@shipfox/api-workspaces-dto';
|
|
2
2
|
import { signHs256, verifyHs256 } from '@shipfox/node-jwt';
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
import { recordTokenIssued, recordTokenVerified } from '#metrics/index.js';
|
|
5
5
|
export const tokenMembershipSchema = z.object({
|
|
6
6
|
workspaceId: z.string().uuid(),
|
|
7
|
-
role: workspaceRoleSchema
|
|
7
|
+
role: workspaceRoleSchema,
|
|
8
|
+
workspaceStatus: workspaceStatusSchema.default('active')
|
|
8
9
|
});
|
|
9
10
|
export const userTokenClaimsSchema = z.object({
|
|
10
11
|
sub: z.string().uuid(),
|
package/dist/core/jwt.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/core/jwt.ts"],"sourcesContent":["import {workspaceRoleSchema} from '@shipfox/api-workspaces-dto';\nimport {signHs256, verifyHs256} from '@shipfox/node-jwt';\nimport {z} from 'zod';\nimport {recordTokenIssued, recordTokenVerified} from '#metrics/index.js';\n\nexport const tokenMembershipSchema = z.object({\n workspaceId: z.string().uuid(),\n role: workspaceRoleSchema,\n});\n\nexport type TokenMembership = z.infer<typeof tokenMembershipSchema>;\n\nexport const userTokenClaimsSchema = z.object({\n sub: z.string().uuid(),\n refreshSessionId: z.string().uuid().optional(),\n email: z.string().email(),\n name: z.string().nullable().optional(),\n memberships: z.array(tokenMembershipSchema),\n iat: z.number().int(),\n exp: z.number().int(),\n});\n\nexport type UserTokenClaims = z.infer<typeof userTokenClaimsSchema>;\n\nexport interface SignUserTokenParams {\n refreshSessionId?: string | undefined;\n userId: string;\n email: string;\n name?: string | null | undefined;\n memberships: TokenMembership[];\n secret: string | Uint8Array;\n expiresIn: string;\n}\n\nexport interface VerifyUserTokenParams {\n token: string;\n secret: string | Uint8Array;\n}\n\nexport async function signUserToken(params: SignUserTokenParams): Promise<string> {\n const token = await signHs256({\n payload: {\n email: params.email,\n name: params.name ?? null,\n memberships: params.memberships,\n refreshSessionId: params.refreshSessionId,\n },\n secret: params.secret,\n expiresIn: params.expiresIn,\n subject: params.userId,\n });\n recordTokenIssued('session');\n return token;\n}\n\nexport async function verifyUserToken(params: VerifyUserTokenParams): Promise<UserTokenClaims> {\n try {\n const claims = await verifyHs256({\n token: params.token,\n secret: params.secret,\n schema: userTokenClaimsSchema,\n });\n recordTokenVerified('session', 'ok');\n return claims;\n } catch (error) {\n recordTokenVerified('session', 'rejected');\n throw error;\n }\n}\n"],"names":["workspaceRoleSchema","signHs256","verifyHs256","z","recordTokenIssued","recordTokenVerified","tokenMembershipSchema","object","workspaceId","string","uuid","role","userTokenClaimsSchema","sub","refreshSessionId","optional","email","name","nullable","memberships","array","iat","number","int","exp","signUserToken","params","token","payload","secret","expiresIn","subject","userId","verifyUserToken","claims","schema","error"],"mappings":"AAAA,SAAQA,mBAAmB,QAAO,8BAA8B;
|
|
1
|
+
{"version":3,"sources":["../../src/core/jwt.ts"],"sourcesContent":["import {workspaceRoleSchema, workspaceStatusSchema} from '@shipfox/api-workspaces-dto';\nimport {signHs256, verifyHs256} from '@shipfox/node-jwt';\nimport {z} from 'zod';\nimport {recordTokenIssued, recordTokenVerified} from '#metrics/index.js';\n\nexport const tokenMembershipSchema = z.object({\n workspaceId: z.string().uuid(),\n role: workspaceRoleSchema,\n workspaceStatus: workspaceStatusSchema.default('active'),\n});\n\nexport type TokenMembership = z.infer<typeof tokenMembershipSchema>;\n\nexport const userTokenClaimsSchema = z.object({\n sub: z.string().uuid(),\n refreshSessionId: z.string().uuid().optional(),\n email: z.string().email(),\n name: z.string().nullable().optional(),\n memberships: z.array(tokenMembershipSchema),\n iat: z.number().int(),\n exp: z.number().int(),\n});\n\nexport type UserTokenClaims = z.infer<typeof userTokenClaimsSchema>;\n\nexport interface SignUserTokenParams {\n refreshSessionId?: string | undefined;\n userId: string;\n email: string;\n name?: string | null | undefined;\n memberships: TokenMembership[];\n secret: string | Uint8Array;\n expiresIn: string;\n}\n\nexport interface VerifyUserTokenParams {\n token: string;\n secret: string | Uint8Array;\n}\n\nexport async function signUserToken(params: SignUserTokenParams): Promise<string> {\n const token = await signHs256({\n payload: {\n email: params.email,\n name: params.name ?? null,\n memberships: params.memberships,\n refreshSessionId: params.refreshSessionId,\n },\n secret: params.secret,\n expiresIn: params.expiresIn,\n subject: params.userId,\n });\n recordTokenIssued('session');\n return token;\n}\n\nexport async function verifyUserToken(params: VerifyUserTokenParams): Promise<UserTokenClaims> {\n try {\n const claims = await verifyHs256({\n token: params.token,\n secret: params.secret,\n schema: userTokenClaimsSchema,\n });\n recordTokenVerified('session', 'ok');\n return claims;\n } catch (error) {\n recordTokenVerified('session', 'rejected');\n throw error;\n }\n}\n"],"names":["workspaceRoleSchema","workspaceStatusSchema","signHs256","verifyHs256","z","recordTokenIssued","recordTokenVerified","tokenMembershipSchema","object","workspaceId","string","uuid","role","workspaceStatus","default","userTokenClaimsSchema","sub","refreshSessionId","optional","email","name","nullable","memberships","array","iat","number","int","exp","signUserToken","params","token","payload","secret","expiresIn","subject","userId","verifyUserToken","claims","schema","error"],"mappings":"AAAA,SAAQA,mBAAmB,EAAEC,qBAAqB,QAAO,8BAA8B;AACvF,SAAQC,SAAS,EAAEC,WAAW,QAAO,oBAAoB;AACzD,SAAQC,CAAC,QAAO,MAAM;AACtB,SAAQC,iBAAiB,EAAEC,mBAAmB,QAAO,oBAAoB;AAEzE,OAAO,MAAMC,wBAAwBH,EAAEI,MAAM,CAAC;IAC5CC,aAAaL,EAAEM,MAAM,GAAGC,IAAI;IAC5BC,MAAMZ;IACNa,iBAAiBZ,sBAAsBa,OAAO,CAAC;AACjD,GAAG;AAIH,OAAO,MAAMC,wBAAwBX,EAAEI,MAAM,CAAC;IAC5CQ,KAAKZ,EAAEM,MAAM,GAAGC,IAAI;IACpBM,kBAAkBb,EAAEM,MAAM,GAAGC,IAAI,GAAGO,QAAQ;IAC5CC,OAAOf,EAAEM,MAAM,GAAGS,KAAK;IACvBC,MAAMhB,EAAEM,MAAM,GAAGW,QAAQ,GAAGH,QAAQ;IACpCI,aAAalB,EAAEmB,KAAK,CAAChB;IACrBiB,KAAKpB,EAAEqB,MAAM,GAAGC,GAAG;IACnBC,KAAKvB,EAAEqB,MAAM,GAAGC,GAAG;AACrB,GAAG;AAmBH,OAAO,eAAeE,cAAcC,MAA2B;IAC7D,MAAMC,QAAQ,MAAM5B,UAAU;QAC5B6B,SAAS;YACPZ,OAAOU,OAAOV,KAAK;YACnBC,MAAMS,OAAOT,IAAI,IAAI;YACrBE,aAAaO,OAAOP,WAAW;YAC/BL,kBAAkBY,OAAOZ,gBAAgB;QAC3C;QACAe,QAAQH,OAAOG,MAAM;QACrBC,WAAWJ,OAAOI,SAAS;QAC3BC,SAASL,OAAOM,MAAM;IACxB;IACA9B,kBAAkB;IAClB,OAAOyB;AACT;AAEA,OAAO,eAAeM,gBAAgBP,MAA6B;IACjE,IAAI;QACF,MAAMQ,SAAS,MAAMlC,YAAY;YAC/B2B,OAAOD,OAAOC,KAAK;YACnBE,QAAQH,OAAOG,MAAM;YACrBM,QAAQvB;QACV;QACAT,oBAAoB,WAAW;QAC/B,OAAO+B;IACT,EAAE,OAAOE,OAAO;QACdjC,oBAAoB,WAAW;QAC/B,MAAMiC;IACR;AACF"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type RunnerSessionTokenClaims } from '@shipfox/api-auth-dto';
|
|
2
2
|
export type IssueRunnerSessionTokenParams = Omit<RunnerSessionTokenClaims, 'aud' | 'iat' | 'exp'>;
|
|
3
3
|
export declare function issueRunnerSessionToken(claims: IssueRunnerSessionTokenParams): Promise<string>;
|
|
4
|
-
/** Returns the claims on success, or `null` for any invalid input
|
|
4
|
+
/** Returns the claims on success, or `null` for any invalid input. Never throws. */
|
|
5
5
|
export declare function verifyRunnerSessionToken(token: string): Promise<RunnerSessionTokenClaims | null>;
|
|
6
6
|
//# sourceMappingURL=runner-session-token.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runner-session-token.d.ts","sourceRoot":"","sources":["../../src/core/runner-session-token.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,wBAAwB,EAE9B,MAAM,uBAAuB,CAAC;AAO/B,MAAM,MAAM,6BAA6B,GAAG,IAAI,CAAC,wBAAwB,EAAE,KAAK,GAAG,KAAK,GAAG,KAAK,CAAC,CAAC;AAElG,wBAAsB,uBAAuB,CAC3C,MAAM,EAAE,6BAA6B,GACpC,OAAO,CAAC,MAAM,CAAC,CAejB;AAED,
|
|
1
|
+
{"version":3,"file":"runner-session-token.d.ts","sourceRoot":"","sources":["../../src/core/runner-session-token.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,wBAAwB,EAE9B,MAAM,uBAAuB,CAAC;AAO/B,MAAM,MAAM,6BAA6B,GAAG,IAAI,CAAC,wBAAwB,EAAE,KAAK,GAAG,KAAK,GAAG,KAAK,CAAC,CAAC;AAElG,wBAAsB,uBAAuB,CAC3C,MAAM,EAAE,6BAA6B,GACpC,OAAO,CAAC,MAAM,CAAC,CAejB;AAED,oFAAoF;AACpF,wBAAsB,wBAAwB,CAC5C,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,wBAAwB,GAAG,IAAI,CAAC,CAc1C"}
|
|
@@ -19,7 +19,7 @@ export async function issueRunnerSessionToken(claims) {
|
|
|
19
19
|
recordTokenIssued('runner_session');
|
|
20
20
|
return token;
|
|
21
21
|
}
|
|
22
|
-
/** Returns the claims on success, or `null` for any invalid input
|
|
22
|
+
/** Returns the claims on success, or `null` for any invalid input. Never throws. */ export async function verifyRunnerSessionToken(token) {
|
|
23
23
|
try {
|
|
24
24
|
const claims = await verifyHs256({
|
|
25
25
|
token,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/core/runner-session-token.ts"],"sourcesContent":["import {\n RUNNER_SESSION_TOKEN_AUDIENCE,\n type RunnerSessionTokenClaims,\n runnerSessionTokenClaimsSchema,\n} from '@shipfox/api-auth-dto';\nimport {runnerSessionTokenKey} from '@shipfox/node-auth-root-key';\nimport {signHs256, verifyHs256} from '@shipfox/node-jwt';\nimport {config} from '#config.js';\nimport {recordTokenIssued, recordTokenVerified} from '#metrics/index.js';\n\n// `aud`, `iat` and `exp` are set by the codec (jose); callers supply the business ids only.\nexport type IssueRunnerSessionTokenParams = Omit<RunnerSessionTokenClaims, 'aud' | 'iat' | 'exp'>;\n\nexport async function issueRunnerSessionToken(\n claims: IssueRunnerSessionTokenParams,\n): Promise<string> {\n const token = await signHs256({\n payload: {\n runnerSessionId: claims.runnerSessionId,\n workspaceId: claims.workspaceId,\n scope: claims.scope,\n labels: claims.labels,\n maxClaims: claims.maxClaims,\n },\n secret: runnerSessionTokenKey(),\n expiresIn: config.AUTH_RUNNER_SESSION_TOKEN_EXPIRES_IN,\n audience: RUNNER_SESSION_TOKEN_AUDIENCE,\n });\n recordTokenIssued('runner_session');\n return token;\n}\n\n/** Returns the claims on success, or `null` for any invalid input
|
|
1
|
+
{"version":3,"sources":["../../src/core/runner-session-token.ts"],"sourcesContent":["import {\n RUNNER_SESSION_TOKEN_AUDIENCE,\n type RunnerSessionTokenClaims,\n runnerSessionTokenClaimsSchema,\n} from '@shipfox/api-auth-dto';\nimport {runnerSessionTokenKey} from '@shipfox/node-auth-root-key';\nimport {signHs256, verifyHs256} from '@shipfox/node-jwt';\nimport {config} from '#config.js';\nimport {recordTokenIssued, recordTokenVerified} from '#metrics/index.js';\n\n// `aud`, `iat` and `exp` are set by the codec (jose); callers supply the business ids only.\nexport type IssueRunnerSessionTokenParams = Omit<RunnerSessionTokenClaims, 'aud' | 'iat' | 'exp'>;\n\nexport async function issueRunnerSessionToken(\n claims: IssueRunnerSessionTokenParams,\n): Promise<string> {\n const token = await signHs256({\n payload: {\n runnerSessionId: claims.runnerSessionId,\n workspaceId: claims.workspaceId,\n scope: claims.scope,\n labels: claims.labels,\n maxClaims: claims.maxClaims,\n },\n secret: runnerSessionTokenKey(),\n expiresIn: config.AUTH_RUNNER_SESSION_TOKEN_EXPIRES_IN,\n audience: RUNNER_SESSION_TOKEN_AUDIENCE,\n });\n recordTokenIssued('runner_session');\n return token;\n}\n\n/** Returns the claims on success, or `null` for any invalid input. Never throws. */\nexport async function verifyRunnerSessionToken(\n token: string,\n): Promise<RunnerSessionTokenClaims | null> {\n try {\n const claims = await verifyHs256({\n token,\n secret: runnerSessionTokenKey(),\n schema: runnerSessionTokenClaimsSchema,\n audience: RUNNER_SESSION_TOKEN_AUDIENCE,\n });\n recordTokenVerified('runner_session', 'ok');\n return claims;\n } catch {\n recordTokenVerified('runner_session', 'rejected');\n return null;\n }\n}\n"],"names":["RUNNER_SESSION_TOKEN_AUDIENCE","runnerSessionTokenClaimsSchema","runnerSessionTokenKey","signHs256","verifyHs256","config","recordTokenIssued","recordTokenVerified","issueRunnerSessionToken","claims","token","payload","runnerSessionId","workspaceId","scope","labels","maxClaims","secret","expiresIn","AUTH_RUNNER_SESSION_TOKEN_EXPIRES_IN","audience","verifyRunnerSessionToken","schema"],"mappings":"AAAA,SACEA,6BAA6B,EAE7BC,8BAA8B,QACzB,wBAAwB;AAC/B,SAAQC,qBAAqB,QAAO,8BAA8B;AAClE,SAAQC,SAAS,EAAEC,WAAW,QAAO,oBAAoB;AACzD,SAAQC,MAAM,QAAO,aAAa;AAClC,SAAQC,iBAAiB,EAAEC,mBAAmB,QAAO,oBAAoB;AAKzE,OAAO,eAAeC,wBACpBC,MAAqC;IAErC,MAAMC,QAAQ,MAAMP,UAAU;QAC5BQ,SAAS;YACPC,iBAAiBH,OAAOG,eAAe;YACvCC,aAAaJ,OAAOI,WAAW;YAC/BC,OAAOL,OAAOK,KAAK;YACnBC,QAAQN,OAAOM,MAAM;YACrBC,WAAWP,OAAOO,SAAS;QAC7B;QACAC,QAAQf;QACRgB,WAAWb,OAAOc,oCAAoC;QACtDC,UAAUpB;IACZ;IACAM,kBAAkB;IAClB,OAAOI;AACT;AAEA,kFAAkF,GAClF,OAAO,eAAeW,yBACpBX,KAAa;IAEb,IAAI;QACF,MAAMD,SAAS,MAAML,YAAY;YAC/BM;YACAO,QAAQf;YACRoB,QAAQrB;YACRmB,UAAUpB;QACZ;QACAO,oBAAoB,kBAAkB;QACtC,OAAOE;IACT,EAAE,OAAM;QACNF,oBAAoB,kBAAkB;QACtC,OAAO;IACT;AACF"}
|
package/dist/db/rate-limits.d.ts
CHANGED
|
@@ -1,18 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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?: {
|
|
1
|
+
import { type ConsumeRateLimitParams, type ConsumeRateLimitResult } from '@shipfox/node-rate-limit';
|
|
2
|
+
export type ConsumeAuthRateLimitParams = ConsumeRateLimitParams<string, string>;
|
|
3
|
+
export type ConsumeAuthRateLimitResult = ConsumeRateLimitResult;
|
|
4
|
+
export declare const consumeAuthRateLimit: (params: ConsumeRateLimitParams<string, string>) => Promise<ConsumeRateLimitResult>;
|
|
5
|
+
export declare const pruneExpiredAuthRateLimits: (params?: {
|
|
15
6
|
now?: Date | undefined;
|
|
16
7
|
minIntervalMs?: number | undefined;
|
|
17
|
-
})
|
|
8
|
+
}) => Promise<number | undefined>;
|
|
18
9
|
//# sourceMappingURL=rate-limits.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rate-limits.d.ts","sourceRoot":"","sources":["../../src/db/rate-limits.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"rate-limits.d.ts","sourceRoot":"","sources":["../../src/db/rate-limits.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,sBAAsB,EAC3B,KAAK,sBAAsB,EAE5B,MAAM,0BAA0B,CAAC;AA+ClC,MAAM,MAAM,0BAA0B,GAAG,sBAAsB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAChF,MAAM,MAAM,0BAA0B,GAAG,sBAAsB,CAAC;AAChE,eAAO,MAAM,oBAAoB,qFAAsB,CAAC;AACxD,eAAO,MAAM,0BAA0B;;;iCAAoB,CAAC"}
|
package/dist/db/rate-limits.js
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
|
+
import { createRateLimitPersistence } from '@shipfox/node-rate-limit';
|
|
1
2
|
import { lt, sql } from 'drizzle-orm';
|
|
2
3
|
import { db } from './db.js';
|
|
3
4
|
import { authRateLimits } from './schema/rate-limits.js';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
const persistence = createRateLimitPersistence({
|
|
6
|
+
transaction: (callback)=>db().transaction(callback),
|
|
7
|
+
setStatementTimeout: async (transaction, timeoutMs)=>{
|
|
8
|
+
await transaction.execute(sql`select set_config('statement_timeout', ${`${timeoutMs}ms`}, true)`);
|
|
9
|
+
},
|
|
10
|
+
consume: async (transaction, params)=>{
|
|
11
|
+
const rows = await transaction.insert(authRateLimits).values({
|
|
8
12
|
action: params.action,
|
|
9
13
|
scope: params.scope,
|
|
10
14
|
identifierHmac: params.identifierHmac,
|
|
@@ -26,19 +30,14 @@ export async function consumeAuthRateLimit(params) {
|
|
|
26
30
|
count: authRateLimits.count,
|
|
27
31
|
expiresAt: authRateLimits.expiresAt
|
|
28
32
|
});
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
}
|
|
33
|
+
return rows[0];
|
|
34
|
+
},
|
|
35
|
+
prune: async (now)=>{
|
|
36
|
+
const result = await db().delete(authRateLimits).where(lt(authRateLimits.expiresAt, now));
|
|
37
|
+
return result.rowCount ?? 0;
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
export const consumeAuthRateLimit = persistence.consume;
|
|
41
|
+
export const pruneExpiredAuthRateLimits = persistence.prune;
|
|
43
42
|
|
|
44
43
|
//# sourceMappingURL=rate-limits.js.map
|
|
@@ -1 +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\
|
|
1
|
+
{"version":3,"sources":["../../src/db/rate-limits.ts"],"sourcesContent":["import {\n type ConsumeRateLimitParams,\n type ConsumeRateLimitResult,\n createRateLimitPersistence,\n} from '@shipfox/node-rate-limit';\nimport {lt, sql} from 'drizzle-orm';\nimport {db} from './db.js';\nimport {authRateLimits} from './schema/rate-limits.js';\n\ntype AuthRateLimitTransaction = Parameters<Parameters<ReturnType<typeof db>['transaction']>[0]>[0];\n\nconst persistence = createRateLimitPersistence<AuthRateLimitTransaction>({\n transaction: (callback) => db().transaction(callback),\n setStatementTimeout: async (transaction, timeoutMs) => {\n await transaction.execute(\n sql`select set_config('statement_timeout', ${`${timeoutMs}ms`}, true)`,\n );\n },\n consume: async (transaction, params) => {\n const rows = await transaction\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 return rows[0];\n },\n prune: async (now) => {\n const result = await db().delete(authRateLimits).where(lt(authRateLimits.expiresAt, now));\n return result.rowCount ?? 0;\n },\n});\n\nexport type ConsumeAuthRateLimitParams = ConsumeRateLimitParams<string, string>;\nexport type ConsumeAuthRateLimitResult = ConsumeRateLimitResult;\nexport const consumeAuthRateLimit = persistence.consume;\nexport const pruneExpiredAuthRateLimits = persistence.prune;\n"],"names":["createRateLimitPersistence","lt","sql","db","authRateLimits","persistence","transaction","callback","setStatementTimeout","timeoutMs","execute","consume","params","rows","insert","values","action","scope","identifierHmac","windowStart","count","expiresAt","onConflictDoUpdate","target","set","updatedAt","returning","prune","now","result","delete","where","rowCount","consumeAuthRateLimit","pruneExpiredAuthRateLimits"],"mappings":"AAAA,SAGEA,0BAA0B,QACrB,2BAA2B;AAClC,SAAQC,EAAE,EAAEC,GAAG,QAAO,cAAc;AACpC,SAAQC,EAAE,QAAO,UAAU;AAC3B,SAAQC,cAAc,QAAO,0BAA0B;AAIvD,MAAMC,cAAcL,2BAAqD;IACvEM,aAAa,CAACC,WAAaJ,KAAKG,WAAW,CAACC;IAC5CC,qBAAqB,OAAOF,aAAaG;QACvC,MAAMH,YAAYI,OAAO,CACvBR,GAAG,CAAC,uCAAuC,EAAE,GAAGO,UAAU,EAAE,CAAC,CAAC,OAAO,CAAC;IAE1E;IACAE,SAAS,OAAOL,aAAaM;QAC3B,MAAMC,OAAO,MAAMP,YAChBQ,MAAM,CAACV,gBACPW,MAAM,CAAC;YACNC,QAAQJ,OAAOI,MAAM;YACrBC,OAAOL,OAAOK,KAAK;YACnBC,gBAAgBN,OAAOM,cAAc;YACrCC,aAAaP,OAAOO,WAAW;YAC/BC,OAAO;YACPC,WAAWT,OAAOS,SAAS;QAC7B,GACCC,kBAAkB,CAAC;YAClBC,QAAQ;gBACNnB,eAAeY,MAAM;gBACrBZ,eAAea,KAAK;gBACpBb,eAAec,cAAc;gBAC7Bd,eAAee,WAAW;aAC3B;YACDK,KAAK;gBACHJ,OAAOlB,GAAG,CAAC,EAAEE,eAAegB,KAAK,CAAC,IAAI,CAAC;gBACvCK,WAAWvB,GAAG,CAAC,KAAK,CAAC;YACvB;QACF,GACCwB,SAAS,CAAC;YAACN,OAAOhB,eAAegB,KAAK;YAAEC,WAAWjB,eAAeiB,SAAS;QAAA;QAE9E,OAAOR,IAAI,CAAC,EAAE;IAChB;IACAc,OAAO,OAAOC;QACZ,MAAMC,SAAS,MAAM1B,KAAK2B,MAAM,CAAC1B,gBAAgB2B,KAAK,CAAC9B,GAAGG,eAAeiB,SAAS,EAAEO;QACpF,OAAOC,OAAOG,QAAQ,IAAI;IAC5B;AACF;AAIA,OAAO,MAAMC,uBAAuB5B,YAAYM,OAAO,CAAC;AACxD,OAAO,MAAMuB,6BAA6B7B,YAAYsB,KAAK,CAAC"}
|
|
@@ -11,7 +11,7 @@ export interface CreateRefreshTokenParams {
|
|
|
11
11
|
export declare function createRefreshToken(params: CreateRefreshTokenParams): Promise<RefreshToken>;
|
|
12
12
|
export declare function createRefreshTokenForActiveUser(params: CreateRefreshTokenParams): Promise<RefreshToken | undefined>;
|
|
13
13
|
/**
|
|
14
|
-
* Looks up the live session token by hash
|
|
14
|
+
* Looks up the live session token by hash. It can still authenticate as
|
|
15
15
|
* the current session. Returns `undefined` for revoked, expired, or already
|
|
16
16
|
* rotated tokens; rotated rows survive in the table only for the grace window
|
|
17
17
|
* and are not "active".
|
|
@@ -19,15 +19,11 @@ export declare function createRefreshTokenForActiveUser(params: CreateRefreshTok
|
|
|
19
19
|
export declare function findActiveRefreshTokenByHash(params: {
|
|
20
20
|
hashedToken: string;
|
|
21
21
|
}): Promise<RefreshToken | undefined>;
|
|
22
|
-
export declare function findActiveRefreshSession(params: {
|
|
23
|
-
sessionId: string;
|
|
24
|
-
userId: string;
|
|
25
|
-
}): Promise<RefreshToken | undefined>;
|
|
26
22
|
/**
|
|
27
23
|
* Looks up a non-revoked, non-expired token by hash, including ones already
|
|
28
24
|
* rotated. Rotation reads {@link RefreshToken.rotatedAt} to decide whether to
|
|
29
|
-
* rotate, honour the grace window, or reject reuse, so
|
|
30
|
-
* {@link findActiveRefreshTokenByHash}
|
|
25
|
+
* rotate, honour the grace window, or reject reuse, so, unlike
|
|
26
|
+
* {@link findActiveRefreshTokenByHash}, it must still see rotated rows.
|
|
31
27
|
*/
|
|
32
28
|
export declare function findRefreshTokenByHash(params: {
|
|
33
29
|
hashedToken: string;
|
|
@@ -1 +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;AAClE,OAAO,EAAC,EAAE,EAAC,MAAM,SAAS,CAAC;AAI3B,KAAK,EAAE,GAAG,UAAU,CAAC,UAAU,CAAC,UAAU,CAAC,OAAO,EAAE,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAE7E,wBAAsB,wBAAwB,CAAC,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAEpF;AAED,MAAM,WAAW,wBAAwB;IACvC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,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,CAchG;AAED,wBAAsB,+BAA+B,CACnD,MAAM,EAAE,wBAAwB,GAC/B,OAAO,CAAC,YAAY,GAAG,SAAS,CAAC,CAuBnC;AAED;;;;;GAKG;AACH,wBAAsB,4BAA4B,CAAC,MAAM,EAAE;IACzD,WAAW,EAAE,MAAM,CAAC;CACrB,GAAG,OAAO,CAAC,YAAY,GAAG,SAAS,CAAC,CAiBpC;AAED
|
|
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;AAClE,OAAO,EAAC,EAAE,EAAC,MAAM,SAAS,CAAC;AAI3B,KAAK,EAAE,GAAG,UAAU,CAAC,UAAU,CAAC,UAAU,CAAC,OAAO,EAAE,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAE7E,wBAAsB,wBAAwB,CAAC,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAEpF;AAED,MAAM,WAAW,wBAAwB;IACvC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,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,CAchG;AAED,wBAAsB,+BAA+B,CACnD,MAAM,EAAE,wBAAwB,GAC/B,OAAO,CAAC,YAAY,GAAG,SAAS,CAAC,CAuBnC;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,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,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,CAwDpC;AAED,wBAAsB,wBAAwB,CAAC,MAAM,EAAE;IAAC,WAAW,EAAE,MAAM,CAAA;CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAK3F;AAED,wBAAsB,oBAAoB,CAAC,MAAM,EAAE;IACjD,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;CAChB,GAAG,OAAO,CAAC,IAAI,CAAC,CAWhB;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"}
|
|
@@ -35,7 +35,7 @@ export async function createRefreshTokenForActiveUser(params) {
|
|
|
35
35
|
});
|
|
36
36
|
}
|
|
37
37
|
/**
|
|
38
|
-
* Looks up the live session token by hash
|
|
38
|
+
* Looks up the live session token by hash. It can still authenticate as
|
|
39
39
|
* the current session. Returns `undefined` for revoked, expired, or already
|
|
40
40
|
* rotated tokens; rotated rows survive in the table only for the grace window
|
|
41
41
|
* and are not "active".
|
|
@@ -45,17 +45,11 @@ export async function createRefreshTokenForActiveUser(params) {
|
|
|
45
45
|
if (!row) return undefined;
|
|
46
46
|
return toRefreshToken(row);
|
|
47
47
|
}
|
|
48
|
-
export async function findActiveRefreshSession(params) {
|
|
49
|
-
const rows = await db().select().from(refreshTokens).where(and(eq(refreshTokens.sessionId, params.sessionId), eq(refreshTokens.userId, params.userId), isNull(refreshTokens.revokedAt), isNull(refreshTokens.rotatedAt), gt(refreshTokens.expiresAt, sql`now()`))).limit(1);
|
|
50
|
-
const row = rows[0];
|
|
51
|
-
if (!row) return undefined;
|
|
52
|
-
return toRefreshToken(row);
|
|
53
|
-
}
|
|
54
48
|
/**
|
|
55
49
|
* Looks up a non-revoked, non-expired token by hash, including ones already
|
|
56
50
|
* rotated. Rotation reads {@link RefreshToken.rotatedAt} to decide whether to
|
|
57
|
-
* rotate, honour the grace window, or reject reuse, so
|
|
58
|
-
* {@link findActiveRefreshTokenByHash}
|
|
51
|
+
* rotate, honour the grace window, or reject reuse, so, unlike
|
|
52
|
+
* {@link findActiveRefreshTokenByHash}, it must still see rotated rows.
|
|
59
53
|
*/ export async function findRefreshTokenByHash(params) {
|
|
60
54
|
const rows = await db().select().from(refreshTokens).where(and(eq(refreshTokens.hashedToken, params.hashedToken), isNull(refreshTokens.revokedAt), gt(refreshTokens.expiresAt, sql`now()`))).limit(1);
|
|
61
55
|
const row = rows[0];
|
|
@@ -1 +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';\nimport {users} from './schema/users.js';\n\ntype Tx = Parameters<Parameters<ReturnType<typeof db>['transaction']>[0]>[0];\n\nexport async function lockUserSessionMutations(tx: Tx, userId: string): Promise<void> {\n await tx.execute(sql`select pg_advisory_xact_lock(hashtext(${`auth_user_sessions:${userId}`}))`);\n}\n\nexport interface CreateRefreshTokenParams {\n sessionId?: string | undefined;\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 sessionId: params.sessionId,\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\nexport async function createRefreshTokenForActiveUser(\n params: CreateRefreshTokenParams,\n): Promise<RefreshToken | undefined> {\n return await db().transaction(async (tx) => {\n await lockUserSessionMutations(tx, params.userId);\n const userRows = await tx\n .select({status: users.status})\n .from(users)\n .where(eq(users.id, params.userId))\n .limit(1);\n if (userRows[0]?.status !== 'active') return undefined;\n\n const rows = await tx\n .insert(refreshTokens)\n .values({\n sessionId: params.sessionId,\n userId: params.userId,\n hashedToken: params.hashedToken,\n expiresAt: params.expiresAt,\n })\n .returning();\n const row = rows[0];\n if (!row) throw new Error('Insert returned no rows');\n return toRefreshToken(row);\n });\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\nexport async function findActiveRefreshSession(params: {\n sessionId: string;\n userId: string;\n}): Promise<RefreshToken | undefined> {\n const rows = await db()\n .select()\n .from(refreshTokens)\n .where(\n and(\n eq(refreshTokens.sessionId, params.sessionId),\n eq(refreshTokens.userId, params.userId),\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 userId?: string | undefined;\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 userId =\n params.userId ??\n (\n await tx\n .select({userId: refreshTokens.userId})\n .from(refreshTokens)\n .where(eq(refreshTokens.id, params.id))\n .limit(1)\n )[0]?.userId;\n if (!userId) return undefined;\n\n await lockUserSessionMutations(tx, userId);\n const userRows = await tx\n .select({status: users.status})\n .from(users)\n .where(eq(users.id, userId))\n .limit(1);\n if (userRows[0]?.status !== 'active') return undefined;\n\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 sessionId: rotated.sessionId,\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 revokeRefreshSession(params: {\n sessionId: string;\n userId: string;\n}): Promise<void> {\n await db()\n .update(refreshTokens)\n .set({revokedAt: sql`now()`, updatedAt: sql`now()`})\n .where(\n and(\n eq(refreshTokens.sessionId, params.sessionId),\n eq(refreshTokens.userId, params.userId),\n isNull(refreshTokens.revokedAt),\n ),\n );\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","users","lockUserSessionMutations","tx","userId","execute","createRefreshToken","params","rows","insert","values","sessionId","hashedToken","expiresAt","returning","row","Error","createRefreshTokenForActiveUser","transaction","userRows","select","status","from","where","id","limit","undefined","findActiveRefreshTokenByHash","revokedAt","rotatedAt","findActiveRefreshSession","findRefreshTokenByHash","rotateRefreshToken","rotatedRows","update","set","lastUsedAt","updatedAt","currentHashedToken","rotated","successorRows","nextHashedToken","revokeRefreshTokenByHash","revokeRefreshSession","revokeRefreshTokensForUser","exceptRefreshTokenId"],"mappings":"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"}
|
|
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';\nimport {users} from './schema/users.js';\n\ntype Tx = Parameters<Parameters<ReturnType<typeof db>['transaction']>[0]>[0];\n\nexport async function lockUserSessionMutations(tx: Tx, userId: string): Promise<void> {\n await tx.execute(sql`select pg_advisory_xact_lock(hashtext(${`auth_user_sessions:${userId}`}))`);\n}\n\nexport interface CreateRefreshTokenParams {\n sessionId?: string | undefined;\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 sessionId: params.sessionId,\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\nexport async function createRefreshTokenForActiveUser(\n params: CreateRefreshTokenParams,\n): Promise<RefreshToken | undefined> {\n return await db().transaction(async (tx) => {\n await lockUserSessionMutations(tx, params.userId);\n const userRows = await tx\n .select({status: users.status})\n .from(users)\n .where(eq(users.id, params.userId))\n .limit(1);\n if (userRows[0]?.status !== 'active') return undefined;\n\n const rows = await tx\n .insert(refreshTokens)\n .values({\n sessionId: params.sessionId,\n userId: params.userId,\n hashedToken: params.hashedToken,\n expiresAt: params.expiresAt,\n })\n .returning();\n const row = rows[0];\n if (!row) throw new Error('Insert returned no rows');\n return toRefreshToken(row);\n });\n}\n\n/**\n * Looks up the live session token by hash. It 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 userId?: string | undefined;\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 userId =\n params.userId ??\n (\n await tx\n .select({userId: refreshTokens.userId})\n .from(refreshTokens)\n .where(eq(refreshTokens.id, params.id))\n .limit(1)\n )[0]?.userId;\n if (!userId) return undefined;\n\n await lockUserSessionMutations(tx, userId);\n const userRows = await tx\n .select({status: users.status})\n .from(users)\n .where(eq(users.id, userId))\n .limit(1);\n if (userRows[0]?.status !== 'active') return undefined;\n\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 sessionId: rotated.sessionId,\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 revokeRefreshSession(params: {\n sessionId: string;\n userId: string;\n}): Promise<void> {\n await db()\n .update(refreshTokens)\n .set({revokedAt: sql`now()`, updatedAt: sql`now()`})\n .where(\n and(\n eq(refreshTokens.sessionId, params.sessionId),\n eq(refreshTokens.userId, params.userId),\n isNull(refreshTokens.revokedAt),\n ),\n );\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","users","lockUserSessionMutations","tx","userId","execute","createRefreshToken","params","rows","insert","values","sessionId","hashedToken","expiresAt","returning","row","Error","createRefreshTokenForActiveUser","transaction","userRows","select","status","from","where","id","limit","undefined","findActiveRefreshTokenByHash","revokedAt","rotatedAt","findRefreshTokenByHash","rotateRefreshToken","rotatedRows","update","set","lastUsedAt","updatedAt","currentHashedToken","rotated","successorRows","nextHashedToken","revokeRefreshTokenByHash","revokeRefreshSession","revokeRefreshTokensForUser","exceptRefreshTokenId"],"mappings":"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"}
|
|
@@ -14,10 +14,7 @@ export interface CreateJwtAuthMethodOptions {
|
|
|
14
14
|
secret: string;
|
|
15
15
|
}
|
|
16
16
|
export declare function getClientContext(request: FastifyRequest): ClientContext | null;
|
|
17
|
-
/**
|
|
18
|
-
* Resolves the active refresh session backing an authenticated user request.
|
|
19
|
-
* The session check intentionally happens only for consumers that need browser-session binding.
|
|
20
|
-
*/
|
|
17
|
+
/** Returns the refresh-session metadata carried by a verified access token. */
|
|
21
18
|
export declare function getAuthenticatedSessionContext(request: FastifyRequest): Promise<AuthenticatedSessionContext>;
|
|
22
19
|
export declare function createJwtAuthMethod(): AuthMethod;
|
|
23
20
|
//# sourceMappingURL=jwt-auth.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"jwt-auth.d.ts","sourceRoot":"","sources":["../../../src/presentation/auth/jwt-auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,WAAW,EACjB,MAAM,2BAA2B,CAAC;AAEnC,OAAO,EAAC,KAAK,UAAU,EAAc,MAAM,uBAAuB,CAAC;AACnE,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,SAAS,CAAC;AAC5C,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,iCAAiC,CAAC;AAClE,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"jwt-auth.d.ts","sourceRoot":"","sources":["../../../src/presentation/auth/jwt-auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,WAAW,EACjB,MAAM,2BAA2B,CAAC;AAEnC,OAAO,EAAC,KAAK,UAAU,EAAc,MAAM,uBAAuB,CAAC;AACnE,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,SAAS,CAAC;AAC5C,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,iCAAiC,CAAC;AAClE,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,wBAAwB,CAAC;AAOjD,MAAM,MAAM,aAAa,GAAG,WAAW,CAAC;AAExC,MAAM,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC;AAChC,MAAM,MAAM,gBAAgB,GAAG,YAAY,CAAC,WAAW,CAAC,CAAC;AAEzD,MAAM,MAAM,2BAA2B,GAAG;IACxC,MAAM,EAAE,MAAM,CAAC;IACf,gBAAgB,EAAE,gBAAgB,CAAC;CACpC,CAAC;AAEF,MAAM,WAAW,0BAA0B;IACzC,MAAM,EAAE,MAAM,CAAC;CAChB;AASD,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,cAAc,GAAG,aAAa,GAAG,IAAI,CAE9E;AAoBD,+EAA+E;AAC/E,wBAAsB,8BAA8B,CAClD,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,2BAA2B,CAAC,CAOtC;AAED,wBAAgB,mBAAmB,IAAI,UAAU,CAyBhD"}
|
|
@@ -2,8 +2,6 @@ import { AUTH_USER, buildUserContext, getUserContext, setUserContext } from '@sh
|
|
|
2
2
|
import { userAccessTokenKey } from '@shipfox/node-auth-root-key';
|
|
3
3
|
import { ClientError } from '@shipfox/node-fastify';
|
|
4
4
|
import { verifyUserToken } from '#core/jwt.js';
|
|
5
|
-
import { findActiveRefreshSession } from '#db/refresh-tokens.js';
|
|
6
|
-
import { findUserById } from '#db/users.js';
|
|
7
5
|
import { createBearerTokenAuthMethod } from './bearer-token-auth.js';
|
|
8
6
|
const AUTHENTICATED_SESSION_CONTEXT_KEY = Symbol.for('@shipfox/api-auth/session');
|
|
9
7
|
let InvalidJwtTokenError = class InvalidJwtTokenError extends Error {
|
|
@@ -24,26 +22,16 @@ function setAuthenticatedSessionContext(request, claims) {
|
|
|
24
22
|
function getAuthenticatedSessionTokenContext(request) {
|
|
25
23
|
return request[AUTHENTICATED_SESSION_CONTEXT_KEY] ?? null;
|
|
26
24
|
}
|
|
27
|
-
/**
|
|
28
|
-
* Resolves the active refresh session backing an authenticated user request.
|
|
29
|
-
* The session check intentionally happens only for consumers that need browser-session binding.
|
|
30
|
-
*/ export async function getAuthenticatedSessionContext(request) {
|
|
25
|
+
/** Returns the refresh-session metadata carried by a verified access token. */ export async function getAuthenticatedSessionContext(request) {
|
|
31
26
|
const client = getClientContext(request);
|
|
32
27
|
const token = getAuthenticatedSessionTokenContext(request);
|
|
33
28
|
if (!client || !token || token.userId !== client.userId || !token.refreshSessionId) throw new ClientError('Invalid or expired token', 'unauthorized', {
|
|
34
29
|
status: 401
|
|
35
30
|
});
|
|
36
|
-
|
|
37
|
-
sessionId: token.refreshSessionId,
|
|
38
|
-
userId: client.userId
|
|
39
|
-
});
|
|
40
|
-
if (!session) throw new ClientError('Invalid or expired token', 'unauthorized', {
|
|
41
|
-
status: 401
|
|
42
|
-
});
|
|
43
|
-
return {
|
|
31
|
+
return await Promise.resolve({
|
|
44
32
|
userId: client.userId,
|
|
45
|
-
refreshSessionId:
|
|
46
|
-
};
|
|
33
|
+
refreshSessionId: token.refreshSessionId
|
|
34
|
+
});
|
|
47
35
|
}
|
|
48
36
|
export function createJwtAuthMethod() {
|
|
49
37
|
return createBearerTokenAuthMethod({
|
|
@@ -58,19 +46,7 @@ export function createJwtAuthMethod() {
|
|
|
58
46
|
} catch {
|
|
59
47
|
throw new InvalidJwtTokenError();
|
|
60
48
|
}
|
|
61
|
-
|
|
62
|
-
id: claims.sub
|
|
63
|
-
});
|
|
64
|
-
// Legacy access tokens without a session claim remain verifiable for the
|
|
65
|
-
// migration window; known suspended or deleted users are still rejected.
|
|
66
|
-
if (user && user.status !== 'active') return null;
|
|
67
|
-
if (!claims.refreshSessionId) return claims;
|
|
68
|
-
if (!user) return null;
|
|
69
|
-
const session = await findActiveRefreshSession({
|
|
70
|
-
sessionId: claims.refreshSessionId,
|
|
71
|
-
userId: claims.sub
|
|
72
|
-
});
|
|
73
|
-
return session ? claims : null;
|
|
49
|
+
return claims;
|
|
74
50
|
},
|
|
75
51
|
isInvalidTokenError: (error)=>error instanceof InvalidJwtTokenError,
|
|
76
52
|
invalidTokenError: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/presentation/auth/jwt-auth.ts"],"sourcesContent":["import {\n AUTH_USER,\n buildUserContext,\n getUserContext,\n setUserContext,\n type UserContext,\n} from '@shipfox/api-auth-context';\nimport {userAccessTokenKey} from '@shipfox/node-auth-root-key';\nimport {type AuthMethod, ClientError} from '@shipfox/node-fastify';\nimport type {FastifyRequest} from 'fastify';\nimport type {RefreshToken} from '#core/entities/refresh-token.js';\nimport type {User} from '#core/entities/user.js';\nimport type {UserTokenClaims} from '#core/jwt.js';\nimport {verifyUserToken} from '#core/jwt.js';\nimport {
|
|
1
|
+
{"version":3,"sources":["../../../src/presentation/auth/jwt-auth.ts"],"sourcesContent":["import {\n AUTH_USER,\n buildUserContext,\n getUserContext,\n setUserContext,\n type UserContext,\n} from '@shipfox/api-auth-context';\nimport {userAccessTokenKey} from '@shipfox/node-auth-root-key';\nimport {type AuthMethod, ClientError} from '@shipfox/node-fastify';\nimport type {FastifyRequest} from 'fastify';\nimport type {RefreshToken} from '#core/entities/refresh-token.js';\nimport type {User} from '#core/entities/user.js';\nimport type {UserTokenClaims} from '#core/jwt.js';\nimport {verifyUserToken} from '#core/jwt.js';\nimport {createBearerTokenAuthMethod} from './bearer-token-auth.js';\n\nconst AUTHENTICATED_SESSION_CONTEXT_KEY = Symbol.for('@shipfox/api-auth/session');\n\nexport type ClientContext = UserContext;\n\nexport type UserId = User['id'];\nexport type RefreshSessionId = RefreshToken['sessionId'];\n\nexport type AuthenticatedSessionContext = {\n userId: UserId;\n refreshSessionId: RefreshSessionId;\n};\n\nexport interface CreateJwtAuthMethodOptions {\n secret: string;\n}\n\nclass InvalidJwtTokenError extends Error {\n constructor() {\n super('Invalid JWT');\n this.name = 'InvalidJwtTokenError';\n }\n}\n\nexport function getClientContext(request: FastifyRequest): ClientContext | null {\n return getUserContext(request);\n}\n\nfunction setAuthenticatedSessionContext(request: FastifyRequest, claims: UserTokenClaims): void {\n (request as unknown as Record<symbol, unknown>)[AUTHENTICATED_SESSION_CONTEXT_KEY] = {\n userId: claims.sub,\n refreshSessionId: claims.refreshSessionId,\n };\n}\n\nfunction getAuthenticatedSessionTokenContext(request: FastifyRequest): {\n userId: UserId;\n refreshSessionId: RefreshSessionId | undefined;\n} | null {\n return (\n ((request as unknown as Record<symbol, unknown>)[AUTHENTICATED_SESSION_CONTEXT_KEY] as\n | {userId: UserId; refreshSessionId: RefreshSessionId | undefined}\n | undefined) ?? null\n );\n}\n\n/** Returns the refresh-session metadata carried by a verified access token. */\nexport async function getAuthenticatedSessionContext(\n request: FastifyRequest,\n): Promise<AuthenticatedSessionContext> {\n const client = getClientContext(request);\n const token = getAuthenticatedSessionTokenContext(request);\n if (!client || !token || token.userId !== client.userId || !token.refreshSessionId)\n throw new ClientError('Invalid or expired token', 'unauthorized', {status: 401});\n\n return await Promise.resolve({userId: client.userId, refreshSessionId: token.refreshSessionId});\n}\n\nexport function createJwtAuthMethod(): AuthMethod {\n return createBearerTokenAuthMethod({\n name: AUTH_USER,\n verifyToken: async (token) => {\n let claims: UserTokenClaims;\n try {\n claims = await verifyUserToken({token, secret: userAccessTokenKey()});\n } catch {\n throw new InvalidJwtTokenError();\n }\n return claims;\n },\n isInvalidTokenError: (error) => error instanceof InvalidJwtTokenError,\n invalidTokenError: {message: 'Invalid or expired token', code: 'unauthorized'},\n setContext: (request, claims) => {\n const clientContext: ClientContext = buildUserContext({\n userId: claims.sub,\n email: claims.email,\n name: claims.name ?? null,\n memberships: claims.memberships,\n });\n setUserContext(request, clientContext);\n setAuthenticatedSessionContext(request, claims);\n },\n });\n}\n"],"names":["AUTH_USER","buildUserContext","getUserContext","setUserContext","userAccessTokenKey","ClientError","verifyUserToken","createBearerTokenAuthMethod","AUTHENTICATED_SESSION_CONTEXT_KEY","Symbol","for","InvalidJwtTokenError","Error","name","getClientContext","request","setAuthenticatedSessionContext","claims","userId","sub","refreshSessionId","getAuthenticatedSessionTokenContext","getAuthenticatedSessionContext","client","token","status","Promise","resolve","createJwtAuthMethod","verifyToken","secret","isInvalidTokenError","error","invalidTokenError","message","code","setContext","clientContext","email","memberships"],"mappings":"AAAA,SACEA,SAAS,EACTC,gBAAgB,EAChBC,cAAc,EACdC,cAAc,QAET,4BAA4B;AACnC,SAAQC,kBAAkB,QAAO,8BAA8B;AAC/D,SAAyBC,WAAW,QAAO,wBAAwB;AAKnE,SAAQC,eAAe,QAAO,eAAe;AAC7C,SAAQC,2BAA2B,QAAO,yBAAyB;AAEnE,MAAMC,oCAAoCC,OAAOC,GAAG,CAAC;AAgBrD,IAAA,AAAMC,uBAAN,MAAMA,6BAA6BC;IACjC,aAAc;QACZ,KAAK,CAAC;QACN,IAAI,CAACC,IAAI,GAAG;IACd;AACF;AAEA,OAAO,SAASC,iBAAiBC,OAAuB;IACtD,OAAOb,eAAea;AACxB;AAEA,SAASC,+BAA+BD,OAAuB,EAAEE,MAAuB;IACrFF,OAA8C,CAACP,kCAAkC,GAAG;QACnFU,QAAQD,OAAOE,GAAG;QAClBC,kBAAkBH,OAAOG,gBAAgB;IAC3C;AACF;AAEA,SAASC,oCAAoCN,OAAuB;IAIlE,OACE,AAAC,AAACA,OAA8C,CAACP,kCAAkC,IAEjE;AAEtB;AAEA,6EAA6E,GAC7E,OAAO,eAAec,+BACpBP,OAAuB;IAEvB,MAAMQ,SAAST,iBAAiBC;IAChC,MAAMS,QAAQH,oCAAoCN;IAClD,IAAI,CAACQ,UAAU,CAACC,SAASA,MAAMN,MAAM,KAAKK,OAAOL,MAAM,IAAI,CAACM,MAAMJ,gBAAgB,EAChF,MAAM,IAAIf,YAAY,4BAA4B,gBAAgB;QAACoB,QAAQ;IAAG;IAEhF,OAAO,MAAMC,QAAQC,OAAO,CAAC;QAACT,QAAQK,OAAOL,MAAM;QAAEE,kBAAkBI,MAAMJ,gBAAgB;IAAA;AAC/F;AAEA,OAAO,SAASQ;IACd,OAAOrB,4BAA4B;QACjCM,MAAMb;QACN6B,aAAa,OAAOL;YAClB,IAAIP;YACJ,IAAI;gBACFA,SAAS,MAAMX,gBAAgB;oBAACkB;oBAAOM,QAAQ1B;gBAAoB;YACrE,EAAE,OAAM;gBACN,MAAM,IAAIO;YACZ;YACA,OAAOM;QACT;QACAc,qBAAqB,CAACC,QAAUA,iBAAiBrB;QACjDsB,mBAAmB;YAACC,SAAS;YAA4BC,MAAM;QAAc;QAC7EC,YAAY,CAACrB,SAASE;YACpB,MAAMoB,gBAA+BpC,iBAAiB;gBACpDiB,QAAQD,OAAOE,GAAG;gBAClBmB,OAAOrB,OAAOqB,KAAK;gBACnBzB,MAAMI,OAAOJ,IAAI,IAAI;gBACrB0B,aAAatB,OAAOsB,WAAW;YACjC;YACApC,eAAeY,SAASsB;YACxBrB,+BAA+BD,SAASE;QAC1C;IACF;AACF"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { AuthMethod } from '@shipfox/node-fastify';
|
|
2
2
|
/**
|
|
3
|
-
* Trust boundary: the signed token is the sole authority
|
|
3
|
+
* Trust boundary: the signed token is the sole authority. `claims.jobId` scopes
|
|
4
4
|
* every runner request to exactly one job. `workflowRunId`/`workflowRunAttemptId`/`workspaceId`
|
|
5
5
|
* are carried for consumers but are NOT verified against the database here.
|
|
6
6
|
*
|
|
@@ -2,7 +2,7 @@ import { AUTH_LEASED_JOB, setLeasedJobContext } from '@shipfox/api-auth-context'
|
|
|
2
2
|
import { verifyJobLeaseToken } from '#core/job-lease-token.js';
|
|
3
3
|
import { createBearerTokenAuthMethod } from './bearer-token-auth.js';
|
|
4
4
|
/**
|
|
5
|
-
* Trust boundary: the signed token is the sole authority
|
|
5
|
+
* Trust boundary: the signed token is the sole authority. `claims.jobId` scopes
|
|
6
6
|
* every runner request to exactly one job. `workflowRunId`/`workflowRunAttemptId`/`workspaceId`
|
|
7
7
|
* are carried for consumers but are NOT verified against the database here.
|
|
8
8
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/presentation/auth/lease-token-auth.ts"],"sourcesContent":["import {AUTH_LEASED_JOB, setLeasedJobContext} from '@shipfox/api-auth-context';\nimport type {AuthMethod} from '@shipfox/node-fastify';\nimport {verifyJobLeaseToken} from '#core/job-lease-token.js';\nimport {createBearerTokenAuthMethod} from './bearer-token-auth.js';\n\n/**\n * Trust boundary: the signed token is the sole authority
|
|
1
|
+
{"version":3,"sources":["../../../src/presentation/auth/lease-token-auth.ts"],"sourcesContent":["import {AUTH_LEASED_JOB, setLeasedJobContext} from '@shipfox/api-auth-context';\nimport type {AuthMethod} from '@shipfox/node-fastify';\nimport {verifyJobLeaseToken} from '#core/job-lease-token.js';\nimport {createBearerTokenAuthMethod} from './bearer-token-auth.js';\n\n/**\n * Trust boundary: the signed token is the sole authority. `claims.jobId` scopes\n * every runner request to exactly one job. `workflowRunId`/`workflowRunAttemptId`/`workspaceId`\n * are carried for consumers but are NOT verified against the database here.\n *\n * Revocation tradeoff: `runnerSessionId` is carried in the claims but is not checked\n * against runner-session storage, so a lease minted before its registration token is\n * revoked stays usable until it expires (`AUTH_JOB_LEASE_TOKEN_EXPIRES_IN`, 90m).\n * Accepted deliberately for a short-lived, job-scoped capability token; tightening\n * this would mean a per-request DB lookup on the hot status-reporting path. See the\n * \"Security model\" section in the package README for the full rationale.\n */\nexport function createLeaseTokenAuthMethod(): AuthMethod {\n return createBearerTokenAuthMethod({\n name: AUTH_LEASED_JOB,\n verifyToken: verifyJobLeaseToken,\n invalidTokenError: {message: 'Invalid or expired job lease token', code: 'unauthorized'},\n setContext: setLeasedJobContext,\n });\n}\n"],"names":["AUTH_LEASED_JOB","setLeasedJobContext","verifyJobLeaseToken","createBearerTokenAuthMethod","createLeaseTokenAuthMethod","name","verifyToken","invalidTokenError","message","code","setContext"],"mappings":"AAAA,SAAQA,eAAe,EAAEC,mBAAmB,QAAO,4BAA4B;AAE/E,SAAQC,mBAAmB,QAAO,2BAA2B;AAC7D,SAAQC,2BAA2B,QAAO,yBAAyB;AAEnE;;;;;;;;;;;CAWC,GACD,OAAO,SAASC;IACd,OAAOD,4BAA4B;QACjCE,MAAML;QACNM,aAAaJ;QACbK,mBAAmB;YAACC,SAAS;YAAsCC,MAAM;QAAc;QACvFC,YAAYT;IACd;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rate-limit.d.ts","sourceRoot":"","sources":["../../../src/presentation/routes/rate-limit.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,KAAK,YAAY,EAAE,KAAK,cAAc,EAAC,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"rate-limit.d.ts","sourceRoot":"","sources":["../../../src/presentation/routes/rate-limit.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,KAAK,YAAY,EAAE,KAAK,cAAc,EAAC,MAAM,uBAAuB,CAAC;AAE1F,OAAO,EACL,KAAK,mBAAmB,EAIzB,MAAM,qBAAqB,CAAC;AAyB7B,UAAU,SAAS;IACjB,KAAK,EAAE,MAAM,CAAC;CACf;AAgDD,wBAAgB,6BAA6B,CAAC,MAAM,EAAE,mBAAmB,IACzD,SAAS,cAAc,CAAC;IAAC,IAAI,EAAE,SAAS,CAAA;CAAC,CAAC,EAAE,OAAO,YAAY,KAAG,OAAO,CAAC,IAAI,CAAC,CAiB9F;AAED,wBAAgB,+BAA+B,CAAC,MAAM,EAAE,mBAAmB,IAC3D,SAAS,cAAc,EAAE,OAAO,YAAY,KAAG,OAAO,CAAC,IAAI,CAAC,CAS3E"}
|