@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.
Files changed (58) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +40 -0
  3. package/README.md +48 -32
  4. package/dist/core/auth.d.ts.map +1 -1
  5. package/dist/core/auth.js +16 -8
  6. package/dist/core/auth.js.map +1 -1
  7. package/dist/core/job-lease-token.d.ts +1 -1
  8. package/dist/core/job-lease-token.d.ts.map +1 -1
  9. package/dist/core/job-lease-token.js +1 -1
  10. package/dist/core/job-lease-token.js.map +1 -1
  11. package/dist/core/jwt.d.ts +10 -0
  12. package/dist/core/jwt.d.ts.map +1 -1
  13. package/dist/core/jwt.js +3 -2
  14. package/dist/core/jwt.js.map +1 -1
  15. package/dist/core/runner-session-token.d.ts +1 -1
  16. package/dist/core/runner-session-token.d.ts.map +1 -1
  17. package/dist/core/runner-session-token.js +1 -1
  18. package/dist/core/runner-session-token.js.map +1 -1
  19. package/dist/db/rate-limits.d.ts +6 -15
  20. package/dist/db/rate-limits.d.ts.map +1 -1
  21. package/dist/db/rate-limits.js +17 -18
  22. package/dist/db/rate-limits.js.map +1 -1
  23. package/dist/db/refresh-tokens.d.ts +3 -7
  24. package/dist/db/refresh-tokens.d.ts.map +1 -1
  25. package/dist/db/refresh-tokens.js +3 -9
  26. package/dist/db/refresh-tokens.js.map +1 -1
  27. package/dist/presentation/auth/jwt-auth.d.ts +1 -4
  28. package/dist/presentation/auth/jwt-auth.d.ts.map +1 -1
  29. package/dist/presentation/auth/jwt-auth.js +5 -29
  30. package/dist/presentation/auth/jwt-auth.js.map +1 -1
  31. package/dist/presentation/auth/lease-token-auth.d.ts +1 -1
  32. package/dist/presentation/auth/lease-token-auth.js +1 -1
  33. package/dist/presentation/auth/lease-token-auth.js.map +1 -1
  34. package/dist/presentation/routes/rate-limit.d.ts.map +1 -1
  35. package/dist/presentation/routes/rate-limit.js +32 -53
  36. package/dist/presentation/routes/rate-limit.js.map +1 -1
  37. package/dist/tsconfig.test.tsbuildinfo +1 -1
  38. package/package.json +13 -13
  39. package/src/core/auth.test.ts +29 -10
  40. package/src/core/auth.ts +27 -12
  41. package/src/core/job-lease-token.ts +1 -1
  42. package/src/core/jwt.test.ts +20 -4
  43. package/src/core/jwt.ts +2 -1
  44. package/src/core/runner-session-token.ts +1 -1
  45. package/src/db/rate-limits.ts +28 -42
  46. package/src/db/refresh-tokens.test.ts +0 -19
  47. package/src/db/refresh-tokens.ts +3 -26
  48. package/src/presentation/auth/jwt-auth.test.ts +33 -8
  49. package/src/presentation/auth/jwt-auth.ts +3 -26
  50. package/src/presentation/auth/lease-token-auth.ts +1 -1
  51. package/src/presentation/routes/administration.test.ts +9 -1
  52. package/src/presentation/routes/password/change-password.test.ts +6 -0
  53. package/src/presentation/routes/password/password-reset-confirm.test.ts +14 -0
  54. package/src/presentation/routes/rate-limit.ts +31 -64
  55. package/src/presentation/routes/registration/signup.test.ts +2 -2
  56. package/src/presentation/routes/session/logout.test.ts +6 -0
  57. package/src/presentation/routes/session/refresh.test.ts +11 -0
  58. package/tsconfig.build.tsbuildinfo +1 -1
