@shipfox/api-auth 7.0.1 → 7.1.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 (46) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +28 -0
  3. package/README.md +19 -0
  4. package/dist/core/auth.d.ts.map +1 -1
  5. package/dist/core/auth.js +38 -21
  6. package/dist/core/auth.js.map +1 -1
  7. package/dist/core/entities/refresh-token.d.ts +1 -0
  8. package/dist/core/entities/refresh-token.d.ts.map +1 -1
  9. package/dist/core/entities/refresh-token.js.map +1 -1
  10. package/dist/core/jwt.d.ts +2 -0
  11. package/dist/core/jwt.d.ts.map +1 -1
  12. package/dist/core/jwt.js +3 -1
  13. package/dist/core/jwt.js.map +1 -1
  14. package/dist/db/db.d.ts +34 -0
  15. package/dist/db/db.d.ts.map +1 -1
  16. package/dist/db/refresh-tokens.d.ts +9 -0
  17. package/dist/db/refresh-tokens.d.ts.map +1 -1
  18. package/dist/db/refresh-tokens.js +14 -0
  19. package/dist/db/refresh-tokens.js.map +1 -1
  20. package/dist/db/schema/refresh-tokens.d.ts +17 -0
  21. package/dist/db/schema/refresh-tokens.d.ts.map +1 -1
  22. package/dist/db/schema/refresh-tokens.js +4 -0
  23. package/dist/db/schema/refresh-tokens.js.map +1 -1
  24. package/dist/index.d.ts +1 -1
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +1 -1
  27. package/dist/index.js.map +1 -1
  28. package/dist/presentation/auth/jwt-auth.d.ts +14 -1
  29. package/dist/presentation/auth/jwt-auth.d.ts.map +1 -1
  30. package/dist/presentation/auth/jwt-auth.js +34 -0
  31. package/dist/presentation/auth/jwt-auth.js.map +1 -1
  32. package/dist/tsconfig.test.tsbuildinfo +1 -1
  33. package/drizzle/0000_initial.sql +2 -0
  34. package/drizzle/meta/0000_snapshot.json +29 -0
  35. package/package.json +9 -23
  36. package/src/core/auth.test.ts +41 -0
  37. package/src/core/auth.ts +36 -19
  38. package/src/core/entities/refresh-token.ts +1 -0
  39. package/src/core/jwt.ts +3 -0
  40. package/src/db/refresh-tokens.test.ts +42 -0
  41. package/src/db/refresh-tokens.ts +42 -0
  42. package/src/db/schema/refresh-tokens.ts +6 -0
  43. package/src/index.ts +7 -1
  44. package/src/presentation/auth/jwt-auth.test.ts +102 -1
  45. package/src/presentation/auth/jwt-auth.ts +55 -1
  46. package/tsconfig.build.tsbuildinfo +1 -1
@@ -3,6 +3,7 @@ import { db } from './db.js';
3
3
  import { refreshTokens, toRefreshToken } from './schema/refresh-tokens.js';
