@spfn/auth 0.1.0-alpha.0 → 0.1.0-alpha.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.
Files changed (67) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +70 -12
  3. package/dist/api-BcQM4WKb.d.ts +45 -0
  4. package/dist/client.d.ts +2 -0
  5. package/dist/client.js +1 -0
  6. package/dist/client.js.map +1 -0
  7. package/dist/index.d.ts +57 -0
  8. package/dist/index.js +8966 -0
  9. package/dist/index.js.map +1 -0
  10. package/dist/lib/contracts/auth.d.ts +262 -0
  11. package/dist/lib/contracts/auth.js +2923 -0
  12. package/dist/lib/contracts/auth.js.map +1 -0
  13. package/dist/lib/contracts/index.d.ts +3 -0
  14. package/dist/lib/contracts/index.js +3162 -0
  15. package/dist/lib/contracts/index.js.map +1 -0
  16. package/dist/lib/contracts/invitation.d.ts +243 -0
  17. package/dist/lib/contracts/invitation.js +2883 -0
  18. package/dist/lib/contracts/invitation.js.map +1 -0
  19. package/dist/plugin.d.ts +12 -0
  20. package/dist/plugin.js +8949 -0
  21. package/dist/plugin.js.map +1 -0
  22. package/dist/server/entities/index.d.ts +10 -0
  23. package/dist/server/entities/index.js +399 -0
  24. package/dist/server/entities/index.js.map +1 -0
  25. package/dist/server/entities/invitations.d.ts +241 -0
  26. package/dist/server/entities/invitations.js +181 -0
  27. package/dist/server/entities/invitations.js.map +1 -0
  28. package/dist/server/entities/permissions.d.ts +196 -0
  29. package/dist/server/entities/permissions.js +44 -0
  30. package/dist/server/entities/permissions.js.map +1 -0
  31. package/dist/server/entities/role-permissions.d.ts +107 -0
  32. package/dist/server/entities/role-permissions.js +112 -0
  33. package/dist/server/entities/role-permissions.js.map +1 -0
  34. package/dist/server/entities/roles.d.ts +196 -0
  35. package/dist/server/entities/roles.js +45 -0
  36. package/dist/server/entities/roles.js.map +1 -0
  37. package/dist/server/entities/user-permissions.d.ts +163 -0
  38. package/dist/server/entities/user-permissions.js +191 -0
  39. package/dist/server/entities/user-permissions.js.map +1 -0
  40. package/dist/server/entities/user-public-keys.d.ts +227 -0
  41. package/dist/server/entities/user-public-keys.js +153 -0
  42. package/dist/server/entities/user-public-keys.js.map +1 -0
  43. package/dist/server/entities/user-social-accounts.d.ts +189 -0
  44. package/dist/server/entities/user-social-accounts.js +146 -0
  45. package/dist/server/entities/user-social-accounts.js.map +1 -0
  46. package/dist/server/entities/users.d.ts +235 -0
  47. package/dist/server/entities/users.js +113 -0
  48. package/dist/server/entities/users.js.map +1 -0
  49. package/dist/server/entities/verification-codes.d.ts +191 -0
  50. package/dist/server/entities/verification-codes.js +44 -0
  51. package/dist/server/entities/verification-codes.js.map +1 -0
  52. package/dist/server/routes/auth/index.d.ts +10 -0
  53. package/dist/server/routes/auth/index.js +4475 -0
  54. package/dist/server/routes/auth/index.js.map +1 -0
  55. package/dist/server/routes/index.d.ts +6 -0
  56. package/dist/server/routes/index.js +6352 -0
  57. package/dist/server/routes/index.js.map +1 -0
  58. package/dist/server/routes/invitations/index.d.ts +10 -0
  59. package/dist/server/routes/invitations/index.js +4209 -0
  60. package/dist/server/routes/invitations/index.js.map +1 -0
  61. package/dist/server.d.ts +1243 -0
  62. package/dist/server.js +2281 -0
  63. package/dist/server.js.map +1 -0
  64. package/migrations/0000_tired_gambit.sql +165 -0
  65. package/migrations/meta/0000_snapshot.json +1395 -0
  66. package/migrations/meta/_journal.json +13 -0
  67. package/package.json +32 -24
