@@ -176,6 +176,84 @@ describe('requestSessionTransferToken', () => {
176176 assert . equal ( response . body . err . error , 'actor_unavailable' ) ;
177177 } ) ;
178178
179+ it ( 'throws 400 with actor_unavailable when id_token is expired and no refresh_token' , async ( ) => {
180+ const expiredIdToken = makeIdToken ( {
181+ exp : Math . floor ( Date . now ( ) / 1000 ) - 3600 ,
182+ } ) ;
183+ const { response } = await setup ( {
184+ sttOptions : {
185+ subject_token : '__test_subject__' ,
186+ subject_token_type : 'urn:mycompany:test-token' ,
187+ } ,
188+ sessionData : {
189+ id_token : expiredIdToken ,
190+ access_token : '__test_access_token__' ,
191+ token_type : 'Bearer' ,
192+ expires_at : Math . floor ( Date . now ( ) / 1000 ) - 3600 ,
193+ } ,
194+ mockTokenResponse : false ,
195+ } ) ;
196+ assert . equal ( response . statusCode , 400 ) ;
197+ assert . equal ( response . body . err . error , 'actor_unavailable' ) ;
198+ } ) ;
199+
200+ it ( 'refreshes expired id_token and uses the fresh one as actor when refresh_token is available' , async ( ) => {
201+ const expiredIdToken = makeIdToken ( {
202+ exp : Math . floor ( Date . now ( ) / 1000 ) - 3600 ,
203+ } ) ;
204+ const freshIdToken = makeIdToken ( ) ;
205+
206+ const router = express . Router ( ) ;
207+ router . use ( auth ( { ...defaultConfig } ) ) ;
208+ router . get ( '/stt' , async ( req , res , next ) => {
209+ try {
210+ const result = await req . oidc . requestSessionTransferToken ( {
211+ subject_token : '__test_subject__' ,
212+ subject_token_type : 'urn:mycompany:test-token' ,
213+ } ) ;
214+ res . json ( result ) ;
215+ } catch ( err ) {
216+ next ( err ) ;
217+ }
218+ } ) ;
219+
220+ server = await createServer ( router ) ;
221+ const jar = request . jar ( ) ;
222+ await request . post ( '/session' , {
223+ baseUrl,
224+ jar,
225+ json : {
226+ id_token : expiredIdToken ,
227+ access_token : '__test_access_token__' ,
228+ refresh_token : '__test_refresh_token__' ,
229+ token_type : 'Bearer' ,
230+ expires_at : Math . floor ( Date . now ( ) / 1000 ) - 3600 ,
231+ } ,
232+ } ) ;
233+
234+ let capturedSttBody ;
235+ // First call: refresh grant; second call: STT exchange
236+ nock ( 'https://op.example.com' )
237+ . post ( '/oauth/token' )
238+ . reply ( 200 , {
239+ access_token : '__new_access_token__' ,
240+ id_token : freshIdToken ,
241+ refresh_token : '__new_refresh_token__' ,
242+ token_type : 'Bearer' ,
243+ expires_in : 86400 ,
244+ } )
245+ . post ( '/oauth/token' )
246+ . reply ( 200 , function ( uri , body ) {
247+ capturedSttBody = qs . parse ( body ) ;
248+ return defaultSTTResponse ( ) ;
249+ } ) ;
250+
251+ const response = await request . get ( '/stt' , { baseUrl, jar, json : true } ) ;
252+ assert . equal ( response . statusCode , 200 ) ;
253+ assert . equal ( capturedSttBody . actor_token , freshIdToken ) ;
254+ assert . equal ( capturedSttBody . actor_token_type , ID_TOKEN_IDENTIFIER ) ;
255+ } ) ;
256+
179257 // -------------------------------------------------------------------------
180258 // subject_token validation (reuses validateSubjectToken)
181259 // -------------------------------------------------------------------------
@@ -268,18 +346,18 @@ describe('requestSessionTransferToken', () => {
268346 assert . equal ( capturedBody . organization , 'org_abc123' ) ;
269347 } ) ;
270348
271- it ( 'sends reason when provided ' , async ( ) => {
349+ it ( 'forwards extra params to the token endpoint ' , async ( ) => {
272350 const { capturedBody } = await setup ( {
273351 sttOptions : {
274352 subject_token : '__test_subject__' ,
275353 subject_token_type : 'urn:mycompany:test-token' ,
276- reason : 'Investigating TCK-4821' ,
354+ extra : { reason : 'Investigating TCK-4821' } ,
277355 } ,
278356 } ) ;
279357 assert . equal ( capturedBody . reason , 'Investigating TCK-4821' ) ;
280358 } ) ;
281359
282- it ( 'omits scope, organization, and reason when not provided' , async ( ) => {
360+ it ( 'omits scope, organization, and extra when not provided' , async ( ) => {
283361 const { capturedBody } = await setup ( {
284362 sttOptions : {
285363 subject_token : '__test_subject__' ,
@@ -288,7 +366,6 @@ describe('requestSessionTransferToken', () => {
288366 } ) ;
289367 assert . isUndefined ( capturedBody . scope ) ;
290368 assert . isUndefined ( capturedBody . organization ) ;
291- assert . isUndefined ( capturedBody . reason ) ;
292369 } ) ;
293370
294371 // -------------------------------------------------------------------------
@@ -319,7 +396,7 @@ describe('requestSessionTransferToken', () => {
319396 ) ;
320397 } ) ;
321398
322- it ( 'falls back to SESSION_TRANSFER_TOKEN_IDENTIFIER when issued_token_type is absent' , async ( ) => {
399+ it ( 'returns empty string for issued_token_type when absent in AS response ' , async ( ) => {
323400 const { response } = await setup ( {
324401 sttOptions : {
325402 subject_token : '__test_subject__' ,
@@ -336,10 +413,7 @@ describe('requestSessionTransferToken', () => {
336413 } ,
337414 } ) ;
338415 assert . equal ( response . statusCode , 200 ) ;
339- assert . equal (
340- response . body . issued_token_type ,
341- SESSION_TRANSFER_TOKEN_IDENTIFIER ,
342- ) ;
416+ assert . equal ( response . body . issued_token_type , '' ) ;
343417 } ) ;
344418
345419 it ( 'result has no access_token field — STT is in session_transfer_token' , async ( ) => {
0 commit comments