Skip to content

Commit 52ca1c9

Browse files
tijnemasmnandre
authored andcommitted
[LiveComponent] Update CSRF token after component request
1 parent b066280 commit 52ca1c9

File tree

8 files changed

+53
-2
lines changed

8 files changed

+53
-2
lines changed

src/LiveComponent/assets/dist/Backend/Backend.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export interface BackendInterface {
1313
}, files: {
1414
[key: string]: FileList;
1515
}): BackendRequest;
16+
updateCsrfToken(csrfToken: string): void;
1617
}
1718
export interface BackendAction {
1819
name: string;
@@ -28,4 +29,5 @@ export default class implements BackendInterface {
2829
}, files: {
2930
[key: string]: FileList;
3031
}): BackendRequest;
32+
updateCsrfToken(csrfToken: string): void;
3133
}

src/LiveComponent/assets/dist/Backend/RequestBuilder.d.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type { BackendAction, ChildrenFingerprints } from './Backend';
22
export default class {
33
private url;
44
private method;
5-
private readonly csrfToken;
5+
private csrfToken;
66
constructor(url: string, method?: 'get' | 'post', csrfToken?: string | null);
77
buildRequest(props: any, actions: BackendAction[], updated: {
88
[key: string]: any;
@@ -15,4 +15,5 @@ export default class {
1515
fetchOptions: RequestInit;
1616
};
1717
private willDataFitInUrl;
18+
updateCsrfToken(csrfToken: string): void;
1819
}

src/LiveComponent/assets/dist/live_controller.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2052,6 +2052,9 @@ class Component {
20522052
return response;
20532053
}
20542054
this.processRerender(html, backendResponse);
2055+
if (this.element.dataset.liveCsrfValue) {
2056+
this.backend.updateCsrfToken(this.element.dataset.liveCsrfValue);
2057+
}
20552058
this.backendRequest = null;
20562059
thisPromiseResolve(backendResponse);
20572060
if (this.isRequestPending) {
@@ -2325,6 +2328,9 @@ class RequestBuilder {
23252328
const urlEncodedJsonData = new URLSearchParams(propsJson + updatedJson + childrenJson + propsFromParentJson).toString();
23262329
return (urlEncodedJsonData + params.toString()).length < 1500;
23272330
}
2331+
updateCsrfToken(csrfToken) {
2332+
this.csrfToken = csrfToken;
2333+
}
23282334
}
23292335

23302336
class Backend {
@@ -2335,6 +2341,9 @@ class Backend {
23352341
const { url, fetchOptions } = this.requestBuilder.buildRequest(props, actions, updated, children, updatedPropsFromParent, files);
23362342
return new BackendRequest(fetch(url, fetchOptions), actions.map((backendAction) => backendAction.name), Object.keys(updated));
23372343
}
2344+
updateCsrfToken(csrfToken) {
2345+
this.requestBuilder.updateCsrfToken(csrfToken);
2346+
}
23382347
}
23392348

23402349
class StimulusElementDriver {

src/LiveComponent/assets/src/Backend/Backend.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export interface BackendInterface {
1515
updatedPropsFromParent: { [key: string]: any },
1616
files: { [key: string]: FileList }
1717
): BackendRequest;
18+
updateCsrfToken(csrfToken: string): void;
1819
}
1920

2021
export interface BackendAction {
@@ -52,4 +53,8 @@ export default class implements BackendInterface {
5253
Object.keys(updated)
5354
);
5455
}
56+
57+
updateCsrfToken(csrfToken: string) {
58+
this.requestBuilder.updateCsrfToken(csrfToken);
59+
}
5560
}

src/LiveComponent/assets/src/Backend/RequestBuilder.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import type { BackendAction, ChildrenFingerprints } from './Backend';
33
export default class {
44
private url: string;
55
private method: 'get' | 'post';
6-
private readonly csrfToken: string | null;
6+
private csrfToken: string | null;
77

88
constructor(url: string, method: 'get' | 'post' = 'post', csrfToken: string | null = null) {
99
this.url = url;
@@ -117,4 +117,8 @@ export default class {
117117
// if the URL gets remotely close to 2000 chars, it may not fit
118118
return (urlEncodedJsonData + params.toString()).length < 1500;
119119
}
120+
121+
updateCsrfToken(csrfToken: string) {
122+
this.csrfToken = csrfToken;
123+
}
120124
}

src/LiveComponent/assets/src/Component/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,11 @@ export default class Component {
329329

330330
this.processRerender(html, backendResponse);
331331

332+
// Store updated csrf token
333+
if (this.element.dataset.liveCsrfValue) {
334+
this.backend.updateCsrfToken(this.element.dataset.liveCsrfValue);
335+
}
336+
332337
// finally resolve this promise
333338
this.backendRequest = null;
334339
thisPromiseResolve(backendResponse);

src/LiveComponent/assets/test/controller/render.test.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -630,4 +630,23 @@ describe('LiveController rendering Tests', () => {
630630
// verify the selectedIndex of the select option 2 is 0
631631
expect(selectOption2.selectedIndex).toBe(0);
632632
});
633+
634+
it('backend will have a new csrf token', async () => {
635+
const test = await createTest(
636+
{},
637+
(data: any) => `
638+
<div ${initComponent(data)} data-live-csrf-value="${data.csrf}">
639+
</div>
640+
`
641+
);
642+
643+
test.expectsAjaxCall().serverWillChangeProps((data: any) => {
644+
// change csrf token
645+
data.csrf = 'Hello';
646+
});
647+
648+
await test.component.render();
649+
650+
expect(test.mockedBackend.csrfToken).toEqual('Hello');
651+
});
633652
});

src/LiveComponent/assets/test/tools.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,8 @@ class FunctionalTest {
9898
class MockedBackend implements BackendInterface {
9999
private expectedMockedAjaxCalls: Array<MockedAjaxCall> = [];
100100

101+
public csrfToken: string | null = null;
102+
101103
addMockedAjaxCall(mock: MockedAjaxCall) {
102104
this.expectedMockedAjaxCalls.push(mock);
103105
}
@@ -139,6 +141,10 @@ class MockedBackend implements BackendInterface {
139141
return matchedMock.createBackendRequest();
140142
}
141143

144+
updateCsrfToken(csrfToken: string) {
145+
this.csrfToken = csrfToken;
146+
}
147+
142148
getExpectedMockedAjaxCalls(): Array<MockedAjaxCall> {
143149
return this.expectedMockedAjaxCalls;
144150
}

0 commit comments

Comments
 (0)