fix(sso): re-check domain conflict before write and reject IP-address domains (#4825)

This commit is contained in:
Waleed
2026-05-30 21:01:47 -07:00
committed by GitHub
parent 5fa84160c1
commit e8f648547c
3 changed files with 39 additions and 16 deletions
+29 -15
View File
@@ -81,28 +81,33 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
return orgId ? provider.organizationId === orgId : false
}
const existingProviders = await db
.select({
userId: ssoProvider.userId,
organizationId: ssoProvider.organizationId,
})
.from(ssoProvider)
.where(sql`lower(${ssoProvider.domain}) = ${domain}`)
const conflictingProvider = existingProviders.find((provider) => !isOwnedByCaller(provider))
const findDomainConflict = async () =>
(
await db
.select({
userId: ssoProvider.userId,
organizationId: ssoProvider.organizationId,
})
.from(ssoProvider)
.where(sql`lower(${ssoProvider.domain}) = ${domain}`)
).find((provider) => !isOwnedByCaller(provider))
if (conflictingProvider) {
logger.warn('Rejected SSO registration for domain owned by another tenant', {
domain,
orgId,
userId: session.user.id,
})
return NextResponse.json(
const domainConflictResponse = () =>
NextResponse.json(
{
error: 'This domain is already registered for SSO by another organization.',
code: 'SSO_DOMAIN_ALREADY_REGISTERED',
},
{ status: 409 }
)
if (await findDomainConflict()) {
logger.warn('Rejected SSO registration for domain owned by another tenant', {
domain,
orgId,
userId: session.user.id,
})
return domainConflictResponse()
}
const headers: Record<string, string> = {}
@@ -446,6 +451,15 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
),
})
if (await findDomainConflict()) {
logger.warn('Rejected SSO registration: domain was claimed during registration', {
domain,
orgId,
userId: session.user.id,
})
return domainConflictResponse()
}
const registration = await auth.api.registerSSOProvider({
body: providerConfig,
headers,
+6
View File
@@ -35,4 +35,10 @@ describe('normalizeSSODomain', () => {
expect(normalizeSSODomain('not a domain')).toBeNull()
expect(normalizeSSODomain('company')).toBeNull()
})
it('rejects bare IP addresses and numeric TLDs', () => {
expect(normalizeSSODomain('10.0.0.1')).toBeNull()
expect(normalizeSSODomain('192.168.1.1')).toBeNull()
expect(normalizeSSODomain('company.123')).toBeNull()
})
})
+4 -1
View File
@@ -19,7 +19,10 @@ export function normalizeSSODomain(input: string): string | null {
value = value.replace(/\.$/, '')
if (!/^[a-z0-9-]+(\.[a-z0-9-]+)+$/.test(value)) return null
if (value.split('.').some((label) => label.length === 0 || label.length > 63)) return null
const labels = value.split('.')
if (labels.some((label) => label.length === 0 || label.length > 63)) return null
if (/^\d+$/.test(labels[labels.length - 1])) return null
return value
}