@mesob/auth-hono 0.2.5 → 0.3.1

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
@@ -44,36 +44,36 @@ __export(schema_exports, {
44
44
  usersInIam: () => usersInIam,
45
45
  verificationsInIam: () => verificationsInIam
46
46
  });
47
- import { pgSchema, uniqueIndex, foreignKey, unique, pgPolicy, check, uuid, varchar, timestamp, text, boolean, smallint, index, inet, jsonb } from "drizzle-orm/pg-core";
47
+ import { pgSchema, index, foreignKey, pgPolicy, check, uuid, varchar, timestamp, text, smallint, unique, inet, jsonb, boolean, uniqueIndex } from "drizzle-orm/pg-core";
48
48
  import { sql } from "drizzle-orm";
49
49
  var iam = pgSchema("iam");
50
- var usersInIam = iam.table("users", {
50
+ var verificationsInIam = iam.table("verifications", {
51
51
  id: uuid().default(sql`uuid_generate_v7()`).primaryKey().notNull(),
52
52
  tenantId: varchar("tenant_id", { length: 30 }).notNull(),
53
53
  createdAt: timestamp("created_at", { withTimezone: true, mode: "string" }).default(sql`CURRENT_TIMESTAMP`).notNull(),
54
54
  updatedAt: timestamp("updated_at", { withTimezone: true, mode: "string" }).default(sql`CURRENT_TIMESTAMP`).notNull(),
55
- fullName: text("full_name").notNull(),
56
- image: text(),
57
- phone: text(),
58
- email: text(),
59
- handle: text().notNull(),
60
- emailVerified: boolean("email_verified").default(false).notNull(),
61
- phoneVerified: boolean("phone_verified").default(false).notNull(),
62
- bannedUntil: timestamp("banned_until", { withTimezone: true, mode: "string" }),
63
- lastSignInAt: timestamp("last_sign_in_at", { withTimezone: true, mode: "string" }),
64
- loginAttempt: smallint("login_attempt").default(0).notNull()
55
+ userId: uuid("user_id").notNull(),
56
+ code: text().notNull(),
57
+ expiresAt: timestamp("expires_at", { withTimezone: true, mode: "string" }).notNull(),
58
+ type: text(),
59
+ attempt: smallint().default(0),
60
+ to: text()
65
61
  }, (table) => [
66
- uniqueIndex("users_tenant_lower_email_idx").using("btree", sql`tenant_id`, sql`lower(email)`),
67
- uniqueIndex("users_tenant_lower_handle_idx").using("btree", sql`tenant_id`, sql`lower(handle)`),
62
+ index("verifications_expires_at_idx").using("btree", table.expiresAt.asc().nullsLast().op("timestamptz_ops")),
63
+ index("verifications_lookup_idx").using("btree", table.tenantId.asc().nullsLast().op("text_ops"), table.userId.asc().nullsLast().op("text_ops"), table.type.asc().nullsLast().op("uuid_ops"), table.to.asc().nullsLast().op("text_ops"), table.code.asc().nullsLast().op("text_ops")),
68
64
  foreignKey({
69
65
  columns: [table.tenantId],
70
66
  foreignColumns: [tenantsInIam.id],
71
- name: "users_tenant_id_fkey"
67
+ name: "verifications_tenant_id_fkey"
68
+ }).onUpdate("cascade").onDelete("cascade"),
69
+ foreignKey({
70
+ columns: [table.userId],
71
+ foreignColumns: [usersInIam.id],
72
+ name: "verifications_user_id_fkey"
72
73
  }).onUpdate("cascade").onDelete("cascade"),
73
- unique("users_tenant_phone_key").on(table.tenantId, table.phone),
74
74
  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)` }),
75
- check("users_contact_required_check", sql`(email IS NOT NULL) OR (phone IS NOT NULL)`),
76
- check("users_login_attempt_nonnegative_check", sql`login_attempt >= 0`)
75
+ check("verifications_attempt_nonnegative_check", sql`attempt >= 0`),
76
+ check("verifications_expires_after_created_check", sql`expires_at > created_at`)
77
77
  ]);
78
78
  var sessionsInIam = iam.table("sessions", {
79
79
  id: uuid().default(sql`uuid_generate_v7()`).primaryKey().notNull(),
@@ -89,7 +89,7 @@ var sessionsInIam = iam.table("sessions", {
89
89
  rotatedAt: timestamp("rotated_at", { withTimezone: true, mode: "string" }).default(sql`CURRENT_TIMESTAMP`)
90
90
  }, (table) => [
91
91
  index("sessions_expires_at_idx").using("btree", table.expiresAt.asc().nullsLast().op("timestamptz_ops")),
92
- index("sessions_tenant_user_idx").using("btree", table.tenantId.asc().nullsLast().op("text_ops"), table.userId.asc().nullsLast().op("uuid_ops")),
92
+ index("sessions_tenant_user_idx").using("btree", table.tenantId.asc().nullsLast().op("uuid_ops"), table.userId.asc().nullsLast().op("text_ops")),
93
93
  foreignKey({
94
94
  columns: [table.tenantId],
95
95
  foreignColumns: [tenantsInIam.id],
@@ -104,34 +104,6 @@ var sessionsInIam = iam.table("sessions", {
104
104
  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)` }),
105
105
  check("sessions_expires_after_created_check", sql`expires_at > created_at`)
106
106
  ]);
107
- var verificationsInIam = iam.table("verifications", {
108
- id: uuid().default(sql`uuid_generate_v7()`).primaryKey().notNull(),
109
- tenantId: varchar("tenant_id", { length: 30 }).notNull(),
110
- createdAt: timestamp("created_at", { withTimezone: true, mode: "string" }).default(sql`CURRENT_TIMESTAMP`).notNull(),
111
- updatedAt: timestamp("updated_at", { withTimezone: true, mode: "string" }).default(sql`CURRENT_TIMESTAMP`).notNull(),
112
- userId: uuid("user_id").notNull(),
113
- code: text().notNull(),
114
- expiresAt: timestamp("expires_at", { withTimezone: true, mode: "string" }).notNull(),
115
- type: text(),
116
- attempt: smallint().default(0),
117
- to: text()
118
- }, (table) => [
119
- index("verifications_expires_at_idx").using("btree", table.expiresAt.asc().nullsLast().op("timestamptz_ops")),
120
- index("verifications_lookup_idx").using("btree", table.tenantId.asc().nullsLast().op("text_ops"), table.userId.asc().nullsLast().op("text_ops"), table.type.asc().nullsLast().op("uuid_ops"), table.to.asc().nullsLast().op("text_ops"), table.code.asc().nullsLast().op("text_ops")),
121
- foreignKey({
122
- columns: [table.tenantId],
123
- foreignColumns: [tenantsInIam.id],
124
- name: "verifications_tenant_id_fkey"
125
- }).onUpdate("cascade").onDelete("cascade"),
126
- foreignKey({
127
- columns: [table.userId],
128
- foreignColumns: [usersInIam.id],
129
- name: "verifications_user_id_fkey"
130
- }).onUpdate("cascade").onDelete("cascade"),
131
- 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)` }),
132
- check("verifications_attempt_nonnegative_check", sql`attempt >= 0`),
133
- check("verifications_expires_after_created_check", sql`expires_at > created_at`)
134
- ]);
135
107
  var accountChangesInIam = iam.table("account_changes", {
136
108
  id: uuid().default(sql`uuid_generate_v7()`).primaryKey().notNull(),
137
109
  tenantId: varchar("tenant_id", { length: 30 }).notNull(),
@@ -150,7 +122,8 @@ var accountChangesInIam = iam.table("account_changes", {
150
122
  reason: text()
151
123
  }, (table) => [
152
124
  index("account_changes_expires_at_idx").using("btree", table.expiresAt.asc().nullsLast().op("timestamptz_ops")),
153
- index("account_changes_tenant_user_status_idx").using("btree", table.tenantId.asc().nullsLast().op("uuid_ops"), table.userId.asc().nullsLast().op("text_ops"), table.status.asc().nullsLast().op("text_ops")),
125
+ index("account_changes_tenant_user_status_idx").using("btree", table.tenantId.asc().nullsLast().op("uuid_ops"), table.userId.asc().nullsLast().op("text_ops"), table.status.asc().nullsLast().op("uuid_ops")),
126
+ index("idx_account_changes_expired").using("btree", table.expiresAt.asc().nullsLast().op("text_ops"), table.status.asc().nullsLast().op("text_ops")).where(sql`((status)::text = 'pending'::text)`),
154
127
  foreignKey({
155
128
  columns: [table.tenantId],
156
129
  foreignColumns: [tenantsInIam.id],
@@ -162,9 +135,9 @@ var accountChangesInIam = iam.table("account_changes", {
162
135
  name: "account_changes_user_id_fkey"
163
136
  }).onUpdate("cascade").onDelete("cascade"),
164
137
  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)` }),
