@mesob/auth-hono 0.5.0 → 0.5.3

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.
@@ -0,0 +1,21 @@
1
+ import { Context } from 'hono';
2
+ import { A as AuthConfig } from '../index-v_uxUd8A.js';
3
+ import '@hono/zod-openapi';
4
+ import '@mesob/common';
5
+ import '../index-Dhe5obDc.js';
6
+ import 'drizzle-orm/node-postgres';
7
+ import 'drizzle-orm';
8
+ import 'drizzle-orm/pg-core';
9
+ import 'pg';
10
+
11
+ /** POST paths relative to mounted auth app root */
12
+ declare const AUTH_RATE_LIMIT_POST_PATHS: Set<string>;
13
+ declare function getClientIp(c: Context, headerName: string): string;
14
+ declare function checkAuthRateLimit(c: Context, config: AuthConfig): Promise<{
15
+ limited: false;
16
+ } | {
17
+ limited: true;
18
+ message: string;
19
+ }>;
20
+
21
+ export { AUTH_RATE_LIMIT_POST_PATHS, checkAuthRateLimit, getClientIp };
@@ -0,0 +1,228 @@
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
+
157
+ // src/lib/auth-rate-limit.ts
158
+ var AUTH_RATE_LIMIT_POST_PATHS = /* @__PURE__ */ new Set([
159
+ "/check-account",
160
+ "/sign-in",
161
+ "/sign-up",
162
+ "/password/forgot",
163
+ "/password/reset",
164
+ "/password/set",
165
+ "/email/verification/request",
166
+ "/email/verification/confirm",
167
+ "/phone/verification/request",
168
+ "/phone/verification/confirm"
169
+ ]);
170
+ var DEFAULT_MESSAGE = "Too many requests. Please wait before trying again.";
171
+ var RL_PREFIX = "msb:rl:v1:";
172
+ function getClientIp(c, headerName) {
173
+ const primary = c.req.header(headerName)?.trim();
174
+ if (primary) {
175
+ return primary;
176
+ }
177
+ const forwarded = c.req.header("x-forwarded-for")?.split(",")[0]?.trim();
178
+ if (forwarded) {
179
+ return forwarded;
180
+ }
181
+ return c.req.header("x-real-ip")?.trim() || "unknown";
182
+ }
183
+ function rateLimitKey(path, windowStart, ip) {
184
+ const bucket = path.replaceAll(/[^a-z0-9/-]/gi, "_");
185
+ return `${RL_PREFIX}${bucket}:${ip}:${windowStart}`;
186
+ }
187
+ async function checkAuthRateLimit(c, config) {
188
+ const rl = config.rateLimit;
189
+ if (!rl?.enabled) {
190
+ return { limited: false };
191
+ }
192
+ if (c.req.method !== "POST") {
193
+ return { limited: false };
194
+ }
195
+ const path = c.req.path;
196
+ if (!AUTH_RATE_LIMIT_POST_PATHS.has(path)) {
197
+ return { limited: false };
198
+ }
199
+ const rawIp = getClientIp(c, rl.ipHeader ?? "cf-connecting-ip");
200
+ const ipKey = rateLimitClientKey(rawIp, rl.ipv6Subnet ?? 64);
201
+ const now = Math.floor(Date.now() / 1e3);
202
+ const windowStart = Math.floor(now / rl.window) * rl.window;
203
+ const key = rateLimitKey(path, windowStart, ipKey);
204
+ const msg = rl.message ?? DEFAULT_MESSAGE;
205
+ const raw = await rl.kv.get(key);
206
+ let count = 0;
207
+ if (raw) {
208
+ try {
209
+ const parsed = JSON.parse(raw);
210
+ count = typeof parsed.n === "number" ? parsed.n : 0;
211
+ } catch {
212
+ count = 0;
213
+ }
214
+ }
215
+ if (count >= rl.max) {
216
+ return { limited: true, message: msg };
217
+ }
218
+ await rl.kv.put(key, JSON.stringify({ n: count + 1 }), {
219
+ expirationTtl: Math.min(rl.window * 2, 2147483647)
220
+ });
221
+ return { limited: false };
222
+ }
223
+ export {
224
+ AUTH_RATE_LIMIT_POST_PATHS,
225
+ checkAuthRateLimit,
226
+ getClientIp
227
+ };
228
+ //# sourceMappingURL=auth-rate-limit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/lib/normalize-rate-limit-ip.ts","../../src/lib/auth-rate-limit.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","import type { Context } from 'hono';\nimport type { AuthConfig } from '../types';\nimport { rateLimitClientKey } from './normalize-rate-limit-ip';\n\n/** POST paths relative to mounted auth app root */\nexport const AUTH_RATE_LIMIT_POST_PATHS = new Set([\n '/check-account',\n '/sign-in',\n '/sign-up',\n '/password/forgot',\n '/password/reset',\n '/password/set',\n '/email/verification/request',\n '/email/verification/confirm',\n '/phone/verification/request',\n '/phone/verification/confirm',\n]);\n\nconst DEFAULT_MESSAGE = 'Too many requests. Please wait before trying again.';\n\nconst RL_PREFIX = 'msb:rl:v1:';\n\nexport function getClientIp(c: Context, headerName: string): string {\n const primary = c.req.header(headerName)?.trim();\n if (primary) {\n return primary;\n }\n const forwarded = c.req.header('x-forwarded-for')?.split(',')[0]?.trim();\n if (forwarded) {\n return forwarded;\n }\n return c.req.header('x-real-ip')?.trim() || 'unknown';\n}\n\nfunction rateLimitKey(path: string, windowStart: number, ip: string): string {\n const bucket = path.replaceAll(/[^a-z0-9/-]/gi, '_');\n return `${RL_PREFIX}${bucket}:${ip}:${windowStart}`;\n}\n\nexport async function checkAuthRateLimit(\n c: Context,\n config: AuthConfig,\n): Promise<{ limited: false } | { limited: true; message: string }> {\n const rl = config.rateLimit;\n if (!rl?.enabled) {\n return { limited: false };\n }\n if (c.req.method !== 'POST') {\n return { limited: false };\n }\n const path = c.req.path;\n if (!AUTH_RATE_LIMIT_POST_PATHS.has(path)) {\n return { limited: false };\n }\n\n const rawIp = getClientIp(c, rl.ipHeader ?? 'cf-connecting-ip');\n const ipKey = rateLimitClientKey(rawIp, rl.ipv6Subnet ?? 64);\n const now = Math.floor(Date.now() / 1000);\n const windowStart = Math.floor(now / rl.window) * rl.window;\n const key = rateLimitKey(path, windowStart, ipKey);\n const msg = rl.message ?? DEFAULT_MESSAGE;\n\n const raw = await rl.kv.get(key);\n let count = 0;\n if (raw) {\n try {\n const parsed = JSON.parse(raw) as { n?: number };\n count = typeof parsed.n === 'number' ? parsed.n : 0;\n } catch {\n count = 0;\n }\n }\n\n if (count >= rl.max) {\n return { limited: true, message: msg };\n }\n\n await rl.kv.put(key, JSON.stringify({ n: count + 1 }), {\n expirationTtl: Math.min(rl.window * 2, 2_147_483_647),\n });\n\n return { limited: false };\n}\n"],"mappings":"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","names":[]}
@@ -1,5 +1,5 @@
1
1
  import * as pg from 'pg';
