@@ -101,6 +101,17 @@ export const POST = withRouteHandler(
101101 // instead of mapping an undefined row or trusting a superseded challenge. A
102102 // concurrent cross-org verification trips the partial unique index; surface
103103 // that as a 409 rather than an unhandled 500.
104+ /**
105+ * Providers this proof covers. Normalized the way migration 0268 stored these
106+ * rows (lower, trimmed, leading `*.` dropped) and identical to the expression
107+ * the deletion path revokes with, so granting and revoking can never diverge.
108+ */
109+ const providersOnDomain = ( verifiedDomain : string ) =>
110+ and (
111+ eq ( ssoProvider . organizationId , organizationId ) ,
112+ sql `lower(regexp_replace(btrim(${ ssoProvider . domain } ), '^\\*\\.', '')) = ${ verifiedDomain } `
113+ )
114+
104115 let updated : ( typeof row ) [ ]
105116 try {
106117 updated = await db . transaction ( async ( tx ) => {
@@ -118,18 +129,12 @@ export const POST = withRouteHandler(
118129
119130 // Restore trust this proof covers, mirroring the revocation on delete.
120131 // Without it a delete-then-reverify leaves the provider untrusted, and
121- // since that flag gates sign-in the org sits in a silent SSO outage. The
122- // comparison matches the revoking one exactly so the two stay symmetric.
132+ // since that flag gates sign-in the org sits in a silent SSO outage.
123133 if ( flipped . length > 0 ) {
124134 await tx
125135 . update ( ssoProvider )
126136 . set ( { domainVerified : true } )
127- . where (
128- and (
129- eq ( ssoProvider . organizationId , organizationId ) ,
130- sql `lower(regexp_replace(btrim(${ ssoProvider . domain } ), '^\\*\\.', '')) = ${ flipped [ 0 ] . domain } `
131- )
132- )
137+ . where ( providersOnDomain ( flipped [ 0 ] . domain ) )
133138 }
134139
135140 return flipped
@@ -155,6 +160,15 @@ export const POST = withRouteHandler(
155160 . where ( and ( eq ( ssoDomain . id , domainId ) , eq ( ssoDomain . organizationId , organizationId ) ) )
156161 . limit ( 1 )
157162 if ( current ?. status === 'verified' ) {
163+ // Re-grant rather than returning early. A provider can hold a verified
164+ // domain while its own trust flag is off — an update whose grant was
165+ // refused reverts to the previous config and clears it. Re-running
166+ // verification is the obvious way to fix that, so it must actually do
167+ // something; the proof is present, which is exactly what authorizes this.
168+ await db
169+ . update ( ssoProvider )
170+ . set ( { domainVerified : true } )
171+ . where ( providersOnDomain ( current . domain ) )
158172 return NextResponse . json ( { success : true , data : { domain : toDomainResponse ( current ) } } )
159173 }
160174 return NextResponse . json (
0 commit comments