4
4
  export async function createRefreshToken(params) {
5
5
  const rows = await db().insert(refreshTokens).values({
6
+ sessionId: params.sessionId,
6
7
  userId: params.userId,
7
8
  hashedToken: params.hashedToken,
8
9
  expiresAt: params.expiresAt
@@ -22,6 +23,12 @@ export async function createRefreshToken(params) {
22
23
  if (!row) return undefined;
23
24
  return toRefreshToken(row);
24
25
  }
26
+ export async function findActiveRefreshSession(params) {
27
+ 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);
28
+ const row = rows[0];
29
+ if (!row) return undefined;
30
+ return toRefreshToken(row);
31
+ }
25
32
  /**
26
33
  * Looks up a non-revoked, non-expired token by hash, including ones already
27
34
  * rotated. Rotation reads {@link RefreshToken.rotatedAt} to decide whether to
@@ -47,6 +54,7 @@ export async function createRefreshToken(params) {
47
54
  const rotated = rotatedRows[0];
48
55
  if (!rotated) return undefined;
49
56
  const successorRows = await tx.insert(refreshTokens).values({
57
+ sessionId: rotated.sessionId,
50
58
  userId: rotated.userId,
51
59
  hashedToken: params.nextHashedToken,
52
60
  expiresAt: params.expiresAt
@@ -61,6 +69,12 @@ export async function revokeRefreshTokenByHash(params) {
61
69
  updatedAt: sql`now()`
62
70
  }).where(and(eq(refreshTokens.hashedToken, params.hashedToken), isNull(refreshTokens.revokedAt)));
63
71
  }
72
+ export async function revokeRefreshSession(params) {
73
+ await db().update(refreshTokens).set({
74
+ revokedAt: sql`now()`,
75
+ updatedAt: sql`now()`
76
+ }).where(and(eq(refreshTokens.sessionId, params.sessionId), eq(refreshTokens.userId, params.userId), isNull(refreshTokens.revokedAt)));
77
+ }
64
78
  export async function revokeRefreshTokensForUser(params) {
65
79
  await db().update(refreshTokens).set({
66
80
  revokedAt: sql`now()`,
@@ -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';\n\nexport interface CreateRefreshTokenParams {\n userId: string;\n hashedToken: string;\n expiresAt: Date;\n}\n\nexport async function createRefreshToken(params: CreateRefreshTokenParams): Promise<RefreshToken> {\n const rows = await db()\n .insert(refreshTokens)\n .values({\n userId: params.userId,\n hashedToken: params.hashedToken,\n expiresAt: params.expiresAt,\n })\n .returning();\n\n const row = rows[0];\n if (!row) throw new Error('Insert returned no rows');\n return toRefreshToken(row);\n}\n\n/**\n * Looks up the live session token by hash — one that can still authenticate as\n * the current session. Returns `undefined` for revoked, expired, or already\n * rotated tokens; rotated rows survive in the table only for the grace window\n * and are not \"active\".\n */\nexport async function findActiveRefreshTokenByHash(params: {\n hashedToken: string;\n}): Promise<RefreshToken | undefined> {\n const rows = await db()\n .select()\n .from(refreshTokens)\n .where(\n and(\n eq(refreshTokens.hashedToken, params.hashedToken),\n isNull(refreshTokens.revokedAt),\n isNull(refreshTokens.rotatedAt),\n gt(refreshTokens.expiresAt, sql`now()`),\n ),\n )\n .limit(1);\n\n const row = rows[0];\n if (!row) return undefined;\n return toRefreshToken(row);\n}\n\n/**\n * Looks up a non-revoked, non-expired token by hash, including ones already\n * rotated. Rotation reads {@link RefreshToken.rotatedAt} to decide whether to\n * rotate, honour the grace window, or reject reuse, so — unlike\n * {@link findActiveRefreshTokenByHash} — it must still see rotated rows.\n */\nexport async function findRefreshTokenByHash(params: {\n hashedToken: string;\n}): Promise<RefreshToken | undefined> {\n const rows = await db()\n .select()\n .from(refreshTokens)\n .where(\n and(\n eq(refreshTokens.hashedToken, params.hashedToken),\n isNull(refreshTokens.revokedAt),\n gt(refreshTokens.expiresAt, sql`now()`),\n ),\n )\n .limit(1);\n\n const row = rows[0];\n if (!row) return undefined;\n return toRefreshToken(row);\n}\n\n/**\n * Atomically claims rotation and inserts the successor token. The predecessor is\n * marked rather than deleted so later reuse of the retired token can be detected.\n * If the successor insert fails, the predecessor rotation rolls back.\n */\nexport async function rotateRefreshToken(params: {\n id: string;\n currentHashedToken: string;\n nextHashedToken: string;\n expiresAt: Date;\n}): Promise<RefreshToken | undefined> {\n return await db().transaction(async (tx) => {\n const rotatedRows = await tx\n .update(refreshTokens)\n .set({\n rotatedAt: sql`now()`,\n lastUsedAt: sql`now()`,\n updatedAt: sql`now()`,\n })\n .where(\n and(\n eq(refreshTokens.id, params.id),\n eq(refreshTokens.hashedToken, params.currentHashedToken),\n isNull(refreshTokens.revokedAt),\n isNull(refreshTokens.rotatedAt),\n gt(refreshTokens.expiresAt, sql`now()`),\n ),\n )\n .returning();\n\n const rotated = rotatedRows[0];\n if (!rotated) return undefined;\n\n const successorRows = await tx\n .insert(refreshTokens)\n .values({\n userId: rotated.userId,\n hashedToken: params.nextHashedToken,\n expiresAt: params.expiresAt,\n })\n .returning();\n\n if (!successorRows[0]) throw new Error('Insert returned no rows');\n\n return toRefreshToken(rotated);\n });\n}\n\nexport async function revokeRefreshTokenByHash(params: {hashedToken: string}): Promise<void> {\n await db()\n .update(refreshTokens)\n .set({revokedAt: sql`now()`, updatedAt: sql`now()`})\n .where(and(eq(refreshTokens.hashedToken, params.hashedToken), isNull(refreshTokens.revokedAt)));\n}\n\nexport async function revokeRefreshTokensForUser(params: {\n userId: string;\n exceptRefreshTokenId?: string | undefined;\n}): Promise<void> {\n await db()\n .update(refreshTokens)\n .set({revokedAt: sql`now()`, updatedAt: sql`now()`})\n .where(\n and(\n eq(refreshTokens.userId, params.userId),\n isNull(refreshTokens.revokedAt),\n params.exceptRefreshTokenId ? ne(refreshTokens.id, params.exceptRefreshTokenId) : undefined,\n ),\n );\n}\n"],"names":["and","eq","gt","isNull","ne","sql","db","refreshTokens","toRefreshToken","createRefreshToken","params","rows","insert","values","userId","hashedToken","expiresAt","returning","row","Error","findActiveRefreshTokenByHash","select","from","where","revokedAt","rotatedAt","limit","undefined","findRefreshTokenByHash","rotateRefreshToken","transaction","tx","rotatedRows","update","set","lastUsedAt","updatedAt","id","currentHashedToken","rotated","successorRows","nextHashedToken","revokeRefreshTokenByHash","revokeRefreshTokensForUser","exceptRefreshTokenId"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/db/refresh-tokens.ts"],"sourcesContent":["import {and, eq, gt, isNull, ne, sql} from 'drizzle-orm';\nimport type {RefreshToken} from '#core/entities/refresh-token.js';\nimport {db} from './db.js';\nimport {refreshTokens, toRefreshToken} from './schema/refresh-tokens.js';\n\nexport interface CreateRefreshTokenParams {\n 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\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 id: string;\n currentHashedToken: string;\n nextHashedToken: string;\n expiresAt: Date;\n}): Promise<RefreshToken | undefined> {\n return await db().transaction(async (tx) => {\n const rotatedRows = await tx\n .update(refreshTokens)\n .set({\n rotatedAt: sql`now()`,\n lastUsedAt: sql`now()`,\n updatedAt: sql`now()`,\n })\n .where(\n and(\n eq(refreshTokens.id, params.id),\n eq(refreshTokens.hashedToken, params.currentHashedToken),\n isNull(refreshTokens.revokedAt),\n isNull(refreshTokens.rotatedAt),\n gt(refreshTokens.expiresAt, sql`now()`),\n ),\n )\n .returning();\n\n const rotated = rotatedRows[0];\n if (!rotated) return undefined;\n\n const successorRows = await tx\n .insert(refreshTokens)\n .values({\n 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","createRefreshToken","params","rows","insert","values","sessionId","userId","hashedToken","expiresAt","returning","row","Error","findActiveRefreshTokenByHash","select","from","where","revokedAt","rotatedAt","limit","undefined","findActiveRefreshSession","findRefreshTokenByHash","rotateRefreshToken","transaction","tx","rotatedRows","update","set","lastUsedAt","updatedAt","id","currentHashedToken","rotated","successorRows","nextHashedToken","revokeRefreshTokenByHash","revokeRefreshSession","revokeRefreshTokensForUser","exceptRefreshTokenId"],"mappings":"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"}
@@ -20,6 +20,23 @@ export declare const refreshTokens: import("drizzle-orm/pg-core").PgTableWithCol
20
20
  identity: undefined;
21
21
  generated: undefined;
22
22
  }, {}, {}>;