165
- check("account_changes_change_type_check", sql`((change_type = 'email'::text) AND (old_email IS NOT NULL) AND (new_email IS NOT NULL) AND (old_phone IS NULL) AND (new_phone IS NULL)) OR ((change_type = 'phone'::text) AND (old_phone IS NOT NULL) AND (new_phone IS NOT NULL) AND (old_email IS NULL) AND (new_email IS NULL))`),
166
138
  check("account_changes_expires_after_created_check", sql`expires_at > created_at`),
167
- check("account_changes_status_check", sql`(status)::text = ANY ((ARRAY['pending'::character varying, 'applied'::character varying, 'cancelled'::character varying, 'expired'::character varying])::text[])`)
139
+ check("account_changes_change_type_check", sql`((change_type = 'EMAIL'::text) AND (old_email IS NOT NULL) AND (new_email IS NOT NULL) AND (old_phone IS NULL) AND (new_phone IS NULL)) OR ((change_type = 'PHONE'::text) AND (old_phone IS NOT NULL) AND (new_phone IS NOT NULL) AND (old_email IS NULL) AND (new_email IS NULL))`),
140
+ check("account_changes_status_check", sql`(status)::text = ANY (ARRAY[('PENDING'::character varying)::text, ('APPLIED'::character varying)::text, ('CANCELLED'::character varying)::text, ('EXPIRED'::character varying)::text])`)
168
141
  ]);
169
142
  var tenantsInIam = iam.table("tenants", {
170
143
  id: varchar({ length: 30 }).primaryKey().notNull(),
@@ -185,21 +158,30 @@ var tenantsInIam = iam.table("tenants", {
185
158
  }, (table) => [
186
159
  index("tenants_is_active_idx").using("btree", table.isActive.asc().nullsLast().op("bool_ops"))
187
160
  ]);
188
- var rolesInIam = iam.table("roles", {
161
+ var rolePermissionsInIam = iam.table("role_permissions", {
189
162
  id: uuid().default(sql`uuid_generate_v7()`).primaryKey().notNull(),
190
163
  tenantId: varchar("tenant_id", { length: 30 }).notNull(),
191
- createdAt: timestamp("created_at", { withTimezone: true, mode: "string" }).default(sql`CURRENT_TIMESTAMP`).notNull(),
192
- updatedAt: timestamp("updated_at", { withTimezone: true, mode: "string" }).default(sql`CURRENT_TIMESTAMP`).notNull(),
193
- name: jsonb().notNull(),
194
- description: jsonb().notNull(),
195
- code: text().notNull()
164
+ roleId: text("role_id").notNull(),
165
+ permissionId: text("permission_id").notNull()
196
166
  }, (table) => [
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")),
197
169
  foreignKey({
198
170
  columns: [table.tenantId],
199
171
  foreignColumns: [tenantsInIam.id],
200
- name: "roles_tenant_id_fkey"
172
+ name: "role_permissions_tenant_id_fkey"
201
173
  }).onUpdate("cascade").onDelete("cascade"),
202
- unique("roles_tenant_code_unique").on(table.tenantId, table.code),
174
+ foreignKey({
175
+ columns: [table.permissionId],
176
+ foreignColumns: [permissionsInIam.id],
177
+ name: "role_permissions_permission_id_fkey"
178
+ }).onUpdate("cascade").onDelete("cascade"),
179
+ 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),
203
185
  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)` })
204
186
  ]);
205
187
  var permissionsInIam = iam.table("permissions", {
@@ -211,82 +193,118 @@ var permissionsInIam = iam.table("permissions", {
211
193
  }, (table) => [
212
194
  unique("permissions_activity_application_feature_key").on(table.activity, table.application, table.feature)
213
195
  ]);
214
- var rolePermissionsInIam = iam.table("role_permissions", {
196
+ var accountsInIam = iam.table("accounts", {
215
197
  id: uuid().default(sql`uuid_generate_v7()`).primaryKey().notNull(),
216
198
  tenantId: varchar("tenant_id", { length: 30 }).notNull(),
217
- roleId: uuid("role_id").notNull(),
218
- permissionId: text("permission_id").notNull()
199
+ userId: uuid("user_id").notNull(),
200
+ provider: text().notNull(),
201
+ providerAccountId: text("provider_account_id").notNull(),
202
+ password: text(),
203
+ passwordLastChangedAt: timestamp("password_last_changed_at", { withTimezone: true, mode: "string" }),
204
+ idToken: text("id_token"),
205
+ accessToken: text("access_token"),
206
+ accessTokenExpiresAt: timestamp("access_token_expires_at", { withTimezone: true, mode: "string" }),
207
+ refreshToken: text("refresh_token"),
208
+ refreshTokenExpiresAt: timestamp("refresh_token_expires_at", { withTimezone: true, mode: "string" }),
209
+ scope: text(),
210
+ expiresAt: timestamp("expires_at", { withTimezone: true, mode: "string" }),
211
+ meta: jsonb()
219
212
  }, (table) => [
213
+ index("idx_accounts_provider_lookup").using("btree", table.tenantId.asc().nullsLast().op("text_ops"), table.provider.asc().nullsLast().op("text_ops"), table.providerAccountId.asc().nullsLast().op("text_ops")),
214
+ index("idx_accounts_user_id").using("btree", table.tenantId.asc().nullsLast().op("text_ops"), table.userId.asc().nullsLast().op("text_ops")),
220
215
  foreignKey({
221
- columns: [table.permissionId],
222
- foreignColumns: [permissionsInIam.id],
223
- name: "role_permissions_permission_id_fkey"
216
+ columns: [table.tenantId],
217
+ foreignColumns: [tenantsInIam.id],
218
+ name: "accounts_tenant_id_fkey"
224
219
  }).onUpdate("cascade").onDelete("cascade"),
225
220
  foreignKey({
226
- columns: [table.roleId],
227
- foreignColumns: [rolesInIam.id],
228
- name: "role_permissions_role_id_fkey"
221
+ columns: [table.userId],
222
+ foreignColumns: [usersInIam.id],
223
+ name: "accounts_user_id_fkey"
229
224
  }).onUpdate("cascade").onDelete("cascade"),
225
+ unique("accounts_tenant_provider_account_unique").on(table.tenantId, table.provider, table.providerAccountId),
226
+ 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
+ ]);
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) => [
230
237
  foreignKey({
231
238
  columns: [table.tenantId],
232
239
  foreignColumns: [tenantsInIam.id],
233
- name: "role_permissions_tenant_id_fkey"
240
+ name: "roles_tenant_id_fkey"
234
241
  }).onUpdate("cascade").onDelete("cascade"),
235
- unique("role_permissions_unique").on(table.roleId, table.permissionId),
242
+ unique("roles_tenant_code_unique").on(table.tenantId, table.code),
236
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)` })
237
244
  ]);
