@checkstack/auth-backend 0.2.2 โ†’ 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 CHANGED
@@ -1,5 +1,17 @@
1
1
  # @checkstack/auth-backend
2
2
 
3
+ ## 0.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 993d81a: Export role ID constants (USERS_ROLE_ID, ANONYMOUS_ROLE_ID, APPLICATIONS_ROLE_ID) for consistent usage across the codebase. Added protection to prevent deleting users with the admin role.
8
+ - df6ac7b: Added onboarding flow and user profile
9
+
10
+ ### Patch Changes
11
+
12
+ - Updated dependencies [df6ac7b]
13
+ - @checkstack/auth-common@0.4.0
14
+
3
15
  ## 0.2.2
4
16
 
5
17
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@checkstack/auth-backend",
3
- "version": "0.2.2",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "main": "src/index.ts",
6
6
  "scripts": {
package/src/index.ts CHANGED
@@ -17,13 +17,13 @@ import {
17
17
  } from "@checkstack/auth-common";
18
18
  import { NotificationApi } from "@checkstack/notification-common";
19
19
  import * as schema from "./schema";
20
- import { eq, inArray, or } from "drizzle-orm";
20
+ import { eq, inArray } from "drizzle-orm";
21
21
  import { NodePgDatabase } from "drizzle-orm/node-postgres";
22
22
  import { User } from "better-auth/types";
23
- import { hashPassword, verifyPassword } from "better-auth/crypto";
23
+ import { verifyPassword } from "better-auth/crypto";
24
24
  import { createExtensionPoint } from "@checkstack/backend-api";
25
25
  import { enrichUser } from "./utils/user";
26
- import { createAuthRouter } from "./router";
26
+ import { ADMIN_ROLE_ID, createAuthRouter } from "./router";
27
27
  import { validateStrategySchema } from "./utils/validate-schema";
28
28
  import {
29
29
  strategyMetaConfigV1,
@@ -43,7 +43,7 @@ export interface BetterAuthExtensionPoint {
43
43
 
44
44
  export const betterAuthExtensionPoint =
45
45
  createExtensionPoint<BetterAuthExtensionPoint>(
46
- "auth.betterAuthExtensionPoint"
46
+ "auth.betterAuthExtensionPoint",
47
47
  );
48
48
 
49
49
  /**
@@ -101,7 +101,7 @@ async function syncAccessRulesToDb({
101
101
 
102
102
  for (const rule of accessRules) {
103
103
  const hasAccess = adminRoleAccessRules.some(
104
- (rp) => rp.accessRuleId === rule.id
104
+ (rp) => rp.accessRuleId === rule.id,
105
105
  );
106
106
 
107
107
  if (!hasAccess) {
@@ -126,12 +126,12 @@ async function syncAccessRulesToDb({
126
126
  const registeredIds = new Set(accessRules.map((r) => r.id));
127
127
  const allDbAccessRules = await database.select().from(schema.accessRule);
128
128
  const orphanAccessRules = allDbAccessRules.filter(
129
- (p) => !registeredIds.has(p.id)
129
+ (p) => !registeredIds.has(p.id),
130
130
  );
131
131
 
132
132
  if (orphanAccessRules.length > 0) {
133
133
  logger.debug(
134
- `๐Ÿงน Removing ${orphanAccessRules.length} orphan access rule(s)...`
134
+ `๐Ÿงน Removing ${orphanAccessRules.length} orphan access rule(s)...`,
135
135
  );
136
136
  for (const orphan of orphanAccessRules) {
137
137
  // Delete role_access_rule entries first (FK doesn't cascade)
@@ -176,7 +176,7 @@ async function syncAuthenticatedDefaultAccessRulesToUsersRole({
176
176
  }) {
177
177
  // Debug: log all access rules with their isDefault status
178
178
  logger.debug(
179
- `[DEBUG] All access rules received (${accessRules.length} total):`
179
+ `[DEBUG] All access rules received (${accessRules.length} total):`,
180
180
  );
181
181
  for (const r of accessRules) {
182
182
  logger.debug(` -> ${r.id}: isDefault=${r.isDefault}`);
@@ -184,11 +184,11 @@ async function syncAuthenticatedDefaultAccessRulesToUsersRole({
184
184
 
185
185
  const defaultRules = accessRules.filter((r) => r.isDefault);
186
186
  logger.debug(
187
- `๐Ÿ‘ฅ Found ${defaultRules.length} authenticated default access rules to sync to users role`
187
+ `๐Ÿ‘ฅ Found ${defaultRules.length} authenticated default access rules to sync to users role`,
188
188
  );
189
189
  if (defaultRules.length === 0) {
190
190
  logger.debug(
191
- ` -> No authenticated default access rules found, skipping sync`
191
+ ` -> No authenticated default access rules found, skipping sync`,
192
192
  );
193
193
  return;
194
194
  }
@@ -213,7 +213,7 @@ async function syncAuthenticatedDefaultAccessRulesToUsersRole({
213
213
  }
214
214
 
215
215
  const hasAccess = usersRoleAccessRules.some(
216
- (rp) => rp.accessRuleId === rule.id
216
+ (rp) => rp.accessRuleId === rule.id,
217
217
  );
218
218
 
219
219
  if (!hasAccess) {
@@ -222,7 +222,7 @@ async function syncAuthenticatedDefaultAccessRulesToUsersRole({
222
222
  accessRuleId: rule.id,
223
223
  });
224
224
  logger.debug(
225
- ` -> Assigned authenticated default access rule ${rule.id} to users role`
225
+ ` -> Assigned authenticated default access rule ${rule.id} to users role`,
226
226
  );
227
227
  }
228
228
  }
@@ -243,7 +243,7 @@ async function syncPublicDefaultAccessRulesToAnonymousRole({
243
243
  }) {
244
244
  const publicDefaults = accessRules.filter((r) => r.isPublic);
245
245
  logger.debug(
246
- `๐ŸŒ Found ${publicDefaults.length} public default access rules to sync to anonymous role`
246
+ `๐ŸŒ Found ${publicDefaults.length} public default access rules to sync to anonymous role`,
247
247
  );
248
248
  if (publicDefaults.length === 0) {
249
249
  logger.debug(` -> No public default access rules found, skipping sync`);
@@ -270,7 +270,7 @@ async function syncPublicDefaultAccessRulesToAnonymousRole({
270
270
  }
271
271
 
272
272
  const hasAccess = anonymousRoleAccessRules.some(
273
- (rp) => rp.accessRuleId === rule.id
273
+ (rp) => rp.accessRuleId === rule.id,
274
274
  );
275
275
 
276
276
  if (!hasAccess) {
@@ -279,7 +279,7 @@ async function syncPublicDefaultAccessRulesToAnonymousRole({
279
279
  accessRuleId: rule.id,
280
280
  });
281
281
  logger.debug(
282
- ` -> Assigned public default access rule ${rule.id} to anonymous role`
282
+ ` -> Assigned public default access rule ${rule.id} to anonymous role`,
283
283
  );
284
284
  }
285
285
  }
@@ -317,7 +317,7 @@ export default createBackendPlugin({
317
317
  const message =
318
318
  error instanceof Error ? error.message : String(error);
319
319
  throw new Error(
320
- `Failed to register authentication strategy "${s.id}": ${message}`
320
+ `Failed to register authentication strategy "${s.id}": ${message}`,
321
321
  );
322
322
  }
323
323
  strategies.push(s);
@@ -385,7 +385,7 @@ export default createBackendPlugin({
385
385
  .select({ roleId: schema.applicationRole.roleId })
386
386
  .from(schema.applicationRole)
387
387
  .where(
388
- eq(schema.applicationRole.applicationId, applicationId)
388
+ eq(schema.applicationRole.applicationId, applicationId),
389
389
  );
390
390
 
391
391
  const roleIds = appRoles.map((r) => r.roleId);
@@ -410,7 +410,7 @@ export default createBackendPlugin({
410
410
  .select({ teamId: schema.applicationTeam.teamId })
411
411
  .from(schema.applicationTeam)
412
412
  .where(
413
- eq(schema.applicationTeam.applicationId, applicationId)
413
+ eq(schema.applicationTeam.applicationId, applicationId),
414
414
  );
415
415
  const teamIds = appTeams.map((t) => t.teamId);
416
416
 
@@ -470,12 +470,12 @@ export default createBackendPlugin({
470
470
  const initializeBetterAuth = async () => {
471
471
  const socialProviders: Record<string, unknown> = {};
472
472
  logger.debug(
473
- `[auth-backend] Processing ${strategies.length} strategies...`
473
+ `[auth-backend] Processing ${strategies.length} strategies...`,
474
474
  );
475
475
 
476
476
  for (const strategy of strategies) {
477
477
  logger.debug(
478
- `[auth-backend] -> Processing auth strategy: ${strategy.id}`
478
+ `[auth-backend] -> Processing auth strategy: ${strategy.id}`,
479
479
  );
480
480
 
481
481
  // Skip credential strategy - it's built into better-auth
@@ -486,20 +486,20 @@ export default createBackendPlugin({
486
486
  strategy.id,
487
487
  strategy.configSchema,
488
488
  strategy.configVersion,
489
- strategy.migrations
489
+ strategy.migrations,
490
490
  );
491
491
 
492
492
  // Check if strategy is enabled from meta config
493
493
  const metaConfig = await config.get(
494
494
  `${strategy.id}.meta`,
495
495
  strategyMetaConfigV1,
496
- STRATEGY_META_CONFIG_VERSION
496
+ STRATEGY_META_CONFIG_VERSION,
497
497
  );
498
498
  const enabled = metaConfig?.enabled ?? false;
499
499
 
500
500
  if (!enabled) {
501
501
  logger.debug(
502
- `[auth-backend] -> Strategy ${strategy.id} is disabled, skipping`
502
+ `[auth-backend] -> Strategy ${strategy.id} is disabled, skipping`,
503
503
  );
504
504
  continue;
505
505
  }
@@ -508,23 +508,23 @@ export default createBackendPlugin({
508
508
  logger.debug(
509
509
  `[auth-backend] -> Config keys for ${
510
510
  strategy.id
511
- }: ${Object.keys(strategyConfig || {}).join(", ")}`
511
+ }: ${Object.keys(strategyConfig || {}).join(", ")}`,
512
512
  );
513
513
  socialProviders[strategy.id] = strategyConfig;
514
514
  logger.debug(
515
- `[auth-backend] -> โœ… Added ${strategy.id} to socialProviders`
515
+ `[auth-backend] -> โœ… Added ${strategy.id} to socialProviders`,
516
516
  );
517
517
  }
518
518
 
519
519
  // Check if credential strategy is enabled from meta config
520
520
  const credentialStrategy = strategies.find(
521
- (s) => s.id === "credential"
521
+ (s) => s.id === "credential",
522
522
  );
523
523
  const credentialMetaConfig = credentialStrategy
524
524
  ? await config.get(
525
525
  "credential.meta",
526
526
  strategyMetaConfigV1,
527
- STRATEGY_META_CONFIG_VERSION
527
+ STRATEGY_META_CONFIG_VERSION,
528
528
  )
529
529
  : undefined;
530
530
  // Default to true on fresh installs (no meta config)
@@ -534,7 +534,7 @@ export default createBackendPlugin({
534
534
  const platformRegistrationConfig = await config.get(
535
535
  PLATFORM_REGISTRATION_CONFIG_ID,
536
536
  platformRegistrationConfigV1,
537
- PLATFORM_REGISTRATION_CONFIG_VERSION
537
+ PLATFORM_REGISTRATION_CONFIG_VERSION,
538
538
  );
539
539
  const registrationAllowed =
540
540
  platformRegistrationConfig?.allowRegistration ?? true;
@@ -542,7 +542,7 @@ export default createBackendPlugin({
542
542
  logger.debug(
543
543
  `[auth-backend] Initializing Better Auth with ${
544
544
  Object.keys(socialProviders).length
545
- } social providers: ${Object.keys(socialProviders).join(", ")}`
545
+ } social providers: ${Object.keys(socialProviders).join(", ")}`,
546
546
  );
547
547
 
548
548
  return betterAuth({
@@ -579,7 +579,7 @@ export default createBackendPlugin({
579
579
  });
580
580
 
581
581
  logger.debug(
582
- `[auth-backend] Password reset email sent to user: ${user.id}`
582
+ `[auth-backend] Password reset email sent to user: ${user.id}`,
583
583
  );
584
584
  },
585
585
  resetPasswordTokenExpiresIn: 60 * 60, // 1 hour
@@ -611,12 +611,12 @@ export default createBackendPlugin({
611
611
  roleId: "users",
612
612
  });
613
613
  logger.debug(
614
- `[auth-backend] Assigned 'users' role to new user: ${user.id}`
614
+ `[auth-backend] Assigned 'users' role to new user: ${user.id}`,
615
615
  );
616
616
  } catch (error) {
617
617
  // Role might not exist yet on first boot, that's okay
618
618
  logger.debug(
619
- `[auth-backend] Could not assign 'users' role to ${user.id}: ${error}`
619
+ `[auth-backend] Could not assign 'users' role to ${user.id}: ${error}`,
620
620
  );
621
621
  }
622
622
  },
@@ -632,7 +632,7 @@ export default createBackendPlugin({
632
632
  // Reload function for dynamic auth config changes
633
633
  const reloadAuth = async () => {
634
634
  logger.info(
635
- "[auth-backend] Reloading authentication configuration..."
635
+ "[auth-backend] Reloading authentication configuration...",
636
636
  );
637
637
  auth = await initializeBetterAuth();
638
638
  logger.info("[auth-backend] โœ… Authentication reloaded successfully");
@@ -643,10 +643,10 @@ export default createBackendPlugin({
643
643
  const adminRole = await database
644
644
  .select()
645
645
  .from(schema.role)
646
- .where(eq(schema.role.id, "admin"));
646
+ .where(eq(schema.role.id, ADMIN_ROLE_ID));
647
647
  if (adminRole.length === 0) {
648
648
  await database.insert(schema.role).values({
649
- id: "admin",
649
+ id: ADMIN_ROLE_ID,
650
650
  name: "Administrators",
651
651
  isSystem: true,
652
652
  });
@@ -706,7 +706,7 @@ export default createBackendPlugin({
706
706
  strategyRegistry,
707
707
  reloadAuth,
708
708
  config,
709
- accessRuleRegistry
709
+ accessRuleRegistry,
710
710
  );
711
711
  rpc.registerRouter(authRouter, authContract);
712
712
 
@@ -714,50 +714,7 @@ export default createBackendPlugin({
714
714
  rpc.registerHttpHandler((req: Request) => auth!.handler(req));
715
715
 
716
716
  // All auth management endpoints are now via oRPC (see ./router.ts)
717
-
718
- // 5. Idempotent Admin User Seeding (roles already seeded above)
719
- const adminId = "initial-admin-id";
720
- const existingAdmin = await database
721
- .select()
722
- .from(schema.user)
723
- .where(
724
- or(
725
- eq(schema.user.email, "admin@checkstack.dev"),
726
- eq(schema.user.id, adminId)
727
- )
728
- );
729
-
730
- // Skip seeding if user exists by either email or ID
731
- if (existingAdmin.length === 0) {
732
- await database.insert(schema.user).values({
733
- id: adminId,
734
- name: "Admin",
735
- email: "admin@checkstack.dev",
736
- emailVerified: true,
737
- createdAt: new Date(),
738
- updatedAt: new Date(),
739
- });
740
-
741
- const hashedAdminPassword = await hashPassword("admin");
742
- await database.insert(schema.account).values({
743
- id: "initial-admin-account-id",
744
- accountId: "admin@checkstack.dev",
745
- providerId: "credential",
746
- userId: adminId,
747
- password: hashedAdminPassword,
748
- createdAt: new Date(),
749
- updatedAt: new Date(),
750
- });
751
-
752
- await database.insert(schema.userRole).values({
753
- userId: adminId,
754
- roleId: "admin",
755
- });
756
-
757
- logger.info(
758
- " -> Created initial admin user (admin@checkstack.dev : admin)"
759
- );
760
- }
717
+ // Note: Admin user seeding removed - handled via onboarding flow
761
718
 
762
719
  // Register command palette commands
763
720
  registerSearchProvider({
@@ -810,7 +767,7 @@ export default createBackendPlugin({
810
767
  // This is critical because during init, other plugins haven't registered yet
811
768
  const allAccessRules = accessRuleRegistry.getAccessRules();
812
769
  logger.debug(
813
- `[auth-backend] afterPluginsReady: syncing ${allAccessRules.length} access rules from all plugins`
770
+ `[auth-backend] afterPluginsReady: syncing ${allAccessRules.length} access rules from all plugins`,
814
771
  );
815
772
  await syncAccessRulesToDb({
816
773
  database: database as NodePgDatabase<typeof schema>,
@@ -834,7 +791,7 @@ export default createBackendPlugin({
834
791
  mode: "work-queue",
835
792
  workerGroup: "access-rule-db-sync",
836
793
  maxRetries: 5,
837
- }
794
+ },
838
795
  );
839
796
 
840
797
  // Subscribe to plugin deregistered hook for access rule cleanup
@@ -843,7 +800,7 @@ export default createBackendPlugin({
843
800
  coreHooks.pluginDeregistered,
844
801
  async ({ pluginId }) => {
845
802
  logger.debug(
846
- `[auth-backend] Cleaning up access rules for deregistered plugin: ${pluginId}`
803
+ `[auth-backend] Cleaning up access rules for deregistered plugin: ${pluginId}`,
847
804
  );
848
805
 
849
806
  // Delete all access rules with this plugin's prefix
@@ -851,7 +808,7 @@ export default createBackendPlugin({
851
808
  .select()
852
809
  .from(schema.accessRule);
853
810
  const pluginAccessRules = allDbAccessRules.filter((p) =>
854
- p.id.startsWith(`${pluginId}.`)
811
+ p.id.startsWith(`${pluginId}.`),
855
812
  );
856
813
 
857
814
  for (const perm of pluginAccessRules) {
@@ -867,14 +824,14 @@ export default createBackendPlugin({
867
824
  }
868
825
 
869
826
  logger.debug(
870
- `[auth-backend] Cleaned up ${pluginAccessRules.length} access rules for ${pluginId}`
827
+ `[auth-backend] Cleaned up ${pluginAccessRules.length} access rules for ${pluginId}`,
871
828
  );
872
829
  },
873
830
  {
874
831
  mode: "work-queue",
875
832
  workerGroup: "access-rule-cleanup",
876
833
  maxRetries: 3,
877
- }
834
+ },
878
835
  );
879
836
 
880
837
  logger.debug("โœ… Auth Backend afterPluginsReady complete.");