23
+ sessionId: import("drizzle-orm/pg-core").PgColumn<{
24
+ name: "session_id";
25
+ tableName: "refresh_tokens";
26
+ dataType: "string";
27
+ columnType: "PgUUID";
28
+ data: string;
29
+ driverParam: string;
30
+ notNull: true;
31
+ hasDefault: true;
32
+ isPrimaryKey: false;
33
+ isAutoincrement: false;
34
+ hasRuntimeDefault: false;
35
+ enumValues: undefined;
36
+ baseColumn: never;
37
+ identity: undefined;
38
+ generated: undefined;
39
+ }, {}, {}>;
23
40
  userId: import("drizzle-orm/pg-core").PgColumn<{
24
41
  name: "user_id";
25
42
  tableName: "refresh_tokens";
@@ -1 +1 @@
1
- {"version":3,"file":"refresh-tokens.d.ts","sourceRoot":"","sources":["../../../src/db/schema/refresh-tokens.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,iCAAiC,CAAC;AAIlE,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAmBzB,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG,OAAO,aAAa,CAAC,YAAY,CAAC;AAC/D,MAAM,MAAM,oBAAoB,GAAG,OAAO,aAAa,CAAC,YAAY,CAAC;AAErE,wBAAgB,cAAc,CAAC,GAAG,EAAE,cAAc,GAAG,YAAY,CAYhE"}
1
+ {"version":3,"file":"refresh-tokens.d.ts","sourceRoot":"","sources":["../../../src/db/schema/refresh-tokens.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,iCAAiC,CAAC;AAIlE,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAuBzB,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG,OAAO,aAAa,CAAC,YAAY,CAAC;AAC/D,MAAM,MAAM,oBAAoB,GAAG,OAAO,aAAa,CAAC,YAAY,CAAC;AAErE,wBAAgB,cAAc,CAAC,GAAG,EAAE,cAAc,GAAG,YAAY,CAahE"}
@@ -1,9 +1,11 @@
1
1
  import { uuidv7PrimaryKey } from '@shipfox/node-drizzle';
2
+ import { sql } from 'drizzle-orm';
2
3
  import { index, text, timestamp, uniqueIndex, uuid } from 'drizzle-orm/pg-core';
3
4
  import { pgTable } from './common.js';
4
5
  import { users } from './users.js';
5
6
  export const refreshTokens = pgTable('refresh_tokens', {
6
7
  id: uuidv7PrimaryKey(),
8
+ sessionId: uuid('session_id').notNull().default(sql`uuidv7()`),
7
9
  userId: uuid('user_id').notNull().references(()=>users.id, {
8
10
  onDelete: 'cascade'
9
11
  }),
@@ -28,11 +30,13 @@ export const refreshTokens = pgTable('refresh_tokens', {
28
30
  }).notNull().defaultNow()
29
31
  }, (table)=>[
30
32
  uniqueIndex('auth_refresh_tokens_hashed_token_unique').on(table.hashedToken),
33
+ uniqueIndex('auth_refresh_tokens_active_session_unique').on(table.userId, table.sessionId).where(sql`${table.revokedAt} IS NULL AND ${table.rotatedAt} IS NULL`),
31
34
  index('auth_refresh_tokens_user_id_idx').on(table.userId)
32
35
  ]);
33
36
  export function toRefreshToken(row) {
34
37
  return {
35
38
  id: row.id,
39
+ sessionId: row.sessionId,
36
40
  userId: row.userId,
37
41
  hashedToken: row.hashedToken,
38
42
  expiresAt: row.expiresAt,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/db/schema/refresh-tokens.ts"],"sourcesContent":["import {uuidv7PrimaryKey} from '@shipfox/node-drizzle';\nimport {index, text, timestamp, uniqueIndex, uuid} from 'drizzle-orm/pg-core';\nimport type {RefreshToken} from '#core/entities/refresh-token.js';\nimport {pgTable} from './common.js';\nimport {users} from './users.js';\n\nexport const refreshTokens = pgTable(\n 'refresh_tokens',\n {\n id: uuidv7PrimaryKey(),\n userId: uuid('user_id')\n .notNull()\n .references(() => users.id, {onDelete: 'cascade'}),\n hashedToken: text('hashed_token').notNull(),\n expiresAt: timestamp('expires_at', {withTimezone: true}).notNull(),\n revokedAt: timestamp('revoked_at', {withTimezone: true}),\n rotatedAt: timestamp('rotated_at', {withTimezone: true}),\n lastUsedAt: timestamp('last_used_at', {withTimezone: true}),\n createdAt: timestamp('created_at', {withTimezone: true}).notNull().defaultNow(),\n updatedAt: timestamp('updated_at', {withTimezone: true}).notNull().defaultNow(),\n },\n (table) => [\n uniqueIndex('auth_refresh_tokens_hashed_token_unique').on(table.hashedToken),\n index('auth_refresh_tokens_user_id_idx').on(table.userId),\n ],\n);\n\nexport type RefreshTokenDb = typeof refreshTokens.$inferSelect;\nexport type RefreshTokenCreateDb = typeof refreshTokens.$inferInsert;\n\nexport function toRefreshToken(row: RefreshTokenDb): RefreshToken {\n return {\n id: row.id,\n userId: row.userId,\n hashedToken: row.hashedToken,\n expiresAt: row.expiresAt,\n revokedAt: row.revokedAt,\n rotatedAt: row.rotatedAt,\n lastUsedAt: row.lastUsedAt,\n createdAt: row.createdAt,\n updatedAt: row.updatedAt,\n };\n}\n"],"names":["uuidv7PrimaryKey","index","text","timestamp","uniqueIndex","uuid","pgTable","users","refreshTokens","id","userId","notNull","references","onDelete","hashedToken","expiresAt","withTimezone","revokedAt","rotatedAt","lastUsedAt","createdAt","defaultNow","updatedAt","table","on","toRefreshToken","row"],"mappings":"AAAA,SAAQA,gBAAgB,QAAO,wBAAwB;AACvD,SAAQC,KAAK,EAAEC,IAAI,EAAEC,SAAS,EAAEC,WAAW,EAAEC,IAAI,QAAO,sBAAsB;AAE9E,SAAQC,OAAO,QAAO,cAAc;AACpC,SAAQC,KAAK,QAAO,aAAa;AAEjC,OAAO,MAAMC,gBAAgBF,QAC3B,kBACA;IACEG,IAAIT;IACJU,QAAQL,KAAK,WACVM,OAAO,GACPC,UAAU,CAAC,IAAML,MAAME,EAAE,EAAE;QAACI,UAAU;IAAS;IAClDC,aAAaZ,KAAK,gBAAgBS,OAAO;IACzCI,WAAWZ,UAAU,cAAc;QAACa,cAAc;IAAI,GAAGL,OAAO;IAChEM,WAAWd,UAAU,cAAc;QAACa,cAAc;IAAI;IACtDE,WAAWf,UAAU,cAAc;QAACa,cAAc;IAAI;IACtDG,YAAYhB,UAAU,gBAAgB;QAACa,cAAc;IAAI;IACzDI,WAAWjB,UAAU,cAAc;QAACa,cAAc;IAAI,GAAGL,OAAO,GAAGU,UAAU;IAC7EC,WAAWnB,UAAU,cAAc;QAACa,cAAc;IAAI,GAAGL,OAAO,GAAGU,UAAU;AAC/E,GACA,CAACE,QAAU;QACTnB,YAAY,2CAA2CoB,EAAE,CAACD,MAAMT,WAAW;QAC3Eb,MAAM,mCAAmCuB,EAAE,CAACD,MAAMb,MAAM;KACzD,EACD;AAKF,OAAO,SAASe,eAAeC,GAAmB;IAChD,OAAO;QACLjB,IAAIiB,IAAIjB,EAAE;QACVC,QAAQgB,IAAIhB,MAAM;QAClBI,aAAaY,IAAIZ,WAAW;QAC5BC,WAAWW,IAAIX,SAAS;QACxBE,WAAWS,IAAIT,SAAS;QACxBC,WAAWQ,IAAIR,SAAS;QACxBC,YAAYO,IAAIP,UAAU;QAC1BC,WAAWM,IAAIN,SAAS;QACxBE,WAAWI,IAAIJ,SAAS;IAC1B;AACF"}
1
+ {"version":3,"sources":["../../../src/db/schema/refresh-tokens.ts"],"sourcesContent":["import {uuidv7PrimaryKey} from '@shipfox/node-drizzle';\nimport {sql} from 'drizzle-orm';\nimport {index, text, timestamp, uniqueIndex, uuid} from 'drizzle-orm/pg-core';\nimport type {RefreshToken} from '#core/entities/refresh-token.js';\nimport {pgTable} from './common.js';\nimport {users} from './users.js';\n\nexport const refreshTokens = pgTable(\n 'refresh_tokens',\n {\n id: uuidv7PrimaryKey(),\n sessionId: uuid('session_id').notNull().default(sql`uuidv7()`),\n userId: uuid('user_id')\n .notNull()\n .references(() => users.id, {onDelete: 'cascade'}),\n hashedToken: text('hashed_token').notNull(),\n expiresAt: timestamp('expires_at', {withTimezone: true}).notNull(),\n revokedAt: timestamp('revoked_at', {withTimezone: true}),\n rotatedAt: timestamp('rotated_at', {withTimezone: true}),\n lastUsedAt: timestamp('last_used_at', {withTimezone: true}),\n createdAt: timestamp('created_at', {withTimezone: true}).notNull().defaultNow(),\n updatedAt: timestamp('updated_at', {withTimezone: true}).notNull().defaultNow(),\n },\n (table) => [\n uniqueIndex('auth_refresh_tokens_hashed_token_unique').on(table.hashedToken),\n uniqueIndex('auth_refresh_tokens_active_session_unique')\n .on(table.userId, table.sessionId)\n .where(sql`${table.revokedAt} IS NULL AND ${table.rotatedAt} IS NULL`),\n index('auth_refresh_tokens_user_id_idx').on(table.userId),\n ],\n);\n\nexport type RefreshTokenDb = typeof refreshTokens.$inferSelect;\nexport type RefreshTokenCreateDb = typeof refreshTokens.$inferInsert;\n\nexport function toRefreshToken(row: RefreshTokenDb): RefreshToken {\n return {\n id: row.id,\n sessionId: row.sessionId,\n userId: row.userId,\n hashedToken: row.hashedToken,\n expiresAt: row.expiresAt,\n revokedAt: row.revokedAt,\n rotatedAt: row.rotatedAt,\n lastUsedAt: row.lastUsedAt,\n createdAt: row.createdAt,\n updatedAt: row.updatedAt,\n };\n}\n"],"names":["uuidv7PrimaryKey","sql","index","text","timestamp","uniqueIndex","uuid","pgTable","users","refreshTokens","id","sessionId","notNull","default","userId","references","onDelete","hashedToken","expiresAt","withTimezone","revokedAt","rotatedAt","lastUsedAt","createdAt","defaultNow","updatedAt","table","on","where","toRefreshToken","row"],"mappings":"AAAA,SAAQA,gBAAgB,QAAO,wBAAwB;AACvD,SAAQC,GAAG,QAAO,cAAc;AAChC,SAAQC,KAAK,EAAEC,IAAI,EAAEC,SAAS,EAAEC,WAAW,EAAEC,IAAI,QAAO,sBAAsB;AAE9E,SAAQC,OAAO,QAAO,cAAc;AACpC,SAAQC,KAAK,QAAO,aAAa;AAEjC,OAAO,MAAMC,gBAAgBF,QAC3B,kBACA;IACEG,IAAIV;IACJW,WAAWL,KAAK,cAAcM,OAAO,GAAGC,OAAO,CAACZ,GAAG,CAAC,QAAQ,CAAC;IAC7Da,QAAQR,KAAK,WACVM,OAAO,GACPG,UAAU,CAAC,IAAMP,MAAME,EAAE,EAAE;QAACM,UAAU;IAAS;IAClDC,aAAad,KAAK,gBAAgBS,OAAO;IACzCM,WAAWd,UAAU,cAAc;QAACe,cAAc;IAAI,GAAGP,OAAO;IAChEQ,WAAWhB,UAAU,cAAc;QAACe,cAAc;IAAI;IACtDE,WAAWjB,UAAU,cAAc;QAACe,cAAc;IAAI;IACtDG,YAAYlB,UAAU,gBAAgB;QAACe,cAAc;IAAI;IACzDI,WAAWnB,UAAU,cAAc;QAACe,cAAc;IAAI,GAAGP,OAAO,GAAGY,UAAU;IAC7EC,WAAWrB,UAAU,cAAc;QAACe,cAAc;IAAI,GAAGP,OAAO,GAAGY,UAAU;AAC/E,GACA,CAACE,QAAU;QACTrB,YAAY,2CAA2CsB,EAAE,CAACD,MAAMT,WAAW;QAC3EZ,YAAY,6CACTsB,EAAE,CAACD,MAAMZ,MAAM,EAAEY,MAAMf,SAAS,EAChCiB,KAAK,CAAC3B,GAAG,CAAC,EAAEyB,MAAMN,SAAS,CAAC,aAAa,EAAEM,MAAML,SAAS,CAAC,QAAQ,CAAC;QACvEnB,MAAM,mCAAmCyB,EAAE,CAACD,MAAMZ,MAAM;KACzD,EACD;AAKF,OAAO,SAASe,eAAeC,GAAmB;IAChD,OAAO;QACLpB,IAAIoB,IAAIpB,EAAE;QACVC,WAAWmB,IAAInB,SAAS;QACxBG,QAAQgB,IAAIhB,MAAM;QAClBG,aAAaa,IAAIb,WAAW;QAC5BC,WAAWY,IAAIZ,SAAS;QACxBE,WAAWU,IAAIV,SAAS;QACxBC,WAAWS,IAAIT,SAAS;QACxBC,YAAYQ,IAAIR,UAAU;QAC1BC,WAAWO,IAAIP,SAAS;QACxBE,WAAWK,IAAIL,SAAS;IAC1B;AACF"}
package/dist/index.d.ts CHANGED
@@ -8,7 +8,7 @@ export type { User, UserStatus } from '#core/entities/user.js';
8
8
  export { AuthDependencyUnavailableError, EmailNotVerifiedError, InvalidCredentialsError, UserNotFoundError, } from '#core/errors.js';
9
9
  export { issueJobLeaseToken, jobLeaseParamsFrom, verifyJobLeaseToken, } from '#core/job-lease-token.js';
10
10
  export { issueRunnerSessionToken, verifyRunnerSessionToken, } from '#core/runner-session-token.js';
11
- export { createJwtAuthMethod } from '#presentation/auth/jwt-auth.js';
11
+ export { type AuthenticatedSessionContext, createJwtAuthMethod, getAuthenticatedSessionContext, type RefreshSessionId, type UserId, } from '#presentation/auth/jwt-auth.js';
12
12
  export { createLeaseTokenAuthMethod } from '#presentation/auth/lease-token-auth.js';
13
13
  export { authCookiePlugin, clearRefreshTokenCookie, getRefreshTokenCookie, setRefreshTokenCookie, } from '#presentation/auth/refresh-cookie.js';
14
14
  export { createRunnerSessionAuthMethod } from '#presentation/auth/runner-session-auth.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,sBAAsB,CAAC;AAexD,YAAY,EAAC,mBAAmB,EAAE,wBAAwB,EAAC,MAAM,uBAAuB,CAAC;AACzF,YAAY,EACV,yBAAyB,EACzB,0BAA0B,EAC1B,0BAA0B,EAC1B,mBAAmB,GACpB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAC,oBAAoB,EAAE,aAAa,EAAC,MAAM,eAAe,CAAC;AAClE,YAAY,EAAC,UAAU,EAAE,qBAAqB,EAAC,MAAM,sBAAsB,CAAC;AAC5E,OAAO,EAAC,eAAe,EAAC,MAAM,sBAAsB,CAAC;AACrD,YAAY,EAAC,IAAI,EAAE,UAAU,EAAC,MAAM,wBAAwB,CAAC;AAC7D,OAAO,EACL,8BAA8B,EAC9B,qBAAqB,EACrB,uBAAuB,EACvB,iBAAiB,GAClB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,kBAAkB,EAClB,kBAAkB,EAClB,mBAAmB,GACpB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,uBAAuB,EACvB,wBAAwB,GACzB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAAC,mBAAmB,EAAC,MAAM,gCAAgC,CAAC;AACnE,OAAO,EAAC,0BAA0B,EAAC,MAAM,wCAAwC,CAAC;AAClF,OAAO,EACL,gBAAgB,EAChB,uBAAuB,EACvB,qBAAqB,EACrB,qBAAqB,GACtB,MAAM,sCAAsC,CAAC;AAC9C,OAAO,EAAC,6BAA6B,EAAC,MAAM,2CAA2C,CAAC;AAIxF,wBAAgB,gBAAgB,CAAC,EAC/B,UAAU,GACX,EAAE;IACD,UAAU,EAAE,OAAO,0CAA0C,EAAE,2BAA2B,CAAC;CAC5F,GAAG,aAAa,CAYhB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,sBAAsB,CAAC;AAexD,YAAY,EAAC,mBAAmB,EAAE,wBAAwB,EAAC,MAAM,uBAAuB,CAAC;AACzF,YAAY,EACV,yBAAyB,EACzB,0BAA0B,EAC1B,0BAA0B,EAC1B,mBAAmB,GACpB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAC,oBAAoB,EAAE,aAAa,EAAC,MAAM,eAAe,CAAC;AAClE,YAAY,EAAC,UAAU,EAAE,qBAAqB,EAAC,MAAM,sBAAsB,CAAC;AAC5E,OAAO,EAAC,eAAe,EAAC,MAAM,sBAAsB,CAAC;AACrD,YAAY,EAAC,IAAI,EAAE,UAAU,EAAC,MAAM,wBAAwB,CAAC;AAC7D,OAAO,EACL,8BAA8B,EAC9B,qBAAqB,EACrB,uBAAuB,EACvB,iBAAiB,GAClB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,kBAAkB,EAClB,kBAAkB,EAClB,mBAAmB,GACpB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,uBAAuB,EACvB,wBAAwB,GACzB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EACL,KAAK,2BAA2B,EAChC,mBAAmB,EACnB,8BAA8B,EAC9B,KAAK,gBAAgB,EACrB,KAAK,MAAM,GACZ,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAAC,0BAA0B,EAAC,MAAM,wCAAwC,CAAC;AAClF,OAAO,EACL,gBAAgB,EAChB,uBAAuB,EACvB,qBAAqB,EACrB,qBAAqB,GACtB,MAAM,sCAAsC,CAAC;AAC9C,OAAO,EAAC,6BAA6B,EAAC,MAAM,2CAA2C,CAAC;AAIxF,wBAAgB,gBAAgB,CAAC,EAC/B,UAAU,GACX,EAAE;IACD,UAAU,EAAE,OAAO,0CAA0C,EAAE,2BAA2B,CAAC;CAC5F,GAAG,aAAa,CAYhB"}
package/dist/index.js CHANGED
@@ -17,7 +17,7 @@ export { findUserByEmail } from '#core/email-owner.js';
17
17
  export { AuthDependencyUnavailableError, EmailNotVerifiedError, InvalidCredentialsError, UserNotFoundError } from '#core/errors.js';
18
18
  export { issueJobLeaseToken, jobLeaseParamsFrom, verifyJobLeaseToken } from '#core/job-lease-token.js';
19
19
  export { issueRunnerSessionToken, verifyRunnerSessionToken } from '#core/runner-session-token.js';
20
- export { createJwtAuthMethod } from '#presentation/auth/jwt-auth.js';
20
+ export { createJwtAuthMethod, getAuthenticatedSessionContext } from '#presentation/auth/jwt-auth.js';
21
21
  export { createLeaseTokenAuthMethod } from '#presentation/auth/lease-token-auth.js';
22
22
  export { authCookiePlugin, clearRefreshTokenCookie, getRefreshTokenCookie, setRefreshTokenCookie } from '#presentation/auth/refresh-cookie.js';
23
23
  export { createRunnerSessionAuthMethod } from '#presentation/auth/runner-session-auth.js';
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import {\n AUTH_PASSWORD_RESET_SEND_REQUESTED,\n type AuthEventMap,\n authEventSchemas,\n} from '@shipfox/api-auth-dto';\nimport type {ShipfoxModule} from '@shipfox/node-module';\nimport {subscriberFactory} from '@shipfox/node-module';\nimport {config} from '#config.js';\nimport {db} from '#db/db.js';\nimport {migrationsPath} from '#db/migrations.js';\nimport {authOutbox} from '#db/schema/outbox.js';\nimport {createJwtAuthMethod} from '#presentation/auth/jwt-auth.js';\nimport {createLeaseTokenAuthMethod} from '#presentation/auth/lease-token-auth.js';\nimport {createRunnerSessionAuthMethod} from '#presentation/auth/runner-session-auth.js';\nimport {createAuthE2eRoutes} from '#presentation/e2eRoutes/index.js';\nimport {createAuthInterModulePresentation} from '#presentation/inter-module.js';\nimport {buildAuthRoutes} from '#presentation/routes/index.js';\nimport {onPasswordResetSendRequested} from '#presentation/subscribers/index.js';\nimport {passwordLoginMethods} from './login-methods.js';\n\nexport type {JobLeaseTokenClaims, RunnerSessionTokenClaims} from '@shipfox/api-auth-dto';\nexport type {\n CreateSessionForUserError,\n CreateSessionForUserParams,\n CreateSessionForUserResult,\n ProvisionUserParams,\n} from '#core/auth.js';\nexport {createSessionForUser, provisionUser} from '#core/auth.js';\nexport type {EmailOwner, FindUserByEmailParams} from '#core/email-owner.js';\nexport {findUserByEmail} from '#core/email-owner.js';\nexport type {User, UserStatus} from '#core/entities/user.js';\nexport {\n AuthDependencyUnavailableError,\n EmailNotVerifiedError,\n InvalidCredentialsError,\n UserNotFoundError,\n} from '#core/errors.js';\nexport {\n issueJobLeaseToken,\n jobLeaseParamsFrom,\n verifyJobLeaseToken,\n} from '#core/job-lease-token.js';\nexport {\n issueRunnerSessionToken,\n verifyRunnerSessionToken,\n} from '#core/runner-session-token.js';\nexport {createJwtAuthMethod} from '#presentation/auth/jwt-auth.js';\nexport {createLeaseTokenAuthMethod} from '#presentation/auth/lease-token-auth.js';\nexport {\n authCookiePlugin,\n clearRefreshTokenCookie,\n getRefreshTokenCookie,\n setRefreshTokenCookie,\n} from '#presentation/auth/refresh-cookie.js';\nexport {createRunnerSessionAuthMethod} from '#presentation/auth/runner-session-auth.js';\n\nconst subscriber = subscriberFactory<AuthEventMap>();\n\nexport function createAuthModule({\n workspaces,\n}: {\n workspaces: import('@shipfox/api-workspaces-dto/inter-module').WorkspacesInterModuleClient;\n}): ShipfoxModule {\n return {\n name: 'auth',\n database: {db, migrationsPath},\n auth: [createJwtAuthMethod(), createLeaseTokenAuthMethod(), createRunnerSessionAuthMethod()],\n loginMethods: passwordLoginMethods(config.AUTH_PASSWORD_ENABLED),\n routes: [buildAuthRoutes(config.AUTH_PASSWORD_ENABLED, workspaces)],\n e2eRoutes: [createAuthE2eRoutes(workspaces)],\n publishers: [{name: 'auth', table: authOutbox, db, eventSchemas: authEventSchemas}],\n subscribers: [subscriber(AUTH_PASSWORD_RESET_SEND_REQUESTED, onPasswordResetSendRequested)],\n interModulePresentations: [createAuthInterModulePresentation()],\n };\n}\n"],"names":["AUTH_PASSWORD_RESET_SEND_REQUESTED","authEventSchemas","subscriberFactory","config","db","migrationsPath","authOutbox","createJwtAuthMethod","createLeaseTokenAuthMethod","createRunnerSessionAuthMethod","createAuthE2eRoutes","createAuthInterModulePresentation","buildAuthRoutes","onPasswordResetSendRequested","passwordLoginMethods","createSessionForUser","provisionUser","findUserByEmail","AuthDependencyUnavailableError","EmailNotVerifiedError","InvalidCredentialsError","UserNotFoundError","issueJobLeaseToken","jobLeaseParamsFrom","verifyJobLeaseToken","issueRunnerSessionToken","verifyRunnerSessionToken","authCookiePlugin","clearRefreshTokenCookie","getRefreshTokenCookie","setRefreshTokenCookie","subscriber","createAuthModule","workspaces","name","database","auth","loginMethods","AUTH_PASSWORD_ENABLED","routes","e2eRoutes","publishers","table","eventSchemas","subscribers","interModulePresentations"],"mappings":"AAAA,SACEA,kCAAkC,EAElCC,gBAAgB,QACX,wBAAwB;AAE/B,SAAQC,iBAAiB,QAAO,uBAAuB;AACvD,SAAQC,MAAM,QAAO,aAAa;AAClC,SAAQC,EAAE,QAAO,YAAY;AAC7B,SAAQC,cAAc,QAAO,oBAAoB;AACjD,SAAQC,UAAU,QAAO,uBAAuB;AAChD,SAAQC,mBAAmB,QAAO,iCAAiC;AACnE,SAAQC,0BAA0B,QAAO,yCAAyC;AAClF,SAAQC,6BAA6B,QAAO,4CAA4C;AACxF,SAAQC,mBAAmB,QAAO,mCAAmC;AACrE,SAAQC,iCAAiC,QAAO,gCAAgC;AAChF,SAAQC,eAAe,QAAO,gCAAgC;AAC9D,SAAQC,4BAA4B,QAAO,qCAAqC;AAChF,SAAQC,oBAAoB,QAAO,qBAAqB;AASxD,SAAQC,oBAAoB,EAAEC,aAAa,QAAO,gBAAgB;AAElE,SAAQC,eAAe,QAAO,uBAAuB;AAErD,SACEC,8BAA8B,EAC9BC,qBAAqB,EACrBC,uBAAuB,EACvBC,iBAAiB,QACZ,kBAAkB;AACzB,SACEC,kBAAkB,EAClBC,kBAAkB,EAClBC,mBAAmB,QACd,2BAA2B;AAClC,SACEC,uBAAuB,EACvBC,wBAAwB,QACnB,gCAAgC;AACvC,SAAQnB,mBAAmB,QAAO,iCAAiC;AACnE,SAAQC,0BAA0B,QAAO,yCAAyC;AAClF,SACEmB,gBAAgB,EAChBC,uBAAuB,EACvBC,qBAAqB,EACrBC,qBAAqB,QAChB,uCAAuC;AAC9C,SAAQrB,6BAA6B,QAAO,4CAA4C;AAExF,MAAMsB,aAAa7B;AAEnB,OAAO,SAAS8B,iBAAiB,EAC/BC,UAAU,EAGX;IACC,OAAO;QACLC,MAAM;QACNC,UAAU;YAAC/B;YAAIC;QAAc;QAC7B+B,MAAM;YAAC7B;YAAuBC;YAA8BC;SAAgC;QAC5F4B,cAAcvB,qBAAqBX,OAAOmC,qBAAqB;QAC/DC,QAAQ;YAAC3B,gBAAgBT,OAAOmC,qBAAqB,EAAEL;SAAY;QACnEO,WAAW;YAAC9B,oBAAoBuB;SAAY;QAC5CQ,YAAY;YAAC;gBAACP,MAAM;gBAAQQ,OAAOpC;gBAAYF;gBAAIuC,cAAc1C;YAAgB;SAAE;QACnF2C,aAAa;YAACb,WAAW/B,oCAAoCa;SAA8B;QAC3FgC,0BAA0B;YAAClC;SAAoC;IACjE;AACF"}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import {\n AUTH_PASSWORD_RESET_SEND_REQUESTED,\n type AuthEventMap,\n authEventSchemas,\n} from '@shipfox/api-auth-dto';\nimport type {ShipfoxModule} from '@shipfox/node-module';\nimport {subscriberFactory} from '@shipfox/node-module';\nimport {config} from '#config.js';\nimport {db} from '#db/db.js';\nimport {migrationsPath} from '#db/migrations.js';\nimport {authOutbox} from '#db/schema/outbox.js';\nimport {createJwtAuthMethod} from '#presentation/auth/jwt-auth.js';\nimport {createLeaseTokenAuthMethod} from '#presentation/auth/lease-token-auth.js';\nimport {createRunnerSessionAuthMethod} from '#presentation/auth/runner-session-auth.js';\nimport {createAuthE2eRoutes} from '#presentation/e2eRoutes/index.js';\nimport {createAuthInterModulePresentation} from '#presentation/inter-module.js';\nimport {buildAuthRoutes} from '#presentation/routes/index.js';\nimport {onPasswordResetSendRequested} from '#presentation/subscribers/index.js';\nimport {passwordLoginMethods} from './login-methods.js';\n\nexport type {JobLeaseTokenClaims, RunnerSessionTokenClaims} from '@shipfox/api-auth-dto';\nexport type {\n CreateSessionForUserError,\n CreateSessionForUserParams,\n CreateSessionForUserResult,\n ProvisionUserParams,\n} from '#core/auth.js';\nexport {createSessionForUser, provisionUser} from '#core/auth.js';\nexport type {EmailOwner, FindUserByEmailParams} from '#core/email-owner.js';\nexport {findUserByEmail} from '#core/email-owner.js';\nexport type {User, UserStatus} from '#core/entities/user.js';\nexport {\n AuthDependencyUnavailableError,\n EmailNotVerifiedError,\n InvalidCredentialsError,\n UserNotFoundError,\n} from '#core/errors.js';\nexport {\n issueJobLeaseToken,\n jobLeaseParamsFrom,\n verifyJobLeaseToken,\n} from '#core/job-lease-token.js';\nexport {\n issueRunnerSessionToken,\n verifyRunnerSessionToken,\n} from '#core/runner-session-token.js';\nexport {\n type AuthenticatedSessionContext,\n createJwtAuthMethod,\n getAuthenticatedSessionContext,\n type RefreshSessionId,\n type UserId,\n} from '#presentation/auth/jwt-auth.js';\nexport {createLeaseTokenAuthMethod} from '#presentation/auth/lease-token-auth.js';\nexport {\n authCookiePlugin,\n clearRefreshTokenCookie,\n getRefreshTokenCookie,\n setRefreshTokenCookie,\n} from '#presentation/auth/refresh-cookie.js';\nexport {createRunnerSessionAuthMethod} from '#presentation/auth/runner-session-auth.js';\n\nconst subscriber = subscriberFactory<AuthEventMap>();\n\nexport function createAuthModule({\n workspaces,\n}: {\n workspaces: import('@shipfox/api-workspaces-dto/inter-module').WorkspacesInterModuleClient;\n}): ShipfoxModule {\n return {\n name: 'auth',\n database: {db, migrationsPath},\n auth: [createJwtAuthMethod(), createLeaseTokenAuthMethod(), createRunnerSessionAuthMethod()],\n loginMethods: passwordLoginMethods(config.AUTH_PASSWORD_ENABLED),\n routes: [buildAuthRoutes(config.AUTH_PASSWORD_ENABLED, workspaces)],\n e2eRoutes: [createAuthE2eRoutes(workspaces)],\n publishers: [{name: 'auth', table: authOutbox, db, eventSchemas: authEventSchemas}],\n subscribers: [subscriber(AUTH_PASSWORD_RESET_SEND_REQUESTED, onPasswordResetSendRequested)],\n interModulePresentations: [createAuthInterModulePresentation()],\n };\n}\n"],"names":["AUTH_PASSWORD_RESET_SEND_REQUESTED","authEventSchemas","subscriberFactory","config","db","migrationsPath","authOutbox","createJwtAuthMethod","createLeaseTokenAuthMethod","createRunnerSessionAuthMethod","createAuthE2eRoutes","createAuthInterModulePresentation","buildAuthRoutes","onPasswordResetSendRequested","passwordLoginMethods","createSessionForUser","provisionUser","findUserByEmail","AuthDependencyUnavailableError","EmailNotVerifiedError","InvalidCredentialsError","UserNotFoundError","issueJobLeaseToken","jobLeaseParamsFrom","verifyJobLeaseToken","issueRunnerSessionToken","verifyRunnerSessionToken","getAuthenticatedSessionContext","authCookiePlugin","clearRefreshTokenCookie","getRefreshTokenCookie","setRefreshTokenCookie","subscriber","createAuthModule","workspaces","name","database","auth","loginMethods","AUTH_PASSWORD_ENABLED","routes","e2eRoutes","publishers","table","eventSchemas","subscribers","interModulePresentations"],"mappings":"AAAA,SACEA,kCAAkC,EAElCC,gBAAgB,QACX,wBAAwB;AAE/B,SAAQC,iBAAiB,QAAO,uBAAuB;AACvD,SAAQC,MAAM,QAAO,aAAa;AAClC,SAAQC,EAAE,QAAO,YAAY;AAC7B,SAAQC,cAAc,QAAO,oBAAoB;AACjD,SAAQC,UAAU,QAAO,uBAAuB;AAChD,SAAQC,mBAAmB,QAAO,iCAAiC;AACnE,SAAQC,0BAA0B,QAAO,yCAAyC;AAClF,SAAQC,6BAA6B,QAAO,4CAA4C;AACxF,SAAQC,mBAAmB,QAAO,mCAAmC;AACrE,SAAQC,iCAAiC,QAAO,gCAAgC;AAChF,SAAQC,eAAe,QAAO,gCAAgC;AAC9D,SAAQC,4BAA4B,QAAO,qCAAqC;AAChF,SAAQC,oBAAoB,QAAO,qBAAqB;AASxD,SAAQC,oBAAoB,EAAEC,aAAa,QAAO,gBAAgB;AAElE,SAAQC,eAAe,QAAO,uBAAuB;AAErD,SACEC,8BAA8B,EAC9BC,qBAAqB,EACrBC,uBAAuB,EACvBC,iBAAiB,QACZ,kBAAkB;AACzB,SACEC,kBAAkB,EAClBC,kBAAkB,EAClBC,mBAAmB,QACd,2BAA2B;AAClC,SACEC,uBAAuB,EACvBC,wBAAwB,QACnB,gCAAgC;AACvC,SAEEnB,mBAAmB,EACnBoB,8BAA8B,QAGzB,iCAAiC;AACxC,SAAQnB,0BAA0B,QAAO,yCAAyC;AAClF,SACEoB,gBAAgB,EAChBC,uBAAuB,EACvBC,qBAAqB,EACrBC,qBAAqB,QAChB,uCAAuC;AAC9C,SAAQtB,6BAA6B,QAAO,4CAA4C;AAExF,MAAMuB,aAAa9B;AAEnB,OAAO,SAAS+B,iBAAiB,EAC/BC,UAAU,EAGX;IACC,OAAO;QACLC,MAAM;QACNC,UAAU;YAAChC;YAAIC;QAAc;QAC7BgC,MAAM;YAAC9B;YAAuBC;YAA8BC;SAAgC;QAC5F6B,cAAcxB,qBAAqBX,OAAOoC,qBAAqB;QAC/DC,QAAQ;YAAC5B,gBAAgBT,OAAOoC,qBAAqB,EAAEL;SAAY;QACnEO,WAAW;YAAC/B,oBAAoBwB;SAAY;QAC5CQ,YAAY;YAAC;gBAACP,MAAM;gBAAQQ,OAAOrC;gBAAYF;gBAAIwC,cAAc3C;YAAgB;SAAE;QACnF4C,aAAa;YAACb,WAAWhC,oCAAoCa;SAA8B;QAC3FiC,0BAA0B;YAACnC;SAAoC;IACjE;AACF"}
@@ -1,10 +1,23 @@
1
1
  import { type UserContext } from '@shipfox/api-auth-context';
2
- import type { AuthMethod } from '@shipfox/node-fastify';
2
+ import { type AuthMethod } from '@shipfox/node-fastify';
3
3
  import type { FastifyRequest } from 'fastify';
4
+ import type { RefreshToken } from '#core/entities/refresh-token.js';
5
+ import type { User } from '#core/entities/user.js';
4
6
  export type ClientContext = UserContext;
7
+ export type UserId = User['id'];
8
+ export type RefreshSessionId = RefreshToken['sessionId'];
9
+ export type AuthenticatedSessionContext = {
10
+ userId: UserId;
11
+ refreshSessionId: RefreshSessionId;
12
+ };
5
13
  export interface CreateJwtAuthMethodOptions {
6
14
  secret: string;
7
15
  }
8
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
+ */
21
+ export declare function getAuthenticatedSessionContext(request: FastifyRequest): Promise<AuthenticatedSessionContext>;
9
22
  export declare function createJwtAuthMethod(): AuthMethod;
10
23
  //# 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,KAAK,EAAC,UAAU,EAAC,MAAM,uBAAuB,CAAC;AACtD,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,SAAS,CAAC;AAI5C,MAAM,MAAM,aAAa,GAAG,WAAW,CAAC;AAExC,MAAM,WAAW,0BAA0B;IACzC,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,cAAc,GAAG,aAAa,GAAG,IAAI,CAE9E;AAED,wBAAgB,mBAAmB,IAAI,UAAU,CAehD"}
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;AAQjD,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;AAED,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,CAgBhD"}
@@ -1,10 +1,43 @@
1
1
  import { AUTH_USER, buildUserContext, getUserContext, setUserContext } from '@shipfox/api-auth-context';
2
2
  import { userAccessTokenKey } from '@shipfox/node-auth-root-key';
3
+ import { ClientError } from '@shipfox/node-fastify';
3
4
  import { verifyUserToken } from '#core/jwt.js';
5
+ import { findActiveRefreshSession } from '#db/refresh-tokens.js';
4
6
  import { createBearerTokenAuthMethod } from './bearer-token-auth.js';
7
+ const AUTHENTICATED_SESSION_CONTEXT_KEY = Symbol.for('@shipfox/api-auth/session');
5
8
  export function getClientContext(request) {
6
9
  return getUserContext(request);
7
10
  }
11
+ function setAuthenticatedSessionContext(request, claims) {
12
+ request[AUTHENTICATED_SESSION_CONTEXT_KEY] = {
13
+ userId: claims.sub,
14
+ refreshSessionId: claims.refreshSessionId
15
+ };
16
+ }
17
+ function getAuthenticatedSessionTokenContext(request) {
18
+ return request[AUTHENTICATED_SESSION_CONTEXT_KEY] ?? null;
19
+ }
20
+ /**
21
+ * Resolves the active refresh session backing an authenticated user request.
22
+ * The session check intentionally happens only for consumers that need browser-session binding.
23
+ */ export async function getAuthenticatedSessionContext(request) {
24
+ const client = getClientContext(request);
25
+ const token = getAuthenticatedSessionTokenContext(request);
26
+ if (!client || !token || token.userId !== client.userId || !token.refreshSessionId) throw new ClientError('Invalid or expired token', 'unauthorized', {
27
+ status: 401
28
+ });
29
+ const session = await findActiveRefreshSession({
30
+ sessionId: token.refreshSessionId,
31
+ userId: client.userId
32
+ });
33
+ if (!session) throw new ClientError('Invalid or expired token', 'unauthorized', {
34
+ status: 401
35
+ });
36
+ return {
37
+ userId: client.userId,
38
+ refreshSessionId: session.sessionId
39
+ };
40
+ }
8
41
  export function createJwtAuthMethod() {
9
42
  return createBearerTokenAuthMethod({
10
43
  name: AUTH_USER,
@@ -24,6 +57,7 @@ export function createJwtAuthMethod() {
24
57
  memberships: claims.memberships
25
58
  });
26
59
  setUserContext(request, clientContext);
60
+ setAuthenticatedSessionContext(request, claims);
27
61
  }
28
62
  });
29
63
  }
@@ -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} from '@shipfox/node-fastify';\nimport type {FastifyRequest} from 'fastify';\nimport {verifyUserToken} from '#core/jwt.js';\nimport {createBearerTokenAuthMethod} from './bearer-token-auth.js';\n\nexport type ClientContext = UserContext;\n\nexport interface CreateJwtAuthMethodOptions {\n secret: string;\n}\n\nexport function getClientContext(request: FastifyRequest): ClientContext | null {\n return getUserContext(request);\n}\n\nexport function createJwtAuthMethod(): AuthMethod {\n return createBearerTokenAuthMethod({\n name: AUTH_USER,\n verifyToken: (token) => verifyUserToken({token, secret: userAccessTokenKey()}),\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 },\n });\n}\n"],"names":["AUTH_USER","buildUserContext","getUserContext","setUserContext","userAccessTokenKey","verifyUserToken","createBearerTokenAuthMethod","getClientContext","request","createJwtAuthMethod","name","verifyToken","token","secret","invalidTokenError","message","code","setContext","claims","clientContext","userId","sub","email","memberships"],"mappings":"AAAA,SACEA,SAAS,EACTC,gBAAgB,EAChBC,cAAc,EACdC,cAAc,QAET,4BAA4B;AACnC,SAAQC,kBAAkB,QAAO,8BAA8B;AAG/D,SAAQC,eAAe,QAAO,eAAe;AAC7C,SAAQC,2BAA2B,QAAO,yBAAyB;AAQnE,OAAO,SAASC,iBAAiBC,OAAuB;IACtD,OAAON,eAAeM;AACxB;AAEA,OAAO,SAASC;IACd,OAAOH,4BAA4B;QACjCI,MAAMV;QACNW,aAAa,CAACC,QAAUP,gBAAgB;gBAACO;gBAAOC,QAAQT;YAAoB;QAC5EU,mBAAmB;YAACC,SAAS;YAA4BC,MAAM;QAAc;QAC7EC,YAAY,CAACT,SAASU;YACpB,MAAMC,gBAA+BlB,iBAAiB;gBACpDmB,QAAQF,OAAOG,GAAG;gBAClBC,OAAOJ,OAAOI,KAAK;gBACnBZ,MAAMQ,OAAOR,IAAI,IAAI;gBACrBa,aAAaL,OAAOK,WAAW;YACjC;YACApB,eAAeK,SAASW;QAC1B;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 {findActiveRefreshSession} from '#db/refresh-tokens.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\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: (token) => verifyUserToken({token, secret: userAccessTokenKey()}),\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","createBearerTokenAuthMethod","AUTHENTICATED_SESSION_CONTEXT_KEY","Symbol","for","getClientContext","request","setAuthenticatedSessionContext","claims","userId","sub","refreshSessionId","getAuthenticatedSessionTokenContext","getAuthenticatedSessionContext","client","token","status","session","sessionId","createJwtAuthMethod","name","verifyToken","secret","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,2BAA2B,QAAO,yBAAyB;AAEnE,MAAMC,oCAAoCC,OAAOC,GAAG,CAAC;AAgBrD,OAAO,SAASC,iBAAiBC,OAAuB;IACtD,OAAOX,eAAeW;AACxB;AAEA,SAASC,+BAA+BD,OAAuB,EAAEE,MAAuB;IACrFF,OAA8C,CAACJ,kCAAkC,GAAG;QACnFO,QAAQD,OAAOE,GAAG;QAClBC,kBAAkBH,OAAOG,gBAAgB;IAC3C;AACF;AAEA,SAASC,oCAAoCN,OAAuB;IAIlE,OACE,AAAC,AAACA,OAA8C,CAACJ,kCAAkC,IAEjE;AAEtB;AAEA;;;CAGC,GACD,OAAO,eAAeW,+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,IAAIb,YAAY,4BAA4B,gBAAgB;QAACkB,QAAQ;IAAG;IAEhF,MAAMC,UAAU,MAAMjB,yBAAyB;QAC7CkB,WAAWH,MAAMJ,gBAAgB;QACjCF,QAAQK,OAAOL,MAAM;IACvB;IACA,IAAI,CAACQ,SAAS,MAAM,IAAInB,YAAY,4BAA4B,gBAAgB;QAACkB,QAAQ;IAAG;IAE5F,OAAO;QAACP,QAAQK,OAAOL,MAAM;QAAEE,kBAAkBM,QAAQC,SAAS;IAAA;AACpE;AAEA,OAAO,SAASC;IACd,OAAOlB,4BAA4B;QACjCmB,MAAM3B;QACN4B,aAAa,CAACN,QAAUhB,gBAAgB;gBAACgB;gBAAOO,QAAQzB;YAAoB;QAC5E0B,mBAAmB;YAACC,SAAS;YAA4BC,MAAM;QAAc;QAC7EC,YAAY,CAACpB,SAASE;YACpB,MAAMmB,gBAA+BjC,iBAAiB;gBACpDe,QAAQD,OAAOE,GAAG;gBAClBkB,OAAOpB,OAAOoB,KAAK;gBACnBR,MAAMZ,OAAOY,IAAI,IAAI;gBACrBS,aAAarB,OAAOqB,WAAW;YACjC;YACAjC,eAAeU,SAASqB;YACxBpB,+BAA+BD,SAASE;QAC1C;IACF;AACF"}