238
- var userRolesInIam = iam.table("user_roles", {
245
+ var usersInIam = iam.table("users", {
239
246
  id: uuid().default(sql`uuid_generate_v7()`).primaryKey().notNull(),
240
247
  tenantId: varchar("tenant_id", { length: 30 }).notNull(),
241
- userId: uuid("user_id").notNull(),
242
- roleId: uuid("role_id").notNull()
248
+ createdAt: timestamp("created_at", { withTimezone: true, mode: "string" }).default(sql`CURRENT_TIMESTAMP`).notNull(),
249
+ updatedAt: timestamp("updated_at", { withTimezone: true, mode: "string" }).default(sql`CURRENT_TIMESTAMP`).notNull(),
250
+ fullName: text("full_name").notNull(),
251
+ image: text(),
252
+ phone: text(),
253
+ email: text(),
254
+ handle: text().notNull(),
255
+ emailVerified: boolean("email_verified").default(false).notNull(),
256
+ phoneVerified: boolean("phone_verified").default(false).notNull(),
257
+ bannedUntil: timestamp("banned_until", { withTimezone: true, mode: "string" }),
258
+ lastSignInAt: timestamp("last_sign_in_at", { withTimezone: true, mode: "string" }),
259
+ loginAttempt: smallint("login_attempt").default(0).notNull(),
260
+ userType: text("user_type").array().default(["employee"]).notNull()
243
261
  }, (table) => [
244
- foreignKey({
245
- columns: [table.roleId],
246
- foreignColumns: [rolesInIam.id],
247
- name: "user_roles_role_id_fkey"
248
- }).onUpdate("cascade").onDelete("cascade"),
262
+ 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
+ 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)`),
264
+ index("idx_users_handle_lookup").using("btree", table.tenantId.asc().nullsLast().op("text_ops"), table.handle.asc().nullsLast().op("text_ops")),
265
+ index("idx_users_phone_lookup").using("btree", table.tenantId.asc().nullsLast().op("text_ops"), table.phone.asc().nullsLast().op("text_ops")).where(sql`(phone IS NOT NULL)`),
266
+ index("idx_users_tenant_email_unique").using("btree", table.tenantId.asc().nullsLast().op("text_ops"), table.email.asc().nullsLast().op("text_ops")).where(sql`(email IS NOT NULL)`),
267
+ index("idx_users_tenant_is_admin").using("btree", table.tenantId.asc().nullsLast().op("text_ops")).where(sql`(user_type @> ARRAY['admin'::text])`),
268
+ index("idx_users_tenant_is_candidate").using("btree", table.tenantId.asc().nullsLast().op("text_ops")).where(sql`(user_type @> ARRAY['candidate'::text])`),
269
+ index("idx_users_tenant_is_employee").using("btree", table.tenantId.asc().nullsLast().op("text_ops")).where(sql`(user_type @> ARRAY['employee'::text])`),
270
+ index("idx_users_user_types_gin").using("gin", table.userType.asc().nullsLast().op("array_ops")),
271
+ uniqueIndex("users_tenant_lower_email_idx").using("btree", sql`tenant_id`, sql`lower(email)`),
272
+ uniqueIndex("users_tenant_lower_handle_idx").using("btree", sql`tenant_id`, sql`lower(handle)`),
249
273
  foreignKey({
250
274
  columns: [table.tenantId],
251
275
  foreignColumns: [tenantsInIam.id],
252
- name: "user_roles_tenant_id_fkey"
253
- }).onUpdate("cascade").onDelete("cascade"),
254
- foreignKey({
255
- columns: [table.userId],
256
- foreignColumns: [usersInIam.id],
257
- name: "user_roles_user_id_fkey"
276
+ name: "users_tenant_id_fkey"
258
277
  }).onUpdate("cascade").onDelete("cascade"),
259
- unique("user_roles_unique").on(table.userId, table.roleId),
260
- 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)` })
278
+ unique("users_tenant_phone_key").on(table.tenantId, table.phone),
279
+ 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)` }),
280
+ check("users_login_attempt_nonnegative_check", sql`login_attempt >= 0`),
281
+ check("users_contact_required_check", sql`(email IS NOT NULL) OR (phone IS NOT NULL)`),
282
+ check("users_user_type_check", sql`user_type <@ ARRAY['candidate'::text, 'employee'::text, 'admin'::text]`)
261
283
  ]);
262
- var accountsInIam = iam.table("accounts", {
284
+ var userRolesInIam = iam.table("user_roles", {
263
285
  id: uuid().default(sql`uuid_generate_v7()`).primaryKey().notNull(),
264
286
  tenantId: varchar("tenant_id", { length: 30 }).notNull(),
265
287
  userId: uuid("user_id").notNull(),
266
- provider: text().notNull(),
267
- providerAccountId: text("provider_account_id").notNull(),
268
- password: text(),
269
- passwordLastChangedAt: timestamp("password_last_changed_at", { withTimezone: true, mode: "string" }),
270
- idToken: text("id_token"),
271
- accessToken: text("access_token"),
272
- accessTokenExpiresAt: timestamp("access_token_expires_at", { withTimezone: true, mode: "string" }),
273
- refreshToken: text("refresh_token"),
274
- refreshTokenExpiresAt: timestamp("refresh_token_expires_at", { withTimezone: true, mode: "string" }),
275
- scope: text(),
276
- expiresAt: timestamp("expires_at", { withTimezone: true, mode: "string" }),
277
- meta: jsonb()
288
+ roleId: text("role_id").notNull()
278
289
  }, (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")),
279
292
  foreignKey({
280
293
  columns: [table.tenantId],
281
294
  foreignColumns: [tenantsInIam.id],
282
- name: "accounts_tenant_id_fkey"
295
+ name: "user_roles_tenant_id_fkey"
283
296
  }).onUpdate("cascade").onDelete("cascade"),
284
297
  foreignKey({
285
298
  columns: [table.userId],
286
299
  foreignColumns: [usersInIam.id],
287
- name: "accounts_user_id_fkey"
300
+ name: "user_roles_user_id_fkey"
288
301
  }).onUpdate("cascade").onDelete("cascade"),
289
- unique("accounts_tenant_provider_account_unique").on(table.tenantId, table.provider, table.providerAccountId),
302
+ 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),
290
308
  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)` })
291
309
  ]);
