Skip to content

Commit f746b02

Browse files
committed
feat(webapp): pass database writer and reader config to the SSO plugin
Same treatment the RBAC plugin got: the host resolves writer and read-replica URLs from its env and hands them to an installed SSO plugin at create time, with separate connection limits for writes (default 2) and reads (default 5). A plugin that owns its own database client can route login-path reads to the read replica instead of holding them on the primary. The database config type moves to a shared PluginDatabaseConfig used by both plugin contracts.
1 parent ef86613 commit f746b02

7 files changed

Lines changed: 61 additions & 19 deletions

File tree

apps/webapp/app/env.server.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2081,6 +2081,13 @@ const EnvironmentSchema = z
20812081
// Force SSO to not use the plugin (contributors without the cloud
20822082
// plugin installed can opt in to a clean OSS-only experience).
20832083
SSO_FORCE_FALLBACK: BoolEnv.default(false),
2084+
2085+
// Per-process pool sizes for an SSO plugin that owns its own database
2086+
// client (the fallback queries through Prisma and ignores these). Writes
2087+
// are rare config mutations and webhook processing; reads run on the
2088+
// login path.
2089+
SSO_DATABASE_WRITER_CONNECTION_LIMIT: z.coerce.number().int().default(2),
2090+
SSO_DATABASE_READER_CONNECTION_LIMIT: z.coerce.number().int().default(5),
20842091
// Emit a console.log when the SSO fallback is selected because no
20852092
// plugin is installed. Default off so OSS deployments stay quiet.
20862093
SSO_LOG_FALLBACK: BoolEnv.default(false),

apps/webapp/app/services/sso.server.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,17 @@ export const ssoController = sso.create(
1818
// fallback so the entire SSO surface (login, settings, callback,
1919
// re-validation) stays inert. SSO_FORCE_FALLBACK remains an
2020
// independent contributor/debug override.
21-
{ forceFallback: !env.SSO_ENABLED || env.SSO_FORCE_FALLBACK }
21+
{
22+
forceFallback: !env.SSO_ENABLED || env.SSO_FORCE_FALLBACK,
23+
// A plugin that owns its own database client gets the same
24+
// writer/replica topology the webapp's Prisma clients use (see
25+
// getClient/getReplicaClient in db.server.ts): control-plane URLs win,
26+
// and with no replica configured reads share the writer.
27+
database: {
28+
writerUrl: env.CONTROL_PLANE_DATABASE_URL ?? env.DATABASE_URL,
29+
readerUrl: env.CONTROL_PLANE_DATABASE_READ_REPLICA_URL ?? env.DATABASE_READ_REPLICA_URL,
30+
writerConnectionLimit: env.SSO_DATABASE_WRITER_CONNECTION_LIMIT,
31+
readerConnectionLimit: env.SSO_DATABASE_READER_CONNECTION_LIMIT,
32+
},
33+
}
2234
);

