@mesob/auth-hono 0.3.4 → 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,14 +1321,18 @@ 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);
1236
1330
  if (pendingVerification) {
1237
- return { action: "pending", verificationId: pendingVerification.id };
1331
+ return {
1332
+ action: "pending",
1333
+ verificationId: pendingVerification.id,
1334
+ user: existingUser
1335
+ };
1238
1336
  }
1239
1337
  await deleteUnverifiedUser({ tx, userId: existingUser.id, tenantId });
1240
1338
  return { action: "proceed" };
@@ -1245,18 +1343,18 @@ var deleteUnverifiedUser = async ({
1245
1343
  tenantId
1246
1344
  }) => {
1247
1345
  await tx.delete(verificationsInIam).where(
1248
- and6(
1249
- eq6(verificationsInIam.userId, userId),
1250
- eq6(verificationsInIam.tenantId, tenantId)
1346
+ and7(
1347
+ eq7(verificationsInIam.userId, userId),
1348
+ eq7(verificationsInIam.tenantId, tenantId)
1251
1349
  )
1252
1350
  );
1253
1351
  await tx.delete(accountsInIam).where(
1254
- and6(
1255
- eq6(accountsInIam.userId, userId),
1256
- eq6(accountsInIam.tenantId, tenantId)
1352
+ and7(
1353
+ eq7(accountsInIam.userId, userId),
1354
+ eq7(accountsInIam.tenantId, tenantId)
1257
1355
  )
1258
1356
  );
1259
- 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)));
1260
1358
  };
