@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-ULpI-i0z.d.ts → index-Bh3bDnP5.d.ts} +539 -496
- package/dist/{index-DCxsFKQ2.d.ts → index-BvdbhtRX.d.ts} +8 -12
- package/dist/index.d.ts +5 -4
- package/dist/index.js +423 -268
- package/dist/index.js.map +1 -1
- package/dist/lib/cleanup.d.ts +1 -1
- package/dist/lib/cleanup.js +121 -103
- package/dist/lib/cleanup.js.map +1 -1
- package/dist/lib/cookie.d.ts +2 -2
- package/dist/lib/has-role-permission.d.ts +18 -0
- package/dist/lib/has-role-permission.js +40 -0
- package/dist/lib/has-role-permission.js.map +1 -0
- package/dist/lib/normalize-user.d.ts +5 -9
- package/dist/lib/normalize-user.js +5 -10
- package/dist/lib/normalize-user.js.map +1 -1
- package/dist/lib/openapi-config.d.ts +2 -2
- package/dist/lib/phone-validation.d.ts +2 -2
- package/dist/lib/session.d.ts +2 -2
- package/dist/lib/tenant.d.ts +2 -2
- package/package.json +2 -2
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,
|
|
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
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
-
|
|
67
|
-
|
|
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: "
|
|
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("
|
|
76
|
-
check("
|
|
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("
|
|
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("
|
|
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("
|
|
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
|
|
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
|
-
|
|
192
|
-
|
|
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: "
|
|
172
|
+
name: "role_permissions_tenant_id_fkey"
|
|
201
173
|
}).onUpdate("cascade").onDelete("cascade"),
|
|
202
|
-
|
|
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
|
|
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
|
-
|
|
218
|
-
|
|
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.
|
|
222
|
-
foreignColumns: [
|
|
223
|
-
name: "
|
|
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.
|
|
227
|
-
foreignColumns: [
|
|
228
|
-
name: "
|
|
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: "
|
|
240
|
+
name: "roles_tenant_id_fkey"
|
|
234
241
|
}).onUpdate("cascade").onDelete("cascade"),
|
|
235
|
-
unique("
|
|
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
|
|
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
|
-
|
|
242
|
-
|
|
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
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
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: "
|
|
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("
|
|
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
|
|
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
|
-
|
|
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: "
|
|
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: "
|
|
300
|
+
name: "user_roles_user_id_fkey"
|
|
288
301
|
}).onUpdate("cascade").onDelete("cascade"),
|
|
289
|
-
|
|
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['
|
|
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
|
|
335
|
+
var verificationsInIamRelations = relations(verificationsInIam, ({ one }) => ({
|
|
318
336
|
tenantsInIam: one(tenantsInIam, {
|
|
319
|
-
fields: [
|
|
337
|
+
fields: [verificationsInIam.tenantId],
|
|
320
338
|
references: [tenantsInIam.id]
|
|
321
339
|
}),
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
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
|
|
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: [
|
|
362
|
+
fields: [usersInIam.tenantId],
|
|
342
363
|
references: [tenantsInIam.id]
|
|
343
364
|
}),
|
|
344
|
-
|
|
345
|
-
fields: [sessionsInIam.userId],
|
|
346
|
-
references: [usersInIam.id]
|
|
347
|
-
})
|
|
365
|
+
userRolesInIam: many(userRolesInIam)
|
|
348
366
|
}));
|
|
349
|
-
var
|
|
367
|
+
var sessionsInIamRelations = relations(sessionsInIam, ({ one }) => ({
|
|
350
368
|
tenantsInIam: one(tenantsInIam, {
|
|
351
|
-
fields: [
|
|
369
|
+
fields: [sessionsInIam.tenantId],
|
|
352
370
|
references: [tenantsInIam.id]
|
|
353
371
|
}),
|
|
354
372
|
usersInIam: one(usersInIam, {
|
|
355
|
-
fields: [
|
|
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
|
|
387
|
+
var rolePermissionsInIamRelations = relations(rolePermissionsInIam, ({ one }) => ({
|
|
370
388
|
tenantsInIam: one(tenantsInIam, {
|
|
371
|
-
fields: [
|
|
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
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
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: [
|
|
414
|
+
fields: [accountsInIam.tenantId],
|
|
401
415
|
references: [tenantsInIam.id]
|
|
402
416
|
}),
|
|
403
417
|
usersInIam: one(usersInIam, {
|
|
404
|
-
fields: [
|
|
418
|
+
fields: [accountsInIam.userId],
|
|
405
419
|
references: [usersInIam.id]
|
|
406
420
|
})
|
|
407
421
|
}));
|
|
408
|
-
var
|
|
422
|
+
var userRolesInIamRelations = relations(userRolesInIam, ({ one }) => ({
|
|
409
423
|
tenantsInIam: one(tenantsInIam, {
|
|
410
|
-
fields: [
|
|
424
|
+
fields: [userRolesInIam.tenantId],
|
|
411
425
|
references: [tenantsInIam.id]
|
|
412
426
|
}),
|
|
413
427
|
usersInIam: one(usersInIam, {
|
|
414
|
-
fields: [
|
|
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
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
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
|
|
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(
|
|
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
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
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
|
-
).
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
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
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
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(
|
|
4947
|
-
database.select({ count: sql20`count(*)` }).from(tenantsInIam).where(
|
|
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
|
|
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(
|
|
5424
|
+
userRoles: z10.array(userRoleSchema)
|
|
5307
5425
|
});
|
|
5308
5426
|
var userRoleResponseSchema = z10.object({
|
|
5309
|
-
userRole:
|
|
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
|
|
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(
|
|
5685
|
+
conditions.push(ilike3(usersInIam.email, `%${query.email}%`));
|
|
5561
5686
|
}
|
|
5562
5687
|
if (query.phone) {
|
|
5563
|
-
conditions.push(
|
|
5688
|
+
conditions.push(ilike3(usersInIam.phone, `%${query.phone}%`));
|
|
5564
5689
|
}
|
|
5565
5690
|
if (query.handle) {
|
|
5566
|
-
conditions.push(
|
|
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
|
-
|
|
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
|
|
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 =
|
|
5608
|
-
|
|
5609
|
-
|
|
5610
|
-
|
|
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
|
|
6468
|
+
import { logger as logger3 } from "@mesob/common";
|
|
6318
6469
|
import { createMiddleware as createMiddleware2 } from "hono/factory";
|
|
6319
|
-
import { HTTPException as
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
6637
|
+
import { logger as logger4 } from "@mesob/common";
|
|
6487
6638
|
var createDefaultSendVerificationOTP = (expiresIn) => {
|
|
6488
6639
|
return (params) => {
|
|
6489
|
-
|
|
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
|