@@ -16,35 +16,51 @@ type AuthUser = {
1616 totpEnabled ?: boolean
1717 webAuthnEnabled ?: boolean
1818}
19- type TokenSetter = ( token : string ) => void
19+ type AuthWithTokens = {
20+ setUserToken ?: ( token : string , refreshToken ?: string ) => Promise < unknown >
21+ tokenStrategy ?: {
22+ token ?: {
23+ set ?: ( token : string ) => unknown
24+ sync ?: ( ) => unknown
25+ }
26+ refreshToken ?: {
27+ set ?: ( token : string ) => unknown
28+ }
29+ }
30+ }
2031
2132function asRecord ( value : unknown ) : Record < string , unknown > | undefined {
2233 return value && typeof value === 'object' ? ( value as Record < string , unknown > ) : undefined
2334}
2435
25- function isTokenSetter ( value : unknown ) : value is TokenSetter {
26- return typeof value === 'function'
27- }
28-
2936function getAuthUser ( auth : unknown ) : AuthUser {
3037 const user = asRecord ( asRecord ( auth ) ?. user )
3138 const unwrappedUser = asRecord ( user ?. value ) || user
3239 return ( unwrappedUser || { } ) as AuthUser
3340}
3441
35- function setRefreshToken ( auth : unknown , refreshToken : string ) : void {
36- const tokenStrategy = asRecord ( asRecord ( auth ) ?. tokenStrategy )
37- const refreshTokenStore = asRecord ( tokenStrategy ?. refreshToken )
38- const set = refreshTokenStore ?. set
39- if ( isTokenSetter ( set ) ) set ( refreshToken )
42+ function extractStepUpTokens ( response : unknown ) : MfaStepUpResponse {
43+ const payload = extractHttpPayload ( response )
44+ const access_token = typeof payload . access_token === 'string' ? payload . access_token . trim ( ) : ''
45+ const refresh_token = typeof payload . refresh_token === 'string' ? payload . refresh_token . trim ( ) : undefined
46+ return { access_token , refresh_token }
4047}
4148
42- function setAuthTokens ( auth : unknown , response : MfaStepUpResponse ) : void {
43- const tokenStrategy = asRecord ( asRecord ( auth ) ?. tokenStrategy )
44- const tokenStore = asRecord ( tokenStrategy ?. token )
45- const setAccessToken = tokenStore ?. set
46- if ( isTokenSetter ( setAccessToken ) ) setAccessToken ( response . access_token )
47- if ( response . refresh_token ) setRefreshToken ( auth , response . refresh_token )
49+ async function setAuthTokens ( auth : unknown , response : MfaStepUpResponse ) : Promise < void > {
50+ const authApi = auth as AuthWithTokens
51+ if ( typeof authApi ?. setUserToken === 'function' ) {
52+ await authApi . setUserToken ( response . access_token , response . refresh_token )
53+ return
54+ }
55+
56+ const tokenStrategy = authApi ?. tokenStrategy
57+ if ( typeof tokenStrategy ?. token ?. set === 'function' ) {
58+ tokenStrategy . token . set ( response . access_token )
59+ tokenStrategy . token . sync ?.( )
60+ }
61+ if ( response . refresh_token && typeof tokenStrategy ?. refreshToken ?. set === 'function' ) {
62+ tokenStrategy . refreshToken . set ( response . refresh_token )
63+ }
4864}
4965
5066function extractHttpPayload ( response : unknown ) : Record < string , unknown > {
@@ -124,15 +140,16 @@ export default defineNuxtPlugin((nuxtApp) => {
124140 if ( ! options || ! challengeId ) throw new Error ( 'Invalid WebAuthn step-up begin payload' )
125141
126142 const response = ( await startAuthentication ( options ) ) as AuthenticationResponseJSON
127- const finish = ( await post ( '/core/auth/mfa/webauthn/finish' , {
143+ const finish = await post ( '/core/auth/mfa/webauthn/finish' , {
128144 body : {
129145 challengeId,
130146 response,
131147 } ,
132- } ) ) as MfaStepUpResponse
148+ } )
149+ const finishTokens = extractStepUpTokens ( finish )
133150
134- if ( ! finish ? .access_token ) throw new Error ( 'Invalid WebAuthn step-up response (missing access_token)' )
135- setAuthTokens ( auth , finish )
151+ if ( ! finishTokens . access_token ) throw new Error ( 'Invalid WebAuthn step-up response (missing access_token)' )
152+ await setAuthTokens ( auth , finishTokens )
136153 return
137154 } catch ( error ) {
138155 if ( user ?. totpEnabled !== true ) throw error
@@ -175,15 +192,16 @@ export default defineNuxtPlugin((nuxtApp) => {
175192 . onDismiss ( ( ) => reject ( new Error ( 'MFA step-up dismissed' ) ) )
176193 } )
177194
178- const response = ( await post ( '/core/auth/mfa/step-up' , {
195+ const response = await post ( '/core/auth/mfa/step-up' , {
179196 body : {
180197 otpCode : String ( otpCode || '' ) . trim ( ) ,
181198 } ,
182- } ) ) as MfaStepUpResponse
199+ } )
200+ const tokens = extractStepUpTokens ( response )
183201
184- if ( ! response ? .access_token ) throw new Error ( 'Invalid step-up response (missing access_token)' )
202+ if ( ! tokens . access_token ) throw new Error ( 'Invalid step-up response (missing access_token)' )
185203
186- setAuthTokens ( auth , response )
204+ await setAuthTokens ( auth , tokens )
187205 } else {
188206 const password = await new Promise < string > ( ( resolve , reject ) => {
189207 $q . dialog ( {
@@ -207,15 +225,16 @@ export default defineNuxtPlugin((nuxtApp) => {
207225 const post = rawHttpRef . $post
208226 if ( typeof post !== 'function' ) throw new Error ( 'HTTP client not ready' )
209227
210- const response = ( await post ( '/core/auth/mfa/step-up' , {
228+ const response = await post ( '/core/auth/mfa/step-up' , {
211229 body : {
212230 password : String ( password || '' ) ,
213231 } ,
214- } ) ) as MfaStepUpResponse
232+ } )
233+ const tokens = extractStepUpTokens ( response )
215234
216- if ( ! response ? .access_token ) throw new Error ( 'Invalid step-up response (missing access_token)' )
235+ if ( ! tokens . access_token ) throw new Error ( 'Invalid step-up response (missing access_token)' )
217236
218- setAuthTokens ( auth , response )
237+ await setAuthTokens ( auth , tokens )
219238 }
220239
221240 // Refresh user payload so subsequent UI has updated session info.
@@ -233,12 +252,13 @@ export default defineNuxtPlugin((nuxtApp) => {
233252 }
234253 }
235254
236- const PATCH_MARK = '__sesameMfaPatched__ '
255+ const WRAP_MARK = '__sesameMfaWrapped__ '
237256
238257 const wrap = ( fn : unknown , kind : 'read' | 'write' ) => {
239258 if ( typeof fn !== 'function' ) return fn
240- const httpFn = fn as HttpMethod
241- return async ( ...args : unknown [ ] ) => {
259+ const httpFn = fn as HttpMethod & { [ WRAP_MARK ] ?: boolean }
260+ if ( httpFn [ WRAP_MARK ] ) return httpFn
261+ const wrapped = async ( ...args : unknown [ ] ) => {
242262 const url = args ?. [ 0 ]
243263 // Never intercept the step-up endpoint itself.
244264 if (
@@ -260,22 +280,23 @@ export default defineNuxtPlugin((nuxtApp) => {
260280 return await httpFn ( ...args )
261281 }
262282 }
283+ ; ( wrapped as HttpMethod & { [ WRAP_MARK ] ?: boolean } ) [ WRAP_MARK ] = true
284+ return wrapped
263285 }
264286
265287 const install = ( ) => {
266288 const rawHttp = ( nuxtApp as unknown as { $http ?: HttpClient } ) . $http
267289 if ( ! rawHttp ) return
268290 rawHttpRef = rawHttp
269291
270- if ( rawHttp [ PATCH_MARK ] ) return
271- rawHttp [ PATCH_MARK ] = true
272-
273292 // Patch common methods in-place (no reassignment of `$http`, which is getter-only).
274293 // Important: we ONLY patch write methods so the step-up modal appears only on "save"-like actions.
294+ // Re-apply when module plugins replace $http methods after our first install.
275295 for ( const key of [ '$post' , '$put' , '$patch' , '$delete' , 'post' , 'put' , 'patch' , 'delete' ] ) {
276296 const method = rawHttp [ key ]
277297 if ( typeof method === 'function' ) {
278- rawHttp [ key ] = wrap ( ( method as HttpMethod ) . bind ( rawHttp ) , 'write' )
298+ const bound = ( method as HttpMethod ) . bind ( rawHttp )
299+ rawHttp [ key ] = wrap ( bound , 'write' )
279300 }
280301 }
281302 }
0 commit comments