Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 29 additions & 15 deletions apps/sim/app/api/auth/sso/register/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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> = {}
Expand Down Expand Up @@ -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,
Expand Down
6 changes: 6 additions & 0 deletions apps/sim/lib/auth/sso/domain.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()
})
})
5 changes: 4 additions & 1 deletion apps/sim/lib/auth/sso/domain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Loading