@@ -96,23 +96,45 @@ test.describe('governance settings', () => {
9696 (select id from "user" where email = ${ adminEmail ( ) } ), ${ providerId } , ${ organizationId ( ) } )`
9797 } )
9898 try {
99- // Signed in elsewhere first, so the body read here cannot belong to a page that navigated away.
99+ // The section's first read runs on the server and reaches the browser inside the document,
100+ // so the document, every response after it and the API itself are all checked for the secret.
100101 await openSection ( page , 'general' )
101- const providers = page . waitForResponse (
102- ( response ) =>
103- response . request ( ) . method ( ) === 'GET' &&
104- response . url ( ) . includes ( '/api/auth/sso/providers' )
102+ const leaks : string [ ] = [ ]
103+ page . on ( 'response' , async ( response ) => {
104+ const body = await response . text ( ) . catch ( ( ) => '' )
105+ if ( body . includes ( clientSecret ) ) leaks . push ( response . url ( ) )
106+ } )
107+ const document = await page . goto ( liveRoute ( `/settings/sso?provider=${ providerId } ` ) )
108+ const html = ( await document ?. text ( ) ) ?? ''
109+ expect ( html , 'the document did not carry the seeded provider' ) . toContain ( providerId )
110+ expect ( html ) . not . toContain ( clientSecret )
111+
112+ const api = await page . request . get (
113+ `/api/auth/sso/providers?organizationId=${ organizationId ( ) } `
105114 )
106- await page . goto ( liveRoute ( '/settings/sso' ) )
107- await expect ( page . getByRole ( 'tab' , { name : 'Domains' } ) ) . toBeAttached ( )
108- const body = await ( await providers ) . text ( )
115+ const body = await api . text ( )
109116 expect ( body ) . not . toContain ( clientSecret )
110117 const listed = (
111118 JSON . parse ( body ) as { providers : { providerId : string ; oidcConfig : string | null } [ ] }
112119 ) . providers . find ( ( provider ) => provider . providerId === providerId )
113120 expect ( listed ?. oidcConfig , 'the seeded provider was not listed' ) . toBeTruthy ( )
114121 expect ( JSON . parse ( listed ?. oidcConfig ?? '{}' ) . clientSecret ) . toBe ( '[REDACTED]' )
122+
123+ // The provider's detail shows the saved secret as a masked fact, never as a value.
124+ await expect ( page . getByRole ( 'heading' , { level : 1 , name : providerId } ) ) . toBeVisible ( )
125+ await expect ( page . getByRole ( 'textbox' , { name : 'Client secret (saved)' } ) ) . toBeVisible ( )
115126 await expect ( page . getByRole ( 'button' , { name : / C o p y C l i e n t s e c r e t / } ) ) . toHaveCount ( 0 )
127+ for ( const input of await page . locator ( 'input' ) . all ( ) )
128+ expect ( await input . inputValue ( ) ) . not . toContain ( clientSecret )
129+
130+ // Saving without retyping the secret asks the server to keep it, never to store a blank.
131+ const registration = page . waitForRequest ( '**/api/auth/sso/register' )
132+ await page . route ( '**/api/auth/sso/register' , ( route ) => route . abort ( ) )
133+ await page . getByRole ( 'textbox' , { name : 'Client ID' } ) . fill ( 'platform-live-client-edited' )
134+ await page . getByRole ( 'button' , { name : 'Save' , exact : true } ) . click ( )
135+ const sent = ( await registration ) . postDataJSON ( ) as { clientSecret ?: string }
136+ expect ( sent . clientSecret ) . toBe ( '[REDACTED]' )
137+ expect ( leaks ) . toEqual ( [ ] )
116138 } finally {
117139 await withLiveDatabase (
118140 ( sql ) => sql `delete from sso_provider where provider_id = ${ providerId } `
0 commit comments