1
1
import type { MiddlewareClass , MiddlewareContext , MiddlewareNext } from './Connect.js' ;
2
2
import CookieManager from './CookieManager.js' ;
3
- import {
4
- getSpringCsrfInfo ,
5
- getSpringCsrfTokenHeadersForAuthRequest ,
6
- getSpringCsrfTokenParametersForAuthRequest ,
3
+ import csrfInfoSource , {
7
4
VAADIN_CSRF_HEADER ,
8
- } from './CsrfUtils.js' ;
5
+ clearCsrfInfoMeta ,
6
+ type CsrfInfo ,
7
+ CsrfInfoType ,
8
+ extractCsrfInfoFromMeta ,
9
+ updateCsrfInfoMeta ,
10
+ } from './CsrfInfoSource.js' ;
11
+
12
+ function createHeaders ( headerEntries : ReadonlyArray < readonly [ name : string , value : string ] > ) : Headers {
13
+ const headers = new Headers ( ) ;
14
+ for ( const [ name , value ] of headerEntries ) {
15
+ headers . append ( name , value ) ;
16
+ }
17
+ return headers ;
18
+ }
9
19
10
20
const JWT_COOKIE_NAME = 'jwt.headerAndPayload' ;
11
21
12
- function getSpringCsrfTokenFromResponseBody ( body : string ) : Record < string , string > {
22
+ async function getCsrfInfoFromResponseBody ( body : string ) : Promise < CsrfInfo > {
13
23
const doc = new DOMParser ( ) . parseFromString ( body , 'text/html' ) ;
14
- return getSpringCsrfInfo ( doc ) ;
24
+ return extractCsrfInfoFromMeta ( doc ) ;
15
25
}
16
26
17
- function clearSpringCsrfMetaTags ( ) {
18
- Array . from (
19
- document . head . querySelectorAll ( 'meta[name="_csrf"], meta[name="_csrf_header"], meta[name="_csrf_parameter"]' ) ,
20
- ) . forEach ( ( el ) => el . remove ( ) ) ;
21
- }
22
-
23
- function updateSpringCsrfMetaTags ( springCsrfInfo : Record < string , string > ) {
24
- clearSpringCsrfMetaTags ( ) ;
25
- const headerNameMeta : HTMLMetaElement = document . createElement ( 'meta' ) ;
26
- headerNameMeta . name = '_csrf_header' ;
27
- headerNameMeta . content = springCsrfInfo . _csrf_header ;
28
- document . head . appendChild ( headerNameMeta ) ;
29
- const tokenMeta : HTMLMetaElement = document . createElement ( 'meta' ) ;
30
- tokenMeta . name = '_csrf' ;
31
- tokenMeta . content = springCsrfInfo . _csrf ;
32
- document . head . appendChild ( tokenMeta ) ;
33
- }
34
-
35
- const getVaadinCsrfTokenFromResponseBody = ( body : string ) : string | undefined => {
36
- const match = / w i n d o w \. V a a d i n = \{ T y p e S c r i p t : \{ " c s r f T o k e n " : " ( [ 0 - 9 a - z A - Z \\ - ] { 36 } ) " \} \} ; / iu. exec ( body ) ;
37
- return match ? match [ 1 ] : undefined ;
38
- } ;
39
-
40
- async function updateCsrfTokensBasedOnResponse ( response : Response ) : Promise < string | undefined > {
27
+ async function updateCsrfTokensBasedOnResponse ( response : Response ) : Promise < void > {
41
28
const responseText = await response . text ( ) ;
42
- const token = getVaadinCsrfTokenFromResponseBody ( responseText ) ;
43
- const springCsrfTokenInfo = getSpringCsrfTokenFromResponseBody ( responseText ) ;
44
- updateSpringCsrfMetaTags ( springCsrfTokenInfo ) ;
45
-
46
- return token ;
29
+ const csrfInfo = await getCsrfInfoFromResponseBody ( responseText ) ;
30
+ updateCsrfInfoMeta ( csrfInfo , document ) ;
47
31
}
48
32
49
- async function doFetchLogout ( logoutUrl : URL | string , headers : Record < string , string > ) {
33
+ async function doFetchLogout (
34
+ logoutUrl : URL | string ,
35
+ headerEntries : ReadonlyArray < readonly [ name : string , value : string ] > ,
36
+ ) {
37
+ const headers = createHeaders ( headerEntries ) ;
50
38
const response = await fetch ( logoutUrl , { headers, method : 'POST' } ) ;
51
39
if ( ! response . ok ) {
52
40
throw new Error ( `failed to logout with response ${ response . status } ` ) ;
53
41
}
54
42
55
43
await updateCsrfTokensBasedOnResponse ( response ) ;
44
+ csrfInfoSource . reset ( ) ;
56
45
57
46
return response ;
58
47
}
59
48
60
- async function doFormLogout ( url : URL | string , parameters : Record < string , string > ) : Promise < void > {
49
+ async function doFormLogout (
50
+ url : URL | string ,
51
+ formDataEntries : ReadonlyArray < readonly [ name : string , value : string ] > ,
52
+ ) : Promise < void > {
61
53
const logoutUrl = typeof url === 'string' ? url : url . toString ( ) ;
62
54
63
55
// Create form to send POST request
@@ -67,7 +59,7 @@ async function doFormLogout(url: URL | string, parameters: Record<string, string
67
59
form . style . display = 'none' ;
68
60
69
61
// Add data to form as hidden input fields
70
- for ( const [ name , value ] of Object . entries ( parameters ) ) {
62
+ for ( const [ name , value ] of formDataEntries ) {
71
63
const input = document . createElement ( 'input' ) ;
72
64
input . setAttribute ( 'type' , 'hidden' ) ;
73
65
input . setAttribute ( 'name' , name ) ;
@@ -96,17 +88,18 @@ async function doLogout(doc: Document, options?: LogoutOptions): Promise<Respons
96
88
const shouldSubmitFormLogout = ! options ?. navigate && ! options ?. onSuccess ;
97
89
// this assumes the default Spring Security logout configuration (handler URL)
98
90
const logoutUrl = options ?. logoutUrl ?? 'logout' ;
91
+ const csrfInfo = doc === document ? await csrfInfoSource . get ( ) : await extractCsrfInfoFromMeta ( doc ) ;
99
92
if ( shouldSubmitFormLogout ) {
100
- const parameters = getSpringCsrfTokenParametersForAuthRequest ( doc ) ;
101
- await doFormLogout ( logoutUrl , parameters ) ;
93
+ const formDataEntries = csrfInfo . type === CsrfInfoType . SPRING ? csrfInfo . formDataEntries : [ ] ;
94
+ await doFormLogout ( logoutUrl , formDataEntries ) ;
102
95
// This should never be reached, as form submission will navigate away
103
96
return new Response ( null , {
104
97
status : 500 ,
105
98
statusText : 'Form submission did not navigate away.' ,
106
99
} as ResponseInit ) ;
107
100
}
108
- const headers = getSpringCsrfTokenHeadersForAuthRequest ( doc ) ;
109
- return await doFetchLogout ( logoutUrl , headers ) ;
101
+ const headerEntries = csrfInfo . type === CsrfInfoType . SPRING ? csrfInfo . headerEntries : [ ] ;
102
+ return await doFetchLogout ( logoutUrl , headerEntries ) ;
110
103
}
111
104
112
105
export interface LoginResult {
@@ -200,8 +193,9 @@ export async function login(username: string, password: string, options?: LoginO
200
193
data . append ( 'password' , password ) ;
201
194
202
195
const loginProcessingUrl = options ?. loginProcessingUrl ?? 'login' ;
203
- const headers = getSpringCsrfTokenHeadersForAuthRequest ( document ) ;
204
- headers . source = 'typescript' ;
196
+ const csrfInfo = await csrfInfoSource . get ( ) ;
197
+ const headers = createHeaders ( csrfInfo . headerEntries ) ;
198
+ headers . append ( 'source' , 'typescript' ) ;
205
199
const response = await fetch ( loginProcessingUrl , {
206
200
body : data ,
207
201
headers,
@@ -217,16 +211,19 @@ export async function login(username: string, password: string, options?: LoginO
217
211
const loginSuccessful = response . ok && result === 'success' ;
218
212
219
213
if ( loginSuccessful ) {
220
- const vaadinCsrfToken = response . headers . get ( 'Vaadin-CSRF' ) ?? undefined ;
221
-
222
214
const springCsrfHeader = response . headers . get ( 'Spring-CSRF-header' ) ?? undefined ;
223
215
const springCsrfToken = response . headers . get ( 'Spring-CSRF-token' ) ?? undefined ;
224
216
if ( springCsrfHeader && springCsrfToken ) {
225
- const springCsrfTokenInfo : Record < string , string > = { } ;
226
- springCsrfTokenInfo . _csrf = springCsrfToken ;
227
- // eslint-disable-next-line camelcase
228
- springCsrfTokenInfo . _csrf_header = springCsrfHeader ;
229
- updateSpringCsrfMetaTags ( springCsrfTokenInfo ) ;
217
+ updateCsrfInfoMeta (
218
+ {
219
+ headerEntries : [ [ springCsrfHeader , springCsrfToken ] ] ,
220
+ formDataEntries : [ ] ,
221
+ type : CsrfInfoType . SPRING ,
222
+ timestamp : Date . now ( ) ,
223
+ } ,
224
+ document ,
225
+ ) ;
226
+ csrfInfoSource . reset ( ) ;
230
227
}
231
228
232
229
if ( options ?. onSuccess ) {
@@ -242,7 +239,6 @@ export async function login(username: string, password: string, options?: LoginO
242
239
defaultUrl,
243
240
error : false ,
244
241
redirectUrl : savedUrl ,
245
- token : vaadinCsrfToken ,
246
242
} ;
247
243
}
248
244
return {
@@ -279,7 +275,8 @@ export async function logout(options?: LogoutOptions): Promise<void> {
279
275
response = await doLogout ( doc , options ) ;
280
276
} catch ( error ) {
281
277
// clear the token if the call fails
282
- clearSpringCsrfMetaTags ( ) ;
278
+ clearCsrfInfoMeta ( document ) ;
279
+ csrfInfoSource . reset ( ) ;
283
280
throw error ;
284
281
}
285
282
} finally {
0 commit comments