@checkstack/auth-backend 0.4.10 → 0.4.12

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,30 @@
1
1
  # @checkstack/auth-backend
2
2
 
3
+ ## 0.4.12
4
+
5
+ ### Patch Changes
6
+
7
+ - c0c0ed2: Introduce generic "Login Flows" to allow authentication strategies to define their own interaction patterns (form, redirect, or oauth) during registration. This fixes an issue where LDAP login attempts were incorrectly routed through the standard social login flow by instead providing a dedicated credential collection form for LDAP.
8
+ - c0c0ed2: Refactor manual session creation to use a secure, bridged oRPC endpoint. This ensures that custom authentication strategies (LDAP, SAML) leverage Better-Auth's native session establishment utilities, including cryptographic signing and reliable cookie attribute management.
9
+ - Updated dependencies [c0c0ed2]
10
+ - Updated dependencies [c0c0ed2]
11
+ - @checkstack/backend-api@0.9.0
12
+ - @checkstack/auth-common@0.6.0
13
+ - @checkstack/command-backend@0.1.14
14
+
15
+ ## 0.4.11
16
+
17
+ ### Patch Changes
18
+
19
+ - 67158e2: Standardize package metadata, unify AJV versions to 8.18.0, and enforce monorepo architecture rules via updated ESLint configuration. This ensures consistent package discovery and runtime dependency safety across the platform.
20
+ - b839ccb: Security: Hardened production Docker image by upgrading Alpine system libraries, migrating to Drizzle beta (v1.0.0-beta.21), and implementing aggressive binary pruning to eliminate vulnerable build-time tools (esbuild/drizzle-kit).
21
+ - Updated dependencies [67158e2]
22
+ - @checkstack/auth-common@0.5.7
23
+ - @checkstack/backend-api@0.8.2
24
+ - @checkstack/command-backend@0.1.13
25
+ - @checkstack/common@0.6.4
26
+ - @checkstack/notification-common@0.2.7
27
+
3
28
  ## 0.4.10
4
29
 
5
30
  ### Patch Changes
package/package.json CHANGED
@@ -1,8 +1,11 @@
1
1
  {
2
2
  "name": "@checkstack/auth-backend",
3
- "version": "0.4.10",
3
+ "version": "0.4.12",
4
4
  "type": "module",
5
5
  "main": "src/index.ts",
6
+ "checkstack": {
7
+ "type": "backend"
8
+ },
6
9
  "scripts": {
7
10
  "typecheck": "tsc --noEmit",
8
11
  "generate": "drizzle-kit generate",
@@ -11,24 +14,23 @@
11
14
  "test": "bun test"
12
15
  },
13
16
  "dependencies": {
14
- "@checkstack/auth-common": "0.5.5",
15
- "@checkstack/backend-api": "0.8.0",
16
- "@checkstack/notification-common": "0.2.5",
17
- "@checkstack/command-backend": "0.1.11",
17
+ "@checkstack/auth-common": "0.5.7",
18
+ "@checkstack/backend-api": "0.8.2",
19
+ "@checkstack/notification-common": "0.2.7",
20
+ "@checkstack/command-backend": "0.1.13",
18
21
  "better-auth": "^1.4.7",
19
- "drizzle-orm": "^0.45.1",
20
- "hono": "^4.0.0",
22
+ "drizzle-orm": "^0.45.0",
23
+ "hono": "^4.12.14",
21
24
  "jose": "^6.1.3",
22
25
  "zod": "^4.2.1",
23
- "@checkstack/common": "0.6.2"
26
+ "@checkstack/common": "0.6.4",
27
+ "@orpc/server": "^1.13.2"
24
28
  },
25
29
  "devDependencies": {
26
- "@checkstack/drizzle-helper": "0.0.3",
27
- "@checkstack/scripts": "0.1.1",
28
- "@checkstack/tsconfig": "0.0.3",
29
- "@orpc/server": "^1.13.2",
30
+ "@checkstack/drizzle-helper": "0.0.4",
31
+ "@checkstack/scripts": "0.1.2",
32
+ "@checkstack/tsconfig": "0.0.4",
30
33
  "@types/node": "^20.0.0",
31
- "drizzle-kit": "^0.31.8",
32
34
  "typescript": "^5.0.0"
33
35
  }
34
36
  }
package/src/index.ts CHANGED
@@ -1,7 +1,9 @@
1
1
  import { betterAuth } from "better-auth";
2
2
  import * as socialProviderFactories from "better-auth/social-providers";
3
3
  import { drizzleAdapter } from "better-auth/adapters/drizzle";
