@mesob/auth-hono 0.5.2 → 0.5.4

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.
@@ -7,7 +7,10 @@ var normalizeAuthUser = (user) => ({
7
7
  phone: user.phone,
8
8
  image: user.image,
9
9
  emailVerified: user.emailVerified,
10
- phoneVerified: user.phoneVerified
10
+ phoneVerified: user.phoneVerified,
11
+ roles: user.roles ?? null,
12
+ roleCodes: user.roleCodes ?? null,
13
+ permissions: user.permissions ?? null
11
14
  });
12
15
  var normalizeAuthSession = (session) => session ? {
13
16
  id: session.id,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/lib/normalize-auth-response.ts"],"sourcesContent":["import type { Session, User } from '../types';\n\nexport const normalizeAuthUser = (\n user: Pick<\n User,\n | 'id'\n | 'tenantId'\n | 'fullName'\n | 'email'\n | 'phone'\n | 'image'\n | 'emailVerified'\n | 'phoneVerified'\n >,\n) => ({\n id: user.id,\n tenantId: user.tenantId,\n fullName: user.fullName,\n email: user.email,\n phone: user.phone,\n image: user.image,\n emailVerified: user.emailVerified,\n phoneVerified: user.phoneVerified,\n});\n\nexport const normalizeAuthSession = (\n session: Pick<Session, 'id' | 'expiresAt'> | null,\n) =>\n session\n ? {\n id: session.id,\n expiresAt: session.expiresAt,\n }\n : null;\n"],"mappings":";AAEO,IAAM,oBAAoB,CAC/B,UAWI;AAAA,EACJ,IAAI,KAAK;AAAA,EACT,UAAU,KAAK;AAAA,EACf,UAAU,KAAK;AAAA,EACf,OAAO,KAAK;AAAA,EACZ,OAAO,KAAK;AAAA,EACZ,OAAO,KAAK;AAAA,EACZ,eAAe,KAAK;AAAA,EACpB,eAAe,KAAK;AACtB;AAEO,IAAM,uBAAuB,CAClC,YAEA,UACI;AAAA,EACE,IAAI,QAAQ;AAAA,EACZ,WAAW,QAAQ;AACrB,IACA;","names":[]}
1
+ {"version":3,"sources":["../../src/lib/normalize-auth-response.ts"],"sourcesContent":["import type { Session, User } from '../types';\n\nexport const normalizeAuthUser = (\n user: Pick<\n User,\n | 'id'\n | 'tenantId'\n | 'fullName'\n | 'email'\n | 'phone'\n | 'image'\n | 'emailVerified'\n | 'phoneVerified'\n > &\n Pick<Partial<User>, 'roles' | 'roleCodes' | 'permissions'>,\n) => ({\n id: user.id,\n tenantId: user.tenantId,\n fullName: user.fullName,\n email: user.email,\n phone: user.phone,\n image: user.image,\n emailVerified: user.emailVerified,\n phoneVerified: user.phoneVerified,\n roles: user.roles ?? null,\n roleCodes: user.roleCodes ?? null,\n permissions: user.permissions ?? null,\n});\n\nexport const normalizeAuthSession = (\n session: Pick<Session, 'id' | 'expiresAt'> | null,\n) =>\n session\n ? {\n id: session.id,\n expiresAt: session.expiresAt,\n }\n : null;\n"],"mappings":";AAEO,IAAM,oBAAoB,CAC/B,UAYI;AAAA,EACJ,IAAI,KAAK;AAAA,EACT,UAAU,KAAK;AAAA,EACf,UAAU,KAAK;AAAA,EACf,OAAO,KAAK;AAAA,EACZ,OAAO,KAAK;AAAA,EACZ,OAAO,KAAK;AAAA,EACZ,eAAe,KAAK;AAAA,EACpB,eAAe,KAAK;AAAA,EACpB,OAAO,KAAK,SAAS;AAAA,EACrB,WAAW,KAAK,aAAa;AAAA,EAC7B,aAAa,KAAK,eAAe;AACnC;AAEO,IAAM,uBAAuB,CAClC,YAEA,UACI;AAAA,EACE,IAAI,QAAQ;AAAA,EACZ,WAAW,QAAQ;AACrB,IACA;","names":[]}
@@ -0,0 +1,5 @@
1
+ /** Stable client key for auth rate limits (IPv6 canonical + optional prefix). */
2
+ /** IPv4 full address; IPv6 masked to prefixBits (1–128), default subnet /64. */
3
+ declare function rateLimitClientKey(raw: string, ipv6SubnetBits: number): string;
4
+
5
+ export { rateLimitClientKey };
@@ -0,0 +1,159 @@
1
+ // src/lib/normalize-rate-limit-ip.ts
2
+ function parseIpv4Dotted(s) {
3
+ const p = s.split(".");
4
+ if (p.length !== 4) {
5
+ return null;
6
+ }
7
+ const b = new Uint8Array(4);
8
+ for (let i = 0; i < 4; i++) {
9
+ if (!/^\d{1,3}$/.test(p[i])) {
10
+ return null;
11
+ }
12
+ const n = Number(p[i]);
13
+ if (!Number.isInteger(n) || n < 0 || n > 255) {
14
+ return null;
15
+ }
16
+ b[i] = n;
17
+ }
18
+ return b;
19
+ }
20
+ function ipv4Key(bytes) {
21
+ return `4:${bytes[0]}.${bytes[1]}.${bytes[2]}.${bytes[3]}`;
22
+ }
23
+ function maskIpv6InPlace(bytes, prefixBits) {
24
+ if (prefixBits >= 128) {
25
+ return;
26
+ }
27
+ const whole = Math.floor(prefixBits / 8);
28
+ const rem = prefixBits % 8;
29
+ if (rem === 0) {
30
+ for (let i = whole; i < 16; i++) {
31
+ bytes[i] = 0;
32
+ }
33
+ } else {
34
+ for (let i = whole + 1; i < 16; i++) {
35
+ bytes[i] = 0;
36
+ }
37
+ const keep = 255 << 8 - rem & 255;
38
+ bytes[whole] &= keep;
39
+ }
40
+ }
41
+ function isIpv4MappedIpv6(bytes) {
42
+ for (let i = 0; i < 10; i++) {
43
+ if (bytes[i] !== 0) {
44
+ return false;
45
+ }
46
+ }
47
+ return bytes[10] === 255 && bytes[11] === 255;
48
+ }
49
+ function parseIpv6HextetsToBytes(s) {
50
+ const buf = new Uint8Array(16);
51
+ if (s === "") {
52
+ return buf;
53
+ }
54
+ const double = s.includes("::");
55
+ if (double && s.split("::").length > 2) {
56
+ return null;
57
+ }
58
+ const [leftRaw, rightRaw] = double ? s.split("::", 2) : [s, ""];
59
+ const left = leftRaw ? leftRaw.split(":").filter(Boolean) : [];
60
+ const right = rightRaw ? rightRaw.split(":").filter(Boolean) : [];
61
+ const partsLen = left.length + right.length;
62
+ if (double) {
63
+ if (partsLen > 8) {
64
+ return null;
65
+ }
66
+ } else if (partsLen !== 8) {
67
+ return null;
68
+ }
69
+ const pad = double ? 8 - partsLen : 0;
70
+ const all = [...left, ...Array(pad).fill("0"), ...right];
71
+ if (all.length !== 8) {
72
+ return null;
73
+ }
74
+ for (let i = 0; i < 8; i++) {
75
+ const h = all[i];
76
+ if (!h || h.includes(".")) {
77
+ return null;
78
+ }
79
+ const v = Number.parseInt(h, 16);
80
+ if (v > 65535 || Number.isNaN(v)) {
81
+ return null;
82
+ }
83
+ buf[i * 2] = v >> 8;
84
+ buf[i * 2 + 1] = v & 255;
85
+ }
86
+ return buf;
87
+ }
88
+ function parseIpv6ToBytes(address) {
89
+ let a = address.trim().toLowerCase();
90
+ if (a.startsWith("[") && a.endsWith("]")) {
91
+ a = a.slice(1, -1);
92
+ }
93
+ const zi = a.indexOf("%");
94
+ if (zi >= 0) {
95
+ a = a.slice(0, zi);
96
+ }
97
+ if (a === "") {
98
+ return null;
99
+ }
100
+ if (!a.includes(":")) {
101
+ const v4 = parseIpv4Dotted(a);
102
+ return v4 ? v4 : null;
103
+ }
104
+ const lastColon = a.lastIndexOf(":");
105
+ if (lastColon > 0) {
106
+ const maybeV4 = a.slice(lastColon + 1);
107
+ if (/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(maybeV4)) {
108
+ const v4b = parseIpv4Dotted(maybeV4);
109
+ if (!v4b) {
110
+ return null;
111
+ }
112
+ const prefix = a.slice(0, lastColon);
113
+ const w1 = v4b[0] << 8 | v4b[1];
114
+ const w2 = v4b[2] << 8 | v4b[3];
115
+ const tailHex = `${w1.toString(16)}:${w2.toString(16)}`;
116
+ let merged;
117
+ if (!prefix) {
118
+ merged = tailHex;
119
+ } else if (prefix.endsWith(":")) {
120
+ merged = `${prefix}${tailHex}`;
121
+ } else {
122
+ merged = `${prefix}:${tailHex}`;
123
+ }
124
+ return parseIpv6HextetsToBytes(merged);
125
+ }
126
+ }
127
+ return parseIpv6HextetsToBytes(a);
128
+ }
129
+ function bytesToHex(bytes) {
130
+ let o = "";
131
+ for (let i = 0; i < bytes.length; i++) {
132
+ o += bytes[i].toString(16).padStart(2, "0");
133
+ }
134
+ return o;
135
+ }
136
+ function rateLimitClientKey(raw, ipv6SubnetBits) {
137
+ const s = raw.trim();
138
+ if (!s || s === "unknown") {
139
+ return "unknown";
140
+ }
141
+ const prefixBits = Number.isFinite(ipv6SubnetBits) && ipv6SubnetBits >= 1 && ipv6SubnetBits <= 128 ? Math.floor(ipv6SubnetBits) : 64;
142
+ if (!s.includes(":")) {
143
+ const v4 = parseIpv4Dotted(s);
144
+ return v4 ? ipv4Key(v4) : `raw:${s}`;
145
+ }
146
+ const bytes = parseIpv6ToBytes(s);
147
+ if (!bytes) {
148
+ return `raw:${s}`;
149
+ }
150
+ if (isIpv4MappedIpv6(bytes)) {
151
+ return ipv4Key(bytes.subarray(12, 16));
152
+ }
153
+ maskIpv6InPlace(bytes, prefixBits);
154
+ return `6:${bytesToHex(bytes)}`;
155
+ }
156
+ export {
157
+ rateLimitClientKey
158
+ };
159
+ //# sourceMappingURL=normalize-rate-limit-ip.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/lib/normalize-rate-limit-ip.ts"],"sourcesContent":["/** Stable client key for auth rate limits (IPv6 canonical + optional prefix). */\n\nfunction parseIpv4Dotted(s: string): Uint8Array | null {\n const p = s.split('.');\n if (p.length !== 4) {\n return null;\n }\n const b = new Uint8Array(4);\n for (let i = 0; i < 4; i++) {\n if (!/^\\d{1,3}$/.test(p[i])) {\n return null;\n }\n const n = Number(p[i]);\n if (!Number.isInteger(n) || n < 0 || n > 255) {\n return null;\n }\n b[i] = n;\n }\n return b;\n}\n\nfunction ipv4Key(bytes: Uint8Array): string {\n return `4:${bytes[0]}.${bytes[1]}.${bytes[2]}.${bytes[3]}`;\n}\n\nfunction maskIpv6InPlace(bytes: Uint8Array, prefixBits: number): void {\n if (prefixBits >= 128) {\n return;\n }\n const whole = Math.floor(prefixBits / 8);\n const rem = prefixBits % 8;\n if (rem === 0) {\n for (let i = whole; i < 16; i++) {\n bytes[i] = 0;\n }\n } else {\n for (let i = whole + 1; i < 16; i++) {\n bytes[i] = 0;\n }\n const keep = (0xff << (8 - rem)) & 0xff;\n bytes[whole] &= keep;\n }\n}\n\nfunction isIpv4MappedIpv6(bytes: Uint8Array): boolean {\n for (let i = 0; i < 10; i++) {\n if (bytes[i] !== 0) {\n return false;\n }\n }\n return bytes[10] === 0xff && bytes[11] === 0xff;\n}\n\nfunction parseIpv6HextetsToBytes(s: string): Uint8Array | null {\n const buf = new Uint8Array(16);\n if (s === '') {\n return buf;\n }\n const double = s.includes('::');\n if (double && s.split('::').length > 2) {\n return null;\n }\n const [leftRaw, rightRaw] = double ? s.split('::', 2) : [s, ''];\n const left = leftRaw ? leftRaw.split(':').filter(Boolean) : [];\n const right = rightRaw ? rightRaw.split(':').filter(Boolean) : [];\n const partsLen = left.length + right.length;\n if (double) {\n if (partsLen > 8) {\n return null;\n }\n } else if (partsLen !== 8) {\n return null;\n }\n const pad = double ? 8 - partsLen : 0;\n const all = [...left, ...Array(pad).fill('0'), ...right];\n if (all.length !== 8) {\n return null;\n }\n for (let i = 0; i < 8; i++) {\n const h = all[i];\n if (!h || h.includes('.')) {\n return null;\n }\n const v = Number.parseInt(h, 16);\n if (v > 0xffff || Number.isNaN(v)) {\n return null;\n }\n buf[i * 2] = v >> 8;\n buf[i * 2 + 1] = v & 0xff;\n }\n return buf;\n}\n\nfunction parseIpv6ToBytes(address: string): Uint8Array | null {\n let a = address.trim().toLowerCase();\n if (a.startsWith('[') && a.endsWith(']')) {\n a = a.slice(1, -1);\n }\n const zi = a.indexOf('%');\n if (zi >= 0) {\n a = a.slice(0, zi);\n }\n if (a === '') {\n return null;\n }\n\n if (!a.includes(':')) {\n const v4 = parseIpv4Dotted(a);\n return v4 ? v4 : null;\n }\n\n const lastColon = a.lastIndexOf(':');\n if (lastColon > 0) {\n const maybeV4 = a.slice(lastColon + 1);\n if (/^\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}$/.test(maybeV4)) {\n const v4b = parseIpv4Dotted(maybeV4);\n if (!v4b) {\n return null;\n }\n const prefix = a.slice(0, lastColon);\n const w1 = (v4b[0] << 8) | v4b[1];\n const w2 = (v4b[2] << 8) | v4b[3];\n const tailHex = `${w1.toString(16)}:${w2.toString(16)}`;\n let merged: string;\n if (!prefix) {\n merged = tailHex;\n } else if (prefix.endsWith(':')) {\n merged = `${prefix}${tailHex}`;\n } else {\n merged = `${prefix}:${tailHex}`;\n }\n return parseIpv6HextetsToBytes(merged);\n }\n }\n\n return parseIpv6HextetsToBytes(a);\n}\n\nfunction bytesToHex(bytes: Uint8Array): string {\n let o = '';\n for (let i = 0; i < bytes.length; i++) {\n o += bytes[i].toString(16).padStart(2, '0');\n }\n return o;\n}\n\n/** IPv4 full address; IPv6 masked to prefixBits (1–128), default subnet /64. */\nexport function rateLimitClientKey(\n raw: string,\n ipv6SubnetBits: number,\n): string {\n const s = raw.trim();\n if (!s || s === 'unknown') {\n return 'unknown';\n }\n\n const prefixBits =\n Number.isFinite(ipv6SubnetBits) &&\n ipv6SubnetBits >= 1 &&\n ipv6SubnetBits <= 128\n ? Math.floor(ipv6SubnetBits)\n : 64;\n\n if (!s.includes(':')) {\n const v4 = parseIpv4Dotted(s);\n return v4 ? ipv4Key(v4) : `raw:${s}`;\n }\n\n const bytes = parseIpv6ToBytes(s);\n if (!bytes) {\n return `raw:${s}`;\n }\n\n if (isIpv4MappedIpv6(bytes)) {\n return ipv4Key(bytes.subarray(12, 16));\n }\n\n maskIpv6InPlace(bytes, prefixBits);\n return `6:${bytesToHex(bytes)}`;\n}\n"],"mappings":"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","names":[]}
@@ -1,4 +1,4 @@
1
- import { U as User } from '../index-CDgzxZzO.js';
1
+ import { U as User } from '../index-v_uxUd8A.js';
2
2
  import 'hono';
3
3
  import '@hono/zod-openapi';
4
4
  import '@mesob/common';
@@ -1,4 +1,4 @@
1
- import { A as AuthConfig } from '../index-CDgzxZzO.js';
1
+ import { A as AuthConfig } from '../index-v_uxUd8A.js';
2
2
  import 'hono';
3
3
  import '@hono/zod-openapi';
4
4
  import '@mesob/common';
@@ -1,4 +1,4 @@
1
- import { A as AuthConfig } from '../index-CDgzxZzO.js';
1
+ import { A as AuthConfig } from '../index-v_uxUd8A.js';
2
2
  import 'hono';
3
3
  import '@hono/zod-openapi';
4
4
  import '@mesob/common';
@@ -0,0 +1,22 @@
1
+ import { D as Database } from '../index-Dhe5obDc.js';
2
+ import { A as AuthConfig, f as Session, U as User } from '../index-v_uxUd8A.js';
3
+ import 'drizzle-orm/node-postgres';
4
+ import 'drizzle-orm';
5
+ import 'drizzle-orm/pg-core';
6
+ import 'pg';
7
+ import 'hono';
8
+ import '@hono/zod-openapi';
9
+ import '@mesob/common';
10
+
11
+ type SessionCachePayload = {
12
+ session: Session;
13
+ user: User;
14
+ };
15
+ declare const sessionCacheKey: (config: AuthConfig, hashedToken: string) => string;
16
+ declare function readSessionCache(config: AuthConfig, hashedToken: string): Promise<SessionCachePayload | null>;
17
+ declare function writeSessionCache(config: AuthConfig, hashedToken: string, payload: SessionCachePayload): Promise<void>;
18
+ declare function deleteSessionCacheKeys(config: AuthConfig, hashedTokens: string[]): Promise<void>;
19
+ declare function invalidateSessionCacheForHashedToken(config: AuthConfig, hashedToken: string): Promise<void>;
20
+ declare function invalidateSessionCacheForUser(config: AuthConfig, database: Database, userId: string, tenantId: string): Promise<void>;
21
+
22
+ export { type SessionCachePayload, deleteSessionCacheKeys, invalidateSessionCacheForHashedToken, invalidateSessionCacheForUser, readSessionCache, sessionCacheKey, writeSessionCache };
@@ -0,0 +1,396 @@
1
+ // src/db/orm/session.ts
2
+ import { and, eq, gt } from "drizzle-orm";
3
+
4
+ // src/db/schema.ts
5
+ import { pgSchema, index, foreignKey, pgPolicy, check, uuid, varchar, timestamp, text, smallint, unique, inet, jsonb, boolean, uniqueIndex } from "drizzle-orm/pg-core";
6
+ import { sql } from "drizzle-orm";
7
+ var iam = pgSchema("iam");
8
+ var verificationsInIam = iam.table("verifications", {
9
+ id: uuid().default(sql`uuid_generate_v7()`).primaryKey().notNull(),
10
+ tenantId: varchar("tenant_id", { length: 30 }).notNull(),
11
+ createdAt: timestamp("created_at", { withTimezone: true, mode: "string" }).default(sql`CURRENT_TIMESTAMP`).notNull(),
12
+ updatedAt: timestamp("updated_at", { withTimezone: true, mode: "string" }).default(sql`CURRENT_TIMESTAMP`).notNull(),
13
+ userId: uuid("user_id").notNull(),
14
+ code: text().notNull(),
15
+ expiresAt: timestamp("expires_at", { withTimezone: true, mode: "string" }).notNull(),
16
+ type: text(),
17
+ attempt: smallint().default(0),
18
+ to: text()
19
+ }, (table) => [
20
+ index("verifications_expires_at_idx").using("btree", table.expiresAt.asc().nullsLast().op("timestamptz_ops")),
21
+ index("verifications_lookup_idx").using("btree", table.tenantId.asc().nullsLast().op("text_ops"), table.userId.asc().nullsLast().op("text_ops"), table.type.asc().nullsLast().op("uuid_ops"), table.to.asc().nullsLast().op("text_ops"), table.code.asc().nullsLast().op("text_ops")),
22
+ foreignKey({
23
+ columns: [table.tenantId],
24
+ foreignColumns: [tenantsInIam.id],
25
+ name: "verifications_tenant_id_fkey"
26
+ }).onUpdate("cascade").onDelete("cascade"),
27
+ foreignKey({
28
+ columns: [table.userId],
29
+ foreignColumns: [usersInIam.id],
30
+ name: "verifications_user_id_fkey"
31
+ }).onUpdate("cascade").onDelete("cascade"),
32
+ pgPolicy("tenant_isolation", { as: "permissive", for: "all", to: ["public"], using: sql`((tenant_id)::text = (iam.current_tenant_id())::text)`, withCheck: sql`((tenant_id)::text = (iam.current_tenant_id())::text)` }),
33
+ check("verifications_attempt_nonnegative_check", sql`attempt >= 0`),
34
+ check("verifications_expires_after_created_check", sql`expires_at > created_at`)
35
+ ]);
36
+ var sessionsInIam = iam.table("sessions", {
37
+ id: uuid().default(sql`uuid_generate_v7()`).primaryKey().notNull(),
38
+ tenantId: varchar("tenant_id", { length: 30 }).notNull(),
39
+ createdAt: timestamp("created_at", { withTimezone: true, mode: "string" }).default(sql`CURRENT_TIMESTAMP`).notNull(),
40
+ updatedAt: timestamp("updated_at", { withTimezone: true, mode: "string" }).default(sql`CURRENT_TIMESTAMP`).notNull(),
41
+ userId: uuid("user_id").notNull(),
42
+ expiresAt: timestamp("expires_at", { withTimezone: true, mode: "string" }).notNull(),
43
+ userAgent: text("user_agent"),
44
+ ip: inet(),
45
+ meta: jsonb(),
46
+ token: text().notNull(),
47
+ rotatedAt: timestamp("rotated_at", { withTimezone: true, mode: "string" }).default(sql`CURRENT_TIMESTAMP`)
48
+ }, (table) => [
49
+ index("sessions_expires_at_idx").using("btree", table.expiresAt.asc().nullsLast().op("timestamptz_ops")),
50
+ index("sessions_tenant_user_idx").using("btree", table.tenantId.asc().nullsLast().op("uuid_ops"), table.userId.asc().nullsLast().op("text_ops")),
51
+ foreignKey({
52
+ columns: [table.tenantId],
53
+ foreignColumns: [tenantsInIam.id],
54
+ name: "sessions_tenant_id_fkey"
55
+ }).onUpdate("cascade").onDelete("cascade"),
56
+ foreignKey({
57
+ columns: [table.userId],
58
+ foreignColumns: [usersInIam.id],
59
+ name: "sessions_user_id_fkey"
60
+ }).onUpdate("cascade").onDelete("cascade"),
61
+ unique("sessions_token_key").on(table.token),
62
+ pgPolicy("tenant_isolation", { as: "permissive", for: "all", to: ["public"], using: sql`((tenant_id)::text = (iam.current_tenant_id())::text)`, withCheck: sql`((tenant_id)::text = (iam.current_tenant_id())::text)` }),
63
+ check("sessions_expires_after_created_check", sql`expires_at > created_at`)
64
+ ]);
65
+ var accountChangesInIam = iam.table("account_changes", {
66
+ id: uuid().default(sql`uuid_generate_v7()`).primaryKey().notNull(),
67
+ tenantId: varchar("tenant_id", { length: 30 }).notNull(),
68
+ userId: uuid("user_id").notNull(),
69
+ createdAt: timestamp("created_at", { withTimezone: true, mode: "string" }).default(sql`CURRENT_TIMESTAMP`).notNull(),
70
+ updatedAt: timestamp("updated_at", { withTimezone: true, mode: "string" }).default(sql`CURRENT_TIMESTAMP`).notNull(),
71
+ changeType: text("change_type").notNull(),
72
+ oldEmail: varchar("old_email"),
73
+ newEmail: varchar("new_email"),
74
+ oldPhone: text("old_phone"),
75
+ newPhone: text("new_phone"),
76
+ status: varchar().notNull(),
77
+ expiresAt: timestamp("expires_at", { withTimezone: true, mode: "string" }).notNull(),
78
+ confirmedAt: timestamp("confirmed_at", { withTimezone: true, mode: "string" }),
79
+ cancelledAt: timestamp("cancelled_at", { withTimezone: true, mode: "string" }),
80
+ reason: text()
81
+ }, (table) => [
82
+ index("account_changes_expires_at_idx").using("btree", table.expiresAt.asc().nullsLast().op("timestamptz_ops")),
83
+ index("account_changes_tenant_user_status_idx").using("btree", table.tenantId.asc().nullsLast().op("uuid_ops"), table.userId.asc().nullsLast().op("text_ops"), table.status.asc().nullsLast().op("uuid_ops")),
84
+ index("idx_account_changes_expired").using("btree", table.expiresAt.asc().nullsLast().op("text_ops"), table.status.asc().nullsLast().op("text_ops")).where(sql`((status)::text = 'pending'::text)`),
85
+ foreignKey({
86
+ columns: [table.tenantId],
87
+ foreignColumns: [tenantsInIam.id],
88
+ name: "account_changes_tenant_id_fkey"
89
+ }).onUpdate("cascade").onDelete("cascade"),
90
+ foreignKey({
91
+ columns: [table.userId],
92
+ foreignColumns: [usersInIam.id],
93
+ name: "account_changes_user_id_fkey"
94
+ }).onUpdate("cascade").onDelete("cascade"),
95
+ pgPolicy("tenant_isolation", { as: "permissive", for: "all", to: ["public"], using: sql`((tenant_id)::text = (iam.current_tenant_id())::text)`, withCheck: sql`((tenant_id)::text = (iam.current_tenant_id())::text)` }),
96
+ check("account_changes_expires_after_created_check", sql`expires_at > created_at`),
97
+ check("account_changes_change_type_check", sql`((change_type = 'EMAIL'::text) AND (old_email IS NOT NULL) AND (new_email IS NOT NULL) AND (old_phone IS NULL) AND (new_phone IS NULL)) OR ((change_type = 'PHONE'::text) AND (old_phone IS NOT NULL) AND (new_phone IS NOT NULL) AND (old_email IS NULL) AND (new_email IS NULL))`),
98
+ check("account_changes_status_check", sql`(status)::text = ANY (ARRAY[('PENDING'::character varying)::text, ('APPLIED'::character varying)::text, ('CANCELLED'::character varying)::text, ('EXPIRED'::character varying)::text])`)
99
+ ]);
100
+ var tenantsInIam = iam.table("tenants", {
101
+ id: varchar({ length: 30 }).primaryKey().notNull(),
102
+ createdAt: timestamp("created_at", { withTimezone: true, mode: "string" }).default(sql`CURRENT_TIMESTAMP`).notNull(),
103
+ updatedAt: timestamp("updated_at", { withTimezone: true, mode: "string" }).default(sql`CURRENT_TIMESTAMP`).notNull(),
104
+ name: jsonb().notNull(),
105
+ description: jsonb(),
106
+ theme: jsonb(),
107
+ supportedLanguages: jsonb("supported_languages"),
108
+ defaultLanguage: text("default_language"),
109
+ supportedCurrency: jsonb("supported_currency"),
110
+ defaultCurrency: text("default_currency"),
111
+ timezone: text(),
112
+ isActive: boolean("is_active").default(true).notNull(),
113
+ locale: jsonb(),
114
+ settings: jsonb(),
115
+ seo: jsonb()
116
+ }, (table) => [
117
+ index("tenants_is_active_idx").using("btree", table.isActive.asc().nullsLast().op("bool_ops"))
118
+ ]);
119
+ var rolePermissionsInIam = iam.table("role_permissions", {
120
+ id: uuid().default(sql`uuid_generate_v7()`).primaryKey().notNull(),
121
+ tenantId: varchar("tenant_id", { length: 30 }).notNull(),
122
+ permissionId: text("permission_id").notNull(),
123
+ roleId: uuid("role_id").notNull()
124
+ }, (table) => [
125
+ index("idx_role_permissions_permission_id").using("btree", table.tenantId.asc().nullsLast().op("text_ops"), table.permissionId.asc().nullsLast().op("text_ops")),
126
+ foreignKey({
127
+ columns: [table.tenantId],
128
+ foreignColumns: [tenantsInIam.id],
129
+ name: "role_permissions_tenant_id_fkey"
130
+ }).onUpdate("cascade").onDelete("cascade"),
131
+ foreignKey({
132
+ columns: [table.permissionId],
133
+ foreignColumns: [permissionsInIam.id],
134
+ name: "role_permissions_permission_id_fkey"
135
+ }).onUpdate("cascade").onDelete("cascade"),
136
+ foreignKey({
137
+ columns: [table.tenantId, table.roleId],
138
+ foreignColumns: [rolesInIam.tenantId, rolesInIam.id],
139
+ name: "role_permissions_tenant_role_fkey"
140
+ }).onDelete("cascade"),
141
+ unique("role_permissions_tenant_role_permission_unique").on(table.tenantId, table.permissionId, table.roleId),
142
+ pgPolicy("tenant_isolation", { as: "permissive", for: "all", to: ["public"], using: sql`((tenant_id)::text = (iam.current_tenant_id())::text)`, withCheck: sql`((tenant_id)::text = (iam.current_tenant_id())::text)` })
143
+ ]);
144
+ var permissionsInIam = iam.table("permissions", {
145
+ id: text().primaryKey().notNull(),
146
+ description: jsonb().notNull(),
147
+ activity: text().notNull(),
148
+ application: text().notNull(),
149
+ feature: text().notNull()
150
+ }, (table) => [
151
+ unique("permissions_activity_application_feature_key").on(table.activity, table.application, table.feature)
152
+ ]);
153
+ var accountsInIam = iam.table("accounts", {
154
+ id: uuid().default(sql`uuid_generate_v7()`).primaryKey().notNull(),
155
+ tenantId: varchar("tenant_id", { length: 30 }).notNull(),
156
+ userId: uuid("user_id").notNull(),
157
+ provider: text().notNull(),
158
+ providerAccountId: text("provider_account_id").notNull(),
159
+ password: text(),
160
+ passwordLastChangedAt: timestamp("password_last_changed_at", { withTimezone: true, mode: "string" }),
161
+ idToken: text("id_token"),
162
+ accessToken: text("access_token"),
163
+ accessTokenExpiresAt: timestamp("access_token_expires_at", { withTimezone: true, mode: "string" }),
164
+ refreshToken: text("refresh_token"),
165
+ refreshTokenExpiresAt: timestamp("refresh_token_expires_at", { withTimezone: true, mode: "string" }),
166
+ scope: text(),
167
+ expiresAt: timestamp("expires_at", { withTimezone: true, mode: "string" }),
168
+ meta: jsonb()
169
+ }, (table) => [
170
+ index("idx_accounts_provider_lookup").using("btree", table.tenantId.asc().nullsLast().op("text_ops"), table.provider.asc().nullsLast().op("text_ops"), table.providerAccountId.asc().nullsLast().op("text_ops")),
171
+ index("idx_accounts_user_id").using("btree", table.tenantId.asc().nullsLast().op("text_ops"), table.userId.asc().nullsLast().op("text_ops")),
172
+ foreignKey({
173
+ columns: [table.tenantId],
174
+ foreignColumns: [tenantsInIam.id],
175
+ name: "accounts_tenant_id_fkey"
176
+ }).onUpdate("cascade").onDelete("cascade"),
177
+ foreignKey({
178
+ columns: [table.userId],
179
+ foreignColumns: [usersInIam.id],
180
+ name: "accounts_user_id_fkey"
181
+ }).onUpdate("cascade").onDelete("cascade"),
182
+ unique("accounts_tenant_provider_account_unique").on(table.tenantId, table.provider, table.providerAccountId),
183
+ pgPolicy("tenant_isolation", { as: "permissive", for: "all", to: ["public"], using: sql`((tenant_id)::text = (iam.current_tenant_id())::text)`, withCheck: sql`((tenant_id)::text = (iam.current_tenant_id())::text)` })
184
+ ]);
185
+ var usersInIam = iam.table("users", {
186
+ id: uuid().default(sql`uuid_generate_v7()`).primaryKey().notNull(),
187
+ tenantId: varchar("tenant_id", { length: 30 }).notNull(),
188
+ createdAt: timestamp("created_at", { withTimezone: true, mode: "string" }).default(sql`CURRENT_TIMESTAMP`).notNull(),
189
+ updatedAt: timestamp("updated_at", { withTimezone: true, mode: "string" }).default(sql`CURRENT_TIMESTAMP`).notNull(),
190
+ fullName: text("full_name").notNull(),
191
+ image: text(),
192
+ phone: text(),
193
+ email: text(),
194
+ handle: text().notNull(),
195
+ emailVerified: boolean("email_verified").default(false).notNull(),
196
+ phoneVerified: boolean("phone_verified").default(false).notNull(),
197
+ bannedUntil: timestamp("banned_until", { withTimezone: true, mode: "string" }),
198
+ lastSignInAt: timestamp("last_sign_in_at", { withTimezone: true, mode: "string" }),
199
+ loginAttempt: smallint("login_attempt").default(0).notNull(),
200
+ userType: text("user_type").array().default(["RAY"]).notNull()
201
+ }, (table) => [
202
+ index("idx_users_auth_lookup").using("btree", table.tenantId.asc().nullsLast().op("bool_ops"), table.email.asc().nullsLast().op("bool_ops"), table.id.asc().nullsLast().op("timestamptz_ops"), table.emailVerified.asc().nullsLast().op("timestamptz_ops"), table.bannedUntil.asc().nullsLast().op("uuid_ops")).where(sql`(email IS NOT NULL)`),
203
+ index("idx_users_email_lookup").using("btree", table.tenantId.asc().nullsLast().op("text_ops"), table.email.asc().nullsLast().op("text_ops")).where(sql`(email IS NOT NULL)`),
204
+ index("idx_users_handle_lookup").using("btree", table.tenantId.asc().nullsLast().op("text_ops"), table.handle.asc().nullsLast().op("text_ops")),
205
+ index("idx_users_phone_lookup").using("btree", table.tenantId.asc().nullsLast().op("text_ops"), table.phone.asc().nullsLast().op("text_ops")).where(sql`(phone IS NOT NULL)`),
206
+ index("idx_users_tenant_email_unique").using("btree", table.tenantId.asc().nullsLast().op("text_ops"), table.email.asc().nullsLast().op("text_ops")).where(sql`(email IS NOT NULL)`),
207
+ index("idx_users_tenant_is_admin").using("btree", table.tenantId.asc().nullsLast().op("text_ops")).where(sql`(user_type @> ARRAY['admin'::text])`),
208
+ index("idx_users_tenant_is_candidate").using("btree", table.tenantId.asc().nullsLast().op("text_ops")).where(sql`(user_type @> ARRAY['candidate'::text])`),
209
+ index("idx_users_tenant_is_employee").using("btree", table.tenantId.asc().nullsLast().op("text_ops")).where(sql`(user_type @> ARRAY['employee'::text])`),
210
+ index("idx_users_user_types_gin").using("gin", table.userType.asc().nullsLast().op("array_ops")),
211
+ uniqueIndex("users_tenant_lower_email_idx").using("btree", sql`tenant_id`, sql`lower(email)`),
212
+ uniqueIndex("users_tenant_lower_handle_idx").using("btree", sql`tenant_id`, sql`lower(handle)`),
213
+ foreignKey({
214
+ columns: [table.tenantId],
215
+ foreignColumns: [tenantsInIam.id],
216
+ name: "users_tenant_id_fkey"
217
+ }).onUpdate("cascade").onDelete("cascade"),
218
+ unique("users_tenant_phone_key").on(table.tenantId, table.phone),
219
+ pgPolicy("tenant_isolation", { as: "permissive", for: "all", to: ["public"], using: sql`((tenant_id)::text = (iam.current_tenant_id())::text)`, withCheck: sql`((tenant_id)::text = (iam.current_tenant_id())::text)` }),
220
+ check("users_login_attempt_nonnegative_check", sql`login_attempt >= 0`),
221
+ check("users_contact_required_check", sql`(email IS NOT NULL) OR (phone IS NOT NULL)`),
222
+ check("users_user_type_check", sql`user_type <@ ARRAY['candidate'::text, 'employee'::text, 'admin'::text]`)
223
+ ]);
224
+ var rolesInIam = iam.table("roles", {
225
+ tenantId: varchar("tenant_id", { length: 30 }).notNull(),
226
+ createdAt: timestamp("created_at", { withTimezone: true, mode: "string" }).default(sql`CURRENT_TIMESTAMP`).notNull(),
227
+ updatedAt: timestamp("updated_at", { withTimezone: true, mode: "string" }).default(sql`CURRENT_TIMESTAMP`).notNull(),
228
+ name: jsonb().notNull(),
229
+ description: jsonb().notNull(),
230
+ code: text().notNull(),
231
+ id: uuid().default(sql`uuid_generate_v7()`).primaryKey().notNull(),
232
+ isSystem: boolean("is_system").default(false).notNull(),
233
+ isEditable: boolean("is_editable").default(true).notNull(),
234
+ isDeletable: boolean("is_deletable").default(true).notNull()
235
+ }, (table) => [
236
+ foreignKey({
237
+ columns: [table.tenantId],
238
+ foreignColumns: [tenantsInIam.id],
239
+ name: "roles_tenant_id_fkey"
240
+ }).onUpdate("cascade").onDelete("cascade"),
241
+ unique("roles_tenant_code_unique").on(table.tenantId, table.code),
242
+ unique("roles_tenant_id_unique").on(table.tenantId, table.id),
243
+ pgPolicy("tenant_isolation", { as: "permissive", for: "all", to: ["public"], using: sql`((tenant_id)::text = (iam.current_tenant_id())::text)`, withCheck: sql`((tenant_id)::text = (iam.current_tenant_id())::text)` })
244
+ ]);
245
+ var userRolesInIam = iam.table("user_roles", {
246
+ id: uuid().default(sql`uuid_generate_v7()`).primaryKey().notNull(),
247
+ tenantId: varchar("tenant_id", { length: 30 }).notNull(),
248
+ userId: uuid("user_id").notNull(),
249
+ roleId: uuid("role_id").notNull()
250
+ }, (table) => [
251
+ index("idx_user_roles_tenant_user").using("btree", table.tenantId.asc().nullsLast().op("text_ops"), table.userId.asc().nullsLast().op("uuid_ops")),
252
+ foreignKey({
253
+ columns: [table.tenantId],
254
+ foreignColumns: [tenantsInIam.id],
255
+ name: "user_roles_tenant_id_fkey"
256
+ }).onUpdate("cascade").onDelete("cascade"),
257
+ foreignKey({
258
+ columns: [table.userId],
259
+ foreignColumns: [usersInIam.id],
260
+ name: "user_roles_user_id_fkey"
261
+ }).onUpdate("cascade").onDelete("cascade"),
262
+ foreignKey({
263
+ columns: [table.tenantId, table.roleId],
264
+ foreignColumns: [rolesInIam.tenantId, rolesInIam.id],
265
+ name: "user_roles_tenant_role_fkey"
266
+ }).onDelete("cascade"),
267
+ unique("user_roles_tenant_user_role_unique").on(table.tenantId, table.userId, table.roleId),
268
+ pgPolicy("tenant_isolation", { as: "permissive", for: "all", to: ["public"], using: sql`((tenant_id)::text = (iam.current_tenant_id())::text)`, withCheck: sql`((tenant_id)::text = (iam.current_tenant_id())::text)` })
269
+ ]);
270
+ var domainsInIam = iam.table("domains", {
271
+ id: uuid().default(sql`uuid_generate_v7()`).primaryKey().notNull(),
272
+ tenantId: varchar("tenant_id", { length: 30 }).notNull(),
273
+ domain: text().notNull(),
274
+ status: text().default("pending").notNull(),
275
+ meta: jsonb(),
276
+ isPrimary: boolean("is_primary").default(false).notNull(),
277
+ createdAt: timestamp("created_at", { withTimezone: true, mode: "string" }).default(sql`CURRENT_TIMESTAMP`).notNull(),
278
+ updatedAt: timestamp("updated_at", { withTimezone: true, mode: "string" }).default(sql`CURRENT_TIMESTAMP`).notNull()
279
+ }, (table) => [
280
+ uniqueIndex("domains_domain_unique_idx").using("btree", sql`lower(domain)`),
281
+ uniqueIndex("domains_primary_per_tenant_idx").using("btree", table.tenantId.asc().nullsLast().op("text_ops")).where(sql`(is_primary = true)`),
282
+ index("domains_tenant_status_idx").using("btree", table.tenantId.asc().nullsLast().op("text_ops"), table.status.asc().nullsLast().op("text_ops")),
283
+ index("idx_domains_tenant_domain_status").using("btree", table.tenantId.asc().nullsLast().op("text_ops"), table.domain.asc().nullsLast().op("text_ops"), table.status.asc().nullsLast().op("text_ops")),
284
+ foreignKey({
285
+ columns: [table.tenantId],
286
+ foreignColumns: [tenantsInIam.id],
287
+ name: "domains_tenant_id_fkey"
288
+ }).onUpdate("cascade").onDelete("cascade"),
289
+ pgPolicy("tenant_isolation", { as: "permissive", for: "all", to: ["public"], using: sql`((tenant_id)::text = (iam.current_tenant_id())::text)`, withCheck: sql`((tenant_id)::text = (iam.current_tenant_id())::text)` }),
290
+ check("domains_domain_format_check", sql`domain ~ '^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?)+$'::text`),
291
+ check("domains_status_check", sql`status = ANY (ARRAY['PENDING'::text, 'ACTIVE'::text, 'DISABLED'::text, 'DELETED'::text])`)
292
+ ]);
293
+
294
+ // src/db/orm/session.ts
295
+ var listHashedTokensForUser = async ({
296
+ database,
297
+ userId,
298
+ tenantId
299
+ }) => {
300
+ const rows = await database.select({ token: sessionsInIam.token }).from(sessionsInIam).where(
301
+ and(
302
+ eq(sessionsInIam.userId, userId),
303
+ eq(sessionsInIam.tenantId, tenantId)
304
+ )
305
+ );
306
+ return rows.map((r) => r.token);
307
+ };
308
+
309
+ // src/db/orm/tenant.ts
310
+ import { and as and2, eq as eq2, sql as sql2 } from "drizzle-orm";
311
+
312
+ // src/db/orm/user.ts
313
+ import { and as and4, eq as eq4 } from "drizzle-orm";
314
+
315
+ // src/lib/user-auth-select.ts
316
+ import { and as and3, eq as eq3, sql as sql3 } from "drizzle-orm";
317
+
318
+ // src/lib/cookie.ts
319
+ import { deleteCookie as honoDel, setCookie as honoSet } from "hono/cookie";
320
+ var isProduction = process.env.NODE_ENV === "production";
321
+ var getSessionKeyNamespace = (config) => {
322
+ const p = config.prefix?.trim();
323
+ return p && p.length > 0 ? p : "msb";
324
+ };
325
+
326
+ // src/lib/session-cache.ts
327
+ var sessionCacheKey = (config, hashedToken) => `${getSessionKeyNamespace(config)}:sc:v1:${hashedToken}`;
328
+ async function readSessionCache(config, hashedToken) {
329
+ const sc = config.sessionCache;
330
+ if (!sc?.enabled) {
331
+ return null;
332
+ }
333
+ const raw = await sc.kv.get(sessionCacheKey(config, hashedToken));
334
+ if (!raw) {
335
+ return null;
336
+ }
337
+ try {
338
+ const parsed = JSON.parse(raw);
339
+ if (!parsed?.session?.expiresAt || new Date(parsed.session.expiresAt) <= /* @__PURE__ */ new Date()) {
340
+ await sc.kv.delete(sessionCacheKey(config, hashedToken));
341
+ return null;
342
+ }
343
+ return parsed;
344
+ } catch {
345
+ await sc.kv.delete(sessionCacheKey(config, hashedToken));
346
+ return null;
347
+ }
348
+ }
349
+ async function writeSessionCache(config, hashedToken, payload) {
350
+ const sc = config.sessionCache;
351
+ if (!sc?.enabled) {
352
+ return;
353
+ }
354
+ const ttl = sc.ttlSeconds ?? Math.max(
355
+ 60,
356
+ Math.ceil(
357
+ (new Date(payload.session.expiresAt).getTime() - Date.now()) / 1e3
358
+ )
359
+ );
360
+ await sc.kv.put(
361
+ sessionCacheKey(config, hashedToken),
362
+ JSON.stringify(payload),
363
+ {
364
+ expirationTtl: Math.min(Math.max(ttl, 60), 2147483647)
365
+ }
366
+ );
367
+ }
368
+ async function deleteSessionCacheKeys(config, hashedTokens) {
369
+ const sc = config.sessionCache;
370
+ if (!sc?.enabled || hashedTokens.length === 0) {
371
+ return;
372
+ }
373
+ await Promise.all(
374
+ hashedTokens.map((t) => sc.kv.delete(sessionCacheKey(config, t)))
375
+ );
376
+ }
377
+ async function invalidateSessionCacheForHashedToken(config, hashedToken) {
378
+ await deleteSessionCacheKeys(config, [hashedToken]);
379
+ }
380
+ async function invalidateSessionCacheForUser(config, database, userId, tenantId) {
381
+ const tokens = await listHashedTokensForUser({
382
+ database,
383
+ userId,
384
+ tenantId
385
+ });
386
+ await deleteSessionCacheKeys(config, tokens);
387
+ }
388
+ export {
389
+ deleteSessionCacheKeys,
390
+ invalidateSessionCacheForHashedToken,
391
+ invalidateSessionCacheForUser,
392
+ readSessionCache,
393
+ sessionCacheKey,
394
+ writeSessionCache
395
+ };
396
+ //# sourceMappingURL=session-cache.js.map