@mesob/auth-hono 0.4.7 → 0.5.2

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 CHANGED
@@ -11,23 +11,6 @@ import { deepmerge } from "deepmerge-ts";
11
11
  import { drizzle } from "drizzle-orm/node-postgres";
12
12
  import { Pool } from "pg";
13
13
 
14
- // src/db/relations.ts
15
- var relations_exports = {};
16
- __export(relations_exports, {
17
- accountChangesInIamRelations: () => accountChangesInIamRelations,
18
- accountsInIamRelations: () => accountsInIamRelations,
19
- domainsInIamRelations: () => domainsInIamRelations,
20
- permissionsInIamRelations: () => permissionsInIamRelations,
21
- rolePermissionsInIamRelations: () => rolePermissionsInIamRelations,
22
- rolesInIamRelations: () => rolesInIamRelations,
23
- sessionsInIamRelations: () => sessionsInIamRelations,
24
- tenantsInIamRelations: () => tenantsInIamRelations,
25
- userRolesInIamRelations: () => userRolesInIamRelations,
26
- usersInIamRelations: () => usersInIamRelations,
27
- verificationsInIamRelations: () => verificationsInIamRelations
28
- });
29
- import { relations } from "drizzle-orm/relations";
30
-
31
14
  // src/db/schema.ts
32
15
  var schema_exports = {};