292
310
  var domainsInIam = iam.table("domains", {
@@ -301,8 +319,8 @@ var domainsInIam = iam.table("domains", {
301
319
  }, (table) => [
302
320
  uniqueIndex("domains_domain_unique_idx").using("btree", sql`lower(domain)`),
303
321
  uniqueIndex("domains_primary_per_tenant_idx").using("btree", table.tenantId.asc().nullsLast().op("text_ops")).where(sql`(is_primary = true)`),
304
- index("domains_tenant_id_idx").using("btree", table.tenantId.asc().nullsLast().op("text_ops")),
305
322
  index("domains_tenant_status_idx").using("btree", table.tenantId.asc().nullsLast().op("text_ops"), table.status.asc().nullsLast().op("text_ops")),
323
+ index("idx_domains_tenant_domain_status").using("btree", table.tenantId.asc().nullsLast().op("text_ops"), table.domain.asc().nullsLast().op("text_ops"), table.status.asc().nullsLast().op("text_ops")),
306
324
  foreignKey({
307
325
  columns: [table.tenantId],
308
326
  foreignColumns: [tenantsInIam.id],
@@ -310,49 +328,49 @@ var domainsInIam = iam.table("domains", {
310
328
  }).onUpdate("cascade").onDelete("cascade"),
311
329
  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)` }),
312
330
  check("domains_domain_format_check", sql`domain ~ '^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?)+$'::text`),
313
- check("domains_status_check", sql`status = ANY (ARRAY['pending'::text, 'active'::text, 'disabled'::text, 'deleted'::text])`)
331
+ check("domains_status_check", sql`status = ANY (ARRAY['PENDING'::text, 'ACTIVE'::text, 'DISABLED'::text, 'DELETED'::text])`)
314
332
  ]);
315
333
 
316
334
  // src/db/relations.ts
317
- var usersInIamRelations = relations(usersInIam, ({ one, many }) => ({
335
+ var verificationsInIamRelations = relations(verificationsInIam, ({ one }) => ({
318
336
  tenantsInIam: one(tenantsInIam, {
319
- fields: [usersInIam.tenantId],
337
+ fields: [verificationsInIam.tenantId],
320
338
  references: [tenantsInIam.id]
321
339
  }),
322
- sessionsInIam: many(sessionsInIam),
323
- verificationsInIam: many(verificationsInIam),
324
- accountChangesInIam: many(accountChangesInIam),
325
- userRolesInIam: many(userRolesInIam),
326
- accountsInIam: many(accountsInIam)
340
+ usersInIam: one(usersInIam, {
341
+ fields: [verificationsInIam.userId],
342
+ references: [usersInIam.id]
343
+ })
327
344
  }));
328
345
  var tenantsInIamRelations = relations(tenantsInIam, ({ many }) => ({
329
- usersInIam: many(usersInIam),
330
- sessionsInIam: many(sessionsInIam),
331
346
  verificationsInIam: many(verificationsInIam),
347
+ sessionsInIam: many(sessionsInIam),
332
348
  accountChangesInIam: many(accountChangesInIam),
333
- rolesInIam: many(rolesInIam),
334
349
  rolePermissionsInIam: many(rolePermissionsInIam),
335
- userRolesInIam: many(userRolesInIam),
336
350
  accountsInIam: many(accountsInIam),
351
+ rolesInIam: many(rolesInIam),
352
+ usersInIam: many(usersInIam),
353
+ userRolesInIam: many(userRolesInIam),
337
354
  domainsInIam: many(domainsInIam)
338
355
  }));
339
- var sessionsInIamRelations = relations(sessionsInIam, ({ one }) => ({
356
+ var usersInIamRelations = relations(usersInIam, ({ one, many }) => ({
357
+ verificationsInIam: many(verificationsInIam),
358
+ sessionsInIam: many(sessionsInIam),
359
+ accountChangesInIam: many(accountChangesInIam),
360
+ accountsInIam: many(accountsInIam),
340
361
  tenantsInIam: one(tenantsInIam, {
341
- fields: [sessionsInIam.tenantId],
362
+ fields: [usersInIam.tenantId],
342
363
  references: [tenantsInIam.id]
343
364
  }),
344
- usersInIam: one(usersInIam, {
345
- fields: [sessionsInIam.userId],
346
- references: [usersInIam.id]
347
- })
365
+ userRolesInIam: many(userRolesInIam)
348
366
  }));
349
- var verificationsInIamRelations = relations(verificationsInIam, ({ one }) => ({
367
+ var sessionsInIamRelations = relations(sessionsInIam, ({ one }) => ({
350
368
  tenantsInIam: one(tenantsInIam, {
351
- fields: [verificationsInIam.tenantId],
369
+ fields: [sessionsInIam.tenantId],
352
370
  references: [tenantsInIam.id]
353
371
  }),
354
372
  usersInIam: one(usersInIam, {
355
- fields: [verificationsInIam.userId],
373
+ fields: [sessionsInIam.userId],
356
374
  references: [usersInIam.id]
357
375
  })
358
376
  }));
@@ -366,15 +384,11 @@ var accountChangesInIamRelations = relations(accountChangesInIam, ({ one }) => (
366
384
  references: [usersInIam.id]
367
385
  })
368
386
  }));
369
- var rolesInIamRelations = relations(rolesInIam, ({ one, many }) => ({
387
+ var rolePermissionsInIamRelations = relations(rolePermissionsInIam, ({ one }) => ({
370
388
  tenantsInIam: one(tenantsInIam, {
371
- fields: [rolesInIam.tenantId],
389
+ fields: [rolePermissionsInIam.tenantId],
372
390
  references: [tenantsInIam.id]
373
391
  }),
374
- rolePermissionsInIam: many(rolePermissionsInIam),
375
- userRolesInIam: many(userRolesInIam)
376
- }));
377
- var rolePermissionsInIamRelations = relations(rolePermissionsInIam, ({ one }) => ({
378
392
  permissionsInIam: one(permissionsInIam, {
379
393
  fields: [rolePermissionsInIam.permissionId],
380
394
  references: [permissionsInIam.id]
@@ -382,37 +396,41 @@ var rolePermissionsInIamRelations = relations(rolePermissionsInIam, ({ one }) =>
382
396
  rolesInIam: one(rolesInIam, {
383
397
  fields: [rolePermissionsInIam.roleId],
384
398
  references: [rolesInIam.id]
385
- }),
386
- tenantsInIam: one(tenantsInIam, {
387
- fields: [rolePermissionsInIam.tenantId],
388
- references: [tenantsInIam.id]
389
399
  })
390
400
  }));
391
401
  var permissionsInIamRelations = relations(permissionsInIam, ({ many }) => ({
392
402
  rolePermissionsInIam: many(rolePermissionsInIam)
393
403
  }));
394
- var userRolesInIamRelations = relations(userRolesInIam, ({ one }) => ({
395
- rolesInIam: one(rolesInIam, {
396
- fields: [userRolesInIam.roleId],
397
- references: [rolesInIam.id]
404
+ var rolesInIamRelations = relations(rolesInIam, ({ one, many }) => ({
405
+ rolePermissionsInIam: many(rolePermissionsInIam),
406
+ tenantsInIam: one(tenantsInIam, {
407
+ fields: [rolesInIam.tenantId],
408
+ references: [tenantsInIam.id]
398
409
  }),
410
+ userRolesInIam: many(userRolesInIam)
411
+ }));
412
+ var accountsInIamRelations = relations(accountsInIam, ({ one }) => ({
399
413
  tenantsInIam: one(tenantsInIam, {
400
- fields: [userRolesInIam.tenantId],
414
+ fields: [accountsInIam.tenantId],
401
415
  references: [tenantsInIam.id]
402
416
  }),
403
417
  usersInIam: one(usersInIam, {
404
- fields: [userRolesInIam.userId],
418
+ fields: [accountsInIam.userId],
405
419
  references: [usersInIam.id]
406
420
  })
407
421
  }));
408
- var accountsInIamRelations = relations(accountsInIam, ({ one }) => ({
422
+ var userRolesInIamRelations = relations(userRolesInIam, ({ one }) => ({
409
423
  tenantsInIam: one(tenantsInIam, {
410
- fields: [accountsInIam.tenantId],
424
+ fields: [userRolesInIam.tenantId],
411
425
  references: [tenantsInIam.id]
412
426
  }),
413
427
  usersInIam: one(usersInIam, {
414
- fields: [accountsInIam.userId],
428
+ fields: [userRolesInIam.userId],
415
429
  references: [usersInIam.id]
430
+ }),
431
+ rolesInIam: one(rolesInIam, {
432
+ fields: [userRolesInIam.roleId],
433
+ references: [rolesInIam.id]
416
434
  })
417
435
  }));
418
436
  var domainsInIamRelations = relations(domainsInIam, ({ one }) => ({
@@ -446,8 +464,6 @@ var fetchSessionByToken = async ({
446
464
  userId: sessionsInIam.userId,
447
465
  expiresAt: sessionsInIam.expiresAt,
448
466
  createdAt: sessionsInIam.createdAt,
449
- userAgent: sessionsInIam.userAgent,
450
- ip: sessionsInIam.ip,
451
467
  meta: sessionsInIam.meta
452
468
  }).from(sessionsInIam).where(
453
469
  and(
@@ -535,20 +551,18 @@ var fetchUserWithRoles = async ({
535
551
  emailVerified: usersInIam.emailVerified,
536
552
  phoneVerified: usersInIam.phoneVerified,
537
553
  lastSignInAt: usersInIam.lastSignInAt,
538
- userRoles: sql3`
539
- COALESCE(
540
- json_agg(
541
- json_build_object(
542
- 'id', ${userRolesInIam.id},
543
- 'roleId', ${rolesInIam.id},
544
- 'code', ${rolesInIam.code},
545
- 'name', ${rolesInIam.name},
546
- 'description', ${rolesInIam.description}
547
- )
548
- ) FILTER (WHERE ${userRolesInIam.id} IS NOT NULL),
549
- '[]'::json
550
- )
551
- `
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
+ )`
552
566
  }).from(usersInIam).leftJoin(
553
567
  userRolesInIam,
554
568
  and3(
@@ -561,6 +575,15 @@ var fetchUserWithRoles = async ({
561
575
  eq3(userRolesInIam.roleId, rolesInIam.id),
562
576
  eq3(rolesInIam.tenantId, tenantId)
563
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)
564
587
  ).where(and3(eq3(usersInIam.id, userId), eq3(usersInIam.tenantId, tenantId))).groupBy(usersInIam.id).limit(1);
565
588
  return userResult || null;
566
589
  };
@@ -798,13 +821,6 @@ import { z } from "zod";
798
821
  var emailField = z.string().trim().email("Invalid email address").max(255, "Email too long");
799
822
  var phoneField = z.string().trim().min(6, "Phone too short").max(30, "Phone too long").regex(/^[+()\d\s-]+$/, "Invalid phone number format");
800
823
  var passwordField = z.string().min(8, "Password must be at least 8 characters").max(128, "Password too long");
801
- var userRoleSchema = z.object({
802
- id: z.string().uuid(),
803
- roleId: z.string().uuid(),
804
- code: z.string(),
805
- name: z.string(),
806
- description: z.string()
807
- });
808
824
  var userSchema = z.object({
809
825
  id: z.string().uuid(),
810
826
  tenantId: z.string(),
@@ -816,7 +832,9 @@ var userSchema = z.object({
816
832
  emailVerified: z.boolean(),
817
833
  phoneVerified: z.boolean(),
818
834
  lastSignInAt: z.string().datetime().nullable(),
819
- userRoles: z.array(userRoleSchema).nullable()
835
+ roles: z.array(z.string()).nullable().optional(),
836
+ roleCodes: z.array(z.string()).nullable().optional(),
837
+ permissions: z.array(z.string()).nullable().optional()
820
838
  });
821
839
  var sessionSchema = z.object({
822
840
  id: z.string().uuid(),
@@ -962,11 +980,14 @@ var checkAccountHandler = async (c) => {
962
980
  const resolvedTenantId = ensureTenantId(config, tenantId);
963
981
  const { username } = body;
964
982
  const isEmail = username.includes("@");
983
+ const userTypeFilter = sql4`${usersInIam.userType} @> ARRAY[${config.userType}]::text[]`;
965
984
  const whereClause = isEmail ? and4(
966
985
  eq4(usersInIam.tenantId, resolvedTenantId),
986
+ userTypeFilter,
967
987
  sql4`lower(${usersInIam.email}) = lower(${username})`
968
988
  ) : and4(
969
989
  eq4(usersInIam.tenantId, resolvedTenantId),
990
+ userTypeFilter,
970
991
  eq4(usersInIam.phone, username)
971
992
  );
972
993
  const [result] = await database.select({
@@ -982,6 +1003,7 @@ var checkAccountHandler = async (c) => {
982
1003
  };
983
1004
 
984
1005
  // src/routes/auth/handler/sign-in.ts
1006
+ import { logger as logger2 } from "@mesob/common";
985
1007
  import { and as and8, eq as eq8 } from "drizzle-orm";
986
1008
 
987
1009
  // src/errors.ts
@@ -1000,16 +1022,11 @@ var AUTH_ERRORS = {
1000
1022
  };
1001
1023
 
1002
1024
  // src/lib/normalize-user.ts
1003
- var normalizeUser = (user) => {
1004
- return {
1005
- ...user,
1006
- userRoles: user.userRoles ? user.userRoles.map((role) => ({
1007
- ...role,
1008
- name: typeof role.name === "string" ? role.name : "",
1009
- description: typeof role.description === "string" ? role.description : ""
1010
- })) : null
1011
- };
1012
- };
1025
+ var normalizeUser = (user) => ({
1026
+ ...user,
1027
+ roles: user.roles ?? null,
1028
+ roleCodes: user.roleCodes ?? null
1029
+ });
1013
1030
 
1014
1031
  // src/lib/session.ts
1015
1032
  import { dayjs } from "@mesob/common";
@@ -1258,7 +1275,8 @@ var createUserWithAccount = async ({
1258
1275
  email: email || null,
1259
1276
  phone: phone || null,
1260
1277
  emailVerified: email ? !config.email.required : false,
1261
- phoneVerified: phone ? !config.phone.required : false
1278
+ phoneVerified: phone ? !config.phone.required : false,
1279
+ userType: [config.userType]
1262
1280
  }).returning();
1263
1281
  const passwordHash = await hashPassword(password);
1264
1282
  await tx.insert(accountsInIam).values({
@@ -1270,16 +1288,44 @@ var createUserWithAccount = async ({
1270
1288
  });
1271
1289
  return user;
1272
1290
  };
1273
- var fetchUserByIdentifier = async ({
1291
+ var fetchUserForLogin = async ({
1274
1292
  database,
1275
1293
  identifier,
1276
1294
  tenantId,
1277
- isEmail
1295
+ isEmail,
1296
+ userType
1278
1297
  }) => {
1298
+ const userTypeFilter = sql6`${usersInIam.userType} @> ARRAY[${userType}]::text[]`;
1279
1299
  const whereClause = isEmail ? and6(
1280
1300
  eq6(usersInIam.tenantId, tenantId),
1301
+ userTypeFilter,
1281
1302
  sql6`lower(${usersInIam.email}) = lower(${identifier})`
1282
- ) : and6(eq6(usersInIam.tenantId, tenantId), eq6(usersInIam.phone, identifier));
1303
+ ) : and6(
1304
+ eq6(usersInIam.tenantId, tenantId),
1305
+ userTypeFilter,
1306
+ eq6(usersInIam.phone, identifier)
1307
+ );
1308
+ const [row] = await database.select({
1309
+ id: usersInIam.id,
1310
+ tenantId: usersInIam.tenantId,
1311
+ fullName: usersInIam.fullName,
1312
+ email: usersInIam.email,
1313
+ phone: usersInIam.phone,
1314
+ handle: usersInIam.handle,
1315
+ image: usersInIam.image,
1316
+ emailVerified: usersInIam.emailVerified,
1317
+ phoneVerified: usersInIam.phoneVerified,
1318
+ lastSignInAt: usersInIam.lastSignInAt,
1319
+ bannedUntil: usersInIam.bannedUntil,
1320
+ loginAttempt: usersInIam.loginAttempt
1321
+ }).from(usersInIam).where(whereClause).limit(1);
1322
+ return row || null;
1323
+ };
1324
+ var fetchUserByIdWithRoles = async ({
1325
+ database,
1326
+ userId,
1327
+ tenantId
1328
+ }) => {
1283
1329
  const [result] = await database.select({
1284
1330
  id: usersInIam.id,
1285
1331
  tenantId: usersInIam.tenantId,
@@ -1293,20 +1339,18 @@ var fetchUserByIdentifier = async ({
1293
1339
  lastSignInAt: usersInIam.lastSignInAt,
1294
1340
  bannedUntil: usersInIam.bannedUntil,
1295
1341
  loginAttempt: usersInIam.loginAttempt,
1296
- userRoles: sql6`
1297
- COALESCE(
1298
- json_agg(
1299
- json_build_object(
1300
- 'id', ${userRolesInIam.id},
1301
- 'roleId', ${rolesInIam.id},
1302
- 'code', ${rolesInIam.code},
1303
- 'name', ${rolesInIam.name},
1304
- 'description', ${rolesInIam.description}
1305
- )
1306
- ) FILTER (WHERE ${userRolesInIam.id} IS NOT NULL),
1307
- '[]'::json
1308
- )
1309
- `
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
+ )`
1310
1354
  }).from(usersInIam).leftJoin(
1311
1355
  userRolesInIam,
1312
1356
  and6(
@@ -1319,7 +1363,16 @@ var fetchUserByIdentifier = async ({
1319
1363
  eq6(userRolesInIam.roleId, rolesInIam.id),
1320
1364
  eq6(rolesInIam.tenantId, tenantId)
1321
1365
  )
1322
- ).where(whereClause).groupBy(usersInIam.id).limit(1);
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);
1323
1376
  return result || null;
1324
1377
  };
1325
1378
 
@@ -1535,16 +1588,26 @@ var signInHandler = async (c) => {
1535
1588
  if (!(isEmail || config.phone.enabled)) {
1536
1589
  return c.json({ error: "Phone authentication is disabled" }, 401);
1537
1590
  }
1538
- const user = await fetchUserByIdentifier({
1591
+ const user = await fetchUserForLogin({
1539
1592
  database,
1540
1593
  identifier,
1541
1594
  tenantId: resolvedTenantId,
1542
- isEmail
1595
+ isEmail,
1596
+ userType: config.userType
1543
1597
  });
1544
1598
  if (!user) {
1599
+ logger2.log("[sign-in] 401: user not found", {
1600
+ identifier,
1601
+ tenantId: resolvedTenantId,
1602
+ userType: config.userType
1603
+ });
1545
1604
  return c.json({ error: AUTH_ERRORS.UNAUTHORIZED }, 401);
1546
1605
  }
1547
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
+ });
1548
1611
  return c.json(
1549
1612
  {
1550
1613
  error: "Account locked. Try again later.",
@@ -1568,6 +1631,7 @@ var signInHandler = async (c) => {
1568
1631
  )
1569
1632
  ).limit(1);
1570
1633
  if (!account?.password) {
1634
+ logger2.log("[sign-in] 401: no credentials account", { userId: user.id });
1571
1635
  return c.json({ error: AUTH_ERRORS.UNAUTHORIZED }, 401);
1572
1636
  }
1573
1637
  const passwordValid = await verifyPassword(password, account.password);
@@ -1585,13 +1649,17 @@ var signInHandler = async (c) => {
1585
1649
  eq8(usersInIam.tenantId, resolvedTenantId)
1586
1650
  )
1587
1651
  );
1652
+ logger2.log("[sign-in] 401: invalid password", {
1653
+ userId: user.id,
1654
+ loginAttempt: newAttempt
1655
+ });
1588
1656
  return c.json({ error: AUTH_ERRORS.UNAUTHORIZED }, 401);
1589
1657
  }
1590
1658
  const isVerified = isEmail ? user.emailVerified : user.phoneVerified;
1591
1659
  if (isEmail && config.email.required && !isVerified) {
1592
1660
  return handleEmailVerification({
1593
1661
  c,
1594
- user,
1662
+ user: { ...user, roles: [] },
1595
1663
  config,
1596
1664
  database,
1597
1665
  tenantId: resolvedTenantId
@@ -1600,7 +1668,7 @@ var signInHandler = async (c) => {
1600
1668
  if (!isEmail && config.phone.required && !isVerified) {
1601
1669
  return handlePhoneVerification({
1602
1670
  c,
1603
- user,
1671
+ user: { ...user, roles: [] },
1604
1672
  config,
1605
1673
  database,
1606
1674
  tenantId: resolvedTenantId
@@ -1641,22 +1709,24 @@ var signInHandler = async (c) => {
1641
1709
  userId: sessionsInIam.userId,
1642
1710
  expiresAt: sessionsInIam.expiresAt,
1643
1711
  createdAt: sessionsInIam.createdAt,
1644
- updatedAt: sessionsInIam.updatedAt,
1645
- userAgent: sessionsInIam.userAgent,
1646
- ip: sessionsInIam.ip
1712
+ meta: sessionsInIam.meta
1647
1713
  });
1648
1714
  setSessionCookie(c, sessionToken, config, {
1649
1715
  expires: new Date(expiresAt)
1650
1716
  });
1717
+ const fullUser = await fetchUserByIdWithRoles({
1718
+ database,
1719
+ userId: user.id,
1720
+ tenantId: resolvedTenantId
1721
+ });
1651
1722
  return c.json(
1652
1723
  {
1653
- user: normalizeUser(user),
1724
+ user: normalizeUser(fullUser ?? { ...user, roles: [] }),
1654
1725
  session: {
1655
1726
  id: session.id,
1656
1727
  expiresAt: session.expiresAt,
1657
1728
  createdAt: session.createdAt,
1658
- userAgent: session.userAgent,
1659
- ip: session.ip
1729
+ meta: session.meta
1660
1730
  },
1661
1731
  sessionExpiresAt: session.expiresAt
1662
1732
  },
@@ -2736,20 +2806,10 @@ var forgotPasswordHandler = async (c) => {
2736
2806
  emailVerified: usersInIam.emailVerified,
2737
2807
  phoneVerified: usersInIam.phoneVerified,
2738
2808
  lastSignInAt: usersInIam.lastSignInAt,
2739
- userRoles: sql11`
2740
- COALESCE(
2741
- json_agg(
2742
- json_build_object(
2743
- 'id', ${userRolesInIam.id},
2744
- 'roleId', ${rolesInIam.id},
2745
- 'code', ${rolesInIam.code},
2746
- 'name', ${rolesInIam.name},
2747
- 'description', ${rolesInIam.description}
2748
- )
2749
- ) FILTER (WHERE ${userRolesInIam.id} IS NOT NULL),
2750
- '[]'::json
2751
- )
2752
- `
2809
+ roles: sql11`COALESCE(
2810
+ array_to_json(array_agg(${rolesInIam.id}) FILTER (WHERE ${userRolesInIam.id} IS NOT NULL)),
2811
+ '[]'::json
2812
+ )`
2753
2813
  }).from(usersInIam).leftJoin(
2754
2814
  userRolesInIam,
2755
2815
  and18(
@@ -2781,20 +2841,10 @@ var forgotPasswordHandler = async (c) => {
2781
2841
  emailVerified: usersInIam.emailVerified,
2782
2842
  phoneVerified: usersInIam.phoneVerified,
2783
2843
  lastSignInAt: usersInIam.lastSignInAt,
2784
- userRoles: sql11`
2785
- COALESCE(
2786
- json_agg(
2787
- json_build_object(
2788
- 'id', ${userRolesInIam.id},
2789
- 'roleId', ${rolesInIam.id},
2790
- 'code', ${rolesInIam.code},
2791
- 'name', ${rolesInIam.name},
2792
- 'description', ${rolesInIam.description}
2793
- )
2794
- ) FILTER (WHERE ${userRolesInIam.id} IS NOT NULL),
2795
- '[]'::json
2796
- )
2797
- `
2844
+ roles: sql11`COALESCE(
2845
+ array_to_json(array_agg(${rolesInIam.id}) FILTER (WHERE ${userRolesInIam.id} IS NOT NULL)),
2846
+ '[]'::json
2847
+ )`
2798
2848
  }).from(usersInIam).leftJoin(
2799
2849
  userRolesInIam,
2800
2850
  and18(
@@ -3739,9 +3789,7 @@ var sessionHandler = (c) => {
3739
3789
  session: {
3740
3790
  id: session.id,
3741
3791
  expiresAt: session.expiresAt,
3742
- createdAt: session.createdAt,
3743
- userAgent: session.userAgent,
3744
- ip: session.ip
3792
+ createdAt: session.createdAt
3745
3793
  }
3746
3794
  },
3747
3795
  200
@@ -3966,9 +4014,7 @@ var sessionRoute = createRoute7({
3966
4014
  session: z4.object({
3967
4015
  id: z4.string().uuid(),
3968
4016
  expiresAt: z4.string().datetime(),
3969
- createdAt: z4.string().datetime(),
3970
- userAgent: z4.string().nullable(),
3971
- ip: z4.string().nullable()
4017
+ createdAt: z4.string().datetime()
3972
4018
  }).nullable()
3973
4019
  })
3974
4020
  }
@@ -4332,6 +4378,7 @@ var role_permissions_route_default = rolePermissionRoutes;
4332
4378
  import { createRoute as createRoute9, OpenAPIHono as OpenAPIHono9 } from "@hono/zod-openapi";
4333
4379
 
4334
4380
  // src/routes/roles/handler/create-role.ts
4381
+ import { randomUUID } from "crypto";
4335
4382
  var createRoleHandler = async (c) => {
4336
4383
  const body = c.req.valid("json");
4337
4384
  const config = c.get("config");
@@ -4339,6 +4386,7 @@ var createRoleHandler = async (c) => {
4339
4386
  const tenantId = c.get("tenantId");
4340
4387
  const resolvedTenantId = ensureTenantId(config, tenantId);
4341
4388
  const [role] = await database.insert(rolesInIam).values({
4389
+ id: randomUUID(),
4342
4390
  tenantId: resolvedTenantId,
4343
4391
  name: body.name,
4344
4392
  description: body.description,
@@ -4880,7 +4928,44 @@ import { createRoute as createRoute12, OpenAPIHono as OpenAPIHono12 } from "@hon
4880
4928
 
4881
4929
  // src/routes/tenants/handler/create-tenant.ts
4882
4930
  import { eq as eq38 } from "drizzle-orm";
4931
+
4932
+ // src/lib/has-role-permission.ts
4933
+ import { HTTPException as HTTPException3 } from "hono/http-exception";
4934
+ var toArray = (v) => {
4935
+ return Array.isArray(v) ? v : [v];
4936
+ };
4937
+ var hasRole = (c, role) => {
4938
+ const user = c.get("user");
4939
+ const codes = user?.roleCodes;
4940
+ if (!codes?.length) {
4941
+ return false;
4942
+ }
4943
+ const check2 = toArray(role);
4944
+ return check2.some((r) => codes.includes(r));
4945
+ };
4946
+ var hasRoleThrow = (c, role) => {
4947
+ if (!hasRole(c, role)) {
4948
+ throw new HTTPException3(401, { message: "Unauthorized" });
4949
+ }
4950
+ };
4951
+ var hasPermission = (c, permission) => {
4952
+ const user = c.get("user");
4953
+ const perms = user?.permissions;
4954
+ if (!perms?.length) {
4955
+ return false;
4956
+ }
4957
+ const check2 = toArray(permission);
4958
+ return check2.some((p) => perms.includes(p));
4959
+ };
4960
+ var hasPermissionThrow = (c, permission) => {
4961
+ if (!hasPermission(c, permission)) {
4962
+ throw new HTTPException3(401, { message: "Unauthorized" });
4963
+ }
4964
+ };
4965
+
4966
+ // src/routes/tenants/handler/create-tenant.ts
4883
4967
  var createTenantHandler = async (c) => {
4968
+ hasRoleThrow(c, ["owner", "tenant-admin"]);
4884
4969
  const body = c.req.valid("json");
4885
4970
  const database = c.get("database");
4886
4971
  const [existing] = await database.select().from(tenantsInIam).where(eq38(tenantsInIam.id, body.id)).limit(1);
@@ -4908,6 +4993,7 @@ var createTenantHandler = async (c) => {
4908
4993
  // src/routes/tenants/handler/delete-tenant.ts
4909
4994
  import { eq as eq39 } from "drizzle-orm";
4910
4995
  var deleteTenantHandler = async (c) => {
4996
+ hasRoleThrow(c, ["owner", "tenant-admin"]);
4911
4997
  const { id } = c.req.valid("param");
4912
4998
  const database = c.get("database");
4913
4999
  const [existing] = await database.select().from(tenantsInIam).where(eq39(tenantsInIam.id, id)).limit(1);
@@ -4921,6 +5007,7 @@ var deleteTenantHandler = async (c) => {
4921
5007
  // src/routes/tenants/handler/get-tenant.ts
4922
5008
  import { eq as eq40 } from "drizzle-orm";
4923
5009
  var getTenantHandler = async (c) => {
5010
+ hasRoleThrow(c, ["owner", "tenant-admin"]);
4924
5011
  const { id } = c.req.valid("param");
4925
5012
  const database = c.get("database");
4926
5013
  const [tenant] = await database.select().from(tenantsInIam).where(eq40(tenantsInIam.id, id)).limit(1);
@@ -4931,8 +5018,14 @@ var getTenantHandler = async (c) => {
4931
5018
  };
4932
5019
 
4933
5020
  // src/routes/tenants/handler/list-tenants.ts
4934
- import { and as and38, eq as eq41, sql as sql20 } from "drizzle-orm";
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 = {
5023
+ createdAt: tenantsInIam.createdAt,
5024
+ updatedAt: tenantsInIam.updatedAt,
5025
+ name: sql20`${tenantsInIam.name}::text`
5026
+ };
4935
5027
  var listTenantsHandler = async (c) => {
5028
+ hasRoleThrow(c, ["owner", "tenant-admin"]);
4936
5029
  const query = c.req.valid("query");
4937
5030
  const database = c.get("database");
4938
5031
  const page = query.page || 1;
@@ -4942,9 +5035,27 @@ var listTenantsHandler = async (c) => {
4942
5035
  if (query.isActive !== void 0) {
4943
5036
  conditions.push(eq41(tenantsInIam.isActive, query.isActive));
4944
5037
  }
5038
+ if (query.filter === "isActive:true") {
5039
+ conditions.push(eq41(tenantsInIam.isActive, true));
5040
+ } else if (query.filter === "isActive:false") {
5041
+ conditions.push(eq41(tenantsInIam.isActive, false));
5042
+ }
5043
+ if (query.search?.trim()) {
5044
+ const term = `%${query.search.trim()}%`;
5045
+ const searchCond = or(
5046
+ ilike2(tenantsInIam.id, term),
5047
+ ilike2(sql20`${tenantsInIam.name}::text`, term)
5048
+ );
5049
+ if (searchCond) {
5050
+ conditions.push(searchCond);
5051
+ }
5052
+ }
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;
4945
5056
  const [tenants, totalResult] = await Promise.all([
4946
- database.select().from(tenantsInIam).where(conditions.length > 0 ? and38(...conditions) : void 0).limit(limit).offset(offset),
4947
- database.select({ count: sql20`count(*)` }).from(tenantsInIam).where(conditions.length > 0 ? and38(...conditions) : void 0)
5057
+ database.select().from(tenantsInIam).where(whereClause).orderBy(orderDir(sortCol)).limit(limit).offset(offset),
5058
+ database.select({ count: sql20`count(*)` }).from(tenantsInIam).where(whereClause)
4948
5059
  ]);
4949
5060
  const total = Number(totalResult[0]?.count || 0);
4950
5061
  return c.json({ tenants, total, page, limit }, 200);
@@ -4953,6 +5064,7 @@ var listTenantsHandler = async (c) => {
4953
5064
  // src/routes/tenants/handler/update-tenant.ts
4954
5065
  import { eq as eq42, sql as sql21 } from "drizzle-orm";
4955
5066
  var updateTenantHandler = async (c) => {
5067
+ hasRoleThrow(c, ["owner", "tenant-admin"]);
4956
5068
  const { id } = c.req.valid("param");
4957
5069
  const body = c.req.valid("json");
4958
5070
  const database = c.get("database");
@@ -5009,10 +5121,16 @@ var updateTenantHandler = async (c) => {
5009
5121
 
5010
5122
  // src/routes/tenants/tenants.schema.ts
5011
5123
  import { z as z9 } from "zod";
5124
+ var tenantSortFields = ["createdAt", "updatedAt", "name"];
5125
+ var tenantFilterValues = ["", "isActive:true", "isActive:false"];
5012
5126
  var listTenantsQuerySchema = z9.object({
5013
5127
  page: z9.coerce.number().min(1).default(1).optional(),
5014
5128
  limit: z9.coerce.number().min(1).max(100).default(20).optional(),
5015
- isActive: z9.coerce.boolean().optional()
5129
+ isActive: z9.coerce.boolean().optional(),
5130
+ search: z9.string().optional(),
5131
+ filter: z9.enum(tenantFilterValues).optional(),
5132
+ sort: z9.enum(tenantSortFields).optional(),
5133
+ order: z9.enum(["asc", "desc"]).optional()
5016
5134
  });
5017
5135
  var tenantIdParamSchema = z9.object({
5018
5136
  id: z9.string()
@@ -5296,17 +5414,17 @@ var assignUserRoleSchema = z10.object({
5296
5414
  userId: z10.uuid(),
5297
5415
  roleId: z10.uuid()
5298
5416
  });
5299
- var userRoleSchema2 = z10.object({
5417
+ var userRoleSchema = z10.object({
5300
5418
  id: z10.uuid(),
5301
5419
  tenantId: z10.string(),
5302
5420
  userId: z10.uuid(),
5303
5421
  roleId: z10.uuid()
5304
5422
  });
5305
5423
  var listUserRolesResponseSchema = z10.object({
5306
- userRoles: z10.array(userRoleSchema2)
5424
+ userRoles: z10.array(userRoleSchema)
5307
5425
  });
5308
5426
  var userRoleResponseSchema = z10.object({
5309
- userRole: userRoleSchema2
5427
+ userRole: userRoleSchema
5310
5428
  });
5311
5429
  var revokeUserRoleResponseSchema = z10.object({
5312
5430
  message: z10.string()
@@ -5547,7 +5665,14 @@ var getUserHandler = async (c) => {
5547
5665
  };
5548
5666
 
5549
5667
  // src/routes/users/handler/list-users.ts
5550
- import { and as and44, eq as eq48, ilike as ilike2, sql as sql24 } from "drizzle-orm";
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 = {
5670
+ createdAt: usersInIam.createdAt,
5671
+ updatedAt: usersInIam.updatedAt,
5672
+ fullName: usersInIam.fullName,
5673
+ handle: usersInIam.handle,
5674
+ lastSignInAt: usersInIam.lastSignInAt
5675
+ };
5551
5676
  var listUsersHandler = async (c) => {
5552
5677
  const query = c.req.valid("query");
5553
5678
  const database = c.get("database");
@@ -5557,14 +5682,24 @@ var listUsersHandler = async (c) => {
5557
5682
  const offset = (page - 1) * limit;
5558
5683
  const conditions = [eq48(usersInIam.tenantId, tenantId)];
5559
5684
  if (query.email) {
5560
- conditions.push(ilike2(usersInIam.email, `%${query.email}%`));
5685
+ conditions.push(ilike3(usersInIam.email, `%${query.email}%`));
5561
5686
  }
5562
5687
  if (query.phone) {
5563
- conditions.push(ilike2(usersInIam.phone, `%${query.phone}%`));
5688
+ conditions.push(ilike3(usersInIam.phone, `%${query.phone}%`));
5564
5689
  }
5565
5690
  if (query.handle) {
5566
- conditions.push(ilike2(usersInIam.handle, `%${query.handle}%`));
5567
- }
5691
+ conditions.push(ilike3(usersInIam.handle, `%${query.handle}%`));
5692
+ }
5693
+ if (query.filter === "emailVerified") {
5694
+ conditions.push(eq48(usersInIam.emailVerified, true));
5695
+ } else if (query.filter === "phoneVerified") {
5696
+ conditions.push(eq48(usersInIam.phoneVerified, true));
5697
+ } else if (query.filter === "notVerified") {
5698
+ conditions.push(eq48(usersInIam.emailVerified, false));
5699
+ conditions.push(eq48(usersInIam.phoneVerified, false));
5700
+ }
5701
+ const orderDir = query.order === "asc" ? asc3 : desc3;
5702
+ const sortCol = query.sort && sortColumnMap2[query.sort] ? sortColumnMap2[query.sort] : usersInIam.createdAt;
5568
5703
  const [users, totalResult] = await Promise.all([
5569
5704
  database.select({
5570
5705
  id: usersInIam.id,
@@ -5577,7 +5712,7 @@ var listUsersHandler = async (c) => {
5577
5712
  emailVerified: usersInIam.emailVerified,
5578
5713
  phoneVerified: usersInIam.phoneVerified,
5579
5714
  lastSignInAt: usersInIam.lastSignInAt
5580
- }).from(usersInIam).where(and44(...conditions)).limit(limit).offset(offset),
5715
+ }).from(usersInIam).where(and44(...conditions)).orderBy(orderDir(sortCol)).limit(limit).offset(offset),
5581
5716
  database.select({ count: sql24`count(*)` }).from(usersInIam).where(and44(...conditions))
5582
5717
  ]);
5583
5718
  const total = Number(totalResult[0]?.count || 0);
@@ -5585,7 +5720,7 @@ var listUsersHandler = async (c) => {
5585
5720
  {
5586
5721
  users: users.map((u) => ({
5587
5722
  ...u,
5588
- userRoles: null
5723
+ roles: null
5589
5724
  })),
5590
5725
  total,
5591
5726
  page,
@@ -5596,7 +5731,7 @@ var listUsersHandler = async (c) => {
5596
5731
  };
5597
5732
 
5598
5733
  // src/routes/users/handler/search-users.ts
5599
- import { and as and45, eq as eq49, ilike as ilike3, or } from "drizzle-orm";
5734
+ import { and as and45, eq as eq49, ilike as ilike4, or as or2 } from "drizzle-orm";
5600
5735
  var searchUsersHandler = async (c) => {
5601
5736
  const query = c.req.valid("query");
5602
5737
  const database = c.get("database");
@@ -5604,10 +5739,10 @@ var searchUsersHandler = async (c) => {
5604
5739
  const limit = query.limit || 20;
5605
5740
  const conditions = [eq49(usersInIam.tenantId, tenantId)];
5606
5741
  if (query.search && query.search.trim().length > 0) {
5607
- const searchCondition = or(
5608
- ilike3(usersInIam.fullName, `%${query.search}%`),
5609
- ilike3(usersInIam.email, `%${query.search}%`),
5610
- ilike3(usersInIam.handle, `%${query.search}%`)
5742
+ const searchCondition = or2(
5743
+ ilike4(usersInIam.fullName, `%${query.search}%`),
5744
+ ilike4(usersInIam.email, `%${query.search}%`),
5745
+ ilike4(usersInIam.handle, `%${query.search}%`)
5611
5746
  );
5612
5747
  if (searchCondition) {
5613
5748
  conditions.push(searchCondition);
@@ -5691,13 +5826,29 @@ var updateUserHandler = async (c) => {
5691
5826
 
5692
5827
  // src/routes/users/users.schema.ts
5693
5828
  import { z as z11 } from "zod";
5829
+ var sortableFields = [
5830
+ "createdAt",
5831
+ "updatedAt",
5832
+ "fullName",
5833
+ "handle",
5834
+ "lastSignInAt"
5835
+ ];
5836
+ var filterValues = [
5837
+ "",
5838
+ "emailVerified",
5839
+ "phoneVerified",
5840
+ "notVerified"
5841
+ ];
5694
5842
  var listUsersQuerySchema = z11.object({
5695
5843
  page: z11.coerce.number().min(1).default(1).optional(),
5696
5844
  limit: z11.coerce.number().min(1).max(100).default(20).optional(),
5697
5845
  tenantId: z11.string().optional(),
5698
5846
  email: z11.string().optional(),
5699
5847
  phone: z11.string().optional(),
5700
- handle: z11.string().optional()
5848
+ handle: z11.string().optional(),
5849
+ filter: z11.enum(filterValues).optional(),
5850
+ sort: z11.enum(sortableFields).optional(),
5851
+ order: z11.enum(["asc", "desc"]).optional()
5701
5852
  });
5702
5853
  var userIdParamSchema2 = z11.object({
5703
5854
  id: z11.uuid()
@@ -6314,9 +6465,9 @@ var createSessionMiddleware = () => {
6314
6465
  };
6315
6466
 
6316
6467
  // src/middlewares/tenant-middleware.ts
6317
- import { logger as logger2 } from "@mesob/common";
6468
+ import { logger as logger3 } from "@mesob/common";
6318
6469
  import { createMiddleware as createMiddleware2 } from "hono/factory";
6319
- import { HTTPException as HTTPException3 } from "hono/http-exception";
6470
+ import { HTTPException as HTTPException4 } from "hono/http-exception";
6320
6471
  function resolveHost(hostHeader, forwardedHost) {
6321
6472
  const hostHeaderStr = hostHeader || "";
6322
6473
  const forwardedHostStr = forwardedHost || "";
@@ -6356,7 +6507,7 @@ async function resolveTenant(database, config, host) {
6356
6507
  }
6357
6508
  return { tenantId, tenant };
6358
6509
  } catch (err) {
6359
- logger2.error("Tenant resolution error:", err);
6510
+ logger3.error("Tenant resolution error:", err);
6360
6511
  throw err;
6361
6512
  }
6362
6513
  }
@@ -6380,7 +6531,7 @@ var createTenantMiddleware = (database, config) => {
6380
6531
  );
6381
6532
  c.set("host", host);
6382
6533
  if (!host) {
6383
- throw new HTTPException3(400, { message: "Missing Host header" });
6534
+ throw new HTTPException4(400, { message: "Missing Host header" });
6384
6535
  }
6385
6536
  let tenantId = null;
6386
6537
  let tenant = null;
@@ -6389,13 +6540,13 @@ var createTenantMiddleware = (database, config) => {
6389
6540
  tenantId = result.tenantId;
6390
6541
  tenant = result.tenant;
6391
6542
  } catch {
6392
- throw new HTTPException3(500, { message: "Tenant resolution failed" });
6543
+ throw new HTTPException4(500, { message: "Tenant resolution failed" });
6393
6544
  }
6394
6545
  c.set("tenantId", tenantId);
6395
6546
  c.set("tenant", tenant);
6396
6547
  const error = validateTenant(tenantId, tenant);
6397
6548
  if (error) {
6398
- throw new HTTPException3(404, { message: error });
6549
+ throw new HTTPException4(404, { message: error });
6399
6550
  }
6400
6551
  return await next();
6401
6552
  });
@@ -6483,10 +6634,10 @@ var createGetSession = (database, config) => {
6483
6634
  };
6484
6635
 
6485
6636
  // src/types/index.ts
6486
- import { logger as logger3 } from "@mesob/common";
6637
+ import { logger as logger4 } from "@mesob/common";
6487
6638
  var createDefaultSendVerificationOTP = (expiresIn) => {
6488
6639
  return (params) => {
6489
- logger3.log(
6640
+ logger4.log(
6490
6641
  `[Verification OTP] Code: ${params.code}, Hash: ${params.hash}, ExpiresIn: ${expiresIn}, Type: ${params.type}`
6491
6642
  );
6492
6643
  };
@@ -6595,6 +6746,10 @@ export {
6595
6746
  createDatabase,
6596
6747
  createMesobAuth,
6597
6748
  createSessionMiddleware,
6598
- createTenantMiddleware
6749
+ createTenantMiddleware,
6750
+ hasPermission,
6751
+ hasPermissionThrow,
6752
+ hasRole,
6753
+ hasRoleThrow
6599
6754
  };
6600
6755
  //# sourceMappingURL=index.js.map