2
- import { D as Database } from '../index-D8OE85f8.js';
2
+ import { D as Database } from '../index-Dhe5obDc.js';
3
3
  import 'drizzle-orm/node-postgres';
4
4
  import 'drizzle-orm';
5
5
  import 'drizzle-orm/pg-core';
@@ -1,13 +1,15 @@
1
1
  import { Context } from 'hono';
2
- import { A as AuthConfig } from '../index-DwIwuvVj.js';
2
+ import { A as AuthConfig } from '../index-v_uxUd8A.js';
3
3
  import '@hono/zod-openapi';
4
4
  import '@mesob/common';
5
- import '../index-D8OE85f8.js';
5
+ import '../index-Dhe5obDc.js';
6
6
  import 'drizzle-orm/node-postgres';
7
7
  import 'drizzle-orm';
8
8
  import 'drizzle-orm/pg-core';
9
9
  import 'pg';
10
10
 
11
+ /** Namespace for session cookie + session KV cache (`{ns}:sc:v1:…`). */
12
+ declare const getSessionKeyNamespace: (config: AuthConfig) => string;
11
13
  declare const getSessionCookieName: (config: AuthConfig) => string;
12
14
  type CookieOptions = {
13
15
  expires?: Date;
@@ -16,4 +18,4 @@ type CookieOptions = {
16
18
  declare const setSessionCookie: (c: Context, token: string, config: AuthConfig, options: CookieOptions) => void;
17
19
  declare const deleteSessionCookie: (c: Context, config: AuthConfig) => void;
18
20
 
19
- export { type CookieOptions, deleteSessionCookie, getSessionCookieName, setSessionCookie };
21
+ export { type CookieOptions, deleteSessionCookie, getSessionCookieName, getSessionKeyNamespace, setSessionCookie };
@@ -1,8 +1,12 @@
1
1
  // src/lib/cookie.ts
2
2
  import { deleteCookie as honoDel, setCookie as honoSet } from "hono/cookie";
3
3
  var isProduction = process.env.NODE_ENV === "production";
4
+ var getSessionKeyNamespace = (config) => {
5
+ const p = config.prefix?.trim();
6
+ return p && p.length > 0 ? p : "msb";
7
+ };
4
8
  var getSessionCookieName = (config) => {
5
- const prefix = config.cookie?.prefix || "";
9
+ const prefix = config.prefix?.trim() || "";
6
10
  const baseName = "session_token";
7
11
  if (prefix) {
8
12
  return `${prefix}_${baseName}`;
@@ -35,6 +39,7 @@ var deleteSessionCookie = (c, config) => {
35
39
  export {
36
40
  deleteSessionCookie,
37
41
  getSessionCookieName,
42
+ getSessionKeyNamespace,
38
43
  setSessionCookie
39
44
  };
40
45
  //# sourceMappingURL=cookie.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/lib/cookie.ts"],"sourcesContent":["import type { Context } from 'hono';\nimport { deleteCookie as honoDel, setCookie as honoSet } from 'hono/cookie';\nimport type { AuthConfig } from '../types';\n\nconst isProduction = process.env.NODE_ENV === 'production';\n\nexport const getSessionCookieName = (config: AuthConfig): string => {\n const prefix = config.cookie?.prefix || '';\n const baseName = 'session_token';\n if (prefix) {\n return `${prefix}_${baseName}`;\n }\n return isProduction ? '__Host-session_token' : baseName;\n};\n\nexport type CookieOptions = {\n expires?: Date;\n path?: string;\n};\n\nexport const setSessionCookie = (\n c: Context,\n token: string,\n config: AuthConfig,\n options: CookieOptions,\n) => {\n const cookieName = getSessionCookieName(config);\n const cookieOptions = {\n httpOnly: true,\n secure: isProduction,\n sameSite: 'Lax' as const,\n path: options.path || '/',\n expires: options.expires,\n ...(isProduction && { domain: undefined }), // __Host- requires no domain\n };\n\n honoSet(c, cookieName, token, cookieOptions);\n};\n\nexport const deleteSessionCookie = (c: Context, config: AuthConfig) => {\n const cookieName = getSessionCookieName(config);\n honoDel(c, cookieName, {\n httpOnly: true,\n secure: isProduction,\n sameSite: 'Lax' as const,\n path: '/',\n expires: new Date(0),\n });\n};\n"],"mappings":";AACA,SAAS,gBAAgB,SAAS,aAAa,eAAe;AAG9D,IAAM,eAAe,QAAQ,IAAI,aAAa;AAEvC,IAAM,uBAAuB,CAAC,WAA+B;AAClE,QAAM,SAAS,OAAO,QAAQ,UAAU;AACxC,QAAM,WAAW;AACjB,MAAI,QAAQ;AACV,WAAO,GAAG,MAAM,IAAI,QAAQ;AAAA,EAC9B;AACA,SAAO,eAAe,yBAAyB;AACjD;AAOO,IAAM,mBAAmB,CAC9B,GACA,OACA,QACA,YACG;AACH,QAAM,aAAa,qBAAqB,MAAM;AAC9C,QAAM,gBAAgB;AAAA,IACpB,UAAU;AAAA,IACV,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,MAAM,QAAQ,QAAQ;AAAA,IACtB,SAAS,QAAQ;AAAA,IACjB,GAAI,gBAAgB,EAAE,QAAQ,OAAU;AAAA;AAAA,EAC1C;AAEA,UAAQ,GAAG,YAAY,OAAO,aAAa;AAC7C;AAEO,IAAM,sBAAsB,CAAC,GAAY,WAAuB;AACrE,QAAM,aAAa,qBAAqB,MAAM;AAC9C,UAAQ,GAAG,YAAY;AAAA,IACrB,UAAU;AAAA,IACV,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,MAAM;AAAA,IACN,SAAS,oBAAI,KAAK,CAAC;AAAA,EACrB,CAAC;AACH;","names":[]}
1
+ {"version":3,"sources":["../../src/lib/cookie.ts"],"sourcesContent":["import type { Context } from 'hono';\nimport { deleteCookie as honoDel, setCookie as honoSet } from 'hono/cookie';\nimport type { AuthConfig } from '../types';\n\nconst isProduction = process.env.NODE_ENV === 'production';\n\n/** Namespace for session cookie + session KV cache (`{ns}:sc:v1:…`). */\nexport const getSessionKeyNamespace = (config: AuthConfig): string => {\n const p = config.prefix?.trim();\n return p && p.length > 0 ? p : 'msb';\n};\n\nexport const getSessionCookieName = (config: AuthConfig): string => {\n const prefix = config.prefix?.trim() || '';\n const baseName = 'session_token';\n if (prefix) {\n return `${prefix}_${baseName}`;\n }\n return isProduction ? '__Host-session_token' : baseName;\n};\n\nexport type CookieOptions = {\n expires?: Date;\n path?: string;\n};\n\nexport const setSessionCookie = (\n c: Context,\n token: string,\n config: AuthConfig,\n options: CookieOptions,\n) => {\n const cookieName = getSessionCookieName(config);\n const cookieOptions = {\n httpOnly: true,\n secure: isProduction,\n sameSite: 'Lax' as const,\n path: options.path || '/',\n expires: options.expires,\n ...(isProduction && { domain: undefined }), // __Host- requires no domain\n };\n\n honoSet(c, cookieName, token, cookieOptions);\n};\n\nexport const deleteSessionCookie = (c: Context, config: AuthConfig) => {\n const cookieName = getSessionCookieName(config);\n honoDel(c, cookieName, {\n httpOnly: true,\n secure: isProduction,\n sameSite: 'Lax' as const,\n path: '/',\n expires: new Date(0),\n });\n};\n"],"mappings":";AACA,SAAS,gBAAgB,SAAS,aAAa,eAAe;AAG9D,IAAM,eAAe,QAAQ,IAAI,aAAa;AAGvC,IAAM,yBAAyB,CAAC,WAA+B;AACpE,QAAM,IAAI,OAAO,QAAQ,KAAK;AAC9B,SAAO,KAAK,EAAE,SAAS,IAAI,IAAI;AACjC;AAEO,IAAM,uBAAuB,CAAC,WAA+B;AAClE,QAAM,SAAS,OAAO,QAAQ,KAAK,KAAK;AACxC,QAAM,WAAW;AACjB,MAAI,QAAQ;AACV,WAAO,GAAG,MAAM,IAAI,QAAQ;AAAA,EAC9B;AACA,SAAO,eAAe,yBAAyB;AACjD;AAOO,IAAM,mBAAmB,CAC9B,GACA,OACA,QACA,YACG;AACH,QAAM,aAAa,qBAAqB,MAAM;AAC9C,QAAM,gBAAgB;AAAA,IACpB,UAAU;AAAA,IACV,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,MAAM,QAAQ,QAAQ;AAAA,IACtB,SAAS,QAAQ;AAAA,IACjB,GAAI,gBAAgB,EAAE,QAAQ,OAAU;AAAA;AAAA,EAC1C;AAEA,UAAQ,GAAG,YAAY,OAAO,aAAa;AAC7C;AAEO,IAAM,sBAAsB,CAAC,GAAY,WAAuB;AACrE,QAAM,aAAa,qBAAqB,MAAM;AAC9C,UAAQ,GAAG,YAAY;AAAA,IACrB,UAAU;AAAA,IACV,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,MAAM;AAAA,IACN,SAAS,oBAAI,KAAK,CAAC;AAAA,EACrB,CAAC;AACH;","names":[]}
@@ -1,8 +1,8 @@
1
- import { U as User } from '../index-DwIwuvVj.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';
5
- import '../index-D8OE85f8.js';
5
+ import '../index-Dhe5obDc.js';
6
6
  import 'drizzle-orm/node-postgres';
7
7
  import 'drizzle-orm';
8
8
  import 'drizzle-orm/pg-core';
@@ -1,10 +1,10 @@
1
1
  import { PermissionTree } from '@mesob/common';
2
2
  import * as drizzle_orm from 'drizzle-orm';
3
3
  import { ExtractTablesWithRelations, InferInsertModel } from 'drizzle-orm';
4
- import { D as Database } from '../index-D8OE85f8.js';
4
+ import { D as Database } from '../index-Dhe5obDc.js';
5
5
  import * as drizzle_orm_pg_core from 'drizzle-orm/pg-core';
6
6
  import { PgTransaction, PgQueryResultHKT } from 'drizzle-orm/pg-core';
7
- import { d as SeedRole } from '../index-DwIwuvVj.js';
7
+ import { h as SeedRole } from '../index-v_uxUd8A.js';
8
8
  import 'drizzle-orm/node-postgres';
9
9
  import 'pg';
10
10
  import 'hono';
@@ -1630,6 +1630,7 @@ declare const usersInIam: drizzle_orm_pg_core.PgTableWithColumns<{
1630
1630
  identity: undefined;
1631
1631
  generated: undefined;
1632
1632
  }, {}, {
1633
+ size: undefined;
1633
1634
  baseBuilder: drizzle_orm_pg_core.PgColumnBuilder<{
1634
1635
  name: "user_type";
1635
1636
  dataType: "string";
@@ -1638,7 +1639,6 @@ declare const usersInIam: drizzle_orm_pg_core.PgTableWithColumns<{
1638
1639
  enumValues: [string, ...string[]];
1639
1640
  driverParam: string;
1640
1641
  }, {}, {}, drizzle_orm.ColumnBuilderExtraConfig>;
1641
- size: undefined;
1642
1642
  }>;
1643
1643
  };
1644
1644
  dialect: "pg";
@@ -0,0 +1,16 @@
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
+ declare function loadSessionPair(database: Database, config: AuthConfig, hashedToken: string): Promise<{
12
+ session: Session;
13
+ user: User;
14
+ } | null>;
15
+
16
+ export { loadSessionPair };