@mesob/auth-hono 0.0.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.
package/dist/index.js ADDED
@@ -0,0 +1,2397 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __export = (target, all) => {
3
+ for (var name in all)
4
+ __defProp(target, name, { get: all[name], enumerable: true });
5
+ };
6
+
7
+ // src/db/index.ts
8
+ import { drizzle } from "drizzle-orm/node-postgres";
9
+ import { Pool } from "pg";
10
+
11
+ // src/db/relations.ts
12
+ var relations_exports = {};
13
+ __export(relations_exports, {
14
+ accountChangesInIamRelations: () => accountChangesInIamRelations,
15
+ accountsInIamRelations: () => accountsInIamRelations,
16
+ domainsInIamRelations: () => domainsInIamRelations,
17
+ permissionsInIamRelations: () => permissionsInIamRelations,
18
+ rolePermissionsInIamRelations: () => rolePermissionsInIamRelations,
19
+ rolesInIamRelations: () => rolesInIamRelations,
20
+ sessionsInIamRelations: () => sessionsInIamRelations,
21
+ tenantsInIamRelations: () => tenantsInIamRelations,
22
+ userRolesInIamRelations: () => userRolesInIamRelations,
23
+ usersInIamRelations: () => usersInIamRelations,
24
+ verificationsInIamRelations: () => verificationsInIamRelations
25
+ });
26
+ import { relations } from "drizzle-orm/relations";
27
+
28
+ // src/db/schema.ts
29
+ var schema_exports = {};
30
+ __export(schema_exports, {
31
+ accountChangesInIam: () => accountChangesInIam,
32
+ accountsInIam: () => accountsInIam,
33
+ domainsInIam: () => domainsInIam,
34
+ iam: () => iam,
35
+ permissionsInIam: () => permissionsInIam,
36
+ rolePermissionsInIam: () => rolePermissionsInIam,
37
+ rolesInIam: () => rolesInIam,
38
+ sessionsInIam: () => sessionsInIam,
39
+ tenantsInIam: () => tenantsInIam,
40
+ userRolesInIam: () => userRolesInIam,
41
+ usersInIam: () => usersInIam,
42
+ verificationsInIam: () => verificationsInIam
43
+ });
44
+ import { pgSchema, uniqueIndex, foreignKey, unique, pgPolicy, check, uuid, varchar, timestamp, text, boolean, smallint, index, inet, jsonb } from "drizzle-orm/pg-core";
45
+ import { sql } from "drizzle-orm";
46
+ var iam = pgSchema("iam");
47
+ var usersInIam = iam.table("users", {
48
+ id: uuid().default(sql`uuid_generate_v7()`).primaryKey().notNull(),
49
+ tenantId: varchar("tenant_id", { length: 30 }).notNull(),
50
+ createdAt: timestamp("created_at", { withTimezone: true, mode: "string" }).default(sql`CURRENT_TIMESTAMP`).notNull(),
51
+ updatedAt: timestamp("updated_at", { withTimezone: true, mode: "string" }).default(sql`CURRENT_TIMESTAMP`).notNull(),
52
+ fullName: text("full_name").notNull(),
53
+ image: text(),
54
+ phone: text(),
55
+ email: text(),
56
+ handle: text().notNull(),
57
+ emailVerified: boolean("email_verified").default(false).notNull(),
58
+ phoneVerified: boolean("phone_verified").default(false).notNull(),
59
+ bannedUntil: timestamp("banned_until", { withTimezone: true, mode: "string" }),
60
+ lastSignInAt: timestamp("last_sign_in_at", { withTimezone: true, mode: "string" }),
61
+ loginAttempt: smallint("login_attempt").default(0).notNull()
62
+ }, (table) => [
63
+ uniqueIndex("users_tenant_lower_email_idx").using("btree", sql`tenant_id`, sql`lower(email)`),
64
+ uniqueIndex("users_tenant_lower_handle_idx").using("btree", sql`tenant_id`, sql`lower(handle)`),
65
+ foreignKey({
66
+ columns: [table.tenantId],
67
+ foreignColumns: [tenantsInIam.id],
68
+ name: "users_tenant_id_fkey"
69
+ }).onUpdate("cascade").onDelete("cascade"),
70
+ unique("users_tenant_phone_key").on(table.tenantId, table.phone),
71
+ 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)` }),
72
+ check("users_login_attempt_nonnegative_check", sql`login_attempt >= 0`),
73
+ check("users_contact_required_check", sql`(email IS NOT NULL) OR (phone IS NOT NULL)`)
74
+ ]);
75
+ var sessionsInIam = iam.table("sessions", {
76
+ id: uuid().default(sql`uuid_generate_v7()`).primaryKey().notNull(),
77
+ tenantId: varchar("tenant_id", { length: 30 }).notNull(),
78
+ createdAt: timestamp("created_at", { withTimezone: true, mode: "string" }).default(sql`CURRENT_TIMESTAMP`).notNull(),
79
+ updatedAt: timestamp("updated_at", { withTimezone: true, mode: "string" }).default(sql`CURRENT_TIMESTAMP`).notNull(),
80
+ userId: uuid("user_id").notNull(),
81
+ expiresAt: timestamp("expires_at", { withTimezone: true, mode: "string" }).notNull(),
82
+ userAgent: text("user_agent"),
83
+ ip: inet(),
84
+ meta: jsonb(),
85
+ token: text().notNull(),
86
+ rotatedAt: timestamp("rotated_at", { withTimezone: true, mode: "string" }).default(sql`CURRENT_TIMESTAMP`)
87
+ }, (table) => [
88
+ index("sessions_expires_at_idx").using("btree", table.expiresAt.asc().nullsLast().op("timestamptz_ops")),
89
+ index("sessions_tenant_user_idx").using("btree", table.tenantId.asc().nullsLast().op("uuid_ops"), table.userId.asc().nullsLast().op("text_ops")),
90
+ foreignKey({
91
+ columns: [table.tenantId],
92
+ foreignColumns: [tenantsInIam.id],
93
+ name: "sessions_tenant_id_fkey"
94
+ }).onUpdate("cascade").onDelete("cascade"),
95
+ foreignKey({
96
+ columns: [table.userId],
97
+ foreignColumns: [usersInIam.id],
98
+ name: "sessions_user_id_fkey"
99
+ }).onUpdate("cascade").onDelete("cascade"),
100
+ unique("sessions_token_key").on(table.token),
101
+ 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)` }),
102
+ check("sessions_expires_after_created_check", sql`expires_at > created_at`)
103
+ ]);
104
+ var verificationsInIam = iam.table("verifications", {
105
+ id: uuid().default(sql`uuid_generate_v7()`).primaryKey().notNull(),
106
+ tenantId: varchar("tenant_id", { length: 30 }).notNull(),
107
+ createdAt: timestamp("created_at", { withTimezone: true, mode: "string" }).default(sql`CURRENT_TIMESTAMP`).notNull(),
108
+ updatedAt: timestamp("updated_at", { withTimezone: true, mode: "string" }).default(sql`CURRENT_TIMESTAMP`).notNull(),
109
+ userId: uuid("user_id").notNull(),
110
+ code: text().notNull(),
111
+ expiresAt: timestamp("expires_at", { withTimezone: true, mode: "string" }).notNull(),
112
+ type: text(),
113
+ attempt: smallint().default(0),
114
+ to: text()
115
+ }, (table) => [
116
+ index("verifications_expires_at_idx").using("btree", table.expiresAt.asc().nullsLast().op("timestamptz_ops")),
117
+ 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")),
118
+ foreignKey({
119
+ columns: [table.tenantId],
120
+ foreignColumns: [tenantsInIam.id],
121
+ name: "verifications_tenant_id_fkey"
122
+ }).onUpdate("cascade").onDelete("cascade"),
123
+ foreignKey({
124
+ columns: [table.userId],
125
+ foreignColumns: [usersInIam.id],
126
+ name: "verifications_user_id_fkey"
127
+ }).onUpdate("cascade").onDelete("cascade"),
128
+ 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)` }),
129
+ check("verifications_attempt_nonnegative_check", sql`attempt >= 0`),
130
+ check("verifications_expires_after_created_check", sql`expires_at > created_at`)
131
+ ]);
132
+ var accountChangesInIam = iam.table("account_changes", {
133
+ id: uuid().default(sql`uuid_generate_v7()`).primaryKey().notNull(),
134
+ tenantId: varchar("tenant_id", { length: 30 }).notNull(),
135
+ userId: uuid("user_id").notNull(),
136
+ createdAt: timestamp("created_at", { withTimezone: true, mode: "string" }).default(sql`CURRENT_TIMESTAMP`).notNull(),
137
+ updatedAt: timestamp("updated_at", { withTimezone: true, mode: "string" }).default(sql`CURRENT_TIMESTAMP`).notNull(),
138
+ changeType: text("change_type").notNull(),
139
+ oldEmail: varchar("old_email"),
140
+ newEmail: varchar("new_email"),
141
+ oldPhone: text("old_phone"),
142
+ newPhone: text("new_phone"),
143
+ status: varchar().notNull(),
144
+ expiresAt: timestamp("expires_at", { withTimezone: true, mode: "string" }).notNull(),
145
+ confirmedAt: timestamp("confirmed_at", { withTimezone: true, mode: "string" }),
146
+ cancelledAt: timestamp("cancelled_at", { withTimezone: true, mode: "string" }),
147
+ reason: text()
148
+ }, (table) => [
149
+ index("account_changes_expires_at_idx").using("btree", table.expiresAt.asc().nullsLast().op("timestamptz_ops")),
150
+ 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("text_ops")),
151
+ foreignKey({
152
+ columns: [table.tenantId],
153
+ foreignColumns: [tenantsInIam.id],
154
+ name: "account_changes_tenant_id_fkey"
155
+ }).onUpdate("cascade").onDelete("cascade"),
156
+ foreignKey({
157
+ columns: [table.userId],
158
+ foreignColumns: [usersInIam.id],
159
+ name: "account_changes_user_id_fkey"
160
+ }).onUpdate("cascade").onDelete("cascade"),
161
+ 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)` }),
162
+ check("account_changes_status_check", sql`(status)::text = ANY ((ARRAY['pending'::character varying, 'applied'::character varying, 'cancelled'::character varying, 'expired'::character varying])::text[])`),
163
+ 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))`),
164
+ check("account_changes_expires_after_created_check", sql`expires_at > created_at`)
165
+ ]);
166
+ var tenantsInIam = iam.table("tenants", {
167
+ id: varchar({ length: 30 }).primaryKey().notNull(),
168
+ createdAt: timestamp("created_at", { withTimezone: true, mode: "string" }).default(sql`CURRENT_TIMESTAMP`).notNull(),
169
+ updatedAt: timestamp("updated_at", { withTimezone: true, mode: "string" }).default(sql`CURRENT_TIMESTAMP`).notNull(),
170
+ name: jsonb().notNull(),
171
+ description: jsonb(),
172
+ theme: jsonb(),
173
+ supportedLanguages: jsonb("supported_languages"),
174
+ defaultLanguage: text("default_language"),
175
+ supportedCurrency: jsonb("supported_currency"),
176
+ defaultCurrency: text("default_currency"),
177
+ timezone: text(),
178
+ isActive: boolean("is_active").default(true).notNull(),
179
+ locale: jsonb(),
180
+ settings: jsonb(),
181
+ seo: jsonb()
182
+ }, (table) => [
183
+ index("tenants_is_active_idx").using("btree", table.isActive.asc().nullsLast().op("bool_ops"))
184
+ ]);
185
+ var rolesInIam = iam.table("roles", {
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
+ name: jsonb().notNull(),
191
+ description: jsonb().notNull(),
192
+ code: text().notNull()
193
+ }, (table) => [
194
+ foreignKey({
195
+ columns: [table.tenantId],
196
+ foreignColumns: [tenantsInIam.id],
197
+ name: "roles_tenant_id_fkey"
198
+ }).onUpdate("cascade").onDelete("cascade"),
199
+ unique("roles_tenant_code_unique").on(table.tenantId, table.code),
200
+ 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)` })
201
+ ]);
202
+ var permissionsInIam = iam.table("permissions", {
203
+ id: text().primaryKey().notNull(),
204
+ description: jsonb().notNull(),
205
+ activity: text().notNull(),
206
+ application: text().notNull(),
207
+ feature: text().notNull()
208
+ }, (table) => [
209
+ unique("permissions_activity_application_feature_key").on(table.activity, table.application, table.feature)
210
+ ]);
211
+ var rolePermissionsInIam = iam.table("role_permissions", {
212
+ id: uuid().default(sql`uuid_generate_v7()`).primaryKey().notNull(),
213
+ tenantId: varchar("tenant_id", { length: 30 }).notNull(),
214
+ roleId: uuid("role_id").notNull(),
215
+ permissionId: text("permission_id").notNull()
216
+ }, (table) => [
217
+ foreignKey({
218
+ columns: [table.tenantId],
219
+ foreignColumns: [tenantsInIam.id],
220
+ name: "role_permissions_tenant_id_fkey"
221
+ }).onUpdate("cascade").onDelete("cascade"),
222
+ foreignKey({
223
+ columns: [table.roleId],
224
+ foreignColumns: [rolesInIam.id],
225
+ name: "role_permissions_role_id_fkey"
226
+ }).onUpdate("cascade").onDelete("cascade"),
227
+ foreignKey({
228
+ columns: [table.permissionId],
229
+ foreignColumns: [permissionsInIam.id],
230
+ name: "role_permissions_permission_id_fkey"
231
+ }).onUpdate("cascade").onDelete("cascade"),
232
+ unique("role_permissions_unique").on(table.roleId, table.permissionId),
233
+ 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)` })
234
+ ]);
235
+ var userRolesInIam = iam.table("user_roles", {
236
+ id: uuid().default(sql`uuid_generate_v7()`).primaryKey().notNull(),
237
+ tenantId: varchar("tenant_id", { length: 30 }).notNull(),
238
+ userId: uuid("user_id").notNull(),
239
+ roleId: uuid("role_id").notNull()
240
+ }, (table) => [
241
+ foreignKey({
242
+ columns: [table.tenantId],
243
+ foreignColumns: [tenantsInIam.id],
244
+ name: "user_roles_tenant_id_fkey"
245
+ }).onUpdate("cascade").onDelete("cascade"),
246
+ foreignKey({
247
+ columns: [table.userId],
248
+ foreignColumns: [usersInIam.id],
249
+ name: "user_roles_user_id_fkey"
250
+ }).onUpdate("cascade").onDelete("cascade"),
251
+ foreignKey({
252
+ columns: [table.roleId],
253
+ foreignColumns: [rolesInIam.id],
254
+ name: "user_roles_role_id_fkey"
255
+ }).onUpdate("cascade").onDelete("cascade"),
256
+ unique("user_roles_unique").on(table.userId, table.roleId),
257
+ 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)` })
258
+ ]);
259
+ var accountsInIam = iam.table("accounts", {
260
+ id: uuid().default(sql`uuid_generate_v7()`).primaryKey().notNull(),
261
+ tenantId: varchar("tenant_id", { length: 30 }).notNull(),
262
+ userId: uuid("user_id").notNull(),
263
+ provider: text().notNull(),
264
+ providerAccountId: text("provider_account_id").notNull(),
265
+ password: text(),
266
+ passwordLastChangedAt: timestamp("password_last_changed_at", { withTimezone: true, mode: "string" }),
267
+ idToken: text("id_token"),
268
+ accessToken: text("access_token"),
269
+ accessTokenExpiresAt: timestamp("access_token_expires_at", { withTimezone: true, mode: "string" }),
270
+ refreshToken: text("refresh_token"),
271
+ refreshTokenExpiresAt: timestamp("refresh_token_expires_at", { withTimezone: true, mode: "string" }),
272
+ scope: text(),
273
+ expiresAt: timestamp("expires_at", { withTimezone: true, mode: "string" }),
274
+ meta: jsonb()
275
+ }, (table) => [
276
+ foreignKey({
277
+ columns: [table.tenantId],
278
+ foreignColumns: [tenantsInIam.id],
279
+ name: "accounts_tenant_id_fkey"
280
+ }).onUpdate("cascade").onDelete("cascade"),
281
+ foreignKey({
282
+ columns: [table.userId],
283
+ foreignColumns: [usersInIam.id],
284
+ name: "accounts_user_id_fkey"
285
+ }).onUpdate("cascade").onDelete("cascade"),
286
+ unique("accounts_provider_account_unique").on(table.provider, table.providerAccountId),
287
+ 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)` })
288
+ ]);
289
+ var domainsInIam = iam.table("domains", {
290
+ id: uuid().default(sql`uuid_generate_v7()`).primaryKey().notNull(),
291
+ tenantId: varchar("tenant_id", { length: 30 }).notNull(),
292
+ domain: text().notNull(),
293
+ status: text().default("pending").notNull(),
294
+ meta: jsonb(),
295
+ isPrimary: boolean("is_primary").default(false).notNull(),
296
+ createdAt: timestamp("created_at", { withTimezone: true, mode: "string" }).default(sql`CURRENT_TIMESTAMP`).notNull(),
297
+ updatedAt: timestamp("updated_at", { withTimezone: true, mode: "string" }).default(sql`CURRENT_TIMESTAMP`).notNull(),
298
+ createdBy: uuid("created_by"),
299
+ updatedBy: uuid("updated_by")
300
+ }, (table) => [
301
+ uniqueIndex("domains_domain_unique_idx").using("btree", sql`lower(domain)`),
302
+ uniqueIndex("domains_primary_per_tenant_idx").using("btree", table.tenantId.asc().nullsLast().op("text_ops")).where(sql`(is_primary = true)`),
303
+ index("domains_tenant_id_idx").using("btree", table.tenantId.asc().nullsLast().op("text_ops")),
304
+ index("domains_tenant_status_idx").using("btree", table.tenantId.asc().nullsLast().op("text_ops"), table.status.asc().nullsLast().op("text_ops")),
305
+ foreignKey({
306
+ columns: [table.tenantId],
307
+ foreignColumns: [tenantsInIam.id],
308
+ name: "domains_tenant_id_fkey"
309
+ }).onUpdate("cascade").onDelete("cascade"),
310
+ 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)` }),
311
+ check("domains_status_check", sql`status = ANY (ARRAY['pending'::text, 'active'::text, 'disabled'::text, 'deleted'::text])`),
312
+ 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`)
313
+ ]);
314
+
315
+ // src/db/relations.ts
316
+ var usersInIamRelations = relations(usersInIam, ({ one, many }) => ({
317
+ tenantsInIam: one(tenantsInIam, {
318
+ fields: [usersInIam.tenantId],
319
+ references: [tenantsInIam.id]
320
+ }),
321
+ sessionsInIam: many(sessionsInIam),
322
+ verificationsInIam: many(verificationsInIam),
323
+ accountChangesInIam: many(accountChangesInIam),
324
+ userRolesInIam: many(userRolesInIam),
325
+ accountsInIam: many(accountsInIam)
326
+ }));
327
+ var tenantsInIamRelations = relations(tenantsInIam, ({ many }) => ({
328
+ usersInIam: many(usersInIam),
329
+ sessionsInIam: many(sessionsInIam),
330
+ verificationsInIam: many(verificationsInIam),
331
+ accountChangesInIam: many(accountChangesInIam),
332
+ rolesInIam: many(rolesInIam),
333
+ rolePermissionsInIam: many(rolePermissionsInIam),
334
+ userRolesInIam: many(userRolesInIam),
335
+ accountsInIam: many(accountsInIam),
336
+ domainsInIam: many(domainsInIam)
337
+ }));
338
+ var sessionsInIamRelations = relations(sessionsInIam, ({ one }) => ({
339
+ tenantsInIam: one(tenantsInIam, {
340
+ fields: [sessionsInIam.tenantId],
341
+ references: [tenantsInIam.id]
342
+ }),
343
+ usersInIam: one(usersInIam, {
344
+ fields: [sessionsInIam.userId],
345
+ references: [usersInIam.id]
346
+ })
347
+ }));
348
+ var verificationsInIamRelations = relations(verificationsInIam, ({ one }) => ({
349
+ tenantsInIam: one(tenantsInIam, {
350
+ fields: [verificationsInIam.tenantId],
351
+ references: [tenantsInIam.id]
352
+ }),
353
+ usersInIam: one(usersInIam, {
354
+ fields: [verificationsInIam.userId],
355
+ references: [usersInIam.id]
356
+ })
357
+ }));
358
+ var accountChangesInIamRelations = relations(accountChangesInIam, ({ one }) => ({
359
+ tenantsInIam: one(tenantsInIam, {
360
+ fields: [accountChangesInIam.tenantId],
361
+ references: [tenantsInIam.id]
362
+ }),
363
+ usersInIam: one(usersInIam, {
364
+ fields: [accountChangesInIam.userId],
365
+ references: [usersInIam.id]
366
+ })
367
+ }));
368
+ var rolesInIamRelations = relations(rolesInIam, ({ one, many }) => ({
369
+ tenantsInIam: one(tenantsInIam, {
370
+ fields: [rolesInIam.tenantId],
371
+ references: [tenantsInIam.id]
372
+ }),
373
+ rolePermissionsInIam: many(rolePermissionsInIam),
374
+ userRolesInIam: many(userRolesInIam)
375
+ }));
376
+ var rolePermissionsInIamRelations = relations(rolePermissionsInIam, ({ one }) => ({
377
+ tenantsInIam: one(tenantsInIam, {
378
+ fields: [rolePermissionsInIam.tenantId],
379
+ references: [tenantsInIam.id]
380
+ }),
381
+ rolesInIam: one(rolesInIam, {
382
+ fields: [rolePermissionsInIam.roleId],
383
+ references: [rolesInIam.id]
384
+ }),
385
+ permissionsInIam: one(permissionsInIam, {
386
+ fields: [rolePermissionsInIam.permissionId],
387
+ references: [permissionsInIam.id]
388
+ })
389
+ }));
390
+ var permissionsInIamRelations = relations(permissionsInIam, ({ many }) => ({
391
+ rolePermissionsInIam: many(rolePermissionsInIam)
392
+ }));
393
+ var userRolesInIamRelations = relations(userRolesInIam, ({ one }) => ({
394
+ tenantsInIam: one(tenantsInIam, {
395
+ fields: [userRolesInIam.tenantId],
396
+ references: [tenantsInIam.id]
397
+ }),
398
+ usersInIam: one(usersInIam, {
399
+ fields: [userRolesInIam.userId],
400
+ references: [usersInIam.id]
401
+ }),
402
+ rolesInIam: one(rolesInIam, {
403
+ fields: [userRolesInIam.roleId],
404
+ references: [rolesInIam.id]
405
+ })
406
+ }));
407
+ var accountsInIamRelations = relations(accountsInIam, ({ one }) => ({
408
+ tenantsInIam: one(tenantsInIam, {
409
+ fields: [accountsInIam.tenantId],
410
+ references: [tenantsInIam.id]
411
+ }),
412
+ usersInIam: one(usersInIam, {
413
+ fields: [accountsInIam.userId],
414
+ references: [usersInIam.id]
415
+ })
416
+ }));
417
+ var domainsInIamRelations = relations(domainsInIam, ({ one }) => ({
418
+ tenantsInIam: one(tenantsInIam, {
419
+ fields: [domainsInIam.tenantId],
420
+ references: [tenantsInIam.id]
421
+ })
422
+ }));
423
+
424
+ // src/db/index.ts
425
+ var schemaConfig = { schema: { ...schema_exports, ...relations_exports } };
426
+ var createDatabase = (connectionString) => {
427
+ const pool = new Pool({ connectionString });
428
+ return drizzle({ client: pool, ...schemaConfig });
429
+ };
430
+
431
+ // src/db/orm/iam/sessions/find-session-by-token.ts
432
+ import { and, eq, gt } from "drizzle-orm";
433
+ var findSessionByToken = (db, hashedToken) => {
434
+ return db.select({
435
+ id: sessionsInIam.id,
436
+ tenantId: sessionsInIam.tenantId,
437
+ userId: sessionsInIam.userId,
438
+ expiresAt: sessionsInIam.expiresAt,
439
+ createdAt: sessionsInIam.createdAt,
440
+ updatedAt: sessionsInIam.updatedAt,
441
+ userAgent: sessionsInIam.userAgent,
442
+ ip: sessionsInIam.ip
443
+ }).from(sessionsInIam).where(
444
+ and(
445
+ eq(sessionsInIam.token, hashedToken),
446
+ gt(sessionsInIam.expiresAt, (/* @__PURE__ */ new Date()).toISOString())
447
+ )
448
+ ).limit(1).then(([session]) => session || null);
449
+ };
450
+
451
+ // src/db/orm/iam/users/find-user-by-id.ts
452
+ import { and as and2, eq as eq2 } from "drizzle-orm";
453
+ var findUserById = (db, tenantId, userId) => {
454
+ return db.select({
455
+ id: usersInIam.id,
456
+ tenantId: usersInIam.tenantId,
457
+ fullName: usersInIam.fullName,
458
+ email: usersInIam.email,
459
+ phone: usersInIam.phone,
460
+ handle: usersInIam.handle,
461
+ image: usersInIam.image,
462
+ emailVerified: usersInIam.emailVerified,
463
+ phoneVerified: usersInIam.phoneVerified,
464
+ lastSignInAt: usersInIam.lastSignInAt
465
+ }).from(usersInIam).where(and2(eq2(usersInIam.id, userId), eq2(usersInIam.tenantId, tenantId))).limit(1).then(([user]) => user || null);
466
+ };
467
+
468
+ // src/handler.ts
469
+ import { OpenAPIHono as OpenAPIHono2 } from "@hono/zod-openapi";
470
+ import { getCookie as getCookie2 } from "hono/cookie";
471
+ import { HTTPException as HTTPException11 } from "hono/http-exception";
472
+
473
+ // src/lib/crypto.ts
474
+ import { scrypt } from "@noble/hashes/scrypt.js";
475
+ import { randomBytes } from "@noble/hashes/utils.js";
476
+ var encoder = new TextEncoder();
477
+ var toHex = (buffer) => {
478
+ return Array.from(
479
+ buffer,
480
+ (b) => b.toString(16).padStart(2, "0")
481
+ ).join("");
482
+ };
483
+ var hexToBytes = (hex) => {
484
+ const bytes = new Uint8Array(hex.length / 2);
485
+ for (let i = 0; i < hex.length; i += 2) {
486
+ bytes[i / 2] = Number.parseInt(hex.slice(i, i + 2), 16);
487
+ }
488
+ return bytes;
489
+ };
490
+ var SCRYPT_KEYLEN = 64;
491
+ var SCRYPT_COST = 16384;
492
+ var SCRYPT_BLOCK_SIZE = 8;
493
+ var SCRYPT_PARALLELISM = 1;
494
+ var hashPassword = async (password) => {
495
+ const salt = randomBytes(16);
496
+ const saltHex = toHex(salt);
497
+ const passwordBytes = encoder.encode(password);
498
+ const derivedKey = await Promise.resolve(
499
+ scrypt(passwordBytes, salt, {
500
+ N: SCRYPT_COST,
501
+ r: SCRYPT_BLOCK_SIZE,
502
+ p: SCRYPT_PARALLELISM,
503
+ dkLen: SCRYPT_KEYLEN
504
+ })
505
+ );
506
+ return `${saltHex}:${toHex(derivedKey)}`;
507
+ };
508
+ var verifyPassword = async (password, hashed) => {
509
+ if (!hashed) {
510
+ return false;
511
+ }
512
+ const [saltHex, keyHex] = hashed.split(":");
513
+ if (!(saltHex && keyHex)) {
514
+ return false;
515
+ }
516
+ const salt = hexToBytes(saltHex);
517
+ const passwordBytes = encoder.encode(password);
518
+ const derivedKey = await Promise.resolve(
519
+ scrypt(passwordBytes, salt, {
520
+ N: SCRYPT_COST,
521
+ r: SCRYPT_BLOCK_SIZE,
522
+ p: SCRYPT_PARALLELISM,
523
+ dkLen: SCRYPT_KEYLEN
524
+ })
525
+ );
526
+ const derived = toHex(derivedKey);
527
+ if (derived.length !== keyHex.length) {
528
+ return false;
529
+ }
530
+ let result = 0;
531
+ for (let i = 0; i < derived.length; i++) {
532
+ result |= derived.charCodeAt(i) ^ keyHex.charCodeAt(i);
533
+ }
534
+ return result === 0;
535
+ };
536
+ var hashToken = async (token, secret) => {
537
+ const key = await crypto.subtle.importKey(
538
+ "raw",
539
+ encoder.encode(secret),
540
+ { name: "HMAC", hash: "SHA-256" },
541
+ false,
542
+ ["sign"]
543
+ );
544
+ const signature = await crypto.subtle.sign(
545
+ "HMAC",
546
+ key,
547
+ encoder.encode(token)
548
+ );
549
+ return toHex(new Uint8Array(signature));
550
+ };
551
+
552
+ // src/routes/auth.route.ts
553
+ import { createRoute, OpenAPIHono } from "@hono/zod-openapi";
554
+ import { z as z2 } from "zod";
555
+
556
+ // src/routes/auth.schema.ts
557
+ import { z } from "zod";
558
+ var emailField = z.string().trim().email("Invalid email address").max(255, "Email too long");
559
+ var phoneField = z.string().trim().min(6, "Phone too short").max(30, "Phone too long").regex(/^[+()\d\s-]+$/, "Invalid phone number format");
560
+ var passwordField = z.string().min(8, "Password must be at least 8 characters").max(128, "Password too long");
561
+ var userSchema = z.object({
562
+ id: z.string().uuid(),
563
+ tenantId: z.string(),
564
+ fullName: z.string(),
565
+ email: z.string().email().nullable(),
566
+ phone: z.string().nullable(),
567
+ handle: z.string(),
568
+ image: z.string().nullable(),
569
+ emailVerified: z.boolean(),
570
+ phoneVerified: z.boolean(),
571
+ lastSignInAt: z.string().datetime().nullable()
572
+ });
573
+ var sessionSchema = z.object({
574
+ id: z.string().uuid(),
575
+ expiresAt: z.string().datetime(),
576
+ createdAt: z.string().datetime().optional(),
577
+ userAgent: z.string().nullable().optional(),
578
+ ip: z.string().nullable().optional()
579
+ });
580
+ var authSuccessSchema = z.object({
581
+ user: userSchema,
582
+ session: sessionSchema,
583
+ sessionToken: z.string(),
584
+ sessionExpiresAt: z.string().datetime()
585
+ });
586
+ var messageSchema = z.object({
587
+ message: z.string()
588
+ });
589
+ var errorResponseSchema = z.object({
590
+ error: z.string().describe("Error message"),
591
+ code: z.string().optional().describe("Error code"),
592
+ details: z.record(z.string(), z.any()).optional()
593
+ });
594
+ var signUpSchema = z.object({
595
+ email: emailField.optional(),
596
+ phone: phoneField.optional(),
597
+ password: passwordField,
598
+ fullName: z.string().min(2),
599
+ handle: z.string().optional(),
600
+ image: z.string().url().optional()
601
+ }).refine((data) => data.email || data.phone, {
602
+ message: "Either email or phone is required",
603
+ path: ["email"]
604
+ });
605
+ var signInSchema = z.object({
606
+ identifier: z.string(),
607
+ password: passwordField
608
+ });
609
+ var signUpResponseSchema = authSuccessSchema.extend({
610
+ verificationId: z.string().uuid().optional(),
611
+ requiresVerification: z.boolean().optional(),
612
+ debugCode: z.string().optional()
613
+ });
614
+ var signInResponseSchema = authSuccessSchema.extend({
615
+ verificationId: z.string().uuid().optional(),
616
+ requiresVerification: z.boolean().optional()
617
+ });
618
+ var emailVerificationRequestSchema = z.object({
619
+ email: emailField.optional()
620
+ });
621
+ var emailVerificationConfirmSchema = z.object({
622
+ verificationId: z.string().uuid(),
623
+ code: z.string().length(6)
624
+ });
625
+ var phoneVerificationRequestSchema = z.object({
626
+ phone: phoneField,
627
+ context: z.enum(["sign-up", "sign-in", "change-phone"])
628
+ });
629
+ var phoneVerificationConfirmSchema = z.object({
630
+ verificationId: z.string().uuid(),
631
+ code: z.string(),
632
+ context: z.enum(["sign-up", "sign-in", "change-phone"])
633
+ });
634
+ var forgotPasswordSchema = z.object({
635
+ identifier: z.string()
636
+ });
637
+ var resetPasswordSchema = z.object({
638
+ verificationId: z.string().uuid(),
639
+ code: z.string(),
640
+ password: passwordField
641
+ });
642
+ var changePasswordSchema = z.object({
643
+ currentPassword: passwordField,
644
+ newPassword: passwordField
645
+ });
646
+ var messageWithVerificationIdSchema = messageSchema.extend({
647
+ verificationId: z.string().uuid().optional()
648
+ });
649
+ var checkUserSchema = z.object({
650
+ identifier: z.string()
651
+ });
652
+ var checkUserResponseSchema = z.object({
653
+ exists: z.boolean()
654
+ });
655
+
656
+ // src/db/orm/iam/users/find-user-by-email.ts
657
+ import { and as and3, eq as eq3, sql as sql2 } from "drizzle-orm";
658
+ var findUserByEmail = (db, tenantId, email) => {
659
+ return db.select({
660
+ id: usersInIam.id,
661
+ tenantId: usersInIam.tenantId,
662
+ fullName: usersInIam.fullName,
663
+ email: usersInIam.email,
664
+ phone: usersInIam.phone,
665
+ handle: usersInIam.handle,
666
+ image: usersInIam.image,
667
+ emailVerified: usersInIam.emailVerified,
668
+ phoneVerified: usersInIam.phoneVerified,
669
+ lastSignInAt: usersInIam.lastSignInAt
670
+ }).from(usersInIam).where(
671
+ and3(
672
+ eq3(usersInIam.tenantId, tenantId),
673
+ sql2`lower(${usersInIam.email}) = lower(${email})`
674
+ )
675
+ ).limit(1).then(([user]) => user || null);
676
+ };
677
+
678
+ // src/db/orm/iam/users/find-user-by-phone.ts
679
+ import { and as and4, eq as eq4 } from "drizzle-orm";
680
+ var findUserByPhone = (db, tenantId, phone) => {
681
+ return db.select({
682
+ id: usersInIam.id,
683
+ tenantId: usersInIam.tenantId,
684
+ fullName: usersInIam.fullName,
685
+ email: usersInIam.email,
686
+ phone: usersInIam.phone,
687
+ handle: usersInIam.handle,
688
+ image: usersInIam.image,
689
+ emailVerified: usersInIam.emailVerified,
690
+ phoneVerified: usersInIam.phoneVerified,
691
+ lastSignInAt: usersInIam.lastSignInAt
692
+ }).from(usersInIam).where(and4(eq4(usersInIam.tenantId, tenantId), eq4(usersInIam.phone, phone))).limit(1).then(([user]) => user || null);
693
+ };
694
+
695
+ // src/db/orm/iam/users/find-user-by-identifier.ts
696
+ var findUserByIdentifier = async (db, tenantId, identifier) => {
697
+ const isEmail = identifier.includes("@");
698
+ if (isEmail) {
699
+ const user2 = await findUserByEmail(db, tenantId, identifier);
700
+ return { user: user2, type: "email" };
701
+ }
702
+ const user = await findUserByPhone(db, tenantId, identifier);
703
+ return { user, type: "phone" };
704
+ };
705
+
706
+ // src/lib/tenant.ts
707
+ import { HTTPException } from "hono/http-exception";
708
+ var ensureTenantId = (config, tenantId) => {
709
+ if (config.enableTenant) {
710
+ if (!tenantId) {
711
+ throw new HTTPException(400, {
712
+ message: "Missing tenantId. Tenant isolation is enabled."
713
+ });
714
+ }
715
+ return tenantId;
716
+ }
717
+ if (!config.tenantId) {
718
+ throw new HTTPException(500, {
719
+ message: "tenantId must be provided in config when enableTenant is false."
720
+ });
721
+ }
722
+ return config.tenantId;
723
+ };
724
+
725
+ // src/routes/handler/check-user.ts
726
+ var checkUserHandler = async (c) => {
727
+ const body = c.req.valid("json");
728
+ const { config, database, tenantId } = c.var;
729
+ const resolvedTenantId = ensureTenantId(config, tenantId);
730
+ const { identifier } = body;
731
+ const lookup = await findUserByIdentifier(
732
+ database,
733
+ resolvedTenantId,
734
+ identifier
735
+ );
736
+ return c.json({ exists: !!lookup.user });
737
+ };
738
+
739
+ // src/routes/handler/email-verification-confirm.ts
740
+ import { setCookie } from "hono/cookie";
741
+ import { HTTPException as HTTPException2 } from "hono/http-exception";
742
+
743
+ // src/db/orm/iam/sessions/insert-session.ts
744
+ var insertSession = (db, data) => {
745
+ return db.insert(sessionsInIam).values({
746
+ tenantId: data.tenantId,
747
+ userId: data.userId,
748
+ token: data.token,
749
+ expiresAt: data.expiresAt,
750
+ userAgent: data.userAgent || null,
751
+ ip: data.ip || null,
752
+ meta: data.meta || null
753
+ }).returning({
754
+ id: sessionsInIam.id,
755
+ tenantId: sessionsInIam.tenantId,
756
+ userId: sessionsInIam.userId,
757
+ expiresAt: sessionsInIam.expiresAt,
758
+ createdAt: sessionsInIam.createdAt,
759
+ updatedAt: sessionsInIam.updatedAt,
760
+ userAgent: sessionsInIam.userAgent,
761
+ ip: sessionsInIam.ip
762
+ }).then(([session]) => session);
763
+ };
764
+
765
+ // src/db/orm/iam/users/update-user-verified.ts
766
+ import { and as and5, eq as eq5 } from "drizzle-orm";
767
+ var updateUserVerified = (db, tenantId, userId, type) => {
768
+ return db.update(usersInIam).set({
769
+ [type === "email" ? "emailVerified" : "phoneVerified"]: true,
770
+ lastSignInAt: (/* @__PURE__ */ new Date()).toISOString()
771
+ }).where(and5(eq5(usersInIam.id, userId), eq5(usersInIam.tenantId, tenantId)));
772
+ };
773
+
774
+ // src/db/orm/iam/verifications/consume-verification.ts
775
+ import { eq as eq6 } from "drizzle-orm";
776
+ var consumeVerification = (db, verificationId) => {
777
+ return db.delete(verificationsInIam).where(eq6(verificationsInIam.id, verificationId));
778
+ };
779
+
780
+ // src/db/orm/iam/verifications/find-verification-by-id.ts
781
+ import { eq as eq7 } from "drizzle-orm";
782
+ var findVerificationById = (db, verificationId) => {
783
+ return db.select({
784
+ id: verificationsInIam.id,
785
+ tenantId: verificationsInIam.tenantId,
786
+ userId: verificationsInIam.userId,
787
+ type: verificationsInIam.type,
788
+ code: verificationsInIam.code,
789
+ to: verificationsInIam.to,
790
+ expiresAt: verificationsInIam.expiresAt,
791
+ createdAt: verificationsInIam.createdAt,
792
+ attempt: verificationsInIam.attempt
793
+ }).from(verificationsInIam).where(eq7(verificationsInIam.id, verificationId)).limit(1).then(([verification]) => verification || null);
794
+ };
795
+
796
+ // src/db/orm/iam/verifications/update-verification-attempt.ts
797
+ import { eq as eq8 } from "drizzle-orm";
798
+ var updateVerificationAttempt = async (db, verificationId) => {
799
+ const verification = await findVerificationById(db, verificationId);
800
+ if (!verification) {
801
+ return;
802
+ }
803
+ await db.update(verificationsInIam).set({ attempt: (verification.attempt || 0) + 1 }).where(eq8(verificationsInIam.id, verificationId));
804
+ };
805
+
806
+ // src/errors.ts
807
+ var AUTH_ERRORS = {
808
+ USER_NOT_FOUND: "USER_NOT_FOUND",
809
+ INVALID_PASSWORD: "INVALID_PASSWORD",
810
+ USER_EXISTS: "USER_EXISTS",
811
+ VERIFICATION_EXPIRED: "VERIFICATION_EXPIRED",
812
+ VERIFICATION_MISMATCH: "VERIFICATION_MISMATCH",
813
+ VERIFICATION_NOT_FOUND: "VERIFICATION_NOT_FOUND",
814
+ TOO_MANY_ATTEMPTS: "TOO_MANY_ATTEMPTS",
815
+ REQUIRES_VERIFICATION: "REQUIRES_VERIFICATION",
816
+ UNAUTHORIZED: "UNAUTHORIZED",
817
+ ACCESS_DENIED: "ACCESS_DENIED",
818
+ HAS_NO_PASSWORD: "HAS_NO_PASSWORD"
819
+ };
820
+
821
+ // src/lib/session.ts
822
+ import { dayjs } from "@mesob/common";
823
+ var generateHandle = (seed) => {
824
+ const base = seed?.replace(/[^a-zA-Z0-9]/g, "").toLowerCase() || `user${Math.random().toString(36).slice(2, 8)}`;
825
+ return `${base}-${Math.random().toString(36).slice(2, 6)}`;
826
+ };
827
+ var parseDuration = (duration) => {
828
+ const match = duration.match(/^(\d+)([smhd])$/);
829
+ if (!match) {
830
+ throw new Error(`Invalid duration format: ${duration}`);
831
+ }
832
+ const value = Number.parseInt(match[1], 10);
833
+ const unit = match[2];
834
+ const multipliers = {
835
+ s: 1,
836
+ m: 60,
837
+ h: 3600,
838
+ d: 86400
839
+ };
840
+ return value * (multipliers[unit] || 1);
841
+ };
842
+ var addDuration = (duration) => {
843
+ const seconds = parseDuration(duration);
844
+ return dayjs().add(seconds, "second").toISOString();
845
+ };
846
+ var generateOtpCode = (length = 6) => {
847
+ const digits = "0123456789";
848
+ let code = "";
849
+ for (let i = 0; i < length; i++) {
850
+ code += digits[Math.floor(Math.random() * digits.length)];
851
+ }
852
+ return code;
853
+ };
854
+
855
+ // src/routes/handler/email-verification-confirm.ts
856
+ var emailVerificationConfirmHandler = async (c) => {
857
+ const body = c.req.valid("json");
858
+ const { config, database, tenantId } = c.var;
859
+ const resolvedTenantId = ensureTenantId(config, tenantId);
860
+ const { verificationId, code } = body;
861
+ const verification = await findVerificationById(database, verificationId);
862
+ if (!verification) {
863
+ throw new HTTPException2(400, {
864
+ message: AUTH_ERRORS.VERIFICATION_NOT_FOUND
865
+ });
866
+ }
867
+ if (new Date(verification.expiresAt) < /* @__PURE__ */ new Date()) {
868
+ throw new HTTPException2(400, {
869
+ message: AUTH_ERRORS.VERIFICATION_EXPIRED
870
+ });
871
+ }
872
+ const hashedCode = await hashToken(code, config.secret);
873
+ if (verification.code !== hashedCode) {
874
+ await updateVerificationAttempt(database, verificationId);
875
+ throw new HTTPException2(400, {
876
+ message: AUTH_ERRORS.VERIFICATION_MISMATCH
877
+ });
878
+ }
879
+ await updateUserVerified(
880
+ database,
881
+ resolvedTenantId,
882
+ verification.userId,
883
+ "email"
884
+ );
885
+ await consumeVerification(database, verificationId);
886
+ const user = await findUserById(
887
+ database,
888
+ resolvedTenantId,
889
+ verification.userId
890
+ );
891
+ if (!user) {
892
+ throw new HTTPException2(500, { message: "User not found" });
893
+ }
894
+ const sessionToken = crypto.randomUUID();
895
+ const hashedToken = await hashToken(sessionToken, config.secret);
896
+ const expiresAt = addDuration(config.session.expiresIn);
897
+ const session = await insertSession(database, {
898
+ tenantId: resolvedTenantId,
899
+ userId: user.id,
900
+ token: hashedToken,
901
+ expiresAt,
902
+ userAgent: c.req.header("user-agent") || null,
903
+ ip: c.req.header("cf-connecting-ip") || c.req.header("x-forwarded-for") || null,
904
+ meta: { action: "email-verification" }
905
+ });
906
+ setCookie(c, "session_token", sessionToken, {
907
+ httpOnly: true,
908
+ secure: process.env.NODE_ENV === "production",
909
+ sameSite: "Lax",
910
+ path: "/",
911
+ expires: new Date(expiresAt)
912
+ });
913
+ return c.json({
914
+ user,
915
+ session: {
916
+ id: session.id,
917
+ expiresAt: session.expiresAt,
918
+ createdAt: session.createdAt,
919
+ userAgent: session.userAgent,
920
+ ip: session.ip
921
+ },
922
+ sessionToken,
923
+ sessionExpiresAt: session.expiresAt
924
+ });
925
+ };
926
+
927
+ // src/routes/handler/email-verification-request.ts
928
+ import { HTTPException as HTTPException3 } from "hono/http-exception";
929
+
930
+ // src/db/orm/iam/verifications/delete-verifications-by-user-and-type.ts
931
+ import { and as and6, eq as eq9 } from "drizzle-orm";
932
+ var deleteVerificationsByUserAndType = (db, tenantId, userId, type) => {
933
+ return db.delete(verificationsInIam).where(
934
+ and6(
935
+ eq9(verificationsInIam.tenantId, tenantId),
936
+ eq9(verificationsInIam.userId, userId),
937
+ eq9(verificationsInIam.type, type)
938
+ )
939
+ );
940
+ };
941
+
942
+ // src/db/orm/iam/verifications/insert-verification.ts
943
+ var insertVerification = async (db, data) => {
944
+ return await db.insert(verificationsInIam).values({
945
+ tenantId: data.tenantId,
946
+ userId: data.userId,
947
+ type: data.type,
948
+ code: data.code,
949
+ expiresAt: data.expiresAt,
950
+ to: data.to || null,
951
+ attempt: 0
952
+ }).returning({
953
+ id: verificationsInIam.id,
954
+ tenantId: verificationsInIam.tenantId,
955
+ userId: verificationsInIam.userId,
956
+ type: verificationsInIam.type,
957
+ code: verificationsInIam.code,
958
+ to: verificationsInIam.to,
959
+ expiresAt: verificationsInIam.expiresAt,
960
+ createdAt: verificationsInIam.createdAt,
961
+ attempt: verificationsInIam.attempt
962
+ }).then(([verification]) => verification);
963
+ };
964
+
965
+ // src/lib/send-email.ts
966
+ import { Resend } from "resend";
967
+ var SendEmail = async ({
968
+ key,
969
+ provider,
970
+ to,
971
+ subject,
972
+ html,
973
+ text: text2,
974
+ from
975
+ }) => {
976
+ switch (provider) {
977
+ case "resend":
978
+ return await sendEmailWithResend(key, to, subject, html, text2, from);
979
+ default:
980
+ throw new Error(`Unsupported email provider: ${provider}`);
981
+ }
982
+ };
983
+ var sendEmailWithResend = async (key, to, subject, html, text2, from) => {
984
+ const resend = new Resend(key);
985
+ return await resend.emails.send({
986
+ from,
987
+ to,
988
+ subject,
989
+ html,
990
+ text: text2
991
+ });
992
+ };
993
+
994
+ // src/providers/resend-email.ts
995
+ var ResendEmailProvider = class {
996
+ config;
997
+ constructor(config) {
998
+ this.config = config;
999
+ }
1000
+ async sendVerificationEmail(email, code, tenantName) {
1001
+ const subject = this.config.verificationSubject || `Verify your email${tenantName ? ` for ${tenantName}` : ""}`;
1002
+ const verificationPath = this.config.verificationPath || "/verify-email";
1003
+ const verificationUrl = `${this.config.frontendBaseUrl}${verificationPath}?token=${code}`;
1004
+ const html = `
1005
+ <!DOCTYPE html>
1006
+ <html>
1007
+ <head>
1008
+ <meta charset="utf-8">
1009
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
1010
+ <title>${subject}</title>
1011
+ </head>
1012
+ <body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;">
1013
+ <div style="max-width: 600px; margin: 0 auto; padding: 20px;">
1014
+ <h1 style="color: #2563eb;">Verify Your Email</h1>
1015
+ <p>Your verification code is:</p>
1016
+ <div style="background: #f3f4f6; padding: 20px; text-align: center; font-size: 32px; font-weight: bold; letter-spacing: 8px; margin: 20px 0;">
1017
+ ${code}
1018
+ </div>
1019
+ <p>Or click the link below:</p>
1020
+ <p>
1021
+ <a href="${verificationUrl}" style="background: #2563eb; color: white; padding: 12px 24px; text-decoration: none; border-radius: 6px; display: inline-block;">
1022
+ Verify Email
1023
+ </a>
1024
+ </p>
1025
+ <p style="color: #6b7280; font-size: 14px; margin-top: 30px;">
1026
+ This code will expire in 1 hour. If you didn't request this, please ignore this email.
1027
+ </p>
1028
+ </div>
1029
+ </body>
1030
+ </html>
1031
+ `;
1032
+ const text2 = `Your verification code is: ${code}
1033
+
1034
+ Or visit: ${verificationUrl}
1035
+
1036
+ This code will expire in 1 hour.`;
1037
+ await this.sendEmail({
1038
+ to: [email],
1039
+ subject,
1040
+ html,
1041
+ text: text2
1042
+ });
1043
+ }
1044
+ async sendPasswordResetEmail(email, code, tenantName) {
1045
+ const subject = this.config.resetPasswordSubject || `Reset your password${tenantName ? ` for ${tenantName}` : ""}`;
1046
+ const resetPath = this.config.resetPasswordPath || "/reset-password";
1047
+ const resetUrl = `${this.config.frontendBaseUrl}${resetPath}?token=${code}`;
1048
+ const html = `
1049
+ <!DOCTYPE html>
1050
+ <html>
1051
+ <head>
1052
+ <meta charset="utf-8">
1053
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
1054
+ <title>${subject}</title>
1055
+ </head>
1056
+ <body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;">
1057
+ <div style="max-width: 600px; margin: 0 auto; padding: 20px;">
1058
+ <h1 style="color: #2563eb;">Reset Your Password</h1>
1059
+ <p>Your password reset code is:</p>
1060
+ <div style="background: #f3f4f6; padding: 20px; text-align: center; font-size: 32px; font-weight: bold; letter-spacing: 8px; margin: 20px 0;">
1061
+ ${code}
1062
+ </div>
1063
+ <p>Or click the link below:</p>
1064
+ <p>
1065
+ <a href="${resetUrl}" style="background: #2563eb; color: white; padding: 12px 24px; text-decoration: none; border-radius: 6px; display: inline-block;">
1066
+ Reset Password
1067
+ </a>
1068
+ </p>
1069
+ <p style="color: #6b7280; font-size: 14px; margin-top: 30px;">
1070
+ This code will expire in 1 hour. If you didn't request this, please ignore this email.
1071
+ </p>
1072
+ </div>
1073
+ </body>
1074
+ </html>
1075
+ `;
1076
+ const text2 = `Your password reset code is: ${code}
1077
+
1078
+ Or visit: ${resetUrl}
1079
+
1080
+ This code will expire in 1 hour.`;
1081
+ await this.sendEmail({
1082
+ to: [email],
1083
+ subject,
1084
+ html,
1085
+ text: text2
1086
+ });
1087
+ }
1088
+ async sendEmail(data) {
1089
+ const res = await SendEmail({
1090
+ key: this.config.apiKey,
1091
+ provider: "resend",
1092
+ to: data.to,
1093
+ subject: data.subject,
1094
+ html: data.html,
1095
+ text: data.text,
1096
+ from: this.config.from
1097
+ });
1098
+ if (res.error) {
1099
+ throw new Error(res.error.message);
1100
+ }
1101
+ }
1102
+ };
1103
+
1104
+ // src/routes/handler/email-verification-request.ts
1105
+ var emailVerificationRequestHandler = async (c) => {
1106
+ const body = c.req.valid("json");
1107
+ const { config, database, tenantId, user } = c.var;
1108
+ const resolvedTenantId = ensureTenantId(config, tenantId);
1109
+ const email = body.email || user?.email;
1110
+ if (!email) {
1111
+ throw new HTTPException3(400, { message: "Email required" });
1112
+ }
1113
+ let userId = user?.id;
1114
+ if (!userId) {
1115
+ const lookup = await findUserByIdentifier(
1116
+ database,
1117
+ resolvedTenantId,
1118
+ email
1119
+ );
1120
+ if (!lookup.user) {
1121
+ throw new HTTPException3(404, { message: AUTH_ERRORS.USER_NOT_FOUND });
1122
+ }
1123
+ userId = lookup.user.id;
1124
+ }
1125
+ await deleteVerificationsByUserAndType(
1126
+ database,
1127
+ resolvedTenantId,
1128
+ userId,
1129
+ "email-verification"
1130
+ );
1131
+ const code = generateOtpCode(6);
1132
+ const hashedCode = await hashToken(code, config.secret);
1133
+ const expiresAt = addDuration(config.email.verificationExpiresIn);
1134
+ const verification = await insertVerification(database, {
1135
+ tenantId: resolvedTenantId,
1136
+ userId,
1137
+ type: "email-verification",
1138
+ code: hashedCode,
1139
+ expiresAt,
1140
+ to: email
1141
+ });
1142
+ const emailProvider = new ResendEmailProvider(config.email.resend);
1143
+ await emailProvider.sendVerificationEmail(email, code);
1144
+ return c.json({ verificationId: verification.id });
1145
+ };
1146
+
1147
+ // src/routes/handler/me.ts
1148
+ import { HTTPException as HTTPException4 } from "hono/http-exception";
1149
+ var meHandler = (c) => {
1150
+ const { user } = c.var;
1151
+ if (!user) {
1152
+ throw new HTTPException4(401, { message: "Unauthorized" });
1153
+ }
1154
+ return c.json({ user });
1155
+ };
1156
+
1157
+ // src/routes/handler/password-change.ts
1158
+ import { HTTPException as HTTPException5 } from "hono/http-exception";
1159
+
1160
+ // src/db/orm/iam/accounts/find-account-by-provider.ts
1161
+ import { and as and7, eq as eq10 } from "drizzle-orm";
1162
+ var findAccountByProvider = (db, tenantId, userId, provider) => {
1163
+ return db.select({
1164
+ id: accountsInIam.id,
1165
+ tenantId: accountsInIam.tenantId,
1166
+ userId: accountsInIam.userId,
1167
+ provider: accountsInIam.provider,
1168
+ providerAccountId: accountsInIam.providerAccountId,
1169
+ password: accountsInIam.password
1170
+ }).from(accountsInIam).where(
1171
+ and7(
1172
+ eq10(accountsInIam.tenantId, tenantId),
1173
+ eq10(accountsInIam.userId, userId),
1174
+ eq10(accountsInIam.provider, provider)
1175
+ )
1176
+ ).limit(1).then(([account]) => account || null);
1177
+ };
1178
+
1179
+ // src/db/orm/iam/accounts/update-account-password.ts
1180
+ import { and as and8, eq as eq11 } from "drizzle-orm";
1181
+ var updateAccountPassword = (db, tenantId, userId, password) => {
1182
+ return db.update(accountsInIam).set({ password }).where(
1183
+ and8(
1184
+ eq11(accountsInIam.tenantId, tenantId),
1185
+ eq11(accountsInIam.userId, userId),
1186
+ eq11(accountsInIam.provider, "credentials")
1187
+ )
1188
+ );
1189
+ };
1190
+
1191
+ // src/db/orm/iam/sessions/delete-session-by-id.ts
1192
+ import { eq as eq12 } from "drizzle-orm";
1193
+ var deleteSessionById = (db, sessionId) => {
1194
+ return db.delete(sessionsInIam).where(eq12(sessionsInIam.id, sessionId));
1195
+ };
1196
+
1197
+ // src/db/orm/iam/sessions/list-sessions-for-user.ts
1198
+ import { and as and9, asc, eq as eq13, gt as gt2 } from "drizzle-orm";
1199
+ var listSessionsForUser = (db, tenantId, userId) => {
1200
+ return db.select({
1201
+ id: sessionsInIam.id,
1202
+ tenantId: sessionsInIam.tenantId,
1203
+ userId: sessionsInIam.userId,
1204
+ expiresAt: sessionsInIam.expiresAt,
1205
+ createdAt: sessionsInIam.createdAt,
1206
+ updatedAt: sessionsInIam.updatedAt,
1207
+ userAgent: sessionsInIam.userAgent,
1208
+ ip: sessionsInIam.ip
1209
+ }).from(sessionsInIam).where(
1210
+ and9(
1211
+ eq13(sessionsInIam.tenantId, tenantId),
1212
+ eq13(sessionsInIam.userId, userId),
1213
+ gt2(sessionsInIam.expiresAt, (/* @__PURE__ */ new Date()).toISOString())
1214
+ )
1215
+ ).orderBy(asc(sessionsInIam.createdAt)).then((sessions) => sessions);
1216
+ };
1217
+
1218
+ // src/routes/handler/password-change.ts
1219
+ var changePasswordHandler = async (c) => {
1220
+ const body = c.req.valid("json");
1221
+ const { config, database, tenantId, userId, user } = c.var;
1222
+ if (!userId) {
1223
+ throw new HTTPException5(401, { message: AUTH_ERRORS.UNAUTHORIZED });
1224
+ }
1225
+ if (!user) {
1226
+ throw new HTTPException5(401, { message: AUTH_ERRORS.UNAUTHORIZED });
1227
+ }
1228
+ const resolvedTenantId = ensureTenantId(config, tenantId);
1229
+ const { currentPassword, newPassword } = body;
1230
+ const account = await findAccountByProvider(
1231
+ database,
1232
+ resolvedTenantId,
1233
+ userId,
1234
+ "credentials"
1235
+ );
1236
+ if (!account?.password) {
1237
+ throw new HTTPException5(401, { message: AUTH_ERRORS.HAS_NO_PASSWORD });
1238
+ }
1239
+ const passwordValid = await verifyPassword(currentPassword, account.password);
1240
+ if (!passwordValid) {
1241
+ throw new HTTPException5(401, { message: AUTH_ERRORS.INVALID_PASSWORD });
1242
+ }
1243
+ const passwordHash = await hashPassword(newPassword);
1244
+ await updateAccountPassword(database, resolvedTenantId, userId, passwordHash);
1245
+ const sessions = await listSessionsForUser(
1246
+ database,
1247
+ resolvedTenantId,
1248
+ userId
1249
+ );
1250
+ const currentSessionToken = c.req.cookie("session_token");
1251
+ if (currentSessionToken) {
1252
+ const hashedToken = await hashToken(currentSessionToken, config.secret);
1253
+ const currentSession = await findSessionByToken(database, hashedToken);
1254
+ if (currentSession) {
1255
+ for (const session of sessions) {
1256
+ if (session.id !== currentSession.id) {
1257
+ await deleteSessionById(database, session.id);
1258
+ }
1259
+ }
1260
+ }
1261
+ }
1262
+ return c.json({ message: "Password updated" });
1263
+ };
1264
+
1265
+ // src/providers/afro-sms.ts
1266
+ var AfroSmsProvider = class {
1267
+ config;
1268
+ constructor(config) {
1269
+ this.config = config;
1270
+ }
1271
+ async sendVerificationSms(phone, code) {
1272
+ const template = this.config.templateVerification || "Your verification code is {{code}}";
1273
+ const message = template.replace("{{code}}", code);
1274
+ const sms = {
1275
+ to: phone,
1276
+ message
1277
+ };
1278
+ await this.sendSms(this.config, sms);
1279
+ }
1280
+ async sendLoginSms(phone, code) {
1281
+ const template = this.config.templateLogin || "Your login code is {{code}}";
1282
+ const message = template.replace("{{code}}", code);
1283
+ const sms = {
1284
+ to: phone,
1285
+ message
1286
+ };
1287
+ await this.sendSms(this.config, sms);
1288
+ }
1289
+ async sendSms(config, sms) {
1290
+ const baseUrl = `${config.baseUrl}/send`;
1291
+ const identifierId = config?.identifierId;
1292
+ const senderName = config?.senderName;
1293
+ const key = config?.apiKey;
1294
+ if (!(baseUrl && identifierId && senderName && key)) {
1295
+ throw new Error("SMS configuration is not set");
1296
+ }
1297
+ const myHeaders = new Headers();
1298
+ myHeaders.append("Authorization", `Bearer ${key}`);
1299
+ myHeaders.append("Content-Type", "application/json");
1300
+ const sendSMS = {
1301
+ from: identifierId,
1302
+ sender: "",
1303
+ to: sms.to,
1304
+ message: sms.message,
1305
+ callback: sms?.callback
1306
+ };
1307
+ const requestOptions = {
1308
+ method: "POST",
1309
+ headers: myHeaders,
1310
+ body: JSON.stringify(sendSMS)
1311
+ };
1312
+ const response = await fetch(baseUrl, requestOptions);
1313
+ if (!response.ok) {
1314
+ throw new Error(`Failed to send SMS: ${response.statusText}`);
1315
+ }
1316
+ return response;
1317
+ }
1318
+ };
1319
+
1320
+ // src/routes/handler/password-forgot.ts
1321
+ var forgotPasswordHandler = async (c) => {
1322
+ const body = c.req.valid("json");
1323
+ const { config, database, tenantId } = c.var;
1324
+ const resolvedTenantId = ensureTenantId(config, tenantId);
1325
+ const { identifier } = body;
1326
+ const lookup = await findUserByIdentifier(
1327
+ database,
1328
+ resolvedTenantId,
1329
+ identifier
1330
+ );
1331
+ if (!lookup.user) {
1332
+ return c.json({ message: "If account exists, reset code sent" });
1333
+ }
1334
+ const isEmail = lookup.type === "email";
1335
+ let verificationId;
1336
+ if (isEmail) {
1337
+ const code = generateOtpCode(6);
1338
+ const hashedCode = await hashToken(code, config.secret);
1339
+ const expiresAt = addDuration(config.email.resetPasswordExpiresIn);
1340
+ const verification = await insertVerification(database, {
1341
+ tenantId: resolvedTenantId,
1342
+ userId: lookup.user.id,
1343
+ type: "password-reset",
1344
+ code: hashedCode,
1345
+ expiresAt,
1346
+ to: lookup.user.email
1347
+ });
1348
+ verificationId = verification.id;
1349
+ const emailProvider = new ResendEmailProvider(config.email.resend);
1350
+ await emailProvider.sendPasswordResetEmail(lookup.user.email, code);
1351
+ } else {
1352
+ const code = generateOtpCode(config.phone.otpLength);
1353
+ const hashedCode = await hashToken(code, config.secret);
1354
+ const expiresAt = addDuration(config.phone.otpExpiresIn);
1355
+ const verification = await insertVerification(database, {
1356
+ tenantId: resolvedTenantId,
1357
+ userId: lookup.user.id,
1358
+ type: "password-reset-otp",
1359
+ code: hashedCode,
1360
+ expiresAt,
1361
+ to: lookup.user.phone
1362
+ });
1363
+ verificationId = verification.id;
1364
+ const smsProvider = new AfroSmsProvider(config.phone.smsConfig);
1365
+ await smsProvider.sendVerificationSms(lookup.user.phone, code);
1366
+ }
1367
+ return c.json({
1368
+ message: "If account exists, reset code sent",
1369
+ verificationId
1370
+ });
1371
+ };
1372
+
1373
+ // src/routes/handler/password-reset.ts
1374
+ import { setCookie as setCookie2 } from "hono/cookie";
1375
+ import { HTTPException as HTTPException6 } from "hono/http-exception";
1376
+ var resetPasswordHandler = async (c) => {
1377
+ const body = c.req.valid("json");
1378
+ const { config, database, tenantId } = c.var;
1379
+ const resolvedTenantId = ensureTenantId(config, tenantId);
1380
+ const { verificationId, code, password } = body;
1381
+ const verification = await findVerificationById(database, verificationId);
1382
+ if (!verification) {
1383
+ throw new HTTPException6(400, {
1384
+ message: AUTH_ERRORS.VERIFICATION_NOT_FOUND
1385
+ });
1386
+ }
1387
+ if (new Date(verification.expiresAt) < /* @__PURE__ */ new Date()) {
1388
+ throw new HTTPException6(400, { message: AUTH_ERRORS.VERIFICATION_EXPIRED });
1389
+ }
1390
+ const hashedCode = await hashToken(code, config.secret);
1391
+ if (verification.code !== hashedCode) {
1392
+ await updateVerificationAttempt(database, verificationId);
1393
+ throw new HTTPException6(400, {
1394
+ message: AUTH_ERRORS.VERIFICATION_MISMATCH
1395
+ });
1396
+ }
1397
+ const passwordHash = await hashPassword(password);
1398
+ await updateAccountPassword(
1399
+ database,
1400
+ resolvedTenantId,
1401
+ verification.userId,
1402
+ passwordHash
1403
+ );
1404
+ const sessions = await listSessionsForUser(
1405
+ database,
1406
+ resolvedTenantId,
1407
+ verification.userId
1408
+ );
1409
+ for (const session2 of sessions) {
1410
+ await deleteSessionById(database, session2.id);
1411
+ }
1412
+ await consumeVerification(database, verificationId);
1413
+ const sessionToken = crypto.randomUUID();
1414
+ const hashedToken = await hashToken(sessionToken, config.secret);
1415
+ const expiresAt = addDuration(config.session.expiresIn);
1416
+ const session = await insertSession(database, {
1417
+ tenantId: resolvedTenantId,
1418
+ userId: verification.userId,
1419
+ token: hashedToken,
1420
+ expiresAt,
1421
+ userAgent: c.req.header("user-agent") || null,
1422
+ ip: c.req.header("cf-connecting-ip") || c.req.header("x-forwarded-for") || null,
1423
+ meta: { action: "password-reset" }
1424
+ });
1425
+ const user = await findUserById(
1426
+ database,
1427
+ resolvedTenantId,
1428
+ verification.userId
1429
+ );
1430
+ if (!user) {
1431
+ throw new HTTPException6(500, { message: "User not found" });
1432
+ }
1433
+ setCookie2(c, "session_token", sessionToken, {
1434
+ httpOnly: true,
1435
+ secure: process.env.NODE_ENV === "production",
1436
+ sameSite: "Lax",
1437
+ path: "/",
1438
+ expires: new Date(expiresAt)
1439
+ });
1440
+ return c.json({
1441
+ user,
1442
+ session: {
1443
+ id: session.id,
1444
+ expiresAt: session.expiresAt,
1445
+ createdAt: session.createdAt,
1446
+ userAgent: session.userAgent,
1447
+ ip: session.ip
1448
+ },
1449
+ sessionToken,
1450
+ sessionExpiresAt: session.expiresAt
1451
+ });
1452
+ };
1453
+
1454
+ // src/routes/handler/phone-verification-confirm.ts
1455
+ import { setCookie as setCookie3 } from "hono/cookie";
1456
+ import { HTTPException as HTTPException7 } from "hono/http-exception";
1457
+ var phoneVerificationConfirmHandler = async (c) => {
1458
+ const body = c.req.valid("json");
1459
+ const { config, database, tenantId } = c.var;
1460
+ const resolvedTenantId = ensureTenantId(config, tenantId);
1461
+ const { verificationId, code, context } = body;
1462
+ const verification = await findVerificationById(database, verificationId);
1463
+ if (!verification) {
1464
+ throw new HTTPException7(400, {
1465
+ message: AUTH_ERRORS.VERIFICATION_NOT_FOUND
1466
+ });
1467
+ }
1468
+ if (new Date(verification.expiresAt) < /* @__PURE__ */ new Date()) {
1469
+ throw new HTTPException7(400, { message: AUTH_ERRORS.VERIFICATION_EXPIRED });
1470
+ }
1471
+ const hashedCode = await hashToken(code, config.secret);
1472
+ if (verification.code !== hashedCode) {
1473
+ await updateVerificationAttempt(database, verificationId);
1474
+ throw new HTTPException7(400, {
1475
+ message: AUTH_ERRORS.VERIFICATION_MISMATCH
1476
+ });
1477
+ }
1478
+ await consumeVerification(database, verificationId);
1479
+ if (context === "change-phone" && verification.userId) {
1480
+ await updateUserVerified(
1481
+ database,
1482
+ resolvedTenantId,
1483
+ verification.userId,
1484
+ "phone"
1485
+ );
1486
+ } else if (context === "sign-in" && verification.userId) {
1487
+ await updateUserVerified(
1488
+ database,
1489
+ resolvedTenantId,
1490
+ verification.userId,
1491
+ "phone"
1492
+ );
1493
+ } else if (context === "sign-up" && verification.userId) {
1494
+ await updateUserVerified(
1495
+ database,
1496
+ resolvedTenantId,
1497
+ verification.userId,
1498
+ "phone"
1499
+ );
1500
+ }
1501
+ const user = verification.userId ? await findUserById(database, resolvedTenantId, verification.userId) : null;
1502
+ if (!user) {
1503
+ throw new HTTPException7(500, { message: "User not found" });
1504
+ }
1505
+ if (context === "sign-in" || context === "change-phone" || context === "sign-up") {
1506
+ const sessionToken = crypto.randomUUID();
1507
+ const hashedToken = await hashToken(sessionToken, config.secret);
1508
+ const expiresAt = addDuration(config.session.expiresIn);
1509
+ const session = await insertSession(database, {
1510
+ tenantId: resolvedTenantId,
1511
+ userId: user.id,
1512
+ token: hashedToken,
1513
+ expiresAt,
1514
+ userAgent: c.req.header("user-agent") || null,
1515
+ ip: c.req.header("cf-connecting-ip") || c.req.header("x-forwarded-for") || null,
1516
+ meta: {
1517
+ action: context === "sign-up" ? "phone-verification-sign-up" : `phone-verification-${context}`
1518
+ }
1519
+ });
1520
+ setCookie3(c, "session_token", sessionToken, {
1521
+ httpOnly: true,
1522
+ secure: process.env.NODE_ENV === "production",
1523
+ sameSite: "Lax",
1524
+ path: "/",
1525
+ expires: new Date(expiresAt)
1526
+ });
1527
+ return c.json({
1528
+ user,
1529
+ session: {
1530
+ id: session.id,
1531
+ expiresAt: session.expiresAt,
1532
+ createdAt: session.createdAt,
1533
+ userAgent: session.userAgent,
1534
+ ip: session.ip
1535
+ },
1536
+ sessionToken,
1537
+ sessionExpiresAt: session.expiresAt
1538
+ });
1539
+ }
1540
+ return c.json({
1541
+ user: user || null,
1542
+ session: null,
1543
+ verified: true
1544
+ });
1545
+ };
1546
+
1547
+ // src/routes/handler/phone-verification-request.ts
1548
+ import { HTTPException as HTTPException8 } from "hono/http-exception";
1549
+ var phoneVerificationRequestHandler = async (c) => {
1550
+ const body = c.req.valid("json");
1551
+ const { config, database, tenantId, user } = c.var;
1552
+ const resolvedTenantId = ensureTenantId(config, tenantId);
1553
+ const { phone, context } = body;
1554
+ if (!phone) {
1555
+ throw new HTTPException8(400, { message: "Phone required" });
1556
+ }
1557
+ let userId = user?.id;
1558
+ if (!userId) {
1559
+ const lookup = await findUserByIdentifier(
1560
+ database,
1561
+ resolvedTenantId,
1562
+ phone
1563
+ );
1564
+ if (!lookup.user) {
1565
+ throw new HTTPException8(404, { message: AUTH_ERRORS.USER_NOT_FOUND });
1566
+ }
1567
+ userId = lookup.user.id;
1568
+ }
1569
+ if (!userId) {
1570
+ throw new HTTPException8(404, { message: AUTH_ERRORS.USER_NOT_FOUND });
1571
+ }
1572
+ await deleteVerificationsByUserAndType(
1573
+ database,
1574
+ resolvedTenantId,
1575
+ userId,
1576
+ `phone-otp-${context}`
1577
+ );
1578
+ const code = generateOtpCode(config.phone.otpLength);
1579
+ const hashedCode = await hashToken(code, config.secret);
1580
+ const expiresAt = addDuration(config.phone.otpExpiresIn);
1581
+ const verification = await insertVerification(database, {
1582
+ tenantId: resolvedTenantId,
1583
+ userId,
1584
+ type: `phone-otp-${context}`,
1585
+ code: hashedCode,
1586
+ expiresAt,
1587
+ to: phone
1588
+ });
1589
+ const smsProvider = new AfroSmsProvider(config.phone.smsConfig);
1590
+ await smsProvider.sendVerificationSms(phone, code);
1591
+ return c.json({ verificationId: verification.id });
1592
+ };
1593
+
1594
+ // src/routes/handler/session.ts
1595
+ var sessionHandler = (c) => {
1596
+ const { user, session } = c.var;
1597
+ return c.json({
1598
+ user: user || null,
1599
+ session: session ? {
1600
+ id: session.id,
1601
+ expiresAt: session.expiresAt,
1602
+ createdAt: session.createdAt,
1603
+ userAgent: session.userAgent,
1604
+ ip: session.ip
1605
+ } : null
1606
+ });
1607
+ };
1608
+
1609
+ // src/routes/handler/sign-in.ts
1610
+ import { setCookie as setCookie4 } from "hono/cookie";
1611
+ import { HTTPException as HTTPException9 } from "hono/http-exception";
1612
+
1613
+ // src/db/orm/iam/sessions/delete-oldest-sessions.ts
1614
+ var deleteOldestSessions = async (db, tenantId, userId, keepCount) => {
1615
+ const sessions = await listSessionsForUser(db, tenantId, userId);
1616
+ if (sessions.length <= keepCount) {
1617
+ return;
1618
+ }
1619
+ const toDelete = sessions.slice(0, sessions.length - keepCount);
1620
+ for (const session of toDelete) {
1621
+ await deleteSessionById(db, session.id);
1622
+ }
1623
+ };
1624
+
1625
+ // src/db/orm/iam/users/update-last-sign-in.ts
1626
+ import { and as and10, eq as eq14 } from "drizzle-orm";
1627
+ var updateLastSignIn = (db, tenantId, userId) => {
1628
+ return db.update(usersInIam).set({ lastSignInAt: (/* @__PURE__ */ new Date()).toISOString(), loginAttempt: 0 }).where(and10(eq14(usersInIam.id, userId), eq14(usersInIam.tenantId, tenantId)));
1629
+ };
1630
+
1631
+ // src/routes/handler/sign-in.ts
1632
+ var signInHandler = async (c) => {
1633
+ const body = c.req.valid("json");
1634
+ const { config, database, tenantId } = c.var;
1635
+ const resolvedTenantId = ensureTenantId(config, tenantId);
1636
+ const { identifier, password } = body;
1637
+ const lookup = await findUserByIdentifier(
1638
+ database,
1639
+ resolvedTenantId,
1640
+ identifier
1641
+ );
1642
+ if (!lookup.user) {
1643
+ throw new HTTPException9(401, { message: AUTH_ERRORS.USER_NOT_FOUND });
1644
+ }
1645
+ const account = await findAccountByProvider(
1646
+ database,
1647
+ resolvedTenantId,
1648
+ lookup.user.id,
1649
+ "credentials"
1650
+ );
1651
+ if (!account?.password) {
1652
+ throw new HTTPException9(401, { message: AUTH_ERRORS.HAS_NO_PASSWORD });
1653
+ }
1654
+ const passwordValid = await verifyPassword(password, account.password);
1655
+ if (!passwordValid) {
1656
+ throw new HTTPException9(401, { message: AUTH_ERRORS.INVALID_PASSWORD });
1657
+ }
1658
+ const isEmail = lookup.type === "email";
1659
+ const isVerified = isEmail ? lookup.user.emailVerified : lookup.user.phoneVerified;
1660
+ if (isEmail && config.email.verificationRequired && !isVerified) {
1661
+ const code = generateOtpCode(6);
1662
+ const hashedCode = await hashToken(code, config.secret);
1663
+ const expiresAt2 = addDuration(config.email.verificationExpiresIn);
1664
+ const verification = await insertVerification(database, {
1665
+ tenantId: resolvedTenantId,
1666
+ userId: lookup.user.id,
1667
+ type: "email-verification",
1668
+ code: hashedCode,
1669
+ expiresAt: expiresAt2,
1670
+ to: lookup.user.email
1671
+ });
1672
+ const emailProvider = new ResendEmailProvider(config.email.resend);
1673
+ await emailProvider.sendVerificationEmail(lookup.user.email, code);
1674
+ return c.json({
1675
+ verificationId: verification.id,
1676
+ requiresVerification: true
1677
+ });
1678
+ }
1679
+ if (!isEmail && config.phone.verificationRequired && !isVerified) {
1680
+ const code = generateOtpCode(config.phone.otpLength);
1681
+ const hashedCode = await hashToken(code, config.secret);
1682
+ const expiresAt2 = addDuration(config.phone.otpExpiresIn);
1683
+ const verification = await insertVerification(database, {
1684
+ tenantId: resolvedTenantId,
1685
+ userId: lookup.user.id,
1686
+ type: "phone-otp",
1687
+ code: hashedCode,
1688
+ expiresAt: expiresAt2,
1689
+ to: lookup.user.phone
1690
+ });
1691
+ const smsProvider = new AfroSmsProvider(config.phone.smsConfig);
1692
+ await smsProvider.sendVerificationSms(lookup.user.phone, code);
1693
+ return c.json({
1694
+ verificationId: verification.id,
1695
+ requiresVerification: true
1696
+ });
1697
+ }
1698
+ if (config.session.maxPerUser) {
1699
+ await deleteOldestSessions(
1700
+ database,
1701
+ resolvedTenantId,
1702
+ lookup.user.id,
1703
+ config.session.maxPerUser
1704
+ );
1705
+ }
1706
+ await updateLastSignIn(database, resolvedTenantId, lookup.user.id);
1707
+ const sessionToken = crypto.randomUUID();
1708
+ const hashedToken = await hashToken(sessionToken, config.secret);
1709
+ const expiresAt = addDuration(config.session.expiresIn);
1710
+ const session = await insertSession(database, {
1711
+ tenantId: resolvedTenantId,
1712
+ userId: lookup.user.id,
1713
+ token: hashedToken,
1714
+ expiresAt,
1715
+ userAgent: c.req.header("user-agent") || null,
1716
+ ip: c.req.header("cf-connecting-ip") || c.req.header("x-forwarded-for") || null,
1717
+ meta: { action: "sign-in" }
1718
+ });
1719
+ setCookie4(c, "session_token", sessionToken, {
1720
+ httpOnly: true,
1721
+ secure: process.env.NODE_ENV === "production",
1722
+ sameSite: "Lax",
1723
+ path: "/",
1724
+ expires: new Date(expiresAt)
1725
+ });
1726
+ return c.json({
1727
+ user: lookup.user,
1728
+ session: {
1729
+ id: session.id,
1730
+ expiresAt: session.expiresAt,
1731
+ createdAt: session.createdAt,
1732
+ userAgent: session.userAgent,
1733
+ ip: session.ip
1734
+ },
1735
+ sessionToken,
1736
+ sessionExpiresAt: session.expiresAt
1737
+ });
1738
+ };
1739
+
1740
+ // src/routes/handler/sign-out.ts
1741
+ import { deleteCookie, getCookie } from "hono/cookie";
1742
+ var signOutHandler = async (c) => {
1743
+ const { config, database, tenantId } = c.var;
1744
+ ensureTenantId(config, tenantId);
1745
+ const sessionToken = getCookie(c, "session_token");
1746
+ if (!sessionToken) {
1747
+ return c.json({ message: "Signed out" });
1748
+ }
1749
+ const hashedToken = await hashToken(sessionToken, config.secret);
1750
+ const session = await findSessionByToken(database, hashedToken);
1751
+ if (session) {
1752
+ await deleteSessionById(database, session.id);
1753
+ }
1754
+ deleteCookie(c, "session_token", {
1755
+ httpOnly: true,
1756
+ secure: process.env.NODE_ENV === "production",
1757
+ sameSite: "Lax",
1758
+ path: "/",
1759
+ expires: /* @__PURE__ */ new Date(0)
1760
+ });
1761
+ return c.json({ message: "Signed out" });
1762
+ };
1763
+
1764
+ // src/routes/handler/sign-up.ts
1765
+ import { setCookie as setCookie5 } from "hono/cookie";
1766
+ import { HTTPException as HTTPException10 } from "hono/http-exception";
1767
+
1768
+ // src/db/orm/iam/accounts/insert-credentials-account.ts
1769
+ var insertCredentialsAccount = (db, data) => {
1770
+ return db.insert(accountsInIam).values({
1771
+ tenantId: data.tenantId,
1772
+ userId: data.userId,
1773
+ provider: "credentials",
1774
+ providerAccountId: data.providerAccountId,
1775
+ password: data.password
1776
+ });
1777
+ };
1778
+
1779
+ // src/db/orm/iam/users/find-user-by-handle.ts
1780
+ import { and as and11, eq as eq15, sql as sql3 } from "drizzle-orm";
1781
+ var findUserByHandle = (db, tenantId, handle) => {
1782
+ return db.select({
1783
+ id: usersInIam.id,
1784
+ tenantId: usersInIam.tenantId,
1785
+ fullName: usersInIam.fullName,
1786
+ email: usersInIam.email,
1787
+ phone: usersInIam.phone,
1788
+ handle: usersInIam.handle,
1789
+ image: usersInIam.image,
1790
+ emailVerified: usersInIam.emailVerified,
1791
+ phoneVerified: usersInIam.phoneVerified,
1792
+ lastSignInAt: usersInIam.lastSignInAt
1793
+ }).from(usersInIam).where(
1794
+ and11(
1795
+ eq15(usersInIam.tenantId, tenantId),
1796
+ sql3`lower(${usersInIam.handle}) = lower(${handle})`
1797
+ )
1798
+ ).limit(1).then(([user]) => user || null);
1799
+ };
1800
+
1801
+ // src/db/orm/iam/users/insert-user.ts
1802
+ var insertUser = (db, data) => {
1803
+ return db.insert(usersInIam).values({
1804
+ tenantId: data.tenantId,
1805
+ fullName: data.fullName,
1806
+ handle: data.handle,
1807
+ email: data.email || null,
1808
+ phone: data.phone || null,
1809
+ image: data.image || null,
1810
+ emailVerified: Boolean(data.emailVerified),
1811
+ phoneVerified: Boolean(data.phoneVerified)
1812
+ }).returning({
1813
+ id: usersInIam.id,
1814
+ tenantId: usersInIam.tenantId,
1815
+ fullName: usersInIam.fullName,
1816
+ email: usersInIam.email,
1817
+ phone: usersInIam.phone,
1818
+ handle: usersInIam.handle,
1819
+ image: usersInIam.image,
1820
+ emailVerified: usersInIam.emailVerified,
1821
+ phoneVerified: usersInIam.phoneVerified,
1822
+ lastSignInAt: usersInIam.lastSignInAt
1823
+ }).then(([user]) => user);
1824
+ };
1825
+
1826
+ // src/routes/handler/sign-up.ts
1827
+ var signUpHandler = async (c) => {
1828
+ const body = c.req.valid("json");
1829
+ const { config, database, tenantId } = c.var;
1830
+ const resolvedTenantId = ensureTenantId(config, tenantId);
1831
+ const { email, phone, password, fullName, handle } = body;
1832
+ if (!(email || phone)) {
1833
+ throw new HTTPException10(400, {
1834
+ message: "Either email or phone is required"
1835
+ });
1836
+ }
1837
+ const identifier = email || phone;
1838
+ const existing = await findUserByIdentifier(
1839
+ database,
1840
+ resolvedTenantId,
1841
+ identifier
1842
+ );
1843
+ if (existing.user) {
1844
+ throw new HTTPException10(409, { message: AUTH_ERRORS.USER_EXISTS });
1845
+ }
1846
+ const userHandle = handle || generateHandle(email || phone);
1847
+ const existingHandle = await findUserByHandle(
1848
+ database,
1849
+ resolvedTenantId,
1850
+ userHandle
1851
+ );
1852
+ if (existingHandle) {
1853
+ throw new HTTPException10(409, { message: "Handle already taken" });
1854
+ }
1855
+ const user = await insertUser(database, {
1856
+ tenantId: resolvedTenantId,
1857
+ fullName,
1858
+ handle: userHandle,
1859
+ email: email || null,
1860
+ phone: phone || null,
1861
+ emailVerified: email ? !config.email.verificationRequired : false,
1862
+ phoneVerified: phone ? !config.phone.verificationRequired : false
1863
+ });
1864
+ const passwordHash = await hashPassword(password);
1865
+ await insertCredentialsAccount(database, {
1866
+ tenantId: resolvedTenantId,
1867
+ userId: user.id,
1868
+ providerAccountId: identifier,
1869
+ password: passwordHash
1870
+ });
1871
+ if (phone && config.phone.verificationRequired) {
1872
+ const code = generateOtpCode(config.phone.otpLength);
1873
+ const hashedCode = await hashToken(code, config.secret);
1874
+ const expiresAt2 = addDuration(config.phone.otpExpiresIn);
1875
+ const verification = await insertVerification(database, {
1876
+ tenantId: resolvedTenantId,
1877
+ userId: user.id,
1878
+ type: "phone-otp-sign-up",
1879
+ code: hashedCode,
1880
+ expiresAt: expiresAt2,
1881
+ to: phone
1882
+ });
1883
+ const smsProvider = new AfroSmsProvider(config.phone.smsConfig);
1884
+ await smsProvider.sendVerificationSms(phone, code);
1885
+ return c.json({
1886
+ user: { id: user.id, phone },
1887
+ session: null,
1888
+ verificationId: verification.id,
1889
+ requiresVerification: true
1890
+ });
1891
+ }
1892
+ if (email && config.email.verificationRequired) {
1893
+ const code = generateOtpCode(6);
1894
+ const hashedCode = await hashToken(code, config.secret);
1895
+ const expiresAt2 = addDuration(config.email.verificationExpiresIn);
1896
+ const verification = await insertVerification(database, {
1897
+ tenantId: resolvedTenantId,
1898
+ userId: user.id,
1899
+ type: "email-verification",
1900
+ code: hashedCode,
1901
+ expiresAt: expiresAt2,
1902
+ to: email
1903
+ });
1904
+ const emailProvider = new ResendEmailProvider(config.email.resend);
1905
+ await emailProvider.sendVerificationEmail(email, code);
1906
+ return c.json({
1907
+ user: { id: user.id, email },
1908
+ session: null,
1909
+ verificationId: verification.id,
1910
+ requiresVerification: true
1911
+ });
1912
+ }
1913
+ const sessionToken = crypto.randomUUID();
1914
+ const hashedToken = await hashToken(sessionToken, config.secret);
1915
+ const expiresAt = addDuration(config.session.expiresIn);
1916
+ const session = await insertSession(database, {
1917
+ tenantId: resolvedTenantId,
1918
+ userId: user.id,
1919
+ token: hashedToken,
1920
+ expiresAt,
1921
+ userAgent: c.req.header("user-agent") || null,
1922
+ ip: c.req.header("cf-connecting-ip") || c.req.header("x-forwarded-for") || null,
1923
+ meta: { action: "sign-up" }
1924
+ });
1925
+ setCookie5(c, "session_token", sessionToken, {
1926
+ httpOnly: true,
1927
+ secure: process.env.NODE_ENV === "production",
1928
+ sameSite: "Lax",
1929
+ path: "/",
1930
+ expires: new Date(expiresAt)
1931
+ });
1932
+ return c.json({
1933
+ user,
1934
+ session: {
1935
+ id: session.id,
1936
+ expiresAt: session.expiresAt,
1937
+ createdAt: session.createdAt,
1938
+ userAgent: session.userAgent,
1939
+ ip: session.ip
1940
+ },
1941
+ sessionToken,
1942
+ sessionExpiresAt: session.expiresAt
1943
+ });
1944
+ };
1945
+
1946
+ // src/routes/auth.route.ts
1947
+ var signUpRoute = createRoute({
1948
+ method: "post",
1949
+ path: "/sign-up",
1950
+ tags: ["Auth"],
1951
+ summary: "Sign up with email or phone",
1952
+ request: {
1953
+ body: {
1954
+ content: {
1955
+ "application/json": {
1956
+ schema: signUpSchema
1957
+ }
1958
+ }
1959
+ }
1960
+ },
1961
+ responses: {
1962
+ 201: {
1963
+ content: {
1964
+ "application/json": {
1965
+ schema: signUpResponseSchema
1966
+ }
1967
+ },
1968
+ description: "Account created"
1969
+ },
1970
+ 409: {
1971
+ content: {
1972
+ "application/json": {
1973
+ schema: errorResponseSchema
1974
+ }
1975
+ },
1976
+ description: "User already exists"
1977
+ }
1978
+ }
1979
+ });
1980
+ var signInRoute = createRoute({
1981
+ method: "post",
1982
+ path: "/sign-in",
1983
+ tags: ["Auth"],
1984
+ summary: "Sign in with email or phone",
1985
+ request: {
1986
+ body: {
1987
+ content: {
1988
+ "application/json": {
1989
+ schema: signInSchema
1990
+ }
1991
+ }
1992
+ }
1993
+ },
1994
+ responses: {
1995
+ 200: {
1996
+ content: {
1997
+ "application/json": {
1998
+ schema: signInResponseSchema
1999
+ }
2000
+ },
2001
+ description: "Signed in"
2002
+ },
2003
+ 401: {
2004
+ content: {
2005
+ "application/json": {
2006
+ schema: errorResponseSchema
2007
+ }
2008
+ },
2009
+ description: "Invalid credentials"
2010
+ }
2011
+ }
2012
+ });
2013
+ var checkUserRoute = createRoute({
2014
+ method: "post",
2015
+ path: "/check-user",
2016
+ tags: ["Auth"],
2017
+ summary: "Check if user exists",
2018
+ request: {
2019
+ body: {
2020
+ content: {
2021
+ "application/json": {
2022
+ schema: checkUserSchema
2023
+ }
2024
+ }
2025
+ }
2026
+ },
2027
+ responses: {
2028
+ 200: {
2029
+ content: {
2030
+ "application/json": {
2031
+ schema: checkUserResponseSchema
2032
+ }
2033
+ },
2034
+ description: "User check result"
2035
+ }
2036
+ }
2037
+ });
2038
+ var signOutRoute = createRoute({
2039
+ method: "post",
2040
+ path: "/sign-out",
2041
+ tags: ["Auth"],
2042
+ summary: "Sign out current session",
2043
+ responses: {
2044
+ 200: {
2045
+ content: { "application/json": { schema: messageSchema } },
2046
+ description: "Signed out"
2047
+ }
2048
+ }
2049
+ });
2050
+ var meRoute = createRoute({
2051
+ method: "get",
2052
+ path: "/me",
2053
+ tags: ["Auth"],
2054
+ summary: "Get current user",
2055
+ responses: {
2056
+ 200: {
2057
+ content: {
2058
+ "application/json": {
2059
+ schema: z2.object({ user: userSchema })
2060
+ }
2061
+ },
2062
+ description: "Current user"
2063
+ }
2064
+ }
2065
+ });
2066
+ var sessionRoute = createRoute({
2067
+ method: "get",
2068
+ path: "/session",
2069
+ tags: ["Auth"],
2070
+ summary: "Get current session",
2071
+ responses: {
2072
+ 200: {
2073
+ content: {
2074
+ "application/json": {
2075
+ schema: z2.object({
2076
+ user: userSchema.nullable(),
2077
+ session: z2.object({
2078
+ id: z2.string().uuid(),
2079
+ expiresAt: z2.string().datetime(),
2080
+ createdAt: z2.string().datetime(),
2081
+ userAgent: z2.string().nullable(),
2082
+ ip: z2.string().nullable()
2083
+ }).nullable()
2084
+ })
2085
+ }
2086
+ },
2087
+ description: "Current session"
2088
+ }
2089
+ }
2090
+ });
2091
+ var emailVerificationRequestRoute = createRoute({
2092
+ method: "post",
2093
+ path: "/email/verification/request",
2094
+ tags: ["Email"],
2095
+ summary: "Request email verification",
2096
+ request: {
2097
+ body: {
2098
+ content: {
2099
+ "application/json": {
2100
+ schema: emailVerificationRequestSchema
2101
+ }
2102
+ }
2103
+ }
2104
+ },
2105
+ responses: {
2106
+ 200: {
2107
+ content: {
2108
+ "application/json": {
2109
+ schema: messageWithVerificationIdSchema
2110
+ }
2111
+ },
2112
+ description: "Verification code sent"
2113
+ }
2114
+ }
2115
+ });
2116
+ var emailVerificationConfirmRoute = createRoute({
2117
+ method: "post",
2118
+ path: "/email/verification/confirm",
2119
+ tags: ["Email"],
2120
+ summary: "Confirm email verification",
2121
+ request: {
2122
+ body: {
2123
+ content: {
2124
+ "application/json": {
2125
+ schema: emailVerificationConfirmSchema
2126
+ }
2127
+ }
2128
+ }
2129
+ },
2130
+ responses: {
2131
+ 200: {
2132
+ content: {
2133
+ "application/json": {
2134
+ schema: authSuccessSchema
2135
+ }
2136
+ },
2137
+ description: "Email verified"
2138
+ }
2139
+ }
2140
+ });
2141
+ var phoneVerificationRequestRoute = createRoute({
2142
+ method: "post",
2143
+ path: "/phone/verification/request",
2144
+ tags: ["Phone"],
2145
+ summary: "Request phone OTP",
2146
+ request: {
2147
+ body: {
2148
+ content: {
2149
+ "application/json": {
2150
+ schema: phoneVerificationRequestSchema
2151
+ }
2152
+ }
2153
+ }
2154
+ },
2155
+ responses: {
2156
+ 200: {
2157
+ content: {
2158
+ "application/json": {
2159
+ schema: messageWithVerificationIdSchema
2160
+ }
2161
+ },
2162
+ description: "OTP sent"
2163
+ }
2164
+ }
2165
+ });
2166
+ var phoneVerificationConfirmRoute = createRoute({
2167
+ method: "post",
2168
+ path: "/phone/verification/confirm",
2169
+ tags: ["Phone"],
2170
+ summary: "Confirm phone OTP",
2171
+ request: {
2172
+ body: {
2173
+ content: {
2174
+ "application/json": {
2175
+ schema: phoneVerificationConfirmSchema
2176
+ }
2177
+ }
2178
+ }
2179
+ },
2180
+ responses: {
2181
+ 200: {
2182
+ content: {
2183
+ "application/json": {
2184
+ schema: authSuccessSchema
2185
+ }
2186
+ },
2187
+ description: "Phone verified"
2188
+ }
2189
+ }
2190
+ });
2191
+ var forgotPasswordRoute = createRoute({
2192
+ method: "post",
2193
+ path: "/password/forgot",
2194
+ tags: ["Password"],
2195
+ summary: "Request password reset",
2196
+ request: {
2197
+ body: {
2198
+ content: {
2199
+ "application/json": {
2200
+ schema: forgotPasswordSchema
2201
+ }
2202
+ }
2203
+ }
2204
+ },
2205
+ responses: {
2206
+ 200: {
2207
+ content: {
2208
+ "application/json": {
2209
+ schema: messageWithVerificationIdSchema
2210
+ }
2211
+ },
2212
+ description: "Reset code sent if account exists"
2213
+ }
2214
+ }
2215
+ });
2216
+ var resetPasswordRoute = createRoute({
2217
+ method: "post",
2218
+ path: "/password/reset",
2219
+ tags: ["Password"],
2220
+ summary: "Reset password",
2221
+ request: {
2222
+ body: {
2223
+ content: {
2224
+ "application/json": {
2225
+ schema: resetPasswordSchema
2226
+ }
2227
+ }
2228
+ }
2229
+ },
2230
+ responses: {
2231
+ 200: {
2232
+ content: {
2233
+ "application/json": {
2234
+ schema: authSuccessSchema
2235
+ }
2236
+ },
2237
+ description: "Password reset and new session"
2238
+ }
2239
+ }
2240
+ });
2241
+ var changePasswordRoute = createRoute({
2242
+ method: "post",
2243
+ path: "/password/change",
2244
+ tags: ["Password"],
2245
+ summary: "Change password",
2246
+ request: {
2247
+ body: {
2248
+ content: {
2249
+ "application/json": {
2250
+ schema: changePasswordSchema
2251
+ }
2252
+ }
2253
+ }
2254
+ },
2255
+ responses: {
2256
+ 200: {
2257
+ content: { "application/json": { schema: messageSchema } },
2258
+ description: "Password updated"
2259
+ },
2260
+ 401: {
2261
+ content: { "application/json": { schema: errorResponseSchema } },
2262
+ description: "Invalid password"
2263
+ }
2264
+ }
2265
+ });
2266
+ var createAuthRoutes = () => {
2267
+ const authRoutes = new OpenAPIHono().openapi(signUpRoute, signUpHandler).openapi(signInRoute, signInHandler).openapi(checkUserRoute, checkUserHandler).openapi(signOutRoute, signOutHandler).openapi(meRoute, meHandler).openapi(sessionRoute, sessionHandler).openapi(emailVerificationRequestRoute, emailVerificationRequestHandler).openapi(emailVerificationConfirmRoute, emailVerificationConfirmHandler).openapi(phoneVerificationRequestRoute, phoneVerificationRequestHandler).openapi(phoneVerificationConfirmRoute, phoneVerificationConfirmHandler).openapi(forgotPasswordRoute, forgotPasswordHandler).openapi(resetPasswordRoute, resetPasswordHandler).openapi(changePasswordRoute, changePasswordHandler);
2268
+ return authRoutes;
2269
+ };
2270
+
2271
+ // src/handler.ts
2272
+ var createAuthMiddleware = (config, database, getTenantId) => {
2273
+ const enableTenant = config.enableTenant ?? true;
2274
+ return async (c, next) => {
2275
+ const sessionToken = getCookie2(c, "session_token") || void 0;
2276
+ let tenantId = getTenantId(c);
2277
+ if (enableTenant) {
2278
+ if (!tenantId) {
2279
+ throw new HTTPException11(400, {
2280
+ message: "Missing tenantId. Tenant isolation is enabled."
2281
+ });
2282
+ }
2283
+ } else {
2284
+ tenantId = config.tenantId;
2285
+ if (!tenantId) {
2286
+ throw new HTTPException11(500, {
2287
+ message: "tenantId must be provided in config when enableTenant is false."
2288
+ });
2289
+ }
2290
+ }
2291
+ c.set("config", config);
2292
+ c.set("database", database);
2293
+ c.set("tenantId", tenantId);
2294
+ c.set("userId", void 0);
2295
+ c.set("user", void 0);
2296
+ c.set("session", void 0);
2297
+ if (sessionToken) {
2298
+ try {
2299
+ const hashedToken = await hashToken(sessionToken, config.secret);
2300
+ const session = await findSessionByToken(database, hashedToken);
2301
+ if (session) {
2302
+ const user = await findUserById(
2303
+ database,
2304
+ session.tenantId,
2305
+ session.userId
2306
+ );
2307
+ if (user) {
2308
+ c.set("tenantId", enableTenant ? session.tenantId : tenantId);
2309
+ c.set("userId", user.id);
2310
+ c.set("user", user);
2311
+ c.set("session", session);
2312
+ }
2313
+ }
2314
+ } catch {
2315
+ }
2316
+ }
2317
+ await next();
2318
+ };
2319
+ };
2320
+ var createAuthHandler = (config) => {
2321
+ const app = new OpenAPIHono2();
2322
+ const database = createDatabase(config.connectionString);
2323
+ app.use(
2324
+ "*",
2325
+ createAuthMiddleware(
2326
+ config,
2327
+ database,
2328
+ (c) => c.req.header("x-tenant-id") || config.tenantId
2329
+ )
2330
+ );
2331
+ app.route("/", createAuthRoutes());
2332
+ return app;
2333
+ };
2334
+ var createAuthRoutes2 = (config) => {
2335
+ const app = new OpenAPIHono2();
2336
+ const database = createDatabase(config.connectionString);
2337
+ app.use(
2338
+ "*",
2339
+ createAuthMiddleware(
2340
+ config,
2341
+ database,
2342
+ (c) => c.get("tenantId") || config.tenantId
2343
+ )
2344
+ );
2345
+ app.route("/", createAuthRoutes());
2346
+ return app;
2347
+ };
2348
+
2349
+ // src/index.ts
2350
+ var jiretAuth = (config) => {
2351
+ const handler = createAuthHandler(config);
2352
+ const routes = createAuthRoutes2(config);
2353
+ const database = createDatabase(config.connectionString);
2354
+ const getSession = async (headers) => {
2355
+ const cookieHeader = headers.get("cookie");
2356
+ if (!cookieHeader) {
2357
+ return { session: null, user: null, sessionToken: null };
2358
+ }
2359
+ const cookies = Object.fromEntries(
2360
+ cookieHeader.split("; ").map((c) => {
2361
+ const [key, ...rest] = c.split("=");
2362
+ return [key, rest.join("=")];
2363
+ })
2364
+ );
2365
+ const sessionToken = cookies.session_token;
2366
+ if (!sessionToken) {
2367
+ return { session: null, user: null, sessionToken: null };
2368
+ }
2369
+ try {
2370
+ const hashedToken = await hashToken(sessionToken, config.secret);
2371
+ const session = await findSessionByToken(database, hashedToken);
2372
+ if (!session) {
2373
+ return { session: null, user: null, sessionToken: null };
2374
+ }
2375
+ const user = await findUserById(
2376
+ database,
2377
+ session.tenantId,
2378
+ session.userId
2379
+ );
2380
+ if (!user) {
2381
+ return { session: null, user: null, sessionToken: null };
2382
+ }
2383
+ return { session, user, sessionToken };
2384
+ } catch {
2385
+ return { session: null, user: null, sessionToken: null };
2386
+ }
2387
+ };
2388
+ return {
2389
+ handler,
2390
+ routes,
2391
+ getSession
2392
+ };
2393
+ };
2394
+ export {
2395
+ jiretAuth
2396
+ };
2397
+ //# sourceMappingURL=index.js.map