Skip to content

Commit f451dc2

Browse files
flacoonbandi34
authored andcommitted
fix(csrf): stabilize token sync and harden session handling
- fixes CSRF mismatches on /api/shellCommand.php, /api/previewCamera.php, and /api/capture.php - adds /api/csrf.php endpoint to fetch the current session CSRF token - replaces static frontend token usage with token refresh + single retry on 403 - migrates affected requests to ajaxWithCsrf (including applyEffects/applyVideoEffects) - improves 403 handling: reload only for explicit Invalid CSRF token responses - moves PHP session storage outside the web root and cleans up legacy public session path - reduces token drift after session changes and prevents session file exposure
1 parent 26012f9 commit f451dc2

File tree

6 files changed

+347
-58
lines changed

6 files changed

+347
-58
lines changed

api/csrf.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
require_once '../lib/boot.php';
4+
5+
header('Content-Type: application/json');
6+
header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
7+
header('Pragma: no-cache');
8+
9+
echo json_encode([
10+
'key' => 'csrf',
11+
'token' => $_SESSION['csrf'] ?? '',
12+
]);
13+
14+
exit();

assets/js/admin/index.js

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/* globals photoboothTools csrf */
1+
/* globals photoboothTools */
22
/* eslint-env browser */
33
$(function () {
44
initDirtyTracking();
@@ -288,19 +288,24 @@ const shellCommand = function ($mode, $filename = '') {
288288
mode: $mode,
289289
filename: $filename
290290
};
291-
if (typeof csrf !== 'undefined') {
292-
command[csrf.key] = csrf.token;
293-
}
294291

295292
photoboothTools.console.log('Run' + $mode);
296293

297-
jQuery
298-
.post('../api/shellCommand.php', command)
294+
photoboothTools
295+
.ajaxWithCsrf({
296+
url: '../api/shellCommand.php',
297+
method: 'POST',
298+
data: command
299+
})
299300
.done(function (result) {
300301
photoboothTools.console.log($mode, 'result: ', result);
301302
})
302-
.fail(function (xhr, status, result) {
303-
photoboothTools.console.log($mode, 'result: ', result);
303+
.fail(function (xhr, status, errorThrown) {
304+
if (photoboothTools.isCsrfErrorResponse(xhr)) {
305+
photoboothTools.handleCsrfMismatch('../api/shellCommand.php');
306+
return;
307+
}
308+
photoboothTools.console.log($mode, 'result: ', errorThrown);
304309
});
305310
};
306311

assets/js/core.js

Lines changed: 62 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -471,19 +471,23 @@ const photoBooth = (function () {
471471
mode: cmd,
472472
filename: file
473473
};
474-
if (typeof csrf !== 'undefined') {
475-
command[csrf.key] = csrf.token;
476-
}
477-
478474
photoboothTools.console.log('Run', cmd);
479475

480-
jQuery
481-
.post(environment.publicFolders.api + '/shellCommand.php', command)
476+
photoboothTools
477+
.ajaxWithCsrf({
478+
url: environment.publicFolders.api + '/shellCommand.php',
479+
method: 'POST',
480+
data: command
481+
})
482482
.done(function (result) {
483483
photoboothTools.console.log(cmd, 'result: ', result);
484484
})
485-
.fail(function (xhr, status, result) {
486-
photoboothTools.console.log(cmd, 'result: ', result);
485+
.fail(function (xhr, status, errorThrown) {
486+
if (photoboothTools.isCsrfErrorResponse(xhr)) {
487+
photoboothTools.handleCsrfMismatch(environment.publicFolders.api + '/shellCommand.php');
488+
return;
489+
}
490+
photoboothTools.console.log(cmd, 'result: ', errorThrown);
487491
});
488492
};
489493

@@ -668,9 +672,10 @@ const photoBooth = (function () {
668672
api.callTakePicApi = async (data, retry = 0) => {
669673
startTime = new Date().getTime();
670674
photoboothTools.console.logDev('Capture image.');
671-
jQuery
672-
.post({
675+
photoboothTools
676+
.ajaxWithCsrf({
673677
url: environment.publicFolders.api + '/capture.php',
678+
method: 'POST',
674679
data: data,
675680
timeout: 25000
676681
})
@@ -836,6 +841,10 @@ const photoBooth = (function () {
836841
})
837842
.fail(async (xhr, status, result) => {
838843
try {
844+
if (photoboothTools.isCsrfErrorResponse(xhr)) {
845+
photoboothTools.handleCsrfMismatch(environment.publicFolders.api + '/capture.php');
846+
return;
847+
}
839848
endTime = new Date().getTime();
840849
totalTime = endTime - startTime;
841850
api.cheese.destroy();
@@ -869,9 +878,10 @@ const photoBooth = (function () {
869878
videoAnimation.show();
870879
}
871880
startTime = new Date().getTime();
872-
jQuery
873-
.post({
881+
photoboothTools
882+
.ajaxWithCsrf({
874883
url: environment.publicFolders.api + '/capture.php',
884+
method: 'POST',
875885
data: data,
876886
timeout: 25000
877887
})
@@ -901,6 +911,10 @@ const photoBooth = (function () {
901911
})
902912
.fail(function (xhr, status, result) {
903913
try {
914+
if (photoboothTools.isCsrfErrorResponse(xhr)) {
915+
photoboothTools.handleCsrfMismatch(environment.publicFolders.api + '/capture.php');
916+
return;
917+
}
904918
api.cheese.destroy();
905919
if (result === null || result === undefined || typeof result === 'string') {
906920
result = { error: result || 'Unexpected error: result is null or undefined' };
@@ -995,17 +1009,19 @@ const photoBooth = (function () {
9951009
preloadImage.src = tempImageUrl;
9961010
}
9971011

998-
$.ajax({
999-
method: 'POST',
1000-
url: environment.publicFolders.api + '/applyEffects.php',
1001-
data: {
1002-
file: result.file,
1003-
filter: imgFilter,
1004-
style: api.photoStyle,
1005-
collageLayout: api.collageLayout,
1006-
collageLimit: api.collageLimit
1007-
},
1008-
success: (data) => {
1012+
photoboothTools
1013+
.ajaxWithCsrf({
1014+
method: 'POST',
1015+
url: environment.publicFolders.api + '/applyEffects.php',
1016+
data: {
1017+
file: result.file,
1018+
filter: imgFilter,
1019+
style: api.photoStyle,
1020+
collageLayout: api.collageLayout,
1021+
collageLimit: api.collageLimit
1022+
}
1023+
})
1024+
.done((data) => {
10091025
try {
10101026
setFiltersEnabled(true);
10111027
photoboothTools.console.log(api.photoStyle + ' processed', data);
@@ -1032,14 +1048,17 @@ const photoBooth = (function () {
10321048
photoboothTools.console.log('processPic.success: unexpected error:', error);
10331049
api.errorPic({ error: error.message || 'Unexpected error processing picture' });
10341050
}
1035-
},
1036-
error: (jqXHR, textStatus) => {
1051+
})
1052+
.fail((jqXHR, textStatus) => {
1053+
if (photoboothTools.isCsrfErrorResponse(jqXHR)) {
1054+
photoboothTools.handleCsrfMismatch(environment.publicFolders.api + '/applyEffects.php');
1055+
return;
1056+
}
10371057
setFiltersEnabled(true);
10381058
api.errorPic({
10391059
error: 'Request failed: ' + textStatus
10401060
});
1041-
}
1042-
});
1061+
});
10431062
};
10441063

10451064
api.processVideo = function (result) {
@@ -1053,13 +1072,15 @@ const photoBooth = (function () {
10531072
'<i class="' + config.icons.spinner + '"></i><br>' + photoboothTools.getTranslation('busyVideo')
10541073
);
10551074

1056-
$.ajax({
1057-
method: 'POST',
1058-
url: environment.publicFolders.api + '/applyVideoEffects.php',
1059-
data: {
1060-
file: result.file
1061-
},
1062-
success: (data) => {
1075+
photoboothTools
1076+
.ajaxWithCsrf({
1077+
method: 'POST',
1078+
url: environment.publicFolders.api + '/applyVideoEffects.php',
1079+
data: {
1080+
file: result.file
1081+
}
1082+
})
1083+
.done((data) => {
10631084
try {
10641085
photoboothTools.console.log('video processed', data);
10651086
endTime = new Date().getTime();
@@ -1104,13 +1125,16 @@ const photoBooth = (function () {
11041125
photoboothTools.console.log('processVideo.success: unexpected error:', error);
11051126
api.errorPic({ error: error.message || 'Unexpected error processing video' });
11061127
}
1107-
},
1108-
error: (jqXHR, textStatus) => {
1128+
})
1129+
.fail((jqXHR, textStatus) => {
1130+
if (photoboothTools.isCsrfErrorResponse(jqXHR)) {
1131+
photoboothTools.handleCsrfMismatch(environment.publicFolders.api + '/applyVideoEffects.php');
1132+
return;
1133+
}
11091134
api.errorPic({
11101135
error: 'Request failed: ' + textStatus
11111136
});
1112-
}
1113-
});
1137+
});
11141138
};
11151139

11161140
api.renderChroma = function (filename) {

assets/js/preview.js

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/* eslint n/no-unsupported-features/node-builtins: "off" */
22

3-
/* globals photoBooth photoboothTools csrf */
3+
/* globals photoBooth photoboothTools */
44

55
function getPreviewUrlWithCacheBusting() {
66
const url = getBasePreviewUrl();
@@ -147,18 +147,25 @@ const photoboothPreview = (function () {
147147
api.runCmd = function (mode) {
148148
const dataVideo = {
149149
play: mode,
150-
pid: pid,
151-
[csrf.key]: csrf.token
150+
pid: pid
152151
};
153152

154-
jQuery
155-
.post('api/previewCamera.php', dataVideo)
153+
photoboothTools
154+
.ajaxWithCsrf({
155+
url: 'api/previewCamera.php',
156+
method: 'POST',
157+
data: dataVideo
158+
})
156159
.done(function (result) {
157160
photoboothTools.console.log('Preview: ' + dataVideo.play + ' webcam successfully.');
158161
pid = result.pid;
159162
})
160163
// eslint-disable-next-line no-unused-vars
161164
.fail(function (xhr, status, result) {
165+
if (photoboothTools.isCsrfErrorResponse(xhr)) {
166+
photoboothTools.handleCsrfMismatch('api/previewCamera.php');
167+
return;
168+
}
162169
photoboothTools.console.log('ERROR: Preview: Failed to ' + dataVideo.play + ' webcam!');
163170
});
164171
};

0 commit comments

Comments
 (0)