@@ -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;
@@ -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;;;;;iBAGhC,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"}
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(),
@@ -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;AAChE,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,MAAMX;AACR,GAAG;AAIH,OAAO,MAAMY,wBAAwBT,EAAEI,MAAM,CAAC;IAC5CM,KAAKV,EAAEM,MAAM,GAAGC,IAAI;IACpBI,kBAAkBX,EAAEM,MAAM,GAAGC,IAAI,GAAGK,QAAQ;IAC5CC,OAAOb,EAAEM,MAAM,GAAGO,KAAK;IACvBC,MAAMd,EAAEM,MAAM,GAAGS,QAAQ,GAAGH,QAAQ;IACpCI,aAAahB,EAAEiB,KAAK,CAACd;IACrBe,KAAKlB,EAAEmB,MAAM,GAAGC,GAAG;IACnBC,KAAKrB,EAAEmB,MAAM,GAAGC,GAAG;AACrB,GAAG;AAmBH,OAAO,eAAeE,cAAcC,MAA2B;IAC7D,MAAMC,QAAQ,MAAM1B,UAAU;QAC5B2B,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;IACA5B,kBAAkB;IAClB,OAAOuB;AACT;AAEA,OAAO,eAAeM,gBAAgBP,MAA6B;IACjE,IAAI;QACF,MAAMQ,SAAS,MAAMhC,YAAY;YAC/ByB,OAAOD,OAAOC,KAAK;YACnBE,QAAQH,OAAOG,MAAM;YACrBM,QAAQvB;QACV;QACAP,oBAAoB,WAAW;QAC/B,OAAO6B;IACT,EAAE,OAAOE,OAAO;QACd/B,oBAAoB,WAAW;QAC/B,MAAM+B;IACR;AACF"}
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 never throws. */
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,qFAAqF;AACrF,wBAAsB,wBAAwB,CAC5C,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,wBAAwB,GAAG,IAAI,CAAC,CAc1C"}
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 never throws. */ export async function verifyRunnerSessionToken(token) {
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 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,mFAAmF,GACnF,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"}
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"}
@@ -1,18 +1,9 @@
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?: {
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
- }): Promise<number | undefined>;
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":"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"}
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"}
@@ -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
- 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({
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
- 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
- }
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\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"}
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 one that can still authenticate as
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 unlike
30
- * {@link findActiveRefreshTokenByHash} it must still see rotated rows.
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,wBAAsB,wBAAwB,CAAC,MAAM,EAAE;IACrD,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;CAChB,GAAG,OAAO,CAAC,YAAY,GAAG,SAAS,CAAC,CAkBpC;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"}
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 one that can still authenticate as
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 unlike
58
- * {@link findActiveRefreshTokenByHash} it must still see rotated rows.
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;AASjD,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;;;GAGG;AACH,wBAAsB,8BAA8B,CAClD,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,2BAA2B,CAAC,CAatC;AAED,wBAAgB,mBAAmB,IAAI,UAAU,CAqChD"}
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
- const session = await findActiveRefreshSession({
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: session.sessionId
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
- const user = await findUserById({
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 {findActiveRefreshSession} from '#db/refresh-tokens.js';\nimport {findUserById} from '#db/users.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/**\n * Resolves the active refresh session backing an authenticated user request.\n * The session check intentionally happens only for consumers that need browser-session binding.\n */\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 const session = await findActiveRefreshSession({\n sessionId: token.refreshSessionId,\n userId: client.userId,\n });\n if (!session) throw new ClientError('Invalid or expired token', 'unauthorized', {status: 401});\n\n return {userId: client.userId, refreshSessionId: session.sessionId};\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 const user = await findUserById({id: claims.sub});\n\n // Legacy access tokens without a session claim remain verifiable for the\n // migration window; known suspended or deleted users are still rejected.\n if (user && user.status !== 'active') return null;\n if (!claims.refreshSessionId) return claims;\n if (!user) return null;\n\n const session = await findActiveRefreshSession({\n sessionId: claims.refreshSessionId,\n userId: claims.sub,\n });\n return session ? claims : null;\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","findActiveRefreshSession","findUserById","createBearerTokenAuthMethod","AUTHENTICATED_SESSION_CONTEXT_KEY","Symbol","for","InvalidJwtTokenError","Error","name","getClientContext","request","setAuthenticatedSessionContext","claims","userId","sub","refreshSessionId","getAuthenticatedSessionTokenContext","getAuthenticatedSessionContext","client","token","status","session","sessionId","createJwtAuthMethod","verifyToken","secret","user","id","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,wBAAwB,QAAO,wBAAwB;AAC/D,SAAQC,YAAY,QAAO,eAAe;AAC1C,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,OAAOf,eAAee;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;;;CAGC,GACD,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,IAAIjB,YAAY,4BAA4B,gBAAgB;QAACsB,QAAQ;IAAG;IAEhF,MAAMC,UAAU,MAAMrB,yBAAyB;QAC7CsB,WAAWH,MAAMJ,gBAAgB;QACjCF,QAAQK,OAAOL,MAAM;IACvB;IACA,IAAI,CAACQ,SAAS,MAAM,IAAIvB,YAAY,4BAA4B,gBAAgB;QAACsB,QAAQ;IAAG;IAE5F,OAAO;QAACP,QAAQK,OAAOL,MAAM;QAAEE,kBAAkBM,QAAQC,SAAS;IAAA;AACpE;AAEA,OAAO,SAASC;IACd,OAAOrB,4BAA4B;QACjCM,MAAMf;QACN+B,aAAa,OAAOL;YAClB,IAAIP;YACJ,IAAI;gBACFA,SAAS,MAAMb,gBAAgB;oBAACoB;oBAAOM,QAAQ5B;gBAAoB;YACrE,EAAE,OAAM;gBACN,MAAM,IAAIS;YACZ;YACA,MAAMoB,OAAO,MAAMzB,aAAa;gBAAC0B,IAAIf,OAAOE,GAAG;YAAA;YAE/C,yEAAyE;YACzE,yEAAyE;YACzE,IAAIY,QAAQA,KAAKN,MAAM,KAAK,UAAU,OAAO;YAC7C,IAAI,CAACR,OAAOG,gBAAgB,EAAE,OAAOH;YACrC,IAAI,CAACc,MAAM,OAAO;YAElB,MAAML,UAAU,MAAMrB,yBAAyB;gBAC7CsB,WAAWV,OAAOG,gBAAgB;gBAClCF,QAAQD,OAAOE,GAAG;YACpB;YACA,OAAOO,UAAUT,SAAS;QAC5B;QACAgB,qBAAqB,CAACC,QAAUA,iBAAiBvB;QACjDwB,mBAAmB;YAACC,SAAS;YAA4BC,MAAM;QAAc;QAC7EC,YAAY,CAACvB,SAASE;YACpB,MAAMsB,gBAA+BxC,iBAAiB;gBACpDmB,QAAQD,OAAOE,GAAG;gBAClBqB,OAAOvB,OAAOuB,KAAK;gBACnB3B,MAAMI,OAAOJ,IAAI,IAAI;gBACrB4B,aAAaxB,OAAOwB,WAAW;YACjC;YACAxC,eAAec,SAASwB;YACxBvB,+BAA+BD,SAASE;QAC1C;IACF;AACF"}
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 `claims.jobId` scopes
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 `claims.jobId` scopes
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 `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
+ {"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;AAC1F,OAAO,EACL,KAAK,mBAAmB,EAMzB,MAAM,qBAAqB,CAAC;AAyB7B,UAAU,SAAS;IACjB,KAAK,EAAE,MAAM,CAAC;CACf;AAgFD,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"}
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"}