internal-packages/sso/src/index.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type {
22
DirectorySyncEffect,
33
DirectorySyncStatus,
44
OrgSsoStatus,
5+
PluginDatabaseConfig,
56
SsoBeginError,
67
SsoCompleteError,
78
SsoController,
@@ -32,6 +33,11 @@ export type SsoCreateOptions = {
3233
// module or a synthetic ERR_MODULE_NOT_FOUND failure without touching
3334
// the real plugin install on disk.
3435
importer?: (moduleName: string) => Promise<{ default: SsoPlugin }>;
36+
// Writer/reader connection URLs + pool sizes for a plugin that owns its
37+
// own database client, resolved by the host from its env so the plugin
38+
// follows the host's writer/replica topology. The fallback ignores this —
39+
// it queries through the Prisma clients passed as `SsoPrismaInput`.
40+
database?: PluginDatabaseConfig;
3541
};
3642

3743
// Loads the cloud plugin lazily; falls back to the OSS no-op
@@ -55,7 +61,7 @@ export class LazyController implements SsoController {
5561
const module = await importer(moduleName);
5662
const plugin: SsoPlugin = module.default;
5763
console.log("SSO: using plugin implementation");
58-
return plugin.create();
64+
return plugin.create({ database: options?.database });
5965
} catch (err) {
6066
// Distinguish the two failure modes the dynamic import can hit:
6167
//
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Database connections for a plugin that owns its own client. The host
2+
// resolves the URLs from its env so the plugin follows the same
3+
// writer/replica topology the host uses. Shared by every plugin contract
4+
// that carries a `database` section.
5+
export type PluginDatabaseConfig = {
6+
// Primary (writer) connection URL. Mutations and read-your-writes
7+
// management reads run here.
8+
writerUrl: string;
9+
// Read-replica URL for the per-request auth reads. Omitted → those reads
10+
// share the writer connection.
11+
readerUrl?: string;
12+
// Per-process pool sizes. Omitted → plugin defaults (writer 2, reader 5).
13+
writerConnectionLimit?: number;
14+
readerConnectionLimit?: number;
15+
};

packages/plugins/src/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,11 @@ export {
2929
USER_ACTOR_TOKEN_PREFIX,
3030
} from "./rbac.js";
3131

32+
export type { PluginDatabaseConfig } from "./databaseConfig.js";
33+
3234
export type {
3335
SsoPlugin,
36+
SsoPluginConfig,
3437
SsoController,
3538
OrgSsoStatus,
3639
SsoRouteDecision,

packages/plugins/src/rbac.ts

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -424,30 +424,20 @@ export type RoleMutationResult = { ok: true; role: Role } | { ok: false; error:
424424
// Result for assignment / deletion mutations that don't return a value.
425425
export type RoleAssignmentResult = { ok: true } | { ok: false; error: string };
426426

427+
import type { PluginDatabaseConfig } from "./databaseConfig.js";
428+
427429
// Host-injected configuration the plugin can't read from the environment
428430
// itself (the plugin runs in the host's process but owns no env contract).
429431
export type RbacPluginConfig = {
430432
// Platform secret the host signs user-actor tokens with; the plugin uses
431433
// it to verify them in `authenticateUserActor`. Omitted → UAT auth 401s.
432434
userActorSecret?: string;
433-
// Database connections for a plugin that owns its own client. The host
434-
// resolves the URLs from its env so the plugin follows the same
435-
// writer/replica topology the host uses. Omitted → the plugin falls back
436-
// to its own defaults.
437-
database?: RbacDatabaseConfig;
435+
// Database connections for a plugin that owns its own client. Omitted →
436+
// the plugin falls back to its own defaults.
437+
database?: PluginDatabaseConfig;
438438
};
439439

440-
export type RbacDatabaseConfig = {
441-
// Primary (writer) connection URL. Mutations and read-your-writes
442-
// management reads run here.
443-
writerUrl: string;
444-
// Read-replica URL for the per-request auth reads. Omitted → those reads
445-
// share the writer connection.
446-
readerUrl?: string;
447-
// Per-process pool sizes. Omitted → plugin defaults (writer 2, reader 5).
448-
writerConnectionLimit?: number;
449-
readerConnectionLimit?: number;
450-
};
440+
export type { PluginDatabaseConfig as RbacDatabaseConfig } from "./databaseConfig.js";
451441

452442
export interface RoleBasedAccessControlPlugin {
453443
create(config?: RbacPluginConfig): RoleBaseAccessController | Promise<RoleBaseAccessController>;

packages/plugins/src/sso.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { ResultAsync } from "neverthrow";
2+
import type { PluginDatabaseConfig } from "./databaseConfig.js";
23

34
// === Domain types ===
45

@@ -368,6 +369,14 @@ export interface SsoController {
368369
): ResultAsync<{ effects: DirectorySyncEffect[] }, SsoWebhookError>;
369370
}
370371

372+
// Host-injected configuration the plugin can't read from the environment
373+
// itself (the plugin runs in the host's process but owns no env contract).
374+
export type SsoPluginConfig = {
375+
// Database connections for a plugin that owns its own client. Omitted →
376+
// the plugin falls back to its own defaults.
377+
database?: PluginDatabaseConfig;
378+
};
379+
371380
export interface SsoPlugin {
372-
create(): SsoController | Promise<SsoController>;
381+
create(config?: SsoPluginConfig): SsoController | Promise<SsoController>;
373382
}

0 commit comments

Comments
 (0)