1261
1359
  var createUserWithAccount = async ({
1262
1360
  tx,
@@ -1296,14 +1394,14 @@ var fetchUserForLogin = async ({
1296
1394
  userType
1297
1395
  }) => {
1298
1396
  const userTypeFilter = sql6`${usersInIam.userType} @> ARRAY[${userType}]::text[]`;
1299
- const whereClause = isEmail ? and6(
1300
- eq6(usersInIam.tenantId, tenantId),
1397
+ const whereClause = isEmail ? and7(
1398
+ eq7(usersInIam.tenantId, tenantId),
1301
1399
  userTypeFilter,
1302
1400
  sql6`lower(${usersInIam.email}) = lower(${identifier})`
1303
- ) : and6(
1304
- eq6(usersInIam.tenantId, tenantId),
1401
+ ) : and7(
1402
+ eq7(usersInIam.tenantId, tenantId),
1305
1403
  userTypeFilter,
1306
- eq6(usersInIam.phone, identifier)
1404
+ eq7(usersInIam.phone, identifier)
1307
1405
  );
1308
1406
  const [row] = await database.select({
1309
1407
  id: usersInIam.id,
@@ -1317,7 +1415,15 @@ var fetchUserForLogin = async ({
1317
1415
  phoneVerified: usersInIam.phoneVerified,
1318
1416
  lastSignInAt: usersInIam.lastSignInAt,
1319
1417
  bannedUntil: usersInIam.bannedUntil,
1320
- 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
+ )`
1321
1427
  }).from(usersInIam).where(whereClause).limit(1);
1322
1428
  return row || null;
1323
1429
  };
@@ -1339,46 +1445,14 @@ var fetchUserByIdWithRoles = async ({
1339
1445
  lastSignInAt: usersInIam.lastSignInAt,
1340
1446
  bannedUntil: usersInIam.bannedUntil,
1341
1447
  loginAttempt: usersInIam.loginAttempt,
1342
- roles: sql6`COALESCE(
1343
- array_to_json(array_agg(DISTINCT ${rolesInIam.id}) FILTER (WHERE ${userRolesInIam.id} IS NOT NULL)),
1344
- '[]'::json
1345
- )`,
1346
- roleCodes: sql6`COALESCE(
1347
- array_to_json(array_agg(DISTINCT ${rolesInIam.code}) FILTER (WHERE ${rolesInIam.code} IS NOT NULL)),
1348
- '[]'::json
1349
- )`,
1350
- permissions: sql6`COALESCE(
1351
- array_to_json(array_agg(DISTINCT ${permissionsInIam.id}) FILTER (WHERE ${permissionsInIam.id} IS NOT NULL)),
1352
- '[]'::json
1353
- )`
1354
- }).from(usersInIam).leftJoin(
1355
- userRolesInIam,
1356
- and6(
1357
- eq6(userRolesInIam.userId, usersInIam.id),
1358
- eq6(userRolesInIam.tenantId, tenantId)
1359
- )
1360
- ).leftJoin(
1361
- rolesInIam,
1362
- and6(
1363
- eq6(userRolesInIam.roleId, rolesInIam.id),
1364
- eq6(rolesInIam.tenantId, tenantId)
1365
- )
1366
- ).leftJoin(
1367
- rolePermissionsInIam,
1368
- and6(
1369
- eq6(rolePermissionsInIam.roleId, rolesInIam.id),
1370
- eq6(rolePermissionsInIam.tenantId, tenantId)
1371
- )
1372
- ).leftJoin(
1373
- permissionsInIam,
1374
- eq6(rolePermissionsInIam.permissionId, permissionsInIam.id)
1375
- ).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);
1376
1450
  return result || null;
1377
1451
  };
1378
1452
 
1379
1453
  // src/routes/auth/helper/verification.ts
1380
1454
  import { dayjs as dayjs2 } from "@mesob/common";
1381
- import { and as and7, desc, eq as eq7 } from "drizzle-orm";
1455
+ import { and as and8, desc, eq as eq8 } from "drizzle-orm";
1382
1456
  var createVerification = async ({
1383
1457
  tx,
1384
1458
  tenantId,
@@ -1444,10 +1518,10 @@ var checkVerificationResend = async ({
1444
1518
  id: verificationsInIam.id,
1445
1519
  createdAt: verificationsInIam.createdAt
1446
1520
  }).from(verificationsInIam).where(
1447
- and7(
1448
- eq7(verificationsInIam.tenantId, tenantId),
1449
- eq7(verificationsInIam.userId, userId),
1450
- eq7(verificationsInIam.type, type)
1521
+ and8(
1522
+ eq8(verificationsInIam.tenantId, tenantId),
1523
+ eq8(verificationsInIam.userId, userId),
1524
+ eq8(verificationsInIam.type, type)
1451
1525
  )
1452
1526
  ).orderBy(desc(verificationsInIam.createdAt)).limit(1);
1453
1527
  if (!verification) {
@@ -1469,10 +1543,10 @@ var handleEmailVerification = async ({
1469
1543
  return c.json({ error: "User email not found" }, 401);
1470
1544
  }
1471
1545
  await database.delete(verificationsInIam).where(
1472
- and7(
1473
- eq7(verificationsInIam.tenantId, tenantId),
1474
- eq7(verificationsInIam.userId, user.id),
1475
- eq7(verificationsInIam.type, "email-verification")
1546
+ and8(
1547
+ eq8(verificationsInIam.tenantId, tenantId),
1548
+ eq8(verificationsInIam.userId, user.id),
1549
+ eq8(verificationsInIam.type, "email-verification")
1476
1550
  )
1477
1551
  );
1478
1552
  const code = generateOtpCode(config.email.otpLength);
@@ -1507,7 +1581,7 @@ var handleEmailVerification = async ({
1507
1581
  }
1508
1582
  return c.json(
1509
1583
  {
1510
- user: normalizeUser(user),
1584
+ user: normalizeAuthUser(user),
1511
1585
  session: null,
1512
1586
  verificationId: verification.id,
1513
1587
  requiresVerification: true
@@ -1526,10 +1600,10 @@ var handlePhoneVerification = async ({
1526
1600
  return c.json({ error: "User phone not found" }, 401);
1527
1601
  }
1528
1602
  await database.delete(verificationsInIam).where(
1529
- and7(
1530
- eq7(verificationsInIam.tenantId, tenantId),
1531
- eq7(verificationsInIam.userId, user.id),
1532
- eq7(verificationsInIam.type, "phone-otp")
1603
+ and8(
1604
+ eq8(verificationsInIam.tenantId, tenantId),
1605
+ eq8(verificationsInIam.userId, user.id),
1606
+ eq8(verificationsInIam.type, "phone-otp")
1533
1607
  )
1534
1608
  );
1535
1609
  const code = generateOtpCode(config.phone.otpLength);
@@ -1564,7 +1638,7 @@ var handlePhoneVerification = async ({
1564
1638
  }
1565
1639
  return c.json(
1566
1640
  {
1567
- user: normalizeUser(user),
1641
+ user: normalizeAuthUser(user),
1568
1642
  session: null,
1569
1643
  verificationId: verification.id,
1570
1644
  requiresVerification: true
@@ -1574,177 +1648,178 @@ var handlePhoneVerification = async ({
1574
1648
  };
1575
1649
 
1576
1650
  // src/routes/auth/handler/sign-in.ts
1577
- var signInHandler = async (c) => {
1578
- const body = c.req.valid("json");
1579
- const config = c.get("config");
1580
- const database = c.get("database");
1581
- const tenantId = c.get("tenantId");
1582
- const resolvedTenantId = ensureTenantId(config, tenantId);
1583
- const { identifier, password, rememberMe = true } = body;
1584
- const isEmail = identifier.includes("@");
1585
- if (isEmail && !config.email.enabled) {
1586
- return c.json({ error: "Email authentication is disabled" }, 401);
1587
- }
1588
- if (!(isEmail || config.phone.enabled)) {
1589
- return c.json({ error: "Phone authentication is disabled" }, 401);
1590
- }
1591
- const user = await fetchUserForLogin({
1592
- database,
1593
- identifier,
1594
- tenantId: resolvedTenantId,
1595
- isEmail,
1596
- userType: config.userType
1597
- });
1598
- if (!user) {
1599
- 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,
1600
1669
  identifier,
1601
1670
  tenantId: resolvedTenantId,
1671
+ isEmail,
1602
1672
  userType: config.userType
1603
1673
  });
1604
- return c.json({ error: AUTH_ERRORS.UNAUTHORIZED }, 401);
1605
- }
1606
- if (user.bannedUntil && new Date(user.bannedUntil) > /* @__PURE__ */ new Date()) {
1607
- logger2.log("[sign-in] 401: account banned", {
1608
- userId: user.id,
1609
- bannedUntil: user.bannedUntil
1610
- });
1611
- return c.json(
1612
- {
1613
- 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,
1614
1685
  bannedUntil: user.bannedUntil
1615
- },
1616
- 401
1617
- );
1618
- }
1619
- const [account] = await database.select({
1620
- id: accountsInIam.id,
1621
- tenantId: accountsInIam.tenantId,
1622
- userId: accountsInIam.userId,
1623
- provider: accountsInIam.provider,
1624
- providerAccountId: accountsInIam.providerAccountId,
1625
- password: accountsInIam.password
1626
- }).from(accountsInIam).where(
1627
- and8(
1628
- eq8(accountsInIam.tenantId, resolvedTenantId),
1629
- eq8(accountsInIam.userId, user.id),
1630
- eq8(accountsInIam.provider, "credentials")
1631
- )
1632
- ).limit(1);
1633
- if (!account?.password) {
1634
- logger2.log("[sign-in] 401: no credentials account", { userId: user.id });
1635
- return c.json({ error: AUTH_ERRORS.UNAUTHORIZED }, 401);
1636
- }
1637
- const passwordValid = await verifyPassword(password, account.password);
1638
- if (!passwordValid) {
1639
- const newAttempt = (user.loginAttempt || 0) + 1;
1640
- const updateData = {
1641
- loginAttempt: newAttempt
1642
- };
1643
- if (config.security && newAttempt >= config.security.maxLoginAttempts) {
1644
- 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
+ });
1645
1760
  }
1646
- await database.update(usersInIam).set(updateData).where(
1647
- and8(
1648
- eq8(usersInIam.id, user.id),
1649
- 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)
1650
1765
  )
1651
1766
  );
1652
- 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,
1653
1776
  userId: user.id,
1654
- 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
1655
1789
  });
1656
- return c.json({ error: AUTH_ERRORS.UNAUTHORIZED }, 401);
1657
- }
1658
- const isVerified = isEmail ? user.emailVerified : user.phoneVerified;
1659
- if (isEmail && config.email.required && !isVerified) {
1660
- return handleEmailVerification({
1661
- c,
1662
- user: { ...user, roles: [] },
1663
- config,
1664
- database,
1665
- tenantId: resolvedTenantId
1790
+ setSessionCookie(c, sessionToken, config, {
1791
+ expires: new Date(expiresAt)
1666
1792
  });
1667
- }
1668
- if (!isEmail && config.phone.required && !isVerified) {
1669
- return handlePhoneVerification({
1670
- c,
1671
- user: { ...user, roles: [] },
1672
- config,
1793
+ const fullUser = await fetchUserByIdWithRoles({
1673
1794
  database,
1795
+ userId: user.id,
1674
1796
  tenantId: resolvedTenantId
1675
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
+ );
1676
1809
  }
1677
- if (config.session.maxPerUser) {
1678
- await cleanupOldSessions({
1679
- database,
1680
- userId: user.id,
1681
- tenantId: resolvedTenantId,
1682
- maxSessions: config.session.maxPerUser
1683
- });
1684
- }
1685
- await database.update(usersInIam).set({ lastSignInAt: (/* @__PURE__ */ new Date()).toISOString(), loginAttempt: 0 }).where(
1686
- and8(
1687
- eq8(usersInIam.id, user.id),
1688
- eq8(usersInIam.tenantId, resolvedTenantId)
1689
- )
1690
- );
1691
- const sessionToken = generateToken();
1692
- const hashedToken = await hashToken(sessionToken, config.secret);
1693
- const sessionDuration = getSessionDuration({
1694
- sessionConfig: config.session,
1695
- rememberMe
1696
- });
1697
- const expiresAt = addDuration(sessionDuration);
1698
- const [session] = await database.insert(sessionsInIam).values({
1699
- tenantId: resolvedTenantId,
1700
- userId: user.id,
1701
- token: hashedToken,
1702
- expiresAt,
1703
- userAgent: c.req.header("user-agent") || null,
1704
- ip: c.req.header("cf-connecting-ip") || c.req.header("x-forwarded-for") || null,
1705
- meta: { action: "sign-in", rememberMe }
1706
- }).returning({
1707
- id: sessionsInIam.id,
1708
- tenantId: sessionsInIam.tenantId,
1709
- userId: sessionsInIam.userId,
1710
- expiresAt: sessionsInIam.expiresAt,
1711
- createdAt: sessionsInIam.createdAt,
1712
- meta: sessionsInIam.meta
1713
- });
1714
- setSessionCookie(c, sessionToken, config, {
1715
- expires: new Date(expiresAt)
1716
- });
1717
- const fullUser = await fetchUserByIdWithRoles({
1718
- database,
1719
- userId: user.id,
1720
- tenantId: resolvedTenantId
1721
- });
1722
- return c.json(
1723
- {
1724
- user: normalizeUser(fullUser ?? { ...user, roles: [] }),
1725
- session: {
1726
- id: session.id,
1727
- expiresAt: session.expiresAt,
1728
- createdAt: session.createdAt,
1729
- meta: session.meta
1730
- },
1731
- sessionExpiresAt: session.expiresAt
1732
- },
1733
- 200
1734
- );
1735
- };
1736
-
1737
- // src/routes/auth/handler/sign-out.ts
1738
- import { and as and9, eq as eq9, gt as gt4 } from "drizzle-orm";
1739
- import { getCookie } from "hono/cookie";
1740
- var signOutHandler = async (c) => {
1741
- const config = c.get("config");
1742
- const database = c.get("database");
1743
- const tenantId = c.get("tenantId");
1744
- ensureTenantId(config, tenantId);
1745
- const sessionToken = getCookie(c, getSessionCookieName(config));
1746
- if (!sessionToken) {
1747
- 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);
1748
1823
  }
1749
1824
  const hashedToken = await hashToken(sessionToken, config.secret);
1750
1825
  const [session] = await database.select({
@@ -1757,16 +1832,16 @@ var signOutHandler = async (c) => {
1757
1832
  userAgent: sessionsInIam.userAgent,
1758
1833
  ip: sessionsInIam.ip
1759
1834
  }).from(sessionsInIam).where(
1760
- and9(
1761
- eq9(sessionsInIam.token, hashedToken),
1835
+ and10(
1836
+ eq10(sessionsInIam.token, hashedToken),
1762
1837
  gt4(sessionsInIam.expiresAt, (/* @__PURE__ */ new Date()).toISOString())
1763
1838
  )
1764
1839
  ).limit(1);
1765
1840
  if (session) {
1766
1841
  await database.delete(sessionsInIam).where(
1767
- and9(
1768
- eq9(sessionsInIam.id, session.id),
1769
- eq9(sessionsInIam.tenantId, session.tenantId)
1842
+ and10(
1843
+ eq10(sessionsInIam.id, session.id),
1844
+ eq10(sessionsInIam.tenantId, session.tenantId)
1770
1845
  )
1771
1846
  );
1772
1847
  }
@@ -1839,7 +1914,8 @@ var signUpHandler = async (c) => {
1839
1914
  if (status.action === "pending") {
1840
1915
  return {
1841
1916
  type: "pending",
1842
- verificationId: status.verificationId
1917
+ verificationId: status.verificationId,
1918
+ user: status.user
1843
1919
  };
1844
1920
  }
1845
1921
  const handle = generateHandle();
@@ -1917,11 +1993,12 @@ var signUpHandler = async (c) => {
1917
1993
  if (result.type === "pending") {
1918
1994
  return c.json(
1919
1995
  {
1920
- error: "Verification pending. Please verify or request a new code.",
1996
+ user: normalizeAuthUser(result.user),
1997
+ session: null,
1921
1998
  verificationId: result.verificationId,
1922
1999
  requiresVerification: true
1923
2000
  },
1924
- 409
2001
+ 201
1925
2002
  );
1926
2003
  }
1927
2004
  if (result.type === "verification") {
@@ -1935,7 +2012,7 @@ var signUpHandler = async (c) => {
1935
2012
  });
1936
2013
  return c.json(
1937
2014
  {
1938
- user: normalizeUser(result.user),
2015
+ user: normalizeAuthUser(result.user),
1939
2016
  session: null,
1940
2017
  verificationId: result.verificationId,
1941
2018
  requiresVerification: true
@@ -1948,8 +2025,11 @@ var signUpHandler = async (c) => {
1948
2025
  });
1949
2026
  return c.json(
1950
2027
  {
1951
- user: normalizeUser(result.user),
1952
- session: { id: result.sessionId, expiresAt: result.expiresAt },
2028
+ user: normalizeAuthUser(result.user),
2029
+ session: normalizeAuthSession({
2030
+ id: result.sessionId,
2031
+ expiresAt: result.expiresAt
2032
+ }),
1953
2033
  sessionExpiresAt: result.expiresAt
1954
2034
  },
1955
2035
  201
@@ -2152,26 +2232,26 @@ var createDomainHandler = async (c) => {
2152
2232
  };
2153
2233
 
2154
2234
  // src/routes/domains/handler/delete-domain.ts
2155
- import { and as and10, eq as eq10 } from "drizzle-orm";
2235
+ import { and as and11, eq as eq11 } from "drizzle-orm";
2156
2236
  var deleteDomainHandler = async (c) => {
2157
2237
  const { id } = c.req.valid("param");
2158
2238
  const database = c.get("database");
2159
2239
  const tenantId = c.get("tenantId");
2160
- 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);
2161
2241
  if (!existing) {
2162
2242
  return c.json({ error: "Domain not found" }, 404);
2163
2243
  }
2164
- 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)));
2165
2245
  return c.json({ message: "Domain deleted" }, 200);
2166
2246
  };
2167
2247
 
2168
2248
  // src/routes/domains/handler/get-domain.ts
2169
- import { and as and11, eq as eq11 } from "drizzle-orm";
2249
+ import { and as and12, eq as eq12 } from "drizzle-orm";
2170
2250
  var getDomainHandler = async (c) => {
2171
2251
  const { id } = c.req.valid("param");
2172
2252
  const database = c.get("database");
2173
2253
  const tenantId = c.get("tenantId");
2174
- 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);
2175
2255
  if (!domain) {
2176
2256
  return c.json({ error: "Domain not found" }, 404);
2177
2257
  }
@@ -2179,7 +2259,7 @@ var getDomainHandler = async (c) => {
2179
2259
  };
2180
2260
 
2181
2261
  // src/routes/domains/handler/list-domains.ts
2182
- 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";
2183
2263
  var listDomainsHandler = async (c) => {
2184
2264
  const query = c.req.valid("query");
2185
2265
  const database = c.get("database");
@@ -2187,26 +2267,26 @@ var listDomainsHandler = async (c) => {
2187
2267
  const page = query.page || 1;
2188
2268
  const limit = query.limit || 20;
2189
2269
  const offset = (page - 1) * limit;
2190
- const conditions = [eq12(domainsInIam.tenantId, tenantId)];
2270
+ const conditions = [eq13(domainsInIam.tenantId, tenantId)];
2191
2271
  if (query.status) {
2192
- conditions.push(eq12(domainsInIam.status, query.status));
2272
+ conditions.push(eq13(domainsInIam.status, query.status));
2193
2273
  }
2194
2274
  const [domains, totalResult] = await Promise.all([
2195
- database.select().from(domainsInIam).where(and12(...conditions)).limit(limit).offset(offset),
2196
- 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))
2197
2277
  ]);
2198
2278
  const total = Number(totalResult[0]?.count || 0);
2199
2279
  return c.json({ domains, total, page, limit });
2200
2280
  };
2201
2281
 
2202
2282
  // src/routes/domains/handler/update-domain.ts
2203
- 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";
2204
2284
  var updateDomainHandler = async (c) => {
2205
2285
  const { id } = c.req.valid("param");
2206
2286
  const body = c.req.valid("json");
2207
2287
  const database = c.get("database");
2208
2288
  const tenantId = c.get("tenantId");
2209
- 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);
2210
2290
  if (!existing) {
2211
2291
  return c.json({ error: "Domain not found" }, 404);
2212
2292
  }
@@ -2226,7 +2306,7 @@ var updateDomainHandler = async (c) => {
2226
2306
  const [updated] = await database.update(domainsInIam).set({
2227
2307
  ...updateData,
2228
2308
  updatedAt: sql8`CURRENT_TIMESTAMP`
2229
- }).where(and13(eq13(domainsInIam.id, id), eq13(domainsInIam.tenantId, tenantId))).returning();
2309
+ }).where(and14(eq14(domainsInIam.id, id), eq14(domainsInIam.tenantId, tenantId))).returning();
2230
2310
  if (!updated) {
2231
2311
  return c.json({ error: "Domain not found" }, 404);
2232
2312
  }
@@ -2234,19 +2314,19 @@ var updateDomainHandler = async (c) => {
2234
2314
  };
2235
2315
 
2236
2316
  // src/routes/domains/handler/verify-domain.ts
2237
- 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";
2238
2318
  var verifyDomainHandler = async (c) => {
2239
2319
  const { id } = c.req.valid("param");
2240
2320
  const database = c.get("database");
2241
2321
  const tenantId = c.get("tenantId");
2242
- 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);
2243
2323
  if (!existing) {
2244
2324
  return c.json({ error: "Domain not found" }, 404);
2245
2325
  }
2246
2326
  const [updated] = await database.update(domainsInIam).set({
2247
2327
  status: "active",
2248
2328
  updatedAt: sql9`CURRENT_TIMESTAMP`
2249
- }).where(and14(eq14(domainsInIam.id, id), eq14(domainsInIam.tenantId, tenantId))).returning();
2329
+ }).where(and15(eq15(domainsInIam.id, id), eq15(domainsInIam.tenantId, tenantId))).returning();
2250
2330
  if (!updated) {
2251
2331
  return c.json({ error: "Domain not found" }, 404);
2252
2332
  }
@@ -2420,7 +2500,7 @@ var domains_route_default = domainRoutes;
2420
2500
  import { createRoute as createRoute3, OpenAPIHono as OpenAPIHono3 } from "@hono/zod-openapi";
2421
2501
 
2422
2502
  // src/routes/email/handler/verification-confirm.ts
2423
- import { and as and15, eq as eq15 } from "drizzle-orm";
2503
+ import { and as and16, eq as eq16 } from "drizzle-orm";
2424
2504
  var emailVerificationConfirmHandler = async (c) => {
2425
2505
  const body = c.req.valid("json");
2426
2506
  const config = c.get("config");
@@ -2430,9 +2510,9 @@ var emailVerificationConfirmHandler = async (c) => {
2430
2510
  const { verificationId, code } = body;
2431
2511
  const result = await withTransaction(database, async (tx) => {
2432
2512
  const [verification] = await tx.select().from(verificationsInIam).where(
2433
- and15(
2434
- eq15(verificationsInIam.id, verificationId),
2435
- eq15(verificationsInIam.tenantId, resolvedTenantId)
2513
+ and16(
2514
+ eq16(verificationsInIam.id, verificationId),
2515
+ eq16(verificationsInIam.tenantId, resolvedTenantId)
2436
2516
  )
2437
2517
  ).limit(1);
2438
2518
  if (!verification || verification.type !== "email-verification") {
@@ -2448,7 +2528,7 @@ var emailVerificationConfirmHandler = async (c) => {
2448
2528
  };
2449
2529
  }
2450
2530
  if ((verification.attempt || 0) >= config.email.maxAttempts) {
2451
- await tx.delete(verificationsInIam).where(eq15(verificationsInIam.id, verificationId));
2531
+ await tx.delete(verificationsInIam).where(eq16(verificationsInIam.id, verificationId));
2452
2532
  return {
2453
2533
  status: "error",
2454
2534
  error: AUTH_ERRORS.TOO_MANY_ATTEMPTS
@@ -2456,7 +2536,7 @@ var emailVerificationConfirmHandler = async (c) => {
2456
2536
  }
2457
2537
  const hashedCode = await hashToken(code, config.secret);
2458
2538
  if (verification.code !== hashedCode) {
2459
- 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));
2460
2540
  return {
2461
2541
  status: "error",
2462
2542
  error: AUTH_ERRORS.VERIFICATION_MISMATCH
@@ -2466,12 +2546,12 @@ var emailVerificationConfirmHandler = async (c) => {
2466
2546
  emailVerified: true,
2467
2547
  lastSignInAt: (/* @__PURE__ */ new Date()).toISOString()
2468
2548
  }).where(
2469
- and15(
2470
- eq15(usersInIam.id, verification.userId),
2471
- eq15(usersInIam.tenantId, resolvedTenantId)
2549
+ and16(
2550
+ eq16(usersInIam.id, verification.userId),
2551
+ eq16(usersInIam.tenantId, resolvedTenantId)
2472
2552
  )
2473
2553
  );
2474
- await tx.delete(verificationsInIam).where(eq15(verificationsInIam.id, verificationId));
2554
+ await tx.delete(verificationsInIam).where(eq16(verificationsInIam.id, verificationId));
2475
2555
  const user = await fetchUserWithRoles({
2476
2556
  database: tx,
2477
2557
  userId: verification.userId,
@@ -2506,14 +2586,8 @@ var emailVerificationConfirmHandler = async (c) => {
2506
2586
  });
2507
2587
  return c.json(
2508
2588
  {
2509
- user: normalizeUser(result.user),
2510
- session: {
2511
- id: result.session.id,
2512
- expiresAt: result.session.expiresAt,
2513
- createdAt: result.session.createdAt,
2514
- userAgent: result.session.userAgent,
2515
- ip: result.session.ip
2516
- },
2589
+ user: normalizeAuthUser(result.user),
2590
+ session: normalizeAuthSession(result.session),
2517
2591
  sessionExpiresAt: result.session.expiresAt
2518
2592
  },
2519
2593
  200
@@ -2521,7 +2595,7 @@ var emailVerificationConfirmHandler = async (c) => {
2521
2595
  };
2522
2596
 
2523
2597
  // src/routes/email/handler/verification-request.ts
2524
- 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";
2525
2599
  var emailVerificationRequestHandler = async (c) => {
2526
2600
  const body = c.req.valid("json");
2527
2601
  const config = c.get("config");
@@ -2542,8 +2616,8 @@ var emailVerificationRequestHandler = async (c) => {
2542
2616
  let userId = user?.id;
2543
2617
  if (!userId) {
2544
2618
  const [result] = await database.select({ id: usersInIam.id }).from(usersInIam).where(
2545
- and16(
2546
- eq16(usersInIam.tenantId, resolvedTenantId),
2619
+ and17(
2620
+ eq17(usersInIam.tenantId, resolvedTenantId),
2547
2621
  sql10`lower(${usersInIam.email}) = lower(${email})`
2548
2622
  )
2549
2623
  ).limit(1);
@@ -2569,10 +2643,10 @@ var emailVerificationRequestHandler = async (c) => {
2569
2643
  );
2570
2644
  }
2571
2645
  await database.delete(verificationsInIam).where(
2572
- and16(
2573
- eq16(verificationsInIam.tenantId, resolvedTenantId),
2574
- eq16(verificationsInIam.userId, userId),
2575
- eq16(verificationsInIam.type, "email-verification")
2646
+ and17(
2647
+ eq17(verificationsInIam.tenantId, resolvedTenantId),
2648
+ eq17(verificationsInIam.userId, userId),
2649
+ eq17(verificationsInIam.type, "email-verification")
2576
2650
  )
2577
2651
  );
2578
2652
  const code = generateOtpCode(config.email.otpLength);
@@ -2604,11 +2678,11 @@ var emailVerificationRequestHandler = async (c) => {
2604
2678
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
2605
2679
  reason: "replaced"
2606
2680
  }).where(
2607
- and16(
2608
- eq16(accountChangesInIam.tenantId, resolvedTenantId),
2609
- eq16(accountChangesInIam.userId, user.id),
2610
- eq16(accountChangesInIam.changeType, "email"),
2611
- 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")
2612
2686
  )
2613
2687
  );
2614
2688
  await database.insert(accountChangesInIam).values({
@@ -2723,7 +2797,7 @@ var email_route_default = emailRoutes;
2723
2797
  import { createRoute as createRoute4, OpenAPIHono as OpenAPIHono4 } from "@hono/zod-openapi";
2724
2798
 
2725
2799
  // src/routes/password/handler/change.ts
2726
- 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";
2727
2801
  import { getCookie as getCookie2 } from "hono/cookie";
2728
2802
  var changePasswordHandler = async (c) => {
2729
2803
  const body = c.req.valid("json");
@@ -2741,10 +2815,10 @@ var changePasswordHandler = async (c) => {
2741
2815
  const resolvedTenantId = ensureTenantId(config, tenantId);
2742
2816
  const { currentPassword, newPassword } = body;
2743
2817
  const [account] = await database.select().from(accountsInIam).where(
2744
- and17(
2745
- eq17(accountsInIam.tenantId, resolvedTenantId),
2746
- eq17(accountsInIam.userId, userId),
2747
- eq17(accountsInIam.provider, "credentials")
2818
+ and18(
2819
+ eq18(accountsInIam.tenantId, resolvedTenantId),
2820
+ eq18(accountsInIam.userId, userId),
2821
+ eq18(accountsInIam.provider, "credentials")
2748
2822
  )
2749
2823
  ).limit(1);
2750
2824
  if (!account?.password) {
@@ -2756,19 +2830,19 @@ var changePasswordHandler = async (c) => {
2756
2830
  }
2757
2831
  const passwordHash = await hashPassword(newPassword);
2758
2832
  await database.update(accountsInIam).set({ password: passwordHash }).where(
2759
- and17(
2760
- eq17(accountsInIam.tenantId, resolvedTenantId),
2761
- eq17(accountsInIam.userId, userId),
2762
- eq17(accountsInIam.provider, "credentials")
2833
+ and18(
2834
+ eq18(accountsInIam.tenantId, resolvedTenantId),
2835
+ eq18(accountsInIam.userId, userId),
2836
+ eq18(accountsInIam.provider, "credentials")
2763
2837
  )
2764
2838
  );
2765
2839
  const currentSessionToken = getCookie2(c, getSessionCookieName(config));
2766
2840
  if (currentSessionToken) {
2767
2841
  const hashedToken = await hashToken(currentSessionToken, config.secret);
2768
2842
  await database.delete(sessionsInIam).where(
2769
- and17(
2770
- eq17(sessionsInIam.tenantId, resolvedTenantId),
2771
- eq17(sessionsInIam.userId, userId),
2843
+ and18(
2844
+ eq18(sessionsInIam.tenantId, resolvedTenantId),
2845
+ eq18(sessionsInIam.userId, userId),
2772
2846
  gt5(sessionsInIam.expiresAt, (/* @__PURE__ */ new Date()).toISOString()),
2773
2847
  ne(sessionsInIam.token, hashedToken)
2774
2848
  )
@@ -2778,7 +2852,7 @@ var changePasswordHandler = async (c) => {
2778
2852
  };
2779
2853
 
2780
2854
  // src/routes/password/handler/forgot.ts
2781
- 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";
2782
2856
  var forgotPasswordHandler = async (c) => {
2783
2857
  const body = c.req.valid("json");
2784
2858
  const config = c.get("config");
@@ -2812,19 +2886,19 @@ var forgotPasswordHandler = async (c) => {
2812
2886
  )`
2813
2887
  }).from(usersInIam).leftJoin(
2814
2888
  userRolesInIam,
2815
- and18(
2816
- eq18(userRolesInIam.userId, usersInIam.id),
2817
- eq18(userRolesInIam.tenantId, resolvedTenantId)
2889
+ and19(
2890
+ eq19(userRolesInIam.userId, usersInIam.id),
2891
+ eq19(userRolesInIam.tenantId, resolvedTenantId)
2818
2892
  )
2819
2893
  ).leftJoin(
2820
2894
  rolesInIam,
2821
- and18(
2822
- eq18(userRolesInIam.roleId, rolesInIam.id),
2823
- eq18(rolesInIam.tenantId, resolvedTenantId)
2895
+ and19(
2896
+ eq19(userRolesInIam.roleId, rolesInIam.id),
2897
+ eq19(rolesInIam.tenantId, resolvedTenantId)
2824
2898
  )
2825
2899
  ).where(
2826
- and18(
2827
- eq18(usersInIam.tenantId, resolvedTenantId),
2900
+ and19(
2901
+ eq19(usersInIam.tenantId, resolvedTenantId),
2828
2902
  sql11`lower(${usersInIam.email}) = lower(${identifier})`
2829
2903
  )
2830
2904
  ).groupBy(usersInIam.id).limit(1);
@@ -2847,20 +2921,20 @@ var forgotPasswordHandler = async (c) => {
2847
2921
  )`
2848
2922
  }).from(usersInIam).leftJoin(
2849
2923
  userRolesInIam,
2850
- and18(
2851
- eq18(userRolesInIam.userId, usersInIam.id),
2852
- eq18(userRolesInIam.tenantId, resolvedTenantId)
2924
+ and19(
2925
+ eq19(userRolesInIam.userId, usersInIam.id),
2926
+ eq19(userRolesInIam.tenantId, resolvedTenantId)
2853
2927
  )
2854
2928
  ).leftJoin(
2855
2929
  rolesInIam,
2856
- and18(
2857
- eq18(userRolesInIam.roleId, rolesInIam.id),
2858
- eq18(rolesInIam.tenantId, resolvedTenantId)
2930
+ and19(
2931
+ eq19(userRolesInIam.roleId, rolesInIam.id),
2932
+ eq19(rolesInIam.tenantId, resolvedTenantId)
2859
2933
  )
2860
2934
  ).where(
2861
- and18(
2862
- eq18(usersInIam.tenantId, resolvedTenantId),
2863
- eq18(usersInIam.phone, identifier)
2935
+ and19(
2936
+ eq19(usersInIam.tenantId, resolvedTenantId),
2937
+ eq19(usersInIam.phone, identifier)
2864
2938
  )
2865
2939
  ).groupBy(usersInIam.id).limit(1);
2866
2940
  user = result || null;
@@ -2874,10 +2948,10 @@ var forgotPasswordHandler = async (c) => {
2874
2948
  return c.json({ message: "If account exists, reset code sent" }, 200);
2875
2949
  }
2876
2950
  await database.delete(verificationsInIam).where(
2877
- and18(
2878
- eq18(verificationsInIam.tenantId, resolvedTenantId),
2879
- eq18(verificationsInIam.userId, user.id),
2880
- eq18(verificationsInIam.type, "password-reset")
2951
+ and19(
2952
+ eq19(verificationsInIam.tenantId, resolvedTenantId),
2953
+ eq19(verificationsInIam.userId, user.id),
2954
+ eq19(verificationsInIam.type, "password-reset")
2881
2955
  )
2882
2956
  );
2883
2957
  const code = generateOtpCode(config.email.otpLength);
@@ -2916,10 +2990,10 @@ var forgotPasswordHandler = async (c) => {
2916
2990
  return c.json({ message: "If account exists, reset code sent" }, 200);
2917
2991
  }
2918
2992
  await database.delete(verificationsInIam).where(
2919
- and18(
2920
- eq18(verificationsInIam.tenantId, resolvedTenantId),
2921
- eq18(verificationsInIam.userId, user.id),
2922
- 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")
2923
2997
  )
2924
2998
  );
2925
2999
  const code = generateOtpCode(config.phone.otpLength);
@@ -2966,7 +3040,7 @@ var forgotPasswordHandler = async (c) => {
2966
3040
  };
2967
3041
 
2968
3042
  // src/routes/password/handler/reset.ts
2969
- 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";
2970
3044
 
2971
3045
  // src/routes/password/helper/session.ts
2972
3046
  var createPasswordResetSession = async ({
@@ -2993,14 +3067,8 @@ var createPasswordResetSession = async ({
2993
3067
  });
2994
3068
  return c.json(
2995
3069
  {
2996
- user: normalizeUser(user),
2997
- session: {
2998
- id: session.id,
2999
- expiresAt: session.expiresAt,
3000
- createdAt: session.createdAt,
3001
- userAgent: session.userAgent,
3002
- ip: session.ip
3003
- },
3070
+ user: normalizeAuthUser(user),
3071
+ session: normalizeAuthSession(session),
3004
3072
  sessionExpiresAt: session.expiresAt
3005
3073
  },
3006
3074
  200
@@ -3016,9 +3084,9 @@ var resetPasswordHandler = async (c) => {
3016
3084
  const resolvedTenantId = ensureTenantId(config, tenantId);
3017
3085
  const { verificationId, code, password } = body;
3018
3086
  const [verification] = await database.select().from(verificationsInIam).where(
3019
- and19(
3020
- eq19(verificationsInIam.id, verificationId),
3021
- eq19(verificationsInIam.tenantId, resolvedTenantId)
3087
+ and20(
3088
+ eq20(verificationsInIam.id, verificationId),
3089
+ eq20(verificationsInIam.tenantId, resolvedTenantId)
3022
3090
  )
3023
3091
  ).limit(1);
3024
3092
  if (!verification) {
@@ -3032,34 +3100,34 @@ var resetPasswordHandler = async (c) => {
3032
3100
  }
3033
3101
  const maxAttempts = verification.type === "password-reset" ? config.email.maxAttempts : config.phone.maxAttempts;
3034
3102
  if ((verification.attempt || 0) >= maxAttempts) {
3035
- await database.delete(verificationsInIam).where(eq19(verificationsInIam.id, verificationId));
3103
+ await database.delete(verificationsInIam).where(eq20(verificationsInIam.id, verificationId));
3036
3104
  return c.json({ error: AUTH_ERRORS.TOO_MANY_ATTEMPTS }, 400);
3037
3105
  }
3038
3106
  const hashedCode = await hashToken(code, config.secret);
3039
3107
  if (verification.code !== hashedCode) {
3040
- 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));
3041
3109
  return c.json({ error: AUTH_ERRORS.VERIFICATION_MISMATCH }, 400);
3042
3110
  }
3043
3111
  const passwordHash = await hashPassword(password);
3044
3112
  await database.update(accountsInIam).set({ password: passwordHash }).where(
3045
- and19(
3046
- eq19(accountsInIam.tenantId, resolvedTenantId),
3047
- eq19(accountsInIam.userId, verification.userId),
3048
- eq19(accountsInIam.provider, "credentials")
3113
+ and20(
3114
+ eq20(accountsInIam.tenantId, resolvedTenantId),
3115
+ eq20(accountsInIam.userId, verification.userId),
3116
+ eq20(accountsInIam.provider, "credentials")
3049
3117
  )
3050
3118
  );
3051
3119
  await database.delete(sessionsInIam).where(
3052
- and19(
3053
- eq19(sessionsInIam.tenantId, resolvedTenantId),
3054
- eq19(sessionsInIam.userId, verification.userId),
3120
+ and20(
3121
+ eq20(sessionsInIam.tenantId, resolvedTenantId),
3122
+ eq20(sessionsInIam.userId, verification.userId),
3055
3123
  gt6(sessionsInIam.expiresAt, (/* @__PURE__ */ new Date()).toISOString())
3056
3124
  )
3057
3125
  );
3058
- await database.delete(verificationsInIam).where(eq19(verificationsInIam.id, verificationId));
3126
+ await database.delete(verificationsInIam).where(eq20(verificationsInIam.id, verificationId));
3059
3127
  const [user] = await database.select().from(usersInIam).where(
3060
- and19(
3061
- eq19(usersInIam.id, verification.userId),
3062
- eq19(usersInIam.tenantId, resolvedTenantId)
3128
+ and20(
3129
+ eq20(usersInIam.id, verification.userId),
3130
+ eq20(usersInIam.tenantId, resolvedTenantId)
3063
3131
  )
3064
3132
  ).limit(1);
3065
3133
  if (!user) {
@@ -3074,8 +3142,97 @@ var resetPasswordHandler = async (c) => {
3074
3142
  });
3075
3143
  };
3076
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
+
3077
3234
  // src/routes/password/handler/verify.ts
3078
- import { and as and20, eq as eq20 } from "drizzle-orm";
3235
+ import { and as and22, eq as eq22 } from "drizzle-orm";
3079
3236
  var verifyPasswordHandler = async (c) => {
3080
3237
  const body = c.req.valid("json");
3081
3238
  const config = c.get("config");
@@ -3092,10 +3249,10 @@ var verifyPasswordHandler = async (c) => {
3092
3249
  const resolvedTenantId = ensureTenantId(config, tenantId);
3093
3250
  const { password } = body;
3094
3251
  const [account] = await database.select().from(accountsInIam).where(
3095
- and20(
3096
- eq20(accountsInIam.tenantId, resolvedTenantId),
3097
- eq20(accountsInIam.userId, userId),
3098
- eq20(accountsInIam.provider, "credentials")
3252
+ and22(
3253
+ eq22(accountsInIam.tenantId, resolvedTenantId),
3254
+ eq22(accountsInIam.userId, userId),
3255
+ eq22(accountsInIam.provider, "credentials")
3099
3256
  )
3100
3257
  ).limit(1);
3101
3258
  if (!account?.password) {
@@ -3217,56 +3374,375 @@ var changePasswordRoute = createRoute4({
3217
3374
  }
3218
3375
  }
3219
3376
  });
3220
- 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);
3221
3411
  var password_route_default = passwordRoutes;
3222
3412
 
3223
3413
  // src/routes/permissions/permissions.route.ts
3224
3414
  import { createRoute as createRoute5, OpenAPIHono as OpenAPIHono5 } from "@hono/zod-openapi";
3225
3415
 
3226
3416
  // src/routes/permissions/handler/get-permission.ts
3227
- import { eq as eq21 } from "drizzle-orm";
3417
+ import { getPermissionEntries } from "@mesob/common";
3418
+ import { eq as eq23 } from "drizzle-orm";
3228
3419
  var getPermissionHandler = async (c) => {
3229
3420
  const { id } = c.req.valid("param");
3230
3421
  const database = c.get("database");
3231
- 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);
3232
3424
  if (!permission) {
3233
- 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
+ );
3234
3443
  }
3235
3444
  return c.json({ permission }, 200);
3236
3445
  };
3237
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
+
3238
3515
  // src/routes/permissions/handler/list-permissions.ts
3239
- import { and as and21, ilike, sql as sql12 } from "drizzle-orm";
3240
3516
  var listPermissionsHandler = async (c) => {
3241
3517
  const query = c.req.valid("query");
3242
3518
  const database = c.get("database");
3519
+ const config = c.get("config");
3243
3520
  const page = query.page || 1;
3244
3521
  const limit = query.limit || 20;
3245
3522
  const offset = (page - 1) * limit;
3246
- const conditions = [];
3247
- if (query.application) {
3248
- conditions.push(
3249
- 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
+ )
3250
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
+ });
3251
3701
  }
3252
- if (query.feature) {
3253
- 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);
3254
3711
  }
3255
- const [permissions, totalResult] = await Promise.all([
3256
- database.select().from(permissionsInIam).where(conditions.length > 0 ? and21(...conditions) : void 0).limit(limit).offset(offset),
3257
- database.select({ count: sql12`count(*)` }).from(permissionsInIam).where(conditions.length > 0 ? and21(...conditions) : void 0)
3258
- ]);
3259
- const total = Number(totalResult[0]?.count || 0);
3260
- 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
+ );
3261
3723
  };
3262
3724
 
3263
3725
  // src/routes/permissions/permissions.schema.ts
3264
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
+ ];
3265
3739
  var listPermissionsQuerySchema = z3.object({
3266
3740
  page: z3.coerce.number().min(1).default(1).optional(),
3267
3741
  limit: z3.coerce.number().min(1).max(100).default(20).optional(),
3268
- application: z3.string().optional(),
3269
- 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()
3270
3746
  });
3271
3747
  var permissionIdParamSchema = z3.object({
3272
3748
  id: z3.string()
@@ -3278,6 +3754,10 @@ var permissionSchema = z3.object({
3278
3754
  application: z3.string(),
3279
3755
  feature: z3.string()
3280
3756
  });
3757
+ var seedPermissionsResponseSchema = z3.object({
3758
+ permissions: z3.array(permissionSchema),
3759
+ total: z3.number()
3760
+ });
3281
3761
  var listPermissionsResponseSchema = z3.object({
3282
3762
  permissions: z3.array(permissionSchema),
3283
3763
  total: z3.number(),
@@ -3338,14 +3818,38 @@ var getPermissionRoute = createRoute5({
3338
3818
  }
3339
3819
  }
3340
3820
  });
3341
- var permissionRoutes = new OpenAPIHono5().openapi(listPermissionsRoute, listPermissionsHandler).openapi(getPermissionRoute, getPermissionHandler);
3342
- var permissions_route_default = permissionRoutes;
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;
3343
3847
 
3344
3848
  // src/routes/phone/phone.route.ts
3345
3849
  import { createRoute as createRoute6, OpenAPIHono as OpenAPIHono6 } from "@hono/zod-openapi";
3346
3850
 
3347
3851
  // src/routes/phone/handler/verification-confirm.ts
3348
- import { and as and22, eq as eq22 } from "drizzle-orm";
3852
+ import { and as and24, eq as eq25 } from "drizzle-orm";
3349
3853
 
3350
3854
  // src/routes/phone/helper/session.ts
3351
3855
  var shouldCreateSession = (context) => context === "sign-in" || context === "change-phone" || context === "sign-up";
@@ -3361,9 +3865,9 @@ var phoneVerificationConfirmHandler = async (c) => {
3361
3865
  const { verificationId, code, context } = body;
3362
3866
  const result = await withTransaction(database, async (tx) => {
3363
3867
  const [verification] = await tx.select().from(verificationsInIam).where(
3364
- and22(
3365
- eq22(verificationsInIam.id, verificationId),
3366
- eq22(verificationsInIam.tenantId, resolvedTenantId)
3868
+ and24(
3869
+ eq25(verificationsInIam.id, verificationId),
3870
+ eq25(verificationsInIam.tenantId, resolvedTenantId)
3367
3871
  )
3368
3872
  ).limit(1);
3369
3873
  const expectedType = `phone-otp-${context}`;
@@ -3380,7 +3884,7 @@ var phoneVerificationConfirmHandler = async (c) => {
3380
3884
  };
3381
3885
  }
3382
3886
  if ((verification.attempt || 0) >= config.phone.maxAttempts) {
3383
- await tx.delete(verificationsInIam).where(eq22(verificationsInIam.id, verificationId));
3887
+ await tx.delete(verificationsInIam).where(eq25(verificationsInIam.id, verificationId));
3384
3888
  return {
3385
3889
  status: "error",
3386
3890
  error: AUTH_ERRORS.TOO_MANY_ATTEMPTS
@@ -3388,21 +3892,21 @@ var phoneVerificationConfirmHandler = async (c) => {
3388
3892
  }
3389
3893
  const hashedCode = await hashToken(code, config.secret);
3390
3894
  if (verification.code !== hashedCode) {
3391
- 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));
3392
3896
  return {
3393
3897
  status: "error",
3394
3898
  error: AUTH_ERRORS.VERIFICATION_MISMATCH
3395
3899
  };
3396
3900
  }
3397
- await tx.delete(verificationsInIam).where(eq22(verificationsInIam.id, verificationId));
3901
+ await tx.delete(verificationsInIam).where(eq25(verificationsInIam.id, verificationId));
3398
3902
  if (shouldCreateSession(context) && verification.userId) {
3399
3903
  await tx.update(usersInIam).set({
3400
3904
  phoneVerified: true,
3401
3905
  lastSignInAt: (/* @__PURE__ */ new Date()).toISOString()
3402
3906
  }).where(
3403
- and22(
3404
- eq22(usersInIam.id, verification.userId),
3405
- eq22(usersInIam.tenantId, resolvedTenantId)
3907
+ and24(
3908
+ eq25(usersInIam.id, verification.userId),
3909
+ eq25(usersInIam.tenantId, resolvedTenantId)
3406
3910
  )
3407
3911
  );
3408
3912
  }
@@ -3444,7 +3948,7 @@ var phoneVerificationConfirmHandler = async (c) => {
3444
3948
  if (!result.session) {
3445
3949
  return c.json(
3446
3950
  {
3447
- user: normalizeUser(result.user),
3951
+ user: normalizeAuthUser(result.user),
3448
3952
  session: null
3449
3953
  },
3450
3954
  200
@@ -3455,14 +3959,8 @@ var phoneVerificationConfirmHandler = async (c) => {
3455
3959
  });
3456
3960
  return c.json(
3457
3961
  {
3458
- user: normalizeUser(result.user),
3459
- session: {
3460
- id: result.session.id,
3461
- expiresAt: result.session.expiresAt,
3462
- createdAt: result.session.createdAt,
3463
- userAgent: result.session.userAgent,
3464
- ip: result.session.ip
3465
- },
3962
+ user: normalizeAuthUser(result.user),
3963
+ session: normalizeAuthSession(result.session),
3466
3964
  sessionExpiresAt: result.session.expiresAt
3467
3965
  },
3468
3966
  200
@@ -3470,7 +3968,7 @@ var phoneVerificationConfirmHandler = async (c) => {
3470
3968
  };
3471
3969
 
3472
3970
  // src/routes/phone/handler/verification-request.ts
3473
- import { and as and23, eq as eq23 } from "drizzle-orm";
3971
+ import { and as and25, eq as eq26 } from "drizzle-orm";
3474
3972
  var phoneVerificationRequestHandler = async (c) => {
3475
3973
  const body = c.req.valid("json");
3476
3974
  const config = c.get("config");
@@ -3495,9 +3993,9 @@ var phoneVerificationRequestHandler = async (c) => {
3495
3993
  let userId = user?.id;
3496
3994
  if (!userId) {
3497
3995
  const [result] = await database.select({ id: usersInIam.id }).from(usersInIam).where(
3498
- and23(
3499
- eq23(usersInIam.tenantId, resolvedTenantId),
3500
- eq23(usersInIam.phone, phone)
3996
+ and25(
3997
+ eq26(usersInIam.tenantId, resolvedTenantId),
3998
+ eq26(usersInIam.phone, phone)
3501
3999
  )
3502
4000
  ).limit(1);
3503
4001
  if (!result) {
@@ -3523,10 +4021,10 @@ var phoneVerificationRequestHandler = async (c) => {
3523
4021
  );
3524
4022
  }
3525
4023
  await database.delete(verificationsInIam).where(
3526
- and23(
3527
- eq23(verificationsInIam.tenantId, resolvedTenantId),
3528
- eq23(verificationsInIam.userId, userId),
3529
- eq23(verificationsInIam.type, verificationType)
4024
+ and25(
4025
+ eq26(verificationsInIam.tenantId, resolvedTenantId),
4026
+ eq26(verificationsInIam.userId, userId),
4027
+ eq26(verificationsInIam.type, verificationType)
3530
4028
  )
3531
4029
  );
3532
4030
  const code = generateOtpCode(config.phone.otpLength);
@@ -3558,11 +4056,11 @@ var phoneVerificationRequestHandler = async (c) => {
3558
4056
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
3559
4057
  reason: "replaced"
3560
4058
  }).where(
3561
- and23(
3562
- eq23(accountChangesInIam.tenantId, resolvedTenantId),
3563
- eq23(accountChangesInIam.userId, user.id),
3564
- eq23(accountChangesInIam.changeType, "phone"),
3565
- 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")
3566
4064
  )
3567
4065
  );
3568
4066
  await database.insert(accountChangesInIam).values({
@@ -3678,7 +4176,7 @@ import { createRoute as createRoute7, OpenAPIHono as OpenAPIHono7 } from "@hono/
3678
4176
  import { z as z4 } from "zod";
3679
4177
 
3680
4178
  // src/routes/profile/handler/account-change-pending.ts
3681
- 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";
3682
4180
  var accountChangePendingHandler = async (c) => {
3683
4181
  const config = c.get("config");
3684
4182
  const database = c.get("database");
@@ -3689,17 +4187,17 @@ var accountChangePendingHandler = async (c) => {
3689
4187
  }
3690
4188
  const resolvedTenantId = ensureTenantId(config, tenantId);
3691
4189
  await database.update(accountChangesInIam).set({ status: "expired" }).where(
3692
- and24(
3693
- eq24(accountChangesInIam.tenantId, resolvedTenantId),
3694
- eq24(accountChangesInIam.userId, userId),
4190
+ and26(
4191
+ eq27(accountChangesInIam.tenantId, resolvedTenantId),
4192
+ eq27(accountChangesInIam.userId, userId),
3695
4193
  sql13`${accountChangesInIam.expiresAt} < CURRENT_TIMESTAMP`
3696
4194
  )
3697
4195
  );
3698
4196
  const [accountChange] = await database.select().from(accountChangesInIam).where(
3699
- and24(
3700
- eq24(accountChangesInIam.tenantId, resolvedTenantId),
3701
- eq24(accountChangesInIam.userId, userId),
3702
- eq24(accountChangesInIam.status, "pending")
4197
+ and26(
4198
+ eq27(accountChangesInIam.tenantId, resolvedTenantId),
4199
+ eq27(accountChangesInIam.userId, userId),
4200
+ eq27(accountChangesInIam.status, "pending")
3703
4201
  )
3704
4202
  ).limit(1);
3705
4203
  if (!accountChange) {
@@ -3711,11 +4209,11 @@ var accountChangePendingHandler = async (c) => {
3711
4209
  id: verificationsInIam.id,
3712
4210
  expiresAt: verificationsInIam.expiresAt
3713
4211
  }).from(verificationsInIam).where(
3714
- and24(
3715
- eq24(verificationsInIam.tenantId, resolvedTenantId),
3716
- eq24(verificationsInIam.userId, userId),
3717
- eq24(verificationsInIam.type, "email-verification"),
3718
- 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),
3719
4217
  gt7(verificationsInIam.expiresAt, sql13`CURRENT_TIMESTAMP`)
3720
4218
  )
3721
4219
  ).limit(1);
@@ -3726,11 +4224,11 @@ var accountChangePendingHandler = async (c) => {
3726
4224
  id: verificationsInIam.id,
3727
4225
  expiresAt: verificationsInIam.expiresAt
3728
4226
  }).from(verificationsInIam).where(
3729
- and24(
3730
- eq24(verificationsInIam.tenantId, resolvedTenantId),
3731
- eq24(verificationsInIam.userId, userId),
3732
- eq24(verificationsInIam.type, "phone-otp-change-phone"),
3733
- 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),
3734
4232
  gt7(verificationsInIam.expiresAt, sql13`CURRENT_TIMESTAMP`)
3735
4233
  )
3736
4234
  ).limit(1);
@@ -3753,6 +4251,13 @@ var accountChangePendingHandler = async (c) => {
3753
4251
  );
3754
4252
  };
3755
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
+
3756
4261
  // src/routes/profile/handler/me.ts
3757
4262
  var meHandler = (c) => {
3758
4263
  const user = c.get("user");
@@ -3799,7 +4304,7 @@ var sessionHandler = (c) => {
3799
4304
  };
3800
4305
 
3801
4306
  // src/routes/profile/handler/update.ts
3802
- 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";
3803
4308
  var updateProfileHandler = async (c) => {
3804
4309
  const body = c.req.valid("json");
3805
4310
  const config = c.get("config");
@@ -3822,7 +4327,7 @@ var updateProfileHandler = async (c) => {
3822
4327
  ...updateData,
3823
4328
  updatedAt: sql14`CURRENT_TIMESTAMP`
3824
4329
  }).where(
3825
- and25(eq25(usersInIam.id, userId), eq25(usersInIam.tenantId, resolvedTenantId))
4330
+ and27(eq28(usersInIam.id, userId), eq28(usersInIam.tenantId, resolvedTenantId))
3826
4331
  ).returning({
3827
4332
  id: usersInIam.id,
3828
4333
  tenantId: usersInIam.tenantId,
@@ -3842,7 +4347,7 @@ var updateProfileHandler = async (c) => {
3842
4347
  };
3843
4348
 
3844
4349
  // src/routes/profile/handler/update-email.ts
3845
- 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";
3846
4351
  var updateEmailHandler = async (c) => {
3847
4352
  const body = c.req.valid("json");
3848
4353
  const config = c.get("config");
@@ -3860,9 +4365,9 @@ var updateEmailHandler = async (c) => {
3860
4365
  const resolvedTenantId = ensureTenantId(config, tenantId);
3861
4366
  if (user.email && session?.id) {
3862
4367
  await database.delete(sessionsInIam).where(
3863
- and26(
3864
- eq26(sessionsInIam.tenantId, resolvedTenantId),
3865
- eq26(sessionsInIam.userId, userId),
4368
+ and28(
4369
+ eq29(sessionsInIam.tenantId, resolvedTenantId),
4370
+ eq29(sessionsInIam.userId, userId),
3866
4371
  ne2(sessionsInIam.id, session.id)
3867
4372
  )
3868
4373
  );
@@ -3872,7 +4377,7 @@ var updateEmailHandler = async (c) => {
3872
4377
  emailVerified: true,
3873
4378
  updatedAt: sql15`CURRENT_TIMESTAMP`
3874
4379
  }).where(
3875
- and26(eq26(usersInIam.id, userId), eq26(usersInIam.tenantId, resolvedTenantId))
4380
+ and28(eq29(usersInIam.id, userId), eq29(usersInIam.tenantId, resolvedTenantId))
3876
4381
  ).returning({
3877
4382
  id: usersInIam.id,
3878
4383
  tenantId: usersInIam.tenantId,
@@ -3889,25 +4394,25 @@ var updateEmailHandler = async (c) => {
3889
4394
  return c.json({ error: "User not found" }, 404);
3890
4395
  }
3891
4396
  await database.update(accountChangesInIam).set({ status: "applied" }).where(
3892
- and26(
3893
- eq26(accountChangesInIam.tenantId, resolvedTenantId),
3894
- eq26(accountChangesInIam.userId, userId),
3895
- eq26(accountChangesInIam.changeType, "email"),
3896
- 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)
3897
4402
  )
3898
4403
  );
3899
4404
  await database.update(accountsInIam).set({ providerAccountId: body.email }).where(
3900
- and26(
3901
- eq26(accountsInIam.tenantId, resolvedTenantId),
3902
- eq26(accountsInIam.userId, userId),
3903
- eq26(accountsInIam.provider, "credentials")
4405
+ and28(
4406
+ eq29(accountsInIam.tenantId, resolvedTenantId),
4407
+ eq29(accountsInIam.userId, userId),
4408
+ eq29(accountsInIam.provider, "credentials")
3904
4409
  )
3905
4410
  );
3906
4411
  return c.json({ user: normalizeUser(updatedUser) }, 200);
3907
4412
  };
3908
4413
 
3909
4414
  // src/routes/profile/handler/update-phone.ts
3910
- 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";
3911
4416
  var updatePhoneHandler = async (c) => {
3912
4417
  const body = c.req.valid("json");
3913
4418
  const config = c.get("config");
@@ -3929,9 +4434,9 @@ var updatePhoneHandler = async (c) => {
3929
4434
  }
3930
4435
  if (user.phone && session?.id) {
3931
4436
  await database.delete(sessionsInIam).where(
3932
- and27(
3933
- eq27(sessionsInIam.tenantId, resolvedTenantId),
3934
- eq27(sessionsInIam.userId, userId),
4437
+ and29(
4438
+ eq30(sessionsInIam.tenantId, resolvedTenantId),
4439
+ eq30(sessionsInIam.userId, userId),
3935
4440
  ne3(sessionsInIam.id, session.id)
3936
4441
  )
3937
4442
  );
@@ -3941,7 +4446,7 @@ var updatePhoneHandler = async (c) => {
3941
4446
  phoneVerified: true,
3942
4447
  updatedAt: sql16`CURRENT_TIMESTAMP`
3943
4448
  }).where(
3944
- and27(eq27(usersInIam.id, userId), eq27(usersInIam.tenantId, resolvedTenantId))
4449
+ and29(eq30(usersInIam.id, userId), eq30(usersInIam.tenantId, resolvedTenantId))
3945
4450
  ).returning({
3946
4451
  id: usersInIam.id,
3947
4452
  tenantId: usersInIam.tenantId,
@@ -3958,18 +4463,18 @@ var updatePhoneHandler = async (c) => {
3958
4463
  return c.json({ error: "User not found" }, 404);
3959
4464
  }
3960
4465
  await database.update(accountChangesInIam).set({ status: "applied" }).where(
3961
- and27(
3962
- eq27(accountChangesInIam.tenantId, resolvedTenantId),
3963
- eq27(accountChangesInIam.userId, userId),
3964
- eq27(accountChangesInIam.changeType, "phone"),
3965
- 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)
3966
4471
  )
3967
4472
  );
3968
4473
  await database.update(accountsInIam).set({ providerAccountId: body.phone }).where(
3969
- and27(
3970
- eq27(accountsInIam.tenantId, resolvedTenantId),
3971
- eq27(accountsInIam.userId, userId),
3972
- eq27(accountsInIam.provider, "credentials")
4474
+ and29(
4475
+ eq30(accountsInIam.tenantId, resolvedTenantId),
4476
+ eq30(accountsInIam.userId, userId),
4477
+ eq30(accountsInIam.provider, "credentials")
3973
4478
  )
3974
4479
  );
3975
4480
  return c.json({ user: normalizeUser(updatedUser) }, 200);
@@ -4219,41 +4724,41 @@ var assignRolePermissionHandler = async (c) => {
4219
4724
  };
4220
4725
 
4221
4726
  // src/routes/role-permissions/handler/list-role-permissions.ts
4222
- import { and as and28, eq as eq28 } from "drizzle-orm";
4727
+ import { and as and30, eq as eq31 } from "drizzle-orm";
4223
4728
  var listRolePermissionsHandler = async (c) => {
4224
4729
  const query = c.req.valid("query");
4225
4730
  const database = c.get("database");
4226
4731
  const tenantId = c.get("tenantId");
4227
- const conditions = [eq28(rolePermissionsInIam.tenantId, tenantId)];
4732
+ const conditions = [eq31(rolePermissionsInIam.tenantId, tenantId)];
4228
4733
  if (query.roleId) {
4229
- conditions.push(eq28(rolePermissionsInIam.roleId, query.roleId));
4734
+ conditions.push(eq31(rolePermissionsInIam.roleId, query.roleId));
4230
4735
  }
4231
4736
  if (query.permissionId) {
4232
- conditions.push(eq28(rolePermissionsInIam.permissionId, query.permissionId));
4737
+ conditions.push(eq31(rolePermissionsInIam.permissionId, query.permissionId));
4233
4738
  }
4234
- const rolePermissions = await database.select().from(rolePermissionsInIam).where(and28(...conditions));
4739
+ const rolePermissions = await database.select().from(rolePermissionsInIam).where(and30(...conditions));
4235
4740
  return c.json({ rolePermissions }, 200);
4236
4741
  };
4237
4742
 
4238
4743
  // src/routes/role-permissions/handler/revoke-role-permission.ts
4239
- import { and as and29, eq as eq29 } from "drizzle-orm";
4744
+ import { and as and31, eq as eq32 } from "drizzle-orm";
4240
4745
  var revokeRolePermissionHandler = async (c) => {
4241
4746
  const { id } = c.req.valid("param");
4242
4747
  const database = c.get("database");
4243
4748
  const tenantId = c.get("tenantId");
4244
4749
  const [existing] = await database.select().from(rolePermissionsInIam).where(
4245
- and29(
4246
- eq29(rolePermissionsInIam.id, id),
4247
- eq29(rolePermissionsInIam.tenantId, tenantId)
4750
+ and31(
4751
+ eq32(rolePermissionsInIam.id, id),
4752
+ eq32(rolePermissionsInIam.tenantId, tenantId)
4248
4753
  )
4249
4754
  ).limit(1);
4250
4755
  if (!existing) {
4251
4756
  return c.json({ error: "Role permission not found" }, 404);
4252
4757
  }
4253
4758
  await database.delete(rolePermissionsInIam).where(
4254
- and29(
4255
- eq29(rolePermissionsInIam.id, id),
4256
- eq29(rolePermissionsInIam.tenantId, tenantId)
4759
+ and31(
4760
+ eq32(rolePermissionsInIam.id, id),
4761
+ eq32(rolePermissionsInIam.tenantId, tenantId)
4257
4762
  )
4258
4763
  );
4259
4764
  return c.json({ message: "Permission revoked from role" }, 200);
@@ -4377,53 +4882,390 @@ var role_permissions_route_default = rolePermissionRoutes;
4377
4882
  // src/routes/roles/roles.route.ts
4378
4883
  import { createRoute as createRoute9, OpenAPIHono as OpenAPIHono9 } from "@hono/zod-openapi";
4379
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
+
4380
5006
  // src/routes/roles/handler/create-role.ts
4381
- import { randomUUID } from "crypto";
5007
+ import { randomUUID as randomUUID4 } from "crypto";
4382
5008
  var createRoleHandler = async (c) => {
4383
5009
  const body = c.req.valid("json");
4384
5010
  const config = c.get("config");
4385
5011
  const database = c.get("database");
4386
5012
  const tenantId = c.get("tenantId");
4387
5013
  const resolvedTenantId = ensureTenantId(config, tenantId);
4388
- const [role] = await database.insert(rolesInIam).values({
4389
- id: randomUUID(),
4390
- tenantId: resolvedTenantId,
4391
- name: body.name,
4392
- description: body.description,
4393
- code: body.code
4394
- }).returning();
4395
- 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
+ );
4396
5050
  };
4397
5051
 
4398
5052
  // src/routes/roles/handler/delete-role.ts
4399
- import { and as and30, eq as eq30 } from "drizzle-orm";
5053
+ import { and as and34, eq as eq35 } from "drizzle-orm";
4400
5054
  var deleteRoleHandler = async (c) => {
4401
5055
  const { id } = c.req.valid("param");
4402
5056
  const database = c.get("database");
4403
5057
  const tenantId = c.get("tenantId");
4404
- 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);
4405
5059
  if (!existing) {
4406
5060
  return c.json({ error: "Role not found" }, 404);
4407
5061
  }
4408
- 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)));
4409
5066
  return c.json({ message: "Role deleted" }, 200);
4410
5067
  };
4411
5068
 
4412
5069
  // src/routes/roles/handler/get-role.ts
4413
- import { and as and31, eq as eq31 } from "drizzle-orm";
5070
+ import { and as and35, eq as eq36, sql as sql17 } from "drizzle-orm";
4414
5071
  var getRoleHandler = async (c) => {
4415
5072
  const { id } = c.req.valid("param");
4416
5073
  const database = c.get("database");
4417
5074
  const tenantId = c.get("tenantId");
4418
- 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 });
4419
5210
  if (!role) {
4420
5211
  return c.json({ error: "Role not found" }, 404);
4421
5212
  }
4422
- 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
+ );
4423
5260
  };
4424
5261
 
4425
5262
  // src/routes/roles/handler/list-roles.ts
4426
- 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
+ };
4427
5269
  var listRolesHandler = async (c) => {
4428
5270
  const query = c.req.valid("query");
4429
5271
  const database = c.get("database");
@@ -4431,64 +5273,281 @@ var listRolesHandler = async (c) => {
4431
5273
  const page = query.page || 1;
4432
5274
  const limit = query.limit || 20;
4433
5275
  const offset = (page - 1) * limit;
4434
- 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;
4435
5290
  const [roles, totalResult] = await Promise.all([
4436
- database.select().from(rolesInIam).where(and32(...conditions)).limit(limit).offset(offset),
4437
- 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))
4438
5320
  ]);
4439
5321
  const total = Number(totalResult[0]?.count || 0);
4440
5322
  return c.json({ roles, total, page, limit });
4441
5323
  };
4442
5324
 
4443
- // src/routes/roles/handler/update-role.ts
4444
- import { and as and33, eq as eq33, sql as sql18 } from "drizzle-orm";
4445
- var updateRoleHandler = async (c) => {
4446
- const { id } = c.req.valid("param");
4447
- 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");
4448
5329
  const database = c.get("database");
4449
5330
  const tenantId = c.get("tenantId");
4450
- const [existing] = await database.select().from(rolesInIam).where(and33(eq33(rolesInIam.id, id), eq33(rolesInIam.tenantId, tenantId))).limit(1);
4451
- 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) {
4452
5336
  return c.json({ error: "Role not found" }, 404);
4453
5337
  }
4454
- const updateData = {};
4455
- if (body.name !== void 0) {
4456
- updateData.name = body.name;
4457
- }
4458
- if (body.description !== void 0) {
4459
- updateData.description = body.description;
5338
+ if (!role.isEditable) {
5339
+ return c.json({ error: "Role is not editable" }, 403);
4460
5340
  }
4461
- if (body.code !== void 0) {
4462
- 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);
4463
5350
  }
4464
- const [updated] = await database.update(rolesInIam).set({
4465
- ...updateData,
4466
- updatedAt: sql18`CURRENT_TIMESTAMP`
4467
- }).where(and33(eq33(rolesInIam.id, id), eq33(rolesInIam.tenantId, tenantId))).returning();
4468
- 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) {
4469
5365
  return c.json({ error: "Role not found" }, 404);
4470
5366
  }
4471
- 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);
4472
5381
  };
4473
5382
 
4474
- // src/routes/roles/roles.schema.ts
4475
- import { z as z6 } from "zod";
4476
- var listRolesQuerySchema = z6.object({
4477
- page: z6.coerce.number().min(1).default(1).optional(),
4478
- limit: z6.coerce.number().min(1).max(100).default(20).optional()
4479
- });
4480
- var roleIdParamSchema = z6.object({
4481
- id: z6.uuid()
4482
- });
4483
- var createRoleSchema = z6.object({
4484
- name: z6.unknown(),
4485
- description: z6.unknown(),
4486
- code: z6.string()
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()
5519
+ });
5520
+ var createRoleSchema = z6.object({
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()
4487
5528
  });
4488
5529
  var updateRoleSchema = z6.object({
4489
5530
  name: z6.unknown().optional(),
4490
5531
  description: z6.unknown().optional(),
4491
- 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()
4492
5551
  });
4493
5552
  var roleSchema = z6.object({
4494
5553
  id: z6.uuid(),
@@ -4497,7 +5556,13 @@ var roleSchema = z6.object({
4497
5556
  updatedAt: z6.string(),
4498
5557
  name: z6.unknown(),
4499
5558
  description: z6.unknown(),
4500
- 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()
4501
5566
  });
4502
5567
  var listRolesResponseSchema = z6.object({
4503
5568
  roles: z6.array(roleSchema),
@@ -4508,12 +5573,45 @@ var listRolesResponseSchema = z6.object({
4508
5573
  var roleResponseSchema = z6.object({
4509
5574
  role: roleSchema
4510
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
+ });
4511
5604
  var deleteRoleResponseSchema = z6.object({
4512
5605
  message: z6.string()
4513
5606
  });
5607
+ var seedRolesResponseSchema = z6.object({
5608
+ roles: z6.array(roleSchema),
5609
+ total: z6.number()
5610
+ });
4514
5611
  var errorResponseSchema5 = z6.object({
4515
5612
  error: z6.string()
4516
5613
  });
5614
+ var listRolePermissionsQuerySchema2 = listPermissionsQuerySchema;
4517
5615
 
4518
5616
  // src/routes/roles/roles.route.ts
4519
5617
  var listRolesRoute = createRoute9({
@@ -4531,26 +5629,258 @@ var listRolesRoute = createRoute9({
4531
5629
  schema: listRolesResponseSchema
4532
5630
  }
4533
5631
  },
4534
- 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"
4535
5864
  }
4536
5865
  }
4537
5866
  });
4538
- var getRoleRoute = createRoute9({
5867
+ var listRoleUsersRoute = createRoute9({
4539
5868
  method: "get",
4540
- path: "/{id}",
5869
+ path: "/{id}/users",
4541
5870
  tags: ["Roles"],
4542
- summary: "Get role by ID",
5871
+ summary: "List users assigned to a role",
4543
5872
  request: {
4544
- params: roleIdParamSchema
5873
+ params: roleIdParamSchema,
5874
+ query: listRoleUsersQuerySchema
4545
5875
  },
4546
5876
  responses: {
4547
5877
  200: {
4548
5878
  content: {
4549
5879
  "application/json": {
4550
- schema: roleResponseSchema
5880
+ schema: listRoleUsersResponseSchema
4551
5881
  }
4552
5882
  },
4553
- description: "Role details"
5883
+ description: "Role users"
4554
5884
  },
4555
5885
  404: {
4556
5886
  content: {
@@ -4562,62 +5892,80 @@ var getRoleRoute = createRoute9({
4562
5892
  }
4563
5893
  }
4564
5894
  });
4565
- var createRoleRoute = createRoute9({
5895
+ var assignRoleUsersRoute = createRoute9({
4566
5896
  method: "post",
4567
- path: "/",
5897
+ path: "/{id}/users",
4568
5898
  tags: ["Roles"],
4569
- summary: "Create role",
5899
+ summary: "Assign users to a role",
4570
5900
  request: {
5901
+ params: roleIdParamSchema,
4571
5902
  body: {
4572
5903
  content: {
4573
5904
  "application/json": {
4574
- schema: createRoleSchema
5905
+ schema: assignRoleUsersSchema
4575
5906
  }
4576
5907
  }
4577
5908
  }
4578
5909
  },
4579
5910
  responses: {
4580
- 201: {
5911
+ 200: {
4581
5912
  content: {
4582
5913
  "application/json": {
4583
- schema: roleResponseSchema
5914
+ schema: assignRoleUsersResponseSchema
4584
5915
  }
4585
5916
  },
4586
- description: "Role created"
5917
+ description: "Users assigned"
4587
5918
  },
4588
- 409: {
5919
+ 400: {
4589
5920
  content: {
4590
5921
  "application/json": {
4591
5922
  schema: errorResponseSchema5
4592
5923
  }
4593
5924
  },
4594
- 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"
4595
5942
  }
4596
5943
  }
4597
5944
  });
4598
- var updateRoleRoute = createRoute9({
4599
- method: "put",
4600
- path: "/{id}",
5945
+ var revokeRolePermissionRoute2 = createRoute9({
5946
+ method: "delete",
5947
+ path: "/{id}/permissions/{permissionId}",
4601
5948
  tags: ["Roles"],
4602
- summary: "Update role",
5949
+ summary: "Revoke a permission from a role",
4603
5950
  request: {
4604
- params: roleIdParamSchema,
4605
- body: {
4606
- content: {
4607
- "application/json": {
4608
- schema: updateRoleSchema
4609
- }
4610
- }
4611
- }
5951
+ params: rolePermissionParamSchema
4612
5952
  },
4613
5953
  responses: {
4614
5954
  200: {
4615
5955
  content: {
4616
5956
  "application/json": {
4617
- schema: roleResponseSchema
5957
+ schema: deleteRoleResponseSchema
4618
5958
  }
4619
5959
  },
4620
- 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"
4621
5969
  },
4622
5970
  404: {
4623
5971
  content: {
@@ -4625,17 +5973,17 @@ var updateRoleRoute = createRoute9({
4625
5973
  schema: errorResponseSchema5
4626
5974
  }
4627
5975
  },
4628
- description: "Role not found"
5976
+ description: "Role or permission not found"
4629
5977
  }
4630
5978
  }
4631
5979
  });
4632
- var deleteRoleRoute = createRoute9({
5980
+ var revokeRoleUserRoute = createRoute9({
4633
5981
  method: "delete",
4634
- path: "/{id}",
5982
+ path: "/{id}/users/{userId}",
4635
5983
  tags: ["Roles"],
4636
- summary: "Delete role",
5984
+ summary: "Remove a user from a role",
4637
5985
  request: {
4638
- params: roleIdParamSchema
5986
+ params: roleUserParamSchema
4639
5987
  },
4640
5988
  responses: {
4641
5989
  200: {
@@ -4644,7 +5992,15 @@ var deleteRoleRoute = createRoute9({
4644
5992
  schema: deleteRoleResponseSchema
4645
5993
  }
4646
5994
  },
4647
- 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"
4648
6004
  },
4649
6005
  404: {
4650
6006
  content: {
@@ -4652,23 +6008,47 @@ var deleteRoleRoute = createRoute9({
4652
6008
  schema: errorResponseSchema5
4653
6009
  }
4654
6010
  },
4655
- 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"
4656
6036
  }
4657
6037
  }
4658
6038
  });
4659
- 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);
4660
6040
  var roles_route_default = roleRoutes;
4661
6041
 
4662
6042
  // src/routes/sessions/sessions.route.ts
4663
6043
  import { createRoute as createRoute10, OpenAPIHono as OpenAPIHono10 } from "@hono/zod-openapi";
4664
6044
 
4665
6045
  // src/routes/sessions/handler/get-session.ts
4666
- import { and as and34, eq as eq34 } from "drizzle-orm";
6046
+ import { and as and43, eq as eq44 } from "drizzle-orm";
4667
6047
  var getSessionHandler = async (c) => {
4668
6048
  const { id } = c.req.valid("param");
4669
6049
  const database = c.get("database");
4670
6050
  const tenantId = c.get("tenantId");
4671
- 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);
4672
6052
  if (!session) {
4673
6053
  return c.json({ error: "Session not found" }, 404);
4674
6054
  }
@@ -4676,7 +6056,7 @@ var getSessionHandler = async (c) => {
4676
6056
  };
4677
6057
 
4678
6058
  // src/routes/sessions/handler/list-sessions.ts
4679
- 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";
4680
6060
  var listSessionsHandler = async (c) => {
4681
6061
  const query = c.req.valid("query");
4682
6062
  const database = c.get("database");
@@ -4684,48 +6064,48 @@ var listSessionsHandler = async (c) => {
4684
6064
  const page = query.page || 1;
4685
6065
  const limit = query.limit || 20;
4686
6066
  const offset = (page - 1) * limit;
4687
- const conditions = [eq35(sessionsInIam.tenantId, tenantId)];
6067
+ const conditions = [eq45(sessionsInIam.tenantId, tenantId)];
4688
6068
  if (query.userId) {
4689
- conditions.push(eq35(sessionsInIam.userId, query.userId));
6069
+ conditions.push(eq45(sessionsInIam.userId, query.userId));
4690
6070
  }
4691
6071
  const [sessions, totalResult] = await Promise.all([
4692
- database.select().from(sessionsInIam).where(and35(...conditions)).limit(limit).offset(offset),
4693
- 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))
4694
6074
  ]);
4695
6075
  const total = Number(totalResult[0]?.count || 0);
4696
6076
  return c.json({ sessions, total, page, limit }, 200);
4697
6077
  };
4698
6078
 
4699
6079
  // src/routes/sessions/handler/revoke-all-sessions.ts
4700
- import { and as and36, eq as eq36 } from "drizzle-orm";
6080
+ import { and as and45, eq as eq46 } from "drizzle-orm";
4701
6081
  var revokeAllSessionsHandler = async (c) => {
4702
6082
  const { userId } = c.req.valid("param");
4703
6083
  const database = c.get("database");
4704
6084
  const tenantId = c.get("tenantId");
4705
- 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);
4706
6086
  if (!user) {
4707
6087
  return c.json({ error: "User not found" }, 404);
4708
6088
  }
4709
6089
  await database.delete(sessionsInIam).where(
4710
- and36(
4711
- eq36(sessionsInIam.tenantId, tenantId),
4712
- eq36(sessionsInIam.userId, userId)
6090
+ and45(
6091
+ eq46(sessionsInIam.tenantId, tenantId),
6092
+ eq46(sessionsInIam.userId, userId)
4713
6093
  )
4714
6094
  );
4715
6095
  return c.json({ message: "All user sessions revoked" }, 200);
4716
6096
  };
4717
6097
 
4718
6098
  // src/routes/sessions/handler/revoke-session.ts
4719
- import { and as and37, eq as eq37 } from "drizzle-orm";
6099
+ import { and as and46, eq as eq47 } from "drizzle-orm";
4720
6100
  var revokeSessionHandler = async (c) => {
4721
6101
  const { id } = c.req.valid("param");
4722
6102
  const database = c.get("database");
4723
6103
  const tenantId = c.get("tenantId");
4724
- 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);
4725
6105
  if (!existing) {
4726
6106
  return c.json({ error: "Session not found" }, 404);
4727
6107
  }
4728
- 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)));
4729
6109
  return c.json({ message: "Session revoked" }, 200);
4730
6110
  };
4731
6111
 
@@ -4927,10 +6307,11 @@ var system_route_default = tenantRoutes;
4927
6307
  import { createRoute as createRoute12, OpenAPIHono as OpenAPIHono12 } from "@hono/zod-openapi";
4928
6308
 
4929
6309
  // src/routes/tenants/handler/create-tenant.ts
4930
- import { eq as eq38 } from "drizzle-orm";
6310
+ import { eq as eq48 } from "drizzle-orm";
4931
6311
 
4932
6312
  // src/lib/has-role-permission.ts
4933
- import { HTTPException as HTTPException3 } from "hono/http-exception";
6313
+ import { grant } from "@mesob/common";
6314
+ import { HTTPException as HTTPException4 } from "hono/http-exception";
4934
6315
  var toArray = (v) => {
4935
6316
  return Array.isArray(v) ? v : [v];
4936
6317
  };
@@ -4945,21 +6326,18 @@ var hasRole = (c, role) => {
4945
6326
  };
4946
6327
  var hasRoleThrow = (c, role) => {
4947
6328
  if (!hasRole(c, role)) {
4948
- throw new HTTPException3(401, { message: "Unauthorized" });
6329
+ throw new HTTPException4(401, { message: "Unauthorized" });
4949
6330
  }
4950
6331
  };
4951
6332
  var hasPermission = (c, permission) => {
4952
6333
  const user = c.get("user");
4953
6334
  const perms = user?.permissions;
4954
- if (!perms?.length) {
4955
- return false;
4956
- }
4957
6335
  const check2 = toArray(permission);
4958
- return check2.some((p) => perms.includes(p));
6336
+ return grant(check2, perms);
4959
6337
  };
4960
6338
  var hasPermissionThrow = (c, permission) => {
4961
6339
  if (!hasPermission(c, permission)) {
4962
- throw new HTTPException3(401, { message: "Unauthorized" });
6340
+ throw new HTTPException4(401, { message: "Unauthorized" });
4963
6341
  }
4964
6342
  };
4965
6343
 
@@ -4968,7 +6346,7 @@ var createTenantHandler = async (c) => {
4968
6346
  hasRoleThrow(c, ["owner", "tenant-admin"]);
4969
6347
  const body = c.req.valid("json");
4970
6348
  const database = c.get("database");
4971
- 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);
4972
6350
  if (existing) {
4973
6351
  return c.json({ error: "Tenant already exists" }, 409);
4974
6352
  }
@@ -4991,26 +6369,26 @@ var createTenantHandler = async (c) => {
4991
6369
  };
4992
6370
 
4993
6371
  // src/routes/tenants/handler/delete-tenant.ts
4994
- import { eq as eq39 } from "drizzle-orm";
6372
+ import { eq as eq49 } from "drizzle-orm";
4995
6373
  var deleteTenantHandler = async (c) => {
4996
6374
  hasRoleThrow(c, ["owner", "tenant-admin"]);
4997
6375
  const { id } = c.req.valid("param");
4998
6376
  const database = c.get("database");
4999
- 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);
5000
6378
  if (!existing) {
5001
6379
  return c.json({ error: "Tenant not found" }, 404);
5002
6380
  }
5003
- await database.delete(tenantsInIam).where(eq39(tenantsInIam.id, id));
6381
+ await database.delete(tenantsInIam).where(eq49(tenantsInIam.id, id));
5004
6382
  return c.json({ message: "Tenant deleted" }, 200);
5005
6383
  };
5006
6384
 
5007
6385
  // src/routes/tenants/handler/get-tenant.ts
5008
- import { eq as eq40 } from "drizzle-orm";
6386
+ import { eq as eq50 } from "drizzle-orm";
5009
6387
  var getTenantHandler = async (c) => {
5010
6388
  hasRoleThrow(c, ["owner", "tenant-admin"]);
5011
6389
  const { id } = c.req.valid("param");
5012
6390
  const database = c.get("database");
5013
- 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);
5014
6392
  if (!tenant) {
5015
6393
  return c.json({ error: "Tenant not found" }, 404);
5016
6394
  }
@@ -5018,11 +6396,11 @@ var getTenantHandler = async (c) => {
5018
6396
  };
5019
6397
 
5020
6398
  // src/routes/tenants/handler/list-tenants.ts
5021
- import { and as and38, asc as asc2, desc as desc2, eq as eq41, ilike as ilike2, or, sql as sql20 } from "drizzle-orm";
5022
- 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 = {
5023
6401
  createdAt: tenantsInIam.createdAt,
5024
6402
  updatedAt: tenantsInIam.updatedAt,
5025
- name: sql20`${tenantsInIam.name}::text`
6403
+ name: sql23`${tenantsInIam.name}::text`
5026
6404
  };
5027
6405
  var listTenantsHandler = async (c) => {
5028
6406
  hasRoleThrow(c, ["owner", "tenant-admin"]);
@@ -5033,42 +6411,42 @@ var listTenantsHandler = async (c) => {
5033
6411
  const offset = (page - 1) * limit;
5034
6412
  const conditions = [];
5035
6413
  if (query.isActive !== void 0) {
5036
- conditions.push(eq41(tenantsInIam.isActive, query.isActive));
6414
+ conditions.push(eq51(tenantsInIam.isActive, query.isActive));
5037
6415
  }
5038
6416
  if (query.filter === "isActive:true") {
5039
- conditions.push(eq41(tenantsInIam.isActive, true));
6417
+ conditions.push(eq51(tenantsInIam.isActive, true));
5040
6418
  } else if (query.filter === "isActive:false") {
5041
- conditions.push(eq41(tenantsInIam.isActive, false));
6419
+ conditions.push(eq51(tenantsInIam.isActive, false));
5042
6420
  }
5043
6421
  if (query.search?.trim()) {
5044
6422
  const term = `%${query.search.trim()}%`;
5045
- const searchCond = or(
5046
- ilike2(tenantsInIam.id, term),
5047
- ilike2(sql20`${tenantsInIam.name}::text`, term)
6423
+ const searchCond = or3(
6424
+ ilike3(tenantsInIam.id, term),
6425
+ ilike3(sql23`${tenantsInIam.name}::text`, term)
5048
6426
  );
5049
6427
  if (searchCond) {
5050
6428
  conditions.push(searchCond);
5051
6429
  }
5052
6430
  }
5053
- const orderDir = query.order === "asc" ? asc2 : desc2;
5054
- const sortCol = query.sort && sortColumnMap[query.sort] ? sortColumnMap[query.sort] : tenantsInIam.createdAt;
5055
- 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;
5056
6434
  const [tenants, totalResult] = await Promise.all([
5057
6435
  database.select().from(tenantsInIam).where(whereClause).orderBy(orderDir(sortCol)).limit(limit).offset(offset),
5058
- database.select({ count: sql20`count(*)` }).from(tenantsInIam).where(whereClause)
6436
+ database.select({ count: sql23`count(*)` }).from(tenantsInIam).where(whereClause)
5059
6437
  ]);
5060
6438
  const total = Number(totalResult[0]?.count || 0);
5061
6439
  return c.json({ tenants, total, page, limit }, 200);
5062
6440
  };
5063
6441
 
5064
6442
  // src/routes/tenants/handler/update-tenant.ts
5065
- import { eq as eq42, sql as sql21 } from "drizzle-orm";
6443
+ import { eq as eq52, sql as sql24 } from "drizzle-orm";
5066
6444
  var updateTenantHandler = async (c) => {
5067
6445
  hasRoleThrow(c, ["owner", "tenant-admin"]);
5068
6446
  const { id } = c.req.valid("param");
5069
6447
  const body = c.req.valid("json");
5070
6448
  const database = c.get("database");
5071
- 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);
5072
6450
  if (!existing) {
5073
6451
  return c.json({ error: "Tenant not found" }, 404);
5074
6452
  }
@@ -5111,8 +6489,8 @@ var updateTenantHandler = async (c) => {
5111
6489
  }
5112
6490
  const [updated] = await database.update(tenantsInIam).set({
5113
6491
  ...updateData,
5114
- updatedAt: sql21`CURRENT_TIMESTAMP`
5115
- }).where(eq42(tenantsInIam.id, id)).returning();
6492
+ updatedAt: sql24`CURRENT_TIMESTAMP`
6493
+ }).where(eq52(tenantsInIam.id, id)).returning();
5116
6494
  if (!updated) {
5117
6495
  return c.json({ error: "Tenant not found" }, 404);
5118
6496
  }
@@ -5367,36 +6745,36 @@ var assignUserRoleHandler = async (c) => {
5367
6745
  };
5368
6746
 
5369
6747
  // src/routes/user-roles/handler/list-user-roles.ts
5370
- import { and as and39, eq as eq43 } from "drizzle-orm";
6748
+ import { and as and48, eq as eq53 } from "drizzle-orm";
5371
6749
  var listUserRolesHandler = async (c) => {
5372
6750
  const query = c.req.valid("query");
5373
6751
  const database = c.get("database");
5374
6752
  const tenantId = c.get("tenantId");
5375
- const conditions = [eq43(userRolesInIam.tenantId, tenantId)];
6753
+ const conditions = [eq53(userRolesInIam.tenantId, tenantId)];
5376
6754
  if (query.userId) {
5377
- conditions.push(eq43(userRolesInIam.userId, query.userId));
6755
+ conditions.push(eq53(userRolesInIam.userId, query.userId));
5378
6756
  }
5379
6757
  if (query.roleId) {
5380
- conditions.push(eq43(userRolesInIam.roleId, query.roleId));
6758
+ conditions.push(eq53(userRolesInIam.roleId, query.roleId));
5381
6759
  }
5382
- const userRoles = await database.select().from(userRolesInIam).where(and39(...conditions));
6760
+ const userRoles = await database.select().from(userRolesInIam).where(and48(...conditions));
5383
6761
  return c.json({ userRoles }, 200);
5384
6762
  };
5385
6763
 
5386
6764
  // src/routes/user-roles/handler/revoke-user-role.ts
5387
- import { and as and40, eq as eq44 } from "drizzle-orm";
6765
+ import { and as and49, eq as eq54 } from "drizzle-orm";
5388
6766
  var revokeUserRoleHandler = async (c) => {
5389
6767
  const { id } = c.req.valid("param");
5390
6768
  const database = c.get("database");
5391
6769
  const tenantId = c.get("tenantId");
5392
6770
  const [existing] = await database.select().from(userRolesInIam).where(
5393
- and40(eq44(userRolesInIam.id, id), eq44(userRolesInIam.tenantId, tenantId))
6771
+ and49(eq54(userRolesInIam.id, id), eq54(userRolesInIam.tenantId, tenantId))
5394
6772
  ).limit(1);
5395
6773
  if (!existing) {
5396
6774
  return c.json({ error: "User role not found" }, 404);
5397
6775
  }
5398
6776
  await database.delete(userRolesInIam).where(
5399
- and40(eq44(userRolesInIam.id, id), eq44(userRolesInIam.tenantId, tenantId))
6777
+ and49(eq54(userRolesInIam.id, id), eq54(userRolesInIam.tenantId, tenantId))
5400
6778
  );
5401
6779
  return c.json({ message: "Role revoked from user" }, 200);
5402
6780
  };
@@ -5520,20 +6898,20 @@ var user_roles_route_default = userRoleRoutes;
5520
6898
  import { createRoute as createRoute14, OpenAPIHono as OpenAPIHono14 } from "@hono/zod-openapi";
5521
6899
 
5522
6900
  // src/routes/users/handler/ban-user.ts
5523
- 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";
5524
6902
  var banUserHandler = async (c) => {
5525
6903
  const { id } = c.req.valid("param");
5526
6904
  const body = c.req.valid("json");
5527
6905
  const database = c.get("database");
5528
6906
  const tenantId = c.get("tenantId");
5529
- 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);
5530
6908
  if (!existing) {
5531
6909
  return c.json({ error: "User not found" }, 404);
5532
6910
  }
5533
6911
  const [updated] = await database.update(usersInIam).set({
5534
6912
  bannedUntil: body.bannedUntil || null,
5535
- updatedAt: sql22`CURRENT_TIMESTAMP`
5536
- }).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({
5537
6915
  id: usersInIam.id,
5538
6916
  tenantId: usersInIam.tenantId,
5539
6917
  fullName: usersInIam.fullName,
@@ -5552,17 +6930,17 @@ var banUserHandler = async (c) => {
5552
6930
  };
5553
6931
 
5554
6932
  // src/routes/users/helper/user.ts
5555
- 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";
5556
6934
  var checkUserExists = async ({
5557
6935
  database,
5558
6936
  identifier,
5559
6937
  tenantId,
5560
6938
  isEmail
5561
6939
  }) => {
5562
- const whereClause = isEmail ? and42(
5563
- eq46(usersInIam.tenantId, tenantId),
5564
- sql23`lower(${usersInIam.email}) = lower(${identifier})`
5565
- ) : 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));
5566
6944
  const [user] = await database.select().from(usersInIam).where(whereClause).limit(1);
5567
6945
  return user || null;
5568
6946
  };
@@ -5572,14 +6950,170 @@ var checkHandleExists = async ({
5572
6950
  tenantId
5573
6951
  }) => {
5574
6952
  const [existingHandle] = await database.select().from(usersInIam).where(
5575
- and42(
5576
- eq46(usersInIam.tenantId, tenantId),
5577
- sql23`lower(${usersInIam.handle}) = lower(${handle})`
6953
+ and51(
6954
+ eq56(usersInIam.tenantId, tenantId),
6955
+ sql26`lower(${usersInIam.handle}) = lower(${handle})`
5578
6956
  )
5579
6957
  ).limit(1);
5580
6958
  return existingHandle || null;
5581
6959
  };
5582
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
+
5583
7117
  // src/routes/users/handler/create-user.ts
5584
7118
  var createUserHandler = async (c) => {
5585
7119
  const body = c.req.valid("json");
@@ -5610,41 +7144,55 @@ var createUserHandler = async (c) => {
5610
7144
  if (existingHandle) {
5611
7145
  return c.json({ error: "Handle already taken" }, 409);
5612
7146
  }
5613
- const [user] = await database.insert(usersInIam).values({
5614
- tenantId: resolvedTenantId,
5615
- fullName: body.fullName,
5616
- handle: userHandle,
5617
- email: body.email || null,
5618
- phone: body.phone || null,
5619
- image: body.image || null,
5620
- emailVerified: Boolean(body.emailVerified),
5621
- phoneVerified: Boolean(body.phoneVerified)
5622
- }).returning({
5623
- id: usersInIam.id,
5624
- tenantId: usersInIam.tenantId,
5625
- fullName: usersInIam.fullName,
5626
- email: usersInIam.email,
5627
- phone: usersInIam.phone,
5628
- handle: usersInIam.handle,
5629
- image: usersInIam.image,
5630
- emailVerified: usersInIam.emailVerified,
5631
- phoneVerified: usersInIam.phoneVerified,
5632
- 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];
5633
7181
  });
5634
7182
  return c.json({ user: normalizeUser(user) }, 201);
5635
7183
  };
5636
7184
 
5637
7185
  // src/routes/users/handler/delete-user.ts
5638
- import { and as and43, eq as eq47 } from "drizzle-orm";
7186
+ import { and as and52, eq as eq57 } from "drizzle-orm";
5639
7187
  var deleteUserHandler = async (c) => {
5640
7188
  const { id } = c.req.valid("param");
5641
7189
  const database = c.get("database");
5642
7190
  const tenantId = c.get("tenantId");
5643
- 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);
5644
7192
  if (!existing) {
5645
7193
  return c.json({ error: "User not found" }, 404);
5646
7194
  }
5647
- 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)));
5648
7196
  return c.json({ message: "User deleted" }, 200);
5649
7197
  };
5650
7198
 
@@ -5664,9 +7212,32 @@ var getUserHandler = async (c) => {
5664
7212
  return c.json({ user: normalizeUser(user) }, 200);
5665
7213
  };
5666
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
+
5667
7238
  // src/routes/users/handler/list-users.ts
5668
- import { and as and44, asc as asc3, desc as desc3, eq as eq48, ilike as ilike3, sql as sql24 } from "drizzle-orm";
5669
- 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 = {
5670
7241
  createdAt: usersInIam.createdAt,
5671
7242
  updatedAt: usersInIam.updatedAt,
5672
7243
  fullName: usersInIam.fullName,
@@ -5680,26 +7251,26 @@ var listUsersHandler = async (c) => {
5680
7251
  const page = query.page || 1;
5681
7252
  const limit = query.limit || 20;
5682
7253
  const offset = (page - 1) * limit;
5683
- const conditions = [eq48(usersInIam.tenantId, tenantId)];
7254
+ const conditions = [eq58(usersInIam.tenantId, tenantId)];
5684
7255
  if (query.email) {
5685
- conditions.push(ilike3(usersInIam.email, `%${query.email}%`));
7256
+ conditions.push(ilike4(usersInIam.email, `%${query.email}%`));
5686
7257
  }
5687
7258
  if (query.phone) {
5688
- conditions.push(ilike3(usersInIam.phone, `%${query.phone}%`));
7259
+ conditions.push(ilike4(usersInIam.phone, `%${query.phone}%`));
5689
7260
  }
5690
7261
  if (query.handle) {
5691
- conditions.push(ilike3(usersInIam.handle, `%${query.handle}%`));
7262
+ conditions.push(ilike4(usersInIam.handle, `%${query.handle}%`));
5692
7263
  }
5693
7264
  if (query.filter === "emailVerified") {
5694
- conditions.push(eq48(usersInIam.emailVerified, true));
7265
+ conditions.push(eq58(usersInIam.emailVerified, true));
5695
7266
  } else if (query.filter === "phoneVerified") {
5696
- conditions.push(eq48(usersInIam.phoneVerified, true));
7267
+ conditions.push(eq58(usersInIam.phoneVerified, true));
5697
7268
  } else if (query.filter === "notVerified") {
5698
- conditions.push(eq48(usersInIam.emailVerified, false));
5699
- conditions.push(eq48(usersInIam.phoneVerified, false));
7269
+ conditions.push(eq58(usersInIam.emailVerified, false));
7270
+ conditions.push(eq58(usersInIam.phoneVerified, false));
5700
7271
  }
5701
- const orderDir = query.order === "asc" ? asc3 : desc3;
5702
- 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;
5703
7274
  const [users, totalResult] = await Promise.all([
5704
7275
  database.select({
5705
7276
  id: usersInIam.id,
@@ -5712,8 +7283,8 @@ var listUsersHandler = async (c) => {
5712
7283
  emailVerified: usersInIam.emailVerified,
5713
7284
  phoneVerified: usersInIam.phoneVerified,
5714
7285
  lastSignInAt: usersInIam.lastSignInAt
5715
- }).from(usersInIam).where(and44(...conditions)).orderBy(orderDir(sortCol)).limit(limit).offset(offset),
5716
- 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))
5717
7288
  ]);
5718
7289
  const total = Number(totalResult[0]?.count || 0);
5719
7290
  return c.json(
@@ -5731,18 +7302,18 @@ var listUsersHandler = async (c) => {
5731
7302
  };
5732
7303
 
5733
7304
  // src/routes/users/handler/search-users.ts
5734
- 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";
5735
7306
  var searchUsersHandler = async (c) => {
5736
7307
  const query = c.req.valid("query");
5737
7308
  const database = c.get("database");
5738
7309
  const tenantId = c.get("tenantId");
5739
7310
  const limit = query.limit || 20;
5740
- const conditions = [eq49(usersInIam.tenantId, tenantId)];
7311
+ const conditions = [eq59(usersInIam.tenantId, tenantId)];
5741
7312
  if (query.search && query.search.trim().length > 0) {
5742
- const searchCondition = or2(
5743
- ilike4(usersInIam.fullName, `%${query.search}%`),
5744
- ilike4(usersInIam.email, `%${query.search}%`),
5745
- 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}%`)
5746
7317
  );
5747
7318
  if (searchCondition) {
5748
7319
  conditions.push(searchCondition);
@@ -5755,26 +7326,26 @@ var searchUsersHandler = async (c) => {
5755
7326
  phone: usersInIam.phone,
5756
7327
  handle: usersInIam.handle,
5757
7328
  image: usersInIam.image
5758
- }).from(usersInIam).where(and45(...conditions)).limit(limit);
7329
+ }).from(usersInIam).where(and54(...conditions)).limit(limit);
5759
7330
  return c.json({ users }, 200);
5760
7331
  };
5761
7332
 
5762
7333
  // src/routes/users/handler/update-user.ts
5763
- 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";
5764
7335
  var updateUserHandler = async (c) => {
5765
7336
  const { id } = c.req.valid("param");
5766
7337
  const body = c.req.valid("json");
5767
7338
  const database = c.get("database");
5768
7339
  const tenantId = c.get("tenantId");
5769
- 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);
5770
7341
  if (!existing) {
5771
7342
  return c.json({ error: "User not found" }, 404);
5772
7343
  }
5773
7344
  if (body.handle && body.handle !== existing.handle) {
5774
7345
  const [handleExists] = await database.select().from(usersInIam).where(
5775
- and46(
5776
- eq50(usersInIam.tenantId, tenantId),
5777
- sql25`lower(${usersInIam.handle}) = lower(${body.handle})`
7346
+ and55(
7347
+ eq60(usersInIam.tenantId, tenantId),
7348
+ sql28`lower(${usersInIam.handle}) = lower(${body.handle})`
5778
7349
  )
5779
7350
  ).limit(1);
5780
7351
  if (handleExists) {
@@ -5805,8 +7376,8 @@ var updateUserHandler = async (c) => {
5805
7376
  }
5806
7377
  const [updated] = await database.update(usersInIam).set({
5807
7378
  ...updateData,
5808
- updatedAt: sql25`CURRENT_TIMESTAMP`
5809
- }).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({
5810
7381
  id: usersInIam.id,
5811
7382
  tenantId: usersInIam.tenantId,
5812
7383
  fullName: usersInIam.fullName,
@@ -5859,6 +7430,7 @@ var createUserSchema = z11.object({
5859
7430
  fullName: z11.string().min(1),
5860
7431
  handle: z11.string().optional(),
5861
7432
  image: z11.string().url().optional(),
7433
+ password: z11.string().min(8).max(128).optional(),
5862
7434
  emailVerified: z11.boolean().default(false).optional(),
5863
7435
  phoneVerified: z11.boolean().default(false).optional()
5864
7436
  });
@@ -5889,6 +7461,46 @@ var deleteUserResponseSchema = z11.object({
5889
7461
  var errorResponseSchema9 = z11.object({
5890
7462
  error: z11.string()
5891
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
+ });
5892
7504
  var searchUsersQuerySchema = z11.object({
5893
7505
  search: z11.string().optional().describe("Search term"),
5894
7506
  limit: z11.coerce.number().int().positive().optional().default(20).describe("Limit")
@@ -6107,38 +7719,96 @@ var searchUsersRoute = createRoute14({
6107
7719
  }
6108
7720
  }
6109
7721
  });
6110
- 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);
6111
7781
  var users_route_default = userRoutes;
6112
7782
 
6113
7783
  // src/routes/verifications/verifications.route.ts
6114
7784
  import { createRoute as createRoute15, OpenAPIHono as OpenAPIHono15 } from "@hono/zod-openapi";
6115
7785
 
6116
7786
  // src/routes/verifications/handler/invalidate-verification.ts
6117
- import { and as and47, eq as eq51 } from "drizzle-orm";
7787
+ import { and as and56, eq as eq61 } from "drizzle-orm";
6118
7788
  var invalidateVerificationHandler = async (c) => {
6119
7789
  const { id } = c.req.valid("param");
6120
7790
  const database = c.get("database");
6121
7791
  const tenantId = c.get("tenantId");
6122
7792
  const [existing] = await database.select().from(verificationsInIam).where(
6123
- and47(
6124
- eq51(verificationsInIam.id, id),
6125
- eq51(verificationsInIam.tenantId, tenantId)
7793
+ and56(
7794
+ eq61(verificationsInIam.id, id),
7795
+ eq61(verificationsInIam.tenantId, tenantId)
6126
7796
  )
6127
7797
  ).limit(1);
6128
7798
  if (!existing) {
6129
7799
  return c.json({ error: "Verification not found" }, 404);
6130
7800
  }
6131
7801
  await database.delete(verificationsInIam).where(
6132
- and47(
6133
- eq51(verificationsInIam.id, id),
6134
- eq51(verificationsInIam.tenantId, tenantId)
7802
+ and56(
7803
+ eq61(verificationsInIam.id, id),
7804
+ eq61(verificationsInIam.tenantId, tenantId)
6135
7805
  )
6136
7806
  );
6137
7807
  return c.json({ message: "Verification invalidated" }, 200);
6138
7808
  };
6139
7809
 
6140
7810
  // src/routes/verifications/handler/list-verifications.ts
6141
- 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";
6142
7812
  var listVerificationsHandler = async (c) => {
6143
7813
  const query = c.req.valid("query");
6144
7814
  const database = c.get("database");
@@ -6146,27 +7816,27 @@ var listVerificationsHandler = async (c) => {
6146
7816
  const page = query.page || 1;
6147
7817
  const limit = query.limit || 20;
6148
7818
  const offset = (page - 1) * limit;
6149
- const conditions = [eq52(verificationsInIam.tenantId, tenantId)];
7819
+ const conditions = [eq62(verificationsInIam.tenantId, tenantId)];
6150
7820
  if (query.userId) {
6151
- conditions.push(eq52(verificationsInIam.userId, query.userId));
7821
+ conditions.push(eq62(verificationsInIam.userId, query.userId));
6152
7822
  }
6153
7823
  if (query.type) {
6154
- conditions.push(eq52(verificationsInIam.type, query.type));
7824
+ conditions.push(eq62(verificationsInIam.type, query.type));
6155
7825
  }
6156
7826
  if (query.status) {
6157
7827
  if (query.status === "active") {
6158
- conditions.push(sql26`${verificationsInIam.expiresAt} > CURRENT_TIMESTAMP`);
7828
+ conditions.push(sql29`${verificationsInIam.expiresAt} > CURRENT_TIMESTAMP`);
6159
7829
  } else if (query.status === "expired") {
6160
7830
  conditions.push(
6161
- sql26`${verificationsInIam.expiresAt} <= CURRENT_TIMESTAMP`
7831
+ sql29`${verificationsInIam.expiresAt} <= CURRENT_TIMESTAMP`
6162
7832
  );
6163
7833
  } else if (query.status === "consumed") {
6164
- conditions.push(sql26`${verificationsInIam.attempt} >= 3`);
7834
+ conditions.push(sql29`${verificationsInIam.attempt} >= 3`);
6165
7835
  }
6166
7836
  }
6167
7837
  const [verifications, totalResult] = await Promise.all([
6168
- database.select().from(verificationsInIam).where(and48(...conditions)).limit(limit).offset(offset),
6169
- 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))
6170
7840
  ]);
6171
7841
  const total = Number(totalResult[0]?.count || 0);
6172
7842
  return c.json({ verifications, total, page, limit }, 200);
@@ -6467,7 +8137,7 @@ var createSessionMiddleware = () => {
6467
8137
  // src/middlewares/tenant-middleware.ts
6468
8138
  import { logger as logger3 } from "@mesob/common";
6469
8139
  import { createMiddleware as createMiddleware2 } from "hono/factory";
6470
- import { HTTPException as HTTPException4 } from "hono/http-exception";
8140
+ import { HTTPException as HTTPException5 } from "hono/http-exception";
6471
8141
  function resolveHost(hostHeader, forwardedHost) {
6472
8142
  const hostHeaderStr = hostHeader || "";
6473
8143
  const forwardedHostStr = forwardedHost || "";
@@ -6531,7 +8201,7 @@ var createTenantMiddleware = (database, config) => {
6531
8201
  );
6532
8202
  c.set("host", host);
6533
8203
  if (!host) {
6534
- throw new HTTPException4(400, { message: "Missing Host header" });
8204
+ throw new HTTPException5(400, { message: "Missing Host header" });
6535
8205
  }
6536
8206
  let tenantId = null;
6537
8207
  let tenant = null;
@@ -6540,13 +8210,13 @@ var createTenantMiddleware = (database, config) => {
6540
8210
  tenantId = result.tenantId;
6541
8211
  tenant = result.tenant;
6542
8212
  } catch {
6543
- throw new HTTPException4(500, { message: "Tenant resolution failed" });
8213
+ throw new HTTPException5(500, { message: "Tenant resolution failed" });
6544
8214
  }
6545
8215
  c.set("tenantId", tenantId);
6546
8216
  c.set("tenant", tenant);
6547
8217
  const error = validateTenant(tenantId, tenant);
6548
8218
  if (error) {
6549
- throw new HTTPException4(404, { message: error });
8219
+ throw new HTTPException5(404, { message: error });
6550
8220
  }
6551
8221
  return await next();
6552
8222
  });