@mesob/auth-hono 0.3.5 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -161,11 +161,10 @@ var tenantsInIam = iam.table("tenants", {
161
161
  var rolePermissionsInIam = iam.table("role_permissions", {
162
162
  id: uuid().default(sql`uuid_generate_v7()`).primaryKey().notNull(),
163
163
  tenantId: varchar("tenant_id", { length: 30 }).notNull(),
164
- roleId: text("role_id").notNull(),
165
- permissionId: text("permission_id").notNull()
164
+ permissionId: text("permission_id").notNull(),
165
+ roleId: uuid("role_id").notNull()
166
166
  }, (table) => [
167
167
  index("idx_role_permissions_permission_id").using("btree", table.tenantId.asc().nullsLast().op("text_ops"), table.permissionId.asc().nullsLast().op("text_ops")),
168
- index("idx_role_permissions_tenant_role").using("btree", table.tenantId.asc().nullsLast().op("text_ops"), table.roleId.asc().nullsLast().op("text_ops")),
169
168
  foreignKey({
170
169
  columns: [table.tenantId],
171
170
  foreignColumns: [tenantsInIam.id],
@@ -177,11 +176,11 @@ var rolePermissionsInIam = iam.table("role_permissions", {
177
176
  name: "role_permissions_permission_id_fkey"
178
177
  }).onUpdate("cascade").onDelete("cascade"),
179
178
  foreignKey({
180
- columns: [table.roleId],
181
- foreignColumns: [rolesInIam.id],
182
- name: "role_permissions_role_id_fkey"
183
- }).onUpdate("cascade").onDelete("cascade"),
184
- unique("role_permissions_unique").on(table.roleId, table.permissionId),
179
+ columns: [table.tenantId, table.roleId],
180
+ foreignColumns: [rolesInIam.tenantId, rolesInIam.id],
181
+ name: "role_permissions_tenant_role_fkey"
182
+ }).onDelete("cascade"),
183
+ unique("role_permissions_tenant_role_permission_unique").on(table.tenantId, table.permissionId, table.roleId),
185
184
  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)` })
186
185
  ]);
187
186
  var permissionsInIam = iam.table("permissions", {
@@ -225,23 +224,6 @@ var accountsInIam = iam.table("accounts", {
225
224
  unique("accounts_tenant_provider_account_unique").on(table.tenantId, table.provider, table.providerAccountId),
226
225
  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)` })
227
226
  ]);
228
- var rolesInIam = iam.table("roles", {
229
- id: text().primaryKey().notNull(),
230
- tenantId: varchar("tenant_id", { length: 30 }).notNull(),
231
- createdAt: timestamp("created_at", { withTimezone: true, mode: "string" }).default(sql`CURRENT_TIMESTAMP`).notNull(),
232
- updatedAt: timestamp("updated_at", { withTimezone: true, mode: "string" }).default(sql`CURRENT_TIMESTAMP`).notNull(),
233
- name: jsonb().notNull(),
234
- description: jsonb().notNull(),
235
- code: text().notNull()
236
- }, (table) => [
237
- foreignKey({
238
- columns: [table.tenantId],
239
- foreignColumns: [tenantsInIam.id],
240
- name: "roles_tenant_id_fkey"
241
- }).onUpdate("cascade").onDelete("cascade"),
242
- unique("roles_tenant_code_unique").on(table.tenantId, table.code),
243
- pgPolicy("tenant_isolation", { as: "permissive", for: "all", to: ["public"], using: sql`((tenant_id)::text = (iam.current_tenant_id())::text)`, withCheck: sql`((tenant_id)::text = (iam.current_tenant_id())::text)` })
244
- ]);
245
227
  var usersInIam = iam.table("users", {
246
228
  id: uuid().default(sql`uuid_generate_v7()`).primaryKey().notNull(),
247
229
  tenantId: varchar("tenant_id", { length: 30 }).notNull(),
@@ -257,7 +239,7 @@ var usersInIam = iam.table("users", {
257
239
  bannedUntil: timestamp("banned_until", { withTimezone: true, mode: "string" }),
258
240
  lastSignInAt: timestamp("last_sign_in_at", { withTimezone: true, mode: "string" }),
259
241
  loginAttempt: smallint("login_attempt").default(0).notNull(),
260
- userType: text("user_type").array().default(["employee"]).notNull()
242
+ userType: text("user_type").array().default(["RAY"]).notNull()
261
243
  }, (table) => [
262
244
  index("idx_users_auth_lookup").using("btree", table.tenantId.asc().nullsLast().op("bool_ops"), table.email.asc().nullsLast().op("bool_ops"), table.id.asc().nullsLast().op("timestamptz_ops"), table.emailVerified.asc().nullsLast().op("timestamptz_ops"), table.bannedUntil.asc().nullsLast().op("uuid_ops")).where(sql`(email IS NOT NULL)`),
263
245
  index("idx_users_email_lookup").using("btree", table.tenantId.asc().nullsLast().op("text_ops"), table.email.asc().nullsLast().op("text_ops")).where(sql`(email IS NOT NULL)`),
@@ -281,14 +263,34 @@ var usersInIam = iam.table("users", {
281
263
  check("users_contact_required_check", sql`(email IS NOT NULL) OR (phone IS NOT NULL)`),
282
264
  check("users_user_type_check", sql`user_type <@ ARRAY['candidate'::text, 'employee'::text, 'admin'::text]`)
283
265
  ]);
266
+ var rolesInIam = iam.table("roles", {
267
+ tenantId: varchar("tenant_id", { length: 30 }).notNull(),
268
+ createdAt: timestamp("created_at", { withTimezone: true, mode: "string" }).default(sql`CURRENT_TIMESTAMP`).notNull(),
269
+ updatedAt: timestamp("updated_at", { withTimezone: true, mode: "string" }).default(sql`CURRENT_TIMESTAMP`).notNull(),
270
+ name: jsonb().notNull(),
271
+ description: jsonb().notNull(),
272
+ code: text().notNull(),
273
+ id: uuid().default(sql`uuid_generate_v7()`).primaryKey().notNull(),
274
+ isSystem: boolean("is_system").default(false).notNull(),
275
+ isEditable: boolean("is_editable").default(true).notNull(),
276
+ isDeletable: boolean("is_deletable").default(true).notNull()
277
+ }, (table) => [
278
+ foreignKey({
279
+ columns: [table.tenantId],
280
+ foreignColumns: [tenantsInIam.id],
281
+ name: "roles_tenant_id_fkey"
282
+ }).onUpdate("cascade").onDelete("cascade"),
283
+ unique("roles_tenant_code_unique").on(table.tenantId, table.code),
284
+ unique("roles_tenant_id_unique").on(table.tenantId, table.id),
285
+ 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)` })
286
+ ]);
284
287
  var userRolesInIam = iam.table("user_roles", {
285
288
  id: uuid().default(sql`uuid_generate_v7()`).primaryKey().notNull(),
286
289
  tenantId: varchar("tenant_id", { length: 30 }).notNull(),
287
290
  userId: uuid("user_id").notNull(),
288
- roleId: text("role_id").notNull()
291
+ roleId: uuid("role_id").notNull()
289
292
  }, (table) => [
290
- index("idx_user_roles_role_id").using("btree", table.tenantId.asc().nullsLast().op("text_ops"), table.roleId.asc().nullsLast().op("text_ops")),
291
- index("idx_user_roles_tenant_user").using("btree", table.tenantId.asc().nullsLast().op("text_ops"), table.userId.asc().nullsLast().op("text_ops")),
293
+ index("idx_user_roles_tenant_user").using("btree", table.tenantId.asc().nullsLast().op("text_ops"), table.userId.asc().nullsLast().op("uuid_ops")),
292
294
  foreignKey({
293
295
  columns: [table.tenantId],
294
296
  foreignColumns: [tenantsInIam.id],
@@ -300,11 +302,11 @@ var userRolesInIam = iam.table("user_roles", {
300
302
  name: "user_roles_user_id_fkey"
301
303
  }).onUpdate("cascade").onDelete("cascade"),
302
304
  foreignKey({
303
- columns: [table.roleId],
304
- foreignColumns: [rolesInIam.id],
305
- name: "user_roles_role_id_fkey"
306
- }).onUpdate("cascade").onDelete("cascade"),
307
- unique("user_roles_unique").on(table.userId, table.roleId),
305
+ columns: [table.tenantId, table.roleId],
306
+ foreignColumns: [rolesInIam.tenantId, rolesInIam.id],
307
+ name: "user_roles_tenant_role_fkey"
308
+ }).onDelete("cascade"),
309
+ unique("user_roles_tenant_user_role_unique").on(table.tenantId, table.userId, table.roleId),
308
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)` })
309
311
  ]);
310
312
  var domainsInIam = iam.table("domains", {
@@ -348,8 +350,8 @@ var tenantsInIamRelations = relations(tenantsInIam, ({ many }) => ({
348
350
  accountChangesInIam: many(accountChangesInIam),
349
351
  rolePermissionsInIam: many(rolePermissionsInIam),
350
352
  accountsInIam: many(accountsInIam),
351
- rolesInIam: many(rolesInIam),
352
353
  usersInIam: many(usersInIam),
354
+ rolesInIam: many(rolesInIam),
353
355
  userRolesInIam: many(userRolesInIam),
354
356
  domainsInIam: many(domainsInIam)
355
357
  }));
@@ -394,8 +396,8 @@ var rolePermissionsInIamRelations = relations(rolePermissionsInIam, ({ one }) =>
394
396
  references: [permissionsInIam.id]
395
397
  }),
396
398
  rolesInIam: one(rolesInIam, {
397
- fields: [rolePermissionsInIam.roleId],
398
- references: [rolesInIam.id]
399
+ fields: [rolePermissionsInIam.tenantId],
400
+ references: [rolesInIam.tenantId]
399
401
  })
400
402
  }));
