mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-24 15:45:35 +08:00
fix(sso): re-check domain conflict before write and reject IP-address domains (#4825)
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user