@@ -11,7 +11,6 @@ import { listSsoProvidersContract } from '@/lib/api/contracts/auth'
1111import { parseRequest } from '@/lib/api/server'
1212import { getSession } from '@/lib/auth'
1313import { markSignInProviders } from '@/lib/auth/sso/primary-provider'
14- import { enforceIpRateLimit } from '@/lib/core/rate-limiter'
1514import { REDACTED_MARKER } from '@/lib/core/security/redaction'
1615import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
1716
@@ -32,102 +31,87 @@ function buildClientSecretHint(clientSecret: unknown): string | null {
3231 return clientSecret . slice ( - 4 )
3332}
3433
34+ /**
35+ * Lists the identity providers the caller administers: an organization's when an
36+ * owner or admin names it, otherwise the ones the caller registered.
37+ *
38+ * Signed-in only. Sign-in resolves one address at a time through
39+ * `/api/auth/sso/resolve`; nothing needs every configured domain, and listing
40+ * them would publish which organizations use SSO.
41+ */
3542export const GET = withRouteHandler ( async ( request : NextRequest ) => {
3643 try {
3744 const session = await getSession ( )
3845 if ( ! session ?. user ?. id ) {
39- const rateLimited = await enforceIpRateLimit ( 'sso-providers' , request , {
40- maxTokens : 20 ,
41- refillRate : 20 ,
42- refillIntervalMs : 60_000 ,
43- } )
44- if ( rateLimited ) return rateLimited
46+ return NextResponse . json ( { error : 'Unauthorized' } , { status : 401 } )
4547 }
4648 const parsed = await parseRequest ( listSsoProvidersContract , request , { } )
4749 if ( ! parsed . success ) return parsed . response
4850 const { organizationId } = parsed . data . query
51+ const userId = session . user . id
4952
50- let providers
51- if ( session ?. user ?. id ) {
52- const userId = session . user . id
53-
54- let verifiedOrganizationId : string | null = null
55- if ( organizationId ) {
56- const [ membership ] = await db
57- . select ( { organizationId : member . organizationId , role : member . role } )
58- . from ( member )
59- . where ( and ( eq ( member . userId , userId ) , eq ( member . organizationId , organizationId ) ) )
60- . limit ( 1 )
61- if ( ! membership ) {
62- return NextResponse . json ( { error : 'Forbidden' } , { status : 403 } )
63- }
64- if ( membership . role !== 'owner' && membership . role !== 'admin' ) {
65- return NextResponse . json ( { error : 'Forbidden' } , { status : 403 } )
66- }
67- verifiedOrganizationId = membership . organizationId
53+ let verifiedOrganizationId : string | null = null
54+ if ( organizationId ) {
55+ const [ membership ] = await db
56+ . select ( { organizationId : member . organizationId , role : member . role } )
57+ . from ( member )
58+ . where ( and ( eq ( member . userId , userId ) , eq ( member . organizationId , organizationId ) ) )
59+ . limit ( 1 )
60+ if ( ! membership ) {
61+ return NextResponse . json ( { error : 'Forbidden' } , { status : 403 } )
6862 }
63+ if ( membership . role !== 'owner' && membership . role !== 'admin' ) {
64+ return NextResponse . json ( { error : 'Forbidden' } , { status : 403 } )
65+ }
66+ verifiedOrganizationId = membership . organizationId
67+ }
6968
70- const whereClause = verifiedOrganizationId
71- ? eq ( ssoProvider . organizationId , verifiedOrganizationId )
72- : eq ( ssoProvider . userId , userId )
73-
74- const results = await db
75- . select ( {
76- id : ssoProvider . id ,
77- providerId : ssoProvider . providerId ,
78- domain : ssoProvider . domain ,
79- issuer : ssoProvider . issuer ,
80- oidcConfig : ssoProvider . oidcConfig ,
81- samlConfig : ssoProvider . samlConfig ,
82- userId : ssoProvider . userId ,
83- organizationId : ssoProvider . organizationId ,
84- jitProvisioningEnabled : ssoProvider . jitProvisioningEnabled ,
85- domainVerified : ssoProvider . domainVerified ,
86- domainKey : ssoProviderDomainKey ,
87- isNamedPrimary,
88- } )
89- . from ( ssoProvider )
90- . leftJoin ( ssoDomain , verifiedDomainOfProvider )
91- . where ( whereClause )
92- . orderBy ( asc ( ssoProvider . providerId ) )
69+ const whereClause = verifiedOrganizationId
70+ ? eq ( ssoProvider . organizationId , verifiedOrganizationId )
71+ : eq ( ssoProvider . userId , userId )
9372
94- providers = markSignInProviders ( results ) . map ( ( provider ) => {
95- let oidcConfig = provider . oidcConfig
96- if ( oidcConfig ) {
97- try {
98- const parsed = JSON . parse ( oidcConfig )
99- const hint = buildClientSecretHint ( parsed . clientSecret )
100- parsed . clientSecret = REDACTED_MARKER
101- if ( hint ) parsed . clientSecretHint = hint
102- oidcConfig = JSON . stringify ( parsed )
103- } catch {
104- oidcConfig = null
105- }
106- }
107- return {
108- ...provider ,
109- oidcConfig,
110- providerType : ( provider . samlConfig ? 'saml' : 'oidc' ) as 'oidc' | 'saml' ,
111- }
73+ const results = await db
74+ . select ( {
75+ id : ssoProvider . id ,
76+ providerId : ssoProvider . providerId ,
77+ domain : ssoProvider . domain ,
78+ issuer : ssoProvider . issuer ,
79+ oidcConfig : ssoProvider . oidcConfig ,
80+ samlConfig : ssoProvider . samlConfig ,
81+ userId : ssoProvider . userId ,
82+ organizationId : ssoProvider . organizationId ,
83+ jitProvisioningEnabled : ssoProvider . jitProvisioningEnabled ,
84+ domainVerified : ssoProvider . domainVerified ,
85+ domainKey : ssoProviderDomainKey ,
86+ isNamedPrimary,
11287 } )
113- } else {
114- const results = await db
115- . select ( {
116- domain : ssoProvider . domain ,
117- } )
118- . from ( ssoProvider )
119-
120- providers = results . map ( ( provider ) => ( {
121- domain : provider . domain ,
122- } ) )
123- }
88+ . from ( ssoProvider )
89+ . leftJoin ( ssoDomain , verifiedDomainOfProvider )
90+ . where ( whereClause )
91+ . orderBy ( asc ( ssoProvider . providerId ) )
12492
125- logger . info ( 'Fetched SSO providers' , {
126- userId : session ?. user ?. id ,
127- authenticated : ! ! session ?. user ?. id ,
128- providerCount : providers . length ,
93+ const providers = markSignInProviders ( results ) . map ( ( provider ) => {
94+ let oidcConfig = provider . oidcConfig
95+ if ( oidcConfig ) {
96+ try {
97+ const parsed = JSON . parse ( oidcConfig )
98+ const hint = buildClientSecretHint ( parsed . clientSecret )
99+ parsed . clientSecret = REDACTED_MARKER
100+ if ( hint ) parsed . clientSecretHint = hint
101+ oidcConfig = JSON . stringify ( parsed )
102+ } catch {
103+ oidcConfig = null
104+ }
105+ }
106+ return {
107+ ...provider ,
108+ oidcConfig,
109+ providerType : ( provider . samlConfig ? 'saml' : 'oidc' ) as 'oidc' | 'saml' ,
110+ }
129111 } )
130112
113+ logger . info ( 'Fetched SSO providers' , { userId, providerCount : providers . length } )
114+
131115 return NextResponse . json ( { providers } )
132116 } catch ( error ) {
133117 logger . error ( 'Failed to fetch SSO providers' , { error } )
0 commit comments