package/dist/server.js ADDED
@@ -0,0 +1,2281 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __esm = (fn, res) => function __init() {
4
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
5
+ };
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+
11
+ // src/server/entities/roles.ts
12
+ import { text, boolean, integer, index } from "drizzle-orm/pg-core";
13
+ import { id, timestamps, createFunctionSchema } from "@spfn/core/db";
14
+ var schema, roles;
15
+ var init_roles = __esm({
16
+ "src/server/entities/roles.ts"() {
17
+ "use strict";
18
+ schema = createFunctionSchema("@spfn/auth");
19
+ roles = schema.table(
20
+ "roles",
21
+ {
22
+ // Primary key
23
+ id: id(),
24
+ // Role identifier (used in code, e.g., 'admin', 'editor')
25
+ // Must be unique, lowercase, kebab-case recommended
26
+ name: text("name").notNull().unique(),
27
+ // Display name for UI (e.g., 'Administrator', 'Content Editor')
28
+ displayName: text("display_name").notNull(),
29
+ // Role description
30
+ description: text("description"),
31
+ // Built-in role flag
32
+ // true: Core package roles (user, admin, superadmin) - cannot be deleted
33
+ // false: Custom or preset roles - can be deleted
34
+ isBuiltin: boolean("is_builtin").notNull().default(false),
35
+ // System role flag
36
+ // true: Defined in code (builtin or preset) - deletion restricted
37
+ // false: Runtime created custom role - fully manageable
38
+ isSystem: boolean("is_system").notNull().default(false),
39
+ // Active status
40
+ // false: Deactivated role (users cannot be assigned)
41
+ isActive: boolean("is_active").notNull().default(true),
42
+ // Priority level (higher = more privileged)
43
+ // superadmin: 100, admin: 80, user: 10
44
+ // Used for role hierarchy and conflict resolution
45
+ priority: integer("priority").notNull().default(10),
46
+ ...timestamps()
47
+ },
48
+ (table) => [
49
+ index("roles_name_idx").on(table.name),
50
+ index("roles_is_system_idx").on(table.isSystem),
51
+ index("roles_is_active_idx").on(table.isActive),
52
+ index("roles_is_builtin_idx").on(table.isBuiltin),
53
+ index("roles_priority_idx").on(table.priority)
54
+ ]
55
+ );
56
+ }
57
+ });
58
+
59
+ // src/server/entities/users.ts
60
+ import { text as text2, timestamp, check, boolean as boolean2, bigint, index as index2 } from "drizzle-orm/pg-core";
61
+ import { id as id2, timestamps as timestamps2, createFunctionSchema as createFunctionSchema2 } from "@spfn/core/db";
62
+ import { sql } from "drizzle-orm";
63
+ var schema2, users;
64
+ var init_users = __esm({
65
+ "src/server/entities/users.ts"() {
66
+ "use strict";
67
+ init_roles();
68
+ schema2 = createFunctionSchema2("@spfn/auth");
69
+ users = schema2.table(
70
+ "users",
71
+ {
72
+ // Identity
73
+ id: id2(),
74
+ // Email address (unique identifier)
75
+ // Used for: login, password reset, notifications
76
+ email: text2("email").unique(),
77
+ // Phone number in E.164 international format
78
+ // Format: +[country code][number] (e.g., +821012345678)
79
+ // Used for: SMS login, 2FA, notifications
80
+ phone: text2("phone").unique(),
81
+ // Authentication
82
+ // Bcrypt password hash ($2b$10$[salt][hash], 60 chars)
83
+ // Nullable to support OAuth-only accounts
84
+ passwordHash: text2("password_hash"),
85
+ // Force password change on next login
86
+ // Use cases: initial setup, security breach, policy violation
87
+ passwordChangeRequired: boolean2("password_change_required").notNull().default(false),
88
+ // Authorization (Role-Based Access Control)
89
+ // Foreign key to roles table
90
+ // References built-in roles: user (default), admin, superadmin
91
+ // Can also reference custom roles created at runtime
92
+ roleId: bigint("role_id", { mode: "number" }).references(() => roles.id).notNull(),
93
+ // Account status
94
+ // - active: Normal operation (default)
95
+ // - inactive: Deactivated (user request, dormant)
96
+ // - suspended: Locked (security incident, ToS violation)
97
+ status: text2(
98
+ "status",
99
+ {
100
+ enum: ["active", "inactive", "suspended"]
101
+ }
102
+ ).notNull().default("active"),
103
+ // Verification timestamps
104
+ // null = unverified, timestamp = verified at this time
105
+ // Email verification (via verification code or magic link)
106
+ emailVerifiedAt: timestamp("email_verified_at", { withTimezone: true }),
107
+ // Phone verification (via SMS OTP)
108
+ phoneVerifiedAt: timestamp("phone_verified_at", { withTimezone: true }),
109
+ // Metadata
110
+ // Last successful login timestamp
111
+ // Used for: security auditing, dormant account detection
112
+ lastLoginAt: timestamp("last_login_at", { withTimezone: true }),
113
+ ...timestamps2()
114
+ },
115
+ (table) => [
116
+ // Database constraints
117
+ // Ensure at least one identifier exists (email OR phone)
118
+ check(
119
+ "email_or_phone_check",
120
+ sql`${table.email} IS NOT NULL OR ${table.phone} IS NOT NULL`
121
+ ),
122
+ // Indexes for query optimization
123
+ index2("users_email_idx").on(table.email),
124
+ index2("users_phone_idx").on(table.phone),
125
+ index2("users_status_idx").on(table.status),
126
+ index2("users_role_id_idx").on(table.roleId)
127
+ ]
128
+ );
129
+ }
130
+ });
131
+
132
+ // src/server/entities/user-social-accounts.ts
133
+ import { text as text3, timestamp as timestamp2, uniqueIndex } from "drizzle-orm/pg-core";
134
+ import { id as id3, timestamps as timestamps3, foreignKey, createFunctionSchema as createFunctionSchema3 } from "@spfn/core/db";
135
+ var schema3, userSocialAccounts;
136
+ var init_user_social_accounts = __esm({
137
+ "src/server/entities/user-social-accounts.ts"() {
138
+ "use strict";
139
+ init_users();
140
+ schema3 = createFunctionSchema3("@spfn/auth");
141
+ userSocialAccounts = schema3.table(
142
+ "user_social_accounts",
143
+ {
144
+ id: id3(),
145
+ // Foreign key to users
146
+ userId: foreignKey("user", () => users.id),
147
+ // Provider info
148
+ provider: text3(
149
+ "provider",
150
+ {
151
+ enum: ["google", "github", "kakao", "naver"]
152
+ }
153
+ ).notNull(),
154
+ providerUserId: text3("provider_user_id").notNull(),
155
+ providerEmail: text3("provider_email"),
156
+ // OAuth tokens (encrypted in production)
157
+ accessToken: text3("access_token"),
158
+ refreshToken: text3("refresh_token"),
159
+ tokenExpiresAt: timestamp2("token_expires_at", { withTimezone: true }),
160
+ ...timestamps3()
161
+ },
162
+ (table) => [
163
+ // Unique constraint: one provider account per provider
164
+ uniqueIndex("provider_user_unique_idx").on(table.provider, table.providerUserId)
165
+ ]
166
+ );
167
+ }
168
+ });
169
+
170
+ // src/server/entities/user-public-keys.ts
171
+ import { text as text4, timestamp as timestamp3, boolean as boolean3, index as index3 } from "drizzle-orm/pg-core";
172
+ import { id as id4, foreignKey as foreignKey2, createFunctionSchema as createFunctionSchema4 } from "@spfn/core/db";
173
+ var schema4, userPublicKeys;
174
+ var init_user_public_keys = __esm({
175
+ "src/server/entities/user-public-keys.ts"() {
176
+ "use strict";
177
+ init_users();
178
+ schema4 = createFunctionSchema4("@spfn/auth");
179
+ userPublicKeys = schema4.table(
180
+ "user_public_keys",
181
+ {
182
+ id: id4(),
183
+ // User reference
184
+ userId: foreignKey2("user", () => users.id),
185
+ // Key identification (client-generated UUID)
186
+ keyId: text4("key_id").notNull().unique(),
187
+ // Public key in Base64-encoded DER format (SPKI)
188
+ publicKey: text4("public_key").notNull(),
189
+ // Algorithm used (ES256 recommended, RS256 fallback)
190
+ algorithm: text4("algorithm", {
191
+ enum: ["ES256", "RS256"]
192
+ }).notNull().default("ES256"),
193
+ // Key fingerprint (SHA-256 hash for quick identification)
194
+ fingerprint: text4("fingerprint").notNull(),
195
+ // Key status
196
+ isActive: boolean3("is_active").notNull().default(true),
197
+ // Timestamps
198
+ createdAt: timestamp3("created_at", { mode: "date", withTimezone: true }).notNull().defaultNow(),
199
+ lastUsedAt: timestamp3("last_used_at", { mode: "date", withTimezone: true }),
200
+ expiresAt: timestamp3("expires_at", { mode: "date", withTimezone: true }),
201
+ // Revocation
202
+ revokedAt: timestamp3("revoked_at", { mode: "date", withTimezone: true }),
203
+ revokedReason: text4("revoked_reason")
204
+ },
205
+ (table) => [
206
+ index3("user_public_keys_user_id_idx").on(table.userId),
207
+ index3("user_public_keys_key_id_idx").on(table.keyId),
208
+ index3("user_public_keys_active_idx").on(table.isActive),
209
+ index3("user_public_keys_fingerprint_idx").on(table.fingerprint)
210
+ ]
211
+ );
212
+ }
213
+ });
214
+
215
+ // src/server/entities/verification-codes.ts
216
+ import { text as text5, timestamp as timestamp4, index as index4 } from "drizzle-orm/pg-core";
217
+ import { id as id5, timestamps as timestamps4, createFunctionSchema as createFunctionSchema5 } from "@spfn/core/db";
218
+ var schema5, verificationCodes;
219
+ var init_verification_codes = __esm({
220
+ "src/server/entities/verification-codes.ts"() {
221
+ "use strict";
222
+ schema5 = createFunctionSchema5("@spfn/auth");
223
+ verificationCodes = schema5.table(
224
+ "verification_codes",
225
+ {
226
+ id: id5(),
227
+ // Target (email or phone)
228
+ target: text5("target").notNull(),
229
+ // Email address or E.164 phone number
230
+ targetType: text5(
231
+ "target_type",
232
+ {
233
+ enum: ["email", "phone"]
234
+ }
235
+ ).notNull(),
236
+ // Code
237
+ code: text5("code").notNull(),
238
+ // 6-digit code by default (configurable)
239
+ // Purpose
240
+ purpose: text5(
241
+ "purpose",
242
+ {
243
+ enum: ["registration", "login", "password_reset", "email_change", "phone_change"]
244
+ }
245
+ ).notNull(),
246
+ // Expiry
247
+ expiresAt: timestamp4("expires_at", { withTimezone: true }).notNull(),
248
+ // Usage tracking
249
+ usedAt: timestamp4("used_at", { withTimezone: true }),
250
+ attempts: text5("attempts").notNull().default("0"),
251
+ // Track failed verification attempts
252
+ ...timestamps4()
253
+ },
254
+ (table) => [
255
+ // Index for quick lookup by target and purpose
256
+ index4("target_purpose_idx").on(table.target, table.purpose, table.expiresAt)
257
+ ]
258
+ );
259
+ }
260
+ });
261
+
262
+ // src/server/entities/invitations.ts
263
+ import { text as text6, timestamp as timestamp5, bigint as bigint2, index as index5, jsonb } from "drizzle-orm/pg-core";
264
+ import { id as id6, timestamps as timestamps5, createFunctionSchema as createFunctionSchema6 } from "@spfn/core/db";
265
+ var schema6, invitations;
266
+ var init_invitations = __esm({
267
+ "src/server/entities/invitations.ts"() {
268
+ "use strict";
269
+ init_roles();
270
+ init_users();
271
+ schema6 = createFunctionSchema6("@spfn/auth");
272
+ invitations = schema6.table(
273
+ "user_invitations",
274
+ {
275
+ // Primary key
276
+ id: id6(),
277
+ // Target email address for the invitation
278
+ // Will become the user's email upon acceptance
279
+ email: text6("email").notNull(),
280
+ // Unique invitation token (UUID v4)
281
+ // Used in invitation URL: /auth/invite/{token}
282
+ // Single-use token that expires after acceptance
283
+ token: text6("token").notNull().unique(),
284
+ // Role to be assigned when invitation is accepted
285
+ // Foreign key to roles table
286
+ roleId: bigint2("role_id", { mode: "number" }).references(() => roles.id).notNull(),
287
+ // User who created this invitation
288
+ // Foreign key to users table
289
+ // Used for: audit trail, permission checks
290
+ invitedBy: bigint2("invited_by", { mode: "number" }).references(() => users.id).notNull(),
291
+ // Invitation status
292
+ // - pending: Invitation sent, awaiting acceptance
293
+ // - accepted: User accepted and account created
294
+ // - expired: Invitation expired (automatic)
295
+ // - cancelled: Invitation cancelled by admin
296
+ status: text6(
297
+ "status",
298
+ {
299
+ enum: ["pending", "accepted", "expired", "cancelled"]
300
+ }
301
+ ).notNull().default("pending"),
302
+ // Expiration timestamp (default: 7 days from creation)
303
+ // Invitation cannot be accepted after this time
304
+ // Background job should update status to 'expired'
305
+ expiresAt: timestamp5("expires_at", { withTimezone: true }).notNull(),
306
+ // Timestamp when invitation was accepted
307
+ // null = not yet accepted
308
+ // Used for: audit trail, analytics
309
+ acceptedAt: timestamp5("accepted_at", { withTimezone: true }),
310
+ // Timestamp when invitation was cancelled
311
+ // null = not cancelled
312
+ // Used for: audit trail
313
+ cancelledAt: timestamp5("cancelled_at", { withTimezone: true }),
314
+ // Additional metadata (JSONB)
315
+ // Use cases:
316
+ // - Custom welcome message
317
+ // - Onboarding instructions
318
+ // - Team/department assignment
319
+ // - Custom fields for app-specific data
320
+ // Example: { message: "Welcome!", department: "Engineering" }
321
+ metadata: jsonb("metadata"),
322
+ ...timestamps5()
323
+ },
324
+ (table) => [
325
+ // Indexes for query optimization
326
+ index5("invitations_token_idx").on(table.token),
327
+ index5("invitations_email_idx").on(table.email),
328
+ index5("invitations_status_idx").on(table.status),
329
+ index5("invitations_invited_by_idx").on(table.invitedBy),
330
+ index5("invitations_expires_at_idx").on(table.expiresAt),
331
+ // For cleanup jobs
332
+ index5("invitations_role_id_idx").on(table.roleId)
333
+ ]
334
+ );
335
+ }
336
+ });
337
+
338
+ // src/server/entities/permissions.ts
339
+ import { text as text7, boolean as boolean4, index as index6 } from "drizzle-orm/pg-core";
340
+ import { id as id7, timestamps as timestamps6, createFunctionSchema as createFunctionSchema7 } from "@spfn/core/db";
341
+ var schema7, permissions;
342
+ var init_permissions = __esm({
343
+ "src/server/entities/permissions.ts"() {
344
+ "use strict";
345
+ schema7 = createFunctionSchema7("@spfn/auth");
346
+ permissions = schema7.table(
347
+ "permissions",
348
+ {
349
+ // Primary key
350
+ id: id7(),
351
+ // Permission identifier (e.g., 'user:delete', 'post:publish')
352
+ // Format: resource:action or namespace:resource:action
353
+ // Must be unique
354
+ name: text7("name").notNull().unique(),
355
+ // Display name for UI
356
+ displayName: text7("display_name").notNull(),
357
+ // Permission description
358
+ description: text7("description"),
359
+ // Category for grouping (e.g., 'user', 'post', 'admin', 'system')
360
+ category: text7("category"),
361
+ // Built-in permission flag
362
+ // true: Core package permissions - cannot be deleted
363
+ // false: Custom or preset permissions
364
+ isBuiltin: boolean4("is_builtin").notNull().default(false),
365
+ // System permission flag
366
+ // true: Defined in code (builtin or preset)
367
+ // false: Runtime created custom permission
368
+ isSystem: boolean4("is_system").notNull().default(false),
369
+ // Active status
370
+ // false: Deactivated permission (not enforced)
371
+ isActive: boolean4("is_active").notNull().default(true),
372
+ ...timestamps6()
373
+ },
374
+ (table) => [
375
+ index6("permissions_name_idx").on(table.name),
376
+ index6("permissions_category_idx").on(table.category),
377
+ index6("permissions_is_system_idx").on(table.isSystem),
378
+ index6("permissions_is_active_idx").on(table.isActive),
379
+ index6("permissions_is_builtin_idx").on(table.isBuiltin)
380
+ ]
381
+ );
382
+ }
383
+ });
384
+
385
+ // src/server/entities/role-permissions.ts
386
+ import { bigint as bigint3, index as index7, unique } from "drizzle-orm/pg-core";
387
+ import { id as id8, timestamps as timestamps7, createFunctionSchema as createFunctionSchema8 } from "@spfn/core/db";
388
+ var schema8, rolePermissions;
389
+ var init_role_permissions = __esm({
390
+ "src/server/entities/role-permissions.ts"() {
391
+ "use strict";
392
+ init_roles();
393
+ init_permissions();
394
+ schema8 = createFunctionSchema8("@spfn/auth");
395
+ rolePermissions = schema8.table(
396
+ "role_permissions",
397
+ {
398
+ // Primary key
399
+ id: id8(),
400
+ // Foreign key to roles table
401
+ roleId: bigint3("role_id", { mode: "number" }).notNull().references(() => roles.id, { onDelete: "cascade" }),
402
+ // Foreign key to permissions table
403
+ permissionId: bigint3("permission_id", { mode: "number" }).notNull().references(() => permissions.id, { onDelete: "cascade" }),
404
+ ...timestamps7()
405
+ },
406
+ (table) => [
407
+ // Indexes for query performance
408
+ index7("role_permissions_role_id_idx").on(table.roleId),
409
+ index7("role_permissions_permission_id_idx").on(table.permissionId),
410
+ // Unique constraint: one role-permission pair only
411
+ unique("role_permissions_unique").on(table.roleId, table.permissionId)
412
+ ]
413
+ );
414
+ }
415
+ });
416
+
417
+ // src/server/entities/user-permissions.ts
418
+ import { bigint as bigint4, boolean as boolean5, text as text8, timestamp as timestamp6, index as index8, unique as unique2 } from "drizzle-orm/pg-core";
419
+ import { id as id9, timestamps as timestamps8, createFunctionSchema as createFunctionSchema9 } from "@spfn/core/db";
420
+ var schema9, userPermissions;
421
+ var init_user_permissions = __esm({
422
+ "src/server/entities/user-permissions.ts"() {
423
+ "use strict";
424
+ init_users();
425
+ init_permissions();
426
+ schema9 = createFunctionSchema9("@spfn/auth");
427
+ userPermissions = schema9.table(
428
+ "user_permissions",
429
+ {
430
+ // Primary key
431
+ id: id9(),
432
+ // Foreign key to users table
433
+ userId: bigint4("user_id", { mode: "number" }).notNull().references(() => users.id, { onDelete: "cascade" }),
434
+ // Foreign key to permissions table
435
+ permissionId: bigint4("permission_id", { mode: "number" }).notNull().references(() => permissions.id, { onDelete: "cascade" }),
436
+ // Grant or revoke
437
+ // true: Grant this permission (even if role doesn't have it)
438
+ // false: Revoke this permission (even if role has it)
439
+ granted: boolean5("granted").notNull().default(true),
440
+ // Reason for grant/revocation (audit trail)
441
+ reason: text8("reason"),
442
+ // Expiration timestamp (optional)
443
+ // null: Permanent override
444
+ // timestamp: Permission expires at this time
445
+ expiresAt: timestamp6("expires_at", { withTimezone: true }),
446
+ ...timestamps8()
447
+ },
448
+ (table) => [
449
+ // Indexes for query performance
450
+ index8("user_permissions_user_id_idx").on(table.userId),
451
+ index8("user_permissions_permission_id_idx").on(table.permissionId),
452
+ index8("user_permissions_expires_at_idx").on(table.expiresAt),
453
+ // Unique constraint: one user-permission pair only
454
+ unique2("user_permissions_unique").on(table.userId, table.permissionId)
455
+ ]
456
+ );
457
+ }
458
+ });
459
+
460
+ // src/server/entities/index.ts
461
+ var entities_exports = {};
462
+ __export(entities_exports, {
463
+ invitations: () => invitations,
464
+ permissions: () => permissions,
465
+ rolePermissions: () => rolePermissions,
466
+ roles: () => roles,
467
+ userPermissions: () => userPermissions,
468
+ userPublicKeys: () => userPublicKeys,
469
+ userSocialAccounts: () => userSocialAccounts,
470
+ users: () => users,
471
+ verificationCodes: () => verificationCodes
472
+ });
473
+ var init_entities = __esm({
474
+ "src/server/entities/index.ts"() {
475
+ "use strict";
476
+ init_users();
477
+ init_user_social_accounts();
478
+ init_user_public_keys();
479
+ init_verification_codes();
480
+ init_invitations();
481
+ init_roles();
482
+ init_permissions();
483
+ init_role_permissions();
484
+ init_user_permissions();
485
+ }
486
+ });
487
+
488
+ // src/server/services/role.service.ts
489
+ var role_service_exports = {};
490
+ __export(role_service_exports, {
491
+ addPermissionToRole: () => addPermissionToRole,
492
+ createRole: () => createRole,
493
+ deleteRole: () => deleteRole,
494
+ getAllRoles: () => getAllRoles,
495
+ getRoleByName: () => getRoleByName,
496
+ getRolePermissions: () => getRolePermissions,
497
+ removePermissionFromRole: () => removePermissionFromRole,
498
+ setRolePermissions: () => setRolePermissions,
499
+ updateRole: () => updateRole
500
+ });
501
+ import { getDatabase as getDatabase3 } from "@spfn/core/db";
502
+ import { eq as eq3, and as and3 } from "drizzle-orm";
503
+ async function createRole(data) {
504
+ const db = getDatabase3();
505
+ if (!db) {
506
+ throw new Error("[Auth] Database not initialized");
507
+ }
508
+ const existing = await db.select().from(roles).where(eq3(roles.name, data.name)).limit(1);
509
+ if (existing.length > 0) {
510
+ throw new Error(`Role with name '${data.name}' already exists`);
511
+ }
512
+ const [newRole] = await db.insert(roles).values({
513
+ name: data.name,
514
+ displayName: data.displayName,
515
+ description: data.description,
516
+ priority: data.priority ?? 10,
517
+ isSystem: false,
518
+ // Custom roles are never system roles
519
+ isBuiltin: false
520
+ }).returning();
521
+ if (data.permissionIds && data.permissionIds.length > 0) {
522
+ const mappings = data.permissionIds.map((permId) => ({
523
+ roleId: newRole.id,
524
+ permissionId: Number(permId)
525
+ }));
526
+ await db.insert(rolePermissions).values(mappings);
527
+ }
528
+ console.log(`[Auth] \u2705 Created custom role: ${data.name}`);
529
+ return newRole;
530
+ }
531
+ async function updateRole(roleId, data) {
532
+ const db = getDatabase3();
533
+ if (!db) {
534
+ throw new Error("[Auth] Database not initialized");
535
+ }
536
+ const roleIdNum = Number(roleId);
537
+ const [role] = await db.select().from(roles).where(eq3(roles.id, roleIdNum)).limit(1);
538
+ if (!role) {
539
+ throw new Error("Role not found");
540
+ }
541
+ if (role.isBuiltin && data.priority !== void 0) {
542
+ throw new Error("Cannot modify priority of built-in roles");
543
+ }
544
+ const [updated] = await db.update(roles).set(data).where(eq3(roles.id, roleIdNum)).returning();
545
+ return updated;
546
+ }
547
+ async function deleteRole(roleId) {
548
+ const db = getDatabase3();
549
+ if (!db) {
550
+ throw new Error("[Auth] Database not initialized");
551
+ }
552
+ const roleIdNum = Number(roleId);
553
+ const [role] = await db.select().from(roles).where(eq3(roles.id, roleIdNum)).limit(1);
554
+ if (!role) {
555
+ throw new Error("Role not found");
556
+ }
557
+ if (role.isBuiltin) {
558
+ throw new Error(`Cannot delete built-in role: ${role.name}`);
559
+ }
560
+ if (role.isSystem) {
561
+ throw new Error(`Cannot delete system role: ${role.name}. Deactivate it instead.`);
562
+ }
563
+ await db.delete(roles).where(eq3(roles.id, roleIdNum));
564
+ console.log(`[Auth] \u{1F5D1}\uFE0F Deleted role: ${role.name}`);
565
+ }
566
+ async function addPermissionToRole(roleId, permissionId) {
567
+ const db = getDatabase3();
568
+ if (!db) {
569
+ throw new Error("[Auth] Database not initialized");
570
+ }
571
+ const roleIdNum = Number(roleId);
572
+ const permissionIdNum = Number(permissionId);
573
+ const existing = await db.select().from(rolePermissions).where(
574
+ and3(
575
+ eq3(rolePermissions.roleId, roleIdNum),
576
+ eq3(rolePermissions.permissionId, permissionIdNum)
577
+ )
578
+ ).limit(1);
579
+ if (existing.length > 0) {
580
+ return;
581
+ }
582
+ await db.insert(rolePermissions).values({
583
+ roleId: roleIdNum,
584
+ permissionId: permissionIdNum
585
+ });
586
+ }
587
+ async function removePermissionFromRole(roleId, permissionId) {
588
+ const db = getDatabase3();
589
+ if (!db) {
590
+ throw new Error("[Auth] Database not initialized");
591
+ }
592
+ const roleIdNum = Number(roleId);
593
+ const permissionIdNum = Number(permissionId);
594
+ await db.delete(rolePermissions).where(
595
+ and3(
596
+ eq3(rolePermissions.roleId, roleIdNum),
597
+ eq3(rolePermissions.permissionId, permissionIdNum)
598
+ )
599
+ );
600
+ }
601
+ async function setRolePermissions(roleId, permissionIds) {
602
+ const db = getDatabase3();
603
+ if (!db) {
604
+ throw new Error("[Auth] Database not initialized");
605
+ }
606
+ const roleIdNum = Number(roleId);
607
+ await db.delete(rolePermissions).where(eq3(rolePermissions.roleId, roleIdNum));
608
+ if (permissionIds.length > 0) {
609
+ const mappings = permissionIds.map((permId) => ({
610
+ roleId: roleIdNum,
611
+ permissionId: Number(permId)
612
+ }));
613
+ await db.insert(rolePermissions).values(mappings);
614
+ }
615
+ }
616
+ async function getAllRoles(includeInactive = false) {
617
+ const db = getDatabase3();
618
+ if (!db) {
619
+ throw new Error("[Auth] Database not initialized");
620
+ }
621
+ const query = db.select().from(roles);
622
+ if (!includeInactive) {
623
+ return query.where(eq3(roles.isActive, true));
624
+ }
625
+ return query;
626
+ }
627
+ async function getRoleByName(name) {
628
+ const db = getDatabase3();
629
+ if (!db) {
630
+ throw new Error("[Auth] Database not initialized");
631
+ }
632
+ const [role] = await db.select().from(roles).where(eq3(roles.name, name)).limit(1);
633
+ return role || null;
634
+ }
635
+ async function getRolePermissions(roleId) {
636
+ const db = getDatabase3();
637
+ if (!db) {
638
+ throw new Error("[Auth] Database not initialized");
639
+ }
640
+ const roleIdNum = Number(roleId);
641
+ const perms = await db.select({ name: permissions.name }).from(rolePermissions).innerJoin(permissions, eq3(rolePermissions.permissionId, permissions.id)).where(eq3(rolePermissions.roleId, roleIdNum));
642
+ return perms.map((p) => p.name);
643
+ }
644
+ var init_role_service = __esm({
645
+ "src/server/services/role.service.ts"() {
646
+ "use strict";
647
+ init_entities();
648
+ }
649
+ });
650
+
651
+ // src/server/rbac/builtin.ts
652
+ var BUILTIN_ROLES = {
653
+ SUPERADMIN: {
654
+ name: "superadmin",
655
+ displayName: "Super Administrator",
656
+ description: "Full system access and RBAC management",
657
+ priority: 100,
658
+ isSystem: true,
659
+ isBuiltin: true
660
+ },
661
+ ADMIN: {
662
+ name: "admin",
663
+ displayName: "Administrator",
664
+ description: "User management and organization administration",
665
+ priority: 80,
666
+ isSystem: true,
667
+ isBuiltin: true
668
+ },
669
+ USER: {
670
+ name: "user",
671
+ displayName: "User",
672
+ description: "Default user role with basic permissions",
673
+ priority: 10,
674
+ isSystem: true,
675
+ isBuiltin: true
676
+ }
677
+ };
678
+ var BUILTIN_PERMISSIONS = {
679
+ // Self-service auth management
680
+ AUTH_SELF_MANAGE: {
681
+ name: "auth:self:manage",
682
+ displayName: "Manage Own Auth",
683
+ description: "Change own password, rotate keys, manage own sessions",
684
+ category: "auth",
685
+ isSystem: true,
686
+ isBuiltin: true
687
+ },
688
+ // User management (admin functions)
689
+ USER_READ: {
690
+ name: "user:read",
691
+ displayName: "Read Users",
692
+ description: "View user information and list users",
693
+ category: "user",
694
+ isSystem: true,
695
+ isBuiltin: true
696
+ },
697
+ USER_WRITE: {
698
+ name: "user:write",
699
+ displayName: "Write Users",
700
+ description: "Create and update user accounts",
701
+ category: "user",
702
+ isSystem: true,
703
+ isBuiltin: true
704
+ },
705
+ USER_DELETE: {
706
+ name: "user:delete",
707
+ displayName: "Delete Users",
708
+ description: "Delete user accounts",
709
+ category: "user",
710
+ isSystem: true,
711
+ isBuiltin: true
712
+ },
713
+ // RBAC management (superadmin functions)
714
+ RBAC_ROLE_MANAGE: {
715
+ name: "rbac:role:manage",
716
+ displayName: "Manage Roles",
717
+ description: "Create, update, and delete roles",
718
+ category: "rbac",
719
+ isSystem: true,
720
+ isBuiltin: true
721
+ },
722
+ RBAC_PERMISSION_MANAGE: {
723
+ name: "rbac:permission:manage",
724
+ displayName: "Manage Permissions",
725
+ description: "Assign permissions to roles and users",
726
+ category: "rbac",
727
+ isSystem: true,
728
+ isBuiltin: true
729
+ }
730
+ };
731
+ var BUILTIN_ROLE_PERMISSIONS = {
732
+ superadmin: [
733
+ "auth:self:manage",
734
+ "user:read",
735
+ "user:write",
736
+ "user:delete",
737
+ "rbac:role:manage",
738
+ "rbac:permission:manage"
739
+ ],
740
+ admin: [
741
+ "auth:self:manage",
742
+ "user:read",
743
+ "user:write",
744
+ "user:delete"
745
+ ],
746
+ user: [
747
+ "auth:self:manage"
748
+ ]
749
+ };
750
+
751
+ // src/server/rbac/presets.ts
752
+ var PRESET_ROLES = {
753
+ MODERATOR: {
754
+ name: "moderator",
755
+ displayName: "Moderator",
756
+ description: "Content moderation and community management",
757
+ priority: 50,
758
+ isSystem: true
759
+ },
760
+ EDITOR: {
761
+ name: "editor",
762
+ displayName: "Editor",
763
+ description: "Content creation and editing",
764
+ priority: 30,
765
+ isSystem: true
766
+ },
767
+ VIEWER: {
768
+ name: "viewer",
769
+ displayName: "Viewer",
770
+ description: "Read-only access to content",
771
+ priority: 5,
772
+ isSystem: true
773
+ }
774
+ };
775
+ var PRESET_PERMISSIONS = {
776
+ // Content management
777
+ CONTENT_READ: {
778
+ name: "content:read",
779
+ displayName: "Read Content",
780
+ description: "View all content including drafts",
781
+ category: "content",
782
+ isSystem: true
783
+ },
784
+ CONTENT_WRITE: {
785
+ name: "content:write",
786
+ displayName: "Write Content",
787
+ description: "Create and edit content",
788
+ category: "content",
789
+ isSystem: true
790
+ },
791
+ CONTENT_DELETE: {
792
+ name: "content:delete",
793
+ displayName: "Delete Content",
794
+ description: "Delete any content",
795
+ category: "content",
796
+ isSystem: true
797
+ },
798
+ CONTENT_PUBLISH: {
799
+ name: "content:publish",
800
+ displayName: "Publish Content",
801
+ description: "Publish content to make it public",
802
+ category: "content",
803
+ isSystem: true
804
+ },
805
+ // Moderation
806
+ COMMENT_MODERATE: {
807
+ name: "comment:moderate",
808
+ displayName: "Moderate Comments",
809
+ description: "Review and delete inappropriate comments",
810
+ category: "moderation",
811
+ isSystem: true
812
+ },
813
+ // System
814
+ SYSTEM_CONFIG: {
815
+ name: "system:config",
816
+ displayName: "System Configuration",
817
+ description: "Configure application settings",
818
+ category: "system",
819
+ isSystem: true
820
+ },
821
+ // Analytics
822
+ ANALYTICS_VIEW: {
823
+ name: "analytics:view",
824
+ displayName: "View Analytics",
825
+ description: "Access analytics dashboard and reports",
826
+ category: "analytics",
827
+ isSystem: true
828
+ }
829
+ };
830
+ var PRESET_ROLE_PERMISSIONS = {
831
+ moderator: [
832
+ "auth:self:manage",
833
+ "user:read",
834
+ "content:read",
835
+ "content:write",
836
+ "content:delete",
837
+ "comment:moderate"
838
+ ],
839
+ editor: [
840
+ "auth:self:manage",
841
+ "content:read",
842
+ "content:write",
843
+ "content:publish"
844
+ ],
845
+ viewer: [
846
+ "auth:self:manage",
847
+ "content:read"
848
+ ]
849
+ };
850
+
851
+ // src/server/services/auth.service.ts
852
+ init_entities();
853
+ import { findOne as findOne2, create as create3 } from "@spfn/core/db";
854
+ import { ValidationError as ValidationError2 } from "@spfn/core/errors";
855
+
856
+ // src/server/helpers/password.ts
857
+ import bcrypt from "bcrypt";
858
+ var SALT_ROUNDS = parseInt(
859
+ process.env.SPFN_AUTH_BCRYPT_SALT_ROUNDS || // New prefixed version (recommended)
860
+ process.env.BCRYPT_SALT_ROUNDS || // Legacy fallback
861
+ "10",
862
+ 10
863
+ );
864
+ async function hashPassword(password) {
865
+ if (!password || password.length === 0) {
866
+ throw new Error("Password cannot be empty");
867
+ }
868
+ return bcrypt.hash(password, SALT_ROUNDS);
869
+ }
870
+ async function verifyPassword(password, hash) {
871
+ if (!password || password.length === 0) {
872
+ throw new Error("Password cannot be empty");
873
+ }
874
+ if (!hash || hash.length === 0) {
875
+ throw new Error("Hash cannot be empty");
876
+ }
877
+ return bcrypt.compare(password, hash);
878
+ }
879
+ function validatePasswordStrength(password) {
880
+ const errors = [];
881
+ if (password.length < 8) {
882
+ errors.push("Password must be at least 8 characters");
883
+ }
884
+ if (!/[A-Z]/.test(password)) {
885
+ errors.push("Password must contain at least one uppercase letter");
886
+ }
887
+ if (!/[a-z]/.test(password)) {
888
+ errors.push("Password must contain at least one lowercase letter");
889
+ }
890
+ if (!/[0-9]/.test(password)) {
891
+ errors.push("Password must contain at least one number");
892
+ }
893
+ if (!/[^A-Za-z0-9]/.test(password)) {
894
+ errors.push("Password must contain at least one special character");
895
+ }
896
+ return {
897
+ valid: errors.length === 0,
898
+ errors
899
+ };
900
+ }
901
+
902
+ // src/server/helpers/jwt.ts
903
+ import jwt from "jsonwebtoken";
904
+ import crypto from "crypto";
905
+ var JWT_SECRET = process.env.SPFN_AUTH_JWT_SECRET || // New prefixed version (recommended)
906
+ process.env.JWT_SECRET || // Legacy fallback
907
+ "dev-secret-key-change-in-production";
908
+ var JWT_EXPIRES_IN = process.env.SPFN_AUTH_JWT_EXPIRES_IN || // New prefixed version (recommended)
909
+ process.env.JWT_EXPIRES_IN || // Legacy fallback
910
+ "7d";
911
+ function generateToken(payload) {
912
+ return jwt.sign(payload, JWT_SECRET, {
913
+ expiresIn: JWT_EXPIRES_IN
914
+ });
915
+ }
916
+ function verifyToken(token) {
917
+ return jwt.verify(token, JWT_SECRET);
918
+ }
919
+ function verifyClientToken(token, publicKeyB64, algorithm) {
920
+ try {
921
+ const publicKeyDER = Buffer.from(publicKeyB64, "base64");
922
+ const publicKeyObject = crypto.createPublicKey({
923
+ key: publicKeyDER,
924
+ format: "der",
925
+ type: "spki"
926
+ });
927
+ const decoded = jwt.verify(token, publicKeyObject, {
928
+ algorithms: [algorithm],
929
+ // Prevent algorithm confusion attacks
930
+ issuer: "spfn-client"
931
+ // Validate token issuer
932
+ });
933
+ if (typeof decoded === "string") {
934
+ throw new Error("Invalid token format: expected object payload");
935
+ }
936
+ return decoded;
937
+ } catch (error) {
938
+ if (error instanceof jwt.TokenExpiredError) {
939
+ throw new Error("Token has expired");
940
+ }
941
+ if (error instanceof jwt.JsonWebTokenError) {
942
+ throw new Error("Invalid token signature");
943
+ }
944
+ throw new Error(`Token verification failed: ${error instanceof Error ? error.message : "Unknown error"}`);
945
+ }
946
+ }
947
+ function decodeToken(token) {
948
+ try {
949
+ return jwt.decode(token);
950
+ } catch {
951
+ return null;
952
+ }
953
+ }
954
+ function verifyKeyFingerprint(publicKeyB64, expectedFingerprint) {
955
+ try {
956
+ const publicKeyDER = Buffer.from(publicKeyB64, "base64");
957
+ const fingerprint = crypto.createHash("sha256").update(publicKeyDER).digest("hex");
958
+ return fingerprint === expectedFingerprint;
959
+ } catch (error) {
960
+ console.error("Failed to verify key fingerprint:", error);
961
+ return false;
962
+ }
963
+ }
964
+
965
+ // src/server/helpers/verification.ts
966
+ init_verification_codes();
967
+ import jwt2 from "jsonwebtoken";
968
+ import { getDatabase, create } from "@spfn/core/db";
969
+ import { eq, and } from "drizzle-orm";
970
+ function getVerificationTokenSecret() {
971
+ const secret = process.env.SPFN_AUTH_VERIFICATION_TOKEN_SECRET || // New prefixed version (recommended)
972
+ process.env.VERIFICATION_TOKEN_SECRET || // Legacy fallback
973
+ process.env.SPFN_AUTH_JWT_SECRET || // New JWT secret fallback
974
+ process.env.JWT_SECRET;
975
+ if (!secret || secret.length < 32) {
976
+ throw new Error("SPFN_AUTH_VERIFICATION_TOKEN_SECRET must be at least 32 characters long");
977
+ }
978
+ return secret;
979
+ }
980
+ var VERIFICATION_TOKEN_EXPIRY = "15m";
981
+ var VERIFICATION_CODE_EXPIRY_MINUTES = 5;
982
+ var MAX_VERIFICATION_ATTEMPTS = 5;
983
+ function generateVerificationCode() {
984
+ const code = Math.floor(Math.random() * 1e6).toString().padStart(6, "0");
985
+ return code;
986
+ }
987
+ async function storeVerificationCode(target, targetType, code, purpose) {
988
+ const db = getDatabase();
989
+ if (!db) {
990
+ throw new Error("Database not initialized");
991
+ }
992
+ const expiresAt = /* @__PURE__ */ new Date();
993
+ expiresAt.setMinutes(expiresAt.getMinutes() + VERIFICATION_CODE_EXPIRY_MINUTES);
994
+ const record = await create(verificationCodes, {
995
+ target,
996
+ targetType,
997
+ code,
998
+ purpose,
999
+ expiresAt,
1000
+ attempts: "0"
1001
+ });
1002
+ return record;
1003
+ }
1004
+ async function validateVerificationCode(target, targetType, code, purpose) {
1005
+ const db = getDatabase();
1006
+ if (!db) {
1007
+ throw new Error("Database not initialized");
1008
+ }
1009
+ const records = await db.select().from(verificationCodes).where(
1010
+ and(
1011
+ eq(verificationCodes.target, target),
1012
+ eq(verificationCodes.targetType, targetType),
1013
+ eq(verificationCodes.code, code),
1014
+ eq(verificationCodes.purpose, purpose)
1015
+ )
1016
+ ).limit(1);
1017
+ if (records.length === 0) {
1018
+ return { valid: false, error: "Invalid verification code" };
1019
+ }
1020
+ const record = records[0];
1021
+ if (record.usedAt) {
1022
+ return { valid: false, error: "Verification code already used" };
1023
+ }
1024
+ if (/* @__PURE__ */ new Date() > new Date(record.expiresAt)) {
1025
+ return { valid: false, error: "Verification code expired" };
1026
+ }
1027
+ const attempts = parseInt(record.attempts, 10);
1028
+ if (attempts >= MAX_VERIFICATION_ATTEMPTS) {
1029
+ return { valid: false, error: "Too many attempts, please request a new code" };
1030
+ }
1031
+ await db.update(verificationCodes).set({ attempts: (attempts + 1).toString() }).where(eq(verificationCodes.id, record.id));
1032
+ return { valid: true, codeId: record.id };
1033
+ }
1034
+ async function markCodeAsUsed(codeId) {
1035
+ const db = getDatabase();
1036
+ if (!db) {
1037
+ throw new Error("Database not initialized");
1038
+ }
1039
+ await db.update(verificationCodes).set({ usedAt: /* @__PURE__ */ new Date() }).where(eq(verificationCodes.id, codeId));
1040
+ }
1041
+ function createVerificationToken(payload) {
1042
+ const secret = getVerificationTokenSecret();
1043
+ return jwt2.sign(payload, secret, {
1044
+ expiresIn: VERIFICATION_TOKEN_EXPIRY,
1045
+ issuer: "spfn-auth",
1046
+ audience: "spfn-client"
1047
+ });
1048
+ }
1049
+ function validateVerificationToken(token) {
1050
+ try {
1051
+ const secret = getVerificationTokenSecret();
1052
+ const decoded = jwt2.verify(token, secret, {
1053
+ issuer: "spfn-auth",
1054
+ audience: "spfn-client"
1055
+ });
1056
+ if (typeof decoded === "object" && decoded !== null && "target" in decoded && "targetType" in decoded && "purpose" in decoded && "codeId" in decoded) {
1057
+ return decoded;
1058
+ }
1059
+ return null;
1060
+ } catch (error) {
1061
+ console.error("[validateVerificationToken] Error:", error);
1062
+ return null;
1063
+ }
1064
+ }
1065
+ async function sendVerificationEmail(email, code, purpose) {
1066
+ console.log(`[VERIFICATION EMAIL] To: ${email}, Code: ${code}, Purpose: ${purpose}`);
1067
+ }
1068
+ async function sendVerificationSMS(phone, code, purpose) {
1069
+ console.log(`[VERIFICATION SMS] To: ${phone}, Code: ${code}, Purpose: ${purpose}`);
1070
+ }
1071
+
1072
+ // src/server/helpers/context.ts
1073
+ function getAuth(c) {
1074
+ if ("raw" in c && c.raw) {
1075
+ return c.raw.get("auth");
1076
+ }
1077
+ return c.get("auth");
1078
+ }
1079
+ function getUser(c) {
1080
+ return getAuth(c).user;
1081
+ }
1082
+ function getUserId(c) {
1083
+ return getAuth(c).userId;
1084
+ }
1085
+ function getKeyId(c) {
1086
+ return getAuth(c).keyId;
1087
+ }
1088
+
1089
+ // src/server/errors/auth-errors.ts
1090
+ import {
1091
+ ValidationError,
1092
+ UnauthorizedError,
1093
+ ForbiddenError,
1094
+ ConflictError
1095
+ } from "@spfn/core/errors";
1096
+ var InvalidCredentialsError = class extends UnauthorizedError {
1097
+ constructor(message = "Invalid credentials") {
1098
+ super(message);
1099
+ this.name = "InvalidCredentialsError";
1100
+ }
1101
+ };
1102
+ var InvalidTokenError = class extends UnauthorizedError {
1103
+ constructor(message = "Invalid authentication token") {
1104
+ super(message);
1105
+ this.name = "InvalidTokenError";
1106
+ }
1107
+ };
1108
+ var TokenExpiredError = class extends UnauthorizedError {
1109
+ constructor(message = "Authentication token has expired") {
1110
+ super(message);
1111
+ this.name = "TokenExpiredError";
1112
+ }
1113
+ };
1114
+ var KeyExpiredError = class extends UnauthorizedError {
1115
+ constructor(message = "Public key has expired") {
1116
+ super(message);
1117
+ this.name = "KeyExpiredError";
1118
+ }
1119
+ };
1120
+ var AccountDisabledError = class extends ForbiddenError {
1121
+ constructor(status = "disabled") {
1122
+ super(`Account is ${status}`);
1123
+ this.name = "AccountDisabledError";
1124
+ this.details = { status };
1125
+ }
1126
+ };
1127
+ var AccountAlreadyExistsError = class extends ConflictError {
1128
+ constructor(identifier, identifierType) {
1129
+ super("Account already exists");
1130
+ this.name = "AccountAlreadyExistsError";
1131
+ this.details = { identifier, identifierType };
1132
+ }
1133
+ };
1134
+ var InvalidVerificationCodeError = class extends ValidationError {
1135
+ constructor(reason = "Invalid verification code") {
1136
+ super(reason);
1137
+ this.name = "InvalidVerificationCodeError";
1138
+ }
1139
+ };
1140
+ var InvalidVerificationTokenError = class extends ValidationError {
1141
+ constructor(message = "Invalid or expired verification token") {
1142
+ super(message);
1143
+ this.name = "InvalidVerificationTokenError";
1144
+ }
1145
+ };
1146
+ var InvalidKeyFingerprintError = class extends ValidationError {
1147
+ constructor(message = "Invalid key fingerprint") {
1148
+ super(message);
1149
+ this.name = "InvalidKeyFingerprintError";
1150
+ }
1151
+ };
1152
+ var VerificationTokenPurposeMismatchError = class extends ValidationError {
1153
+ constructor(expected, actual) {
1154
+ super(`Verification token is for ${actual}, but ${expected} was expected`);
1155
+ this.name = "VerificationTokenPurposeMismatchError";
1156
+ this.details = { expected, actual };
1157
+ }
1158
+ };
1159
+ var VerificationTokenTargetMismatchError = class extends ValidationError {
1160
+ constructor() {
1161
+ super("Verification token does not match provided email/phone");
1162
+ this.name = "VerificationTokenTargetMismatchError";
1163
+ }
1164
+ };
1165
+
1166
+ // src/server/services/key.service.ts
1167
+ init_entities();
1168
+ import { create as create2, getDatabase as getDatabase2 } from "@spfn/core/db";
1169
+ import { eq as eq2, and as and2 } from "drizzle-orm";
1170
+ function getKeyExpiryDate() {
1171
+ const expiresAt = /* @__PURE__ */ new Date();
1172
+ expiresAt.setDate(expiresAt.getDate() + 90);
1173
+ return expiresAt;
1174
+ }
1175
+ async function registerPublicKeyService(params) {
1176
+ const { userId, keyId, publicKey, fingerprint, algorithm = "ES256" } = params;
1177
+ const isValidFingerprint = verifyKeyFingerprint(publicKey, fingerprint);
1178
+ if (!isValidFingerprint) {
1179
+ throw new InvalidKeyFingerprintError();
1180
+ }
1181
+ await create2(userPublicKeys, {
1182
+ userId,
1183
+ keyId,
1184
+ publicKey,
1185
+ algorithm,
1186
+ fingerprint,
1187
+ isActive: true,
1188
+ createdAt: /* @__PURE__ */ new Date(),
1189
+ expiresAt: getKeyExpiryDate()
1190
+ });
1191
+ }
1192
+ async function rotateKeyService(params) {
1193
+ const { userId, oldKeyId, newKeyId, newPublicKey, fingerprint, algorithm = "ES256" } = params;
1194
+ const isValidFingerprint = verifyKeyFingerprint(newPublicKey, fingerprint);
1195
+ if (!isValidFingerprint) {
1196
+ throw new InvalidKeyFingerprintError();
1197
+ }
1198
+ const db = getDatabase2();
1199
+ await db.update(userPublicKeys).set({
1200
+ isActive: false,
1201
+ revokedAt: /* @__PURE__ */ new Date(),
1202
+ revokedReason: "Replaced by key rotation"
1203
+ }).where(
1204
+ and2(
1205
+ eq2(userPublicKeys.keyId, oldKeyId),
1206
+ eq2(userPublicKeys.userId, userId)
1207
+ )
1208
+ );
1209
+ await create2(userPublicKeys, {
1210
+ userId,
1211
+ keyId: newKeyId,
1212
+ publicKey: newPublicKey,
1213
+ algorithm,
1214
+ fingerprint,
1215
+ isActive: true,
1216
+ createdAt: /* @__PURE__ */ new Date(),
1217
+ expiresAt: getKeyExpiryDate()
1218
+ });
1219
+ return {
1220
+ success: true,
1221
+ keyId: newKeyId
1222
+ };
1223
+ }
1224
+ async function revokeKeyService(params) {
1225
+ const { userId, keyId, reason } = params;
1226
+ const db = getDatabase2();
1227
+ await db.update(userPublicKeys).set({
1228
+ isActive: false,
1229
+ revokedAt: /* @__PURE__ */ new Date(),
1230
+ revokedReason: reason
1231
+ }).where(
1232
+ and2(
1233
+ eq2(userPublicKeys.keyId, keyId),
1234
+ eq2(userPublicKeys.userId, userId)
1235
+ )
1236
+ );
1237
+ }
1238
+
1239
+ // src/server/services/user.service.ts
1240
+ init_entities();
1241
+ import { findOne, updateOne } from "@spfn/core/db";
1242
+ async function getUserByIdService(userId) {
1243
+ return await findOne(users, { id: userId });
1244
+ }
1245
+ async function getUserByEmailService(email) {
1246
+ return await findOne(users, { email });
1247
+ }
1248
+ async function getUserByPhoneService(phone) {
1249
+ return await findOne(users, { phone });
1250
+ }
1251
+ async function updateLastLoginService(userId) {
1252
+ await updateOne(users, { id: userId }, {
1253
+ lastLoginAt: /* @__PURE__ */ new Date()
1254
+ });
1255
+ }
1256
+ async function updateUserService(userId, updates) {
1257
+ await updateOne(users, { id: userId }, {
1258
+ ...updates,
1259
+ updatedAt: /* @__PURE__ */ new Date()
1260
+ });
1261
+ }
1262
+
1263
+ // src/server/services/auth.service.ts
1264
+ async function checkAccountExistsService(params) {
1265
+ const { email, phone } = params;
1266
+ let identifier;
1267
+ let identifierType;
1268
+ let user;
1269
+ if (email) {
1270
+ identifier = email;
1271
+ identifierType = "email";
1272
+ user = await findOne2(users, { email });
1273
+ } else if (phone) {
1274
+ identifier = phone;
1275
+ identifierType = "phone";
1276
+ user = await findOne2(users, { phone });
1277
+ } else {
1278
+ throw new ValidationError2("Either email or phone must be provided");
1279
+ }
1280
+ return {
1281
+ exists: !!user,
1282
+ identifier,
1283
+ identifierType
1284
+ };
1285
+ }
1286
+ async function registerService(params) {
1287
+ const { email, phone, verificationToken, password, publicKey, keyId, fingerprint, algorithm } = params;
1288
+ const tokenPayload = validateVerificationToken(verificationToken);
1289
+ if (!tokenPayload) {
1290
+ throw new InvalidVerificationTokenError();
1291
+ }
1292
+ if (tokenPayload.purpose !== "registration") {
1293
+ throw new VerificationTokenPurposeMismatchError("registration", tokenPayload.purpose);
1294
+ }
1295
+ const providedTarget = email || phone;
1296
+ if (tokenPayload.target !== providedTarget) {
1297
+ throw new VerificationTokenTargetMismatchError();
1298
+ }
1299
+ const providedTargetType = email ? "email" : "phone";
1300
+ if (tokenPayload.targetType !== providedTargetType) {
1301
+ throw new VerificationTokenTargetMismatchError();
1302
+ }
1303
+ let existingUser;
1304
+ if (email) {
1305
+ existingUser = await findOne2(users, { email });
1306
+ } else if (phone) {
1307
+ existingUser = await findOne2(users, { phone });
1308
+ } else {
1309
+ throw new ValidationError2("Either email or phone must be provided");
1310
+ }
1311
+ if (existingUser) {
1312
+ const identifierType = email ? "email" : "phone";
1313
+ throw new AccountAlreadyExistsError(email || phone, identifierType);
1314
+ }
1315
+ const passwordHash = await hashPassword(password);
1316
+ const { getRoleByName: getRoleByName2 } = await Promise.resolve().then(() => (init_role_service(), role_service_exports));
1317
+ const userRole = await getRoleByName2("user");
1318
+ if (!userRole) {
1319
+ throw new Error("Default user role not found. Run initializeAuth() first.");
1320
+ }
1321
+ const newUser = await create3(users, {
1322
+ email: email || null,
1323
+ phone: phone || null,
1324
+ passwordHash,
1325
+ passwordChangeRequired: false,
1326
+ roleId: userRole.id,
1327
+ status: "active",
1328
+ createdAt: /* @__PURE__ */ new Date(),
1329
+ updatedAt: /* @__PURE__ */ new Date()
1330
+ });
1331
+ await registerPublicKeyService({
1332
+ userId: newUser.id,
1333
+ keyId,
1334
+ publicKey,
1335
+ fingerprint,
1336
+ algorithm
1337
+ });
1338
+ return {
1339
+ userId: String(newUser.id),
1340
+ email: newUser.email || void 0,
1341
+ phone: newUser.phone || void 0
1342
+ };
1343
+ }
1344
+ async function loginService(params) {
1345
+ const { email, phone, password, publicKey, keyId, fingerprint, oldKeyId, algorithm } = params;
1346
+ let user;
1347
+ if (email) {
1348
+ user = await findOne2(users, { email });
1349
+ } else if (phone) {
1350
+ user = await findOne2(users, { phone });
1351
+ } else {
1352
+ throw new ValidationError2("Either email or phone must be provided");
1353
+ }
1354
+ if (!user || !user.passwordHash) {
1355
+ throw new InvalidCredentialsError();
1356
+ }
1357
+ const isValid = await verifyPassword(password, user.passwordHash);
1358
+ if (!isValid) {
1359
+ throw new InvalidCredentialsError();
1360
+ }
1361
+ if (user.status !== "active") {
1362
+ throw new AccountDisabledError(user.status);
1363
+ }
1364
+ if (oldKeyId) {
1365
+ await revokeKeyService({
1366
+ userId: user.id,
1367
+ keyId: oldKeyId,
1368
+ reason: "Replaced by new key on login"
1369
+ });
1370
+ }
1371
+ await registerPublicKeyService({
1372
+ userId: user.id,
1373
+ keyId,
1374
+ publicKey,
1375
+ fingerprint,
1376
+ algorithm
1377
+ });
1378
+ await updateLastLoginService(user.id);
1379
+ return {
1380
+ userId: String(user.id),
1381
+ email: user.email || void 0,
1382
+ phone: user.phone || void 0,
1383
+ passwordChangeRequired: user.passwordChangeRequired
1384
+ };
1385
+ }
1386
+ async function logoutService(params) {
1387
+ const { userId, keyId } = params;
1388
+ await revokeKeyService({
1389
+ userId,
1390
+ keyId,
1391
+ reason: "Revoked by logout"
1392
+ });
1393
+ }
1394
+ async function changePasswordService(params) {
1395
+ const { userId, currentPassword, newPassword, passwordHash: providedHash } = params;
1396
+ let passwordHash;
1397
+ if (providedHash) {
1398
+ passwordHash = providedHash;
1399
+ } else {
1400
+ const user = await findOne2(users, { id: userId });
1401
+ if (!user) {
1402
+ throw new ValidationError2("User not found");
1403
+ }
1404
+ passwordHash = user.passwordHash;
1405
+ }
1406
+ if (!passwordHash) {
1407
+ throw new ValidationError2("No password set for this account");
1408
+ }
1409
+ const isValid = await verifyPassword(currentPassword, passwordHash);
1410
+ if (!isValid) {
1411
+ throw new InvalidCredentialsError("Current password is incorrect");
1412
+ }
1413
+ const newPasswordHash = await hashPassword(newPassword);
1414
+ const { updateOne: updateOne2 } = await import("@spfn/core/db");
1415
+ await updateOne2(users, { id: userId }, {
1416
+ passwordHash: newPasswordHash,
1417
+ passwordChangeRequired: false,
1418
+ updatedAt: /* @__PURE__ */ new Date()
1419
+ });
1420
+ }
1421
+
1422
+ // src/server/services/verification.service.ts
1423
+ async function sendVerificationCodeService(params) {
1424
+ const { target, targetType, purpose } = params;
1425
+ const code = generateVerificationCode();
1426
+ const codeRecord = await storeVerificationCode(target, targetType, code, purpose);
1427
+ if (targetType === "email") {
1428
+ await sendVerificationEmail(target, code, purpose);
1429
+ } else {
1430
+ await sendVerificationSMS(target, code, purpose);
1431
+ }
1432
+ return {
1433
+ success: true,
1434
+ expiresAt: codeRecord.expiresAt.toISOString()
1435
+ };
1436
+ }
1437
+ async function verifyCodeService(params) {
1438
+ const { target, targetType, code, purpose } = params;
1439
+ const validation = await validateVerificationCode(target, targetType, code, purpose);
1440
+ if (!validation.valid) {
1441
+ throw new InvalidVerificationCodeError(validation.error || "Invalid verification code");
1442
+ }
1443
+ await markCodeAsUsed(validation.codeId);
1444
+ const verificationToken = createVerificationToken({
1445
+ target,
1446
+ targetType,
1447
+ purpose,
1448
+ codeId: validation.codeId
1449
+ });
1450
+ return {
1451
+ valid: true,
1452
+ verificationToken
1453
+ };
1454
+ }
1455
+
1456
+ // src/server/services/rbac.service.ts
1457
+ init_entities();
1458
+ import { getDatabase as getDatabase4 } from "@spfn/core/db";
1459
+ import { eq as eq4, and as and4, inArray } from "drizzle-orm";
1460
+ async function initializeAuth(options = {}) {
1461
+ const db = getDatabase4();
1462
+ if (!db) {
1463
+ throw new Error("[Auth] Database not initialized. Call initDatabase() first.");
1464
+ }
1465
+ console.log("[Auth] \u{1F510} Initializing RBAC system...");
1466
+ const allRoles = [...Object.values(BUILTIN_ROLES)];
1467
+ if (options.usePresets) {
1468
+ allRoles.push(...Object.values(PRESET_ROLES));
1469
+ } else if (options.presetRoles) {
1470
+ for (const presetKey of options.presetRoles) {
1471
+ if (PRESET_ROLES[presetKey]) {
1472
+ allRoles.push(PRESET_ROLES[presetKey]);
1473
+ }
1474
+ }
1475
+ }
1476
+ if (options.roles) {
1477
+ allRoles.push(...options.roles);
1478
+ }
1479
+ for (const roleConfig of allRoles) {
1480
+ await upsertRole(roleConfig);
1481
+ }
1482
+ const allPermissions = [...Object.values(BUILTIN_PERMISSIONS)];
1483
+ if (options.usePresets) {
1484
+ allPermissions.push(...Object.values(PRESET_PERMISSIONS));
1485
+ } else if (options.presetPermissions) {
1486
+ for (const presetKey of options.presetPermissions) {
1487
+ if (PRESET_PERMISSIONS[presetKey]) {
1488
+ allPermissions.push(PRESET_PERMISSIONS[presetKey]);
1489
+ }
1490
+ }
1491
+ }
1492
+ if (options.permissions) {
1493
+ allPermissions.push(...options.permissions);
1494
+ }
1495
+ for (const permConfig of allPermissions) {
1496
+ await upsertPermission(permConfig);
1497
+ }
1498
+ const allMappings = { ...BUILTIN_ROLE_PERMISSIONS };
1499
+ if (options.usePresets) {
1500
+ Object.assign(allMappings, PRESET_ROLE_PERMISSIONS);
1501
+ }
1502
+ if (options.rolePermissions) {
1503
+ for (const [roleName, permNames] of Object.entries(options.rolePermissions)) {
1504
+ if (allMappings[roleName]) {
1505
+ allMappings[roleName] = [
1506
+ .../* @__PURE__ */ new Set([...allMappings[roleName], ...permNames])
1507
+ ];
1508
+ } else {
1509
+ allMappings[roleName] = permNames;
1510
+ }
1511
+ }
1512
+ }
1513
+ for (const [roleName, permNames] of Object.entries(allMappings)) {
1514
+ await assignPermissionsToRole(roleName, permNames);
1515
+ }
1516
+ console.log("[Auth] \u2705 RBAC initialization complete");
1517
+ console.log(`[Auth] \u{1F4CA} Roles: ${allRoles.length}, Permissions: ${allPermissions.length}`);
1518
+ console.log(`[Auth] \u{1F512} Built-in roles: user, admin, superadmin`);
1519
+ }
1520
+ async function upsertRole(config) {
1521
+ const db = getDatabase4();
1522
+ const existing = await db.select().from(roles).where(eq4(roles.name, config.name)).limit(1);
1523
+ if (existing.length === 0) {
1524
+ await db.insert(roles).values({
1525
+ name: config.name,
1526
+ displayName: config.displayName,
1527
+ description: config.description,
1528
+ priority: config.priority ?? 10,
1529
+ isSystem: config.isSystem ?? false,
1530
+ isBuiltin: config.isBuiltin ?? false
1531
+ });
1532
+ console.log(`[Auth] \u2705 Created role: ${config.name}`);
1533
+ } else {
1534
+ const updateData = {
1535
+ displayName: config.displayName,
1536
+ description: config.description
1537
+ };
1538
+ if (!existing[0].isBuiltin) {
1539
+ updateData.priority = config.priority ?? existing[0].priority;
1540
+ }
1541
+ await db.update(roles).set(updateData).where(eq4(roles.id, existing[0].id));
1542
+ }
1543
+ }
1544
+ async function upsertPermission(config) {
1545
+ const db = getDatabase4();
1546
+ const existing = await db.select().from(permissions).where(eq4(permissions.name, config.name)).limit(1);
1547
+ if (existing.length === 0) {
1548
+ await db.insert(permissions).values({
1549
+ name: config.name,
1550
+ displayName: config.displayName,
1551
+ description: config.description,
1552
+ category: config.category,
1553
+ isSystem: config.isSystem ?? false,
1554
+ isBuiltin: config.isBuiltin ?? false
1555
+ });
1556
+ console.log(`[Auth] \u2705 Created permission: ${config.name}`);
1557
+ } else {
1558
+ await db.update(permissions).set({
1559
+ displayName: config.displayName,
1560
+ description: config.description,
1561
+ category: config.category
1562
+ }).where(eq4(permissions.id, existing[0].id));
1563
+ }
1564
+ }
1565
+ async function assignPermissionsToRole(roleName, permissionNames) {
1566
+ const db = getDatabase4();
1567
+ const [role] = await db.select().from(roles).where(eq4(roles.name, roleName)).limit(1);
1568
+ if (!role) {
1569
+ console.warn(`[Auth] \u26A0\uFE0F Role not found: ${roleName}, skipping permission assignment`);
1570
+ return;
1571
+ }
1572
+ const perms = await db.select().from(permissions).where(inArray(permissions.name, permissionNames));
1573
+ if (perms.length === 0) {
1574
+ console.warn(`[Auth] \u26A0\uFE0F No permissions found for role: ${roleName}`);
1575
+ return;
1576
+ }
1577
+ for (const perm of perms) {
1578
+ const existing = await db.select().from(rolePermissions).where(
1579
+ and4(
1580
+ eq4(rolePermissions.roleId, role.id),
1581
+ eq4(rolePermissions.permissionId, perm.id)
1582
+ )
1583
+ ).limit(1);
1584
+ if (existing.length === 0) {
1585
+ await db.insert(rolePermissions).values({
1586
+ roleId: role.id,
1587
+ permissionId: perm.id
1588
+ });
1589
+ }
1590
+ }
1591
+ }
1592
+
1593
+ // src/server/services/permission.service.ts
1594
+ init_entities();
1595
+ import { getDatabase as getDatabase5 } from "@spfn/core/db";
1596
+ import { eq as eq5, and as and5 } from "drizzle-orm";
1597
+ async function getUserPermissions(userId) {
1598
+ const db = getDatabase5();
1599
+ if (!db) {
1600
+ throw new Error("[Auth] Database not initialized");
1601
+ }
1602
+ const userIdNum = typeof userId === "string" ? Number(userId) : Number(userId);
1603
+ const [user] = await db.select({ roleId: users.roleId }).from(users).where(eq5(users.id, userIdNum)).limit(1);
1604
+ if (!user || !user.roleId) {
1605
+ return [];
1606
+ }
1607
+ const permSet = /* @__PURE__ */ new Set();
1608
+ const rolePerms = await db.select({ name: permissions.name }).from(rolePermissions).innerJoin(permissions, eq5(rolePermissions.permissionId, permissions.id)).where(
1609
+ and5(
1610
+ eq5(rolePermissions.roleId, user.roleId),
1611
+ eq5(permissions.isActive, true)
1612
+ )
1613
+ );
1614
+ for (const perm of rolePerms) {
1615
+ permSet.add(perm.name);
1616
+ }
1617
+ const userPerms = await db.select({
1618
+ name: permissions.name,
1619
+ granted: userPermissions.granted,
1620
+ expiresAt: userPermissions.expiresAt
1621
+ }).from(userPermissions).innerJoin(permissions, eq5(userPermissions.permissionId, permissions.id)).where(eq5(userPermissions.userId, userIdNum));
1622
+ const now = /* @__PURE__ */ new Date();
1623
+ for (const userPerm of userPerms) {
1624
+ if (userPerm.expiresAt && userPerm.expiresAt < now) {
1625
+ continue;
1626
+ }
1627
+ if (userPerm.granted) {
1628
+ permSet.add(userPerm.name);
1629
+ } else {
1630
+ permSet.delete(userPerm.name);
1631
+ }
1632
+ }
1633
+ return Array.from(permSet);
1634
+ }
1635
+ async function hasPermission(userId, permissionName) {
1636
+ const perms = await getUserPermissions(userId);
1637
+ return perms.includes(permissionName);
1638
+ }
1639
+ async function hasAnyPermission(userId, permissionNames) {
1640
+ const perms = await getUserPermissions(userId);
1641
+ return permissionNames.some((p) => perms.includes(p));
1642
+ }
1643
+ async function hasAllPermissions(userId, permissionNames) {
1644
+ const perms = await getUserPermissions(userId);
1645
+ return permissionNames.every((p) => perms.includes(p));
1646
+ }
1647
+ async function hasRole(userId, roleName) {
1648
+ const db = getDatabase5();
1649
+ if (!db) {
1650
+ throw new Error("[Auth] Database not initialized");
1651
+ }
1652
+ const userIdNum = typeof userId === "string" ? Number(userId) : Number(userId);
1653
+ const [user] = await db.select({ roleId: users.roleId }).from(users).where(eq5(users.id, userIdNum)).limit(1);
1654
+ if (!user || !user.roleId) {
1655
+ return false;
1656
+ }
1657
+ const [role] = await db.select({ name: roles.name }).from(roles).where(eq5(roles.id, user.roleId)).limit(1);
1658
+ return role?.name === roleName;
1659
+ }
1660
+ async function hasAnyRole(userId, roleNames) {
1661
+ for (const roleName of roleNames) {
1662
+ if (await hasRole(userId, roleName)) {
1663
+ return true;
1664
+ }
1665
+ }
1666
+ return false;
1667
+ }
1668
+
1669
+ // src/server/services/index.ts
1670
+ init_role_service();
1671
+
1672
+ // src/server/services/invitation.service.ts
1673
+ init_entities();
1674
+ import { getDatabase as getDatabase6 } from "@spfn/core/db";
1675
+ import { eq as eq6, and as and6, lt, desc, sql as sql2 } from "drizzle-orm";
1676
+ import crypto2 from "crypto";
1677
+ function generateInvitationToken() {
1678
+ return crypto2.randomUUID();
1679
+ }
1680
+ function calculateExpiresAt(days = 7) {
1681
+ const expiresAt = /* @__PURE__ */ new Date();
1682
+ expiresAt.setDate(expiresAt.getDate() + days);
1683
+ return expiresAt;
1684
+ }
1685
+ async function createInvitation(params) {
1686
+ const db = getDatabase6();
1687
+ if (!db) {
1688
+ throw new Error("[Auth] Database not initialized");
1689
+ }
1690
+ const { email, roleId, invitedBy, expiresInDays = 7, metadata } = params;
1691
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
1692
+ if (!emailRegex.test(email)) {
1693
+ throw new Error("Invalid email format");
1694
+ }
1695
+ const existingUser = await db.select().from(users).where(eq6(users.email, email)).limit(1);
1696
+ if (existingUser.length > 0) {
1697
+ throw new Error("User with this email already exists");
1698
+ }
1699
+ const existingInvitation = await db.select().from(invitations).where(
1700
+ and6(
1701
+ eq6(invitations.email, email),
1702
+ eq6(invitations.status, "pending")
1703
+ )
1704
+ ).limit(1);
1705
+ if (existingInvitation.length > 0) {
1706
+ throw new Error("Pending invitation already exists for this email");
1707
+ }
1708
+ const role = await db.select().from(roles).where(eq6(roles.id, roleId)).limit(1);
1709
+ if (role.length === 0) {
1710
+ throw new Error(`Role with id ${roleId} not found`);
1711
+ }
1712
+ const inviter = await db.select().from(users).where(eq6(users.id, invitedBy)).limit(1);
1713
+ if (inviter.length === 0) {
1714
+ throw new Error(`User with id ${invitedBy} not found`);
1715
+ }
1716
+ const token = generateInvitationToken();
1717
+ const expiresAt = calculateExpiresAt(expiresInDays);
1718
+ const [invitation] = await db.insert(invitations).values({
1719
+ email,
1720
+ token,
1721
+ roleId,
1722
+ invitedBy,
1723
+ status: "pending",
1724
+ expiresAt,
1725
+ metadata: metadata || null
1726
+ }).returning();
1727
+ console.log(`[Auth] \u2705 Created invitation: ${email} as ${role[0].name} (expires: ${expiresAt.toISOString()})`);
1728
+ return invitation;
1729
+ }
1730
+ async function getInvitationByToken(token) {
1731
+ const db = getDatabase6();
1732
+ if (!db) {
1733
+ throw new Error("[Auth] Database not initialized");
1734
+ }
1735
+ const result = await db.select().from(invitations).where(eq6(invitations.token, token)).limit(1);
1736
+ return result[0] || null;
1737
+ }
1738
+ async function getInvitationWithDetails(token) {
1739
+ const db = getDatabase6();
1740
+ if (!db) {
1741
+ throw new Error("[Auth] Database not initialized");
1742
+ }
1743
+ const result = await db.select({
1744
+ id: invitations.id,
1745
+ email: invitations.email,
1746
+ token: invitations.token,
1747
+ roleId: invitations.roleId,
1748
+ invitedBy: invitations.invitedBy,
1749
+ status: invitations.status,
1750
+ expiresAt: invitations.expiresAt,
1751
+ acceptedAt: invitations.acceptedAt,
1752
+ cancelledAt: invitations.cancelledAt,
1753
+ metadata: invitations.metadata,
1754
+ createdAt: invitations.createdAt,
1755
+ updatedAt: invitations.updatedAt,
1756
+ role: {
1757
+ id: roles.id,
1758
+ name: roles.name,
1759
+ displayName: roles.displayName
1760
+ },
1761
+ inviter: {
1762
+ id: users.id,
1763
+ email: users.email
1764
+ }
1765
+ }).from(invitations).innerJoin(roles, eq6(invitations.roleId, roles.id)).innerJoin(users, eq6(invitations.invitedBy, users.id)).where(eq6(invitations.token, token)).limit(1);
1766
+ return result[0] || null;
1767
+ }
1768
+ async function validateInvitation(token) {
1769
+ const invitation = await getInvitationByToken(token);
1770
+ if (!invitation) {
1771
+ return { valid: false, error: "Invitation not found" };
1772
+ }
1773
+ if (invitation.status === "accepted") {
1774
+ return { valid: false, error: "Invitation already accepted", invitation };
1775
+ }
1776
+ if (invitation.status === "cancelled") {
1777
+ return { valid: false, error: "Invitation was cancelled", invitation };
1778
+ }
1779
+ if (invitation.status === "expired") {
1780
+ return { valid: false, error: "Invitation has expired", invitation };
1781
+ }
1782
+ if (/* @__PURE__ */ new Date() > new Date(invitation.expiresAt)) {
1783
+ return { valid: false, error: "Invitation has expired", invitation };
1784
+ }
1785
+ return { valid: true, invitation };
1786
+ }
1787
+ async function acceptInvitation(params) {
1788
+ const db = getDatabase6();
1789
+ if (!db) {
1790
+ throw new Error("[Auth] Database not initialized");
1791
+ }
1792
+ const { token, password, publicKey, keyId, fingerprint, algorithm } = params;
1793
+ const validation = await validateInvitation(token);
1794
+ if (!validation.valid || !validation.invitation) {
1795
+ throw new Error(validation.error || "Invalid invitation");
1796
+ }
1797
+ const invitation = validation.invitation;
1798
+ const role = await db.select().from(roles).where(eq6(roles.id, invitation.roleId)).limit(1);
1799
+ if (role.length === 0) {
1800
+ throw new Error("Role not found");
1801
+ }
1802
+ const passwordHash = await hashPassword(password);
1803
+ const result = await db.transaction(async (tx) => {
1804
+ const [newUser] = await tx.insert(users).values({
1805
+ email: invitation.email,
1806
+ passwordHash,
1807
+ roleId: invitation.roleId,
1808
+ emailVerifiedAt: /* @__PURE__ */ new Date(),
1809
+ // Auto-verify invited users
1810
+ passwordChangeRequired: false,
1811
+ status: "active"
1812
+ }).returning();
1813
+ const { userPublicKeys: userPublicKeys2 } = await Promise.resolve().then(() => (init_entities(), entities_exports));
1814
+ await tx.insert(userPublicKeys2).values({
1815
+ userId: newUser.id,
1816
+ keyId,
1817
+ publicKey,
1818
+ algorithm,
1819
+ fingerprint,
1820
+ isActive: true,
1821
+ expiresAt: new Date(Date.now() + 90 * 24 * 60 * 60 * 1e3)
1822
+ // 90 days
1823
+ });
1824
+ await tx.update(invitations).set({
1825
+ status: "accepted",
1826
+ acceptedAt: /* @__PURE__ */ new Date(),
1827
+ updatedAt: /* @__PURE__ */ new Date()
1828
+ }).where(eq6(invitations.id, invitation.id));
1829
+ return { newUser, role: role[0] };
1830
+ });
1831
+ console.log(`[Auth] \u2705 Invitation accepted: ${invitation.email} as ${result.role.name}`);
1832
+ return {
1833
+ userId: result.newUser.id,
1834
+ email: result.newUser.email,
1835
+ role: result.role.name
1836
+ };
1837
+ }
1838
+ async function listInvitations(params) {
1839
+ const db = getDatabase6();
1840
+ if (!db) {
1841
+ throw new Error("[Auth] Database not initialized");
1842
+ }
1843
+ const { status, invitedBy, page = 1, limit = 20 } = params;
1844
+ const offset = (page - 1) * limit;
1845
+ const conditions = [];
1846
+ if (status) {
1847
+ conditions.push(eq6(invitations.status, status));
1848
+ }
1849
+ if (invitedBy) {
1850
+ conditions.push(eq6(invitations.invitedBy, invitedBy));
1851
+ }
1852
+ const whereClause = conditions.length > 0 ? and6(...conditions) : void 0;
1853
+ const countResult = await db.select({ count: sql2`count(*)` }).from(invitations).where(whereClause);
1854
+ const total = Number(countResult[0]?.count || 0);
1855
+ const results = await db.select({
1856
+ id: invitations.id,
1857
+ email: invitations.email,
1858
+ token: invitations.token,
1859
+ roleId: invitations.roleId,
1860
+ invitedBy: invitations.invitedBy,
1861
+ status: invitations.status,
1862
+ expiresAt: invitations.expiresAt,
1863
+ acceptedAt: invitations.acceptedAt,
1864
+ cancelledAt: invitations.cancelledAt,
1865
+ metadata: invitations.metadata,
1866
+ createdAt: invitations.createdAt,
1867
+ updatedAt: invitations.updatedAt,
1868
+ role: {
1869
+ id: roles.id,
1870
+ name: roles.name,
1871
+ displayName: roles.displayName
1872
+ },
1873
+ inviter: {
1874
+ id: users.id,
1875
+ email: users.email
1876
+ }
1877
+ }).from(invitations).innerJoin(roles, eq6(invitations.roleId, roles.id)).innerJoin(users, eq6(invitations.invitedBy, users.id)).where(whereClause).orderBy(desc(invitations.createdAt)).limit(limit).offset(offset);
1878
+ return {
1879
+ invitations: results,
1880
+ total,
1881
+ page,
1882
+ limit,
1883
+ totalPages: Math.ceil(total / limit)
1884
+ };
1885
+ }
1886
+ async function cancelInvitation(id10, cancelledBy, reason) {
1887
+ const db = getDatabase6();
1888
+ if (!db) {
1889
+ throw new Error("[Auth] Database not initialized");
1890
+ }
1891
+ const invitation = await db.select().from(invitations).where(eq6(invitations.id, id10)).limit(1);
1892
+ if (invitation.length === 0) {
1893
+ throw new Error("Invitation not found");
1894
+ }
1895
+ if (invitation[0].status !== "pending") {
1896
+ throw new Error(`Cannot cancel ${invitation[0].status} invitation`);
1897
+ }
1898
+ await db.update(invitations).set({
1899
+ status: "cancelled",
1900
+ cancelledAt: /* @__PURE__ */ new Date(),
1901
+ updatedAt: /* @__PURE__ */ new Date(),
1902
+ metadata: invitation[0].metadata ? { ...invitation[0].metadata, cancelReason: reason, cancelledBy } : { cancelReason: reason, cancelledBy }
1903
+ }).where(eq6(invitations.id, id10));
1904
+ console.log(`[Auth] \u26A0\uFE0F Invitation cancelled: ${invitation[0].email} (reason: ${reason || "none"})`);
1905
+ }
1906
+ async function deleteInvitation(id10) {
1907
+ const db = getDatabase6();
1908
+ if (!db) {
1909
+ throw new Error("[Auth] Database not initialized");
1910
+ }
1911
+ await db.delete(invitations).where(eq6(invitations.id, id10));
1912
+ console.log(`[Auth] \u{1F5D1}\uFE0F Invitation deleted: ${id10}`);
1913
+ }
1914
+ async function expireOldInvitations() {
1915
+ const db = getDatabase6();
1916
+ if (!db) {
1917
+ throw new Error("[Auth] Database not initialized");
1918
+ }
1919
+ const now = /* @__PURE__ */ new Date();
1920
+ const expiredInvitations = await db.select().from(invitations).where(
1921
+ and6(
1922
+ eq6(invitations.status, "pending"),
1923
+ lt(invitations.expiresAt, now)
1924
+ )
1925
+ );
1926
+ if (expiredInvitations.length === 0) {
1927
+ return 0;
1928
+ }
1929
+ await db.update(invitations).set({
1930
+ status: "expired",
1931
+ updatedAt: now
1932
+ }).where(
1933
+ and6(
1934
+ eq6(invitations.status, "pending"),
1935
+ lt(invitations.expiresAt, now)
1936
+ )
1937
+ );
1938
+ console.log(`[Auth] \u23F0 Expired ${expiredInvitations.length} old invitations`);
1939
+ return expiredInvitations.length;
1940
+ }
1941
+ async function resendInvitation(id10, expiresInDays = 7) {
1942
+ const db = getDatabase6();
1943
+ if (!db) {
1944
+ throw new Error("[Auth] Database not initialized");
1945
+ }
1946
+ const invitation = await db.select().from(invitations).where(eq6(invitations.id, id10)).limit(1);
1947
+ if (invitation.length === 0) {
1948
+ throw new Error("Invitation not found");
1949
+ }
1950
+ if (!["pending", "expired"].includes(invitation[0].status)) {
1951
+ throw new Error(`Cannot resend ${invitation[0].status} invitation`);
1952
+ }
1953
+ const newExpiresAt = calculateExpiresAt(expiresInDays);
1954
+ const [updated] = await db.update(invitations).set({
1955
+ status: "pending",
1956
+ expiresAt: newExpiresAt,
1957
+ updatedAt: /* @__PURE__ */ new Date()
1958
+ }).where(eq6(invitations.id, id10)).returning();
1959
+ console.log(`[Auth] \u{1F4E7} Invitation resent: ${invitation[0].email} (new expiry: ${newExpiresAt.toISOString()})`);
1960
+ return updated;
1961
+ }
1962
+
1963
+ // src/server/middleware/authenticate.ts
1964
+ init_entities();
1965
+ import { findOne as findOne3, getDatabase as getDatabase7 } from "@spfn/core/db";
1966
+ import { UnauthorizedError as UnauthorizedError2 } from "@spfn/core/errors";
1967
+ import { eq as eq7, and as and7 } from "drizzle-orm";
1968
+ async function authenticate(c, next) {
1969
+ const authHeader = c.req.header("Authorization");
1970
+ const keyId = c.req.header("X-Key-Id");
1971
+ if (!authHeader || !authHeader.startsWith("Bearer ")) {
1972
+ throw new UnauthorizedError2("Missing or invalid authorization header");
1973
+ }
1974
+ if (!keyId) {
1975
+ throw new UnauthorizedError2("Missing X-Key-Id header");
1976
+ }
1977
+ const token = authHeader.substring(7);
1978
+ const db = getDatabase7();
1979
+ const [keyRecord] = await db.select().from(userPublicKeys).where(
1980
+ and7(
1981
+ eq7(userPublicKeys.keyId, keyId),
1982
+ eq7(userPublicKeys.isActive, true)
1983
+ )
1984
+ );
1985
+ if (!keyRecord) {
1986
+ throw new UnauthorizedError2("Invalid or revoked key");
1987
+ }
1988
+ if (keyRecord.expiresAt && /* @__PURE__ */ new Date() > keyRecord.expiresAt) {
1989
+ throw new KeyExpiredError();
1990
+ }
1991
+ try {
1992
+ verifyClientToken(
1993
+ token,
1994
+ keyRecord.publicKey,
1995
+ keyRecord.algorithm
1996
+ );
1997
+ } catch (err) {
1998
+ if (err instanceof Error) {
1999
+ if (err.name === "TokenExpiredError") {
2000
+ throw new TokenExpiredError();
2001
+ }
2002
+ if (err.name === "JsonWebTokenError") {
2003
+ throw new InvalidTokenError("Invalid token signature");
2004
+ }
2005
+ }
2006
+ throw new UnauthorizedError2("Authentication failed");
2007
+ }
2008
+ const user = await findOne3(users, { id: keyRecord.userId });
2009
+ if (!user) {
2010
+ throw new UnauthorizedError2("User not found");
2011
+ }
2012
+ if (user.status !== "active") {
2013
+ throw new AccountDisabledError(user.status);
2014
+ }
2015
+ db.update(userPublicKeys).set({ lastUsedAt: /* @__PURE__ */ new Date() }).where(eq7(userPublicKeys.id, keyRecord.id)).execute().catch((err) => console.error("Failed to update lastUsedAt:", err));
2016
+ c.set("auth", {
2017
+ user,
2018
+ userId: String(user.id),
2019
+ keyId
2020
+ });
2021
+ await next();
2022
+ }
2023
+
2024
+ // src/server/middleware/require-permission.ts
2025
+ import { ForbiddenError as ForbiddenError2 } from "@spfn/core/errors";
2026
+ function requirePermissions(...permissionNames) {
2027
+ return async (c, next) => {
2028
+ const auth = getAuth(c);
2029
+ if (!auth) {
2030
+ throw new ForbiddenError2("Authentication required");
2031
+ }
2032
+ const { userId } = auth;
2033
+ const allowed = await hasAllPermissions(userId, permissionNames);
2034
+ if (!allowed) {
2035
+ throw new ForbiddenError2(
2036
+ `Missing required permissions: ${permissionNames.join(", ")}`
2037
+ );
2038
+ }
2039
+ await next();
2040
+ };
2041
+ }
2042
+ function requireAnyPermission(...permissionNames) {
2043
+ return async (c, next) => {
2044
+ const auth = getAuth(c);
2045
+ if (!auth) {
2046
+ throw new ForbiddenError2("Authentication required");
2047
+ }
2048
+ const { userId } = auth;
2049
+ const allowed = await hasAnyPermission(userId, permissionNames);
2050
+ if (!allowed) {
2051
+ throw new ForbiddenError2(
2052
+ `Requires one of: ${permissionNames.join(", ")}`
2053
+ );
2054
+ }
2055
+ await next();
2056
+ };
2057
+ }
2058
+
2059
+ // src/server/middleware/require-role.ts
2060
+ import { ForbiddenError as ForbiddenError3 } from "@spfn/core/errors";
2061
+ function requireRole(...roleNames) {
2062
+ return async (c, next) => {
2063
+ const auth = getAuth(c);
2064
+ if (!auth) {
2065
+ throw new ForbiddenError3("Authentication required");
2066
+ }
2067
+ const { userId } = auth;
2068
+ const allowed = await hasAnyRole(userId, roleNames);
2069
+ if (!allowed) {
2070
+ throw new ForbiddenError3(
2071
+ `Required roles: ${roleNames.join(", ")}`
2072
+ );
2073
+ }
2074
+ await next();
2075
+ };
2076
+ }
2077
+
2078
+ // src/server/setup.ts
2079
+ init_entities();
2080
+ import { findOne as findOne4, create as create4 } from "@spfn/core/db";
2081
+ init_role_service();
2082
+ function parseAdminAccounts() {
2083
+ const accounts = [];
2084
+ if (process.env.SPFN_AUTH_ADMIN_ACCOUNTS || process.env.ADMIN_ACCOUNTS) {
2085
+ try {
2086
+ const accountsJson = process.env.SPFN_AUTH_ADMIN_ACCOUNTS || // New prefixed version (recommended)
2087
+ process.env.ADMIN_ACCOUNTS;
2088
+ const parsed = JSON.parse(accountsJson);
2089
+ if (!Array.isArray(parsed)) {
2090
+ console.error("[Auth] \u274C SPFN_AUTH_ADMIN_ACCOUNTS must be an array");
2091
+ return accounts;
2092
+ }
2093
+ for (const item of parsed) {
2094
+ if (!item.email || !item.password) {
2095
+ console.warn("[Auth] \u26A0\uFE0F Skipping account: missing email or password");
2096
+ continue;
2097
+ }
2098
+ accounts.push({
2099
+ email: item.email,
2100
+ password: item.password,
2101
+ role: item.role || "user",
2102
+ phone: item.phone,
2103
+ passwordChangeRequired: item.passwordChangeRequired !== false
2104
+ // Default: true
2105
+ });
2106
+ }
2107
+ return accounts;
2108
+ } catch (error) {
2109
+ const err = error;
2110
+ console.error("[Auth] \u274C Failed to parse SPFN_AUTH_ADMIN_ACCOUNTS:", err.message);
2111
+ return accounts;
2112
+ }
2113
+ }
2114
+ const adminEmails = process.env.SPFN_AUTH_ADMIN_EMAILS || // New prefixed version (recommended)
2115
+ process.env.ADMIN_EMAILS;
2116
+ if (adminEmails) {
2117
+ const emails = adminEmails.split(",").map((s) => s.trim());
2118
+ const passwords = (process.env.SPFN_AUTH_ADMIN_PASSWORDS || // New prefixed version (recommended)
2119
+ process.env.ADMIN_PASSWORDS || // Legacy fallback
2120
+ "").split(",").map((s) => s.trim());
2121
+ const roles2 = (process.env.SPFN_AUTH_ADMIN_ROLES || // New prefixed version (recommended)
2122
+ process.env.ADMIN_ROLES || // Legacy fallback
2123
+ "").split(",").map((s) => s.trim());
2124
+ if (passwords.length !== emails.length) {
2125
+ console.error("[Auth] \u274C SPFN_AUTH_ADMIN_EMAILS and SPFN_AUTH_ADMIN_PASSWORDS length mismatch");
2126
+ return accounts;
2127
+ }
2128
+ for (let i = 0; i < emails.length; i++) {
2129
+ const email = emails[i];
2130
+ const password = passwords[i];
2131
+ const role = roles2[i] || "user";
2132
+ if (!email || !password) {
2133
+ console.warn(`[Auth] \u26A0\uFE0F Skipping account ${i + 1}: missing email or password`);
2134
+ continue;
2135
+ }
2136
+ accounts.push({
2137
+ email,
2138
+ password,
2139
+ role,
2140
+ passwordChangeRequired: true
2141
+ });
2142
+ }
2143
+ return accounts;
2144
+ }
2145
+ const adminEmail = process.env.SPFN_AUTH_ADMIN_EMAIL || // New prefixed version (recommended)
2146
+ process.env.ADMIN_EMAIL;
2147
+ const adminPassword = process.env.SPFN_AUTH_ADMIN_PASSWORD || // New prefixed version (recommended)
2148
+ process.env.ADMIN_PASSWORD;
2149
+ if (adminEmail && adminPassword) {
2150
+ accounts.push({
2151
+ email: adminEmail,
2152
+ password: adminPassword,
2153
+ role: "superadmin",
2154
+ passwordChangeRequired: true
2155
+ });
2156
+ }
2157
+ return accounts;
2158
+ }
2159
+ async function ensureAdminExists() {
2160
+ const accounts = parseAdminAccounts();
2161
+ if (accounts.length === 0) {
2162
+ return;
2163
+ }
2164
+ console.log(`[Auth] Creating ${accounts.length} admin account(s)...`);
2165
+ let created = 0;
2166
+ let skipped = 0;
2167
+ let failed = 0;
2168
+ for (const account of accounts) {
2169
+ try {
2170
+ const existing = await findOne4(users, { email: account.email });
2171
+ if (existing) {
2172
+ console.log(`[Auth] \u26A0\uFE0F Account already exists: ${account.email} (skipped)`);
2173
+ skipped++;
2174
+ continue;
2175
+ }
2176
+ const roleName = account.role || "user";
2177
+ const role = await getRoleByName(roleName);
2178
+ if (!role) {
2179
+ console.error(`[Auth] \u274C Role '${roleName}' not found for ${account.email}. Run initializeAuth() first.`);
2180
+ failed++;
2181
+ continue;
2182
+ }
2183
+ const passwordHash = await hashPassword(account.password);
2184
+ await create4(users, {
2185
+ email: account.email,
2186
+ phone: account.phone || null,
2187
+ passwordHash,
2188
+ roleId: role.id,
2189
+ emailVerifiedAt: /* @__PURE__ */ new Date(),
2190
+ // Auto-verify admin
2191
+ passwordChangeRequired: account.passwordChangeRequired !== false,
2192
+ status: "active"
2193
+ });
2194
+ console.log(`[Auth] \u2705 Admin account created: ${account.email} (${roleName})`);
2195
+ created++;
2196
+ } catch (error) {
2197
+ const err = error;
2198
+ console.error(`[Auth] \u274C Failed to create account ${account.email}:`, err.message);
2199
+ failed++;
2200
+ }
2201
+ }
2202
+ console.log(`[Auth] \u{1F4CA} Summary: ${created} created, ${skipped} skipped, ${failed} failed`);
2203
+ if (created > 0) {
2204
+ console.log("[Auth] \u26A0\uFE0F Please change passwords on first login!");
2205
+ }
2206
+ }
2207
+ export {
2208
+ BUILTIN_PERMISSIONS,
2209
+ BUILTIN_ROLES,
2210
+ BUILTIN_ROLE_PERMISSIONS,
2211
+ PRESET_PERMISSIONS,
2212
+ PRESET_ROLES,
2213
+ PRESET_ROLE_PERMISSIONS,
2214
+ acceptInvitation,
2215
+ addPermissionToRole,
2216
+ authenticate,
2217
+ cancelInvitation,
2218
+ changePasswordService,
2219
+ checkAccountExistsService,
2220
+ createInvitation,
2221
+ createRole,
2222
+ createVerificationToken,
2223
+ decodeToken,
2224
+ deleteInvitation,
2225
+ deleteRole,
2226
+ ensureAdminExists,
2227
+ expireOldInvitations,
2228
+ generateToken,
2229
+ generateVerificationCode,
2230
+ getAllRoles,
2231
+ getAuth,
2232
+ getInvitationByToken,
2233
+ getInvitationWithDetails,
2234
+ getKeyId,
2235
+ getRoleByName,
2236
+ getRolePermissions,
2237
+ getUser,
2238
+ getUserByEmailService,
2239
+ getUserByIdService,
2240
+ getUserByPhoneService,
2241
+ getUserId,
2242
+ getUserPermissions,
2243
+ hasAllPermissions,
2244
+ hasAnyPermission,
2245
+ hasAnyRole,
2246
+ hasPermission,
2247
+ hasRole,
2248
+ hashPassword,
2249
+ initializeAuth,
2250
+ listInvitations,
2251
+ loginService,
2252
+ logoutService,
2253
+ markCodeAsUsed,
2254
+ registerPublicKeyService,
2255
+ registerService,
2256
+ removePermissionFromRole,
2257
+ requireAnyPermission,
2258
+ requirePermissions,
2259
+ requireRole,
2260
+ resendInvitation,
2261
+ revokeKeyService,
2262
+ rotateKeyService,
2263
+ sendVerificationCodeService,
2264
+ sendVerificationEmail,
2265
+ sendVerificationSMS,
2266
+ setRolePermissions,
2267
+ storeVerificationCode,
2268
+ updateLastLoginService,
2269
+ updateRole,
2270
+ updateUserService,
2271
+ validateInvitation,
2272
+ validatePasswordStrength,
2273
+ validateVerificationCode,
2274
+ validateVerificationToken,
2275
+ verifyClientToken,
2276
+ verifyCodeService,
2277
+ verifyKeyFingerprint,
2278
+ verifyPassword,
2279
+ verifyToken
2280
+ };
2281
+ //# sourceMappingURL=server.js.map