4
- import { APIError } from "better-auth/api";
4
+ import { APIError, createAuthEndpoint } from "better-auth/api";
5
+ import { setSessionCookie } from "better-auth/cookies";
6
+ import { z } from "zod";
5
7
  import {
6
8
  createBackendPlugin,
7
9
  coreServices,
@@ -20,7 +22,7 @@ import { NotificationApi } from "@checkstack/notification-common";
20
22
  import * as schema from "./schema";
21
23
  import { eq, inArray } from "drizzle-orm";
22
24
  import { SafeDatabase } from "@checkstack/backend-api";
23
- import { User } from "better-auth/types";
25
+ import { BetterAuthOptions, User } from "better-auth/types";
24
26
  import { verifyPassword } from "better-auth/crypto";
25
27
  import { createExtensionPoint } from "@checkstack/backend-api";
26
28
  import { enrichUser } from "./utils/user";
@@ -549,6 +551,74 @@ export default createBackendPlugin({
549
551
  // Default to true on fresh installs (no meta config)
550
552
  const credentialEnabled = credentialMetaConfig?.enabled ?? true;
551
553
 
554
+ const baseUrl = process.env.BASE_URL;
555
+ if (!baseUrl) {
556
+ throw new Error(
557
+ "[auth-backend] BASE_URL environment variable is not defined.",
558
+ );
559
+ }
560
+
561
+ const betterAuthSecret = process.env.BETTER_AUTH_SECRET;
562
+ if (!betterAuthSecret) {
563
+ throw new Error(
564
+ "[auth-backend] BETTER_AUTH_SECRET environment variable is not defined.",
565
+ );
566
+ }
567
+
568
+ const checkstackBridge = {
569
+ id: "checkstack-bridge",
570
+ endpoints: {
571
+ trustedLogin: createAuthEndpoint(
572
+ "/internal/trusted-login",
573
+ {
574
+ method: "POST",
575
+ body: z.object({ userId: z.string() }),
576
+ },
577
+ async (ctx) => {
578
+ const secretHeader = ctx.request?.headers.get(
579
+ "x-checkstack-internal",
580
+ );
581
+ if (
582
+ !secretHeader ||
583
+ secretHeader !== betterAuthSecret
584
+ ) {
585
+ throw new APIError("UNAUTHORIZED");
586
+ }
587
+
588
+ const { userId } = ctx.body;
589
+ const ipAddress =
590
+ ctx.request?.headers
591
+ .get("x-forwarded-for")
592
+ ?.split(",")[0]
593
+ .trim() || undefined;
594
+ const userAgent =
595
+ ctx.request?.headers.get("user-agent") || undefined;
596
+
597
+ const session =
598
+ await ctx.context.internalAdapter.createSession(
599
+ userId,
600
+ false,
601
+ {
602
+ ipAddress,
603
+ userAgent,
604
+ },
605
+ );
606
+ const user =
607
+ await ctx.context.internalAdapter.findUserById(userId);
608
+
609
+ if (!user) {
610
+ throw new APIError("NOT_FOUND", {
611
+ message: "User not found",
612
+ });
613
+ }
614
+
615
+ await setSessionCookie(ctx, { session, user });
616
+ return ctx.json({ success: true, sessionId: session.id });
617
+ },
618
+ ),
619
+ },
620
+ };
621
+
552
622
  // Check platform registration setting
553
623
  const platformRegistrationConfig = await config.get(
554
624
  PLATFORM_REGISTRATION_CONFIG_ID,
@@ -564,7 +634,7 @@ export default createBackendPlugin({
564
634
  } social providers: ${Object.keys(socialProviders).join(", ")}`,
565
635
  );
566
636
 
567
- return betterAuth({
637
+ const authOptions: BetterAuthOptions = {
568
638
  database: drizzleAdapter(database, {
569
639
  provider: "pg",
570
640
  schema: { ...schema },
@@ -579,17 +649,19 @@ export default createBackendPlugin({
579
649
  // Send password reset notification via all enabled strategies
580
650
  // Using void to prevent timing attacks revealing email existence
581
651
  const notificationClient = rpcClient.forPlugin(NotificationApi);
582
- const frontendUrl =
583
- process.env.BASE_URL || "http://localhost:5173";
652
+ const frontendUrl = baseUrl;
584
653
  // SECURITY: Use URL parsing instead of brittle string splitting
585
654
  const parsedUrl = new URL(url);
586
655
  const resetToken = parsedUrl.searchParams.get("token");
587
656
  if (!resetToken) {
588
657
  throw new APIError("BAD_REQUEST", {
589
- message: "Malformed password reset URL: missing token parameter",
658
+ message:
659
+ "Malformed password reset URL: missing token parameter",
590
660
  });
591
661
  }
592
- const resetUrl = `${frontendUrl}/auth/reset-password?token=${encodeURIComponent(resetToken)}`;
662
+ const resetUrl = `${frontendUrl}/auth/reset-password?token=${encodeURIComponent(
663
+ resetToken,
664
+ )}`;
593
665
 
594
666
  void notificationClient.sendTransactional({
595
667
  userId: user.id,
@@ -611,8 +683,8 @@ export default createBackendPlugin({
611
683
  },
612
684
  socialProviders,
613
685
  basePath: "/api/auth",
614
- baseURL: process.env.BASE_URL || "http://localhost:5173",
615
- trustedOrigins: [process.env.BASE_URL || "http://localhost:5173"],
686
+ baseURL: baseUrl,
687
+ trustedOrigins: [baseUrl],
616
688
  databaseHooks: {
617
689
  user: {
618
690
  create: {
@@ -648,7 +720,10 @@ export default createBackendPlugin({
648
720
  },
649
721
  },
650
722
  },
651
- });
723
+ plugins: [checkstackBridge],
724
+ };
725
+
726
+ return betterAuth(authOptions);
652
727
  };
653
728
 
654
729
  // Initialize better-auth
@@ -732,6 +807,8 @@ export default createBackendPlugin({
732
807
  reloadAuth,
733
808
  config,
734
809
  accessRuleRegistry,
810
+ () => auth,
811
+ logger,
735
812
  );
736
813
  rpc.registerRouter(authRouter, authContract);
737
814
 
@@ -89,6 +89,7 @@ describe("Auth Router", () => {
89
89
  async () => {},
90
90
  mockConfigService,
91
91
  mockAccessRuleRegistry,
92
+ () => undefined,
92
93
  );
93
94
 
94
95
  it("getAccessRules returns current user access rules", async () => {
@@ -349,23 +350,53 @@ describe("Auth Router", () => {
349
350
  expect(mockDb.update).toHaveBeenCalled();
350
351
  });
351
352
 
352
- it("createSession creates session record", async () => {
353
+ it("createSession calls the internal auth bridge", async () => {
353
354
  const context = createMockRpcContext({ user: mockServiceUser });
354
355
 
355
- const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000);
356
+ // Setup environment variables for the bridge call
357
+ process.env.BASE_URL = "http://localhost:3000";
358
+ process.env.BETTER_AUTH_SECRET = "test-secret";
359
+
360
+ const mockResponse = { sessionId: "session-123" };
361
+ const mockHandler = mock(async (_req: Request) => {
362
+ return new Response(JSON.stringify(mockResponse), {
363
+ status: 200,
364
+ headers: {
365
+ "Set-Cookie": "better-auth.session_token=test-token; Path=/; HttpOnly",
366
+ },
367
+ });
368
+ });
369
+
370
+ // We need to use a router instance that has access to our mock handler
371
+ const testRouter = createAuthRouter(
372
+ mockDb,
373
+ mockRegistry,
374
+ async () => {},
375
+ mockConfigService,
376
+ mockAccessRuleRegistry,
377
+ () => ({ handler: mockHandler }),
378
+ );
356
379
 
357
380
  const result = await call(
358
- router.createSession,
381
+ testRouter.createSession,
359
382
  {
360
383
  userId: "user-123",
361
- token: "session-token",
362
- expiresAt,
363
384
  },
364
385
  { context },
365
386
  );
366
387
 
367
- expect(result.sessionId).toBeDefined();
368
- expect(mockDb.insert).toHaveBeenCalled();
388
+ expect(result.sessionId).toBe("session-123");
389
+ expect(result.setCookie).toContain("better-auth.session_token=test-token");
390
+ expect(mockHandler).toHaveBeenCalled();
391
+
392
+ // Verify the virtual request headers
393
+ const lastCall = mockHandler.mock.calls[0][0] as unknown as Request;
394
+ expect(lastCall.headers.get("Host")).toBe("localhost:3000");
395
+ expect(lastCall.url).toBe("http://localhost:3000/api/auth/internal/trusted-login");
396
+
397
+ // Cleanup environment variables
398
+ delete process.env.BASE_URL;
399
+ delete process.env.BETTER_AUTH_SECRET;
369
400
  });
370
401
 
371
402
  it("upsertExternalUser syncs roles when syncRoles provided for new user", async () => {
package/src/router.ts CHANGED
@@ -8,7 +8,12 @@ import {
8
8
  type ConfigService,
9
9
  toJsonSchema,
10
10
  } from "@checkstack/backend-api";
11
- import { authContract, passwordSchema, authAccess, pluginMetadata } from "@checkstack/auth-common";
11
+ import {
12
+ authContract,
13
+ passwordSchema,
14
+ authAccess,
15
+ pluginMetadata,
16
+ } from "@checkstack/auth-common";
12
17
  import { qualifyAccessRuleId } from "@checkstack/common";
13
18
  import { hashPassword } from "better-auth/crypto";
14
19
  import * as schema from "./schema";
@@ -132,7 +137,9 @@ async function assertTeamManagementAccess({
132
137
 
133
138
  const hasGlobalManage =
134
139
  user?.accessRules?.includes("*") ||
135
- user?.accessRules?.includes(qualifyAccessRuleId(pluginMetadata, authAccess.teams.manage));
140
+ user?.accessRules?.includes(
141
+ qualifyAccessRuleId(pluginMetadata, authAccess.teams.manage),
142
+ );
136
143
 
137
144
  if (hasGlobalManage) return; // Global manage allows all teams
138
145
 
@@ -157,6 +164,12 @@ async function assertTeamManagementAccess({
157
164
  });
158
165
  }
159
166
 
167
+ const DEFAULT_LOGGER = {
168
+ info: () => {},
169
+ error: () => {},
170
+ debug: () => {},
171
+ };
172
+
160
173
  export const createAuthRouter = (
161
174
  internalDb: SafeDatabase<typeof schema>,
162
175
  strategyRegistry: { getStrategies: () => AuthStrategy<unknown>[] },
@@ -170,6 +183,14 @@ export const createAuthRouter = (
170
183
  isPublic?: boolean;
171
184
  }[];
172
185
  },
186
+ getBetterAuth: () =>
187
+ | { handler: (request: Request) => Promise<Response> }
188
+ | undefined,
189
+ logger: {
190
+ info: (msg: string, metadata?: Record<string, unknown>) => void;
191
+ error: (msg: string, metadata?: Record<string, unknown>) => void;
192
+ debug: (msg: string, metadata?: Record<string, unknown>) => void;
193
+ } = DEFAULT_LOGGER,
173
194
  ) => {
174
195
  // Public endpoint for enabled strategies (no authentication required)
175
196
  const getEnabledStrategies = os.getEnabledStrategies.handler(async () => {
@@ -180,9 +201,15 @@ export const createAuthRouter = (
180
201
  // Get enabled state from meta config
181
202
  const enabled = await getStrategyEnabled(strategy.id, configService);
182
203
 
183
- // Determine strategy type
184
- const type: "credential" | "social" =
185
- strategy.id === "credential" ? "credential" : "social";
204
+ // Determine strategy type (backward compatibility)
205
+ let type: "credential" | "social" | "ldap" | "saml" = "social";
206
+ if (strategy.id === "credential") {
207
+ type = "credential";
208
+ } else if (strategy.clientFlow?.type === "form") {
209
+ type = "ldap"; // Map generic 'form' to 'ldap' for frontend compat
210
+ } else if (strategy.clientFlow?.type === "redirect") {
211
+ type = "saml"; // Map generic 'redirect' to 'saml' for frontend compat
212
+ }
186
213
 
187
214
  return {
188
215
  id: strategy.id,
@@ -192,6 +219,7 @@ export const createAuthRouter = (
192
219
  enabled,
193
220
  icon: strategy.icon,
194
221
  requiresManualRegistration: strategy.requiresManualRegistration,
222
+ clientFlow: strategy.clientFlow,
195
223
  };
196
224
  }),
197
225
  );
@@ -1031,20 +1059,82 @@ export const createAuthRouter = (
1031
1059
  );
1032
1060
 
1033
1061
  const createSession = os.createSession.handler(async ({ input }) => {
1034
- const { userId, token, expiresAt } = input;
1035
- const sessionId = crypto.randomUUID();
1036
- const now = new Date();
1062
+ const { userId, ipAddress, userAgent } = input;
1063
+ const auth = getBetterAuth();
1037
1064
 
1038
- await internalDb.insert(schema.session).values({
1039
- id: sessionId,
1040
- userId,
1041
- token,
1042
- expiresAt,
1043
- createdAt: now,
1044
- updatedAt: now,
1065
+ if (!auth) {
1066
+ throw new ORPCError("INTERNAL_SERVER_ERROR", {
1067
+ message: "Authentication service not fully initialized.",
1068
+ });
1069
+ }
1070
+
1071
+ // Construct virtual request to the internal trusted login endpoint
1072
+ // This allows better-auth to handle cookie signing and database persistence
1073
+ const baseUrl = process.env.BASE_URL;
1074
+ if (!baseUrl) {
1075
+ throw new ORPCError("INTERNAL_SERVER_ERROR", {
1076
+ message: "BASE_URL environment variable is not defined.",
1077
+ });
1078
+ }
1079
+
1080
+ const secret = process.env.BETTER_AUTH_SECRET;
1081
+ if (!secret) {
1082
+ throw new ORPCError("INTERNAL_SERVER_ERROR", {
1083
+ message: "BETTER_AUTH_SECRET environment variable is not defined.",
1084
+ });
1085
+ }
1086
+
1087
+ const url = new URL(baseUrl);
1088
+ const req = new Request(`${url.origin}/api/auth/internal/trusted-login`, {
1089
+ method: "POST",
1090
+ headers: {
1091
+ "Content-Type": "application/json",
1092
+ "x-checkstack-internal": secret,
1093
+ "x-forwarded-for": ipAddress || "",
1094
+ "user-agent": userAgent || "",
1095
+ Host: url.host,
1096
+ },
1097
+ body: JSON.stringify({ userId }),
1045
1098
  });
1046
1099
 
1047
- return { sessionId };
1100
+ const res = await auth.handler(req);
1101
+
1102
+ if (!res.ok) {
1103
+ const errorText = await res.text();
1104
+ throw new ORPCError("INTERNAL_SERVER_ERROR", {
1105
+ message: `Failed to create session via bridge: ${res.status} ${errorText}`,
1106
+ });
1107
+ }
1108
+
1109
+ // Extract Set-Cookie. Use getSetCookie() if available (Bun/Node 20+), otherwise fallback to get()
1110
+ // get() usually joins multiple cookies with a comma, which is often correct but sometimes brittle
1111
+ const setCookie =
1112
+ typeof res.headers.getSetCookie === "function"
1113
+ ? res.headers.getSetCookie().join(", ")
1114
+ : res.headers.get("set-cookie");
1115
+
1116
+ if (!setCookie) {
1117
+ const headers: Record<string, string> = {};
1118
+ // eslint-disable-next-line unicorn/no-array-for-each
1119
+ res.headers.forEach((value, key) => {
1120
+ headers[key] = value;
1121
+ });
1122
+
1123
+ logger.error("Authentication bridge did not return session cookies", {
1124
+ status: res.status,
1125
+ headers,
1126
+ });
1127
+ throw new ORPCError("INTERNAL_SERVER_ERROR", {
1128
+ message: "Authentication service did not return session cookies.",
1129
+ });
1130
+ }
1131
+
1132
+ const body = (await res.json()) as { sessionId: string };
1133
+
1134
+ return {
1135
+ sessionId: body.sessionId,
1136
+ setCookie,
1137
+ };
1048
1138
  });
1049
1139
 
1050
1140
  // ==========================================================================
@@ -1500,33 +1590,37 @@ export const createAuthRouter = (
1500
1590
  },
1501
1591
  );
1502
1592
 
1503
- const addTeamManager = os.addTeamManager.handler(async ({ input, context }) => {
1504
- await assertTeamManagementAccess({
1505
- user: context.user,
1506
- teamId: input.teamId,
1507
- internalDb,
1508
- });
1509
- await internalDb
1510
- .insert(schema.teamManager)
1511
- .values({ userId: input.userId, teamId: input.teamId })
1512
- .onConflictDoNothing();
1513
- });
1593
+ const addTeamManager = os.addTeamManager.handler(
1594
+ async ({ input, context }) => {
1595
+ await assertTeamManagementAccess({
1596
+ user: context.user,
1597
+ teamId: input.teamId,
1598
+ internalDb,
1599
+ });
1600
+ await internalDb
1601
+ .insert(schema.teamManager)
1602
+ .values({ userId: input.userId, teamId: input.teamId })
1603
+ .onConflictDoNothing();
1604
+ },
1605
+ );
1514
1606
 
1515
- const removeTeamManager = os.removeTeamManager.handler(async ({ input, context }) => {
1516
- await assertTeamManagementAccess({
1517
- user: context.user,
1518
- teamId: input.teamId,
1519
- internalDb,
1520
- });
1521
- await internalDb
1522
- .delete(schema.teamManager)
1523
- .where(
1524
- and(
1525
- eq(schema.teamManager.userId, input.userId),
1526
- eq(schema.teamManager.teamId, input.teamId),
1527
- ),
1528
- );
1529
- });
1607
+ const removeTeamManager = os.removeTeamManager.handler(
1608
+ async ({ input, context }) => {
1609
+ await assertTeamManagementAccess({
1610
+ user: context.user,
1611
+ teamId: input.teamId,
1612
+ internalDb,
1613
+ });
1614
+ await internalDb
1615
+ .delete(schema.teamManager)
1616
+ .where(
1617
+ and(
1618
+ eq(schema.teamManager.userId, input.userId),
1619
+ eq(schema.teamManager.teamId, input.teamId),
1620
+ ),
1621
+ );
1622
+ },
1623
+ );
1530
1624
 
1531
1625
  const getResourceTeamAccess = os.getResourceTeamAccess.handler(
1532
1626
  async ({ input }) => {
@@ -1787,6 +1881,50 @@ export const createAuthRouter = (
1787
1881
  },
1788
1882
  );
1789
1883
 
1884
+ const getOwnStrategyConfig = os.getOwnStrategyConfig.handler(
1885
+ async ({ context }) => {
1886
+ if (context.user?.type !== "service") {
1887
+ throw new ORPCError("UNAUTHORIZED", {
1888
+ message: "This endpoint is only callable by services.",
1889
+ });
1890
+ }
1891
+
1892
+ const callerPluginId = context.user?.pluginId;
1893
+
1894
+ // Infer strategyId from pluginId (e.g. auth-ldap-backend -> ldap)
1895
+ const strategyId = callerPluginId
1896
+ .replace(/^auth-/, "")
1897
+ .replace(/-backend$/, "");
1898
+
1899
+ const strategy = strategyRegistry
1900
+ .getStrategies()
1901
+ .find((s) => s.id === strategyId);
1902
+
1903
+ if (!strategy) {
1904
+ throw new ORPCError("NOT_FOUND", {
1905
+ message: `No strategy found for plugin ${callerPluginId} (inferred strategy ID: ${strategyId})`,
1906
+ });
1907
+ }
1908
+
1909
+ // Load full (non-redacted) config from ConfigService
1910
+ // These configurations are stored in the auth-backend's scope
1911
+ const config = await configService.get(
1912
+ strategy.id,
1913
+ strategy.configSchema,
1914
+ strategy.configVersion,
1915
+ strategy.migrations,
1916
+ );
1917
+
1918
+ if (!config) {
1919
+ throw new ORPCError("NOT_FOUND", {
1920
+ message: `Configuration not found for strategy ${strategyId}`,
1921
+ });
1922
+ }
1923
+
1924
+ return { config: config as Record<string, unknown> };
1925
+ },
1926
+ );
1927
+
1790
1928
  return os.router({
1791
1929
  getEnabledStrategies,
1792
1930
  accessRules: accessRulesHandler,
@@ -1820,6 +1958,7 @@ export const createAuthRouter = (
1820
1958
  updateApplication,
1821
1959
  deleteApplication,
1822
1960
  regenerateApplicationSecret,
1961
+ getOwnStrategyConfig,
1823
1962
  // Teams
1824
1963
  getTeams,
1825
1964
  getTeam,
package/src/teams.test.ts CHANGED
@@ -137,7 +137,8 @@ describe("Teams and Resource Access Control", () => {
137
137
  mockRegistry,
138
138
  async () => {},
139
139
  mockConfigService,
140
- mockAccessRuleRegistry
140
+ mockAccessRuleRegistry,
141
+ () => undefined
141
142
  );
142
143
 
143
144
  const context = createMockRpcContext({ user: mockAdminUser });
@@ -187,7 +188,8 @@ describe("Teams and Resource Access Control", () => {
187
188
  mockRegistry,
188
189
  async () => {},
189
190
  mockConfigService,
190
- mockAccessRuleRegistry
191
+ mockAccessRuleRegistry,
192
+ () => undefined
191
193
  );
192
194
 
193
195
  const context = createMockRpcContext({ user: mockAdminUser });
@@ -252,7 +254,8 @@ describe("Teams and Resource Access Control", () => {
252
254
  mockRegistry,
253
255
  async () => {},
254
256
  mockConfigService,
255
- mockAccessRuleRegistry
257
+ mockAccessRuleRegistry,
258
+ () => undefined
256
259
  );
257
260
 
258
261
  // Use mockRegularUser who has only auth.teams.read access
@@ -309,7 +312,8 @@ describe("Teams and Resource Access Control", () => {
309
312
  mockRegistry,
310
313
  async () => {},
311
314
  mockConfigService,
312
- mockAccessRuleRegistry
315
+ mockAccessRuleRegistry,
316
+ () => undefined
313
317
  );
314
318
 
315
319
  const context = createMockRpcContext({ user: mockRegularUser });
@@ -333,7 +337,8 @@ describe("Teams and Resource Access Control", () => {
333
337
  mockRegistry,
334
338
  async () => {},
335
339
  mockConfigService,
336
- mockAccessRuleRegistry
340
+ mockAccessRuleRegistry,
341
+ () => undefined
337
342
  );
338
343
 
339
344
  const context = createMockRpcContext({ user: mockAdminUser });
@@ -389,7 +394,8 @@ describe("Teams and Resource Access Control", () => {
389
394
  mockRegistry,
390
395
  async () => {},
391
396
  mockConfigService,
392
- mockAccessRuleRegistry
397
+ mockAccessRuleRegistry,
398
+ () => undefined
393
399
  );
394
400
 
395
401
  const context = createMockRpcContext({ user: mockAdminUser });
@@ -423,7 +429,8 @@ describe("Teams and Resource Access Control", () => {
423
429
  mockRegistry,
424
430
  async () => {},
425
431
  mockConfigService,
426
- mockAccessRuleRegistry
432
+ mockAccessRuleRegistry,
433
+ () => undefined
427
434
  );
428
435
 
429
436
  const context = createMockRpcContext({ user: mockAdminUser });
@@ -453,7 +460,8 @@ describe("Teams and Resource Access Control", () => {
453
460
  mockRegistry,
454
461
  async () => {},
455
462
  mockConfigService,
456
- mockAccessRuleRegistry
463
+ mockAccessRuleRegistry,
464
+ () => undefined
457
465
  );
458
466
 
459
467
  const context = createMockRpcContext({ user: mockAdminUser });
@@ -487,7 +495,8 @@ describe("Teams and Resource Access Control", () => {
487
495
  mockRegistry,
488
496
  async () => {},
489
497
  mockConfigService,
490
- mockAccessRuleRegistry
498
+ mockAccessRuleRegistry,
499
+ () => undefined
491
500
  );
492
501
 
493
502
  const context = createMockRpcContext({ user: mockAdminUser });
@@ -520,7 +529,8 @@ describe("Teams and Resource Access Control", () => {
520
529
  mockRegistry,
521
530
  async () => {},
522
531
  mockConfigService,
523
- mockAccessRuleRegistry
532
+ mockAccessRuleRegistry,
533
+ () => undefined
524
534
  );
525
535
 
526
536
  const context = createMockRpcContext({ user: mockAdminUser });
@@ -557,7 +567,8 @@ describe("Teams and Resource Access Control", () => {
557
567
  mockRegistry,
558
568
  async () => {},
559
569
  mockConfigService,
560
- mockAccessRuleRegistry
570
+ mockAccessRuleRegistry,
571
+ () => undefined
561
572
  );
562
573
 
563
574
  const context = createMockRpcContext({ user: mockAdminUser });
@@ -596,7 +607,8 @@ describe("Teams and Resource Access Control", () => {
596
607
  mockRegistry,
597
608
  async () => {},
598
609
  mockConfigService,
599
- mockAccessRuleRegistry
610
+ mockAccessRuleRegistry,
611
+ () => undefined
600
612
  );
601
613
 
602
614
  const context = createMockRpcContext({ user: mockAdminUser });
@@ -621,7 +633,8 @@ describe("Teams and Resource Access Control", () => {
621
633
  mockRegistry,
622
634
  async () => {},
623
635
  mockConfigService,
624
- mockAccessRuleRegistry
636
+ mockAccessRuleRegistry,
637
+ () => undefined
625
638
  );
626
639
 
627
640
  const context = createMockRpcContext({ user: mockAdminUser });
@@ -658,7 +671,8 @@ describe("Teams and Resource Access Control", () => {
658
671
  mockRegistry,
659
672
  async () => {},
660
673
  mockConfigService,
661
- mockAccessRuleRegistry
674
+ mockAccessRuleRegistry,
675
+ () => undefined
662
676
  );
663
677
 
664
678
  const context = createMockRpcContext({ user: mockAdminUser });
@@ -683,7 +697,8 @@ describe("Teams and Resource Access Control", () => {
683
697
  mockRegistry,
684
698
  async () => {},
685
699
  mockConfigService,
686
- mockAccessRuleRegistry
700
+ mockAccessRuleRegistry,
701
+ () => undefined
687
702
  );
688
703
 
689
704
  const context = createMockRpcContext({ user: mockAdminUser });
@@ -716,7 +731,8 @@ describe("Teams and Resource Access Control", () => {
716
731
  mockRegistry,
717
732
  async () => {},
718
733
  mockConfigService,
719
- mockAccessRuleRegistry
734
+ mockAccessRuleRegistry,
735
+ () => undefined
720
736
  );
721
737
 
722
738
  const context = createMockRpcContext({ user: mockAdminUser });
@@ -763,7 +779,8 @@ describe("Teams and Resource Access Control", () => {
763
779
  mockRegistry,
764
780
  async () => {},
765
781
  mockConfigService,
766
- mockAccessRuleRegistry
782
+ mockAccessRuleRegistry,
783
+ () => undefined
767
784
  );
768
785
 
769
786
  const context = createMockRpcContext({ user: mockAdminUser });
@@ -808,7 +825,8 @@ describe("Teams and Resource Access Control", () => {
808
825
  mockRegistry,
809
826
  async () => {},
810
827
  mockConfigService,
811
- mockAccessRuleRegistry
828
+ mockAccessRuleRegistry,
829
+ () => undefined
812
830
  );
813
831
 
814
832
  const context = createMockRpcContext({ user: mockAdminUser });
@@ -848,7 +866,8 @@ describe("Teams and Resource Access Control", () => {
848
866
  mockRegistry,
849
867
  async () => {},
850
868
  mockConfigService,
851
- mockAccessRuleRegistry
869
+ mockAccessRuleRegistry,
870
+ () => undefined
852
871
  );
853
872
 
854
873
  const context = createMockRpcContext({ user: mockAdminUser });
@@ -878,7 +897,8 @@ describe("Teams and Resource Access Control", () => {
878
897
  mockRegistry,
879
898
  async () => {},
880
899
  mockConfigService,
881
- mockAccessRuleRegistry
900
+ mockAccessRuleRegistry,
901
+ () => undefined
882
902
  );
883
903
 
884
904
  const context = createMockRpcContext({ user: mockAdminUser });
@@ -914,7 +934,8 @@ describe("Teams and Resource Access Control", () => {
914
934
  mockRegistry,
915
935
  async () => {},
916
936
  mockConfigService,
917
- mockAccessRuleRegistry
937
+ mockAccessRuleRegistry,
938
+ () => undefined
918
939
  );
919
940
 
920
941
  const context = createMockRpcContext({ user: mockServiceUser });
@@ -947,7 +968,8 @@ describe("Teams and Resource Access Control", () => {
947
968
  mockRegistry,
948
969
  async () => {},
949
970
  mockConfigService,
950
- mockAccessRuleRegistry
971
+ mockAccessRuleRegistry,
972
+ () => undefined
951
973
  );
952
974
 
953
975
  const context = createMockRpcContext({ user: mockServiceUser });
@@ -998,7 +1020,8 @@ describe("Teams and Resource Access Control", () => {
998
1020
  mockRegistry,
999
1021
  async () => {},
1000
1022
  mockConfigService,
1001
- mockAccessRuleRegistry
1023
+ mockAccessRuleRegistry,
1024
+ () => undefined
1002
1025
  );
1003
1026
 
1004
1027
  const context = createMockRpcContext({ user: mockServiceUser });
@@ -1049,7 +1072,8 @@ describe("Teams and Resource Access Control", () => {
1049
1072
  mockRegistry,
1050
1073
  async () => {},
1051
1074
  mockConfigService,
1052
- mockAccessRuleRegistry
1075
+ mockAccessRuleRegistry,
1076
+ () => undefined
1053
1077
  );
1054
1078
 
1055
1079
  const context = createMockRpcContext({ user: mockServiceUser });
@@ -1102,7 +1126,8 @@ describe("Teams and Resource Access Control", () => {
1102
1126
  mockRegistry,
1103
1127
  async () => {},
1104
1128
  mockConfigService,
1105
- mockAccessRuleRegistry
1129
+ mockAccessRuleRegistry,
1130
+ () => undefined
1106
1131
  );
1107
1132
 
1108
1133
  const context = createMockRpcContext({ user: mockServiceUser });
@@ -1155,7 +1180,8 @@ describe("Teams and Resource Access Control", () => {
1155
1180
  mockRegistry,
1156
1181
  async () => {},
1157
1182
  mockConfigService,
1158
- mockAccessRuleRegistry
1183
+ mockAccessRuleRegistry,
1184
+ () => undefined
1159
1185
  );
1160
1186
 
1161
1187
  const context = createMockRpcContext({ user: mockServiceUser });
@@ -1206,7 +1232,8 @@ describe("Teams and Resource Access Control", () => {
1206
1232
  mockRegistry,
1207
1233
  async () => {},
1208
1234
  mockConfigService,
1209
- mockAccessRuleRegistry
1235
+ mockAccessRuleRegistry,
1236
+ () => undefined
1210
1237
  );
1211
1238
 
1212
1239
  const context = createMockRpcContext({ user: mockServiceUser });
@@ -1252,7 +1279,8 @@ describe("Teams and Resource Access Control", () => {
1252
1279
  mockRegistry,
1253
1280
  async () => {},
1254
1281
  mockConfigService,
1255
- mockAccessRuleRegistry
1282
+ mockAccessRuleRegistry,
1283
+ () => undefined
1256
1284
  );
1257
1285
 
1258
1286
  const context = createMockRpcContext({ user: mockServiceUser });
@@ -1303,7 +1331,8 @@ describe("Teams and Resource Access Control", () => {
1303
1331
  mockRegistry,
1304
1332
  async () => {},
1305
1333
  mockConfigService,
1306
- mockAccessRuleRegistry
1334
+ mockAccessRuleRegistry,
1335
+ () => undefined
1307
1336
  );
1308
1337
 
1309
1338
  const context = createMockRpcContext({ user: mockServiceUser });
@@ -1356,7 +1385,8 @@ describe("Teams and Resource Access Control", () => {
1356
1385
  mockRegistry,
1357
1386
  async () => {},
1358
1387
  mockConfigService,
1359
- mockAccessRuleRegistry
1388
+ mockAccessRuleRegistry,
1389
+ () => undefined
1360
1390
  );
1361
1391
 
1362
1392
  const context = createMockRpcContext({ user: mockServiceUser });
@@ -1414,7 +1444,8 @@ describe("Teams and Resource Access Control", () => {
1414
1444
  mockRegistry,
1415
1445
  async () => {},
1416
1446
  mockConfigService,
1417
- mockAccessRuleRegistry
1447
+ mockAccessRuleRegistry,
1448
+ () => undefined
1418
1449
  );
1419
1450
 
1420
1451
  const context = createMockRpcContext({ user: mockServiceUser });
@@ -1465,7 +1496,8 @@ describe("Teams and Resource Access Control", () => {
1465
1496
  mockRegistry,
1466
1497
  async () => {},
1467
1498
  mockConfigService,
1468
- mockAccessRuleRegistry
1499
+ mockAccessRuleRegistry,
1500
+ () => undefined
1469
1501
  );
1470
1502
 
1471
1503
  const context = createMockRpcContext({ user: mockServiceUser });
@@ -1518,7 +1550,8 @@ describe("Teams and Resource Access Control", () => {
1518
1550
  mockRegistry,
1519
1551
  async () => {},
1520
1552
  mockConfigService,
1521
- mockAccessRuleRegistry
1553
+ mockAccessRuleRegistry,
1554
+ () => undefined
1522
1555
  );
1523
1556
 
1524
1557
  const context = createMockRpcContext({ user: mockServiceUser });
@@ -1571,7 +1604,8 @@ describe("Teams and Resource Access Control", () => {
1571
1604
  mockRegistry,
1572
1605
  async () => {},
1573
1606
  mockConfigService,
1574
- mockAccessRuleRegistry
1607
+ mockAccessRuleRegistry,
1608
+ () => undefined
1575
1609
  );
1576
1610
 
1577
1611
  const context = createMockRpcContext({ user: mockServiceUser });
@@ -1601,7 +1635,8 @@ describe("Teams and Resource Access Control", () => {
1601
1635
  mockRegistry,
1602
1636
  async () => {},
1603
1637
  mockConfigService,
1604
- mockAccessRuleRegistry
1638
+ mockAccessRuleRegistry,
1639
+ () => undefined
1605
1640
  );
1606
1641
 
1607
1642
  const context = createMockRpcContext({ user: mockServiceUser });
@@ -1639,7 +1674,8 @@ describe("Teams and Resource Access Control", () => {
1639
1674
  mockRegistry,
1640
1675
  async () => {},
1641
1676
  mockConfigService,
1642
- mockAccessRuleRegistry
1677
+ mockAccessRuleRegistry,
1678
+ () => undefined
1643
1679
  );
1644
1680
 
1645
1681
  const context = createMockRpcContext({ user: mockServiceUser });
@@ -1702,7 +1738,8 @@ describe("Teams and Resource Access Control", () => {
1702
1738
  mockRegistry,
1703
1739
  async () => {},
1704
1740
  mockConfigService,
1705
- mockAccessRuleRegistry
1741
+ mockAccessRuleRegistry,
1742
+ () => undefined
1706
1743
  );
1707
1744
 
1708
1745
  const context = createMockRpcContext({ user: mockServiceUser });
@@ -1750,7 +1787,8 @@ describe("Teams and Resource Access Control", () => {
1750
1787
  mockRegistry,
1751
1788
  async () => {},
1752
1789
  mockConfigService,
1753
- mockAccessRuleRegistry
1790
+ mockAccessRuleRegistry,
1791
+ () => undefined
1754
1792
  );
1755
1793
 
1756
1794
  const context = createMockRpcContext({ user: mockServiceUser });
@@ -1813,7 +1851,8 @@ describe("Teams and Resource Access Control", () => {
1813
1851
  mockRegistry,
1814
1852
  async () => {},
1815
1853
  mockConfigService,
1816
- mockAccessRuleRegistry
1854
+ mockAccessRuleRegistry,
1855
+ () => undefined
1817
1856
  );
1818
1857
 
1819
1858
  const context = createMockRpcContext({ user: mockServiceUser });
@@ -1870,7 +1909,8 @@ describe("Teams and Resource Access Control", () => {
1870
1909
  mockRegistry,
1871
1910
  async () => {},
1872
1911
  mockConfigService,
1873
- mockAccessRuleRegistry
1912
+ mockAccessRuleRegistry,
1913
+ () => undefined
1874
1914
  );
1875
1915
 
1876
1916
  const context = createMockRpcContext({ user: mockServiceUser });
@@ -1933,7 +1973,8 @@ describe("Teams and Resource Access Control", () => {
1933
1973
  mockRegistry,
1934
1974
  async () => {},
1935
1975
  mockConfigService,
1936
- mockAccessRuleRegistry
1976
+ mockAccessRuleRegistry,
1977
+ () => undefined
1937
1978
  );
1938
1979
 
1939
1980
  const context = createMockRpcContext({ user: mockServiceUser });
@@ -1966,7 +2007,8 @@ describe("Teams and Resource Access Control", () => {
1966
2007
  mockRegistry,
1967
2008
  async () => {},
1968
2009
  mockConfigService,
1969
- mockAccessRuleRegistry
2010
+ mockAccessRuleRegistry,
2011
+ () => undefined
1970
2012
  );
1971
2013
 
1972
2014
  const context = createMockRpcContext({ user: mockServiceUser });