@checkstack/auth-backend 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +23 -0
- package/package.json +1 -1
- package/src/index.ts +43 -86
- package/src/router.test.ts +415 -30
- package/src/router.ts +299 -88
package/src/router.ts
CHANGED
|
@@ -31,6 +31,11 @@ import {
|
|
|
31
31
|
PLATFORM_REGISTRATION_CONFIG_ID,
|
|
32
32
|
} from "./platform-registration-config";
|
|
33
33
|
|
|
34
|
+
export const ADMIN_ROLE_ID = "admin";
|
|
35
|
+
export const USERS_ROLE_ID = "users";
|
|
36
|
+
export const ANONYMOUS_ROLE_ID = "anonymous";
|
|
37
|
+
export const APPLICATIONS_ROLE_ID = "applications";
|
|
38
|
+
|
|
34
39
|
/**
|
|
35
40
|
* Creates the auth router using contract-based implementation.
|
|
36
41
|
*
|
|
@@ -52,12 +57,12 @@ const os = implement(authContract)
|
|
|
52
57
|
*/
|
|
53
58
|
async function getStrategyEnabled(
|
|
54
59
|
strategyId: string,
|
|
55
|
-
configService: ConfigService
|
|
60
|
+
configService: ConfigService,
|
|
56
61
|
): Promise<boolean> {
|
|
57
62
|
const metaConfig = await configService.get(
|
|
58
63
|
`${strategyId}.meta`,
|
|
59
64
|
strategyMetaConfigV1,
|
|
60
|
-
STRATEGY_META_CONFIG_VERSION
|
|
65
|
+
STRATEGY_META_CONFIG_VERSION,
|
|
61
66
|
);
|
|
62
67
|
|
|
63
68
|
// Default: credential=true (fresh installs), others=false (require explicit config)
|
|
@@ -70,13 +75,13 @@ async function getStrategyEnabled(
|
|
|
70
75
|
async function setStrategyEnabled(
|
|
71
76
|
strategyId: string,
|
|
72
77
|
enabled: boolean,
|
|
73
|
-
configService: ConfigService
|
|
78
|
+
configService: ConfigService,
|
|
74
79
|
): Promise<void> {
|
|
75
80
|
await configService.set(
|
|
76
81
|
`${strategyId}.meta`,
|
|
77
82
|
strategyMetaConfigV1,
|
|
78
83
|
STRATEGY_META_CONFIG_VERSION,
|
|
79
|
-
{ enabled }
|
|
84
|
+
{ enabled },
|
|
80
85
|
);
|
|
81
86
|
}
|
|
82
87
|
|
|
@@ -87,12 +92,12 @@ async function setStrategyEnabled(
|
|
|
87
92
|
* @returns true if registration is allowed, false otherwise
|
|
88
93
|
*/
|
|
89
94
|
async function isRegistrationAllowed(
|
|
90
|
-
configService: ConfigService
|
|
95
|
+
configService: ConfigService,
|
|
91
96
|
): Promise<boolean> {
|
|
92
97
|
const config = await configService.get(
|
|
93
98
|
PLATFORM_REGISTRATION_CONFIG_ID,
|
|
94
99
|
platformRegistrationConfigV1,
|
|
95
|
-
PLATFORM_REGISTRATION_CONFIG_VERSION
|
|
100
|
+
PLATFORM_REGISTRATION_CONFIG_VERSION,
|
|
96
101
|
);
|
|
97
102
|
return config?.allowRegistration ?? true;
|
|
98
103
|
}
|
|
@@ -124,7 +129,7 @@ export const createAuthRouter = (
|
|
|
124
129
|
isDefault?: boolean;
|
|
125
130
|
isPublic?: boolean;
|
|
126
131
|
}[];
|
|
127
|
-
}
|
|
132
|
+
},
|
|
128
133
|
) => {
|
|
129
134
|
// Public endpoint for enabled strategies (no authentication required)
|
|
130
135
|
const getEnabledStrategies = os.getEnabledStrategies.handler(async () => {
|
|
@@ -148,7 +153,7 @@ export const createAuthRouter = (
|
|
|
148
153
|
icon: strategy.icon,
|
|
149
154
|
requiresManualRegistration: strategy.requiresManualRegistration,
|
|
150
155
|
};
|
|
151
|
-
})
|
|
156
|
+
}),
|
|
152
157
|
);
|
|
153
158
|
|
|
154
159
|
// Filter to only return enabled strategies
|
|
@@ -173,8 +178,8 @@ export const createAuthRouter = (
|
|
|
173
178
|
.where(
|
|
174
179
|
inArray(
|
|
175
180
|
schema.userRole.userId,
|
|
176
|
-
users.map((u) => u.id)
|
|
177
|
-
)
|
|
181
|
+
users.map((u) => u.id),
|
|
182
|
+
),
|
|
178
183
|
);
|
|
179
184
|
|
|
180
185
|
return users.map((u) => ({
|
|
@@ -186,9 +191,15 @@ export const createAuthRouter = (
|
|
|
186
191
|
});
|
|
187
192
|
|
|
188
193
|
const deleteUser = os.deleteUser.handler(async ({ input: id, context }) => {
|
|
189
|
-
if
|
|
194
|
+
// Check if user has admin role - prevent deletion to avoid lockout
|
|
195
|
+
const userRoles = await internalDb
|
|
196
|
+
.select({ roleId: schema.userRole.roleId })
|
|
197
|
+
.from(schema.userRole)
|
|
198
|
+
.where(eq(schema.userRole.userId, id));
|
|
199
|
+
|
|
200
|
+
if (userRoles.some((ur) => ur.roleId === ADMIN_ROLE_ID)) {
|
|
190
201
|
throw new ORPCError("FORBIDDEN", {
|
|
191
|
-
message: "Cannot delete
|
|
202
|
+
message: "Cannot delete users with the admin role",
|
|
192
203
|
});
|
|
193
204
|
}
|
|
194
205
|
|
|
@@ -227,7 +238,7 @@ export const createAuthRouter = (
|
|
|
227
238
|
.map((rp) => rp.accessRuleId),
|
|
228
239
|
isSystem: role.isSystem || false,
|
|
229
240
|
// Anonymous role cannot be assigned to users - it's for unauthenticated access
|
|
230
|
-
isAssignable: role.id !==
|
|
241
|
+
isAssignable: role.id !== ANONYMOUS_ROLE_ID,
|
|
231
242
|
}));
|
|
232
243
|
});
|
|
233
244
|
|
|
@@ -244,12 +255,12 @@ export const createAuthRouter = (
|
|
|
244
255
|
|
|
245
256
|
// Get active access rules to filter input
|
|
246
257
|
const activeAccessRules = new Set(
|
|
247
|
-
accessRuleRegistry.getAccessRules().map((p) => p.id)
|
|
258
|
+
accessRuleRegistry.getAccessRules().map((p) => p.id),
|
|
248
259
|
);
|
|
249
260
|
|
|
250
261
|
// Filter to only include active access rules
|
|
251
262
|
const validAccessRules = inputAccessRules.filter((p) =>
|
|
252
|
-
activeAccessRules.has(p)
|
|
263
|
+
activeAccessRules.has(p),
|
|
253
264
|
);
|
|
254
265
|
|
|
255
266
|
await internalDb.transaction(async (tx) => {
|
|
@@ -267,7 +278,7 @@ export const createAuthRouter = (
|
|
|
267
278
|
validAccessRules.map((accessRuleId) => ({
|
|
268
279
|
roleId: id,
|
|
269
280
|
accessRuleId,
|
|
270
|
-
}))
|
|
281
|
+
})),
|
|
271
282
|
);
|
|
272
283
|
}
|
|
273
284
|
});
|
|
@@ -292,8 +303,8 @@ export const createAuthRouter = (
|
|
|
292
303
|
});
|
|
293
304
|
}
|
|
294
305
|
|
|
295
|
-
const isUsersRole = id ===
|
|
296
|
-
const isAdminRole = id ===
|
|
306
|
+
const isUsersRole = id === USERS_ROLE_ID;
|
|
307
|
+
const isAdminRole = id === ADMIN_ROLE_ID;
|
|
297
308
|
|
|
298
309
|
// System roles can have name/description edited, but not deleted
|
|
299
310
|
// Admin role: access rules cannot be changed (wildcard access)
|
|
@@ -302,12 +313,12 @@ export const createAuthRouter = (
|
|
|
302
313
|
|
|
303
314
|
// Get active access rules to filter input
|
|
304
315
|
const activeAccessRules = new Set(
|
|
305
|
-
accessRuleRegistry.getAccessRules().map((p) => p.id)
|
|
316
|
+
accessRuleRegistry.getAccessRules().map((p) => p.id),
|
|
306
317
|
);
|
|
307
318
|
|
|
308
319
|
// Filter to only include active access rules
|
|
309
320
|
const validAccessRules = inputAccessRules.filter((p) =>
|
|
310
|
-
activeAccessRules.has(p)
|
|
321
|
+
activeAccessRules.has(p),
|
|
311
322
|
);
|
|
312
323
|
|
|
313
324
|
// Track disabled authenticated default access rules for "users" role
|
|
@@ -319,7 +330,7 @@ export const createAuthRouter = (
|
|
|
319
330
|
|
|
320
331
|
// Find authenticated default access rules that are being removed
|
|
321
332
|
const removedDefaults = defaultPermIds.filter(
|
|
322
|
-
(defId) => !validAccessRules.includes(defId)
|
|
333
|
+
(defId) => !validAccessRules.includes(defId),
|
|
323
334
|
);
|
|
324
335
|
|
|
325
336
|
// Insert into disabled_default_access_rule table
|
|
@@ -335,7 +346,7 @@ export const createAuthRouter = (
|
|
|
335
346
|
|
|
336
347
|
// Remove from disabled table if being re-added
|
|
337
348
|
const readdedDefaults = validAccessRules.filter((p) =>
|
|
338
|
-
defaultPermIds.includes(p)
|
|
349
|
+
defaultPermIds.includes(p),
|
|
339
350
|
);
|
|
340
351
|
for (const permId of readdedDefaults) {
|
|
341
352
|
await internalDb
|
|
@@ -345,7 +356,7 @@ export const createAuthRouter = (
|
|
|
345
356
|
}
|
|
346
357
|
|
|
347
358
|
// Track disabled public default access rules for "anonymous" role
|
|
348
|
-
const isAnonymousRole = id ===
|
|
359
|
+
const isAnonymousRole = id === ANONYMOUS_ROLE_ID;
|
|
349
360
|
if (isAnonymousRole) {
|
|
350
361
|
const allPerms = accessRuleRegistry.getAccessRules();
|
|
351
362
|
const publicDefaultPermIds = allPerms
|
|
@@ -354,7 +365,7 @@ export const createAuthRouter = (
|
|
|
354
365
|
|
|
355
366
|
// Find public default access rules that are being removed
|
|
356
367
|
const removedPublicDefaults = publicDefaultPermIds.filter(
|
|
357
|
-
(defId) => !validAccessRules.includes(defId)
|
|
368
|
+
(defId) => !validAccessRules.includes(defId),
|
|
358
369
|
);
|
|
359
370
|
|
|
360
371
|
// Insert into disabled_public_default_access_rule table
|
|
@@ -370,13 +381,13 @@ export const createAuthRouter = (
|
|
|
370
381
|
|
|
371
382
|
// Remove from disabled table if being re-added
|
|
372
383
|
const readdedPublicDefaults = validAccessRules.filter((p) =>
|
|
373
|
-
publicDefaultPermIds.includes(p)
|
|
384
|
+
publicDefaultPermIds.includes(p),
|
|
374
385
|
);
|
|
375
386
|
for (const permId of readdedPublicDefaults) {
|
|
376
387
|
await internalDb
|
|
377
388
|
.delete(schema.disabledPublicDefaultAccessRule)
|
|
378
389
|
.where(
|
|
379
|
-
eq(schema.disabledPublicDefaultAccessRule.accessRuleId, permId)
|
|
390
|
+
eq(schema.disabledPublicDefaultAccessRule.accessRuleId, permId),
|
|
380
391
|
);
|
|
381
392
|
}
|
|
382
393
|
}
|
|
@@ -406,7 +417,7 @@ export const createAuthRouter = (
|
|
|
406
417
|
validAccessRules.map((accessRuleId) => ({
|
|
407
418
|
roleId: id,
|
|
408
419
|
accessRuleId,
|
|
409
|
-
}))
|
|
420
|
+
})),
|
|
410
421
|
);
|
|
411
422
|
}
|
|
412
423
|
});
|
|
@@ -468,7 +479,7 @@ export const createAuthRouter = (
|
|
|
468
479
|
}
|
|
469
480
|
|
|
470
481
|
// Prevent assignment of the "anonymous" role - it's reserved for unauthenticated users
|
|
471
|
-
if (roles.includes(
|
|
482
|
+
if (roles.includes(ANONYMOUS_ROLE_ID)) {
|
|
472
483
|
throw new ORPCError("BAD_REQUEST", {
|
|
473
484
|
message: "The 'anonymous' role cannot be assigned to users",
|
|
474
485
|
});
|
|
@@ -483,11 +494,11 @@ export const createAuthRouter = (
|
|
|
483
494
|
roles.map((roleId) => ({
|
|
484
495
|
userId,
|
|
485
496
|
roleId,
|
|
486
|
-
}))
|
|
497
|
+
})),
|
|
487
498
|
);
|
|
488
499
|
}
|
|
489
500
|
});
|
|
490
|
-
}
|
|
501
|
+
},
|
|
491
502
|
);
|
|
492
503
|
|
|
493
504
|
const getStrategies = os.getStrategies.handler(async () => {
|
|
@@ -500,7 +511,7 @@ export const createAuthRouter = (
|
|
|
500
511
|
strategy.id,
|
|
501
512
|
strategy.configSchema,
|
|
502
513
|
strategy.configVersion,
|
|
503
|
-
strategy.migrations
|
|
514
|
+
strategy.migrations,
|
|
504
515
|
);
|
|
505
516
|
|
|
506
517
|
// Convert Zod schema to JSON Schema with automatic secret metadata
|
|
@@ -520,7 +531,7 @@ export const createAuthRouter = (
|
|
|
520
531
|
config,
|
|
521
532
|
adminInstructions: strategy.adminInstructions,
|
|
522
533
|
};
|
|
523
|
-
})
|
|
534
|
+
}),
|
|
524
535
|
);
|
|
525
536
|
});
|
|
526
537
|
|
|
@@ -541,7 +552,7 @@ export const createAuthRouter = (
|
|
|
541
552
|
strategy.configSchema,
|
|
542
553
|
strategy.configVersion,
|
|
543
554
|
config, // Just the config, no enabled mixed in
|
|
544
|
-
strategy.migrations
|
|
555
|
+
strategy.migrations,
|
|
545
556
|
);
|
|
546
557
|
}
|
|
547
558
|
|
|
@@ -574,12 +585,208 @@ export const createAuthRouter = (
|
|
|
574
585
|
PLATFORM_REGISTRATION_CONFIG_ID,
|
|
575
586
|
platformRegistrationConfigV1,
|
|
576
587
|
PLATFORM_REGISTRATION_CONFIG_VERSION,
|
|
577
|
-
{ allowRegistration: input.allowRegistration }
|
|
588
|
+
{ allowRegistration: input.allowRegistration },
|
|
578
589
|
);
|
|
579
590
|
// Trigger auth reload to apply new settings
|
|
580
591
|
await reloadAuthFn();
|
|
581
592
|
return { success: true };
|
|
582
|
-
}
|
|
593
|
+
},
|
|
594
|
+
);
|
|
595
|
+
|
|
596
|
+
// ==========================================================================
|
|
597
|
+
// ONBOARDING ENDPOINTS
|
|
598
|
+
// ==========================================================================
|
|
599
|
+
|
|
600
|
+
const getOnboardingStatus = os.getOnboardingStatus.handler(async () => {
|
|
601
|
+
// Check if any users exist in the database
|
|
602
|
+
const users = await internalDb
|
|
603
|
+
.select({ id: schema.user.id })
|
|
604
|
+
.from(schema.user)
|
|
605
|
+
.limit(1);
|
|
606
|
+
return { needsOnboarding: users.length === 0 };
|
|
607
|
+
});
|
|
608
|
+
|
|
609
|
+
const completeOnboarding = os.completeOnboarding.handler(
|
|
610
|
+
async ({ input }) => {
|
|
611
|
+
const { name, email, password } = input;
|
|
612
|
+
|
|
613
|
+
// Security check: only allow if no users exist
|
|
614
|
+
const existingUsers = await internalDb
|
|
615
|
+
.select({ id: schema.user.id })
|
|
616
|
+
.from(schema.user)
|
|
617
|
+
.limit(1);
|
|
618
|
+
|
|
619
|
+
if (existingUsers.length > 0) {
|
|
620
|
+
throw new ORPCError("FORBIDDEN", {
|
|
621
|
+
message: "Onboarding has already been completed.",
|
|
622
|
+
});
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
// Validate password against platform's password schema
|
|
626
|
+
const passwordValidation = passwordSchema.safeParse(password);
|
|
627
|
+
if (!passwordValidation.success) {
|
|
628
|
+
throw new ORPCError("BAD_REQUEST", {
|
|
629
|
+
message: passwordValidation.error.issues
|
|
630
|
+
.map((issue) => issue.message)
|
|
631
|
+
.join(", "),
|
|
632
|
+
});
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
// Create the first admin user
|
|
636
|
+
const userId = crypto.randomUUID();
|
|
637
|
+
const accountId = crypto.randomUUID();
|
|
638
|
+
const hashedPassword = await hashPassword(password);
|
|
639
|
+
const now = new Date();
|
|
640
|
+
|
|
641
|
+
await internalDb.transaction(async (tx) => {
|
|
642
|
+
// Create user
|
|
643
|
+
await tx.insert(schema.user).values({
|
|
644
|
+
id: userId,
|
|
645
|
+
email,
|
|
646
|
+
name,
|
|
647
|
+
emailVerified: true,
|
|
648
|
+
createdAt: now,
|
|
649
|
+
updatedAt: now,
|
|
650
|
+
});
|
|
651
|
+
|
|
652
|
+
// Create credential account
|
|
653
|
+
await tx.insert(schema.account).values({
|
|
654
|
+
id: accountId,
|
|
655
|
+
accountId: email,
|
|
656
|
+
providerId: "credential",
|
|
657
|
+
userId,
|
|
658
|
+
password: hashedPassword,
|
|
659
|
+
createdAt: now,
|
|
660
|
+
updatedAt: now,
|
|
661
|
+
});
|
|
662
|
+
|
|
663
|
+
// Assign admin role
|
|
664
|
+
await tx.insert(schema.userRole).values({
|
|
665
|
+
userId,
|
|
666
|
+
roleId: ADMIN_ROLE_ID,
|
|
667
|
+
});
|
|
668
|
+
});
|
|
669
|
+
|
|
670
|
+
return { success: true };
|
|
671
|
+
},
|
|
672
|
+
);
|
|
673
|
+
|
|
674
|
+
// ==========================================================================
|
|
675
|
+
// USER PROFILE ENDPOINTS
|
|
676
|
+
// ==========================================================================
|
|
677
|
+
|
|
678
|
+
const getCurrentUserProfile = os.getCurrentUserProfile.handler(
|
|
679
|
+
async ({ context }) => {
|
|
680
|
+
const user = context.user;
|
|
681
|
+
if (!isRealUser(user)) {
|
|
682
|
+
throw new ORPCError("UNAUTHORIZED", {
|
|
683
|
+
message: "Not authenticated",
|
|
684
|
+
});
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
// Get user data
|
|
688
|
+
const users = await internalDb
|
|
689
|
+
.select()
|
|
690
|
+
.from(schema.user)
|
|
691
|
+
.where(eq(schema.user.id, user.id))
|
|
692
|
+
.limit(1);
|
|
693
|
+
|
|
694
|
+
if (users.length === 0) {
|
|
695
|
+
throw new ORPCError("NOT_FOUND", {
|
|
696
|
+
message: "User not found",
|
|
697
|
+
});
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
// Check if user has a credential account
|
|
701
|
+
const accounts = await internalDb
|
|
702
|
+
.select()
|
|
703
|
+
.from(schema.account)
|
|
704
|
+
.where(
|
|
705
|
+
and(
|
|
706
|
+
eq(schema.account.userId, user.id),
|
|
707
|
+
eq(schema.account.providerId, "credential"),
|
|
708
|
+
),
|
|
709
|
+
)
|
|
710
|
+
.limit(1);
|
|
711
|
+
|
|
712
|
+
return {
|
|
713
|
+
id: users[0].id,
|
|
714
|
+
name: users[0].name,
|
|
715
|
+
email: users[0].email,
|
|
716
|
+
hasCredentialAccount: accounts.length > 0,
|
|
717
|
+
};
|
|
718
|
+
},
|
|
719
|
+
);
|
|
720
|
+
|
|
721
|
+
const updateCurrentUser = os.updateCurrentUser.handler(
|
|
722
|
+
async ({ input, context }) => {
|
|
723
|
+
const user = context.user;
|
|
724
|
+
if (!isRealUser(user)) {
|
|
725
|
+
throw new ORPCError("UNAUTHORIZED", {
|
|
726
|
+
message: "Not authenticated",
|
|
727
|
+
});
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
const { name, email } = input;
|
|
731
|
+
|
|
732
|
+
// If email is being updated, check if user has a credential account
|
|
733
|
+
if (email !== undefined) {
|
|
734
|
+
const accounts = await internalDb
|
|
735
|
+
.select()
|
|
736
|
+
.from(schema.account)
|
|
737
|
+
.where(
|
|
738
|
+
and(
|
|
739
|
+
eq(schema.account.userId, user.id),
|
|
740
|
+
eq(schema.account.providerId, "credential"),
|
|
741
|
+
),
|
|
742
|
+
)
|
|
743
|
+
.limit(1);
|
|
744
|
+
|
|
745
|
+
if (accounts.length === 0) {
|
|
746
|
+
throw new ORPCError("FORBIDDEN", {
|
|
747
|
+
message: "Email can only be updated for credential-based accounts.",
|
|
748
|
+
});
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
// Check email uniqueness
|
|
752
|
+
const existingUsers = await internalDb
|
|
753
|
+
.select({ id: schema.user.id })
|
|
754
|
+
.from(schema.user)
|
|
755
|
+
.where(eq(schema.user.email, email))
|
|
756
|
+
.limit(1);
|
|
757
|
+
|
|
758
|
+
if (existingUsers.length > 0 && existingUsers[0].id !== user.id) {
|
|
759
|
+
throw new ORPCError("CONFLICT", {
|
|
760
|
+
message: "A user with this email already exists.",
|
|
761
|
+
});
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
// Build update object
|
|
766
|
+
const updates: { name?: string; email?: string; updatedAt: Date } = {
|
|
767
|
+
updatedAt: new Date(),
|
|
768
|
+
};
|
|
769
|
+
if (name !== undefined) updates.name = name;
|
|
770
|
+
if (email !== undefined) updates.email = email;
|
|
771
|
+
|
|
772
|
+
await internalDb
|
|
773
|
+
.update(schema.user)
|
|
774
|
+
.set(updates)
|
|
775
|
+
.where(eq(schema.user.id, user.id));
|
|
776
|
+
|
|
777
|
+
// If email was updated, also update the credential account's accountId
|
|
778
|
+
if (email !== undefined) {
|
|
779
|
+
await internalDb
|
|
780
|
+
.update(schema.account)
|
|
781
|
+
.set({ accountId: email, updatedAt: new Date() })
|
|
782
|
+
.where(
|
|
783
|
+
and(
|
|
784
|
+
eq(schema.account.userId, user.id),
|
|
785
|
+
eq(schema.account.providerId, "credential"),
|
|
786
|
+
),
|
|
787
|
+
);
|
|
788
|
+
}
|
|
789
|
+
},
|
|
583
790
|
);
|
|
584
791
|
|
|
585
792
|
const getAnonymousAccessRules = os.getAnonymousAccessRules.handler(
|
|
@@ -587,9 +794,9 @@ export const createAuthRouter = (
|
|
|
587
794
|
const rolePerms = await internalDb
|
|
588
795
|
.select()
|
|
589
796
|
.from(schema.roleAccessRule)
|
|
590
|
-
.where(eq(schema.roleAccessRule.roleId,
|
|
797
|
+
.where(eq(schema.roleAccessRule.roleId, ANONYMOUS_ROLE_ID));
|
|
591
798
|
return rolePerms.map((rp) => rp.accessRuleId);
|
|
592
|
-
}
|
|
799
|
+
},
|
|
593
800
|
);
|
|
594
801
|
|
|
595
802
|
const filterUsersByAccessRule = os.filterUsersByAccessRule.handler(
|
|
@@ -605,18 +812,18 @@ export const createAuthRouter = (
|
|
|
605
812
|
.from(schema.userRole)
|
|
606
813
|
.innerJoin(
|
|
607
814
|
schema.roleAccessRule,
|
|
608
|
-
eq(schema.userRole.roleId, schema.roleAccessRule.roleId)
|
|
815
|
+
eq(schema.userRole.roleId, schema.roleAccessRule.roleId),
|
|
609
816
|
)
|
|
610
817
|
.where(
|
|
611
818
|
and(
|
|
612
819
|
inArray(schema.userRole.userId, userIds),
|
|
613
|
-
eq(schema.roleAccessRule.accessRuleId, accessRule)
|
|
614
|
-
)
|
|
820
|
+
eq(schema.roleAccessRule.accessRuleId, accessRule),
|
|
821
|
+
),
|
|
615
822
|
)
|
|
616
823
|
.groupBy(schema.userRole.userId);
|
|
617
824
|
|
|
618
825
|
return usersWithAccess.map((row) => row.userId);
|
|
619
|
-
}
|
|
826
|
+
},
|
|
620
827
|
);
|
|
621
828
|
|
|
622
829
|
// ==========================================================================
|
|
@@ -712,7 +919,7 @@ export const createAuthRouter = (
|
|
|
712
919
|
context.logger.info(`Created new user from ${providerId}: ${email}`);
|
|
713
920
|
|
|
714
921
|
return { userId, created: true };
|
|
715
|
-
}
|
|
922
|
+
},
|
|
716
923
|
);
|
|
717
924
|
|
|
718
925
|
const createSession = os.createSession.handler(async ({ input }) => {
|
|
@@ -753,7 +960,7 @@ export const createAuthRouter = (
|
|
|
753
960
|
// Check if credential strategy is enabled
|
|
754
961
|
const credentialEnabled = await getStrategyEnabled(
|
|
755
962
|
"credential",
|
|
756
|
-
configService
|
|
963
|
+
configService,
|
|
757
964
|
);
|
|
758
965
|
if (!credentialEnabled) {
|
|
759
966
|
throw new ORPCError("BAD_REQUEST", {
|
|
@@ -806,16 +1013,16 @@ export const createAuthRouter = (
|
|
|
806
1013
|
// Assign "users" role to new user
|
|
807
1014
|
await tx.insert(schema.userRole).values({
|
|
808
1015
|
userId,
|
|
809
|
-
roleId:
|
|
1016
|
+
roleId: USERS_ROLE_ID,
|
|
810
1017
|
});
|
|
811
1018
|
});
|
|
812
1019
|
|
|
813
1020
|
context.logger.info(
|
|
814
|
-
`[auth-backend] Admin created credential user: ${email}
|
|
1021
|
+
`[auth-backend] Admin created credential user: ${email}`,
|
|
815
1022
|
);
|
|
816
1023
|
|
|
817
1024
|
return { userId };
|
|
818
|
-
}
|
|
1025
|
+
},
|
|
819
1026
|
);
|
|
820
1027
|
|
|
821
1028
|
// ==========================================================================
|
|
@@ -833,8 +1040,8 @@ export const createAuthRouter = (
|
|
|
833
1040
|
.where(
|
|
834
1041
|
inArray(
|
|
835
1042
|
schema.applicationRole.applicationId,
|
|
836
|
-
apps.map((a) => a.id)
|
|
837
|
-
)
|
|
1043
|
+
apps.map((a) => a.id),
|
|
1044
|
+
),
|
|
838
1045
|
);
|
|
839
1046
|
|
|
840
1047
|
return apps.map((app) => ({
|
|
@@ -868,7 +1075,7 @@ export const createAuthRouter = (
|
|
|
868
1075
|
const now = new Date();
|
|
869
1076
|
|
|
870
1077
|
// Default role for all applications
|
|
871
|
-
const defaultRole =
|
|
1078
|
+
const defaultRole = APPLICATIONS_ROLE_ID;
|
|
872
1079
|
|
|
873
1080
|
await internalDb.transaction(async (tx) => {
|
|
874
1081
|
// Create application
|
|
@@ -890,7 +1097,7 @@ export const createAuthRouter = (
|
|
|
890
1097
|
});
|
|
891
1098
|
|
|
892
1099
|
context.logger.info(
|
|
893
|
-
`[auth-backend] Created application: ${name} (${id})
|
|
1100
|
+
`[auth-backend] Created application: ${name} (${id})`,
|
|
894
1101
|
);
|
|
895
1102
|
|
|
896
1103
|
return {
|
|
@@ -904,7 +1111,7 @@ export const createAuthRouter = (
|
|
|
904
1111
|
},
|
|
905
1112
|
secret: `ck_${id}_${secret}`, // Full secret - only shown once!
|
|
906
1113
|
};
|
|
907
|
-
}
|
|
1114
|
+
},
|
|
908
1115
|
);
|
|
909
1116
|
|
|
910
1117
|
const updateApplication = os.updateApplication.handler(async ({ input }) => {
|
|
@@ -953,7 +1160,7 @@ export const createAuthRouter = (
|
|
|
953
1160
|
roles.map((roleId) => ({
|
|
954
1161
|
applicationId: id,
|
|
955
1162
|
roleId,
|
|
956
|
-
}))
|
|
1163
|
+
})),
|
|
957
1164
|
);
|
|
958
1165
|
}
|
|
959
1166
|
}
|
|
@@ -982,7 +1189,7 @@ export const createAuthRouter = (
|
|
|
982
1189
|
.where(eq(schema.application.id, id));
|
|
983
1190
|
|
|
984
1191
|
context.logger.info(`[auth-backend] Deleted application: ${id}`);
|
|
985
|
-
}
|
|
1192
|
+
},
|
|
986
1193
|
);
|
|
987
1194
|
|
|
988
1195
|
const regenerateApplicationSecret = os.regenerateApplicationSecret.handler(
|
|
@@ -1009,11 +1216,11 @@ export const createAuthRouter = (
|
|
|
1009
1216
|
.where(eq(schema.application.id, id));
|
|
1010
1217
|
|
|
1011
1218
|
context.logger.info(
|
|
1012
|
-
`[auth-backend] Regenerated secret for application: ${id}
|
|
1219
|
+
`[auth-backend] Regenerated secret for application: ${id}`,
|
|
1013
1220
|
);
|
|
1014
1221
|
|
|
1015
1222
|
return { secret: `ck_${id}_${secret}` };
|
|
1016
|
-
}
|
|
1223
|
+
},
|
|
1017
1224
|
);
|
|
1018
1225
|
|
|
1019
1226
|
// ==========================================================================
|
|
@@ -1158,10 +1365,10 @@ export const createAuthRouter = (
|
|
|
1158
1365
|
.where(
|
|
1159
1366
|
and(
|
|
1160
1367
|
eq(schema.userTeam.userId, input.userId),
|
|
1161
|
-
eq(schema.userTeam.teamId, input.teamId)
|
|
1162
|
-
)
|
|
1368
|
+
eq(schema.userTeam.teamId, input.teamId),
|
|
1369
|
+
),
|
|
1163
1370
|
);
|
|
1164
|
-
}
|
|
1371
|
+
},
|
|
1165
1372
|
);
|
|
1166
1373
|
|
|
1167
1374
|
const addTeamManager = os.addTeamManager.handler(async ({ input }) => {
|
|
@@ -1177,8 +1384,8 @@ export const createAuthRouter = (
|
|
|
1177
1384
|
.where(
|
|
1178
1385
|
and(
|
|
1179
1386
|
eq(schema.teamManager.userId, input.userId),
|
|
1180
|
-
eq(schema.teamManager.teamId, input.teamId)
|
|
1181
|
-
)
|
|
1387
|
+
eq(schema.teamManager.teamId, input.teamId),
|
|
1388
|
+
),
|
|
1182
1389
|
);
|
|
1183
1390
|
});
|
|
1184
1391
|
|
|
@@ -1189,13 +1396,13 @@ export const createAuthRouter = (
|
|
|
1189
1396
|
.from(schema.resourceTeamAccess)
|
|
1190
1397
|
.innerJoin(
|
|
1191
1398
|
schema.team,
|
|
1192
|
-
eq(schema.resourceTeamAccess.teamId, schema.team.id)
|
|
1399
|
+
eq(schema.resourceTeamAccess.teamId, schema.team.id),
|
|
1193
1400
|
)
|
|
1194
1401
|
.where(
|
|
1195
1402
|
and(
|
|
1196
1403
|
eq(schema.resourceTeamAccess.resourceType, input.resourceType),
|
|
1197
|
-
eq(schema.resourceTeamAccess.resourceId, input.resourceId)
|
|
1198
|
-
)
|
|
1404
|
+
eq(schema.resourceTeamAccess.resourceId, input.resourceId),
|
|
1405
|
+
),
|
|
1199
1406
|
);
|
|
1200
1407
|
return rows.map((r) => ({
|
|
1201
1408
|
teamId: r.resource_team_access.teamId,
|
|
@@ -1203,7 +1410,7 @@ export const createAuthRouter = (
|
|
|
1203
1410
|
canRead: r.resource_team_access.canRead,
|
|
1204
1411
|
canManage: r.resource_team_access.canManage,
|
|
1205
1412
|
}));
|
|
1206
|
-
}
|
|
1413
|
+
},
|
|
1207
1414
|
);
|
|
1208
1415
|
|
|
1209
1416
|
const setResourceTeamAccess = os.setResourceTeamAccess.handler(
|
|
@@ -1229,7 +1436,7 @@ export const createAuthRouter = (
|
|
|
1229
1436
|
canManage: canManage ?? false,
|
|
1230
1437
|
},
|
|
1231
1438
|
});
|
|
1232
|
-
}
|
|
1439
|
+
},
|
|
1233
1440
|
);
|
|
1234
1441
|
|
|
1235
1442
|
const removeResourceTeamAccess = os.removeResourceTeamAccess.handler(
|
|
@@ -1240,10 +1447,10 @@ export const createAuthRouter = (
|
|
|
1240
1447
|
and(
|
|
1241
1448
|
eq(schema.resourceTeamAccess.resourceType, input.resourceType),
|
|
1242
1449
|
eq(schema.resourceTeamAccess.resourceId, input.resourceId),
|
|
1243
|
-
eq(schema.resourceTeamAccess.teamId, input.teamId)
|
|
1244
|
-
)
|
|
1450
|
+
eq(schema.resourceTeamAccess.teamId, input.teamId),
|
|
1451
|
+
),
|
|
1245
1452
|
);
|
|
1246
|
-
}
|
|
1453
|
+
},
|
|
1247
1454
|
);
|
|
1248
1455
|
|
|
1249
1456
|
// Resource-level access settings
|
|
@@ -1255,12 +1462,12 @@ export const createAuthRouter = (
|
|
|
1255
1462
|
.where(
|
|
1256
1463
|
and(
|
|
1257
1464
|
eq(schema.resourceAccessSettings.resourceType, input.resourceType),
|
|
1258
|
-
eq(schema.resourceAccessSettings.resourceId, input.resourceId)
|
|
1259
|
-
)
|
|
1465
|
+
eq(schema.resourceAccessSettings.resourceId, input.resourceId),
|
|
1466
|
+
),
|
|
1260
1467
|
)
|
|
1261
1468
|
.limit(1);
|
|
1262
1469
|
return { teamOnly: rows[0]?.teamOnly ?? false };
|
|
1263
|
-
}
|
|
1470
|
+
},
|
|
1264
1471
|
);
|
|
1265
1472
|
|
|
1266
1473
|
const setResourceAccessSettings = os.setResourceAccessSettings.handler(
|
|
@@ -1276,7 +1483,7 @@ export const createAuthRouter = (
|
|
|
1276
1483
|
],
|
|
1277
1484
|
set: { teamOnly },
|
|
1278
1485
|
});
|
|
1279
|
-
}
|
|
1486
|
+
},
|
|
1280
1487
|
);
|
|
1281
1488
|
|
|
1282
1489
|
// S2S Endpoints for middleware
|
|
@@ -1297,8 +1504,8 @@ export const createAuthRouter = (
|
|
|
1297
1504
|
.where(
|
|
1298
1505
|
and(
|
|
1299
1506
|
eq(schema.resourceTeamAccess.resourceType, resourceType),
|
|
1300
|
-
eq(schema.resourceTeamAccess.resourceId, resourceId)
|
|
1301
|
-
)
|
|
1507
|
+
eq(schema.resourceTeamAccess.resourceId, resourceId),
|
|
1508
|
+
),
|
|
1302
1509
|
);
|
|
1303
1510
|
|
|
1304
1511
|
// No grants = global access applies
|
|
@@ -1311,8 +1518,8 @@ export const createAuthRouter = (
|
|
|
1311
1518
|
.where(
|
|
1312
1519
|
and(
|
|
1313
1520
|
eq(schema.resourceAccessSettings.resourceType, resourceType),
|
|
1314
|
-
eq(schema.resourceAccessSettings.resourceId, resourceId)
|
|
1315
|
-
)
|
|
1521
|
+
eq(schema.resourceAccessSettings.resourceId, resourceId),
|
|
1522
|
+
),
|
|
1316
1523
|
)
|
|
1317
1524
|
.limit(1);
|
|
1318
1525
|
const isTeamOnly = settingsRows[0]?.teamOnly ?? false;
|
|
@@ -1339,10 +1546,10 @@ export const createAuthRouter = (
|
|
|
1339
1546
|
|
|
1340
1547
|
const field = action === "manage" ? "canManage" : "canRead";
|
|
1341
1548
|
const hasAccess = grants.some(
|
|
1342
|
-
(g) => userTeamIds.has(g.teamId) && g[field]
|
|
1549
|
+
(g) => userTeamIds.has(g.teamId) && g[field],
|
|
1343
1550
|
);
|
|
1344
1551
|
return { hasAccess };
|
|
1345
|
-
}
|
|
1552
|
+
},
|
|
1346
1553
|
);
|
|
1347
1554
|
|
|
1348
1555
|
const getAccessibleResourceIds = os.getAccessibleResourceIds.handler(
|
|
@@ -1364,8 +1571,8 @@ export const createAuthRouter = (
|
|
|
1364
1571
|
.where(
|
|
1365
1572
|
and(
|
|
1366
1573
|
eq(schema.resourceTeamAccess.resourceType, resourceType),
|
|
1367
|
-
inArray(schema.resourceTeamAccess.resourceId, resourceIds)
|
|
1368
|
-
)
|
|
1574
|
+
inArray(schema.resourceTeamAccess.resourceId, resourceIds),
|
|
1575
|
+
),
|
|
1369
1576
|
);
|
|
1370
1577
|
|
|
1371
1578
|
// Get resource-level settings for teamOnly
|
|
@@ -1375,11 +1582,11 @@ export const createAuthRouter = (
|
|
|
1375
1582
|
.where(
|
|
1376
1583
|
and(
|
|
1377
1584
|
eq(schema.resourceAccessSettings.resourceType, resourceType),
|
|
1378
|
-
inArray(schema.resourceAccessSettings.resourceId, resourceIds)
|
|
1379
|
-
)
|
|
1585
|
+
inArray(schema.resourceAccessSettings.resourceId, resourceIds),
|
|
1586
|
+
),
|
|
1380
1587
|
);
|
|
1381
1588
|
const teamOnlyByResource = new Map(
|
|
1382
|
-
settingsRows.map((s) => [s.resourceId, s.teamOnly])
|
|
1589
|
+
settingsRows.map((s) => [s.resourceId, s.teamOnly]),
|
|
1383
1590
|
);
|
|
1384
1591
|
|
|
1385
1592
|
// Get user's teams
|
|
@@ -1414,10 +1621,10 @@ export const createAuthRouter = (
|
|
|
1414
1621
|
const isTeamOnly = teamOnlyByResource.get(id) ?? false;
|
|
1415
1622
|
if (!isTeamOnly && hasGlobalAccess) return true;
|
|
1416
1623
|
return resourceGrants.some(
|
|
1417
|
-
(g) => userTeamIds.has(g.teamId) && g[field]
|
|
1624
|
+
(g) => userTeamIds.has(g.teamId) && g[field],
|
|
1418
1625
|
);
|
|
1419
1626
|
});
|
|
1420
|
-
}
|
|
1627
|
+
},
|
|
1421
1628
|
);
|
|
1422
1629
|
|
|
1423
1630
|
const deleteResourceGrants = os.deleteResourceGrants.handler(
|
|
@@ -1427,10 +1634,10 @@ export const createAuthRouter = (
|
|
|
1427
1634
|
.where(
|
|
1428
1635
|
and(
|
|
1429
1636
|
eq(schema.resourceTeamAccess.resourceType, input.resourceType),
|
|
1430
|
-
eq(schema.resourceTeamAccess.resourceId, input.resourceId)
|
|
1431
|
-
)
|
|
1637
|
+
eq(schema.resourceTeamAccess.resourceId, input.resourceId),
|
|
1638
|
+
),
|
|
1432
1639
|
);
|
|
1433
|
-
}
|
|
1640
|
+
},
|
|
1434
1641
|
);
|
|
1435
1642
|
|
|
1436
1643
|
return os.router({
|
|
@@ -1450,6 +1657,10 @@ export const createAuthRouter = (
|
|
|
1450
1657
|
getRegistrationSchema,
|
|
1451
1658
|
getRegistrationStatus,
|
|
1452
1659
|
setRegistrationStatus,
|
|
1660
|
+
getOnboardingStatus,
|
|
1661
|
+
completeOnboarding,
|
|
1662
|
+
getCurrentUserProfile,
|
|
1663
|
+
updateCurrentUser,
|
|
1453
1664
|
getAnonymousAccessRules,
|
|
1454
1665
|
getUserById,
|
|
1455
1666
|
filterUsersByAccessRule,
|