feat(sso): update SSO provider registration to handle multiple domains

- Refactored `register-oidc-dialog` and `register-saml-dialog` to accept an array of domains instead of a single domain string.
- Enhanced server-side validation to check for duplicate domains across registered providers.
- Updated SSO schema to reflect the change from a single domain to an array of domains, including validation for domain format.
This commit is contained in:
Mauricio Siu
2026-01-31 13:17:24 -06:00
parent 54229b0dcd
commit 69ba901535
4 changed files with 37 additions and 15 deletions

View File

@@ -101,14 +101,10 @@ export function RegisterOidcDialog({ children }: RegisterOidcDialogProps) {
const scopes = data.scopes.filter(Boolean).length
? data.scopes.filter(Boolean)
: DEFAULT_SCOPES;
const domain = data.domains
.map((d) => d.trim())
.filter(Boolean)
.join(",");
await mutateAsync({
providerId: data.providerId,
issuer: data.issuer,
domain,
domains: data.domains,
oidcConfig: {
clientId: data.clientId,
clientSecret: data.clientSecret,

View File

@@ -95,14 +95,10 @@ export function RegisterSamlDialog({ children }: RegisterSamlDialogProps) {
const onSubmit = async (data: SamlProviderForm) => {
try {
const domain = data.domains
.map((d) => d.trim())
.filter(Boolean)
.join(",");
await mutateAsync({
providerId: data.providerId,
issuer: data.issuer,
domain,
domains: data.domains,
samlConfig: {
entryPoint: data.entryPoint,
cert: data.cert,

View File

@@ -99,15 +99,34 @@ export const ssoRouter = createTRPCRouter({
.mutation(async ({ ctx, input }) => {
const organizationId = ctx.session.activeOrganizationId;
const result = await auth.registerSSOProvider({
const providers = await db.query.ssoProvider.findMany({
columns: {
domain: true,
},
});
for (const provider of providers) {
const providerDomains = provider.domain
.split(",")
.map((d) => d.trim().toLowerCase());
for (const domain of input.domains) {
if (providerDomains.includes(domain)) {
throw new TRPCError({
code: "BAD_REQUEST",
message: `Domain ${domain} is already registered for another provider`,
});
}
}
}
const domain = input.domains.join(",");
await auth.registerSSOProvider({
body: {
...input,
organizationId,
domain,
},
headers: requestToHeaders(ctx.req),
});
console.log(result);
return { success: true };
}),
});

View File

@@ -27,11 +27,22 @@ export const ssoProviderRelations = relations(ssoProvider, ({ one }) => ({
references: [user.id],
}),
}));
const domainRegex = /^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,}$/;
export const ssoProviderBodySchema = z.object({
providerId: z.string({}),
issuer: z.string({}),
domain: z.string({}),
domains: z
.string()
.array()
.transform((val) =>
Array.from(
new Set(val.map((d) => d.trim().toLowerCase()).filter(Boolean)),
),
)
.refine((val) => val.every((d) => domainRegex.test(d)), {
message: "Invalid domain",
path: ["domains"],
}),
oidcConfig: z
.object({
clientId: z.string({}),