33
16
  __export(schema_exports, {
@@ -333,117 +316,8 @@ var domainsInIam = iam.table("domains", {
333
316
  check("domains_status_check", sql`status = ANY (ARRAY['PENDING'::text, 'ACTIVE'::text, 'DISABLED'::text, 'DELETED'::text])`)
334
317
  ]);
335
318
 
336
- // src/db/relations.ts
337
- var verificationsInIamRelations = relations(verificationsInIam, ({ one }) => ({
338
- tenantsInIam: one(tenantsInIam, {
339
- fields: [verificationsInIam.tenantId],
340
- references: [tenantsInIam.id]
341
- }),
342
- usersInIam: one(usersInIam, {
343
- fields: [verificationsInIam.userId],
344
- references: [usersInIam.id]
345
- })
346
- }));
347
- var tenantsInIamRelations = relations(tenantsInIam, ({ many }) => ({
348
- verificationsInIam: many(verificationsInIam),
349
- sessionsInIam: many(sessionsInIam),
350
- accountChangesInIam: many(accountChangesInIam),
351
- rolePermissionsInIam: many(rolePermissionsInIam),
352
- accountsInIam: many(accountsInIam),
353
- usersInIam: many(usersInIam),
354
- rolesInIam: many(rolesInIam),
355
- userRolesInIam: many(userRolesInIam),
356
- domainsInIam: many(domainsInIam)
357
- }));
358
- var usersInIamRelations = relations(usersInIam, ({ one, many }) => ({
359
- verificationsInIam: many(verificationsInIam),
360
- sessionsInIam: many(sessionsInIam),
361
- accountChangesInIam: many(accountChangesInIam),
362
- accountsInIam: many(accountsInIam),
363
- tenantsInIam: one(tenantsInIam, {
364
- fields: [usersInIam.tenantId],
365
- references: [tenantsInIam.id]
366
- }),
367
- userRolesInIam: many(userRolesInIam)
368
- }));
369
- var sessionsInIamRelations = relations(sessionsInIam, ({ one }) => ({
370
- tenantsInIam: one(tenantsInIam, {
371
- fields: [sessionsInIam.tenantId],
372
- references: [tenantsInIam.id]
373
- }),
374
- usersInIam: one(usersInIam, {
375
- fields: [sessionsInIam.userId],
376
- references: [usersInIam.id]
377
- })
378
- }));
379
- var accountChangesInIamRelations = relations(accountChangesInIam, ({ one }) => ({
380
- tenantsInIam: one(tenantsInIam, {
381
- fields: [accountChangesInIam.tenantId],
382
- references: [tenantsInIam.id]
383
- }),
384
- usersInIam: one(usersInIam, {
385
- fields: [accountChangesInIam.userId],
386
- references: [usersInIam.id]
387
- })
388
- }));
389
- var rolePermissionsInIamRelations = relations(rolePermissionsInIam, ({ one }) => ({
390
- tenantsInIam: one(tenantsInIam, {
391
- fields: [rolePermissionsInIam.tenantId],
392
- references: [tenantsInIam.id]
393
- }),
394
- permissionsInIam: one(permissionsInIam, {
395
- fields: [rolePermissionsInIam.permissionId],
396
- references: [permissionsInIam.id]
397
- }),
398
- rolesInIam: one(rolesInIam, {
399
- fields: [rolePermissionsInIam.tenantId],
400
- references: [rolesInIam.tenantId]
401
- })
402
- }));
403
- var permissionsInIamRelations = relations(permissionsInIam, ({ many }) => ({
404
- rolePermissionsInIam: many(rolePermissionsInIam)
405
- }));
406
- var rolesInIamRelations = relations(rolesInIam, ({ one, many }) => ({
407
- rolePermissionsInIam: many(rolePermissionsInIam),
408
- tenantsInIam: one(tenantsInIam, {
409
- fields: [rolesInIam.tenantId],
410
- references: [tenantsInIam.id]
411
- }),
412
- userRolesInIam: many(userRolesInIam)
413
- }));
414
- var accountsInIamRelations = relations(accountsInIam, ({ one }) => ({
415
- tenantsInIam: one(tenantsInIam, {
416
- fields: [accountsInIam.tenantId],
417
- references: [tenantsInIam.id]
418
- }),
419
- usersInIam: one(usersInIam, {
420
- fields: [accountsInIam.userId],
421
- references: [usersInIam.id]
422
- })
423
- }));
424
- var userRolesInIamRelations = relations(userRolesInIam, ({ one }) => ({
425
- tenantsInIam: one(tenantsInIam, {
426
- fields: [userRolesInIam.tenantId],
427
- references: [tenantsInIam.id]
428
- }),
429
- usersInIam: one(usersInIam, {
430
- fields: [userRolesInIam.userId],
431
- references: [usersInIam.id]
432
- }),
433
- rolesInIam: one(rolesInIam, {
434
- fields: [userRolesInIam.tenantId],
435
- references: [rolesInIam.tenantId]
436
- })
437
- }));
438
- var domainsInIamRelations = relations(domainsInIam, ({ one }) => ({
439
- tenantsInIam: one(tenantsInIam, {
440
- fields: [domainsInIam.tenantId],
441
- references: [tenantsInIam.id]
442
- })
443
- }));
444
-
445
319
  // src/db/index.ts
446
- var schemaConfig = { schema: { ...schema_exports, ...relations_exports } };
320
+ var schemaConfig = { schema: { ...schema_exports } };
447
321
  var createDatabase = (connectionString) => {
448
322
  const pool = new Pool({ connectionString });
449
323
  return drizzle({ client: pool, ...schemaConfig });
@@ -995,6 +869,8 @@ var checkAccountResponseSchema = z.object({
995
869
  verified: z.boolean(),
996
870
  hasPassword: z.boolean(),
997
871
  requiresPasswordSetup: z.boolean(),
872
+ needsVerification: z.boolean().optional(),
873
+ verificationId: z.string().uuid().optional(),
998
874
  account: authAccountSchema.nullable()
999
875
  });
1000
876
  var updateProfileSchema = z.object({
@@ -1021,7 +897,7 @@ var pendingAccountChangeResponseSchema = z.object({
1021
897
  });
1022
898
 
1023
899
  // src/routes/auth/handler/check-account.ts
1024
- import { and as and5, eq as eq5, sql as sql4 } from "drizzle-orm";
900
+ import { and as and6, eq as eq6, sql as sql4 } from "drizzle-orm";
1025
901
 
1026
902
  // src/lib/tenant.ts
1027
903
  import { HTTPException as HTTPException2 } from "hono/http-exception";
@@ -1043,79 +919,9 @@ var ensureTenantId = (config, tenantId) => {
1043
919
  return config.tenant.tenantId;
1044
920
  };
1045
921
 
1046
- // src/routes/auth/handler/check-account.ts
1047
- var checkAccountHandler = async (c) => {
1048
- const body = c.req.valid("json");
1049
- const config = c.get("config");
1050
- const database = c.get("database");
1051
- const tenantId = c.get("tenantId");
1052
- const resolvedTenantId = ensureTenantId(config, tenantId);
1053
- const { username } = body;
1054
- const isEmail = username.includes("@");
1055
- const userTypeFilter = sql4`${usersInIam.userType} @> ARRAY[${config.userType}]::text[]`;
1056
- const whereClause = isEmail ? and5(
1057
- eq5(usersInIam.tenantId, resolvedTenantId),
1058
- userTypeFilter,
1059
- sql4`lower(${usersInIam.email}) = lower(${username})`
1060
- ) : and5(
1061
- eq5(usersInIam.tenantId, resolvedTenantId),
1062
- userTypeFilter,
1063
- eq5(usersInIam.phone, username)
1064
- );
1065
- const [result] = await database.select({
1066
- fullName: usersInIam.fullName,
1067
- email: usersInIam.email,
1068
- phone: usersInIam.phone,
1069
- verified: isEmail ? usersInIam.emailVerified : usersInIam.phoneVerified,
1070
- hasPassword: sql4`exists(
1071
- select 1
1072
- from ${accountsInIam}
1073
- where ${eq5(accountsInIam.tenantId, resolvedTenantId)}
1074
- and ${eq5(accountsInIam.userId, usersInIam.id)}
1075
- and ${eq5(accountsInIam.provider, "credentials")}
1076
- and ${sql4`${accountsInIam.password} is not null`}
1077
- )`
1078
- }).from(usersInIam).where(whereClause).limit(1);
1079
- const verified = result?.verified ?? false;
1080
- const hasPassword = result?.hasPassword ?? false;
1081
- return c.json(
1082
- {
1083
- exists: !!result,
1084
- verified,
1085
- hasPassword,
1086
- requiresPasswordSetup: !!result && verified && !hasPassword,
1087
- account: result ? {
1088
- fullName: result.fullName,
1089
- email: result.email,
1090
- phone: result.phone,
1091
- verified,
1092
- hasPassword,
1093
- requiresPasswordSetup: verified && !hasPassword
1094
- } : null
1095
- },
1096
- 200
1097
- );
1098
- };
1099
-
1100
- // src/routes/auth/handler/sign-in.ts
1101
- import { logger as logger2 } from "@mesob/common";
1102
- import { and as and9, eq as eq9 } from "drizzle-orm";
1103
-
1104
- // src/errors.ts
1105
- var AUTH_ERRORS = {
1106
- USER_NOT_FOUND: "USER_NOT_FOUND",
1107
- INVALID_PASSWORD: "INVALID_PASSWORD",
1108
- USER_EXISTS: "USER_EXISTS",
1109
- VERIFICATION_EXPIRED: "VERIFICATION_EXPIRED",
1110
- VERIFICATION_MISMATCH: "VERIFICATION_MISMATCH",
1111
- VERIFICATION_NOT_FOUND: "VERIFICATION_NOT_FOUND",
1112
- TOO_MANY_ATTEMPTS: "TOO_MANY_ATTEMPTS",
1113
- REQUIRES_VERIFICATION: "REQUIRES_VERIFICATION",
1114
- UNAUTHORIZED: "UNAUTHORIZED",
1115
- ACCESS_DENIED: "ACCESS_DENIED",
1116
- HAS_NO_PASSWORD: "HAS_NO_PASSWORD",
1117
- PASSWORD_ALREADY_SET: "PASSWORD_ALREADY_SET"
1118
- };
922
+ // src/routes/auth/helper/verification.ts
923
+ import { dayjs as dayjs2 } from "@mesob/common";
924
+ import { and as and5, desc, eq as eq5, gt as gt2 } from "drizzle-orm";
1119
925
 
1120
926
  // src/lib/normalize-auth-response.ts
1121
927
  var normalizeAuthUser = (user) => ({
@@ -1209,8 +1015,374 @@ var getRefreshedExpiresAt = ({
1209
1015
  return addDuration(duration);
1210
1016
  };
1211
1017
 
1018
+ // src/routes/auth/helper/verification.ts
1019
+ var createVerification = async ({
1020
+ tx,
1021
+ tenantId,
1022
+ userId,
1023
+ type,
1024
+ to,
1025
+ config
1026
+ }) => {
1027
+ const isPhone = type === "phone-otp-sign-up";
1028
+ const code = generateOtpCode(
1029
+ isPhone ? config.phone.otpLength : config.email.otpLength
1030
+ );
1031
+ const hashedCode = await hashToken(code, config.secret);
1032
+ const expiresAt = addDuration(
1033
+ isPhone ? config.phone.expiresIn : config.email.expiresIn
1034
+ );
1035
+ const [verification] = await tx.insert(verificationsInIam).values({
1036
+ tenantId,
1037
+ userId,
1038
+ type,
1039
+ code: hashedCode,
1040
+ expiresAt,
1041
+ to,
1042
+ attempt: 0
1043
+ }).returning();
1044
+ return { verificationId: verification.id, code, hash: hashedCode };
1045
+ };
1046
+ var sendVerification = async ({
1047
+ channel,
1048
+ to,
1049
+ code,
1050
+ config,
1051
+ hash,
1052
+ type
1053
+ }) => {
1054
+ if (channel === "phone" && config.phone.sendVerificationOTP) {
1055
+ await config.phone.sendVerificationOTP({
1056
+ phone: to,
1057
+ code,
1058
+ hash,
1059
+ type
1060
+ });
1061
+ } else if (config.email.sendVerificationOTP) {
1062
+ await config.email.sendVerificationOTP({
1063
+ email: to,
1064
+ code,
1065
+ hash,
1066
+ type
1067
+ });
1068
+ }
1069
+ };
1070
+ var checkVerificationResend = async ({
1071
+ database,
1072
+ tenantId,
1073
+ userId,
1074
+ type,
1075
+ resendInterval
1076
+ }) => {
1077
+ if (!resendInterval) {
1078
+ return { blocked: false };
1079
+ }
1080
+ const [verification] = await database.select({
1081
+ id: verificationsInIam.id,
1082
+ createdAt: verificationsInIam.createdAt
1083
+ }).from(verificationsInIam).where(
1084
+ and5(
1085
+ eq5(verificationsInIam.tenantId, tenantId),
1086
+ eq5(verificationsInIam.userId, userId),
1087
+ eq5(verificationsInIam.type, type)
1088
+ )
1089
+ ).orderBy(desc(verificationsInIam.createdAt)).limit(1);
1090
+ if (!verification) {
1091
+ return { blocked: false };
1092
+ }
1093
+ const cooldownSeconds = parseDuration(resendInterval);
1094
+ const createdAt = dayjs2(verification.createdAt);
1095
+ const blocked = dayjs2().diff(createdAt, "second") < cooldownSeconds;
1096
+ return { blocked, verificationId: verification.id };
1097
+ };
1098
+ var ensureVerificationForCheckAccount = async ({
1099
+ database,
1100
+ tenantId,
1101
+ userId,
1102
+ type,
1103
+ to,
1104
+ config
1105
+ }) => {
1106
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1107
+ const [existing] = await database.select({
1108
+ id: verificationsInIam.id,
1109
+ expiresAt: verificationsInIam.expiresAt
1110
+ }).from(verificationsInIam).where(
1111
+ and5(
1112
+ eq5(verificationsInIam.tenantId, tenantId),
1113
+ eq5(verificationsInIam.userId, userId),
1114
+ eq5(verificationsInIam.type, type),
1115
+ gt2(verificationsInIam.expiresAt, now)
1116
+ )
1117
+ ).orderBy(desc(verificationsInIam.createdAt)).limit(1);
1118
+ if (existing) {
1119
+ return { verificationId: existing.id };
1120
+ }
1121
+ await database.delete(verificationsInIam).where(
1122
+ and5(
1123
+ eq5(verificationsInIam.tenantId, tenantId),
1124
+ eq5(verificationsInIam.userId, userId),
1125
+ eq5(verificationsInIam.type, type)
1126
+ )
1127
+ );
1128
+ const isPhone = type === "phone-otp";
1129
+ const code = generateOtpCode(
1130
+ isPhone ? config.phone.otpLength : config.email.otpLength
1131
+ );
1132
+ const hashedCode = await hashToken(code, config.secret);
1133
+ const expiresAt = addDuration(
1134
+ isPhone ? config.phone.expiresIn : config.email.expiresIn
1135
+ );
1136
+ const [verification] = await database.insert(verificationsInIam).values({
1137
+ tenantId,
1138
+ userId,
1139
+ type,
1140
+ code: hashedCode,
1141
+ expiresAt,
1142
+ to,
1143
+ attempt: 0
1144
+ }).returning();
1145
+ if (isPhone && config.phone.sendVerificationOTP) {
1146
+ await config.phone.sendVerificationOTP({
1147
+ phone: to,
1148
+ code,
1149
+ hash: hashedCode,
1150
+ type
1151
+ });
1152
+ } else if (!isPhone && config.email.sendVerificationOTP) {
1153
+ await config.email.sendVerificationOTP({
1154
+ email: to,
1155
+ code,
1156
+ hash: hashedCode,
1157
+ type
1158
+ });
1159
+ }
1160
+ return { verificationId: verification.id };
1161
+ };
1162
+ var handleEmailVerification = async ({
1163
+ c,
1164
+ user,
1165
+ config,
1166
+ database,
1167
+ tenantId
1168
+ }) => {
1169
+ if (!user.email) {
1170
+ return c.json({ error: "User email not found" }, 401);
1171
+ }
1172
+ await database.delete(verificationsInIam).where(
1173
+ and5(
1174
+ eq5(verificationsInIam.tenantId, tenantId),
1175
+ eq5(verificationsInIam.userId, user.id),
1176
+ eq5(verificationsInIam.type, "email-verification")
1177
+ )
1178
+ );
1179
+ const code = generateOtpCode(config.email.otpLength);
1180
+ const hashedCode = await hashToken(code, config.secret);
1181
+ const expiresAt = addDuration(config.email.expiresIn);
1182
+ const [verification] = await database.insert(verificationsInIam).values({
1183
+ tenantId,
1184
+ userId: user.id,
1185
+ type: "email-verification",
1186
+ code: hashedCode,
1187
+ expiresAt,
1188
+ to: user.email,
1189
+ attempt: 0
1190
+ }).returning({
1191
+ id: verificationsInIam.id,
1192
+ tenantId: verificationsInIam.tenantId,
1193
+ userId: verificationsInIam.userId,
1194
+ type: verificationsInIam.type,
1195
+ code: verificationsInIam.code,
1196
+ to: verificationsInIam.to,
1197
+ expiresAt: verificationsInIam.expiresAt,
1198
+ createdAt: verificationsInIam.createdAt,
1199
+ attempt: verificationsInIam.attempt
1200
+ });
1201
+ if (config.email.sendVerificationOTP) {
1202
+ await config.email.sendVerificationOTP({
1203
+ email: user.email,
1204
+ code,
1205
+ hash: hashedCode,
1206
+ type: "email-verification"
1207
+ });
1208
+ }
1209
+ return c.json(
1210
+ {
1211
+ user: normalizeAuthUser(user),
1212
+ session: null,
1213
+ verificationId: verification.id,
1214
+ requiresVerification: true
1215
+ },
1216
+ 200
1217
+ );
1218
+ };
1219
+ var handlePhoneVerification = async ({
1220
+ c,
1221
+ user,
1222
+ config,
1223
+ database,
1224
+ tenantId
1225
+ }) => {
1226
+ if (!user.phone) {
1227
+ return c.json({ error: "User phone not found" }, 401);
1228
+ }
1229
+ await database.delete(verificationsInIam).where(
1230
+ and5(
1231
+ eq5(verificationsInIam.tenantId, tenantId),
1232
+ eq5(verificationsInIam.userId, user.id),
1233
+ eq5(verificationsInIam.type, "phone-otp")
1234
+ )
1235
+ );
1236
+ const code = generateOtpCode(config.phone.otpLength);
1237
+ const hashedCode = await hashToken(code, config.secret);
1238
+ const expiresAt = addDuration(config.phone.expiresIn);
1239
+ const [verification] = await database.insert(verificationsInIam).values({
1240
+ tenantId,
1241
+ userId: user.id,
1242
+ type: "phone-otp",
1243
+ code: hashedCode,
1244
+ expiresAt,
1245
+ to: user.phone,
1246
+ attempt: 0
1247
+ }).returning({
1248
+ id: verificationsInIam.id,
1249
+ tenantId: verificationsInIam.tenantId,
1250
+ userId: verificationsInIam.userId,
1251
+ type: verificationsInIam.type,
1252
+ code: verificationsInIam.code,
1253
+ to: verificationsInIam.to,
1254
+ expiresAt: verificationsInIam.expiresAt,
1255
+ createdAt: verificationsInIam.createdAt,
1256
+ attempt: verificationsInIam.attempt
1257
+ });
1258
+ if (config.phone.sendVerificationOTP) {
1259
+ await config.phone.sendVerificationOTP({
1260
+ phone: user.phone,
1261
+ code,
1262
+ hash: hashedCode,
1263
+ type: "phone-otp"
1264
+ });
1265
+ }
1266
+ return c.json(
1267
+ {
1268
+ user: normalizeAuthUser(user),
1269
+ session: null,
1270
+ verificationId: verification.id,
1271
+ requiresVerification: true
1272
+ },
1273
+ 200
1274
+ );
1275
+ };
1276
+
1277
+ // src/routes/auth/handler/check-account.ts
1278
+ var checkAccountHandler = async (c) => {
1279
+ const body = c.req.valid("json");
1280
+ const config = c.get("config");
1281
+ const database = c.get("database");
1282
+ const tenantId = c.get("tenantId");
1283
+ const resolvedTenantId = ensureTenantId(config, tenantId);
1284
+ const { username } = body;
1285
+ const isEmail = username.includes("@");
1286
+ const disabledChannelResponse = {
1287
+ exists: false,
1288
+ verified: false,
1289
+ hasPassword: false,
1290
+ requiresPasswordSetup: false,
1291
+ account: null
1292
+ };
1293
+ if (isEmail && !config.email.enabled) {
1294
+ return c.json(disabledChannelResponse, 200);
1295
+ }
1296
+ if (!(isEmail || config.phone.enabled)) {
1297
+ return c.json(disabledChannelResponse, 200);
1298
+ }
1299
+ const userTypeFilter = sql4`${usersInIam.userType} @> ARRAY[${config.userType}]::text[]`;
1300
+ const whereClause = isEmail ? and6(
1301
+ eq6(usersInIam.tenantId, resolvedTenantId),
1302
+ userTypeFilter,
1303
+ sql4`lower(${usersInIam.email}) = lower(${username})`
1304
+ ) : and6(
1305
+ eq6(usersInIam.tenantId, resolvedTenantId),
1306
+ userTypeFilter,
1307
+ eq6(usersInIam.phone, username)
1308
+ );
1309
+ const [result] = await database.select({
1310
+ id: usersInIam.id,
1311
+ fullName: usersInIam.fullName,
1312
+ email: usersInIam.email,
1313
+ phone: usersInIam.phone,
1314
+ verified: isEmail ? usersInIam.emailVerified : usersInIam.phoneVerified,
1315
+ hasPassword: sql4`exists(
1316
+ select 1
1317
+ from ${accountsInIam}
1318
+ where ${eq6(accountsInIam.tenantId, resolvedTenantId)}
1319
+ and ${eq6(accountsInIam.userId, usersInIam.id)}
1320
+ and ${eq6(accountsInIam.provider, "credentials")}
1321
+ and ${sql4`${accountsInIam.password} is not null`}
1322
+ )`
1323
+ }).from(usersInIam).where(whereClause).limit(1);
1324
+ const verified = result?.verified ?? false;
1325
+ const hasPassword = result?.hasPassword ?? false;
1326
+ let needsVerification = false;
1327
+ let verificationId;
1328
+ if (result && !verified) {
1329
+ const type = isEmail ? "email-verification" : "phone-otp";
1330
+ const to = isEmail ? result.email ?? username : result.phone ?? username;
1331
+ if (to) {
1332
+ const { verificationId: vid } = await ensureVerificationForCheckAccount({
1333
+ database,
1334
+ tenantId: resolvedTenantId,
1335
+ userId: result.id,
1336
+ type,
1337
+ to,
1338
+ config
1339
+ });
1340
+ needsVerification = true;
1341
+ verificationId = vid;
1342
+ }
1343
+ }
1344
+ return c.json(
1345
+ {
1346
+ exists: !!result,
1347
+ verified,
1348
+ hasPassword,
1349
+ requiresPasswordSetup: !!result && verified && !hasPassword,
1350
+ ...needsVerification && verificationId ? { needsVerification: true, verificationId } : {},
1351
+ account: result ? {
1352
+ fullName: result.fullName,
1353
+ email: result.email,
1354
+ phone: result.phone,
1355
+ verified,
1356
+ hasPassword,
1357
+ requiresPasswordSetup: verified && !hasPassword
1358
+ } : null
1359
+ },
1360
+ 200
1361
+ );
1362
+ };
1363
+
1364
+ // src/routes/auth/handler/sign-in.ts
1365
+ import { logger as logger2 } from "@mesob/common";
1366
+ import { and as and9, eq as eq9 } from "drizzle-orm";
1367
+
1368
+ // src/errors.ts
1369
+ var AUTH_ERRORS = {
1370
+ USER_NOT_FOUND: "USER_NOT_FOUND",
1371
+ INVALID_PASSWORD: "INVALID_PASSWORD",
1372
+ USER_EXISTS: "USER_EXISTS",
1373
+ VERIFICATION_EXPIRED: "VERIFICATION_EXPIRED",
1374
+ VERIFICATION_MISMATCH: "VERIFICATION_MISMATCH",
1375
+ VERIFICATION_NOT_FOUND: "VERIFICATION_NOT_FOUND",
1376
+ TOO_MANY_ATTEMPTS: "TOO_MANY_ATTEMPTS",
1377
+ REQUIRES_VERIFICATION: "REQUIRES_VERIFICATION",
1378
+ UNAUTHORIZED: "UNAUTHORIZED",
1379
+ ACCESS_DENIED: "ACCESS_DENIED",
1380
+ HAS_NO_PASSWORD: "HAS_NO_PASSWORD",
1381
+ PASSWORD_ALREADY_SET: "PASSWORD_ALREADY_SET"
1382
+ };
1383
+
1212
1384
  // src/routes/auth/helper/session.ts
1213
- import { and as and6, asc, eq as eq6, gt as gt2, inArray, sql as sql5 } from "drizzle-orm";
1385
+ import { and as and7, asc, eq as eq7, gt as gt3, inArray, sql as sql5 } from "drizzle-orm";
1214
1386
  var createSessionRecord = async ({
1215
1387
  tx,
1216
1388
  tenantId,
@@ -1279,10 +1451,10 @@ var cleanupOldSessions = async ({
1279
1451
  maxSessions
1280
1452
  }) => {
1281
1453
  const [{ count }] = await database.select({ count: sql5`count(*)` }).from(sessionsInIam).where(
1282
- and6(
1283
- eq6(sessionsInIam.tenantId, tenantId),
1284
- eq6(sessionsInIam.userId, userId),
1285
- gt2(sessionsInIam.expiresAt, (/* @__PURE__ */ new Date()).toISOString())
1454
+ and7(
1455
+ eq7(sessionsInIam.tenantId, tenantId),
1456
+ eq7(sessionsInIam.userId, userId),
1457
+ gt3(sessionsInIam.expiresAt, (/* @__PURE__ */ new Date()).toISOString())
1286
1458
  )
1287
1459
  );
1288
1460
  if (count <= maxSessions) {
@@ -1290,19 +1462,19 @@ var cleanupOldSessions = async ({
1290
1462
  }
1291
1463
  const toDeleteCount = count - maxSessions;
1292
1464
  const idsToDelete = await database.select({ id: sessionsInIam.id }).from(sessionsInIam).where(
1293
- and6(
1294
- eq6(sessionsInIam.tenantId, tenantId),
1295
- eq6(sessionsInIam.userId, userId),
1296
- gt2(sessionsInIam.expiresAt, (/* @__PURE__ */ new Date()).toISOString())
1465
+ and7(
1466
+ eq7(sessionsInIam.tenantId, tenantId),
1467
+ eq7(sessionsInIam.userId, userId),
1468
+ gt3(sessionsInIam.expiresAt, (/* @__PURE__ */ new Date()).toISOString())
1297
1469
  )
1298
1470
  ).orderBy(asc(sessionsInIam.createdAt)).limit(toDeleteCount);
1299
1471
  if (!idsToDelete.length) {
1300
1472
  return;
1301
1473
  }
1302
1474
  await database.delete(sessionsInIam).where(
1303
- and6(
1304
- eq6(sessionsInIam.tenantId, tenantId),
1305
- eq6(sessionsInIam.userId, userId),
1475
+ and7(
1476
+ eq7(sessionsInIam.tenantId, tenantId),
1477
+ eq7(sessionsInIam.userId, userId),
1306
1478
  inArray(
1307
1479
  sessionsInIam.id,
1308
1480
  idsToDelete.map((s) => s.id)
@@ -1312,17 +1484,17 @@ var cleanupOldSessions = async ({
1312
1484
  };
1313
1485
 
1314
1486
  // src/routes/auth/helper/user.ts
1315
- import { and as and7, eq as eq7, gt as gt3, sql as sql6 } from "drizzle-orm";
1487
+ import { and as and8, eq as eq8, gt as gt4, sql as sql6 } from "drizzle-orm";
1316
1488
  var checkExistingUserStatus = async ({
1317
1489
  tx,
1318
1490
  identifier,
1319
1491
  tenantId,
1320
1492
  isEmail
1321
1493
  }) => {
1322
- const whereClause = isEmail ? and7(
1323
- eq7(usersInIam.tenantId, tenantId),
1494
+ const whereClause = isEmail ? and8(
1495
+ eq8(usersInIam.tenantId, tenantId),
1324
1496
  sql6`lower(${usersInIam.email}) = lower(${identifier})`
1325
- ) : and7(eq7(usersInIam.tenantId, tenantId), eq7(usersInIam.phone, identifier));
1497
+ ) : and8(eq8(usersInIam.tenantId, tenantId), eq8(usersInIam.phone, identifier));
1326
1498
  const [existingUser] = await tx.select().from(usersInIam).where(whereClause).limit(1);
1327
1499
  if (!existingUser) {
1328
1500
  return { action: "proceed" };
@@ -1332,10 +1504,10 @@ var checkExistingUserStatus = async ({
1332
1504
  return { action: "error", code: AUTH_ERRORS.USER_EXISTS };
1333
1505
  }
1334
1506
  const [pendingVerification] = await tx.select().from(verificationsInIam).where(
1335
- and7(
1336
- eq7(verificationsInIam.userId, existingUser.id),
1337
- eq7(verificationsInIam.tenantId, tenantId),
1338
- gt3(verificationsInIam.expiresAt, (/* @__PURE__ */ new Date()).toISOString())
1507
+ and8(
1508
+ eq8(verificationsInIam.userId, existingUser.id),
1509
+ eq8(verificationsInIam.tenantId, tenantId),
1510
+ gt4(verificationsInIam.expiresAt, (/* @__PURE__ */ new Date()).toISOString())
1339
1511
  )
1340
1512
  ).limit(1);
1341
1513
  if (pendingVerification) {
@@ -1354,18 +1526,18 @@ var deleteUnverifiedUser = async ({
1354
1526
  tenantId
1355
1527
  }) => {
1356
1528
  await tx.delete(verificationsInIam).where(
1357
- and7(
1358
- eq7(verificationsInIam.userId, userId),
1359
- eq7(verificationsInIam.tenantId, tenantId)
1529
+ and8(
1530
+ eq8(verificationsInIam.userId, userId),
1531
+ eq8(verificationsInIam.tenantId, tenantId)
1360
1532
  )
1361
1533
  );
1362
1534
  await tx.delete(accountsInIam).where(
1363
- and7(
1364
- eq7(accountsInIam.userId, userId),
1365
- eq7(accountsInIam.tenantId, tenantId)
1535
+ and8(
1536
+ eq8(accountsInIam.userId, userId),
1537
+ eq8(accountsInIam.tenantId, tenantId)
1366
1538
  )
1367
1539
  );
1368
- await tx.delete(usersInIam).where(and7(eq7(usersInIam.id, userId), eq7(usersInIam.tenantId, tenantId)));
1540
+ await tx.delete(usersInIam).where(and8(eq8(usersInIam.id, userId), eq8(usersInIam.tenantId, tenantId)));
1369
1541
  };
1370
1542
  var createUserWithAccount = async ({
1371
1543
  tx,
@@ -1405,14 +1577,14 @@ var fetchUserForLogin = async ({
1405
1577
  userType
1406
1578
  }) => {
1407
1579
  const userTypeFilter = sql6`${usersInIam.userType} @> ARRAY[${userType}]::text[]`;
1408
- const whereClause = isEmail ? and7(
1409
- eq7(usersInIam.tenantId, tenantId),
1580
+ const whereClause = isEmail ? and8(
1581
+ eq8(usersInIam.tenantId, tenantId),
1410
1582
  userTypeFilter,
1411
1583
  sql6`lower(${usersInIam.email}) = lower(${identifier})`
1412
- ) : and7(
1413
- eq7(usersInIam.tenantId, tenantId),
1584
+ ) : and8(
1585
+ eq8(usersInIam.tenantId, tenantId),
1414
1586
  userTypeFilter,
1415
- eq7(usersInIam.phone, identifier)
1587
+ eq8(usersInIam.phone, identifier)
1416
1588
  );
1417
1589
  const [row] = await database.select({
1418
1590
  id: usersInIam.id,
@@ -1430,9 +1602,9 @@ var fetchUserForLogin = async ({
1430
1602
  hasPassword: sql6`exists(
1431
1603
  select 1
1432
1604
  from ${accountsInIam}
1433
- where ${eq7(accountsInIam.tenantId, tenantId)}
1434
- and ${eq7(accountsInIam.userId, usersInIam.id)}
1435
- and ${eq7(accountsInIam.provider, "credentials")}
1605
+ where ${eq8(accountsInIam.tenantId, tenantId)}
1606
+ and ${eq8(accountsInIam.userId, usersInIam.id)}
1607
+ and ${eq8(accountsInIam.provider, "credentials")}
1436
1608
  and ${sql6`${accountsInIam.password} is not null`}
1437
1609
  )`
1438
1610
  }).from(usersInIam).where(whereClause).limit(1);
@@ -1457,207 +1629,10 @@ var fetchUserByIdWithRoles = async ({
1457
1629
  bannedUntil: usersInIam.bannedUntil,
1458
1630
  loginAttempt: usersInIam.loginAttempt,
1459
1631
  ...getUserAuthSelect(tenantId)
1460
- }).from(usersInIam).where(and7(eq7(usersInIam.id, userId), eq7(usersInIam.tenantId, tenantId))).limit(1);
1632
+ }).from(usersInIam).where(and8(eq8(usersInIam.id, userId), eq8(usersInIam.tenantId, tenantId))).limit(1);
1461
1633
  return result || null;
1462
1634
  };
1463
1635
 
1464
- // src/routes/auth/helper/verification.ts
1465
- import { dayjs as dayjs2 } from "@mesob/common";
1466
- import { and as and8, desc, eq as eq8 } from "drizzle-orm";
1467
- var createVerification = async ({
1468
- tx,
1469
- tenantId,
1470
- userId,
1471
- type,
1472
- to,
1473
- config
1474
- }) => {
1475
- const isPhone = type === "phone-otp-sign-up";
1476
- const code = generateOtpCode(
1477
- isPhone ? config.phone.otpLength : config.email.otpLength
1478
- );
1479
- const hashedCode = await hashToken(code, config.secret);
1480
- const expiresAt = addDuration(
1481
- isPhone ? config.phone.expiresIn : config.email.expiresIn
1482
- );
1483
- const [verification] = await tx.insert(verificationsInIam).values({
1484
- tenantId,
1485
- userId,
1486
- type,
1487
- code: hashedCode,
1488
- expiresAt,
1489
- to,
1490
- attempt: 0
1491
- }).returning();
1492
- return { verificationId: verification.id, code, hash: hashedCode };
1493
- };
1494
- var sendVerification = async ({
1495
- channel,
1496
- to,
1497
- code,
1498
- config,
1499
- hash,
1500
- type
1501
- }) => {
1502
- if (channel === "phone" && config.phone.sendVerificationOTP) {
1503
- await config.phone.sendVerificationOTP({
1504
- phone: to,
1505
- code,
1506
- hash,
1507
- type
1508
- });
1509
- } else if (config.email.sendVerificationOTP) {
1510
- await config.email.sendVerificationOTP({
1511
- email: to,
1512
- code,
1513
- hash,
1514
- type
1515
- });
1516
- }
1517
- };
1518
- var checkVerificationResend = async ({
1519
- database,
1520
- tenantId,
1521
- userId,
1522
- type,
1523
- resendInterval
1524
- }) => {
1525
- if (!resendInterval) {
1526
- return { blocked: false };
1527
- }
1528
- const [verification] = await database.select({
1529
- id: verificationsInIam.id,
1530
- createdAt: verificationsInIam.createdAt
1531
- }).from(verificationsInIam).where(
1532
- and8(
1533
- eq8(verificationsInIam.tenantId, tenantId),
1534
- eq8(verificationsInIam.userId, userId),
1535
- eq8(verificationsInIam.type, type)
1536
- )
1537
- ).orderBy(desc(verificationsInIam.createdAt)).limit(1);
1538
- if (!verification) {
1539
- return { blocked: false };
1540
- }
1541
- const cooldownSeconds = parseDuration(resendInterval);
1542
- const createdAt = dayjs2(verification.createdAt);
1543
- const blocked = dayjs2().diff(createdAt, "second") < cooldownSeconds;
1544
- return { blocked, verificationId: verification.id };
1545
- };
1546
- var handleEmailVerification = async ({
1547
- c,
1548
- user,
1549
- config,
1550
- database,
1551
- tenantId
1552
- }) => {
1553
- if (!user.email) {
1554
- return c.json({ error: "User email not found" }, 401);
1555
- }
1556
- await database.delete(verificationsInIam).where(
1557
- and8(
1558
- eq8(verificationsInIam.tenantId, tenantId),
1559
- eq8(verificationsInIam.userId, user.id),
1560
- eq8(verificationsInIam.type, "email-verification")
1561
- )
1562
- );
1563
- const code = generateOtpCode(config.email.otpLength);
1564
- const hashedCode = await hashToken(code, config.secret);
1565
- const expiresAt = addDuration(config.email.expiresIn);
1566
- const [verification] = await database.insert(verificationsInIam).values({
1567
- tenantId,
1568
- userId: user.id,
1569
- type: "email-verification",
1570
- code: hashedCode,
1571
- expiresAt,
1572
- to: user.email,
1573
- attempt: 0
1574
- }).returning({
1575
- id: verificationsInIam.id,
1576
- tenantId: verificationsInIam.tenantId,
1577
- userId: verificationsInIam.userId,
1578
- type: verificationsInIam.type,
1579
- code: verificationsInIam.code,
1580
- to: verificationsInIam.to,
1581
- expiresAt: verificationsInIam.expiresAt,
1582
- createdAt: verificationsInIam.createdAt,
1583
- attempt: verificationsInIam.attempt
1584
- });
1585
- if (config.email.sendVerificationOTP) {
1586
- await config.email.sendVerificationOTP({
1587
- email: user.email,
1588
- code,
1589
- hash: hashedCode,
1590
- type: "email-verification"
1591
- });
1592
- }
1593
- return c.json(
1594
- {
1595
- user: normalizeAuthUser(user),
1596
- session: null,
1597
- verificationId: verification.id,
1598
- requiresVerification: true
1599
- },
1600
- 200
1601
- );
1602
- };
1603
- var handlePhoneVerification = async ({
1604
- c,
1605
- user,
1606
- config,
1607
- database,
1608
- tenantId
1609
- }) => {
1610
- if (!user.phone) {
1611
- return c.json({ error: "User phone not found" }, 401);
1612
- }
1613
- await database.delete(verificationsInIam).where(
1614
- and8(
1615
- eq8(verificationsInIam.tenantId, tenantId),
1616
- eq8(verificationsInIam.userId, user.id),
1617
- eq8(verificationsInIam.type, "phone-otp")
1618
- )
1619
- );
1620
- const code = generateOtpCode(config.phone.otpLength);
1621
- const hashedCode = await hashToken(code, config.secret);
1622
- const expiresAt = addDuration(config.phone.expiresIn);
1623
- const [verification] = await database.insert(verificationsInIam).values({
1624
- tenantId,
1625
- userId: user.id,
1626
- type: "phone-otp",
1627
- code: hashedCode,
1628
- expiresAt,
1629
- to: user.phone,
1630
- attempt: 0
1631
- }).returning({
1632
- id: verificationsInIam.id,
1633
- tenantId: verificationsInIam.tenantId,
1634
- userId: verificationsInIam.userId,
1635
- type: verificationsInIam.type,
1636
- code: verificationsInIam.code,
1637
- to: verificationsInIam.to,
1638
- expiresAt: verificationsInIam.expiresAt,
1639
- createdAt: verificationsInIam.createdAt,
1640
- attempt: verificationsInIam.attempt
1641
- });
1642
- if (config.phone.sendVerificationOTP) {
1643
- await config.phone.sendVerificationOTP({
1644
- phone: user.phone,
1645
- code,
1646
- hash: hashedCode,
1647
- type: "phone-otp"
1648
- });
1649
- }
1650
- return c.json(
1651
- {
1652
- user: normalizeAuthUser(user),
1653
- session: null,
1654
- verificationId: verification.id,
1655
- requiresVerification: true
1656
- },
1657
- 200
1658
- );
1659
- };
1660
-
1661
1636
  // src/routes/auth/handler/sign-in.ts
1662
1637
  var signInHandler = (
1663
1638
  // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: auth flow combines lockout, verification, and session creation
@@ -1821,7 +1796,7 @@ var signInHandler = (
1821
1796
  );
1822
1797
 
1823
1798
  // src/routes/auth/handler/sign-out.ts
1824
- import { and as and10, eq as eq10, gt as gt4 } from "drizzle-orm";
1799
+ import { and as and10, eq as eq10, gt as gt5 } from "drizzle-orm";
1825
1800
  import { getCookie } from "hono/cookie";
1826
1801
  var signOutHandler = async (c) => {
1827
1802
  const config = c.get("config");
@@ -1845,7 +1820,7 @@ var signOutHandler = async (c) => {
1845
1820
  }).from(sessionsInIam).where(
1846
1821
  and10(
1847
1822
  eq10(sessionsInIam.token, hashedToken),
1848
- gt4(sessionsInIam.expiresAt, (/* @__PURE__ */ new Date()).toISOString())
1823
+ gt5(sessionsInIam.expiresAt, (/* @__PURE__ */ new Date()).toISOString())
1849
1824
  )
1850
1825
  ).limit(1);
1851
1826
  if (session) {
@@ -1884,6 +1859,20 @@ var SignUpError = class extends Error {
1884
1859
  this.status = status;
1885
1860
  }
1886
1861
  };
1862
+ var normalizeSignupDomain = (value) => {
1863
+ return value.trim().toLowerCase().replace(/^@/, "");
1864
+ };
1865
+ var isAllowedSignupEmail = (email, allowedDomains) => {
1866
+ const domain = email.split("@")[1]?.toLowerCase();
1867
+ if (!domain) {
1868
+ return false;
1869
+ }
1870
+ const normalizedDomains = allowedDomains.map(normalizeSignupDomain).filter(Boolean);
1871
+ if (normalizedDomains.length === 0) {
1872
+ return true;
1873
+ }
1874
+ return normalizedDomains.includes(domain);
1875
+ };
1887
1876
  var signUpHandler = async (c) => {
1888
1877
  const body = c.req.valid("json");
1889
1878
  const config = c.get("config");
@@ -1895,7 +1884,19 @@ var signUpHandler = async (c) => {
1895
1884
  if (!identifier) {
1896
1885
  return c.json({ error: "Either email or phone is required" }, 409);
1897
1886
  }
1887
+ if (config.signUp && !config.signUp.enabled) {
1888
+ return c.json({ error: "Sign up is disabled" }, 403);
1889
+ }
1898
1890
  const isEmail = identifier.includes("@");
1891
+ if (isEmail && config.signUp && !config.signUp.emailEnabled) {
1892
+ return c.json({ error: "Email sign up is disabled" }, 403);
1893
+ }
1894
+ if (!isEmail && config.signUp && !config.signUp.phoneEnabled) {
1895
+ return c.json({ error: "Phone sign up is disabled" }, 403);
1896
+ }
1897
+ if (isEmail && config.signUp?.allowedEmailDomains && !isAllowedSignupEmail(identifier, config.signUp.allowedEmailDomains)) {
1898
+ return c.json({ error: "Email domain is not allowed for sign up" }, 403);
1899
+ }
1899
1900
  if (phone) {
1900
1901
  const phoneValidator = createPhoneField(config);
1901
1902
  if (!phoneValidator.validate(phone)) {
@@ -2832,7 +2833,7 @@ var email_route_default = emailRoutes;
2832
2833
  import { createRoute as createRoute4, OpenAPIHono as OpenAPIHono4 } from "@hono/zod-openapi";
2833
2834
 
2834
2835
  // src/routes/password/handler/change.ts
2835
- import { and as and18, eq as eq18, gt as gt5, ne } from "drizzle-orm";
2836
+ import { and as and18, eq as eq18, gt as gt6, ne } from "drizzle-orm";
2836
2837
  import { getCookie as getCookie2 } from "hono/cookie";
2837
2838
  var changePasswordHandler = async (c) => {
2838
2839
  const body = c.req.valid("json");
@@ -2878,7 +2879,7 @@ var changePasswordHandler = async (c) => {
2878
2879
  and18(
2879
2880
  eq18(sessionsInIam.tenantId, resolvedTenantId),
2880
2881
  eq18(sessionsInIam.userId, userId),
2881
- gt5(sessionsInIam.expiresAt, (/* @__PURE__ */ new Date()).toISOString()),
2882
+ gt6(sessionsInIam.expiresAt, (/* @__PURE__ */ new Date()).toISOString()),
2882
2883
  ne(sessionsInIam.token, hashedToken)
2883
2884
  )
2884
2885
  );
@@ -3075,7 +3076,7 @@ var forgotPasswordHandler = async (c) => {
3075
3076
  };
3076
3077
 
3077
3078
  // src/routes/password/handler/reset.ts
3078
- import { and as and20, eq as eq20, gt as gt6 } from "drizzle-orm";
3079
+ import { and as and20, eq as eq20, gt as gt7 } from "drizzle-orm";
3079
3080
 
3080
3081
  // src/routes/password/helper/session.ts
3081
3082
  var createPasswordResetSession = async ({
@@ -3155,7 +3156,7 @@ var resetPasswordHandler = async (c) => {
3155
3156
  and20(
3156
3157
  eq20(sessionsInIam.tenantId, resolvedTenantId),
3157
3158
  eq20(sessionsInIam.userId, verification.userId),
3158
- gt6(sessionsInIam.expiresAt, (/* @__PURE__ */ new Date()).toISOString())
3159
+ gt7(sessionsInIam.expiresAt, (/* @__PURE__ */ new Date()).toISOString())
3159
3160
  )
3160
3161
  );
3161
3162
  await database.delete(verificationsInIam).where(eq20(verificationsInIam.id, verificationId));
@@ -4025,6 +4026,14 @@ var phoneVerificationRequestHandler = async (c) => {
4025
4026
  return c.json({ error: "Phone authentication is disabled" }, 400);
4026
4027
  }
4027
4028
  const { phone, context } = body;
4029
+ if (context === "sign-up" && config.signUp) {
4030
+ if (!config.signUp.enabled) {
4031
+ return c.json({ error: "Sign up is disabled" }, 400);
4032
+ }
4033
+ if (!config.signUp.phoneEnabled) {
4034
+ return c.json({ error: "Phone sign up is disabled" }, 400);
4035
+ }
4036
+ }
4028
4037
  if (!phone) {
4029
4038
  return c.json({ error: "Phone required" }, 400);
4030
4039
  }
@@ -4221,7 +4230,7 @@ import { createRoute as createRoute7, OpenAPIHono as OpenAPIHono7 } from "@hono/
4221
4230
  import { z as z4 } from "zod";
4222
4231
 
4223
4232
  // src/routes/profile/handler/account-change-pending.ts
4224
- import { and as and26, eq as eq27, gt as gt7, sql as sql13 } from "drizzle-orm";
4233
+ import { and as and26, eq as eq27, gt as gt8, sql as sql13 } from "drizzle-orm";
4225
4234
  var accountChangePendingHandler = async (c) => {
4226
4235
  const config = c.get("config");
4227
4236
  const database = c.get("database");
@@ -4259,7 +4268,7 @@ var accountChangePendingHandler = async (c) => {
4259
4268
  eq27(verificationsInIam.userId, userId),
4260
4269
  eq27(verificationsInIam.type, "email-verification"),
4261
4270
  eq27(verificationsInIam.to, accountChange.newEmail),
4262
- gt7(verificationsInIam.expiresAt, sql13`CURRENT_TIMESTAMP`)
4271
+ gt8(verificationsInIam.expiresAt, sql13`CURRENT_TIMESTAMP`)
4263
4272
  )
4264
4273
  ).limit(1);
4265
4274
  verification = v || null;
@@ -4274,7 +4283,7 @@ var accountChangePendingHandler = async (c) => {
4274
4283
  eq27(verificationsInIam.userId, userId),
4275
4284
  eq27(verificationsInIam.type, "phone-otp-change-phone"),
4276
4285
  eq27(verificationsInIam.to, accountChange.newPhone),
4277
- gt7(verificationsInIam.expiresAt, sql13`CURRENT_TIMESTAMP`)
4286
+ gt8(verificationsInIam.expiresAt, sql13`CURRENT_TIMESTAMP`)
4278
4287
  )
4279
4288
  ).limit(1);
4280
4289
  verification = v || null;
@@ -6959,6 +6968,9 @@ var banUserHandler = async (c) => {
6959
6968
  return c.json({ user: normalizeUser(userWithRoles) }, 200);
6960
6969
  };
6961
6970
 
6971
+ // src/routes/users/helper/invite.ts
6972
+ import { and as and52, eq as eq57 } from "drizzle-orm";
6973
+
6962
6974
  // src/routes/users/helper/user.ts
6963
6975
  import { and as and51, eq as eq56, sql as sql26 } from "drizzle-orm";
6964
6976
  var checkUserExists = async ({
@@ -7028,7 +7040,89 @@ var inviteUser = (
7028
7040
  isEmail
7029
7041
  });
7030
7042
  if (existing) {
7031
- throw new Error("User already exists");
7043
+ const identifierVerified = isEmail ? existing.emailVerified : existing.phoneVerified;
7044
+ const hasInviteType = Array.isArray(existing.userType) && existing.userType.includes(config.userType);
7045
+ if (identifierVerified && hasInviteType) {
7046
+ throw new Error("User already exists");
7047
+ }
7048
+ const updates = {};
7049
+ if (!identifierVerified) {
7050
+ if (payload.emailVerified !== void 0 && payload.email) {
7051
+ updates.emailVerified = Boolean(payload.emailVerified);
7052
+ }
7053
+ if (payload.phoneVerified !== void 0 && payload.phone) {
7054
+ updates.phoneVerified = Boolean(payload.phoneVerified);
7055
+ }
7056
+ }
7057
+ if (!hasInviteType) {
7058
+ updates.userType = [config.userType];
7059
+ }
7060
+ let user2;
7061
+ if (Object.keys(updates).length > 0) {
7062
+ const [updated] = await database.update(usersInIam).set(updates).where(eq57(usersInIam.id, existing.id)).returning();
7063
+ if (!updated) {
7064
+ throw new Error("User update failed");
7065
+ }
7066
+ user2 = updated;
7067
+ } else {
7068
+ user2 = existing;
7069
+ }
7070
+ const [credAccount] = await database.select().from(accountsInIam).where(
7071
+ and52(
7072
+ eq57(accountsInIam.tenantId, tenantId),
7073
+ eq57(accountsInIam.userId, user2.id),
7074
+ eq57(accountsInIam.provider, "credentials")
7075
+ )
7076
+ ).limit(1);
7077
+ const hasPassword2 = Boolean(credAccount?.password);
7078
+ const resolvedInviteUrl2 = resolveInviteUrl({
7079
+ inviteUrl: payload.inviteUrl,
7080
+ identifier,
7081
+ hasPassword: hasPassword2
7082
+ });
7083
+ const delivery2 = {};
7084
+ if (payload.sendVia?.includes("email")) {
7085
+ if (payload.email && config.email.sendInvitation) {
7086
+ try {
7087
+ await config.email.sendInvitation({
7088
+ email: payload.email,
7089
+ fullName: user2.fullName,
7090
+ identifier,
7091
+ inviteUrl: resolvedInviteUrl2,
7092
+ tenantId
7093
+ });
7094
+ delivery2.email = "sent";
7095
+ } catch {
7096
+ delivery2.email = "failed";
7097
+ }
7098
+ } else {
7099
+ delivery2.email = "skipped";
7100
+ }
7101
+ }
7102
+ if (payload.sendVia?.includes("sms")) {
7103
+ if (payload.phone && config.phone.sendInvitation) {
7104
+ try {
7105
+ await config.phone.sendInvitation({
7106
+ phone: payload.phone,
7107
+ fullName: user2.fullName,
7108
+ identifier,
7109
+ inviteUrl: resolvedInviteUrl2,
7110
+ tenantId
7111
+ });
7112
+ delivery2.sms = "sent";
7113
+ } catch {
7114
+ delivery2.sms = "failed";
7115
+ }
7116
+ } else {
7117
+ delivery2.sms = "skipped";
7118
+ }
7119
+ }
7120
+ return {
7121
+ user: normalizeUser(user2),
7122
+ delivery: delivery2,
7123
+ inviteUrl: resolvedInviteUrl2,
7124
+ hasPassword: hasPassword2
7125
+ };
7032
7126
  }
7033
7127
  const userHandle = payload.handle || generateHandle();
7034
7128
  const existingHandle = await checkHandleExists({
@@ -7213,16 +7307,16 @@ var createUserHandler = async (c) => {
7213
7307
  };
7214
7308
 
7215
7309
  // src/routes/users/handler/delete-user.ts
7216
- import { and as and52, eq as eq57 } from "drizzle-orm";
7310
+ import { and as and53, eq as eq58 } from "drizzle-orm";
7217
7311
  var deleteUserHandler = async (c) => {
7218
7312
  const { id } = c.req.valid("param");
7219
7313
  const database = c.get("database");
7220
7314
  const tenantId = c.get("tenantId");
7221
- const [existing] = await database.select().from(usersInIam).where(and52(eq57(usersInIam.id, id), eq57(usersInIam.tenantId, tenantId))).limit(1);
7315
+ const [existing] = await database.select().from(usersInIam).where(and53(eq58(usersInIam.id, id), eq58(usersInIam.tenantId, tenantId))).limit(1);
7222
7316
  if (!existing) {
7223
7317
  return c.json({ error: "User not found" }, 404);
7224
7318
  }
7225
- await database.delete(usersInIam).where(and52(eq57(usersInIam.id, id), eq57(usersInIam.tenantId, tenantId)));
7319
+ await database.delete(usersInIam).where(and53(eq58(usersInIam.id, id), eq58(usersInIam.tenantId, tenantId)));
7226
7320
  return c.json({ message: "User deleted" }, 200);
7227
7321
  };
7228
7322
 
@@ -7266,7 +7360,7 @@ var inviteUserHandler = async (c) => {
7266
7360
  };
7267
7361
 
7268
7362
  // src/routes/users/handler/list-users.ts
7269
- import { and as and53, asc as asc5, desc as desc5, eq as eq58, gt as gt8, ilike as ilike4, inArray as inArray6, or as or4, sql as sql27 } from "drizzle-orm";
7363
+ import { and as and54, asc as asc5, desc as desc5, eq as eq59, gt as gt9, ilike as ilike4, inArray as inArray6, or as or4, sql as sql27 } from "drizzle-orm";
7270
7364
  var userSelect = {
7271
7365
  id: usersInIam.id,
7272
7366
  tenantId: usersInIam.tenantId,
@@ -7319,7 +7413,7 @@ var listUsersHandler = async (c) => {
7319
7413
  const limit = query.limit || 20;
7320
7414
  const offset = (page - 1) * limit;
7321
7415
  const userTypeFilter = query.userType && query.userType !== "all" ? query.userType : null;
7322
- const conditions = [eq58(usersInIam.tenantId, tenantId)];
7416
+ const conditions = [eq59(usersInIam.tenantId, tenantId)];
7323
7417
  if (userTypeFilter) {
7324
7418
  conditions.push(
7325
7419
  sql27`${usersInIam.userType} @> ARRAY[${userTypeFilter}]::text[]`
@@ -7347,19 +7441,19 @@ var listUsersHandler = async (c) => {
7347
7441
  }
7348
7442
  if (query.filter === "verified") {
7349
7443
  const verifiedCond = or4(
7350
- eq58(usersInIam.emailVerified, true),
7351
- eq58(usersInIam.phoneVerified, true)
7444
+ eq59(usersInIam.emailVerified, true),
7445
+ eq59(usersInIam.phoneVerified, true)
7352
7446
  );
7353
7447
  if (verifiedCond) {
7354
7448
  conditions.push(verifiedCond);
7355
7449
  }
7356
7450
  } else if (query.filter === "unverified") {
7357
- conditions.push(eq58(usersInIam.emailVerified, false));
7358
- conditions.push(eq58(usersInIam.phoneVerified, false));
7451
+ conditions.push(eq59(usersInIam.emailVerified, false));
7452
+ conditions.push(eq59(usersInIam.phoneVerified, false));
7359
7453
  }
7360
7454
  const orderDir = query.order === "asc" ? asc5 : desc5;
7361
7455
  const sortCol = query.sort && sortColumnMap4[query.sort] ? sortColumnMap4[query.sort] : usersInIam.fullName;
7362
- const whereClause = and53(...conditions);
7456
+ const whereClause = and54(...conditions);
7363
7457
  const [users, totalResult] = await Promise.all([
7364
7458
  database.select(userSelect).from(usersInIam).where(whereClause).orderBy(orderDir(sortCol)).limit(limit).offset(offset),
7365
7459
  database.select({ count: sql27`count(*)` }).from(usersInIam).where(whereClause)
@@ -7370,10 +7464,10 @@ var listUsersHandler = async (c) => {
7370
7464
  userId: sessionsInIam.userId,
7371
7465
  count: sql27`count(*)::int`.as("count")
7372
7466
  }).from(sessionsInIam).where(
7373
- and53(
7374
- eq58(sessionsInIam.tenantId, tenantId),
7467
+ and54(
7468
+ eq59(sessionsInIam.tenantId, tenantId),
7375
7469
  inArray6(sessionsInIam.userId, userIds),
7376
- gt8(sessionsInIam.expiresAt, (/* @__PURE__ */ new Date()).toISOString())
7470
+ gt9(sessionsInIam.expiresAt, (/* @__PURE__ */ new Date()).toISOString())
7377
7471
  )
7378
7472
  ).groupBy(sessionsInIam.userId) : [];
7379
7473
  const sessionCountByUser = new Map(
@@ -7385,13 +7479,13 @@ var listUsersHandler = async (c) => {
7385
7479
  name: rolesInIam.name
7386
7480
  }).from(userRolesInIam).innerJoin(
7387
7481
  rolesInIam,
7388
- and53(
7389
- eq58(rolesInIam.tenantId, userRolesInIam.tenantId),
7390
- eq58(rolesInIam.id, userRolesInIam.roleId)
7482
+ and54(
7483
+ eq59(rolesInIam.tenantId, userRolesInIam.tenantId),
7484
+ eq59(rolesInIam.id, userRolesInIam.roleId)
7391
7485
  )
7392
7486
  ).where(
7393
- and53(
7394
- eq58(userRolesInIam.tenantId, tenantId),
7487
+ and54(
7488
+ eq59(userRolesInIam.tenantId, tenantId),
7395
7489
  inArray6(userRolesInIam.userId, userIds)
7396
7490
  )
7397
7491
  ) : [];
@@ -7421,13 +7515,13 @@ var listUsersHandler = async (c) => {
7421
7515
  };
7422
7516
 
7423
7517
  // src/routes/users/handler/search-users.ts
7424
- import { and as and54, eq as eq59, ilike as ilike5, or as or5 } from "drizzle-orm";
7518
+ import { and as and55, eq as eq60, ilike as ilike5, or as or5 } from "drizzle-orm";
7425
7519
  var searchUsersHandler = async (c) => {
7426
7520
  const query = c.req.valid("query");
7427
7521
  const database = c.get("database");
7428
7522
  const tenantId = c.get("tenantId");
7429
7523
  const limit = query.limit || 20;
7430
- const conditions = [eq59(usersInIam.tenantId, tenantId)];
7524
+ const conditions = [eq60(usersInIam.tenantId, tenantId)];
7431
7525
  if (query.search && query.search.trim().length > 0) {
7432
7526
  const searchCondition = or5(
7433
7527
  ilike5(usersInIam.fullName, `%${query.search}%`),
@@ -7445,25 +7539,25 @@ var searchUsersHandler = async (c) => {
7445
7539
  phone: usersInIam.phone,
7446
7540
  handle: usersInIam.handle,
7447
7541
  image: usersInIam.image
7448
- }).from(usersInIam).where(and54(...conditions)).limit(limit);
7542
+ }).from(usersInIam).where(and55(...conditions)).limit(limit);
7449
7543
  return c.json({ users }, 200);
7450
7544
  };
7451
7545
 
7452
7546
  // src/routes/users/handler/update-user.ts
7453
- import { and as and55, eq as eq60, inArray as inArray7, sql as sql28 } from "drizzle-orm";
7547
+ import { and as and56, eq as eq61, inArray as inArray7, sql as sql28 } from "drizzle-orm";
7454
7548
  var updateUserHandler = async (c) => {
7455
7549
  const { id } = c.req.valid("param");
7456
7550
  const body = c.req.valid("json");
7457
7551
  const database = c.get("database");
7458
7552
  const tenantId = c.get("tenantId");
7459
- const [existing] = await database.select().from(usersInIam).where(and55(eq60(usersInIam.id, id), eq60(usersInIam.tenantId, tenantId))).limit(1);
7553
+ const [existing] = await database.select().from(usersInIam).where(and56(eq61(usersInIam.id, id), eq61(usersInIam.tenantId, tenantId))).limit(1);
7460
7554
  if (!existing) {
7461
7555
  return c.json({ error: "User not found" }, 404);
7462
7556
  }
7463
7557
  if (body.handle && body.handle !== existing.handle) {
7464
7558
  const [handleExists] = await database.select().from(usersInIam).where(
7465
- and55(
7466
- eq60(usersInIam.tenantId, tenantId),
7559
+ and56(
7560
+ eq61(usersInIam.tenantId, tenantId),
7467
7561
  sql28`lower(${usersInIam.handle}) = lower(${body.handle})`
7468
7562
  )
7469
7563
  ).limit(1);
@@ -7496,7 +7590,7 @@ var updateUserHandler = async (c) => {
7496
7590
  const [updated] = await database.update(usersInIam).set({
7497
7591
  ...updateData,
7498
7592
  updatedAt: sql28`CURRENT_TIMESTAMP`
7499
- }).where(and55(eq60(usersInIam.id, id), eq60(usersInIam.tenantId, tenantId))).returning({
7593
+ }).where(and56(eq61(usersInIam.id, id), eq61(usersInIam.tenantId, tenantId))).returning({
7500
7594
  id: usersInIam.id,
7501
7595
  tenantId: usersInIam.tenantId,
7502
7596
  fullName: usersInIam.fullName,
@@ -7515,8 +7609,8 @@ var updateUserHandler = async (c) => {
7515
7609
  const roleIds = body.roleIds;
7516
7610
  if (roleIds.length > 0) {
7517
7611
  const validRoles = await database.select({ id: rolesInIam.id, code: rolesInIam.code }).from(rolesInIam).where(
7518
- and55(
7519
- eq60(rolesInIam.tenantId, tenantId),
7612
+ and56(
7613
+ eq61(rolesInIam.tenantId, tenantId),
7520
7614
  inArray7(rolesInIam.id, roleIds)
7521
7615
  )
7522
7616
  );
@@ -7525,9 +7619,9 @@ var updateUserHandler = async (c) => {
7525
7619
  );
7526
7620
  const toInsert = roleIds.filter((rid) => assignableIds.has(rid));
7527
7621
  await database.delete(userRolesInIam).where(
7528
- and55(
7529
- eq60(userRolesInIam.tenantId, tenantId),
7530
- eq60(userRolesInIam.userId, id)
7622
+ and56(
7623
+ eq61(userRolesInIam.tenantId, tenantId),
7624
+ eq61(userRolesInIam.userId, id)
7531
7625
  )
7532
7626
  );
7533
7627
  if (toInsert.length > 0) {
@@ -7541,9 +7635,9 @@ var updateUserHandler = async (c) => {
7541
7635
  }
7542
7636
  } else {
7543
7637
  await database.delete(userRolesInIam).where(
7544
- and55(
7545
- eq60(userRolesInIam.tenantId, tenantId),
7546
- eq60(userRolesInIam.userId, id)
7638
+ and56(
7639
+ eq61(userRolesInIam.tenantId, tenantId),
7640
+ eq61(userRolesInIam.userId, id)
7547
7641
  )
7548
7642
  );
7549
7643
  }
@@ -7957,31 +8051,31 @@ var users_route_default = userRoutes;
7957
8051
  import { createRoute as createRoute15, OpenAPIHono as OpenAPIHono15 } from "@hono/zod-openapi";
7958
8052
 
7959
8053
  // src/routes/verifications/handler/invalidate-verification.ts
7960
- import { and as and56, eq as eq61 } from "drizzle-orm";
8054
+ import { and as and57, eq as eq62 } from "drizzle-orm";
7961
8055
  var invalidateVerificationHandler = async (c) => {
7962
8056
  const { id } = c.req.valid("param");
7963
8057
  const database = c.get("database");
7964
8058
  const tenantId = c.get("tenantId");
7965
8059
  const [existing] = await database.select().from(verificationsInIam).where(
7966
- and56(
7967
- eq61(verificationsInIam.id, id),
7968
- eq61(verificationsInIam.tenantId, tenantId)
8060
+ and57(
8061
+ eq62(verificationsInIam.id, id),
8062
+ eq62(verificationsInIam.tenantId, tenantId)
7969
8063
  )
7970
8064
  ).limit(1);
7971
8065
  if (!existing) {
7972
8066
  return c.json({ error: "Verification not found" }, 404);
7973
8067
  }
7974
8068
  await database.delete(verificationsInIam).where(
7975
- and56(
7976
- eq61(verificationsInIam.id, id),
7977
- eq61(verificationsInIam.tenantId, tenantId)
8069
+ and57(
8070
+ eq62(verificationsInIam.id, id),
8071
+ eq62(verificationsInIam.tenantId, tenantId)
7978
8072
  )
7979
8073
  );
7980
8074
  return c.json({ message: "Verification invalidated" }, 200);
7981
8075
  };
7982
8076
 
7983
8077
  // src/routes/verifications/handler/list-verifications.ts
7984
- import { and as and57, eq as eq62, sql as sql29 } from "drizzle-orm";
8078
+ import { and as and58, eq as eq63, sql as sql29 } from "drizzle-orm";
7985
8079
  var listVerificationsHandler = async (c) => {
7986
8080
  const query = c.req.valid("query");
7987
8081
  const database = c.get("database");
@@ -7989,12 +8083,12 @@ var listVerificationsHandler = async (c) => {
7989
8083
  const page = query.page || 1;
7990
8084
  const limit = query.limit || 20;
7991
8085
  const offset = (page - 1) * limit;
7992
- const conditions = [eq62(verificationsInIam.tenantId, tenantId)];
8086
+ const conditions = [eq63(verificationsInIam.tenantId, tenantId)];
7993
8087
  if (query.userId) {
7994
- conditions.push(eq62(verificationsInIam.userId, query.userId));
8088
+ conditions.push(eq63(verificationsInIam.userId, query.userId));
7995
8089
  }
7996
8090
  if (query.type) {
7997
- conditions.push(eq62(verificationsInIam.type, query.type));
8091
+ conditions.push(eq63(verificationsInIam.type, query.type));
7998
8092
  }
7999
8093
  if (query.status) {
8000
8094
  if (query.status === "active") {
@@ -8008,8 +8102,8 @@ var listVerificationsHandler = async (c) => {
8008
8102
  }
8009
8103
  }
8010
8104
  const [verifications, totalResult] = await Promise.all([
8011
- database.select().from(verificationsInIam).where(and57(...conditions)).limit(limit).offset(offset),
8012
- database.select({ count: sql29`count(*)` }).from(verificationsInIam).where(and57(...conditions))
8105
+ database.select().from(verificationsInIam).where(and58(...conditions)).limit(limit).offset(offset),
8106
+ database.select({ count: sql29`count(*)` }).from(verificationsInIam).where(and58(...conditions))
8013
8107
  ]);
8014
8108
  const total = Number(totalResult[0]?.count || 0);
8015
8109
  return c.json({ verifications, total, page, limit }, 200);
@@ -8519,6 +8613,12 @@ var defaultAuthConfig = {
8519
8613
  ...defaultConfig,
8520
8614
  phoneRegex: defaultPhoneRegex
8521
8615
  },
8616
+ signUp: {
8617
+ enabled: true,
8618
+ emailEnabled: true,
8619
+ phoneEnabled: true,
8620
+ allowedEmailDomains: []
8621
+ },
8522
8622
  security: {
8523
8623
  maxLoginAttempts: 5,
8524
8624
  lockoutDuration: "15m"