401
403
  var permissionsInIamRelations = relations(permissionsInIam, ({ many }) => ({
@@ -429,8 +431,8 @@ var userRolesInIamRelations = relations(userRolesInIam, ({ one }) => ({
429
431
  references: [usersInIam.id]
430
432
  }),
431
433
  rolesInIam: one(rolesInIam, {
432
- fields: [userRolesInIam.roleId],
433
- references: [rolesInIam.id]
434
+ fields: [userRolesInIam.tenantId],
435
+ references: [rolesInIam.tenantId]
434
436
  })
435
437
  }));
436
438
  var domainsInIamRelations = relations(domainsInIam, ({ one }) => ({
@@ -534,7 +536,66 @@ var findTenantById = async (database, tenantId) => {
534
536
  };
535
537
 
536
538
  // src/db/orm/user.ts
539
+ import { and as and4, eq as eq4 } from "drizzle-orm";
540
+
541
+ // src/lib/user-auth-select.ts
537
542
  import { and as and3, eq as eq3, sql as sql3 } from "drizzle-orm";
543
+ function getUserAuthSelect(tenantId) {
544
+ return {
545
+ roles: sql3`COALESCE(
546
+ (
547
+ select array_to_json(array_agg(distinct "iam"."roles"."id"))
548
+ from ${userRolesInIam}
549
+ inner join ${rolesInIam}
550
+ on ${and3(
551
+ eq3(rolesInIam.tenantId, userRolesInIam.tenantId),
552
+ eq3(rolesInIam.id, userRolesInIam.roleId)
553
+ )}
554
+ where ${and3(
555
+ eq3(userRolesInIam.tenantId, tenantId),
556
+ eq3(userRolesInIam.userId, usersInIam.id)
557
+ )}
558
+ ),
559
+ '[]'::json
560
+ )`,
561
+ roleCodes: sql3`COALESCE(
562
+ (
563
+ select array_to_json(array_agg(distinct ${rolesInIam.code}))
564
+ from ${userRolesInIam}
565
+ inner join ${rolesInIam}
566
+ on ${and3(
567
+ eq3(rolesInIam.tenantId, userRolesInIam.tenantId),
568
+ eq3(rolesInIam.id, userRolesInIam.roleId)
569
+ )}
570
+ where ${and3(
571
+ eq3(userRolesInIam.tenantId, tenantId),
572
+ eq3(userRolesInIam.userId, usersInIam.id)
573
+ )}
574
+ ),
575
+ '[]'::json
576
+ )`,
577
+ permissions: sql3`COALESCE(
578
+ (
579
+ select array_to_json(array_agg(distinct "iam"."permissions"."id"))
580
+ from ${userRolesInIam}
581
+ inner join ${rolePermissionsInIam}
582
+ on ${and3(
583
+ eq3(rolePermissionsInIam.tenantId, userRolesInIam.tenantId),
584
+ eq3(rolePermissionsInIam.roleId, userRolesInIam.roleId)
585
+ )}
586
+ inner join ${permissionsInIam}
587
+ on ${eq3(permissionsInIam.id, rolePermissionsInIam.permissionId)}
588
+ where ${and3(
589
+ eq3(userRolesInIam.tenantId, tenantId),
590
+ eq3(userRolesInIam.userId, usersInIam.id)
591
+ )}
592
+ ),
593
+ '[]'::json
594
+ )`
595
+ };
596
+ }
597
+
598
+ // src/db/orm/user.ts
538
599
  var fetchUserWithRoles = async ({
539
600
  database,
540
601
  userId,
@@ -551,40 +612,8 @@ var fetchUserWithRoles = async ({
551
612
  emailVerified: usersInIam.emailVerified,
552
613
  phoneVerified: usersInIam.phoneVerified,
553
614
  lastSignInAt: usersInIam.lastSignInAt,
554
- roles: sql3`COALESCE(
555
- array_to_json(array_agg(DISTINCT ${rolesInIam.id}) FILTER (WHERE ${userRolesInIam.id} IS NOT NULL)),
556
- '[]'::json
557
- )`,
558
- roleCodes: sql3`COALESCE(
559
- array_to_json(array_agg(DISTINCT ${rolesInIam.code}) FILTER (WHERE ${rolesInIam.code} IS NOT NULL)),
560
- '[]'::json
561
- )`,
562
- permissions: sql3`COALESCE(
563
- array_to_json(array_agg(DISTINCT ${permissionsInIam.id}) FILTER (WHERE ${permissionsInIam.id} IS NOT NULL)),
564
- '[]'::json
565
- )`
566
- }).from(usersInIam).leftJoin(
567
- userRolesInIam,
568
- and3(
569
- eq3(userRolesInIam.userId, usersInIam.id),
570
- eq3(userRolesInIam.tenantId, tenantId)
571
- )
572
- ).leftJoin(
573
- rolesInIam,
574
- and3(
575
- eq3(userRolesInIam.roleId, rolesInIam.id),
576
- eq3(rolesInIam.tenantId, tenantId)
577
- )
578
- ).leftJoin(
579
- rolePermissionsInIam,
580
- and3(
581
- eq3(rolePermissionsInIam.roleId, rolesInIam.id),
582
- eq3(rolePermissionsInIam.tenantId, tenantId)
583
- )
584
- ).leftJoin(
585
- permissionsInIam,
586
- eq3(rolePermissionsInIam.permissionId, permissionsInIam.id)
587
- ).where(and3(eq3(usersInIam.id, userId), eq3(usersInIam.tenantId, tenantId))).groupBy(usersInIam.id).limit(1);
615
+ ...getUserAuthSelect(tenantId)
616
+ }).from(usersInIam).where(and4(eq4(usersInIam.id, userId), eq4(usersInIam.tenantId, tenantId))).limit(1);
588
617
  return userResult || null;
589
618
  };
590
619
 
@@ -821,6 +850,7 @@ import { z } from "zod";
821
850
  var emailField = z.string().trim().email("Invalid email address").max(255, "Email too long");
822
851
  var phoneField = z.string().trim().min(6, "Phone too short").max(30, "Phone too long").regex(/^[+()\d\s-]+$/, "Invalid phone number format");
823
852
  var passwordField = z.string().min(8, "Password must be at least 8 characters").max(128, "Password too long");
853
+ var identifierField = z.string().trim().min(1, "Identifier is required");
824
854
  var userSchema = z.object({
825
855
  id: z.string().uuid(),
826
856
  tenantId: z.string(),
@@ -843,12 +873,34 @@ var sessionSchema = z.object({
843
873
  userAgent: z.string().nullable().optional(),
844
874
  ip: z.string().nullable().optional()
845
875
  });
876
+ var authUserSchema = z.object({
877
+ id: z.string().uuid(),
878
+ tenantId: z.string(),
879
+ fullName: z.string(),
880
+ email: z.string().email().nullable(),
881
+ phone: z.string().nullable(),
882
+ image: z.string().nullable(),
883
+ emailVerified: z.boolean(),
884
+ phoneVerified: z.boolean()
885
+ });
886
+ var authSessionSchema = z.object({
887
+ id: z.string().uuid(),
888
+ expiresAt: z.string().datetime()
889
+ });
846
890
  var authSuccessSchema = z.object({
847
- user: userSchema,
848
- session: sessionSchema.nullable(),
891
+ user: authUserSchema,
892
+ session: authSessionSchema.nullable(),
849
893
  sessionToken: z.string().optional(),
850
894
  sessionExpiresAt: z.string().datetime().optional()
851
895
  });
896
+ var authAccountSchema = z.object({
897
+ fullName: z.string(),
898
+ email: z.string().email().nullable(),
899
+ phone: z.string().nullable(),
900
+ verified: z.boolean(),
901
+ hasPassword: z.boolean(),
902
+ requiresPasswordSetup: z.boolean()
903
+ });
852
904
  var messageSchema = z.object({
853
905
  message: z.string()
854
906
  });
@@ -870,7 +922,13 @@ var signUpSchema = z.object({
870
922
  path: ["email"]
871
923
  });
872
924
  var signInSchema = z.object({
873
- identifier: z.string(),
925
+ identifier: identifierField,
926
+ password: passwordField,
927
+ /** Keep me signed in for longer. Default: true */
928
+ rememberMe: z.boolean().default(true)
929
+ });
930
+ var setPasswordSchema = z.object({
931
+ identifier: identifierField,
874
932
  password: passwordField,
875
933
  /** Keep me signed in for longer. Default: true */
876
934
  rememberMe: z.boolean().default(true)
@@ -919,11 +977,14 @@ var messageWithVerificationIdSchema = messageSchema.extend({
919
977
  verificationId: z.string().uuid().optional()
920
978
  });
921
979
  var checkAccountSchema = z.object({
922
- username: z.string()
980
+ username: identifierField
923
981
  });
924
982
  var checkAccountResponseSchema = z.object({
925
983
  exists: z.boolean(),
926
- verified: z.boolean()
984
+ verified: z.boolean(),
985
+ hasPassword: z.boolean(),
986
+ requiresPasswordSetup: z.boolean(),
987
+ account: authAccountSchema.nullable()
927
988
  });
928
989
  var updateProfileSchema = z.object({
929
990
  fullName: z.string().min(1).max(255).optional().describe("User full name")
@@ -949,7 +1010,7 @@ var pendingAccountChangeResponseSchema = z.object({
949
1010
  });
950
1011
 
951
1012
  // src/routes/auth/handler/check-account.ts
952
- import { and as and4, eq as eq4, sql as sql4 } from "drizzle-orm";
1013
+ import { and as and5, eq as eq5, sql as sql4 } from "drizzle-orm";
953
1014
 
954
1015
  // src/lib/tenant.ts
955
1016
  import { HTTPException as HTTPException2 } from "hono/http-exception";
@@ -981,22 +1042,45 @@ var checkAccountHandler = async (c) => {
981
1042
  const { username } = body;
982
1043
  const isEmail = username.includes("@");
983
1044
  const userTypeFilter = sql4`${usersInIam.userType} @> ARRAY[${config.userType}]::text[]`;
984
- const whereClause = isEmail ? and4(
985
- eq4(usersInIam.tenantId, resolvedTenantId),
1045
+ const whereClause = isEmail ? and5(
1046
+ eq5(usersInIam.tenantId, resolvedTenantId),
986
1047
  userTypeFilter,
987
1048
  sql4`lower(${usersInIam.email}) = lower(${username})`
988
- ) : and4(
989
- eq4(usersInIam.tenantId, resolvedTenantId),
1049
+ ) : and5(
1050
+ eq5(usersInIam.tenantId, resolvedTenantId),
990
1051
  userTypeFilter,
991
- eq4(usersInIam.phone, username)
1052
+ eq5(usersInIam.phone, username)
992
1053
  );
993
1054
  const [result] = await database.select({
994
- verified: isEmail ? usersInIam.emailVerified : usersInIam.phoneVerified
1055
+ fullName: usersInIam.fullName,
1056
+ email: usersInIam.email,
1057
+ phone: usersInIam.phone,
1058
+ verified: isEmail ? usersInIam.emailVerified : usersInIam.phoneVerified,
1059
+ hasPassword: sql4`exists(
1060
+ select 1
1061
+ from ${accountsInIam}
1062
+ where ${accountsInIam.tenantId} = ${resolvedTenantId}
1063
+ and ${accountsInIam.userId} = ${usersInIam.id}
1064
+ and ${accountsInIam.provider} = 'credentials'
1065
+ and ${accountsInIam.password} is not null
1066
+ )`
995
1067
  }).from(usersInIam).where(whereClause).limit(1);
1068
+ const verified = result?.verified ?? false;
1069
+ const hasPassword = result?.hasPassword ?? false;
996
1070
  return c.json(
997
1071
  {
998
1072
  exists: !!result,
999
- verified: result?.verified ?? false
1073
+ verified,
1074
+ hasPassword,
1075
+ requiresPasswordSetup: !!result && verified && !hasPassword,
1076
+ account: result ? {
1077
+ fullName: result.fullName,
1078
+ email: result.email,
1079
+ phone: result.phone,
1080
+ verified,
1081
+ hasPassword,
1082
+ requiresPasswordSetup: verified && !hasPassword
1083
+ } : null
1000
1084
  },
1001
1085
  200
1002
1086
  );
@@ -1004,7 +1088,7 @@ var checkAccountHandler = async (c) => {
1004
1088
 
1005
1089
  // src/routes/auth/handler/sign-in.ts
1006
1090
  import { logger as logger2 } from "@mesob/common";
1007
- import { and as and8, eq as eq8 } from "drizzle-orm";
1091
+ import { and as and9, eq as eq9 } from "drizzle-orm";
1008
1092
 
1009
1093
  // src/errors.ts
1010
1094
  var AUTH_ERRORS = {
@@ -1018,15 +1102,25 @@ var AUTH_ERRORS = {
1018
1102
  REQUIRES_VERIFICATION: "REQUIRES_VERIFICATION",
1019
1103
  UNAUTHORIZED: "UNAUTHORIZED",
1020
1104
  ACCESS_DENIED: "ACCESS_DENIED",
1021
- HAS_NO_PASSWORD: "HAS_NO_PASSWORD"
1022
- };
1023
-
1024
- // src/lib/normalize-user.ts
1025
- var normalizeUser = (user) => ({
1026
- ...user,
1027
- roles: user.roles ?? null,
1028
- roleCodes: user.roleCodes ?? null
1029
- });
1105
+ HAS_NO_PASSWORD: "HAS_NO_PASSWORD",
1106
+ PASSWORD_ALREADY_SET: "PASSWORD_ALREADY_SET"
1107
+ };
1108
+
1109
+ // src/lib/normalize-auth-response.ts
1110
+ var normalizeAuthUser = (user) => ({
1111
+ id: user.id,
1112
+ tenantId: user.tenantId,
1113
+ fullName: user.fullName,
1114
+ email: user.email,
1115
+ phone: user.phone,
1116
+ image: user.image,
1117
+ emailVerified: user.emailVerified,
1118
+ phoneVerified: user.phoneVerified
1119
+ });
1120
+ var normalizeAuthSession = (session) => session ? {
1121
+ id: session.id,
1122
+ expiresAt: session.expiresAt
1123
+ } : null;
1030
1124
 
1031
1125
  // src/lib/session.ts
1032
1126
  import { dayjs } from "@mesob/common";
@@ -1105,7 +1199,7 @@ var getRefreshedExpiresAt = ({
1105
1199
  };
1106
1200
 
1107
1201
  // src/routes/auth/helper/session.ts
1108
- import { and as and5, asc, eq as eq5, gt as gt2, inArray, sql as sql5 } from "drizzle-orm";
1202
+ import { and as and6, asc, eq as eq6, gt as gt2, inArray, sql as sql5 } from "drizzle-orm";
1109
1203
  var createSessionRecord = async ({
1110
1204
  tx,
1111
1205
  tenantId,
@@ -1174,9 +1268,9 @@ var cleanupOldSessions = async ({
1174
1268
  maxSessions
1175
1269
  }) => {
1176
1270
  const [{ count }] = await database.select({ count: sql5`count(*)` }).from(sessionsInIam).where(
1177
- and5(
1178
- eq5(sessionsInIam.tenantId, tenantId),
1179
- eq5(sessionsInIam.userId, userId),
1271
+ and6(
1272
+ eq6(sessionsInIam.tenantId, tenantId),
1273
+ eq6(sessionsInIam.userId, userId),
1180
1274
  gt2(sessionsInIam.expiresAt, (/* @__PURE__ */ new Date()).toISOString())
1181
1275
  )
1182
1276
  );
@@ -1185,9 +1279,9 @@ var cleanupOldSessions = async ({
1185
1279
  }
1186
1280
  const toDeleteCount = count - maxSessions;
1187
1281
  const idsToDelete = await database.select({ id: sessionsInIam.id }).from(sessionsInIam).where(
1188
- and5(
1189
- eq5(sessionsInIam.tenantId, tenantId),
1190
- eq5(sessionsInIam.userId, userId),
1282
+ and6(
1283
+ eq6(sessionsInIam.tenantId, tenantId),
1284
+ eq6(sessionsInIam.userId, userId),
1191
1285
  gt2(sessionsInIam.expiresAt, (/* @__PURE__ */ new Date()).toISOString())
1192
1286
  )
1193
1287
  ).orderBy(asc(sessionsInIam.createdAt)).limit(toDeleteCount);
@@ -1195,9 +1289,9 @@ var cleanupOldSessions = async ({
1195
1289
  return;
1196
1290
  }
1197
1291
  await database.delete(sessionsInIam).where(
1198
- and5(
1199
- eq5(sessionsInIam.tenantId, tenantId),
1200
- eq5(sessionsInIam.userId, userId),
1292
+ and6(
1293
+ eq6(sessionsInIam.tenantId, tenantId),
1294
+ eq6(sessionsInIam.userId, userId),
1201
1295
  inArray(
1202
1296
  sessionsInIam.id,
1203
1297
  idsToDelete.map((s) => s.id)
@@ -1207,17 +1301,17 @@ var cleanupOldSessions = async ({
1207
1301
  };
1208
1302
 
1209
1303
  // src/routes/auth/helper/user.ts
1210
- import { and as and6, eq as eq6, gt as gt3, sql as sql6 } from "drizzle-orm";
1304
+ import { and as and7, eq as eq7, gt as gt3, sql as sql6 } from "drizzle-orm";
1211
1305
  var checkExistingUserStatus = async ({
1212
1306
  tx,
1213
1307
  identifier,
1214
1308
  tenantId,
1215
1309
  isEmail
1216
1310
  }) => {
1217
- const whereClause = isEmail ? and6(
1218
- eq6(usersInIam.tenantId, tenantId),
1311
+ const whereClause = isEmail ? and7(
1312
+ eq7(usersInIam.tenantId, tenantId),
1219
1313
  sql6`lower(${usersInIam.email}) = lower(${identifier})`
1220
- ) : and6(eq6(usersInIam.tenantId, tenantId), eq6(usersInIam.phone, identifier));
1314
+ ) : and7(eq7(usersInIam.tenantId, tenantId), eq7(usersInIam.phone, identifier));
1221
1315
  const [existingUser] = await tx.select().from(usersInIam).where(whereClause).limit(1);
1222
1316
  if (!existingUser) {
1223
1317
  return { action: "proceed" };
@@ -1227,9 +1321,9 @@ var checkExistingUserStatus = async ({
1227
1321
  return { action: "error", code: AUTH_ERRORS.USER_EXISTS };
1228
1322
  }
1229
1323
  const [pendingVerification] = await tx.select().from(verificationsInIam).where(
1230
- and6(
1231
- eq6(verificationsInIam.userId, existingUser.id),
1232
- eq6(verificationsInIam.tenantId, tenantId),
1324
+ and7(
1325
+ eq7(verificationsInIam.userId, existingUser.id),
1326
+ eq7(verificationsInIam.tenantId, tenantId),
1233
1327
  gt3(verificationsInIam.expiresAt, (/* @__PURE__ */ new Date()).toISOString())
1234
1328
  )
1235
1329
  ).limit(1);
@@ -1249,18 +1343,18 @@ var deleteUnverifiedUser = async ({
1249
1343
  tenantId
1250
1344
  }) => {
1251
1345
  await tx.delete(verificationsInIam).where(
1252
- and6(
1253
- eq6(verificationsInIam.userId, userId),
1254
- eq6(verificationsInIam.tenantId, tenantId)
1346
+ and7(
1347
+ eq7(verificationsInIam.userId, userId),
1348
+ eq7(verificationsInIam.tenantId, tenantId)
1255
1349
  )
1256
1350
  );
1257
1351
  await tx.delete(accountsInIam).where(
1258
- and6(
1259
- eq6(accountsInIam.userId, userId),
1260
- eq6(accountsInIam.tenantId, tenantId)
1352
+ and7(
1353
+ eq7(accountsInIam.userId, userId),
1354
+ eq7(accountsInIam.tenantId, tenantId)
1261
1355
  )
1262
1356
  );
1263
- await tx.delete(usersInIam).where(and6(eq6(usersInIam.id, userId), eq6(usersInIam.tenantId, tenantId)));
1357
+ await tx.delete(usersInIam).where(and7(eq7(usersInIam.id, userId), eq7(usersInIam.tenantId, tenantId)));
1264
1358
  };
1265
1359
  var createUserWithAccount = async ({
1266
1360
  tx,
@@ -1300,14 +1394,14 @@ var fetchUserForLogin = async ({
1300
1394
  userType
1301
1395
  }) => {
1302
1396
  const userTypeFilter = sql6`${usersInIam.userType} @> ARRAY[${userType}]::text[]`;
1303
- const whereClause = isEmail ? and6(
1304
- eq6(usersInIam.tenantId, tenantId),
1397
+ const whereClause = isEmail ? and7(
1398
+ eq7(usersInIam.tenantId, tenantId),
1305
1399
  userTypeFilter,
1306
1400
  sql6`lower(${usersInIam.email}) = lower(${identifier})`
1307
- ) : and6(
1308
- eq6(usersInIam.tenantId, tenantId),
1401
+ ) : and7(
1402
+ eq7(usersInIam.tenantId, tenantId),
1309
1403
  userTypeFilter,
1310
- eq6(usersInIam.phone, identifier)
1404
+ eq7(usersInIam.phone, identifier)
1311
1405
  );
1312
1406
  const [row] = await database.select({
1313
1407
  id: usersInIam.id,
@@ -1321,7 +1415,15 @@ var fetchUserForLogin = async ({
1321
1415
  phoneVerified: usersInIam.phoneVerified,
1322
1416
  lastSignInAt: usersInIam.lastSignInAt,
1323
1417
  bannedUntil: usersInIam.bannedUntil,
1324
- loginAttempt: usersInIam.loginAttempt
1418
+ loginAttempt: usersInIam.loginAttempt,
1419
+ hasPassword: sql6`exists(
1420
+ select 1
1421
+ from ${accountsInIam}
1422
+ where ${accountsInIam.tenantId} = ${tenantId}
1423
+ and ${accountsInIam.userId} = ${usersInIam.id}
1424
+ and ${accountsInIam.provider} = 'credentials'
1425
+ and ${accountsInIam.password} is not null
1426
+ )`
1325
1427
  }).from(usersInIam).where(whereClause).limit(1);
1326
1428
  return row || null;
1327
1429
  };
@@ -1343,46 +1445,14 @@ var fetchUserByIdWithRoles = async ({
1343
1445
  lastSignInAt: usersInIam.lastSignInAt,
1344
1446
  bannedUntil: usersInIam.bannedUntil,
1345
1447
  loginAttempt: usersInIam.loginAttempt,
1346
- roles: sql6`COALESCE(
1347
- array_to_json(array_agg(DISTINCT ${rolesInIam.id}) FILTER (WHERE ${userRolesInIam.id} IS NOT NULL)),
1348
- '[]'::json
1349
- )`,
1350
- roleCodes: sql6`COALESCE(
1351
- array_to_json(array_agg(DISTINCT ${rolesInIam.code}) FILTER (WHERE ${rolesInIam.code} IS NOT NULL)),
1352
- '[]'::json
1353
- )`,
1354
- permissions: sql6`COALESCE(
1355
- array_to_json(array_agg(DISTINCT ${permissionsInIam.id}) FILTER (WHERE ${permissionsInIam.id} IS NOT NULL)),
1356
- '[]'::json
1357
- )`
1358
- }).from(usersInIam).leftJoin(
1359
- userRolesInIam,
1360
- and6(
1361
- eq6(userRolesInIam.userId, usersInIam.id),
1362
- eq6(userRolesInIam.tenantId, tenantId)
1363
- )
1364
- ).leftJoin(
1365
- rolesInIam,
1366
- and6(
1367
- eq6(userRolesInIam.roleId, rolesInIam.id),
1368
- eq6(rolesInIam.tenantId, tenantId)
1369
- )
1370
- ).leftJoin(
1371
- rolePermissionsInIam,
1372
- and6(
1373
- eq6(rolePermissionsInIam.roleId, rolesInIam.id),
1374
- eq6(rolePermissionsInIam.tenantId, tenantId)
1375
- )
1376
- ).leftJoin(
1377
- permissionsInIam,
1378
- eq6(rolePermissionsInIam.permissionId, permissionsInIam.id)
1379
- ).where(and6(eq6(usersInIam.id, userId), eq6(usersInIam.tenantId, tenantId))).groupBy(usersInIam.id).limit(1);
1448
+ ...getUserAuthSelect(tenantId)
1449
+ }).from(usersInIam).where(and7(eq7(usersInIam.id, userId), eq7(usersInIam.tenantId, tenantId))).limit(1);
1380
1450
  return result || null;
1381
1451
  };
1382
1452
 
1383
1453
  // src/routes/auth/helper/verification.ts
1384
1454
  import { dayjs as dayjs2 } from "@mesob/common";
1385
- import { and as and7, desc, eq as eq7 } from "drizzle-orm";
1455
+ import { and as and8, desc, eq as eq8 } from "drizzle-orm";
1386
1456
  var createVerification = async ({
1387
1457
  tx,
1388
1458
  tenantId,
@@ -1448,10 +1518,10 @@ var checkVerificationResend = async ({
1448
1518
  id: verificationsInIam.id,
1449
1519
  createdAt: verificationsInIam.createdAt
1450
1520
  }).from(verificationsInIam).where(
1451
- and7(
1452
- eq7(verificationsInIam.tenantId, tenantId),
1453
- eq7(verificationsInIam.userId, userId),
1454
- eq7(verificationsInIam.type, type)
1521
+ and8(
1522
+ eq8(verificationsInIam.tenantId, tenantId),
1523
+ eq8(verificationsInIam.userId, userId),
1524
+ eq8(verificationsInIam.type, type)
1455
1525
  )
1456
1526
  ).orderBy(desc(verificationsInIam.createdAt)).limit(1);
1457
1527
  if (!verification) {
@@ -1473,10 +1543,10 @@ var handleEmailVerification = async ({
1473
1543
  return c.json({ error: "User email not found" }, 401);
1474
1544
  }
1475
1545
  await database.delete(verificationsInIam).where(
1476
- and7(
1477
- eq7(verificationsInIam.tenantId, tenantId),
1478
- eq7(verificationsInIam.userId, user.id),
1479
- eq7(verificationsInIam.type, "email-verification")
1546
+ and8(
1547
+ eq8(verificationsInIam.tenantId, tenantId),
1548
+ eq8(verificationsInIam.userId, user.id),
1549
+ eq8(verificationsInIam.type, "email-verification")
1480
1550
  )
1481
1551
  );
1482
1552
  const code = generateOtpCode(config.email.otpLength);
@@ -1511,7 +1581,7 @@ var handleEmailVerification = async ({
1511
1581
  }
1512
1582
  return c.json(
1513
1583
  {
1514
- user: normalizeUser(user),
1584
+ user: normalizeAuthUser(user),
1515
1585
  session: null,
1516
1586
  verificationId: verification.id,
1517
1587
  requiresVerification: true
@@ -1530,10 +1600,10 @@ var handlePhoneVerification = async ({
1530
1600
  return c.json({ error: "User phone not found" }, 401);
1531
1601
  }
1532
1602
  await database.delete(verificationsInIam).where(
1533
- and7(
1534
- eq7(verificationsInIam.tenantId, tenantId),
1535
- eq7(verificationsInIam.userId, user.id),
1536
- eq7(verificationsInIam.type, "phone-otp")
1603
+ and8(
1604
+ eq8(verificationsInIam.tenantId, tenantId),
1605
+ eq8(verificationsInIam.userId, user.id),
1606
+ eq8(verificationsInIam.type, "phone-otp")
1537
1607
  )
1538
1608
  );
1539
1609
  const code = generateOtpCode(config.phone.otpLength);
@@ -1568,7 +1638,7 @@ var handlePhoneVerification = async ({
1568
1638
  }
1569
1639
  return c.json(
1570
1640
  {
1571
- user: normalizeUser(user),
1641
+ user: normalizeAuthUser(user),
1572
1642
  session: null,
1573
1643
  verificationId: verification.id,
1574
1644
  requiresVerification: true
@@ -1578,177 +1648,178 @@ var handlePhoneVerification = async ({
1578
1648
  };
1579
1649
 
1580
1650
  // src/routes/auth/handler/sign-in.ts
1581
- var signInHandler = async (c) => {
1582
- const body = c.req.valid("json");
1583
- const config = c.get("config");
1584
- const database = c.get("database");
1585
- const tenantId = c.get("tenantId");
1586
- const resolvedTenantId = ensureTenantId(config, tenantId);
1587
- const { identifier, password, rememberMe = true } = body;
1588
- const isEmail = identifier.includes("@");
1589
- if (isEmail && !config.email.enabled) {
1590
- return c.json({ error: "Email authentication is disabled" }, 401);
1591
- }
1592
- if (!(isEmail || config.phone.enabled)) {
1593
- return c.json({ error: "Phone authentication is disabled" }, 401);
1594
- }
1595
- const user = await fetchUserForLogin({
1596
- database,
1597
- identifier,
1598
- tenantId: resolvedTenantId,
1599
- isEmail,
1600
- userType: config.userType
1601
- });
1602
- if (!user) {
1603
- logger2.log("[sign-in] 401: user not found", {
1651
+ var signInHandler = (
1652
+ // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: auth flow combines lockout, verification, and session creation
1653
+ async function signInHandler2(c) {
1654
+ const body = c.req.valid("json");
1655
+ const config = c.get("config");
1656
+ const database = c.get("database");
1657
+ const tenantId = c.get("tenantId");
1658
+ const resolvedTenantId = ensureTenantId(config, tenantId);
1659
+ const { identifier, password, rememberMe = true } = body;
1660
+ const isEmail = identifier.includes("@");
1661
+ if (isEmail && !config.email.enabled) {
1662
+ return c.json({ error: "Email authentication is disabled" }, 401);
1663
+ }
1664
+ if (!(isEmail || config.phone.enabled)) {
1665
+ return c.json({ error: "Phone authentication is disabled" }, 401);
1666
+ }
1667
+ const user = await fetchUserForLogin({
1668
+ database,
1604
1669
  identifier,
1605
1670
  tenantId: resolvedTenantId,
1671
+ isEmail,
1606
1672
  userType: config.userType
1607
1673
  });
1608
- return c.json({ error: AUTH_ERRORS.UNAUTHORIZED }, 401);
1609
- }
1610
- if (user.bannedUntil && new Date(user.bannedUntil) > /* @__PURE__ */ new Date()) {
1611
- logger2.log("[sign-in] 401: account banned", {
1612
- userId: user.id,
1613
- bannedUntil: user.bannedUntil
1614
- });
1615
- return c.json(
1616
- {
1617
- error: "Account locked. Try again later.",
1674
+ if (!user) {
1675
+ logger2.log("[sign-in] 401: user not found", {
1676
+ identifier,
1677
+ tenantId: resolvedTenantId,
1678
+ userType: config.userType
1679
+ });
1680
+ return c.json({ error: AUTH_ERRORS.UNAUTHORIZED }, 401);
1681
+ }
1682
+ if (user.bannedUntil && new Date(user.bannedUntil) > /* @__PURE__ */ new Date()) {
1683
+ logger2.log("[sign-in] 401: account banned", {
1684
+ userId: user.id,
1618
1685
  bannedUntil: user.bannedUntil
1619
- },
1620
- 401
1621
- );
1622
- }
1623
- const [account] = await database.select({
1624
- id: accountsInIam.id,
1625
- tenantId: accountsInIam.tenantId,
1626
- userId: accountsInIam.userId,
1627
- provider: accountsInIam.provider,
1628
- providerAccountId: accountsInIam.providerAccountId,
1629
- password: accountsInIam.password
1630
- }).from(accountsInIam).where(
1631
- and8(
1632
- eq8(accountsInIam.tenantId, resolvedTenantId),
1633
- eq8(accountsInIam.userId, user.id),
1634
- eq8(accountsInIam.provider, "credentials")
1635
- )
1636
- ).limit(1);
1637
- if (!account?.password) {
1638
- logger2.log("[sign-in] 401: no credentials account", { userId: user.id });
1639
- return c.json({ error: AUTH_ERRORS.UNAUTHORIZED }, 401);
1640
- }
1641
- const passwordValid = await verifyPassword(password, account.password);
1642
- if (!passwordValid) {
1643
- const newAttempt = (user.loginAttempt || 0) + 1;
1644
- const updateData = {
1645
- loginAttempt: newAttempt
1646
- };
1647
- if (config.security && newAttempt >= config.security.maxLoginAttempts) {
1648
- updateData.bannedUntil = addDuration(config.security.lockoutDuration);
1686
+ });
1687
+ return c.json(
1688
+ {
1689
+ error: "Account locked. Try again later.",
1690
+ bannedUntil: user.bannedUntil
1691
+ },
1692
+ 401
1693
+ );
1694
+ }
1695
+ const [account] = await database.select({
1696
+ id: accountsInIam.id,
1697
+ tenantId: accountsInIam.tenantId,
1698
+ userId: accountsInIam.userId,
1699
+ provider: accountsInIam.provider,
1700
+ providerAccountId: accountsInIam.providerAccountId,
1701
+ password: accountsInIam.password
1702
+ }).from(accountsInIam).where(
1703
+ and9(
1704
+ eq9(accountsInIam.tenantId, resolvedTenantId),
1705
+ eq9(accountsInIam.userId, user.id),
1706
+ eq9(accountsInIam.provider, "credentials")
1707
+ )
1708
+ ).limit(1);
1709
+ if (!(user.hasPassword && account?.password)) {
1710
+ logger2.log("[sign-in] 401: no credentials account", { userId: user.id });
1711
+ return c.json({ error: AUTH_ERRORS.HAS_NO_PASSWORD }, 401);
1712
+ }
1713
+ const passwordValid = await verifyPassword(password, account.password);
1714
+ if (!passwordValid) {
1715
+ const newAttempt = (user.loginAttempt || 0) + 1;
1716
+ const updateData = {
1717
+ loginAttempt: newAttempt
1718
+ };
1719
+ if (config.security && newAttempt >= config.security.maxLoginAttempts) {
1720
+ updateData.bannedUntil = addDuration(config.security.lockoutDuration);
1721
+ }
1722
+ await database.update(usersInIam).set(updateData).where(
1723
+ and9(
1724
+ eq9(usersInIam.id, user.id),
1725
+ eq9(usersInIam.tenantId, resolvedTenantId)
1726
+ )
1727
+ );
1728
+ logger2.log("[sign-in] 401: invalid password", {
1729
+ userId: user.id,
1730
+ loginAttempt: newAttempt
1731
+ });
1732
+ return c.json({ error: AUTH_ERRORS.UNAUTHORIZED }, 401);
1733
+ }
1734
+ const isVerified = isEmail ? user.emailVerified : user.phoneVerified;
1735
+ if (isEmail && config.email.required && !isVerified) {
1736
+ return handleEmailVerification({
1737
+ c,
1738
+ user: { ...user, roles: [] },
1739
+ config,
1740
+ database,
1741
+ tenantId: resolvedTenantId
1742
+ });
1743
+ }
1744
+ if (!isEmail && config.phone.required && !isVerified) {
1745
+ return handlePhoneVerification({
1746
+ c,
1747
+ user: { ...user, roles: [] },
1748
+ config,
1749
+ database,
1750
+ tenantId: resolvedTenantId
1751
+ });
1752
+ }
1753
+ if (config.session.maxPerUser) {
1754
+ await cleanupOldSessions({
1755
+ database,
1756
+ userId: user.id,
1757
+ tenantId: resolvedTenantId,
1758
+ maxSessions: config.session.maxPerUser
1759
+ });
1649
1760
  }
1650
- await database.update(usersInIam).set(updateData).where(
1651
- and8(
1652
- eq8(usersInIam.id, user.id),
1653
- eq8(usersInIam.tenantId, resolvedTenantId)
1761
+ await database.update(usersInIam).set({ lastSignInAt: (/* @__PURE__ */ new Date()).toISOString(), loginAttempt: 0 }).where(
1762
+ and9(
1763
+ eq9(usersInIam.id, user.id),
1764
+ eq9(usersInIam.tenantId, resolvedTenantId)
1654
1765
  )
1655
1766
  );
1656
- logger2.log("[sign-in] 401: invalid password", {
1767
+ const sessionToken = generateToken();
1768
+ const hashedToken = await hashToken(sessionToken, config.secret);
1769
+ const sessionDuration = getSessionDuration({
1770
+ sessionConfig: config.session,
1771
+ rememberMe
1772
+ });
1773
+ const expiresAt = addDuration(sessionDuration);
1774
+ const [session] = await database.insert(sessionsInIam).values({
1775
+ tenantId: resolvedTenantId,
1657
1776
  userId: user.id,
1658
- loginAttempt: newAttempt
1777
+ token: hashedToken,
1778
+ expiresAt,
1779
+ userAgent: c.req.header("user-agent") || null,
1780
+ ip: c.req.header("cf-connecting-ip") || c.req.header("x-forwarded-for") || null,
1781
+ meta: { action: "sign-in", rememberMe }
1782
+ }).returning({
1783
+ id: sessionsInIam.id,
1784
+ tenantId: sessionsInIam.tenantId,
1785
+ userId: sessionsInIam.userId,
1786
+ expiresAt: sessionsInIam.expiresAt,
1787
+ createdAt: sessionsInIam.createdAt,
1788
+ meta: sessionsInIam.meta
1659
1789
  });
1660
- return c.json({ error: AUTH_ERRORS.UNAUTHORIZED }, 401);
1661
- }
1662
- const isVerified = isEmail ? user.emailVerified : user.phoneVerified;
1663
- if (isEmail && config.email.required && !isVerified) {
1664
- return handleEmailVerification({
1665
- c,
1666
- user: { ...user, roles: [] },
1667
- config,
1668
- database,
1669
- tenantId: resolvedTenantId
1790
+ setSessionCookie(c, sessionToken, config, {
1791
+ expires: new Date(expiresAt)
1670
1792
  });
1671
- }
1672
- if (!isEmail && config.phone.required && !isVerified) {
1673
- return handlePhoneVerification({
1674
- c,
1675
- user: { ...user, roles: [] },
1676
- config,
1793
+ const fullUser = await fetchUserByIdWithRoles({
1677
1794
  database,
1795
+ userId: user.id,
1678
1796
  tenantId: resolvedTenantId
1679
1797
  });
1798
+ return c.json(
1799
+ {
1800
+ user: normalizeAuthUser(fullUser ?? { ...user, roles: [] }),
1801
+ session: normalizeAuthSession({
1802
+ id: session.id,
1803
+ expiresAt: session.expiresAt
1804
+ }),
1805
+ sessionExpiresAt: session.expiresAt
1806
+ },
1807
+ 200
1808
+ );
1680
1809
  }
1681
- if (config.session.maxPerUser) {
1682
- await cleanupOldSessions({
1683
- database,
1684
- userId: user.id,
1685
- tenantId: resolvedTenantId,
1686
- maxSessions: config.session.maxPerUser
1687
- });
1688
- }
1689
- await database.update(usersInIam).set({ lastSignInAt: (/* @__PURE__ */ new Date()).toISOString(), loginAttempt: 0 }).where(
1690
- and8(
1691
- eq8(usersInIam.id, user.id),
1692
- eq8(usersInIam.tenantId, resolvedTenantId)
1693
- )
1694
- );
1695
- const sessionToken = generateToken();
1696
- const hashedToken = await hashToken(sessionToken, config.secret);
1697
- const sessionDuration = getSessionDuration({
1698
- sessionConfig: config.session,
1699
- rememberMe
1700
- });
1701
- const expiresAt = addDuration(sessionDuration);
1702
- const [session] = await database.insert(sessionsInIam).values({
1703
- tenantId: resolvedTenantId,
1704
- userId: user.id,
1705
- token: hashedToken,
1706
- expiresAt,
1707
- userAgent: c.req.header("user-agent") || null,
1708
- ip: c.req.header("cf-connecting-ip") || c.req.header("x-forwarded-for") || null,
1709
- meta: { action: "sign-in", rememberMe }
1710
- }).returning({
1711
- id: sessionsInIam.id,
1712
- tenantId: sessionsInIam.tenantId,
1713
- userId: sessionsInIam.userId,
1714
- expiresAt: sessionsInIam.expiresAt,
1715
- createdAt: sessionsInIam.createdAt,
1716
- meta: sessionsInIam.meta
1717
- });
1718
- setSessionCookie(c, sessionToken, config, {
1719
- expires: new Date(expiresAt)
1720
- });
1721
- const fullUser = await fetchUserByIdWithRoles({
1722
- database,
1723
- userId: user.id,
1724
- tenantId: resolvedTenantId
1725
- });
1726
- return c.json(
1727
- {
1728
- user: normalizeUser(fullUser ?? { ...user, roles: [] }),
1729
- session: {
1730
- id: session.id,
1731
- expiresAt: session.expiresAt,
1732
- createdAt: session.createdAt,
1733
- meta: session.meta
1734
- },
1735
- sessionExpiresAt: session.expiresAt
1736
- },
1737
- 200
1738
- );
1739
- };
1740
-
1741
- // src/routes/auth/handler/sign-out.ts
1742
- import { and as and9, eq as eq9, gt as gt4 } from "drizzle-orm";
1743
- import { getCookie } from "hono/cookie";
1744
- var signOutHandler = async (c) => {
1745
- const config = c.get("config");
1746
- const database = c.get("database");
1747
- const tenantId = c.get("tenantId");
1748
- ensureTenantId(config, tenantId);
1749
- const sessionToken = getCookie(c, getSessionCookieName(config));
1750
- if (!sessionToken) {
1751
- return c.json({ message: "Signed out" }, 200);
1810
+ );
1811
+
1812
+ // src/routes/auth/handler/sign-out.ts
1813
+ import { and as and10, eq as eq10, gt as gt4 } from "drizzle-orm";
1814
+ import { getCookie } from "hono/cookie";
1815
+ var signOutHandler = async (c) => {
1816
+ const config = c.get("config");
1817
+ const database = c.get("database");
1818
+ const tenantId = c.get("tenantId");
1819
+ ensureTenantId(config, tenantId);
1820
+ const sessionToken = getCookie(c, getSessionCookieName(config));
1821
+ if (!sessionToken) {
1822
+ return c.json({ message: "Signed out" }, 200);
1752
1823
  }
1753
1824
  const hashedToken = await hashToken(sessionToken, config.secret);
1754
1825
  const [session] = await database.select({
@@ -1761,16 +1832,16 @@ var signOutHandler = async (c) => {
1761
1832
  userAgent: sessionsInIam.userAgent,
1762
1833
  ip: sessionsInIam.ip
1763
1834
  }).from(sessionsInIam).where(
1764
- and9(
1765
- eq9(sessionsInIam.token, hashedToken),
1835
+ and10(
1836
+ eq10(sessionsInIam.token, hashedToken),
1766
1837
  gt4(sessionsInIam.expiresAt, (/* @__PURE__ */ new Date()).toISOString())
1767
1838
  )
1768
1839
  ).limit(1);
1769
1840
  if (session) {
1770
1841
  await database.delete(sessionsInIam).where(
1771
- and9(
1772
- eq9(sessionsInIam.id, session.id),
1773
- eq9(sessionsInIam.tenantId, session.tenantId)
1842
+ and10(
1843
+ eq10(sessionsInIam.id, session.id),
1844
+ eq10(sessionsInIam.tenantId, session.tenantId)
1774
1845
  )
1775
1846
  );
1776
1847
  }
@@ -1922,7 +1993,7 @@ var signUpHandler = async (c) => {
1922
1993
  if (result.type === "pending") {
1923
1994
  return c.json(
1924
1995
  {
1925
- user: normalizeUser(result.user),
1996
+ user: normalizeAuthUser(result.user),
1926
1997
  session: null,
1927
1998
  verificationId: result.verificationId,
1928
1999
  requiresVerification: true
@@ -1941,7 +2012,7 @@ var signUpHandler = async (c) => {
1941
2012
  });
1942
2013
  return c.json(
1943
2014
  {
1944
- user: normalizeUser(result.user),
2015
+ user: normalizeAuthUser(result.user),
1945
2016
  session: null,
1946
2017
  verificationId: result.verificationId,
1947
2018
  requiresVerification: true
@@ -1954,8 +2025,11 @@ var signUpHandler = async (c) => {
1954
2025
  });
1955
2026
  return c.json(
1956
2027
  {
1957
- user: normalizeUser(result.user),
1958
- session: { id: result.sessionId, expiresAt: result.expiresAt },
2028
+ user: normalizeAuthUser(result.user),
2029
+ session: normalizeAuthSession({
2030
+ id: result.sessionId,
2031
+ expiresAt: result.expiresAt
2032
+ }),
1959
2033
  sessionExpiresAt: result.expiresAt
1960
2034
  },
1961
2035
  201
@@ -2158,26 +2232,26 @@ var createDomainHandler = async (c) => {
2158
2232
  };
2159
2233
 
2160
2234
  // src/routes/domains/handler/delete-domain.ts
2161
- import { and as and10, eq as eq10 } from "drizzle-orm";
2235
+ import { and as and11, eq as eq11 } from "drizzle-orm";
2162
2236
  var deleteDomainHandler = async (c) => {
2163
2237
  const { id } = c.req.valid("param");
2164
2238
  const database = c.get("database");
2165
2239
  const tenantId = c.get("tenantId");
2166
- const [existing] = await database.select().from(domainsInIam).where(and10(eq10(domainsInIam.id, id), eq10(domainsInIam.tenantId, tenantId))).limit(1);
2240
+ const [existing] = await database.select().from(domainsInIam).where(and11(eq11(domainsInIam.id, id), eq11(domainsInIam.tenantId, tenantId))).limit(1);
2167
2241
  if (!existing) {
2168
2242
  return c.json({ error: "Domain not found" }, 404);
2169
2243
  }
2170
- await database.delete(domainsInIam).where(and10(eq10(domainsInIam.id, id), eq10(domainsInIam.tenantId, tenantId)));
2244
+ await database.delete(domainsInIam).where(and11(eq11(domainsInIam.id, id), eq11(domainsInIam.tenantId, tenantId)));
2171
2245
  return c.json({ message: "Domain deleted" }, 200);
2172
2246
  };
2173
2247
 
2174
2248
  // src/routes/domains/handler/get-domain.ts
2175
- import { and as and11, eq as eq11 } from "drizzle-orm";
2249
+ import { and as and12, eq as eq12 } from "drizzle-orm";
2176
2250
  var getDomainHandler = async (c) => {
2177
2251
  const { id } = c.req.valid("param");
2178
2252
  const database = c.get("database");
2179
2253
  const tenantId = c.get("tenantId");
2180
- const [domain] = await database.select().from(domainsInIam).where(and11(eq11(domainsInIam.id, id), eq11(domainsInIam.tenantId, tenantId))).limit(1);
2254
+ const [domain] = await database.select().from(domainsInIam).where(and12(eq12(domainsInIam.id, id), eq12(domainsInIam.tenantId, tenantId))).limit(1);
2181
2255
  if (!domain) {
2182
2256
  return c.json({ error: "Domain not found" }, 404);
2183
2257
  }
@@ -2185,7 +2259,7 @@ var getDomainHandler = async (c) => {
2185
2259
  };
2186
2260
 
2187
2261
  // src/routes/domains/handler/list-domains.ts
2188
- import { and as and12, eq as eq12, sql as sql7 } from "drizzle-orm";
2262
+ import { and as and13, eq as eq13, sql as sql7 } from "drizzle-orm";
2189
2263
  var listDomainsHandler = async (c) => {
2190
2264
  const query = c.req.valid("query");
2191
2265
  const database = c.get("database");
@@ -2193,26 +2267,26 @@ var listDomainsHandler = async (c) => {
2193
2267
  const page = query.page || 1;
2194
2268
  const limit = query.limit || 20;
2195
2269
  const offset = (page - 1) * limit;
2196
- const conditions = [eq12(domainsInIam.tenantId, tenantId)];
2270
+ const conditions = [eq13(domainsInIam.tenantId, tenantId)];
2197
2271
  if (query.status) {
2198
- conditions.push(eq12(domainsInIam.status, query.status));
2272
+ conditions.push(eq13(domainsInIam.status, query.status));
2199
2273
  }
2200
2274
  const [domains, totalResult] = await Promise.all([
2201
- database.select().from(domainsInIam).where(and12(...conditions)).limit(limit).offset(offset),
2202
- database.select({ count: sql7`count(*)` }).from(domainsInIam).where(and12(...conditions))
2275
+ database.select().from(domainsInIam).where(and13(...conditions)).limit(limit).offset(offset),
2276
+ database.select({ count: sql7`count(*)` }).from(domainsInIam).where(and13(...conditions))
2203
2277
  ]);
2204
2278
  const total = Number(totalResult[0]?.count || 0);
2205
2279
  return c.json({ domains, total, page, limit });
2206
2280
  };
2207
2281
 
2208
2282
  // src/routes/domains/handler/update-domain.ts
2209
- import { and as and13, eq as eq13, sql as sql8 } from "drizzle-orm";
2283
+ import { and as and14, eq as eq14, sql as sql8 } from "drizzle-orm";
2210
2284
  var updateDomainHandler = async (c) => {
2211
2285
  const { id } = c.req.valid("param");
2212
2286
  const body = c.req.valid("json");
2213
2287
  const database = c.get("database");
2214
2288
  const tenantId = c.get("tenantId");
2215
- const [existing] = await database.select().from(domainsInIam).where(and13(eq13(domainsInIam.id, id), eq13(domainsInIam.tenantId, tenantId))).limit(1);
2289
+ const [existing] = await database.select().from(domainsInIam).where(and14(eq14(domainsInIam.id, id), eq14(domainsInIam.tenantId, tenantId))).limit(1);
2216
2290
  if (!existing) {
2217
2291
  return c.json({ error: "Domain not found" }, 404);
2218
2292
  }
@@ -2232,7 +2306,7 @@ var updateDomainHandler = async (c) => {
2232
2306
  const [updated] = await database.update(domainsInIam).set({
2233
2307
  ...updateData,
2234
2308
  updatedAt: sql8`CURRENT_TIMESTAMP`
2235
- }).where(and13(eq13(domainsInIam.id, id), eq13(domainsInIam.tenantId, tenantId))).returning();
2309
+ }).where(and14(eq14(domainsInIam.id, id), eq14(domainsInIam.tenantId, tenantId))).returning();
2236
2310
  if (!updated) {
2237
2311
  return c.json({ error: "Domain not found" }, 404);
2238
2312
  }
@@ -2240,19 +2314,19 @@ var updateDomainHandler = async (c) => {
2240
2314
  };
2241
2315
 
2242
2316
  // src/routes/domains/handler/verify-domain.ts
2243
- import { and as and14, eq as eq14, sql as sql9 } from "drizzle-orm";
2317
+ import { and as and15, eq as eq15, sql as sql9 } from "drizzle-orm";
2244
2318
  var verifyDomainHandler = async (c) => {
2245
2319
  const { id } = c.req.valid("param");
2246
2320
  const database = c.get("database");
2247
2321
  const tenantId = c.get("tenantId");
2248
- const [existing] = await database.select().from(domainsInIam).where(and14(eq14(domainsInIam.id, id), eq14(domainsInIam.tenantId, tenantId))).limit(1);
2322
+ const [existing] = await database.select().from(domainsInIam).where(and15(eq15(domainsInIam.id, id), eq15(domainsInIam.tenantId, tenantId))).limit(1);
2249
2323
  if (!existing) {
2250
2324
  return c.json({ error: "Domain not found" }, 404);
2251
2325
  }
2252
2326
  const [updated] = await database.update(domainsInIam).set({
2253
2327
  status: "active",
2254
2328
  updatedAt: sql9`CURRENT_TIMESTAMP`
2255
- }).where(and14(eq14(domainsInIam.id, id), eq14(domainsInIam.tenantId, tenantId))).returning();
2329
+ }).where(and15(eq15(domainsInIam.id, id), eq15(domainsInIam.tenantId, tenantId))).returning();
2256
2330
  if (!updated) {
2257
2331
  return c.json({ error: "Domain not found" }, 404);
2258
2332
  }
@@ -2426,7 +2500,7 @@ var domains_route_default = domainRoutes;
2426
2500
  import { createRoute as createRoute3, OpenAPIHono as OpenAPIHono3 } from "@hono/zod-openapi";
2427
2501
 
2428
2502
  // src/routes/email/handler/verification-confirm.ts
2429
- import { and as and15, eq as eq15 } from "drizzle-orm";
2503
+ import { and as and16, eq as eq16 } from "drizzle-orm";
2430
2504
  var emailVerificationConfirmHandler = async (c) => {
2431
2505
  const body = c.req.valid("json");
2432
2506
  const config = c.get("config");
@@ -2436,9 +2510,9 @@ var emailVerificationConfirmHandler = async (c) => {
2436
2510
  const { verificationId, code } = body;
2437
2511
  const result = await withTransaction(database, async (tx) => {
2438
2512
  const [verification] = await tx.select().from(verificationsInIam).where(
2439
- and15(
2440
- eq15(verificationsInIam.id, verificationId),
2441
- eq15(verificationsInIam.tenantId, resolvedTenantId)
2513
+ and16(
2514
+ eq16(verificationsInIam.id, verificationId),
2515
+ eq16(verificationsInIam.tenantId, resolvedTenantId)
2442
2516
  )
2443
2517
  ).limit(1);
2444
2518
  if (!verification || verification.type !== "email-verification") {
@@ -2454,7 +2528,7 @@ var emailVerificationConfirmHandler = async (c) => {
2454
2528
  };
2455
2529
  }
2456
2530
  if ((verification.attempt || 0) >= config.email.maxAttempts) {
2457
- await tx.delete(verificationsInIam).where(eq15(verificationsInIam.id, verificationId));
2531
+ await tx.delete(verificationsInIam).where(eq16(verificationsInIam.id, verificationId));
2458
2532
  return {
2459
2533
  status: "error",
2460
2534
  error: AUTH_ERRORS.TOO_MANY_ATTEMPTS
@@ -2462,7 +2536,7 @@ var emailVerificationConfirmHandler = async (c) => {
2462
2536
  }
2463
2537
  const hashedCode = await hashToken(code, config.secret);
2464
2538
  if (verification.code !== hashedCode) {
2465
- await tx.update(verificationsInIam).set({ attempt: (verification.attempt || 0) + 1 }).where(eq15(verificationsInIam.id, verificationId));
2539
+ await tx.update(verificationsInIam).set({ attempt: (verification.attempt || 0) + 1 }).where(eq16(verificationsInIam.id, verificationId));
2466
2540
  return {
2467
2541
  status: "error",
2468
2542
  error: AUTH_ERRORS.VERIFICATION_MISMATCH
@@ -2472,12 +2546,12 @@ var emailVerificationConfirmHandler = async (c) => {
2472
2546
  emailVerified: true,
2473
2547
  lastSignInAt: (/* @__PURE__ */ new Date()).toISOString()
2474
2548
  }).where(
2475
- and15(
2476
- eq15(usersInIam.id, verification.userId),
2477
- eq15(usersInIam.tenantId, resolvedTenantId)
2549
+ and16(
2550
+ eq16(usersInIam.id, verification.userId),
2551
+ eq16(usersInIam.tenantId, resolvedTenantId)
2478
2552
  )
2479
2553
  );
2480
- await tx.delete(verificationsInIam).where(eq15(verificationsInIam.id, verificationId));
2554
+ await tx.delete(verificationsInIam).where(eq16(verificationsInIam.id, verificationId));
2481
2555
  const user = await fetchUserWithRoles({
2482
2556
  database: tx,
2483
2557
  userId: verification.userId,
@@ -2512,14 +2586,8 @@ var emailVerificationConfirmHandler = async (c) => {
2512
2586
  });
2513
2587
  return c.json(
2514
2588
  {
2515
- user: normalizeUser(result.user),
2516
- session: {
2517
- id: result.session.id,
2518
- expiresAt: result.session.expiresAt,
2519
- createdAt: result.session.createdAt,
2520
- userAgent: result.session.userAgent,
2521
- ip: result.session.ip
2522
- },
2589
+ user: normalizeAuthUser(result.user),
2590
+ session: normalizeAuthSession(result.session),
2523
2591
  sessionExpiresAt: result.session.expiresAt
2524
2592
  },
2525
2593
  200
@@ -2527,7 +2595,7 @@ var emailVerificationConfirmHandler = async (c) => {
2527
2595
  };
2528
2596
 
2529
2597
  // src/routes/email/handler/verification-request.ts
2530
- import { and as and16, eq as eq16, sql as sql10 } from "drizzle-orm";
2598
+ import { and as and17, eq as eq17, sql as sql10 } from "drizzle-orm";
2531
2599
  var emailVerificationRequestHandler = async (c) => {
2532
2600
  const body = c.req.valid("json");
2533
2601
  const config = c.get("config");
@@ -2548,8 +2616,8 @@ var emailVerificationRequestHandler = async (c) => {
2548
2616
  let userId = user?.id;
2549
2617
  if (!userId) {
2550
2618
  const [result] = await database.select({ id: usersInIam.id }).from(usersInIam).where(
2551
- and16(
2552
- eq16(usersInIam.tenantId, resolvedTenantId),
2619
+ and17(
2620
+ eq17(usersInIam.tenantId, resolvedTenantId),
2553
2621
  sql10`lower(${usersInIam.email}) = lower(${email})`
2554
2622
  )
2555
2623
  ).limit(1);
@@ -2575,10 +2643,10 @@ var emailVerificationRequestHandler = async (c) => {
2575
2643
  );
2576
2644
  }
2577
2645
  await database.delete(verificationsInIam).where(
2578
- and16(
2579
- eq16(verificationsInIam.tenantId, resolvedTenantId),
2580
- eq16(verificationsInIam.userId, userId),
2581
- eq16(verificationsInIam.type, "email-verification")
2646
+ and17(
2647
+ eq17(verificationsInIam.tenantId, resolvedTenantId),
2648
+ eq17(verificationsInIam.userId, userId),
2649
+ eq17(verificationsInIam.type, "email-verification")
2582
2650
  )
2583
2651
  );
2584
2652
  const code = generateOtpCode(config.email.otpLength);
@@ -2610,11 +2678,11 @@ var emailVerificationRequestHandler = async (c) => {
2610
2678
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
2611
2679
  reason: "replaced"
2612
2680
  }).where(
2613
- and16(
2614
- eq16(accountChangesInIam.tenantId, resolvedTenantId),
2615
- eq16(accountChangesInIam.userId, user.id),
2616
- eq16(accountChangesInIam.changeType, "email"),
2617
- eq16(accountChangesInIam.status, "pending")
2681
+ and17(
2682
+ eq17(accountChangesInIam.tenantId, resolvedTenantId),
2683
+ eq17(accountChangesInIam.userId, user.id),
2684
+ eq17(accountChangesInIam.changeType, "email"),
2685
+ eq17(accountChangesInIam.status, "pending")
2618
2686
  )
2619
2687
  );
2620
2688
  await database.insert(accountChangesInIam).values({
@@ -2729,7 +2797,7 @@ var email_route_default = emailRoutes;
2729
2797
  import { createRoute as createRoute4, OpenAPIHono as OpenAPIHono4 } from "@hono/zod-openapi";
2730
2798
 
2731
2799
  // src/routes/password/handler/change.ts
2732
- import { and as and17, eq as eq17, gt as gt5, ne } from "drizzle-orm";
2800
+ import { and as and18, eq as eq18, gt as gt5, ne } from "drizzle-orm";
2733
2801
  import { getCookie as getCookie2 } from "hono/cookie";
2734
2802
  var changePasswordHandler = async (c) => {
2735
2803
  const body = c.req.valid("json");
@@ -2747,10 +2815,10 @@ var changePasswordHandler = async (c) => {
2747
2815
  const resolvedTenantId = ensureTenantId(config, tenantId);
2748
2816
  const { currentPassword, newPassword } = body;
2749
2817
  const [account] = await database.select().from(accountsInIam).where(
2750
- and17(
2751
- eq17(accountsInIam.tenantId, resolvedTenantId),
2752
- eq17(accountsInIam.userId, userId),
2753
- eq17(accountsInIam.provider, "credentials")
2818
+ and18(
2819
+ eq18(accountsInIam.tenantId, resolvedTenantId),
2820
+ eq18(accountsInIam.userId, userId),
2821
+ eq18(accountsInIam.provider, "credentials")
2754
2822
  )
2755
2823
  ).limit(1);
2756
2824
  if (!account?.password) {
@@ -2762,19 +2830,19 @@ var changePasswordHandler = async (c) => {
2762
2830
  }
2763
2831
  const passwordHash = await hashPassword(newPassword);
2764
2832
  await database.update(accountsInIam).set({ password: passwordHash }).where(
2765
- and17(
2766
- eq17(accountsInIam.tenantId, resolvedTenantId),
2767
- eq17(accountsInIam.userId, userId),
2768
- eq17(accountsInIam.provider, "credentials")
2833
+ and18(
2834
+ eq18(accountsInIam.tenantId, resolvedTenantId),
2835
+ eq18(accountsInIam.userId, userId),
2836
+ eq18(accountsInIam.provider, "credentials")
2769
2837
  )
2770
2838
  );
2771
2839
  const currentSessionToken = getCookie2(c, getSessionCookieName(config));
2772
2840
  if (currentSessionToken) {
2773
2841
  const hashedToken = await hashToken(currentSessionToken, config.secret);
2774
2842
  await database.delete(sessionsInIam).where(
2775
- and17(
2776
- eq17(sessionsInIam.tenantId, resolvedTenantId),
2777
- eq17(sessionsInIam.userId, userId),
2843
+ and18(
2844
+ eq18(sessionsInIam.tenantId, resolvedTenantId),
2845
+ eq18(sessionsInIam.userId, userId),
2778
2846
  gt5(sessionsInIam.expiresAt, (/* @__PURE__ */ new Date()).toISOString()),
2779
2847
  ne(sessionsInIam.token, hashedToken)
2780
2848
  )
@@ -2784,7 +2852,7 @@ var changePasswordHandler = async (c) => {
2784
2852
  };
2785
2853
 
2786
2854
  // src/routes/password/handler/forgot.ts
2787
- import { and as and18, eq as eq18, sql as sql11 } from "drizzle-orm";
2855
+ import { and as and19, eq as eq19, sql as sql11 } from "drizzle-orm";
2788
2856
  var forgotPasswordHandler = async (c) => {
2789
2857
  const body = c.req.valid("json");
2790
2858
  const config = c.get("config");
@@ -2818,19 +2886,19 @@ var forgotPasswordHandler = async (c) => {
2818
2886
  )`
2819
2887
  }).from(usersInIam).leftJoin(
2820
2888
  userRolesInIam,
2821
- and18(
2822
- eq18(userRolesInIam.userId, usersInIam.id),
2823
- eq18(userRolesInIam.tenantId, resolvedTenantId)
2889
+ and19(
2890
+ eq19(userRolesInIam.userId, usersInIam.id),
2891
+ eq19(userRolesInIam.tenantId, resolvedTenantId)
2824
2892
  )
2825
2893
  ).leftJoin(
2826
2894
  rolesInIam,
2827
- and18(
2828
- eq18(userRolesInIam.roleId, rolesInIam.id),
2829
- eq18(rolesInIam.tenantId, resolvedTenantId)
2895
+ and19(
2896
+ eq19(userRolesInIam.roleId, rolesInIam.id),
2897
+ eq19(rolesInIam.tenantId, resolvedTenantId)
2830
2898
  )
2831
2899
  ).where(
2832
- and18(
2833
- eq18(usersInIam.tenantId, resolvedTenantId),
2900
+ and19(
2901
+ eq19(usersInIam.tenantId, resolvedTenantId),
2834
2902
  sql11`lower(${usersInIam.email}) = lower(${identifier})`
2835
2903
  )
2836
2904
  ).groupBy(usersInIam.id).limit(1);
@@ -2853,20 +2921,20 @@ var forgotPasswordHandler = async (c) => {
2853
2921
  )`
2854
2922
  }).from(usersInIam).leftJoin(
2855
2923
  userRolesInIam,
2856
- and18(
2857
- eq18(userRolesInIam.userId, usersInIam.id),
2858
- eq18(userRolesInIam.tenantId, resolvedTenantId)
2924
+ and19(
2925
+ eq19(userRolesInIam.userId, usersInIam.id),
2926
+ eq19(userRolesInIam.tenantId, resolvedTenantId)
2859
2927
  )
2860
2928
  ).leftJoin(
2861
2929
  rolesInIam,
2862
- and18(
2863
- eq18(userRolesInIam.roleId, rolesInIam.id),
2864
- eq18(rolesInIam.tenantId, resolvedTenantId)
2930
+ and19(
2931
+ eq19(userRolesInIam.roleId, rolesInIam.id),
2932
+ eq19(rolesInIam.tenantId, resolvedTenantId)
2865
2933
  )
2866
2934
  ).where(
2867
- and18(
2868
- eq18(usersInIam.tenantId, resolvedTenantId),
2869
- eq18(usersInIam.phone, identifier)
2935
+ and19(
2936
+ eq19(usersInIam.tenantId, resolvedTenantId),
2937
+ eq19(usersInIam.phone, identifier)
2870
2938
  )
2871
2939
  ).groupBy(usersInIam.id).limit(1);
2872
2940
  user = result || null;
@@ -2880,10 +2948,10 @@ var forgotPasswordHandler = async (c) => {
2880
2948
  return c.json({ message: "If account exists, reset code sent" }, 200);
2881
2949
  }
2882
2950
  await database.delete(verificationsInIam).where(
2883
- and18(
2884
- eq18(verificationsInIam.tenantId, resolvedTenantId),
2885
- eq18(verificationsInIam.userId, user.id),
2886
- eq18(verificationsInIam.type, "password-reset")
2951
+ and19(
2952
+ eq19(verificationsInIam.tenantId, resolvedTenantId),
2953
+ eq19(verificationsInIam.userId, user.id),
2954
+ eq19(verificationsInIam.type, "password-reset")
2887
2955
  )
2888
2956
  );
2889
2957
  const code = generateOtpCode(config.email.otpLength);
@@ -2922,10 +2990,10 @@ var forgotPasswordHandler = async (c) => {
2922
2990
  return c.json({ message: "If account exists, reset code sent" }, 200);
2923
2991
  }
2924
2992
  await database.delete(verificationsInIam).where(
2925
- and18(
2926
- eq18(verificationsInIam.tenantId, resolvedTenantId),
2927
- eq18(verificationsInIam.userId, user.id),
2928
- eq18(verificationsInIam.type, "password-reset-otp")
2993
+ and19(
2994
+ eq19(verificationsInIam.tenantId, resolvedTenantId),
2995
+ eq19(verificationsInIam.userId, user.id),
2996
+ eq19(verificationsInIam.type, "password-reset-otp")
2929
2997
  )
2930
2998
  );
2931
2999
  const code = generateOtpCode(config.phone.otpLength);
@@ -2972,7 +3040,7 @@ var forgotPasswordHandler = async (c) => {
2972
3040
  };
2973
3041
 
2974
3042
  // src/routes/password/handler/reset.ts
2975
- import { and as and19, eq as eq19, gt as gt6 } from "drizzle-orm";
3043
+ import { and as and20, eq as eq20, gt as gt6 } from "drizzle-orm";
2976
3044
 
2977
3045
  // src/routes/password/helper/session.ts
2978
3046
  var createPasswordResetSession = async ({
@@ -2999,14 +3067,8 @@ var createPasswordResetSession = async ({
2999
3067
  });
3000
3068
  return c.json(
3001
3069
  {
3002
- user: normalizeUser(user),
3003
- session: {
3004
- id: session.id,
3005
- expiresAt: session.expiresAt,
3006
- createdAt: session.createdAt,
3007
- userAgent: session.userAgent,
3008
- ip: session.ip
3009
- },
3070
+ user: normalizeAuthUser(user),
3071
+ session: normalizeAuthSession(session),
3010
3072
  sessionExpiresAt: session.expiresAt
3011
3073
  },
3012
3074
  200
@@ -3022,9 +3084,9 @@ var resetPasswordHandler = async (c) => {
3022
3084
  const resolvedTenantId = ensureTenantId(config, tenantId);
3023
3085
  const { verificationId, code, password } = body;
3024
3086
  const [verification] = await database.select().from(verificationsInIam).where(
3025
- and19(
3026
- eq19(verificationsInIam.id, verificationId),
3027
- eq19(verificationsInIam.tenantId, resolvedTenantId)
3087
+ and20(
3088
+ eq20(verificationsInIam.id, verificationId),
3089
+ eq20(verificationsInIam.tenantId, resolvedTenantId)
3028
3090
  )
3029
3091
  ).limit(1);
3030
3092
  if (!verification) {
@@ -3038,34 +3100,34 @@ var resetPasswordHandler = async (c) => {
3038
3100
  }
3039
3101
  const maxAttempts = verification.type === "password-reset" ? config.email.maxAttempts : config.phone.maxAttempts;
3040
3102
  if ((verification.attempt || 0) >= maxAttempts) {
3041
- await database.delete(verificationsInIam).where(eq19(verificationsInIam.id, verificationId));
3103
+ await database.delete(verificationsInIam).where(eq20(verificationsInIam.id, verificationId));
3042
3104
  return c.json({ error: AUTH_ERRORS.TOO_MANY_ATTEMPTS }, 400);
3043
3105
  }
3044
3106
  const hashedCode = await hashToken(code, config.secret);
3045
3107
  if (verification.code !== hashedCode) {
3046
- await database.update(verificationsInIam).set({ attempt: (verification.attempt || 0) + 1 }).where(eq19(verificationsInIam.id, verificationId));
3108
+ await database.update(verificationsInIam).set({ attempt: (verification.attempt || 0) + 1 }).where(eq20(verificationsInIam.id, verificationId));
3047
3109
  return c.json({ error: AUTH_ERRORS.VERIFICATION_MISMATCH }, 400);
3048
3110
  }
3049
3111
  const passwordHash = await hashPassword(password);
3050
3112
  await database.update(accountsInIam).set({ password: passwordHash }).where(
3051
- and19(
3052
- eq19(accountsInIam.tenantId, resolvedTenantId),
3053
- eq19(accountsInIam.userId, verification.userId),
3054
- eq19(accountsInIam.provider, "credentials")
3113
+ and20(
3114
+ eq20(accountsInIam.tenantId, resolvedTenantId),
3115
+ eq20(accountsInIam.userId, verification.userId),
3116
+ eq20(accountsInIam.provider, "credentials")
3055
3117
  )
3056
3118
  );
3057
3119
  await database.delete(sessionsInIam).where(
3058
- and19(
3059
- eq19(sessionsInIam.tenantId, resolvedTenantId),
3060
- eq19(sessionsInIam.userId, verification.userId),
3120
+ and20(
3121
+ eq20(sessionsInIam.tenantId, resolvedTenantId),
3122
+ eq20(sessionsInIam.userId, verification.userId),
3061
3123
  gt6(sessionsInIam.expiresAt, (/* @__PURE__ */ new Date()).toISOString())
3062
3124
  )
3063
3125
  );
3064
- await database.delete(verificationsInIam).where(eq19(verificationsInIam.id, verificationId));
3126
+ await database.delete(verificationsInIam).where(eq20(verificationsInIam.id, verificationId));
3065
3127
  const [user] = await database.select().from(usersInIam).where(
3066
- and19(
3067
- eq19(usersInIam.id, verification.userId),
3068
- eq19(usersInIam.tenantId, resolvedTenantId)
3128
+ and20(
3129
+ eq20(usersInIam.id, verification.userId),
3130
+ eq20(usersInIam.tenantId, resolvedTenantId)
3069
3131
  )
3070
3132
  ).limit(1);
3071
3133
  if (!user) {
@@ -3080,8 +3142,97 @@ var resetPasswordHandler = async (c) => {
3080
3142
  });
3081
3143
  };
3082
3144
 
3145
+ // src/routes/password/handler/set.ts
3146
+ import { and as and21, eq as eq21 } from "drizzle-orm";
3147
+ var setPasswordHandler = async (c) => {
3148
+ const body = c.req.valid("json");
3149
+ const config = c.get("config");
3150
+ const database = c.get("database");
3151
+ const tenantId = c.get("tenantId");
3152
+ const resolvedTenantId = ensureTenantId(config, tenantId);
3153
+ const { identifier, password, rememberMe = true } = body;
3154
+ const isEmail = identifier.includes("@");
3155
+ const user = await fetchUserForLogin({
3156
+ database,
3157
+ identifier,
3158
+ tenantId: resolvedTenantId,
3159
+ isEmail,
3160
+ userType: config.userType
3161
+ });
3162
+ if (!user) {
3163
+ return c.json({ error: AUTH_ERRORS.USER_NOT_FOUND }, 400);
3164
+ }
3165
+ const isVerified = isEmail ? user.emailVerified : user.phoneVerified;
3166
+ if (!isVerified) {
3167
+ return c.json({ error: AUTH_ERRORS.REQUIRES_VERIFICATION }, 409);
3168
+ }
3169
+ if (user.hasPassword) {
3170
+ return c.json({ error: AUTH_ERRORS.PASSWORD_ALREADY_SET }, 409);
3171
+ }
3172
+ const userAgent = c.req.header("user-agent") || null;
3173
+ const ip = c.req.header("cf-connecting-ip") || c.req.header("x-forwarded-for") || null;
3174
+ const result = await withTransaction(database, async (tx) => {
3175
+ const passwordHash = await hashPassword(password);
3176
+ const [account] = await tx.select({
3177
+ id: accountsInIam.id
3178
+ }).from(accountsInIam).where(
3179
+ and21(
3180
+ eq21(accountsInIam.tenantId, resolvedTenantId),
3181
+ eq21(accountsInIam.userId, user.id),
3182
+ eq21(accountsInIam.provider, "credentials")
3183
+ )
3184
+ ).limit(1);
3185
+ if (account) {
3186
+ await tx.update(accountsInIam).set({
3187
+ password: passwordHash,
3188
+ providerAccountId: identifier
3189
+ }).where(eq21(accountsInIam.id, account.id));
3190
+ } else {
3191
+ await tx.insert(accountsInIam).values({
3192
+ tenantId: resolvedTenantId,
3193
+ userId: user.id,
3194
+ provider: "credentials",
3195
+ providerAccountId: identifier,
3196
+ password: passwordHash
3197
+ });
3198
+ }
3199
+ await tx.update(usersInIam).set({ lastSignInAt: (/* @__PURE__ */ new Date()).toISOString(), loginAttempt: 0 }).where(
3200
+ and21(
3201
+ eq21(usersInIam.id, user.id),
3202
+ eq21(usersInIam.tenantId, resolvedTenantId)
3203
+ )
3204
+ );
3205
+ return createSessionRecord({
3206
+ tx,
3207
+ tenantId: resolvedTenantId,
3208
+ userId: user.id,
3209
+ config,
3210
+ userAgent,
3211
+ ip,
3212
+ action: "set-password",
3213
+ rememberMe
3214
+ });
3215
+ });
3216
+ setSessionCookie(c, result.sessionToken, config, {
3217
+ expires: new Date(result.expiresAt)
3218
+ });
3219
+ const fullUser = await fetchUserByIdWithRoles({
3220
+ database,
3221
+ userId: user.id,
3222
+ tenantId: resolvedTenantId
3223
+ });
3224
+ return c.json(
3225
+ {
3226
+ user: normalizeAuthUser(fullUser ?? { ...user, roles: [] }),
3227
+ session: normalizeAuthSession(result.session),
3228
+ sessionExpiresAt: result.session.expiresAt
3229
+ },
3230
+ 200
3231
+ );
3232
+ };
3233
+
3083
3234
  // src/routes/password/handler/verify.ts
3084
- import { and as and20, eq as eq20 } from "drizzle-orm";
3235
+ import { and as and22, eq as eq22 } from "drizzle-orm";
3085
3236
  var verifyPasswordHandler = async (c) => {
3086
3237
  const body = c.req.valid("json");
3087
3238
  const config = c.get("config");
@@ -3098,10 +3249,10 @@ var verifyPasswordHandler = async (c) => {
3098
3249
  const resolvedTenantId = ensureTenantId(config, tenantId);
3099
3250
  const { password } = body;
3100
3251
  const [account] = await database.select().from(accountsInIam).where(
3101
- and20(
3102
- eq20(accountsInIam.tenantId, resolvedTenantId),
3103
- eq20(accountsInIam.userId, userId),
3104
- eq20(accountsInIam.provider, "credentials")
3252
+ and22(
3253
+ eq22(accountsInIam.tenantId, resolvedTenantId),
3254
+ eq22(accountsInIam.userId, userId),
3255
+ eq22(accountsInIam.provider, "credentials")
3105
3256
  )
3106
3257
  ).limit(1);
3107
3258
  if (!account?.password) {
@@ -3223,56 +3374,375 @@ var changePasswordRoute = createRoute4({
3223
3374
  }
3224
3375
  }
3225
3376
  });
3226
- var passwordRoutes = new OpenAPIHono4().openapi(forgotPasswordRoute, forgotPasswordHandler).openapi(resetPasswordRoute, resetPasswordHandler).openapi(verifyPasswordRoute, verifyPasswordHandler).openapi(changePasswordRoute, changePasswordHandler);
3377
+ var setPasswordRoute = createRoute4({
3378
+ method: "post",
3379
+ path: "/set",
3380
+ tags: ["Password"],
3381
+ summary: "Set password for an existing verified account",
3382
+ request: {
3383
+ body: {
3384
+ content: {
3385
+ "application/json": {
3386
+ schema: setPasswordSchema
3387
+ }
3388
+ }
3389
+ }
3390
+ },
3391
+ responses: {
3392
+ 200: {
3393
+ content: {
3394
+ "application/json": {
3395
+ schema: authSuccessSchema
3396
+ }
3397
+ },
3398
+ description: "Password set and user signed in"
3399
+ },
3400
+ 400: {
3401
+ content: { "application/json": { schema: errorResponseSchema } },
3402
+ description: "Account not found"
3403
+ },
3404
+ 409: {
3405
+ content: { "application/json": { schema: errorResponseSchema } },
3406
+ description: "Password already exists or verification required"
3407
+ }
3408
+ }
3409
+ });
3410
+ var passwordRoutes = new OpenAPIHono4().openapi(forgotPasswordRoute, forgotPasswordHandler).openapi(resetPasswordRoute, resetPasswordHandler).openapi(verifyPasswordRoute, verifyPasswordHandler).openapi(changePasswordRoute, changePasswordHandler).openapi(setPasswordRoute, setPasswordHandler);
3227
3411
  var password_route_default = passwordRoutes;
3228
3412
 
3229
3413
  // src/routes/permissions/permissions.route.ts
3230
3414
  import { createRoute as createRoute5, OpenAPIHono as OpenAPIHono5 } from "@hono/zod-openapi";
3231
3415
 
3232
3416
  // src/routes/permissions/handler/get-permission.ts
3233
- import { eq as eq21 } from "drizzle-orm";
3417
+ import { getPermissionEntries } from "@mesob/common";
3418
+ import { eq as eq23 } from "drizzle-orm";
3234
3419
  var getPermissionHandler = async (c) => {
3235
3420
  const { id } = c.req.valid("param");
3236
3421
  const database = c.get("database");
3237
- const [permission] = await database.select().from(permissionsInIam).where(eq21(permissionsInIam.id, id)).limit(1);
3422
+ const config = c.get("config");
3423
+ const [permission] = await database.select().from(permissionsInIam).where(eq23(permissionsInIam.id, id)).limit(1);
3238
3424
  if (!permission) {
3239
- return c.json({ error: "Permission not found" }, 404);
3425
+ const configuredPermission = getPermissionEntries(config.permissions).find(
3426
+ (entry) => entry.code === id
3427
+ );
3428
+ if (!configuredPermission) {
3429
+ return c.json({ error: "Permission not found" }, 404);
3430
+ }
3431
+ return c.json(
3432
+ {
3433
+ permission: {
3434
+ id: configuredPermission.code,
3435
+ description: null,
3436
+ activity: configuredPermission.activity,
3437
+ application: configuredPermission.application,
3438
+ feature: configuredPermission.feature
3439
+ }
3440
+ },
3441
+ 200
3442
+ );
3240
3443
  }
3241
3444
  return c.json({ permission }, 200);
3242
3445
  };
3243
3446
 
3447
+ // src/lib/permission-catalog.ts
3448
+ import { getPermissionEntries as getPermissionEntries2 } from "@mesob/common";
3449
+ var collator = new Intl.Collator(void 0, {
3450
+ numeric: true,
3451
+ sensitivity: "base"
3452
+ });
3453
+ var sortValueMap = {
3454
+ id: (permission) => permission.id,
3455
+ application: (permission) => permission.application,
3456
+ feature: (permission) => permission.feature,
3457
+ activity: (permission) => permission.activity
3458
+ };
3459
+ async function getMergedPermissionCatalog({
3460
+ database,
3461
+ permissions
3462
+ }) {
3463
+ const [databasePermissions, configuredPermissions] = await Promise.all([
3464
+ database.select().from(permissionsInIam),
3465
+ Promise.resolve(
3466
+ getPermissionEntries2(permissions).map((entry) => ({
3467
+ id: entry.code,
3468
+ description: null,
3469
+ activity: entry.activity,
3470
+ application: entry.application,
3471
+ feature: entry.feature
3472
+ }))
3473
+ )
3474
+ ]);
3475
+ return [...databasePermissions, ...configuredPermissions].filter(
3476
+ (permission, index2, permissionList) => {
3477
+ const key = [
3478
+ permission.application,
3479
+ permission.feature,
3480
+ permission.activity
3481
+ ].join(":");
3482
+ return permissionList.findIndex((candidate) => {
3483
+ return [candidate.application, candidate.feature, candidate.activity].join(
3484
+ ":"
3485
+ ) === key;
3486
+ }) === index2;
3487
+ }
3488
+ );
3489
+ }
3490
+ function filterAndSortPermissions(permissions, query) {
3491
+ const search = query.search?.trim().toLowerCase();
3492
+ const filteredPermissions = search ? permissions.filter((permission) => {
3493
+ let fields = [
3494
+ permission.id,
3495
+ permission.application,
3496
+ permission.feature,
3497
+ permission.activity
3498
+ ];
3499
+ if (query.filter === "application") {
3500
+ fields = [permission.application];
3501
+ } else if (query.filter === "feature") {
3502
+ fields = [permission.feature];
3503
+ } else if (query.filter === "activity") {
3504
+ fields = [permission.activity];
3505
+ }
3506
+ return fields.some((field) => field.toLowerCase().includes(search));
3507
+ }) : permissions;
3508
+ const sortGetter = sortValueMap[query.sort || "id"] ?? sortValueMap.id;
3509
+ return [...filteredPermissions].sort((a, b) => {
3510
+ const order = collator.compare(sortGetter(a), sortGetter(b)) || collator.compare(a.id, b.id);
3511
+ return query.order === "desc" ? -order : order;
3512
+ });
3513
+ }
3514
+
3244
3515
  // src/routes/permissions/handler/list-permissions.ts
3245
- import { and as and21, ilike, sql as sql12 } from "drizzle-orm";
3246
3516
  var listPermissionsHandler = async (c) => {
3247
3517
  const query = c.req.valid("query");
3248
3518
  const database = c.get("database");
3519
+ const config = c.get("config");
3249
3520
  const page = query.page || 1;
3250
3521
  const limit = query.limit || 20;
3251
3522
  const offset = (page - 1) * limit;
3252
- const conditions = [];
3253
- if (query.application) {
3254
- conditions.push(
3255
- ilike(permissionsInIam.application, `%${query.application}%`)
3523
+ const mergedPermissions = await getMergedPermissionCatalog({
3524
+ database,
3525
+ permissions: config.permissions
3526
+ });
3527
+ const sortedPermissions = filterAndSortPermissions(mergedPermissions, query);
3528
+ const total = sortedPermissions.length;
3529
+ const permissions = sortedPermissions.slice(offset, offset + limit);
3530
+ return c.json({ permissions, total, page, limit }, 200);
3531
+ };
3532
+
3533
+ // src/lib/iam-seed.ts
3534
+ import { randomUUID } from "crypto";
3535
+ import { getPermissionEntries as getPermissionEntries3, toTitleCase } from "@mesob/common";
3536
+ import {
3537
+ and as and23,
3538
+ eq as eq24,
3539
+ inArray as inArray2,
3540
+ notInArray,
3541
+ sql as sql12
3542
+ } from "drizzle-orm";
3543
+ import { HTTPException as HTTPException3 } from "hono/http-exception";
3544
+ function buildPermissionDescription(code) {
3545
+ return {
3546
+ en: toTitleCase(code.replaceAll(":", " ").replaceAll("_", " "))
3547
+ };
3548
+ }
3549
+ function buildPermissionSeedRows(permissions) {
3550
+ return getPermissionEntries3(permissions).map((entry) => ({
3551
+ id: entry.code,
3552
+ application: entry.application,
3553
+ feature: entry.feature,
3554
+ activity: entry.activity,
3555
+ description: buildPermissionDescription(entry.code)
3556
+ }));
3557
+ }
3558
+ async function seedPermissions({
3559
+ database,
3560
+ permissions
3561
+ }) {
3562
+ const rows = buildPermissionSeedRows(permissions);
3563
+ if (!rows.length) {
3564
+ return [];
3565
+ }
3566
+ await database.insert(permissionsInIam).values(rows).onConflictDoUpdate({
3567
+ target: permissionsInIam.id,
3568
+ set: {
3569
+ application: sql12`excluded.application`,
3570
+ feature: sql12`excluded.feature`,
3571
+ activity: sql12`excluded.activity`,
3572
+ description: sql12`excluded.description`
3573
+ }
3574
+ });
3575
+ return database.select().from(permissionsInIam).where(
3576
+ inArray2(
3577
+ permissionsInIam.id,
3578
+ rows.map((row) => row.id)
3579
+ )
3580
+ );
3581
+ }
3582
+ async function assertPermissionsExist({
3583
+ database,
3584
+ permissionIds
3585
+ }) {
3586
+ if (!permissionIds.length) {
3587
+ return;
3588
+ }
3589
+ const existing = await database.select({ id: permissionsInIam.id }).from(permissionsInIam).where(inArray2(permissionsInIam.id, permissionIds));
3590
+ const existingIds = new Set(existing.map((permission) => permission.id));
3591
+ const missingPermissionIds = permissionIds.filter(
3592
+ (id) => !existingIds.has(id)
3593
+ );
3594
+ if (missingPermissionIds.length) {
3595
+ throw new HTTPException3(400, {
3596
+ message: `Unknown permissions: ${missingPermissionIds.join(", ")}`
3597
+ });
3598
+ }
3599
+ }
3600
+ async function syncRolePermissions({
3601
+ database,
3602
+ tenantId,
3603
+ roleId,
3604
+ permissionIds
3605
+ }) {
3606
+ const uniquePermissionIds = [...new Set(permissionIds)];
3607
+ await assertPermissionsExist({
3608
+ database,
3609
+ permissionIds: uniquePermissionIds
3610
+ });
3611
+ if (!uniquePermissionIds.length) {
3612
+ await database.delete(rolePermissionsInIam).where(
3613
+ and23(
3614
+ eq24(rolePermissionsInIam.tenantId, tenantId),
3615
+ eq24(rolePermissionsInIam.roleId, roleId)
3616
+ )
3256
3617
  );
3618
+ return [];
3619
+ }
3620
+ await database.delete(rolePermissionsInIam).where(
3621
+ and23(
3622
+ eq24(rolePermissionsInIam.tenantId, tenantId),
3623
+ eq24(rolePermissionsInIam.roleId, roleId),
3624
+ notInArray(rolePermissionsInIam.permissionId, uniquePermissionIds)
3625
+ )
3626
+ );
3627
+ await database.insert(rolePermissionsInIam).values(
3628
+ uniquePermissionIds.map((permissionId) => ({
3629
+ id: randomUUID(),
3630
+ tenantId,
3631
+ roleId,
3632
+ permissionId
3633
+ }))
3634
+ ).onConflictDoNothing({
3635
+ target: [
3636
+ rolePermissionsInIam.tenantId,
3637
+ rolePermissionsInIam.permissionId,
3638
+ rolePermissionsInIam.roleId
3639
+ ]
3640
+ });
3641
+ return database.select().from(rolePermissionsInIam).where(
3642
+ and23(
3643
+ eq24(rolePermissionsInIam.tenantId, tenantId),
3644
+ eq24(rolePermissionsInIam.roleId, roleId)
3645
+ )
3646
+ );
3647
+ }
3648
+ async function seedRoles({
3649
+ database,
3650
+ tenantId,
3651
+ roles
3652
+ }) {
3653
+ if (!roles.length) {
3654
+ return [];
3655
+ }
3656
+ await database.insert(rolesInIam).values(
3657
+ roles.map((role) => ({
3658
+ id: randomUUID(),
3659
+ tenantId,
3660
+ code: role.code,
3661
+ name: role.name,
3662
+ description: role.description ?? { en: role.code },
3663
+ isSystem: role.isSystem ?? false,
3664
+ isEditable: role.isEditable ?? true,
3665
+ isDeletable: role.isDeletable ?? true
3666
+ }))
3667
+ ).onConflictDoUpdate({
3668
+ target: [rolesInIam.tenantId, rolesInIam.code],
3669
+ set: {
3670
+ name: sql12`excluded.name`,
3671
+ description: sql12`excluded.description`,
3672
+ isSystem: sql12`excluded.is_system`,
3673
+ isEditable: sql12`excluded.is_editable`,
3674
+ isDeletable: sql12`excluded.is_deletable`,
3675
+ updatedAt: sql12`CURRENT_TIMESTAMP`
3676
+ }
3677
+ });
3678
+ const seededRoles = await database.select().from(rolesInIam).where(
3679
+ and23(
3680
+ eq24(rolesInIam.tenantId, tenantId),
3681
+ inArray2(
3682
+ rolesInIam.code,
3683
+ roles.map((role) => role.code)
3684
+ )
3685
+ )
3686
+ );
3687
+ const roleByCode = new Map(
3688
+ seededRoles.map((role) => [role.code, role])
3689
+ );
3690
+ for (const role of roles) {
3691
+ const seededRole = roleByCode.get(role.code);
3692
+ if (!seededRole) {
3693
+ continue;
3694
+ }
3695
+ await syncRolePermissions({
3696
+ database,
3697
+ tenantId,
3698
+ roleId: seededRole.id,
3699
+ permissionIds: [...new Set(role.permissionIds ?? [])]
3700
+ });
3257
3701
  }
3258
- if (query.feature) {
3259
- conditions.push(ilike(permissionsInIam.feature, `%${query.feature}%`));
3702
+ return seededRoles;
3703
+ }
3704
+
3705
+ // src/routes/permissions/handler/seed-permissions.ts
3706
+ var seedPermissionsHandler = async (c) => {
3707
+ const config = c.get("config");
3708
+ const database = c.get("database");
3709
+ if (!config.permissions) {
3710
+ return c.json({ error: "No permissions configured for seeding" }, 400);
3260
3711
  }
3261
- const [permissions, totalResult] = await Promise.all([
3262
- database.select().from(permissionsInIam).where(conditions.length > 0 ? and21(...conditions) : void 0).limit(limit).offset(offset),
3263
- database.select({ count: sql12`count(*)` }).from(permissionsInIam).where(conditions.length > 0 ? and21(...conditions) : void 0)
3264
- ]);
3265
- const total = Number(totalResult[0]?.count || 0);
3266
- return c.json({ permissions, total, page, limit }, 200);
3712
+ const permissions = await seedPermissions({
3713
+ database,
3714
+ permissions: config.permissions
3715
+ });
3716
+ return c.json(
3717
+ {
3718
+ permissions,
3719
+ total: permissions.length
3720
+ },
3721
+ 200
3722
+ );
3267
3723
  };
3268
3724
 
3269
3725
  // src/routes/permissions/permissions.schema.ts
3270
3726
  import { z as z3 } from "zod";
3727
+ var permissionFilterValues = [
3728
+ "",
3729
+ "application",
3730
+ "feature",
3731
+ "activity"
3732
+ ];
3733
+ var permissionSortValues = [
3734
+ "id",
3735
+ "application",
3736
+ "feature",
3737
+ "activity"
3738
+ ];
3271
3739
  var listPermissionsQuerySchema = z3.object({
3272
3740
  page: z3.coerce.number().min(1).default(1).optional(),
3273
3741
  limit: z3.coerce.number().min(1).max(100).default(20).optional(),
3274
- application: z3.string().optional(),
3275
- feature: z3.string().optional()
3742
+ search: z3.string().optional(),
3743
+ filter: z3.enum(permissionFilterValues).optional(),
3744
+ sort: z3.enum(permissionSortValues).optional(),
3745
+ order: z3.enum(["asc", "desc"]).optional()
3276
3746
  });
3277
3747
  var permissionIdParamSchema = z3.object({
3278
3748
  id: z3.string()
@@ -3284,6 +3754,10 @@ var permissionSchema = z3.object({
3284
3754
  application: z3.string(),
3285
3755
  feature: z3.string()
3286
3756
  });
3757
+ var seedPermissionsResponseSchema = z3.object({
3758
+ permissions: z3.array(permissionSchema),
3759
+ total: z3.number()
3760
+ });
3287
3761
  var listPermissionsResponseSchema = z3.object({
3288
3762
  permissions: z3.array(permissionSchema),
3289
3763
  total: z3.number(),
@@ -3344,14 +3818,38 @@ var getPermissionRoute = createRoute5({
3344
3818
  }
3345
3819
  }
3346
3820
  });
3347
- var permissionRoutes = new OpenAPIHono5().openapi(listPermissionsRoute, listPermissionsHandler).openapi(getPermissionRoute, getPermissionHandler);
3348
- var permissions_route_default = permissionRoutes;
3349
-
3350
- // src/routes/phone/phone.route.ts
3351
- import { createRoute as createRoute6, OpenAPIHono as OpenAPIHono6 } from "@hono/zod-openapi";
3352
-
3821
+ var seedPermissionsRoute = createRoute5({
3822
+ method: "post",
3823
+ path: "/seed",
3824
+ tags: ["Permissions"],
3825
+ summary: "Seed permissions from config",
3826
+ responses: {
3827
+ 200: {
3828
+ content: {
3829
+ "application/json": {
3830
+ schema: seedPermissionsResponseSchema
3831
+ }
3832
+ },
3833
+ description: "Seeded permissions"
3834
+ },
3835
+ 400: {
3836
+ content: {
3837
+ "application/json": {
3838
+ schema: errorResponseSchema3
3839
+ }
3840
+ },
3841
+ description: "Invalid permission config"
3842
+ }
3843
+ }
3844
+ });
3845
+ var permissionRoutes = new OpenAPIHono5().openapi(listPermissionsRoute, listPermissionsHandler).openapi(seedPermissionsRoute, seedPermissionsHandler).openapi(getPermissionRoute, getPermissionHandler);
3846
+ var permissions_route_default = permissionRoutes;
3847
+
3848
+ // src/routes/phone/phone.route.ts
3849
+ import { createRoute as createRoute6, OpenAPIHono as OpenAPIHono6 } from "@hono/zod-openapi";
3850
+
3353
3851
  // src/routes/phone/handler/verification-confirm.ts
3354
- import { and as and22, eq as eq22 } from "drizzle-orm";
3852
+ import { and as and24, eq as eq25 } from "drizzle-orm";
3355
3853
 
3356
3854
  // src/routes/phone/helper/session.ts
3357
3855
  var shouldCreateSession = (context) => context === "sign-in" || context === "change-phone" || context === "sign-up";
@@ -3367,9 +3865,9 @@ var phoneVerificationConfirmHandler = async (c) => {
3367
3865
  const { verificationId, code, context } = body;
3368
3866
  const result = await withTransaction(database, async (tx) => {
3369
3867
  const [verification] = await tx.select().from(verificationsInIam).where(
3370
- and22(
3371
- eq22(verificationsInIam.id, verificationId),
3372
- eq22(verificationsInIam.tenantId, resolvedTenantId)
3868
+ and24(
3869
+ eq25(verificationsInIam.id, verificationId),
3870
+ eq25(verificationsInIam.tenantId, resolvedTenantId)
3373
3871
  )
3374
3872
  ).limit(1);
3375
3873
  const expectedType = `phone-otp-${context}`;
@@ -3386,7 +3884,7 @@ var phoneVerificationConfirmHandler = async (c) => {
3386
3884
  };
3387
3885
  }
3388
3886
  if ((verification.attempt || 0) >= config.phone.maxAttempts) {
3389
- await tx.delete(verificationsInIam).where(eq22(verificationsInIam.id, verificationId));
3887
+ await tx.delete(verificationsInIam).where(eq25(verificationsInIam.id, verificationId));
3390
3888
  return {
3391
3889
  status: "error",
3392
3890
  error: AUTH_ERRORS.TOO_MANY_ATTEMPTS
@@ -3394,21 +3892,21 @@ var phoneVerificationConfirmHandler = async (c) => {
3394
3892
  }
3395
3893
  const hashedCode = await hashToken(code, config.secret);
3396
3894
  if (verification.code !== hashedCode) {
3397
- await tx.update(verificationsInIam).set({ attempt: (verification.attempt || 0) + 1 }).where(eq22(verificationsInIam.id, verificationId));
3895
+ await tx.update(verificationsInIam).set({ attempt: (verification.attempt || 0) + 1 }).where(eq25(verificationsInIam.id, verificationId));
3398
3896
  return {
3399
3897
  status: "error",
3400
3898
  error: AUTH_ERRORS.VERIFICATION_MISMATCH
3401
3899
  };
3402
3900
  }
3403
- await tx.delete(verificationsInIam).where(eq22(verificationsInIam.id, verificationId));
3901
+ await tx.delete(verificationsInIam).where(eq25(verificationsInIam.id, verificationId));
3404
3902
  if (shouldCreateSession(context) && verification.userId) {
3405
3903
  await tx.update(usersInIam).set({
3406
3904
  phoneVerified: true,
3407
3905
  lastSignInAt: (/* @__PURE__ */ new Date()).toISOString()
3408
3906
  }).where(
3409
- and22(
3410
- eq22(usersInIam.id, verification.userId),
3411
- eq22(usersInIam.tenantId, resolvedTenantId)
3907
+ and24(
3908
+ eq25(usersInIam.id, verification.userId),
3909
+ eq25(usersInIam.tenantId, resolvedTenantId)
3412
3910
  )
3413
3911
  );
3414
3912
  }
@@ -3450,7 +3948,7 @@ var phoneVerificationConfirmHandler = async (c) => {
3450
3948
  if (!result.session) {
3451
3949
  return c.json(
3452
3950
  {
3453
- user: normalizeUser(result.user),
3951
+ user: normalizeAuthUser(result.user),
3454
3952
  session: null
3455
3953
  },
3456
3954
  200
@@ -3461,14 +3959,8 @@ var phoneVerificationConfirmHandler = async (c) => {
3461
3959
  });
3462
3960
  return c.json(
3463
3961
  {
3464
- user: normalizeUser(result.user),
3465
- session: {
3466
- id: result.session.id,
3467
- expiresAt: result.session.expiresAt,
3468
- createdAt: result.session.createdAt,
3469
- userAgent: result.session.userAgent,
3470
- ip: result.session.ip
3471
- },
3962
+ user: normalizeAuthUser(result.user),
3963
+ session: normalizeAuthSession(result.session),
3472
3964
  sessionExpiresAt: result.session.expiresAt
3473
3965
  },
3474
3966
  200
@@ -3476,7 +3968,7 @@ var phoneVerificationConfirmHandler = async (c) => {
3476
3968
  };
3477
3969
 
3478
3970
  // src/routes/phone/handler/verification-request.ts
3479
- import { and as and23, eq as eq23 } from "drizzle-orm";
3971
+ import { and as and25, eq as eq26 } from "drizzle-orm";
3480
3972
  var phoneVerificationRequestHandler = async (c) => {
3481
3973
  const body = c.req.valid("json");
3482
3974
  const config = c.get("config");
@@ -3501,9 +3993,9 @@ var phoneVerificationRequestHandler = async (c) => {
3501
3993
  let userId = user?.id;
3502
3994
  if (!userId) {
3503
3995
  const [result] = await database.select({ id: usersInIam.id }).from(usersInIam).where(
3504
- and23(
3505
- eq23(usersInIam.tenantId, resolvedTenantId),
3506
- eq23(usersInIam.phone, phone)
3996
+ and25(
3997
+ eq26(usersInIam.tenantId, resolvedTenantId),
3998
+ eq26(usersInIam.phone, phone)
3507
3999
  )
3508
4000
  ).limit(1);
3509
4001
  if (!result) {
@@ -3529,10 +4021,10 @@ var phoneVerificationRequestHandler = async (c) => {
3529
4021
  );
3530
4022
  }
3531
4023
  await database.delete(verificationsInIam).where(
3532
- and23(
3533
- eq23(verificationsInIam.tenantId, resolvedTenantId),
3534
- eq23(verificationsInIam.userId, userId),
3535
- eq23(verificationsInIam.type, verificationType)
4024
+ and25(
4025
+ eq26(verificationsInIam.tenantId, resolvedTenantId),
4026
+ eq26(verificationsInIam.userId, userId),
4027
+ eq26(verificationsInIam.type, verificationType)
3536
4028
  )
3537
4029
  );
3538
4030
  const code = generateOtpCode(config.phone.otpLength);
@@ -3564,11 +4056,11 @@ var phoneVerificationRequestHandler = async (c) => {
3564
4056
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
3565
4057
  reason: "replaced"
3566
4058
  }).where(
3567
- and23(
3568
- eq23(accountChangesInIam.tenantId, resolvedTenantId),
3569
- eq23(accountChangesInIam.userId, user.id),
3570
- eq23(accountChangesInIam.changeType, "phone"),
3571
- eq23(accountChangesInIam.status, "pending")
4059
+ and25(
4060
+ eq26(accountChangesInIam.tenantId, resolvedTenantId),
4061
+ eq26(accountChangesInIam.userId, user.id),
4062
+ eq26(accountChangesInIam.changeType, "phone"),
4063
+ eq26(accountChangesInIam.status, "pending")
3572
4064
  )
3573
4065
  );
3574
4066
  await database.insert(accountChangesInIam).values({
@@ -3684,7 +4176,7 @@ import { createRoute as createRoute7, OpenAPIHono as OpenAPIHono7 } from "@hono/
3684
4176
  import { z as z4 } from "zod";
3685
4177
 
3686
4178
  // src/routes/profile/handler/account-change-pending.ts
3687
- import { and as and24, eq as eq24, gt as gt7, sql as sql13 } from "drizzle-orm";
4179
+ import { and as and26, eq as eq27, gt as gt7, sql as sql13 } from "drizzle-orm";
3688
4180
  var accountChangePendingHandler = async (c) => {
3689
4181
  const config = c.get("config");
3690
4182
  const database = c.get("database");
@@ -3695,17 +4187,17 @@ var accountChangePendingHandler = async (c) => {
3695
4187
  }
3696
4188
  const resolvedTenantId = ensureTenantId(config, tenantId);
3697
4189
  await database.update(accountChangesInIam).set({ status: "expired" }).where(
3698
- and24(
3699
- eq24(accountChangesInIam.tenantId, resolvedTenantId),
3700
- eq24(accountChangesInIam.userId, userId),
4190
+ and26(
4191
+ eq27(accountChangesInIam.tenantId, resolvedTenantId),
4192
+ eq27(accountChangesInIam.userId, userId),
3701
4193
  sql13`${accountChangesInIam.expiresAt} < CURRENT_TIMESTAMP`
3702
4194
  )
3703
4195
  );
3704
4196
  const [accountChange] = await database.select().from(accountChangesInIam).where(
3705
- and24(
3706
- eq24(accountChangesInIam.tenantId, resolvedTenantId),
3707
- eq24(accountChangesInIam.userId, userId),
3708
- eq24(accountChangesInIam.status, "pending")
4197
+ and26(
4198
+ eq27(accountChangesInIam.tenantId, resolvedTenantId),
4199
+ eq27(accountChangesInIam.userId, userId),
4200
+ eq27(accountChangesInIam.status, "pending")
3709
4201
  )
3710
4202
  ).limit(1);
3711
4203
  if (!accountChange) {
@@ -3717,11 +4209,11 @@ var accountChangePendingHandler = async (c) => {
3717
4209
  id: verificationsInIam.id,
3718
4210
  expiresAt: verificationsInIam.expiresAt
3719
4211
  }).from(verificationsInIam).where(
3720
- and24(
3721
- eq24(verificationsInIam.tenantId, resolvedTenantId),
3722
- eq24(verificationsInIam.userId, userId),
3723
- eq24(verificationsInIam.type, "email-verification"),
3724
- eq24(verificationsInIam.to, accountChange.newEmail),
4212
+ and26(
4213
+ eq27(verificationsInIam.tenantId, resolvedTenantId),
4214
+ eq27(verificationsInIam.userId, userId),
4215
+ eq27(verificationsInIam.type, "email-verification"),
4216
+ eq27(verificationsInIam.to, accountChange.newEmail),
3725
4217
  gt7(verificationsInIam.expiresAt, sql13`CURRENT_TIMESTAMP`)
3726
4218
  )
3727
4219
  ).limit(1);
@@ -3732,11 +4224,11 @@ var accountChangePendingHandler = async (c) => {
3732
4224
  id: verificationsInIam.id,
3733
4225
  expiresAt: verificationsInIam.expiresAt
3734
4226
  }).from(verificationsInIam).where(
3735
- and24(
3736
- eq24(verificationsInIam.tenantId, resolvedTenantId),
3737
- eq24(verificationsInIam.userId, userId),
3738
- eq24(verificationsInIam.type, "phone-otp-change-phone"),
3739
- eq24(verificationsInIam.to, accountChange.newPhone),
4227
+ and26(
4228
+ eq27(verificationsInIam.tenantId, resolvedTenantId),
4229
+ eq27(verificationsInIam.userId, userId),
4230
+ eq27(verificationsInIam.type, "phone-otp-change-phone"),
4231
+ eq27(verificationsInIam.to, accountChange.newPhone),
3740
4232
  gt7(verificationsInIam.expiresAt, sql13`CURRENT_TIMESTAMP`)
3741
4233
  )
3742
4234
  ).limit(1);
@@ -3759,6 +4251,13 @@ var accountChangePendingHandler = async (c) => {
3759
4251
  );
3760
4252
  };
3761
4253
 
4254
+ // src/lib/normalize-user.ts
4255
+ var normalizeUser = (user) => ({
4256
+ ...user,
4257
+ roles: user.roles ?? null,
4258
+ roleCodes: user.roleCodes ?? null
4259
+ });
4260
+
3762
4261
  // src/routes/profile/handler/me.ts
3763
4262
  var meHandler = (c) => {
3764
4263
  const user = c.get("user");
@@ -3805,7 +4304,7 @@ var sessionHandler = (c) => {
3805
4304
  };
3806
4305
 
3807
4306
  // src/routes/profile/handler/update.ts
3808
- import { and as and25, eq as eq25, sql as sql14 } from "drizzle-orm";
4307
+ import { and as and27, eq as eq28, sql as sql14 } from "drizzle-orm";
3809
4308
  var updateProfileHandler = async (c) => {
3810
4309
  const body = c.req.valid("json");
3811
4310
  const config = c.get("config");
@@ -3828,7 +4327,7 @@ var updateProfileHandler = async (c) => {
3828
4327
  ...updateData,
3829
4328
  updatedAt: sql14`CURRENT_TIMESTAMP`
3830
4329
  }).where(
3831
- and25(eq25(usersInIam.id, userId), eq25(usersInIam.tenantId, resolvedTenantId))
4330
+ and27(eq28(usersInIam.id, userId), eq28(usersInIam.tenantId, resolvedTenantId))
3832
4331
  ).returning({
3833
4332
  id: usersInIam.id,
3834
4333
  tenantId: usersInIam.tenantId,
@@ -3848,7 +4347,7 @@ var updateProfileHandler = async (c) => {
3848
4347
  };
3849
4348
 
3850
4349
  // src/routes/profile/handler/update-email.ts
3851
- import { and as and26, eq as eq26, ne as ne2, sql as sql15 } from "drizzle-orm";
4350
+ import { and as and28, eq as eq29, ne as ne2, sql as sql15 } from "drizzle-orm";
3852
4351
  var updateEmailHandler = async (c) => {
3853
4352
  const body = c.req.valid("json");
3854
4353
  const config = c.get("config");
@@ -3866,9 +4365,9 @@ var updateEmailHandler = async (c) => {
3866
4365
  const resolvedTenantId = ensureTenantId(config, tenantId);
3867
4366
  if (user.email && session?.id) {
3868
4367
  await database.delete(sessionsInIam).where(
3869
- and26(
3870
- eq26(sessionsInIam.tenantId, resolvedTenantId),
3871
- eq26(sessionsInIam.userId, userId),
4368
+ and28(
4369
+ eq29(sessionsInIam.tenantId, resolvedTenantId),
4370
+ eq29(sessionsInIam.userId, userId),
3872
4371
  ne2(sessionsInIam.id, session.id)
3873
4372
  )
3874
4373
  );
@@ -3878,7 +4377,7 @@ var updateEmailHandler = async (c) => {
3878
4377
  emailVerified: true,
3879
4378
  updatedAt: sql15`CURRENT_TIMESTAMP`
3880
4379
  }).where(
3881
- and26(eq26(usersInIam.id, userId), eq26(usersInIam.tenantId, resolvedTenantId))
4380
+ and28(eq29(usersInIam.id, userId), eq29(usersInIam.tenantId, resolvedTenantId))
3882
4381
  ).returning({
3883
4382
  id: usersInIam.id,
3884
4383
  tenantId: usersInIam.tenantId,
@@ -3895,25 +4394,25 @@ var updateEmailHandler = async (c) => {
3895
4394
  return c.json({ error: "User not found" }, 404);
3896
4395
  }
3897
4396
  await database.update(accountChangesInIam).set({ status: "applied" }).where(
3898
- and26(
3899
- eq26(accountChangesInIam.tenantId, resolvedTenantId),
3900
- eq26(accountChangesInIam.userId, userId),
3901
- eq26(accountChangesInIam.changeType, "email"),
3902
- eq26(accountChangesInIam.newEmail, body.email)
4397
+ and28(
4398
+ eq29(accountChangesInIam.tenantId, resolvedTenantId),
4399
+ eq29(accountChangesInIam.userId, userId),
4400
+ eq29(accountChangesInIam.changeType, "email"),
4401
+ eq29(accountChangesInIam.newEmail, body.email)
3903
4402
  )
3904
4403
  );
3905
4404
  await database.update(accountsInIam).set({ providerAccountId: body.email }).where(
3906
- and26(
3907
- eq26(accountsInIam.tenantId, resolvedTenantId),
3908
- eq26(accountsInIam.userId, userId),
3909
- eq26(accountsInIam.provider, "credentials")
4405
+ and28(
4406
+ eq29(accountsInIam.tenantId, resolvedTenantId),
4407
+ eq29(accountsInIam.userId, userId),
4408
+ eq29(accountsInIam.provider, "credentials")
3910
4409
  )
3911
4410
  );
3912
4411
  return c.json({ user: normalizeUser(updatedUser) }, 200);
3913
4412
  };
3914
4413
 
3915
4414
  // src/routes/profile/handler/update-phone.ts
3916
- import { and as and27, eq as eq27, ne as ne3, sql as sql16 } from "drizzle-orm";
4415
+ import { and as and29, eq as eq30, ne as ne3, sql as sql16 } from "drizzle-orm";
3917
4416
  var updatePhoneHandler = async (c) => {
3918
4417
  const body = c.req.valid("json");
3919
4418
  const config = c.get("config");
@@ -3935,9 +4434,9 @@ var updatePhoneHandler = async (c) => {
3935
4434
  }
3936
4435
  if (user.phone && session?.id) {
3937
4436
  await database.delete(sessionsInIam).where(
3938
- and27(
3939
- eq27(sessionsInIam.tenantId, resolvedTenantId),
3940
- eq27(sessionsInIam.userId, userId),
4437
+ and29(
4438
+ eq30(sessionsInIam.tenantId, resolvedTenantId),
4439
+ eq30(sessionsInIam.userId, userId),
3941
4440
  ne3(sessionsInIam.id, session.id)
3942
4441
  )
3943
4442
  );
@@ -3947,7 +4446,7 @@ var updatePhoneHandler = async (c) => {
3947
4446
  phoneVerified: true,
3948
4447
  updatedAt: sql16`CURRENT_TIMESTAMP`
3949
4448
  }).where(
3950
- and27(eq27(usersInIam.id, userId), eq27(usersInIam.tenantId, resolvedTenantId))
4449
+ and29(eq30(usersInIam.id, userId), eq30(usersInIam.tenantId, resolvedTenantId))
3951
4450
  ).returning({
3952
4451
  id: usersInIam.id,
3953
4452
  tenantId: usersInIam.tenantId,
@@ -3964,18 +4463,18 @@ var updatePhoneHandler = async (c) => {
3964
4463
  return c.json({ error: "User not found" }, 404);
3965
4464
  }
3966
4465
  await database.update(accountChangesInIam).set({ status: "applied" }).where(
3967
- and27(
3968
- eq27(accountChangesInIam.tenantId, resolvedTenantId),
3969
- eq27(accountChangesInIam.userId, userId),
3970
- eq27(accountChangesInIam.changeType, "phone"),
3971
- eq27(accountChangesInIam.newPhone, body.phone)
4466
+ and29(
4467
+ eq30(accountChangesInIam.tenantId, resolvedTenantId),
4468
+ eq30(accountChangesInIam.userId, userId),
4469
+ eq30(accountChangesInIam.changeType, "phone"),
4470
+ eq30(accountChangesInIam.newPhone, body.phone)
3972
4471
  )
3973
4472
  );
3974
4473
  await database.update(accountsInIam).set({ providerAccountId: body.phone }).where(
3975
- and27(
3976
- eq27(accountsInIam.tenantId, resolvedTenantId),
3977
- eq27(accountsInIam.userId, userId),
3978
- eq27(accountsInIam.provider, "credentials")
4474
+ and29(
4475
+ eq30(accountsInIam.tenantId, resolvedTenantId),
4476
+ eq30(accountsInIam.userId, userId),
4477
+ eq30(accountsInIam.provider, "credentials")
3979
4478
  )
3980
4479
  );
3981
4480
  return c.json({ user: normalizeUser(updatedUser) }, 200);
@@ -4225,41 +4724,41 @@ var assignRolePermissionHandler = async (c) => {
4225
4724
  };
4226
4725
 
4227
4726
  // src/routes/role-permissions/handler/list-role-permissions.ts
4228
- import { and as and28, eq as eq28 } from "drizzle-orm";
4727
+ import { and as and30, eq as eq31 } from "drizzle-orm";
4229
4728
  var listRolePermissionsHandler = async (c) => {
4230
4729
  const query = c.req.valid("query");
4231
4730
  const database = c.get("database");
4232
4731
  const tenantId = c.get("tenantId");
4233
- const conditions = [eq28(rolePermissionsInIam.tenantId, tenantId)];
4732
+ const conditions = [eq31(rolePermissionsInIam.tenantId, tenantId)];
4234
4733
  if (query.roleId) {
4235
- conditions.push(eq28(rolePermissionsInIam.roleId, query.roleId));
4734
+ conditions.push(eq31(rolePermissionsInIam.roleId, query.roleId));
4236
4735
  }
4237
4736
  if (query.permissionId) {
4238
- conditions.push(eq28(rolePermissionsInIam.permissionId, query.permissionId));
4737
+ conditions.push(eq31(rolePermissionsInIam.permissionId, query.permissionId));
4239
4738
  }
4240
- const rolePermissions = await database.select().from(rolePermissionsInIam).where(and28(...conditions));
4739
+ const rolePermissions = await database.select().from(rolePermissionsInIam).where(and30(...conditions));
4241
4740
  return c.json({ rolePermissions }, 200);
4242
4741
  };
4243
4742
 
4244
4743
  // src/routes/role-permissions/handler/revoke-role-permission.ts
4245
- import { and as and29, eq as eq29 } from "drizzle-orm";
4744
+ import { and as and31, eq as eq32 } from "drizzle-orm";
4246
4745
  var revokeRolePermissionHandler = async (c) => {
4247
4746
  const { id } = c.req.valid("param");
4248
4747
  const database = c.get("database");
4249
4748
  const tenantId = c.get("tenantId");
4250
4749
  const [existing] = await database.select().from(rolePermissionsInIam).where(
4251
- and29(
4252
- eq29(rolePermissionsInIam.id, id),
4253
- eq29(rolePermissionsInIam.tenantId, tenantId)
4750
+ and31(
4751
+ eq32(rolePermissionsInIam.id, id),
4752
+ eq32(rolePermissionsInIam.tenantId, tenantId)
4254
4753
  )
4255
4754
  ).limit(1);
4256
4755
  if (!existing) {
4257
4756
  return c.json({ error: "Role permission not found" }, 404);
4258
4757
  }
4259
4758
  await database.delete(rolePermissionsInIam).where(
4260
- and29(
4261
- eq29(rolePermissionsInIam.id, id),
4262
- eq29(rolePermissionsInIam.tenantId, tenantId)
4759
+ and31(
4760
+ eq32(rolePermissionsInIam.id, id),
4761
+ eq32(rolePermissionsInIam.tenantId, tenantId)
4263
4762
  )
4264
4763
  );
4265
4764
  return c.json({ message: "Permission revoked from role" }, 200);
@@ -4383,53 +4882,390 @@ var role_permissions_route_default = rolePermissionRoutes;
4383
4882
  // src/routes/roles/roles.route.ts
4384
4883
  import { createRoute as createRoute9, OpenAPIHono as OpenAPIHono9 } from "@hono/zod-openapi";
4385
4884
 
4885
+ // src/routes/roles/handler/assign-role-permissions.ts
4886
+ import { randomUUID as randomUUID2 } from "crypto";
4887
+ import { and as and32, eq as eq33, inArray as inArray3 } from "drizzle-orm";
4888
+ var assignRolePermissionsHandler = async (c) => {
4889
+ const { id } = c.req.valid("param");
4890
+ const body = c.req.valid("json");
4891
+ const database = c.get("database");
4892
+ const config = c.get("config");
4893
+ const tenantId = c.get("tenantId");
4894
+ const resolvedTenantId = ensureTenantId(config, tenantId);
4895
+ const permissionIds = [...new Set(body.permissionIds)];
4896
+ const [role] = await database.select({
4897
+ id: rolesInIam.id,
4898
+ isEditable: rolesInIam.isEditable
4899
+ }).from(rolesInIam).where(and32(eq33(rolesInIam.id, id), eq33(rolesInIam.tenantId, tenantId))).limit(1);
4900
+ if (!role) {
4901
+ return c.json({ error: "Role not found" }, 404);
4902
+ }
4903
+ if (!role.isEditable) {
4904
+ return c.json({ error: "Role is not editable" }, 403);
4905
+ }
4906
+ await seedPermissions({
4907
+ database,
4908
+ permissions: config.permissions
4909
+ });
4910
+ const existingPermissions = permissionIds.length ? await database.select({ id: permissionsInIam.id }).from(permissionsInIam).where(inArray3(permissionsInIam.id, permissionIds)) : [];
4911
+ const existingPermissionIds = new Set(
4912
+ existingPermissions.map((permission) => permission.id)
4913
+ );
4914
+ const missingPermissionIds = permissionIds.filter(
4915
+ (permissionId) => !existingPermissionIds.has(permissionId)
4916
+ );
4917
+ if (missingPermissionIds.length) {
4918
+ return c.json(
4919
+ { error: `Unknown permissions: ${missingPermissionIds.join(", ")}` },
4920
+ 400
4921
+ );
4922
+ }
4923
+ const created = permissionIds.length ? await database.insert(rolePermissionsInIam).values(
4924
+ permissionIds.map((permissionId) => ({
4925
+ id: randomUUID2(),
4926
+ tenantId: resolvedTenantId,
4927
+ roleId: id,
4928
+ permissionId
4929
+ }))
4930
+ ).onConflictDoNothing({
4931
+ target: [
4932
+ rolePermissionsInIam.tenantId,
4933
+ rolePermissionsInIam.permissionId,
4934
+ rolePermissionsInIam.roleId
4935
+ ]
4936
+ }).returning({ permissionId: rolePermissionsInIam.permissionId }) : [];
4937
+ return c.json(
4938
+ {
4939
+ created: created.length,
4940
+ skipped: permissionIds.length - created.length
4941
+ },
4942
+ 200
4943
+ );
4944
+ };
4945
+
4946
+ // src/routes/roles/handler/assign-role-users.ts
4947
+ import { randomUUID as randomUUID3 } from "crypto";
4948
+ import { and as and33, eq as eq34, inArray as inArray4 } from "drizzle-orm";
4949
+ var assignRoleUsersHandler = async (c) => {
4950
+ const { id } = c.req.valid("param");
4951
+ const body = c.req.valid("json");
4952
+ const database = c.get("database");
4953
+ const config = c.get("config");
4954
+ const tenantId = c.get("tenantId");
4955
+ const resolvedTenantId = ensureTenantId(config, tenantId);
4956
+ const userIds = [...new Set(body.userIds)];
4957
+ const [role] = await database.select({
4958
+ id: rolesInIam.id,
4959
+ isEditable: rolesInIam.isEditable
4960
+ }).from(rolesInIam).where(and33(eq34(rolesInIam.id, id), eq34(rolesInIam.tenantId, tenantId))).limit(1);
4961
+ if (!role) {
4962
+ return c.json({ error: "Role not found" }, 404);
4963
+ }
4964
+ if (!role.isEditable) {
4965
+ return c.json({ error: "Role is not editable" }, 403);
4966
+ }
4967
+ const existingUsers = userIds.length ? await database.select({ id: usersInIam.id }).from(usersInIam).where(
4968
+ and33(
4969
+ eq34(usersInIam.tenantId, tenantId),
4970
+ inArray4(usersInIam.id, userIds)
4971
+ )
4972
+ ) : [];
4973
+ const existingUserIds = new Set(existingUsers.map((user) => user.id));
4974
+ const missingUserIds = userIds.filter(
4975
+ (userId) => !existingUserIds.has(userId)
4976
+ );
4977
+ if (missingUserIds.length) {
4978
+ return c.json(
4979
+ { error: `Unknown users: ${missingUserIds.join(", ")}` },
4980
+ 400
4981
+ );
4982
+ }
4983
+ const created = userIds.length ? await database.insert(userRolesInIam).values(
4984
+ userIds.map((userId) => ({
4985
+ id: randomUUID3(),
4986
+ tenantId: resolvedTenantId,
4987
+ roleId: id,
4988
+ userId
4989
+ }))
4990
+ ).onConflictDoNothing({
4991
+ target: [
4992
+ userRolesInIam.tenantId,
4993
+ userRolesInIam.userId,
4994
+ userRolesInIam.roleId
4995
+ ]
4996
+ }).returning({ userId: userRolesInIam.userId }) : [];
4997
+ return c.json(
4998
+ {
4999
+ created: created.length,
5000
+ skipped: userIds.length - created.length
5001
+ },
5002
+ 200
5003
+ );
5004
+ };
5005
+
4386
5006
  // src/routes/roles/handler/create-role.ts
4387
- import { randomUUID } from "crypto";
5007
+ import { randomUUID as randomUUID4 } from "crypto";
4388
5008
  var createRoleHandler = async (c) => {
4389
5009
  const body = c.req.valid("json");
4390
5010
  const config = c.get("config");
4391
5011
  const database = c.get("database");
4392
5012
  const tenantId = c.get("tenantId");
4393
5013
  const resolvedTenantId = ensureTenantId(config, tenantId);
4394
- const [role] = await database.insert(rolesInIam).values({
4395
- id: randomUUID(),
4396
- tenantId: resolvedTenantId,
4397
- name: body.name,
4398
- description: body.description,
4399
- code: body.code
4400
- }).returning();
4401
- return c.json({ role }, 201);
5014
+ const permissionIds = [...new Set(body.permissionIds ?? [])];
5015
+ const role = await withTransaction(database, async (tx) => {
5016
+ await seedPermissions({
5017
+ database: tx,
5018
+ permissions: config.permissions
5019
+ });
5020
+ const [createdRole] = await tx.insert(rolesInIam).values({
5021
+ id: randomUUID4(),
5022
+ tenantId: resolvedTenantId,
5023
+ name: body.name ?? { en: body.code },
5024
+ description: body.description ?? { en: body.code },
5025
+ code: body.code,
5026
+ isSystem: body.isSystem ?? false,
5027
+ isEditable: body.isEditable ?? true,
5028
+ isDeletable: body.isDeletable ?? true
5029
+ }).returning();
5030
+ if (body.permissionIds !== void 0) {
5031
+ await syncRolePermissions({
5032
+ database: tx,
5033
+ tenantId: resolvedTenantId,
5034
+ roleId: createdRole.id,
5035
+ permissionIds
5036
+ });
5037
+ }
5038
+ return createdRole;
5039
+ });
5040
+ return c.json(
5041
+ {
5042
+ role: {
5043
+ ...role,
5044
+ permissionIds,
5045
+ permissionCount: permissionIds.length
5046
+ }
5047
+ },
5048
+ 201
5049
+ );
4402
5050
  };
4403
5051
 
4404
5052
  // src/routes/roles/handler/delete-role.ts
4405
- import { and as and30, eq as eq30 } from "drizzle-orm";
5053
+ import { and as and34, eq as eq35 } from "drizzle-orm";
4406
5054
  var deleteRoleHandler = async (c) => {
4407
5055
  const { id } = c.req.valid("param");
4408
5056
  const database = c.get("database");
4409
5057
  const tenantId = c.get("tenantId");
4410
- const [existing] = await database.select().from(rolesInIam).where(and30(eq30(rolesInIam.id, id), eq30(rolesInIam.tenantId, tenantId))).limit(1);
5058
+ const [existing] = await database.select().from(rolesInIam).where(and34(eq35(rolesInIam.id, id), eq35(rolesInIam.tenantId, tenantId))).limit(1);
4411
5059
  if (!existing) {
4412
5060
  return c.json({ error: "Role not found" }, 404);
4413
5061
  }
4414
- await database.delete(rolesInIam).where(and30(eq30(rolesInIam.id, id), eq30(rolesInIam.tenantId, tenantId)));
5062
+ if (!existing.isDeletable) {
5063
+ return c.json({ error: "Role is not deletable" }, 403);
5064
+ }
5065
+ await database.delete(rolesInIam).where(and34(eq35(rolesInIam.id, id), eq35(rolesInIam.tenantId, tenantId)));
4415
5066
  return c.json({ message: "Role deleted" }, 200);
4416
5067
  };
4417
5068
 
4418
5069
  // src/routes/roles/handler/get-role.ts
4419
- import { and as and31, eq as eq31 } from "drizzle-orm";
5070
+ import { and as and35, eq as eq36, sql as sql17 } from "drizzle-orm";
4420
5071
  var getRoleHandler = async (c) => {
4421
5072
  const { id } = c.req.valid("param");
4422
5073
  const database = c.get("database");
4423
5074
  const tenantId = c.get("tenantId");
4424
- const [role] = await database.select().from(rolesInIam).where(and31(eq31(rolesInIam.id, id), eq31(rolesInIam.tenantId, tenantId))).limit(1);
5075
+ const [role] = await database.select({
5076
+ id: rolesInIam.id,
5077
+ tenantId: rolesInIam.tenantId,
5078
+ createdAt: rolesInIam.createdAt,
5079
+ updatedAt: rolesInIam.updatedAt,
5080
+ name: rolesInIam.name,
5081
+ description: rolesInIam.description,
5082
+ code: rolesInIam.code,
5083
+ isSystem: rolesInIam.isSystem,
5084
+ isEditable: rolesInIam.isEditable,
5085
+ isDeletable: rolesInIam.isDeletable,
5086
+ permissionIds: sql17`COALESCE(
5087
+ (
5088
+ select array_to_json(array_agg(${rolePermissionsInIam.permissionId}))
5089
+ from ${rolePermissionsInIam}
5090
+ where ${and35(
5091
+ eq36(rolePermissionsInIam.tenantId, tenantId),
5092
+ eq36(rolePermissionsInIam.roleId, rolesInIam.id)
5093
+ )}
5094
+ ),
5095
+ '[]'::json
5096
+ )`,
5097
+ permissionCount: sql17`(
5098
+ select count(*)::int
5099
+ from ${rolePermissionsInIam}
5100
+ where ${and35(
5101
+ eq36(rolePermissionsInIam.tenantId, tenantId),
5102
+ eq36(rolePermissionsInIam.roleId, rolesInIam.id)
5103
+ )}
5104
+ )`,
5105
+ userCount: sql17`(
5106
+ select count(*)::int
5107
+ from ${userRolesInIam}
5108
+ where ${and35(
5109
+ eq36(userRolesInIam.tenantId, tenantId),
5110
+ eq36(userRolesInIam.roleId, rolesInIam.id)
5111
+ )}
5112
+ )`
5113
+ }).from(rolesInIam).where(and35(eq36(rolesInIam.id, id), eq36(rolesInIam.tenantId, tenantId))).limit(1);
5114
+ if (!role) {
5115
+ return c.json({ error: "Role not found" }, 404);
5116
+ }
5117
+ return c.json(
5118
+ {
5119
+ role: {
5120
+ ...role,
5121
+ permissionIds: role.permissionIds ?? void 0
5122
+ }
5123
+ },
5124
+ 200
5125
+ );
5126
+ };
5127
+
5128
+ // src/routes/roles/handler/list-role-permissions.ts
5129
+ import { and as and36, eq as eq37 } from "drizzle-orm";
5130
+ var listRolePermissionsHandler2 = async (c) => {
5131
+ const { id } = c.req.valid("param");
5132
+ const query = c.req.valid("query");
5133
+ const database = c.get("database");
5134
+ const config = c.get("config");
5135
+ const tenantId = c.get("tenantId");
5136
+ const [role] = await database.select({ id: rolesInIam.id }).from(rolesInIam).where(and36(eq37(rolesInIam.id, id), eq37(rolesInIam.tenantId, tenantId))).limit(1);
5137
+ if (!role) {
5138
+ return c.json({ error: "Role not found" }, 404);
5139
+ }
5140
+ const page = query.page || 1;
5141
+ const limit = query.limit || 20;
5142
+ const offset = (page - 1) * limit;
5143
+ const [assignedPermissions, permissionCatalog] = await Promise.all([
5144
+ database.select({ permissionId: rolePermissionsInIam.permissionId }).from(rolePermissionsInIam).where(
5145
+ and36(
5146
+ eq37(rolePermissionsInIam.tenantId, tenantId),
5147
+ eq37(rolePermissionsInIam.roleId, id)
5148
+ )
5149
+ ).then((rows) => new Set(rows.map((row) => row.permissionId))),
5150
+ getMergedPermissionCatalog({
5151
+ database,
5152
+ permissions: config.permissions
5153
+ })
5154
+ ]);
5155
+ const sortedPermissions = filterAndSortPermissions(
5156
+ permissionCatalog.filter(
5157
+ (permission) => assignedPermissions.has(permission.id)
5158
+ ),
5159
+ query
5160
+ );
5161
+ const total = sortedPermissions.length;
5162
+ const permissions = sortedPermissions.slice(offset, offset + limit);
5163
+ return c.json({ permissions, total, page, limit }, 200);
5164
+ };
5165
+
5166
+ // src/routes/roles/handler/list-role-users.ts
5167
+ import { and as and37, asc as asc2, desc as desc2, eq as eq38, ilike, or, sql as sql18 } from "drizzle-orm";
5168
+ var sortColumnMap = {
5169
+ createdAt: usersInIam.createdAt,
5170
+ updatedAt: usersInIam.updatedAt,
5171
+ fullName: usersInIam.fullName,
5172
+ handle: usersInIam.handle,
5173
+ lastSignInAt: usersInIam.lastSignInAt
5174
+ };
5175
+ function getRoleUserSearchCondition(search, filter) {
5176
+ const term = `%${search}%`;
5177
+ if (filter === "fullName") {
5178
+ return ilike(usersInIam.fullName, term);
5179
+ }
5180
+ if (filter === "email") {
5181
+ return ilike(usersInIam.email, term);
5182
+ }
5183
+ if (filter === "phone") {
5184
+ return ilike(usersInIam.phone, term);
5185
+ }
5186
+ if (filter === "handle") {
5187
+ return ilike(usersInIam.handle, term);
5188
+ }
5189
+ return or(
5190
+ ilike(usersInIam.fullName, term),
5191
+ ilike(usersInIam.email, term),
5192
+ ilike(usersInIam.phone, term),
5193
+ ilike(usersInIam.handle, term)
5194
+ );
5195
+ }
5196
+ async function ensureRoleExists({
5197
+ database,
5198
+ id,
5199
+ tenantId
5200
+ }) {
5201
+ const [role] = await database.select({ id: rolesInIam.id }).from(rolesInIam).where(and37(eq38(rolesInIam.id, id), eq38(rolesInIam.tenantId, tenantId))).limit(1);
5202
+ return role;
5203
+ }
5204
+ var listRoleUsersHandler = async (c) => {
5205
+ const { id } = c.req.valid("param");
5206
+ const query = c.req.valid("query");
5207
+ const database = c.get("database");
5208
+ const tenantId = c.get("tenantId");
5209
+ const role = await ensureRoleExists({ database, id, tenantId });
4425
5210
  if (!role) {
4426
5211
  return c.json({ error: "Role not found" }, 404);
4427
5212
  }
4428
- return c.json({ role }, 200);
5213
+ const page = query.page || 1;
5214
+ const limit = query.limit || 20;
5215
+ const offset = (page - 1) * limit;
5216
+ const conditions = [
5217
+ eq38(userRolesInIam.tenantId, tenantId),
5218
+ eq38(userRolesInIam.roleId, id),
5219
+ eq38(usersInIam.tenantId, tenantId)
5220
+ ];
5221
+ if (query.search) {
5222
+ const searchCondition = getRoleUserSearchCondition(
5223
+ query.search,
5224
+ query.filter
5225
+ );
5226
+ if (searchCondition) {
5227
+ conditions.push(searchCondition);
5228
+ }
5229
+ }
5230
+ const orderDir = query.order === "asc" ? asc2 : desc2;
5231
+ const sortCol = query.sort && sortColumnMap[query.sort] ? sortColumnMap[query.sort] : usersInIam.createdAt;
5232
+ const [users, totalResult] = await Promise.all([
5233
+ database.select({
5234
+ id: usersInIam.id,
5235
+ tenantId: usersInIam.tenantId,
5236
+ fullName: usersInIam.fullName,
5237
+ email: usersInIam.email,
5238
+ phone: usersInIam.phone,
5239
+ handle: usersInIam.handle,
5240
+ image: usersInIam.image,
5241
+ emailVerified: usersInIam.emailVerified,
5242
+ phoneVerified: usersInIam.phoneVerified,
5243
+ lastSignInAt: usersInIam.lastSignInAt
5244
+ }).from(userRolesInIam).innerJoin(usersInIam, eq38(usersInIam.id, userRolesInIam.userId)).where(and37(...conditions)).orderBy(orderDir(sortCol)).limit(limit).offset(offset),
5245
+ database.select({ count: sql18`count(*)` }).from(userRolesInIam).innerJoin(usersInIam, eq38(usersInIam.id, userRolesInIam.userId)).where(and37(...conditions))
5246
+ ]);
5247
+ const total = Number(totalResult[0]?.count || 0);
5248
+ return c.json(
5249
+ {
5250
+ users: users.map((user) => ({
5251
+ ...user,
5252
+ roles: null
5253
+ })),
5254
+ total,
5255
+ page,
5256
+ limit
5257
+ },
5258
+ 200
5259
+ );
4429
5260
  };
4430
5261
 
4431
5262
  // src/routes/roles/handler/list-roles.ts
4432
- import { and as and32, eq as eq32, sql as sql17 } from "drizzle-orm";
5263
+ import { and as and38, asc as asc3, desc as desc3, eq as eq39, ilike as ilike2, or as or2, sql as sql19 } from "drizzle-orm";
5264
+ var sortColumnMap2 = {
5265
+ createdAt: rolesInIam.createdAt,
5266
+ updatedAt: rolesInIam.updatedAt,
5267
+ code: rolesInIam.code
5268
+ };
4433
5269
  var listRolesHandler = async (c) => {
4434
5270
  const query = c.req.valid("query");
4435
5271
  const database = c.get("database");
@@ -4437,64 +5273,281 @@ var listRolesHandler = async (c) => {
4437
5273
  const page = query.page || 1;
4438
5274
  const limit = query.limit || 20;
4439
5275
  const offset = (page - 1) * limit;
4440
- const conditions = [eq32(rolesInIam.tenantId, tenantId)];
5276
+ const conditions = [eq39(rolesInIam.tenantId, tenantId)];
5277
+ if (query.search) {
5278
+ const term = `%${query.search}%`;
5279
+ const searchCondition = query.filter === "code" ? ilike2(rolesInIam.code, term) : or2(
5280
+ ilike2(rolesInIam.code, term),
5281
+ sql19`cast(${rolesInIam.name} as text) ilike ${term}`,
5282
+ sql19`cast(${rolesInIam.description} as text) ilike ${term}`
5283
+ );
5284
+ if (searchCondition) {
5285
+ conditions.push(searchCondition);
5286
+ }
5287
+ }
5288
+ const orderDir = query.order === "asc" ? asc3 : desc3;
5289
+ const sortCol = query.sort && sortColumnMap2[query.sort] ? sortColumnMap2[query.sort] : rolesInIam.createdAt;
4441
5290
  const [roles, totalResult] = await Promise.all([
4442
- database.select().from(rolesInIam).where(and32(...conditions)).limit(limit).offset(offset),
4443
- database.select({ count: sql17`count(*)` }).from(rolesInIam).where(and32(...conditions))
5291
+ database.select({
5292
+ id: rolesInIam.id,
5293
+ tenantId: rolesInIam.tenantId,
5294
+ createdAt: rolesInIam.createdAt,
5295
+ updatedAt: rolesInIam.updatedAt,
5296
+ name: rolesInIam.name,
5297
+ description: rolesInIam.description,
5298
+ code: rolesInIam.code,
5299
+ isSystem: rolesInIam.isSystem,
5300
+ isEditable: rolesInIam.isEditable,
5301
+ isDeletable: rolesInIam.isDeletable,
5302
+ permissionCount: sql19`(
5303
+ select count(*)::int
5304
+ from ${rolePermissionsInIam}
5305
+ where ${and38(
5306
+ eq39(rolePermissionsInIam.tenantId, tenantId),
5307
+ eq39(rolePermissionsInIam.roleId, rolesInIam.id)
5308
+ )}
5309
+ )`,
5310
+ userCount: sql19`(
5311
+ select count(*)::int
5312
+ from ${userRolesInIam}
5313
+ where ${and38(
5314
+ eq39(userRolesInIam.tenantId, tenantId),
5315
+ eq39(userRolesInIam.roleId, rolesInIam.id)
5316
+ )}
5317
+ )`
5318
+ }).from(rolesInIam).where(and38(...conditions)).orderBy(orderDir(sortCol)).limit(limit).offset(offset),
5319
+ database.select({ count: sql19`count(*)` }).from(rolesInIam).where(and38(...conditions))
4444
5320
  ]);
4445
5321
  const total = Number(totalResult[0]?.count || 0);
4446
5322
  return c.json({ roles, total, page, limit });
4447
5323
  };
4448
5324
 
4449
- // src/routes/roles/handler/update-role.ts
4450
- import { and as and33, eq as eq33, sql as sql18 } from "drizzle-orm";
4451
- var updateRoleHandler = async (c) => {
4452
- const { id } = c.req.valid("param");
4453
- const body = c.req.valid("json");
5325
+ // src/routes/roles/handler/revoke-role-permission.ts
5326
+ import { and as and39, eq as eq40 } from "drizzle-orm";
5327
+ var revokeRolePermissionHandler2 = async (c) => {
5328
+ const { id, permissionId } = c.req.valid("param");
4454
5329
  const database = c.get("database");
4455
5330
  const tenantId = c.get("tenantId");
4456
- const [existing] = await database.select().from(rolesInIam).where(and33(eq33(rolesInIam.id, id), eq33(rolesInIam.tenantId, tenantId))).limit(1);
4457
- if (!existing) {
5331
+ const [role] = await database.select({
5332
+ id: rolesInIam.id,
5333
+ isEditable: rolesInIam.isEditable
5334
+ }).from(rolesInIam).where(and39(eq40(rolesInIam.id, id), eq40(rolesInIam.tenantId, tenantId))).limit(1);
5335
+ if (!role) {
4458
5336
  return c.json({ error: "Role not found" }, 404);
4459
5337
  }
4460
- const updateData = {};
4461
- if (body.name !== void 0) {
4462
- updateData.name = body.name;
4463
- }
4464
- if (body.description !== void 0) {
4465
- updateData.description = body.description;
5338
+ if (!role.isEditable) {
5339
+ return c.json({ error: "Role is not editable" }, 403);
4466
5340
  }
4467
- if (body.code !== void 0) {
4468
- updateData.code = body.code;
5341
+ const [deleted] = await database.delete(rolePermissionsInIam).where(
5342
+ and39(
5343
+ eq40(rolePermissionsInIam.tenantId, tenantId),
5344
+ eq40(rolePermissionsInIam.roleId, id),
5345
+ eq40(rolePermissionsInIam.permissionId, permissionId)
5346
+ )
5347
+ ).returning({ id: rolePermissionsInIam.id });
5348
+ if (!deleted) {
5349
+ return c.json({ error: "Role permission not found" }, 404);
4469
5350
  }
4470
- const [updated] = await database.update(rolesInIam).set({
4471
- ...updateData,
4472
- updatedAt: sql18`CURRENT_TIMESTAMP`
4473
- }).where(and33(eq33(rolesInIam.id, id), eq33(rolesInIam.tenantId, tenantId))).returning();
4474
- if (!updated) {
5351
+ return c.json({ message: "Permission revoked from role" }, 200);
5352
+ };
5353
+
5354
+ // src/routes/roles/handler/revoke-role-user.ts
5355
+ import { and as and40, eq as eq41 } from "drizzle-orm";
5356
+ var revokeRoleUserHandler = async (c) => {
5357
+ const { id, userId } = c.req.valid("param");
5358
+ const database = c.get("database");
5359
+ const tenantId = c.get("tenantId");
5360
+ const [role] = await database.select({
5361
+ id: rolesInIam.id,
5362
+ isEditable: rolesInIam.isEditable
5363
+ }).from(rolesInIam).where(and40(eq41(rolesInIam.id, id), eq41(rolesInIam.tenantId, tenantId))).limit(1);
5364
+ if (!role) {
4475
5365
  return c.json({ error: "Role not found" }, 404);
4476
5366
  }
4477
- return c.json({ role: updated }, 200);
5367
+ if (!role.isEditable) {
5368
+ return c.json({ error: "Role is not editable" }, 403);
5369
+ }
5370
+ const [deleted] = await database.delete(userRolesInIam).where(
5371
+ and40(
5372
+ eq41(userRolesInIam.tenantId, tenantId),
5373
+ eq41(userRolesInIam.roleId, id),
5374
+ eq41(userRolesInIam.userId, userId)
5375
+ )
5376
+ ).returning({ id: userRolesInIam.id });
5377
+ if (!deleted) {
5378
+ return c.json({ error: "Role user not found" }, 404);
5379
+ }
5380
+ return c.json({ message: "User removed from role" }, 200);
4478
5381
  };
4479
5382
 
4480
- // src/routes/roles/roles.schema.ts
4481
- import { z as z6 } from "zod";
4482
- var listRolesQuerySchema = z6.object({
4483
- page: z6.coerce.number().min(1).default(1).optional(),
4484
- limit: z6.coerce.number().min(1).max(100).default(20).optional()
4485
- });
4486
- var roleIdParamSchema = z6.object({
4487
- id: z6.uuid()
5383
+ // src/routes/roles/handler/seed-roles.ts
5384
+ import { and as and41, eq as eq42, inArray as inArray5, sql as sql20 } from "drizzle-orm";
5385
+ var seedRolesHandler = async (c) => {
5386
+ const config = c.get("config");
5387
+ const database = c.get("database");
5388
+ const tenantId = c.get("tenantId");
5389
+ const resolvedTenantId = ensureTenantId(config, tenantId);
5390
+ if (!config.roles?.length) {
5391
+ return c.json({ error: "No roles configured for seeding" }, 400);
5392
+ }
5393
+ const rolesToSeed = config.roles;
5394
+ const seededRoles = await withTransaction(database, async (tx) => {
5395
+ await seedPermissions({
5396
+ database: tx,
5397
+ permissions: config.permissions
5398
+ });
5399
+ return seedRoles({
5400
+ database: tx,
5401
+ tenantId: resolvedTenantId,
5402
+ roles: rolesToSeed
5403
+ });
5404
+ });
5405
+ const permissionCountRows = seededRoles.length ? await database.select({
5406
+ roleId: rolePermissionsInIam.roleId,
5407
+ count: sql20`count(*)::int`
5408
+ }).from(rolePermissionsInIam).where(
5409
+ and41(
5410
+ eq42(rolePermissionsInIam.tenantId, resolvedTenantId),
5411
+ inArray5(
5412
+ rolePermissionsInIam.roleId,
5413
+ seededRoles.map((role) => role.id)
5414
+ )
5415
+ )
5416
+ ).groupBy(rolePermissionsInIam.roleId) : [];
5417
+ const permissionCountByRoleId = new Map(
5418
+ permissionCountRows.map((row) => [row.roleId, row.count])
5419
+ );
5420
+ const roles = seededRoles.map((role) => ({
5421
+ ...role,
5422
+ permissionCount: permissionCountByRoleId.get(role.id) ?? 0
5423
+ }));
5424
+ return c.json(
5425
+ {
5426
+ roles,
5427
+ total: roles.length
5428
+ },
5429
+ 200
5430
+ );
5431
+ };
5432
+
5433
+ // src/routes/roles/handler/update-role.ts
5434
+ import { and as and42, eq as eq43, sql as sql21 } from "drizzle-orm";
5435
+ var updateRoleHandler = async (c) => {
5436
+ const { id } = c.req.valid("param");
5437
+ const body = c.req.valid("json");
5438
+ const database = c.get("database");
5439
+ const config = c.get("config");
5440
+ const tenantId = c.get("tenantId");
5441
+ const permissionIds = body.permissionIds === void 0 ? void 0 : [...new Set(body.permissionIds)];
5442
+ const [existing] = await database.select().from(rolesInIam).where(and42(eq43(rolesInIam.id, id), eq43(rolesInIam.tenantId, tenantId))).limit(1);
5443
+ if (!existing) {
5444
+ return c.json({ error: "Role not found" }, 404);
5445
+ }
5446
+ if (!existing.isEditable) {
5447
+ return c.json({ error: "Role is not editable" }, 403);
5448
+ }
5449
+ const updateData = {};
5450
+ if (body.name !== void 0) {
5451
+ updateData.name = body.name;
5452
+ }
5453
+ if (body.description !== void 0) {
5454
+ updateData.description = body.description;
5455
+ }
5456
+ if (body.code !== void 0) {
5457
+ updateData.code = body.code;
5458
+ }
5459
+ if (body.isSystem !== void 0) {
5460
+ updateData.isSystem = body.isSystem;
5461
+ }
5462
+ if (body.isEditable !== void 0) {
5463
+ updateData.isEditable = body.isEditable;
5464
+ }
5465
+ if (body.isDeletable !== void 0) {
5466
+ updateData.isDeletable = body.isDeletable;
5467
+ }
5468
+ const updated = await withTransaction(database, async (tx) => {
5469
+ await seedPermissions({
5470
+ database: tx,
5471
+ permissions: config.permissions
5472
+ });
5473
+ const [role] = await tx.update(rolesInIam).set({
5474
+ ...updateData,
5475
+ updatedAt: sql21`CURRENT_TIMESTAMP`
5476
+ }).where(and42(eq43(rolesInIam.id, id), eq43(rolesInIam.tenantId, tenantId))).returning();
5477
+ if (!role) {
5478
+ return null;
5479
+ }
5480
+ if (permissionIds !== void 0) {
5481
+ await syncRolePermissions({
5482
+ database: tx,
5483
+ tenantId,
5484
+ roleId: role.id,
5485
+ permissionIds
5486
+ });
5487
+ }
5488
+ return role;
5489
+ });
5490
+ if (!updated) {
5491
+ return c.json({ error: "Role not found" }, 404);
5492
+ }
5493
+ return c.json(
5494
+ {
5495
+ role: {
5496
+ ...updated,
5497
+ permissionIds,
5498
+ permissionCount: permissionIds?.length
5499
+ }
5500
+ },
5501
+ 200
5502
+ );
5503
+ };
5504
+
5505
+ // src/routes/roles/roles.schema.ts
5506
+ import { z as z6 } from "zod";
5507
+ var roleFilterValues = ["", "code"];
5508
+ var roleSortValues = ["createdAt", "updatedAt", "code"];
5509
+ var listRolesQuerySchema = z6.object({
5510
+ page: z6.coerce.number().min(1).default(1).optional(),
5511
+ limit: z6.coerce.number().min(1).max(100).default(20).optional(),
5512
+ search: z6.string().optional(),
5513
+ filter: z6.enum(roleFilterValues).optional(),
5514
+ sort: z6.enum(roleSortValues).optional(),
5515
+ order: z6.enum(["asc", "desc"]).optional()
5516
+ });
5517
+ var roleIdParamSchema = z6.object({
5518
+ id: z6.uuid()
4488
5519
  });
4489
5520
  var createRoleSchema = z6.object({
4490
- name: z6.unknown(),
4491
- description: z6.unknown(),
4492
- code: z6.string()
5521
+ name: z6.unknown().optional(),
5522
+ description: z6.unknown().optional(),
5523
+ code: z6.string(),
5524
+ permissionIds: z6.array(z6.string()).optional(),
5525
+ isSystem: z6.boolean().optional(),
5526
+ isEditable: z6.boolean().optional(),
5527
+ isDeletable: z6.boolean().optional()
4493
5528
  });
4494
5529
  var updateRoleSchema = z6.object({
4495
5530
  name: z6.unknown().optional(),
4496
5531
  description: z6.unknown().optional(),
4497
- code: z6.string().optional()
5532
+ code: z6.string().optional(),
5533
+ permissionIds: z6.array(z6.string()).optional(),
5534
+ isSystem: z6.boolean().optional(),
5535
+ isEditable: z6.boolean().optional(),
5536
+ isDeletable: z6.boolean().optional()
5537
+ });
5538
+ var assignRolePermissionsSchema = z6.object({
5539
+ permissionIds: z6.array(z6.string()).min(1)
5540
+ });
5541
+ var assignRoleUsersSchema = z6.object({
5542
+ userIds: z6.array(z6.uuid()).min(1)
5543
+ });
5544
+ var rolePermissionParamSchema = z6.object({
5545
+ id: z6.uuid(),
5546
+ permissionId: z6.string()
5547
+ });
5548
+ var roleUserParamSchema = z6.object({
5549
+ id: z6.uuid(),
5550
+ userId: z6.uuid()
4498
5551
  });
4499
5552
  var roleSchema = z6.object({
4500
5553
  id: z6.uuid(),
@@ -4503,7 +5556,13 @@ var roleSchema = z6.object({
4503
5556
  updatedAt: z6.string(),
4504
5557
  name: z6.unknown(),
4505
5558
  description: z6.unknown(),
4506
- code: z6.string()
5559
+ code: z6.string(),
5560
+ isSystem: z6.boolean(),
5561
+ isEditable: z6.boolean(),
5562
+ isDeletable: z6.boolean(),
5563
+ permissionIds: z6.array(z6.string()).optional(),
5564
+ permissionCount: z6.number().optional(),
5565
+ userCount: z6.number().optional()
4507
5566
  });
4508
5567
  var listRolesResponseSchema = z6.object({
4509
5568
  roles: z6.array(roleSchema),
@@ -4514,12 +5573,45 @@ var listRolesResponseSchema = z6.object({
4514
5573
  var roleResponseSchema = z6.object({
4515
5574
  role: roleSchema
4516
5575
  });
5576
+ var listRolePermissionsResponseSchema2 = z6.object({
5577
+ permissions: z6.array(permissionSchema),
5578
+ total: z6.number(),
5579
+ page: z6.number(),
5580
+ limit: z6.number()
5581
+ });
5582
+ var assignRolePermissionsResponseSchema = z6.object({
5583
+ created: z6.number(),
5584
+ skipped: z6.number()
5585
+ });
5586
+ var listRoleUsersQuerySchema = z6.object({
5587
+ page: z6.coerce.number().min(1).default(1).optional(),
5588
+ limit: z6.coerce.number().min(1).max(100).default(20).optional(),
5589
+ search: z6.string().optional(),
5590
+ filter: z6.enum(["", "fullName", "email", "phone", "handle"]).optional(),
5591
+ sort: z6.enum(["createdAt", "updatedAt", "fullName", "handle", "lastSignInAt"]).optional(),
5592
+ order: z6.enum(["asc", "desc"]).optional()
5593
+ });
5594
+ var listRoleUsersResponseSchema = z6.object({
5595
+ users: z6.array(userSchema),
5596
+ total: z6.number(),
5597
+ page: z6.number(),
5598
+ limit: z6.number()
5599
+ });
5600
+ var assignRoleUsersResponseSchema = z6.object({
5601
+ created: z6.number(),
5602
+ skipped: z6.number()
5603
+ });
4517
5604
  var deleteRoleResponseSchema = z6.object({
4518
5605
  message: z6.string()
4519
5606
  });
5607
+ var seedRolesResponseSchema = z6.object({
5608
+ roles: z6.array(roleSchema),
5609
+ total: z6.number()
5610
+ });
4520
5611
  var errorResponseSchema5 = z6.object({
4521
5612
  error: z6.string()
4522
5613
  });
5614
+ var listRolePermissionsQuerySchema2 = listPermissionsQuerySchema;
4523
5615
 
4524
5616
  // src/routes/roles/roles.route.ts
4525
5617
  var listRolesRoute = createRoute9({
@@ -4537,26 +5629,258 @@ var listRolesRoute = createRoute9({
4537
5629
  schema: listRolesResponseSchema
4538
5630
  }
4539
5631
  },
4540
- description: "List of roles"
5632
+ description: "List of roles"
5633
+ }
5634
+ }
5635
+ });
5636
+ var getRoleRoute = createRoute9({
5637
+ method: "get",
5638
+ path: "/{id}",
5639
+ tags: ["Roles"],
5640
+ summary: "Get role by ID",
5641
+ request: {
5642
+ params: roleIdParamSchema
5643
+ },
5644
+ responses: {
5645
+ 200: {
5646
+ content: {
5647
+ "application/json": {
5648
+ schema: roleResponseSchema
5649
+ }
5650
+ },
5651
+ description: "Role details"
5652
+ },
5653
+ 404: {
5654
+ content: {
5655
+ "application/json": {
5656
+ schema: errorResponseSchema5
5657
+ }
5658
+ },
5659
+ description: "Role not found"
5660
+ }
5661
+ }
5662
+ });
5663
+ var createRoleRoute = createRoute9({
5664
+ method: "post",
5665
+ path: "/",
5666
+ tags: ["Roles"],
5667
+ summary: "Create role",
5668
+ request: {
5669
+ body: {
5670
+ content: {
5671
+ "application/json": {
5672
+ schema: createRoleSchema
5673
+ }
5674
+ }
5675
+ }
5676
+ },
5677
+ responses: {
5678
+ 201: {
5679
+ content: {
5680
+ "application/json": {
5681
+ schema: roleResponseSchema
5682
+ }
5683
+ },
5684
+ description: "Role created"
5685
+ },
5686
+ 400: {
5687
+ content: {
5688
+ "application/json": {
5689
+ schema: errorResponseSchema5
5690
+ }
5691
+ },
5692
+ description: "Invalid permission assignment"
5693
+ },
5694
+ 409: {
5695
+ content: {
5696
+ "application/json": {
5697
+ schema: errorResponseSchema5
5698
+ }
5699
+ },
5700
+ description: "Role code already exists"
5701
+ }
5702
+ }
5703
+ });
5704
+ var updateRoleRoute = createRoute9({
5705
+ method: "put",
5706
+ path: "/{id}",
5707
+ tags: ["Roles"],
5708
+ summary: "Update role",
5709
+ request: {
5710
+ params: roleIdParamSchema,
5711
+ body: {
5712
+ content: {
5713
+ "application/json": {
5714
+ schema: updateRoleSchema
5715
+ }
5716
+ }
5717
+ }
5718
+ },
5719
+ responses: {
5720
+ 200: {
5721
+ content: {
5722
+ "application/json": {
5723
+ schema: roleResponseSchema
5724
+ }
5725
+ },
5726
+ description: "Role updated"
5727
+ },
5728
+ 400: {
5729
+ content: {
5730
+ "application/json": {
5731
+ schema: errorResponseSchema5
5732
+ }
5733
+ },
5734
+ description: "Invalid permission assignment"
5735
+ },
5736
+ 403: {
5737
+ content: {
5738
+ "application/json": {
5739
+ schema: errorResponseSchema5
5740
+ }
5741
+ },
5742
+ description: "Role is not editable"
5743
+ },
5744
+ 404: {
5745
+ content: {
5746
+ "application/json": {
5747
+ schema: errorResponseSchema5
5748
+ }
5749
+ },
5750
+ description: "Role not found"
5751
+ }
5752
+ }
5753
+ });
5754
+ var deleteRoleRoute = createRoute9({
5755
+ method: "delete",
5756
+ path: "/{id}",
5757
+ tags: ["Roles"],
5758
+ summary: "Delete role",
5759
+ request: {
5760
+ params: roleIdParamSchema
5761
+ },
5762
+ responses: {
5763
+ 200: {
5764
+ content: {
5765
+ "application/json": {
5766
+ schema: deleteRoleResponseSchema
5767
+ }
5768
+ },
5769
+ description: "Role deleted"
5770
+ },
5771
+ 403: {
5772
+ content: {
5773
+ "application/json": {
5774
+ schema: errorResponseSchema5
5775
+ }
5776
+ },
5777
+ description: "Role is not deletable"
5778
+ },
5779
+ 404: {
5780
+ content: {
5781
+ "application/json": {
5782
+ schema: errorResponseSchema5
5783
+ }
5784
+ },
5785
+ description: "Role not found"
5786
+ }
5787
+ }
5788
+ });
5789
+ var listRolePermissionsRoute2 = createRoute9({
5790
+ method: "get",
5791
+ path: "/{id}/permissions",
5792
+ tags: ["Roles"],
5793
+ summary: "List permissions assigned to a role",
5794
+ request: {
5795
+ params: roleIdParamSchema,
5796
+ query: listRolePermissionsQuerySchema2
5797
+ },
5798
+ responses: {
5799
+ 200: {
5800
+ content: {
5801
+ "application/json": {
5802
+ schema: listRolePermissionsResponseSchema2
5803
+ }
5804
+ },
5805
+ description: "Role permissions"
5806
+ },
5807
+ 404: {
5808
+ content: {
5809
+ "application/json": {
5810
+ schema: errorResponseSchema5
5811
+ }
5812
+ },
5813
+ description: "Role not found"
5814
+ }
5815
+ }
5816
+ });
5817
+ var assignRolePermissionsRoute = createRoute9({
5818
+ method: "post",
5819
+ path: "/{id}/permissions",
5820
+ tags: ["Roles"],
5821
+ summary: "Assign permissions to a role",
5822
+ request: {
5823
+ params: roleIdParamSchema,
5824
+ body: {
5825
+ content: {
5826
+ "application/json": {
5827
+ schema: assignRolePermissionsSchema
5828
+ }
5829
+ }
5830
+ }
5831
+ },
5832
+ responses: {
5833
+ 200: {
5834
+ content: {
5835
+ "application/json": {
5836
+ schema: assignRolePermissionsResponseSchema
5837
+ }
5838
+ },
5839
+ description: "Permissions assigned"
5840
+ },
5841
+ 400: {
5842
+ content: {
5843
+ "application/json": {
5844
+ schema: errorResponseSchema5
5845
+ }
5846
+ },
5847
+ description: "Invalid permissions"
5848
+ },
5849
+ 403: {
5850
+ content: {
5851
+ "application/json": {
5852
+ schema: errorResponseSchema5
5853
+ }
5854
+ },
5855
+ description: "Role is not editable"
5856
+ },
5857
+ 404: {
5858
+ content: {
5859
+ "application/json": {
5860
+ schema: errorResponseSchema5
5861
+ }
5862
+ },
5863
+ description: "Role not found"
4541
5864
  }
4542
5865
  }
4543
5866
  });
4544
- var getRoleRoute = createRoute9({
5867
+ var listRoleUsersRoute = createRoute9({
4545
5868
  method: "get",
4546
- path: "/{id}",
5869
+ path: "/{id}/users",
4547
5870
  tags: ["Roles"],
4548
- summary: "Get role by ID",
5871
+ summary: "List users assigned to a role",
4549
5872
  request: {
4550
- params: roleIdParamSchema
5873
+ params: roleIdParamSchema,
5874
+ query: listRoleUsersQuerySchema
4551
5875
  },
4552
5876
  responses: {
4553
5877
  200: {
4554
5878
  content: {
4555
5879
  "application/json": {
4556
- schema: roleResponseSchema
5880
+ schema: listRoleUsersResponseSchema
4557
5881
  }
4558
5882
  },
4559
- description: "Role details"
5883
+ description: "Role users"
4560
5884
  },
4561
5885
  404: {
4562
5886
  content: {
@@ -4568,62 +5892,80 @@ var getRoleRoute = createRoute9({
4568
5892
  }
4569
5893
  }
4570
5894
  });
4571
- var createRoleRoute = createRoute9({
5895
+ var assignRoleUsersRoute = createRoute9({
4572
5896
  method: "post",
4573
- path: "/",
5897
+ path: "/{id}/users",
4574
5898
  tags: ["Roles"],
4575
- summary: "Create role",
5899
+ summary: "Assign users to a role",
4576
5900
  request: {
5901
+ params: roleIdParamSchema,
4577
5902
  body: {
4578
5903
  content: {
4579
5904
  "application/json": {
4580
- schema: createRoleSchema
5905
+ schema: assignRoleUsersSchema
4581
5906
  }
4582
5907
  }
4583
5908
  }
4584
5909
  },
4585
5910
  responses: {
4586
- 201: {
5911
+ 200: {
4587
5912
  content: {
4588
5913
  "application/json": {
4589
- schema: roleResponseSchema
5914
+ schema: assignRoleUsersResponseSchema
4590
5915
  }
4591
5916
  },
4592
- description: "Role created"
5917
+ description: "Users assigned"
4593
5918
  },
4594
- 409: {
5919
+ 400: {
4595
5920
  content: {
4596
5921
  "application/json": {
4597
5922
  schema: errorResponseSchema5
4598
5923
  }
4599
5924
  },
4600
- description: "Role code already exists"
5925
+ description: "Invalid users"
5926
+ },
5927
+ 403: {
5928
+ content: {
5929
+ "application/json": {
5930
+ schema: errorResponseSchema5
5931
+ }
5932
+ },
5933
+ description: "Role is not editable"
5934
+ },
5935
+ 404: {
5936
+ content: {
5937
+ "application/json": {
5938
+ schema: errorResponseSchema5
5939
+ }
5940
+ },
5941
+ description: "Role not found"
4601
5942
  }
4602
5943
  }
4603
5944
  });
4604
- var updateRoleRoute = createRoute9({
4605
- method: "put",
4606
- path: "/{id}",
5945
+ var revokeRolePermissionRoute2 = createRoute9({
5946
+ method: "delete",
5947
+ path: "/{id}/permissions/{permissionId}",
4607
5948
  tags: ["Roles"],
4608
- summary: "Update role",
5949
+ summary: "Revoke a permission from a role",
4609
5950
  request: {
4610
- params: roleIdParamSchema,
4611
- body: {
4612
- content: {
4613
- "application/json": {
4614
- schema: updateRoleSchema
4615
- }
4616
- }
4617
- }
5951
+ params: rolePermissionParamSchema
4618
5952
  },
4619
5953
  responses: {
4620
5954
  200: {
4621
5955
  content: {
4622
5956
  "application/json": {
4623
- schema: roleResponseSchema
5957
+ schema: deleteRoleResponseSchema
4624
5958
  }
4625
5959
  },
4626
- description: "Role updated"
5960
+ description: "Permission revoked"
5961
+ },
5962
+ 403: {
5963
+ content: {
5964
+ "application/json": {
5965
+ schema: errorResponseSchema5
5966
+ }
5967
+ },
5968
+ description: "Role is not editable"
4627
5969
  },
4628
5970
  404: {
4629
5971
  content: {
@@ -4631,17 +5973,17 @@ var updateRoleRoute = createRoute9({
4631
5973
  schema: errorResponseSchema5
4632
5974
  }
4633
5975
  },
4634
- description: "Role not found"
5976
+ description: "Role or permission not found"
4635
5977
  }
4636
5978
  }
4637
5979
  });
4638
- var deleteRoleRoute = createRoute9({
5980
+ var revokeRoleUserRoute = createRoute9({
4639
5981
  method: "delete",
4640
- path: "/{id}",
5982
+ path: "/{id}/users/{userId}",
4641
5983
  tags: ["Roles"],
4642
- summary: "Delete role",
5984
+ summary: "Remove a user from a role",
4643
5985
  request: {
4644
- params: roleIdParamSchema
5986
+ params: roleUserParamSchema
4645
5987
  },
4646
5988
  responses: {
4647
5989
  200: {
@@ -4650,7 +5992,15 @@ var deleteRoleRoute = createRoute9({
4650
5992
  schema: deleteRoleResponseSchema
4651
5993
  }
4652
5994
  },
4653
- description: "Role deleted"
5995
+ description: "User removed"
5996
+ },
5997
+ 403: {
5998
+ content: {
5999
+ "application/json": {
6000
+ schema: errorResponseSchema5
6001
+ }
6002
+ },
6003
+ description: "Role is not editable"
4654
6004
  },
4655
6005
  404: {
4656
6006
  content: {
@@ -4658,23 +6008,47 @@ var deleteRoleRoute = createRoute9({
4658
6008
  schema: errorResponseSchema5
4659
6009
  }
4660
6010
  },
4661
- description: "Role not found"
6011
+ description: "Role or user not found"
6012
+ }
6013
+ }
6014
+ });
6015
+ var seedRolesRoute = createRoute9({
6016
+ method: "post",
6017
+ path: "/seed",
6018
+ tags: ["Roles"],
6019
+ summary: "Seed tenant roles from config",
6020
+ responses: {
6021
+ 200: {
6022
+ content: {
6023
+ "application/json": {
6024
+ schema: seedRolesResponseSchema
6025
+ }
6026
+ },
6027
+ description: "Seeded roles"
6028
+ },
6029
+ 400: {
6030
+ content: {
6031
+ "application/json": {
6032
+ schema: errorResponseSchema5
6033
+ }
6034
+ },
6035
+ description: "Invalid role config"
4662
6036
  }
4663
6037
  }
4664
6038
  });
4665
- var roleRoutes = new OpenAPIHono9().openapi(listRolesRoute, listRolesHandler).openapi(getRoleRoute, getRoleHandler).openapi(createRoleRoute, createRoleHandler).openapi(updateRoleRoute, updateRoleHandler).openapi(deleteRoleRoute, deleteRoleHandler);
6039
+ var roleRoutes = new OpenAPIHono9().openapi(listRolesRoute, listRolesHandler).openapi(seedRolesRoute, seedRolesHandler).openapi(getRoleRoute, getRoleHandler).openapi(createRoleRoute, createRoleHandler).openapi(updateRoleRoute, updateRoleHandler).openapi(listRolePermissionsRoute2, listRolePermissionsHandler2).openapi(assignRolePermissionsRoute, assignRolePermissionsHandler).openapi(revokeRolePermissionRoute2, revokeRolePermissionHandler2).openapi(listRoleUsersRoute, listRoleUsersHandler).openapi(assignRoleUsersRoute, assignRoleUsersHandler).openapi(revokeRoleUserRoute, revokeRoleUserHandler).openapi(deleteRoleRoute, deleteRoleHandler);
4666
6040
  var roles_route_default = roleRoutes;
4667
6041
 
4668
6042
  // src/routes/sessions/sessions.route.ts
4669
6043
  import { createRoute as createRoute10, OpenAPIHono as OpenAPIHono10 } from "@hono/zod-openapi";
4670
6044
 
4671
6045
  // src/routes/sessions/handler/get-session.ts
4672
- import { and as and34, eq as eq34 } from "drizzle-orm";
6046
+ import { and as and43, eq as eq44 } from "drizzle-orm";
4673
6047
  var getSessionHandler = async (c) => {
4674
6048
  const { id } = c.req.valid("param");
4675
6049
  const database = c.get("database");
4676
6050
  const tenantId = c.get("tenantId");
4677
- const [session] = await database.select().from(sessionsInIam).where(and34(eq34(sessionsInIam.id, id), eq34(sessionsInIam.tenantId, tenantId))).limit(1);
6051
+ const [session] = await database.select().from(sessionsInIam).where(and43(eq44(sessionsInIam.id, id), eq44(sessionsInIam.tenantId, tenantId))).limit(1);
4678
6052
  if (!session) {
4679
6053
  return c.json({ error: "Session not found" }, 404);
4680
6054
  }
@@ -4682,7 +6056,7 @@ var getSessionHandler = async (c) => {
4682
6056
  };
4683
6057
 
4684
6058
  // src/routes/sessions/handler/list-sessions.ts
4685
- import { and as and35, eq as eq35, sql as sql19 } from "drizzle-orm";
6059
+ import { and as and44, eq as eq45, sql as sql22 } from "drizzle-orm";
4686
6060
  var listSessionsHandler = async (c) => {
4687
6061
  const query = c.req.valid("query");
4688
6062
  const database = c.get("database");
@@ -4690,48 +6064,48 @@ var listSessionsHandler = async (c) => {
4690
6064
  const page = query.page || 1;
4691
6065
  const limit = query.limit || 20;
4692
6066
  const offset = (page - 1) * limit;
4693
- const conditions = [eq35(sessionsInIam.tenantId, tenantId)];
6067
+ const conditions = [eq45(sessionsInIam.tenantId, tenantId)];
4694
6068
  if (query.userId) {
4695
- conditions.push(eq35(sessionsInIam.userId, query.userId));
6069
+ conditions.push(eq45(sessionsInIam.userId, query.userId));
4696
6070
  }
4697
6071
  const [sessions, totalResult] = await Promise.all([
4698
- database.select().from(sessionsInIam).where(and35(...conditions)).limit(limit).offset(offset),
4699
- database.select({ count: sql19`count(*)` }).from(sessionsInIam).where(and35(...conditions))
6072
+ database.select().from(sessionsInIam).where(and44(...conditions)).limit(limit).offset(offset),
6073
+ database.select({ count: sql22`count(*)` }).from(sessionsInIam).where(and44(...conditions))
4700
6074
  ]);
4701
6075
  const total = Number(totalResult[0]?.count || 0);
4702
6076
  return c.json({ sessions, total, page, limit }, 200);
4703
6077
  };
4704
6078
 
4705
6079
  // src/routes/sessions/handler/revoke-all-sessions.ts
4706
- import { and as and36, eq as eq36 } from "drizzle-orm";
6080
+ import { and as and45, eq as eq46 } from "drizzle-orm";
4707
6081
  var revokeAllSessionsHandler = async (c) => {
4708
6082
  const { userId } = c.req.valid("param");
4709
6083
  const database = c.get("database");
4710
6084
  const tenantId = c.get("tenantId");
4711
- const [user] = await database.select().from(usersInIam).where(and36(eq36(usersInIam.id, userId), eq36(usersInIam.tenantId, tenantId))).limit(1);
6085
+ const [user] = await database.select().from(usersInIam).where(and45(eq46(usersInIam.id, userId), eq46(usersInIam.tenantId, tenantId))).limit(1);
4712
6086
  if (!user) {
4713
6087
  return c.json({ error: "User not found" }, 404);
4714
6088
  }
4715
6089
  await database.delete(sessionsInIam).where(
4716
- and36(
4717
- eq36(sessionsInIam.tenantId, tenantId),
4718
- eq36(sessionsInIam.userId, userId)
6090
+ and45(
6091
+ eq46(sessionsInIam.tenantId, tenantId),
6092
+ eq46(sessionsInIam.userId, userId)
4719
6093
  )
4720
6094
  );
4721
6095
  return c.json({ message: "All user sessions revoked" }, 200);
4722
6096
  };
4723
6097
 
4724
6098
  // src/routes/sessions/handler/revoke-session.ts
4725
- import { and as and37, eq as eq37 } from "drizzle-orm";
6099
+ import { and as and46, eq as eq47 } from "drizzle-orm";
4726
6100
  var revokeSessionHandler = async (c) => {
4727
6101
  const { id } = c.req.valid("param");
4728
6102
  const database = c.get("database");
4729
6103
  const tenantId = c.get("tenantId");
4730
- const [existing] = await database.select().from(sessionsInIam).where(and37(eq37(sessionsInIam.id, id), eq37(sessionsInIam.tenantId, tenantId))).limit(1);
6104
+ const [existing] = await database.select().from(sessionsInIam).where(and46(eq47(sessionsInIam.id, id), eq47(sessionsInIam.tenantId, tenantId))).limit(1);
4731
6105
  if (!existing) {
4732
6106
  return c.json({ error: "Session not found" }, 404);
4733
6107
  }
4734
- await database.delete(sessionsInIam).where(and37(eq37(sessionsInIam.id, id), eq37(sessionsInIam.tenantId, tenantId)));
6108
+ await database.delete(sessionsInIam).where(and46(eq47(sessionsInIam.id, id), eq47(sessionsInIam.tenantId, tenantId)));
4735
6109
  return c.json({ message: "Session revoked" }, 200);
4736
6110
  };
4737
6111
 
@@ -4933,10 +6307,11 @@ var system_route_default = tenantRoutes;
4933
6307
  import { createRoute as createRoute12, OpenAPIHono as OpenAPIHono12 } from "@hono/zod-openapi";
4934
6308
 
4935
6309
  // src/routes/tenants/handler/create-tenant.ts
4936
- import { eq as eq38 } from "drizzle-orm";
6310
+ import { eq as eq48 } from "drizzle-orm";
4937
6311
 
4938
6312
  // src/lib/has-role-permission.ts
4939
- import { HTTPException as HTTPException3 } from "hono/http-exception";
6313
+ import { grant } from "@mesob/common";
6314
+ import { HTTPException as HTTPException4 } from "hono/http-exception";
4940
6315
  var toArray = (v) => {
4941
6316
  return Array.isArray(v) ? v : [v];
4942
6317
  };
@@ -4951,21 +6326,18 @@ var hasRole = (c, role) => {
4951
6326
  };
4952
6327
  var hasRoleThrow = (c, role) => {
4953
6328
  if (!hasRole(c, role)) {
4954
- throw new HTTPException3(401, { message: "Unauthorized" });
6329
+ throw new HTTPException4(401, { message: "Unauthorized" });
4955
6330
  }
4956
6331
  };
4957
6332
  var hasPermission = (c, permission) => {
4958
6333
  const user = c.get("user");
4959
6334
  const perms = user?.permissions;
4960
- if (!perms?.length) {
4961
- return false;
4962
- }
4963
6335
  const check2 = toArray(permission);
4964
- return check2.some((p) => perms.includes(p));
6336
+ return grant(check2, perms);
4965
6337
  };
4966
6338
  var hasPermissionThrow = (c, permission) => {
4967
6339
  if (!hasPermission(c, permission)) {
4968
- throw new HTTPException3(401, { message: "Unauthorized" });
6340
+ throw new HTTPException4(401, { message: "Unauthorized" });
4969
6341
  }
4970
6342
  };
4971
6343
 
@@ -4974,7 +6346,7 @@ var createTenantHandler = async (c) => {
4974
6346
  hasRoleThrow(c, ["owner", "tenant-admin"]);
4975
6347
  const body = c.req.valid("json");
4976
6348
  const database = c.get("database");
4977
- const [existing] = await database.select().from(tenantsInIam).where(eq38(tenantsInIam.id, body.id)).limit(1);
6349
+ const [existing] = await database.select().from(tenantsInIam).where(eq48(tenantsInIam.id, body.id)).limit(1);
4978
6350
  if (existing) {
4979
6351
  return c.json({ error: "Tenant already exists" }, 409);
4980
6352
  }
@@ -4997,26 +6369,26 @@ var createTenantHandler = async (c) => {
4997
6369
  };
4998
6370
 
4999
6371
  // src/routes/tenants/handler/delete-tenant.ts
5000
- import { eq as eq39 } from "drizzle-orm";
6372
+ import { eq as eq49 } from "drizzle-orm";
5001
6373
  var deleteTenantHandler = async (c) => {
5002
6374
  hasRoleThrow(c, ["owner", "tenant-admin"]);
5003
6375
  const { id } = c.req.valid("param");
5004
6376
  const database = c.get("database");
5005
- const [existing] = await database.select().from(tenantsInIam).where(eq39(tenantsInIam.id, id)).limit(1);
6377
+ const [existing] = await database.select().from(tenantsInIam).where(eq49(tenantsInIam.id, id)).limit(1);
5006
6378
  if (!existing) {
5007
6379
  return c.json({ error: "Tenant not found" }, 404);
5008
6380
  }
5009
- await database.delete(tenantsInIam).where(eq39(tenantsInIam.id, id));
6381
+ await database.delete(tenantsInIam).where(eq49(tenantsInIam.id, id));
5010
6382
  return c.json({ message: "Tenant deleted" }, 200);
5011
6383
  };
5012
6384
 
5013
6385
  // src/routes/tenants/handler/get-tenant.ts
5014
- import { eq as eq40 } from "drizzle-orm";
6386
+ import { eq as eq50 } from "drizzle-orm";
5015
6387
  var getTenantHandler = async (c) => {
5016
6388
  hasRoleThrow(c, ["owner", "tenant-admin"]);
5017
6389
  const { id } = c.req.valid("param");
5018
6390
  const database = c.get("database");
5019
- const [tenant] = await database.select().from(tenantsInIam).where(eq40(tenantsInIam.id, id)).limit(1);
6391
+ const [tenant] = await database.select().from(tenantsInIam).where(eq50(tenantsInIam.id, id)).limit(1);
5020
6392
  if (!tenant) {
5021
6393
  return c.json({ error: "Tenant not found" }, 404);
5022
6394
  }
@@ -5024,11 +6396,11 @@ var getTenantHandler = async (c) => {
5024
6396
  };
5025
6397
 
5026
6398
  // src/routes/tenants/handler/list-tenants.ts
5027
- import { and as and38, asc as asc2, desc as desc2, eq as eq41, ilike as ilike2, or, sql as sql20 } from "drizzle-orm";
5028
- var sortColumnMap = {
6399
+ import { and as and47, asc as asc4, desc as desc4, eq as eq51, ilike as ilike3, or as or3, sql as sql23 } from "drizzle-orm";
6400
+ var sortColumnMap3 = {
5029
6401
  createdAt: tenantsInIam.createdAt,
5030
6402
  updatedAt: tenantsInIam.updatedAt,
5031
- name: sql20`${tenantsInIam.name}::text`
6403
+ name: sql23`${tenantsInIam.name}::text`
5032
6404
  };
5033
6405
  var listTenantsHandler = async (c) => {
5034
6406
  hasRoleThrow(c, ["owner", "tenant-admin"]);
@@ -5039,42 +6411,42 @@ var listTenantsHandler = async (c) => {
5039
6411
  const offset = (page - 1) * limit;
5040
6412
  const conditions = [];
5041
6413
  if (query.isActive !== void 0) {
5042
- conditions.push(eq41(tenantsInIam.isActive, query.isActive));
6414
+ conditions.push(eq51(tenantsInIam.isActive, query.isActive));
5043
6415
  }
5044
6416
  if (query.filter === "isActive:true") {
5045
- conditions.push(eq41(tenantsInIam.isActive, true));
6417
+ conditions.push(eq51(tenantsInIam.isActive, true));
5046
6418
  } else if (query.filter === "isActive:false") {
5047
- conditions.push(eq41(tenantsInIam.isActive, false));
6419
+ conditions.push(eq51(tenantsInIam.isActive, false));
5048
6420
  }
5049
6421
  if (query.search?.trim()) {
5050
6422
  const term = `%${query.search.trim()}%`;
5051
- const searchCond = or(
5052
- ilike2(tenantsInIam.id, term),
5053
- ilike2(sql20`${tenantsInIam.name}::text`, term)
6423
+ const searchCond = or3(
6424
+ ilike3(tenantsInIam.id, term),
6425
+ ilike3(sql23`${tenantsInIam.name}::text`, term)
5054
6426
  );
5055
6427
  if (searchCond) {
5056
6428
  conditions.push(searchCond);
5057
6429
  }
5058
6430
  }
5059
- const orderDir = query.order === "asc" ? asc2 : desc2;
5060
- const sortCol = query.sort && sortColumnMap[query.sort] ? sortColumnMap[query.sort] : tenantsInIam.createdAt;
5061
- const whereClause = conditions.length > 0 ? and38(...conditions) : void 0;
6431
+ const orderDir = query.order === "asc" ? asc4 : desc4;
6432
+ const sortCol = query.sort && sortColumnMap3[query.sort] ? sortColumnMap3[query.sort] : tenantsInIam.createdAt;
6433
+ const whereClause = conditions.length > 0 ? and47(...conditions) : void 0;
5062
6434
  const [tenants, totalResult] = await Promise.all([
5063
6435
  database.select().from(tenantsInIam).where(whereClause).orderBy(orderDir(sortCol)).limit(limit).offset(offset),
5064
- database.select({ count: sql20`count(*)` }).from(tenantsInIam).where(whereClause)
6436
+ database.select({ count: sql23`count(*)` }).from(tenantsInIam).where(whereClause)
5065
6437
  ]);
5066
6438
  const total = Number(totalResult[0]?.count || 0);
5067
6439
  return c.json({ tenants, total, page, limit }, 200);
5068
6440
  };
5069
6441
 
5070
6442
  // src/routes/tenants/handler/update-tenant.ts
5071
- import { eq as eq42, sql as sql21 } from "drizzle-orm";
6443
+ import { eq as eq52, sql as sql24 } from "drizzle-orm";
5072
6444
  var updateTenantHandler = async (c) => {
5073
6445
  hasRoleThrow(c, ["owner", "tenant-admin"]);
5074
6446
  const { id } = c.req.valid("param");
5075
6447
  const body = c.req.valid("json");
5076
6448
  const database = c.get("database");
5077
- const [existing] = await database.select().from(tenantsInIam).where(eq42(tenantsInIam.id, id)).limit(1);
6449
+ const [existing] = await database.select().from(tenantsInIam).where(eq52(tenantsInIam.id, id)).limit(1);
5078
6450
  if (!existing) {
5079
6451
  return c.json({ error: "Tenant not found" }, 404);
5080
6452
  }
@@ -5117,8 +6489,8 @@ var updateTenantHandler = async (c) => {
5117
6489
  }
5118
6490
  const [updated] = await database.update(tenantsInIam).set({
5119
6491
  ...updateData,
5120
- updatedAt: sql21`CURRENT_TIMESTAMP`
5121
- }).where(eq42(tenantsInIam.id, id)).returning();
6492
+ updatedAt: sql24`CURRENT_TIMESTAMP`
6493
+ }).where(eq52(tenantsInIam.id, id)).returning();
5122
6494
  if (!updated) {
5123
6495
  return c.json({ error: "Tenant not found" }, 404);
5124
6496
  }
@@ -5373,36 +6745,36 @@ var assignUserRoleHandler = async (c) => {
5373
6745
  };
5374
6746
 
5375
6747
  // src/routes/user-roles/handler/list-user-roles.ts
5376
- import { and as and39, eq as eq43 } from "drizzle-orm";
6748
+ import { and as and48, eq as eq53 } from "drizzle-orm";
5377
6749
  var listUserRolesHandler = async (c) => {
5378
6750
  const query = c.req.valid("query");
5379
6751
  const database = c.get("database");
5380
6752
  const tenantId = c.get("tenantId");
5381
- const conditions = [eq43(userRolesInIam.tenantId, tenantId)];
6753
+ const conditions = [eq53(userRolesInIam.tenantId, tenantId)];
5382
6754
  if (query.userId) {
5383
- conditions.push(eq43(userRolesInIam.userId, query.userId));
6755
+ conditions.push(eq53(userRolesInIam.userId, query.userId));
5384
6756
  }
5385
6757
  if (query.roleId) {
5386
- conditions.push(eq43(userRolesInIam.roleId, query.roleId));
6758
+ conditions.push(eq53(userRolesInIam.roleId, query.roleId));
5387
6759
  }
5388
- const userRoles = await database.select().from(userRolesInIam).where(and39(...conditions));
6760
+ const userRoles = await database.select().from(userRolesInIam).where(and48(...conditions));
5389
6761
  return c.json({ userRoles }, 200);
5390
6762
  };
5391
6763
 
5392
6764
  // src/routes/user-roles/handler/revoke-user-role.ts
5393
- import { and as and40, eq as eq44 } from "drizzle-orm";
6765
+ import { and as and49, eq as eq54 } from "drizzle-orm";
5394
6766
  var revokeUserRoleHandler = async (c) => {
5395
6767
  const { id } = c.req.valid("param");
5396
6768
  const database = c.get("database");
5397
6769
  const tenantId = c.get("tenantId");
5398
6770
  const [existing] = await database.select().from(userRolesInIam).where(
5399
- and40(eq44(userRolesInIam.id, id), eq44(userRolesInIam.tenantId, tenantId))
6771
+ and49(eq54(userRolesInIam.id, id), eq54(userRolesInIam.tenantId, tenantId))
5400
6772
  ).limit(1);
5401
6773
  if (!existing) {
5402
6774
  return c.json({ error: "User role not found" }, 404);
5403
6775
  }
5404
6776
  await database.delete(userRolesInIam).where(
5405
- and40(eq44(userRolesInIam.id, id), eq44(userRolesInIam.tenantId, tenantId))
6777
+ and49(eq54(userRolesInIam.id, id), eq54(userRolesInIam.tenantId, tenantId))
5406
6778
  );
5407
6779
  return c.json({ message: "Role revoked from user" }, 200);
5408
6780
  };
@@ -5526,20 +6898,20 @@ var user_roles_route_default = userRoleRoutes;
5526
6898
  import { createRoute as createRoute14, OpenAPIHono as OpenAPIHono14 } from "@hono/zod-openapi";
5527
6899
 
5528
6900
  // src/routes/users/handler/ban-user.ts
5529
- import { and as and41, eq as eq45, sql as sql22 } from "drizzle-orm";
6901
+ import { and as and50, eq as eq55, sql as sql25 } from "drizzle-orm";
5530
6902
  var banUserHandler = async (c) => {
5531
6903
  const { id } = c.req.valid("param");
5532
6904
  const body = c.req.valid("json");
5533
6905
  const database = c.get("database");
5534
6906
  const tenantId = c.get("tenantId");
5535
- const [existing] = await database.select().from(usersInIam).where(and41(eq45(usersInIam.id, id), eq45(usersInIam.tenantId, tenantId))).limit(1);
6907
+ const [existing] = await database.select().from(usersInIam).where(and50(eq55(usersInIam.id, id), eq55(usersInIam.tenantId, tenantId))).limit(1);
5536
6908
  if (!existing) {
5537
6909
  return c.json({ error: "User not found" }, 404);
5538
6910
  }
5539
6911
  const [updated] = await database.update(usersInIam).set({
5540
6912
  bannedUntil: body.bannedUntil || null,
5541
- updatedAt: sql22`CURRENT_TIMESTAMP`
5542
- }).where(and41(eq45(usersInIam.id, id), eq45(usersInIam.tenantId, tenantId))).returning({
6913
+ updatedAt: sql25`CURRENT_TIMESTAMP`
6914
+ }).where(and50(eq55(usersInIam.id, id), eq55(usersInIam.tenantId, tenantId))).returning({
5543
6915
  id: usersInIam.id,
5544
6916
  tenantId: usersInIam.tenantId,
5545
6917
  fullName: usersInIam.fullName,
@@ -5558,17 +6930,17 @@ var banUserHandler = async (c) => {
5558
6930
  };
5559
6931
 
5560
6932
  // src/routes/users/helper/user.ts
5561
- import { and as and42, eq as eq46, sql as sql23 } from "drizzle-orm";
6933
+ import { and as and51, eq as eq56, sql as sql26 } from "drizzle-orm";
5562
6934
  var checkUserExists = async ({
5563
6935
  database,
5564
6936
  identifier,
5565
6937
  tenantId,
5566
6938
  isEmail
5567
6939
  }) => {
5568
- const whereClause = isEmail ? and42(
5569
- eq46(usersInIam.tenantId, tenantId),
5570
- sql23`lower(${usersInIam.email}) = lower(${identifier})`
5571
- ) : and42(eq46(usersInIam.tenantId, tenantId), eq46(usersInIam.phone, identifier));
6940
+ const whereClause = isEmail ? and51(
6941
+ eq56(usersInIam.tenantId, tenantId),
6942
+ sql26`lower(${usersInIam.email}) = lower(${identifier})`
6943
+ ) : and51(eq56(usersInIam.tenantId, tenantId), eq56(usersInIam.phone, identifier));
5572
6944
  const [user] = await database.select().from(usersInIam).where(whereClause).limit(1);
5573
6945
  return user || null;
5574
6946
  };
@@ -5578,14 +6950,170 @@ var checkHandleExists = async ({
5578
6950
  tenantId
5579
6951
  }) => {
5580
6952
  const [existingHandle] = await database.select().from(usersInIam).where(
5581
- and42(
5582
- eq46(usersInIam.tenantId, tenantId),
5583
- sql23`lower(${usersInIam.handle}) = lower(${handle})`
6953
+ and51(
6954
+ eq56(usersInIam.tenantId, tenantId),
6955
+ sql26`lower(${usersInIam.handle}) = lower(${handle})`
5584
6956
  )
5585
6957
  ).limit(1);
5586
6958
  return existingHandle || null;
5587
6959
  };
5588
6960
 
6961
+ // src/routes/users/helper/invite.ts
6962
+ var resolveInviteUrl = ({
6963
+ inviteUrl,
6964
+ identifier,
6965
+ hasPassword
6966
+ }) => {
6967
+ if (inviteUrl) {
6968
+ return inviteUrl;
6969
+ }
6970
+ if (hasPassword) {
6971
+ return "/auth/sign-in";
6972
+ }
6973
+ return `/auth/set-password?identifier=${encodeURIComponent(identifier)}`;
6974
+ };
6975
+ var inviteUser = (
6976
+ // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: invite flow mixes validation, persistence, and delivery reporting
6977
+ async function inviteUser2({
6978
+ database,
6979
+ tenantId,
6980
+ config,
6981
+ payload
6982
+ }) {
6983
+ const identifier = payload.email || payload.phone;
6984
+ if (!identifier) {
6985
+ throw new Error("Either email or phone is required");
6986
+ }
6987
+ const isEmail = identifier.includes("@");
6988
+ if (payload.phone) {
6989
+ const phoneValidator = createPhoneField(config);
6990
+ if (!phoneValidator.validate(payload.phone)) {
6991
+ throw new Error("Invalid phone number format");
6992
+ }
6993
+ }
6994
+ const existing = await checkUserExists({
6995
+ database,
6996
+ identifier,
6997
+ tenantId,
6998
+ isEmail
6999
+ });
7000
+ if (existing) {
7001
+ throw new Error("User already exists");
7002
+ }
7003
+ const userHandle = payload.handle || generateHandle();
7004
+ const existingHandle = await checkHandleExists({
7005
+ database,
7006
+ handle: userHandle,
7007
+ tenantId
7008
+ });
7009
+ if (existingHandle) {
7010
+ throw new Error("Handle already taken");
7011
+ }
7012
+ const passwordHash = payload.password ? await hashPassword(payload.password) : null;
7013
+ const user = await withTransaction(database, async (tx) => {
7014
+ const [createdUser] = await tx.insert(usersInIam).values({
7015
+ tenantId,
7016
+ fullName: payload.fullName,
7017
+ handle: userHandle,
7018
+ email: payload.email || null,
7019
+ phone: payload.phone || null,
7020
+ image: payload.image || null,
7021
+ emailVerified: Boolean(payload.emailVerified),
7022
+ phoneVerified: Boolean(payload.phoneVerified),
7023
+ userType: [config.userType]
7024
+ }).returning();
7025
+ if (passwordHash) {
7026
+ await tx.insert(accountsInIam).values({
7027
+ tenantId,
7028
+ userId: createdUser.id,
7029
+ provider: "credentials",
7030
+ providerAccountId: identifier,
7031
+ password: passwordHash
7032
+ });
7033
+ }
7034
+ return createdUser;
7035
+ });
7036
+ const hasPassword = Boolean(passwordHash);
7037
+ const resolvedInviteUrl = resolveInviteUrl({
7038
+ inviteUrl: payload.inviteUrl,
7039
+ identifier,
7040
+ hasPassword
7041
+ });
7042
+ const delivery = {};
7043
+ if (payload.sendVia?.includes("email")) {
7044
+ if (payload.email && config.email.sendInvitation) {
7045
+ try {
7046
+ await config.email.sendInvitation({
7047
+ email: payload.email,
7048
+ fullName: payload.fullName,
7049
+ identifier,
7050
+ inviteUrl: resolvedInviteUrl,
7051
+ tenantId
7052
+ });
7053
+ delivery.email = "sent";
7054
+ } catch {
7055
+ delivery.email = "failed";
7056
+ }
7057
+ } else {
7058
+ delivery.email = "skipped";
7059
+ }
7060
+ }
7061
+ if (payload.sendVia?.includes("sms")) {
7062
+ if (payload.phone && config.phone.sendInvitation) {
7063
+ try {
7064
+ await config.phone.sendInvitation({
7065
+ phone: payload.phone,
7066
+ fullName: payload.fullName,
7067
+ identifier,
7068
+ inviteUrl: resolvedInviteUrl,
7069
+ tenantId
7070
+ });
7071
+ delivery.sms = "sent";
7072
+ } catch {
7073
+ delivery.sms = "failed";
7074
+ }
7075
+ } else {
7076
+ delivery.sms = "skipped";
7077
+ }
7078
+ }
7079
+ return {
7080
+ user: normalizeUser(user),
7081
+ delivery,
7082
+ inviteUrl: resolvedInviteUrl,
7083
+ hasPassword
7084
+ };
7085
+ }
7086
+ );
7087
+
7088
+ // src/routes/users/handler/bulk-invite-users.ts
7089
+ var bulkInviteUsersHandler = async (c) => {
7090
+ const body = c.req.valid("json");
7091
+ const config = c.get("config");
7092
+ const database = c.get("database");
7093
+ const tenantId = c.get("tenantId");
7094
+ const resolvedTenantId = ensureTenantId(config, tenantId);
7095
+ const invited = [];
7096
+ const failed = [];
7097
+ for (const [index2, payload] of body.users.entries()) {
7098
+ try {
7099
+ const result = await inviteUser({
7100
+ database,
7101
+ tenantId: resolvedTenantId,
7102
+ config,
7103
+ payload
7104
+ });
7105
+ invited.push(result);
7106
+ } catch (error) {
7107
+ failed.push({
7108
+ index: index2,
7109
+ identifier: payload.email || payload.phone || null,
7110
+ error: error instanceof Error ? error.message : "Unknown error"
7111
+ });
7112
+ }
7113
+ }
7114
+ return c.json({ invited, failed }, 200);
7115
+ };
7116
+
5589
7117
  // src/routes/users/handler/create-user.ts
5590
7118
  var createUserHandler = async (c) => {
5591
7119
  const body = c.req.valid("json");
@@ -5616,41 +7144,55 @@ var createUserHandler = async (c) => {
5616
7144
  if (existingHandle) {
5617
7145
  return c.json({ error: "Handle already taken" }, 409);
5618
7146
  }
5619
- const [user] = await database.insert(usersInIam).values({
5620
- tenantId: resolvedTenantId,
5621
- fullName: body.fullName,
5622
- handle: userHandle,
5623
- email: body.email || null,
5624
- phone: body.phone || null,
5625
- image: body.image || null,
5626
- emailVerified: Boolean(body.emailVerified),
5627
- phoneVerified: Boolean(body.phoneVerified)
5628
- }).returning({
5629
- id: usersInIam.id,
5630
- tenantId: usersInIam.tenantId,
5631
- fullName: usersInIam.fullName,
5632
- email: usersInIam.email,
5633
- phone: usersInIam.phone,
5634
- handle: usersInIam.handle,
5635
- image: usersInIam.image,
5636
- emailVerified: usersInIam.emailVerified,
5637
- phoneVerified: usersInIam.phoneVerified,
5638
- lastSignInAt: usersInIam.lastSignInAt
7147
+ const [user] = await withTransaction(database, async (tx) => {
7148
+ const [createdUser] = await tx.insert(usersInIam).values({
7149
+ tenantId: resolvedTenantId,
7150
+ fullName: body.fullName,
7151
+ handle: userHandle,
7152
+ email: body.email || null,
7153
+ phone: body.phone || null,
7154
+ image: body.image || null,
7155
+ emailVerified: Boolean(body.emailVerified),
7156
+ phoneVerified: Boolean(body.phoneVerified),
7157
+ userType: [config.userType]
7158
+ }).returning({
7159
+ id: usersInIam.id,
7160
+ tenantId: usersInIam.tenantId,
7161
+ fullName: usersInIam.fullName,
7162
+ email: usersInIam.email,
7163
+ phone: usersInIam.phone,
7164
+ handle: usersInIam.handle,
7165
+ image: usersInIam.image,
7166
+ emailVerified: usersInIam.emailVerified,
7167
+ phoneVerified: usersInIam.phoneVerified,
7168
+ lastSignInAt: usersInIam.lastSignInAt
7169
+ });
7170
+ if (body.password) {
7171
+ const passwordHash = await hashPassword(body.password);
7172
+ await tx.insert(accountsInIam).values({
7173
+ tenantId: resolvedTenantId,
7174
+ userId: createdUser.id,
7175
+ provider: "credentials",
7176
+ providerAccountId: identifier,
7177
+ password: passwordHash
7178
+ });
7179
+ }
7180
+ return [createdUser];
5639
7181
  });
5640
7182
  return c.json({ user: normalizeUser(user) }, 201);
5641
7183
  };
5642
7184
 
5643
7185
  // src/routes/users/handler/delete-user.ts
5644
- import { and as and43, eq as eq47 } from "drizzle-orm";
7186
+ import { and as and52, eq as eq57 } from "drizzle-orm";
5645
7187
  var deleteUserHandler = async (c) => {
5646
7188
  const { id } = c.req.valid("param");
5647
7189
  const database = c.get("database");
5648
7190
  const tenantId = c.get("tenantId");
5649
- const [existing] = await database.select().from(usersInIam).where(and43(eq47(usersInIam.id, id), eq47(usersInIam.tenantId, tenantId))).limit(1);
7191
+ const [existing] = await database.select().from(usersInIam).where(and52(eq57(usersInIam.id, id), eq57(usersInIam.tenantId, tenantId))).limit(1);
5650
7192
  if (!existing) {
5651
7193
  return c.json({ error: "User not found" }, 404);
5652
7194
  }
5653
- await database.delete(usersInIam).where(and43(eq47(usersInIam.id, id), eq47(usersInIam.tenantId, tenantId)));
7195
+ await database.delete(usersInIam).where(and52(eq57(usersInIam.id, id), eq57(usersInIam.tenantId, tenantId)));
5654
7196
  return c.json({ message: "User deleted" }, 200);
5655
7197
  };
5656
7198
 
@@ -5670,9 +7212,32 @@ var getUserHandler = async (c) => {
5670
7212
  return c.json({ user: normalizeUser(user) }, 200);
5671
7213
  };
5672
7214
 
7215
+ // src/routes/users/handler/invite-user.ts
7216
+ var inviteUserHandler = async (c) => {
7217
+ const body = c.req.valid("json");
7218
+ const config = c.get("config");
7219
+ const database = c.get("database");
7220
+ const tenantId = c.get("tenantId");
7221
+ const resolvedTenantId = ensureTenantId(config, tenantId);
7222
+ try {
7223
+ const result = await inviteUser({
7224
+ database,
7225
+ tenantId: resolvedTenantId,
7226
+ config,
7227
+ payload: body
7228
+ });
7229
+ return c.json(result, 201);
7230
+ } catch (error) {
7231
+ if (error instanceof Error) {
7232
+ return c.json({ error: error.message }, 409);
7233
+ }
7234
+ throw error;
7235
+ }
7236
+ };
7237
+
5673
7238
  // src/routes/users/handler/list-users.ts
5674
- import { and as and44, asc as asc3, desc as desc3, eq as eq48, ilike as ilike3, sql as sql24 } from "drizzle-orm";
5675
- var sortColumnMap2 = {
7239
+ import { and as and53, asc as asc5, desc as desc5, eq as eq58, ilike as ilike4, sql as sql27 } from "drizzle-orm";
7240
+ var sortColumnMap4 = {
5676
7241
  createdAt: usersInIam.createdAt,
5677
7242
  updatedAt: usersInIam.updatedAt,
5678
7243
  fullName: usersInIam.fullName,
@@ -5686,26 +7251,26 @@ var listUsersHandler = async (c) => {
5686
7251
  const page = query.page || 1;
5687
7252
  const limit = query.limit || 20;
5688
7253
  const offset = (page - 1) * limit;
5689
- const conditions = [eq48(usersInIam.tenantId, tenantId)];
7254
+ const conditions = [eq58(usersInIam.tenantId, tenantId)];
5690
7255
  if (query.email) {
5691
- conditions.push(ilike3(usersInIam.email, `%${query.email}%`));
7256
+ conditions.push(ilike4(usersInIam.email, `%${query.email}%`));
5692
7257
  }
5693
7258
  if (query.phone) {
5694
- conditions.push(ilike3(usersInIam.phone, `%${query.phone}%`));
7259
+ conditions.push(ilike4(usersInIam.phone, `%${query.phone}%`));
5695
7260
  }
5696
7261
  if (query.handle) {
5697
- conditions.push(ilike3(usersInIam.handle, `%${query.handle}%`));
7262
+ conditions.push(ilike4(usersInIam.handle, `%${query.handle}%`));
5698
7263
  }
5699
7264
  if (query.filter === "emailVerified") {
5700
- conditions.push(eq48(usersInIam.emailVerified, true));
7265
+ conditions.push(eq58(usersInIam.emailVerified, true));
5701
7266
  } else if (query.filter === "phoneVerified") {
5702
- conditions.push(eq48(usersInIam.phoneVerified, true));
7267
+ conditions.push(eq58(usersInIam.phoneVerified, true));
5703
7268
  } else if (query.filter === "notVerified") {
5704
- conditions.push(eq48(usersInIam.emailVerified, false));
5705
- conditions.push(eq48(usersInIam.phoneVerified, false));
7269
+ conditions.push(eq58(usersInIam.emailVerified, false));
7270
+ conditions.push(eq58(usersInIam.phoneVerified, false));
5706
7271
  }
5707
- const orderDir = query.order === "asc" ? asc3 : desc3;
5708
- const sortCol = query.sort && sortColumnMap2[query.sort] ? sortColumnMap2[query.sort] : usersInIam.createdAt;
7272
+ const orderDir = query.order === "asc" ? asc5 : desc5;
7273
+ const sortCol = query.sort && sortColumnMap4[query.sort] ? sortColumnMap4[query.sort] : usersInIam.createdAt;
5709
7274
  const [users, totalResult] = await Promise.all([
5710
7275
  database.select({
5711
7276
  id: usersInIam.id,
@@ -5718,8 +7283,8 @@ var listUsersHandler = async (c) => {
5718
7283
  emailVerified: usersInIam.emailVerified,
5719
7284
  phoneVerified: usersInIam.phoneVerified,
5720
7285
  lastSignInAt: usersInIam.lastSignInAt
5721
- }).from(usersInIam).where(and44(...conditions)).orderBy(orderDir(sortCol)).limit(limit).offset(offset),
5722
- database.select({ count: sql24`count(*)` }).from(usersInIam).where(and44(...conditions))
7286
+ }).from(usersInIam).where(and53(...conditions)).orderBy(orderDir(sortCol)).limit(limit).offset(offset),
7287
+ database.select({ count: sql27`count(*)` }).from(usersInIam).where(and53(...conditions))
5723
7288
  ]);
5724
7289
  const total = Number(totalResult[0]?.count || 0);
5725
7290
  return c.json(
@@ -5737,18 +7302,18 @@ var listUsersHandler = async (c) => {
5737
7302
  };
5738
7303
 
5739
7304
  // src/routes/users/handler/search-users.ts
5740
- import { and as and45, eq as eq49, ilike as ilike4, or as or2 } from "drizzle-orm";
7305
+ import { and as and54, eq as eq59, ilike as ilike5, or as or4 } from "drizzle-orm";
5741
7306
  var searchUsersHandler = async (c) => {
5742
7307
  const query = c.req.valid("query");
5743
7308
  const database = c.get("database");
5744
7309
  const tenantId = c.get("tenantId");
5745
7310
  const limit = query.limit || 20;
5746
- const conditions = [eq49(usersInIam.tenantId, tenantId)];
7311
+ const conditions = [eq59(usersInIam.tenantId, tenantId)];
5747
7312
  if (query.search && query.search.trim().length > 0) {
5748
- const searchCondition = or2(
5749
- ilike4(usersInIam.fullName, `%${query.search}%`),
5750
- ilike4(usersInIam.email, `%${query.search}%`),
5751
- ilike4(usersInIam.handle, `%${query.search}%`)
7313
+ const searchCondition = or4(
7314
+ ilike5(usersInIam.fullName, `%${query.search}%`),
7315
+ ilike5(usersInIam.email, `%${query.search}%`),
7316
+ ilike5(usersInIam.handle, `%${query.search}%`)
5752
7317
  );
5753
7318
  if (searchCondition) {
5754
7319
  conditions.push(searchCondition);
@@ -5761,26 +7326,26 @@ var searchUsersHandler = async (c) => {
5761
7326
  phone: usersInIam.phone,
5762
7327
  handle: usersInIam.handle,
5763
7328
  image: usersInIam.image
5764
- }).from(usersInIam).where(and45(...conditions)).limit(limit);
7329
+ }).from(usersInIam).where(and54(...conditions)).limit(limit);
5765
7330
  return c.json({ users }, 200);
5766
7331
  };
5767
7332
 
5768
7333
  // src/routes/users/handler/update-user.ts
5769
- import { and as and46, eq as eq50, sql as sql25 } from "drizzle-orm";
7334
+ import { and as and55, eq as eq60, sql as sql28 } from "drizzle-orm";
5770
7335
  var updateUserHandler = async (c) => {
5771
7336
  const { id } = c.req.valid("param");
5772
7337
  const body = c.req.valid("json");
5773
7338
  const database = c.get("database");
5774
7339
  const tenantId = c.get("tenantId");
5775
- const [existing] = await database.select().from(usersInIam).where(and46(eq50(usersInIam.id, id), eq50(usersInIam.tenantId, tenantId))).limit(1);
7340
+ const [existing] = await database.select().from(usersInIam).where(and55(eq60(usersInIam.id, id), eq60(usersInIam.tenantId, tenantId))).limit(1);
5776
7341
  if (!existing) {
5777
7342
  return c.json({ error: "User not found" }, 404);
5778
7343
  }
5779
7344
  if (body.handle && body.handle !== existing.handle) {
5780
7345
  const [handleExists] = await database.select().from(usersInIam).where(
5781
- and46(
5782
- eq50(usersInIam.tenantId, tenantId),
5783
- sql25`lower(${usersInIam.handle}) = lower(${body.handle})`
7346
+ and55(
7347
+ eq60(usersInIam.tenantId, tenantId),
7348
+ sql28`lower(${usersInIam.handle}) = lower(${body.handle})`
5784
7349
  )
5785
7350
  ).limit(1);
5786
7351
  if (handleExists) {
@@ -5811,8 +7376,8 @@ var updateUserHandler = async (c) => {
5811
7376
  }
5812
7377
  const [updated] = await database.update(usersInIam).set({
5813
7378
  ...updateData,
5814
- updatedAt: sql25`CURRENT_TIMESTAMP`
5815
- }).where(and46(eq50(usersInIam.id, id), eq50(usersInIam.tenantId, tenantId))).returning({
7379
+ updatedAt: sql28`CURRENT_TIMESTAMP`
7380
+ }).where(and55(eq60(usersInIam.id, id), eq60(usersInIam.tenantId, tenantId))).returning({
5816
7381
  id: usersInIam.id,
5817
7382
  tenantId: usersInIam.tenantId,
5818
7383
  fullName: usersInIam.fullName,
@@ -5865,6 +7430,7 @@ var createUserSchema = z11.object({
5865
7430
  fullName: z11.string().min(1),
5866
7431
  handle: z11.string().optional(),
5867
7432
  image: z11.string().url().optional(),
7433
+ password: z11.string().min(8).max(128).optional(),
5868
7434
  emailVerified: z11.boolean().default(false).optional(),
5869
7435
  phoneVerified: z11.boolean().default(false).optional()
5870
7436
  });
@@ -5895,6 +7461,46 @@ var deleteUserResponseSchema = z11.object({
5895
7461
  var errorResponseSchema9 = z11.object({
5896
7462
  error: z11.string()
5897
7463
  });
7464
+ var inviteChannelSchema = z11.enum(["email", "sms"]);
7465
+ var inviteUserSchema = z11.object({
7466
+ email: z11.string().email().optional(),
7467
+ phone: z11.string().optional(),
7468
+ fullName: z11.string().min(1),
7469
+ handle: z11.string().optional(),
7470
+ image: z11.string().url().optional(),
7471
+ password: z11.string().min(8).max(128).optional(),
7472
+ emailVerified: z11.boolean().default(false).optional(),
7473
+ phoneVerified: z11.boolean().default(false).optional(),
7474
+ sendVia: z11.array(inviteChannelSchema).default([]).optional(),
7475
+ inviteUrl: z11.string().url().optional()
7476
+ }).refine((data) => data.email || data.phone, {
7477
+ message: "Either email or phone is required",
7478
+ path: ["email"]
7479
+ });
7480
+ var bulkInviteUsersSchema = z11.object({
7481
+ users: z11.array(inviteUserSchema).min(1)
7482
+ });
7483
+ var deliveryStateSchema = z11.enum(["sent", "skipped", "failed"]);
7484
+ var inviteDeliverySchema = z11.object({
7485
+ email: deliveryStateSchema.optional(),
7486
+ sms: deliveryStateSchema.optional()
7487
+ });
7488
+ var inviteUserResultSchema = z11.object({
7489
+ user: userSchema,
7490
+ delivery: inviteDeliverySchema,
7491
+ inviteUrl: z11.string().nullable(),
7492
+ hasPassword: z11.boolean()
7493
+ });
7494
+ var bulkInviteUsersResponseSchema = z11.object({
7495
+ invited: z11.array(inviteUserResultSchema),
7496
+ failed: z11.array(
7497
+ z11.object({
7498
+ index: z11.number().int().nonnegative(),
7499
+ identifier: z11.string().nullable(),
7500
+ error: z11.string()
7501
+ })
7502
+ )
7503
+ });
5898
7504
  var searchUsersQuerySchema = z11.object({
5899
7505
  search: z11.string().optional().describe("Search term"),
5900
7506
  limit: z11.coerce.number().int().positive().optional().default(20).describe("Limit")
@@ -6113,38 +7719,96 @@ var searchUsersRoute = createRoute14({
6113
7719
  }
6114
7720
  }
6115
7721
  });
6116
- var userRoutes = new OpenAPIHono14().openapi(listUsersRoute, listUsersHandler).openapi(getUserRoute, getUserHandler).openapi(createUserRoute, createUserHandler).openapi(updateUserRoute, updateUserHandler).openapi(deleteUserRoute, deleteUserHandler).openapi(banUserRoute, banUserHandler).openapi(searchUsersRoute, searchUsersHandler);
7722
+ var inviteUserRoute = createRoute14({
7723
+ method: "post",
7724
+ path: "/invite",
7725
+ tags: ["Users"],
7726
+ summary: "Invite a single user",
7727
+ request: {
7728
+ body: {
7729
+ content: {
7730
+ "application/json": {
7731
+ schema: inviteUserSchema
7732
+ }
7733
+ }
7734
+ }
7735
+ },
7736
+ responses: {
7737
+ 201: {
7738
+ content: {
7739
+ "application/json": {
7740
+ schema: inviteUserResultSchema
7741
+ }
7742
+ },
7743
+ description: "User invited"
7744
+ },
7745
+ 409: {
7746
+ content: {
7747
+ "application/json": {
7748
+ schema: errorResponseSchema9
7749
+ }
7750
+ },
7751
+ description: "User or handle already exists"
7752
+ }
7753
+ }
7754
+ });
7755
+ var bulkInviteUsersRoute = createRoute14({
7756
+ method: "post",
7757
+ path: "/invite/bulk",
7758
+ tags: ["Users"],
7759
+ summary: "Invite users in bulk",
7760
+ request: {
7761
+ body: {
7762
+ content: {
7763
+ "application/json": {
7764
+ schema: bulkInviteUsersSchema
7765
+ }
7766
+ }
7767
+ }
7768
+ },
7769
+ responses: {
7770
+ 200: {
7771
+ content: {
7772
+ "application/json": {
7773
+ schema: bulkInviteUsersResponseSchema
7774
+ }
7775
+ },
7776
+ description: "Bulk invite results"
7777
+ }
7778
+ }
7779
+ });
7780
+ var userRoutes = new OpenAPIHono14().openapi(listUsersRoute, listUsersHandler).openapi(getUserRoute, getUserHandler).openapi(createUserRoute, createUserHandler).openapi(updateUserRoute, updateUserHandler).openapi(deleteUserRoute, deleteUserHandler).openapi(banUserRoute, banUserHandler).openapi(searchUsersRoute, searchUsersHandler).openapi(inviteUserRoute, inviteUserHandler).openapi(bulkInviteUsersRoute, bulkInviteUsersHandler);
6117
7781
  var users_route_default = userRoutes;
6118
7782
 
6119
7783
  // src/routes/verifications/verifications.route.ts
6120
7784
  import { createRoute as createRoute15, OpenAPIHono as OpenAPIHono15 } from "@hono/zod-openapi";
6121
7785
 
6122
7786
  // src/routes/verifications/handler/invalidate-verification.ts
6123
- import { and as and47, eq as eq51 } from "drizzle-orm";
7787
+ import { and as and56, eq as eq61 } from "drizzle-orm";
6124
7788
  var invalidateVerificationHandler = async (c) => {
6125
7789
  const { id } = c.req.valid("param");
6126
7790
  const database = c.get("database");
6127
7791
  const tenantId = c.get("tenantId");
6128
7792
  const [existing] = await database.select().from(verificationsInIam).where(
6129
- and47(
6130
- eq51(verificationsInIam.id, id),
6131
- eq51(verificationsInIam.tenantId, tenantId)
7793
+ and56(
7794
+ eq61(verificationsInIam.id, id),
7795
+ eq61(verificationsInIam.tenantId, tenantId)
6132
7796
  )
6133
7797
  ).limit(1);
6134
7798
  if (!existing) {
6135
7799
  return c.json({ error: "Verification not found" }, 404);
6136
7800
  }
6137
7801
  await database.delete(verificationsInIam).where(
6138
- and47(
6139
- eq51(verificationsInIam.id, id),
6140
- eq51(verificationsInIam.tenantId, tenantId)
7802
+ and56(
7803
+ eq61(verificationsInIam.id, id),
7804
+ eq61(verificationsInIam.tenantId, tenantId)
6141
7805
  )
6142
7806
  );
6143
7807
  return c.json({ message: "Verification invalidated" }, 200);
6144
7808
  };
6145
7809
 
6146
7810
  // src/routes/verifications/handler/list-verifications.ts
6147
- import { and as and48, eq as eq52, sql as sql26 } from "drizzle-orm";
7811
+ import { and as and57, eq as eq62, sql as sql29 } from "drizzle-orm";
6148
7812
  var listVerificationsHandler = async (c) => {
6149
7813
  const query = c.req.valid("query");
6150
7814
  const database = c.get("database");
@@ -6152,27 +7816,27 @@ var listVerificationsHandler = async (c) => {
6152
7816
  const page = query.page || 1;
6153
7817
  const limit = query.limit || 20;
6154
7818
  const offset = (page - 1) * limit;
6155
- const conditions = [eq52(verificationsInIam.tenantId, tenantId)];
7819
+ const conditions = [eq62(verificationsInIam.tenantId, tenantId)];
6156
7820
  if (query.userId) {
6157
- conditions.push(eq52(verificationsInIam.userId, query.userId));
7821
+ conditions.push(eq62(verificationsInIam.userId, query.userId));
6158
7822
  }
6159
7823
  if (query.type) {
6160
- conditions.push(eq52(verificationsInIam.type, query.type));
7824
+ conditions.push(eq62(verificationsInIam.type, query.type));
6161
7825
  }
6162
7826
  if (query.status) {
6163
7827
  if (query.status === "active") {
6164
- conditions.push(sql26`${verificationsInIam.expiresAt} > CURRENT_TIMESTAMP`);
7828
+ conditions.push(sql29`${verificationsInIam.expiresAt} > CURRENT_TIMESTAMP`);
6165
7829
  } else if (query.status === "expired") {
6166
7830
  conditions.push(
6167
- sql26`${verificationsInIam.expiresAt} <= CURRENT_TIMESTAMP`
7831
+ sql29`${verificationsInIam.expiresAt} <= CURRENT_TIMESTAMP`
6168
7832
  );
6169
7833
  } else if (query.status === "consumed") {
6170
- conditions.push(sql26`${verificationsInIam.attempt} >= 3`);
7834
+ conditions.push(sql29`${verificationsInIam.attempt} >= 3`);
6171
7835
  }
6172
7836
  }
6173
7837
  const [verifications, totalResult] = await Promise.all([
6174
- database.select().from(verificationsInIam).where(and48(...conditions)).limit(limit).offset(offset),
6175
- database.select({ count: sql26`count(*)` }).from(verificationsInIam).where(and48(...conditions))
7838
+ database.select().from(verificationsInIam).where(and57(...conditions)).limit(limit).offset(offset),
7839
+ database.select({ count: sql29`count(*)` }).from(verificationsInIam).where(and57(...conditions))
6176
7840
  ]);
6177
7841
  const total = Number(totalResult[0]?.count || 0);
6178
7842
  return c.json({ verifications, total, page, limit }, 200);
@@ -6473,7 +8137,7 @@ var createSessionMiddleware = () => {
6473
8137
  // src/middlewares/tenant-middleware.ts
6474
8138
  import { logger as logger3 } from "@mesob/common";
6475
8139
  import { createMiddleware as createMiddleware2 } from "hono/factory";
6476
- import { HTTPException as HTTPException4 } from "hono/http-exception";
8140
+ import { HTTPException as HTTPException5 } from "hono/http-exception";
6477
8141
  function resolveHost(hostHeader, forwardedHost) {
6478
8142
  const hostHeaderStr = hostHeader || "";
6479
8143
  const forwardedHostStr = forwardedHost || "";
@@ -6537,7 +8201,7 @@ var createTenantMiddleware = (database, config) => {
6537
8201
  );
6538
8202
  c.set("host", host);
6539
8203
  if (!host) {
6540
- throw new HTTPException4(400, { message: "Missing Host header" });
8204
+ throw new HTTPException5(400, { message: "Missing Host header" });
6541
8205
  }
6542
8206
  let tenantId = null;
6543
8207
  let tenant = null;
@@ -6546,13 +8210,13 @@ var createTenantMiddleware = (database, config) => {
6546
8210
  tenantId = result.tenantId;
6547
8211
  tenant = result.tenant;
6548
8212
  } catch {
6549
- throw new HTTPException4(500, { message: "Tenant resolution failed" });
8213
+ throw new HTTPException5(500, { message: "Tenant resolution failed" });
6550
8214
  }
6551
8215
  c.set("tenantId", tenantId);
6552
8216
  c.set("tenant", tenant);
6553
8217
  const error = validateTenant(tenantId, tenant);
6554
8218
  if (error) {
6555
- throw new HTTPException4(404, { message: error });
8219
+ throw new HTTPException5(404, { message: error });
6556
8220
  }
6557
8221
  return await next();
6558
8222
  });