Security Fix 1: There was not enough permission checks. Moved migrations to Admin Panel/Settings/Cron.

Thanks to [Joshua Rogers](https://joshua.hu) of [Aisle Research](https://aisle.com) and xet7.
This commit is contained in:
Lauri Ojansivu 2026-01-06 00:15:16 +02:00
parent d6834d0287
commit cbb1cd78de
18 changed files with 397 additions and 1805 deletions

View file

@ -2,6 +2,14 @@ import { ReactiveCache } from '/imports/reactiveCache';
import { TAPi18n } from '/imports/i18n';
import { ALLOWED_WAIT_SPINNERS } from '/config/const';
import LockoutSettings from '/models/lockoutSettings';
import {
cronMigrationProgress,
cronMigrationStatus,
cronMigrationCurrentStep,
cronMigrationSteps,
cronIsMigrating,
cronJobs
} from '/imports/cronMigrationClient';
BlazeComponent.extendComponent({
@ -115,15 +123,27 @@ BlazeComponent.extendComponent({
// Cron settings helpers
migrationStatus() {
return TAPi18n.__('idle'); // Placeholder
return cronMigrationStatus.get() || TAPi18n.__('idle');
},
migrationProgress() {
return 0; // Placeholder
return cronMigrationProgress.get() || 0;
},
migrationCurrentStep() {
return cronMigrationCurrentStep.get() || '';
},
isMigrating() {
return cronIsMigrating.get() || false;
},
migrationSteps() {
return cronMigrationSteps.get() || [];
},
cronJobs() {
return []; // Placeholder
return cronJobs.get() || [];
},
setLoading(w) {
@ -169,7 +189,9 @@ BlazeComponent.extendComponent({
// Event handlers for cron settings
'click button.js-start-all-migrations'(event) {
event.preventDefault();
Meteor.call('startAllMigrations', (error, result) => {
this.setLoading(true);
Meteor.call('cron.startAllMigrations', (error, result) => {
this.setLoading(false);
if (error) {
alert(TAPi18n.__('migration-start-failed') + ': ' + error.reason);
} else {
@ -180,7 +202,9 @@ BlazeComponent.extendComponent({
'click button.js-pause-all-migrations'(event) {
event.preventDefault();
Meteor.call('pauseAllMigrations', (error, result) => {
this.setLoading(true);
Meteor.call('cron.pauseAllMigrations', (error, result) => {
this.setLoading(false);
if (error) {
alert(TAPi18n.__('migration-pause-failed') + ': ' + error.reason);
} else {
@ -192,7 +216,9 @@ BlazeComponent.extendComponent({
'click button.js-stop-all-migrations'(event) {
event.preventDefault();
if (confirm(TAPi18n.__('migration-stop-confirm'))) {
Meteor.call('stopAllMigrations', (error, result) => {
this.setLoading(true);
Meteor.call('cron.stopAllMigrations', (error, result) => {
this.setLoading(false);
if (error) {
alert(TAPi18n.__('migration-stop-failed') + ': ' + error.reason);
} else {
@ -204,41 +230,28 @@ BlazeComponent.extendComponent({
'click button.js-schedule-board-cleanup'(event) {
event.preventDefault();
Meteor.call('scheduleBoardCleanup', (error, result) => {
if (error) {
alert(TAPi18n.__('board-cleanup-failed') + ': ' + error.reason);
} else {
alert(TAPi18n.__('board-cleanup-scheduled'));
}
});
// Placeholder - board cleanup scheduling
alert(TAPi18n.__('board-cleanup-scheduled'));
},
'click button.js-schedule-board-archive'(event) {
event.preventDefault();
Meteor.call('scheduleBoardArchive', (error, result) => {
if (error) {
alert(TAPi18n.__('board-archive-failed') + ': ' + error.reason);
} else {
alert(TAPi18n.__('board-archive-scheduled'));
}
});
// Placeholder - board archive scheduling
alert(TAPi18n.__('board-archive-scheduled'));
},
'click button.js-schedule-board-backup'(event) {
event.preventDefault();
Meteor.call('scheduleBoardBackup', (error, result) => {
if (error) {
alert(TAPi18n.__('board-backup-failed') + ': ' + error.reason);
} else {
alert(TAPi18n.__('board-backup-scheduled'));
}
});
// Placeholder - board backup scheduling
alert(TAPi18n.__('board-backup-scheduled'));
},
'click button.js-pause-job'(event) {
event.preventDefault();
const jobId = $(event.target).data('job-id');
Meteor.call('pauseCronJob', jobId, (error, result) => {
this.setLoading(true);
Meteor.call('cron.pauseJob', jobId, (error, result) => {
this.setLoading(false);
if (error) {
alert(TAPi18n.__('cron-job-pause-failed') + ': ' + error.reason);
} else {
@ -251,7 +264,9 @@ BlazeComponent.extendComponent({
event.preventDefault();
const jobId = $(event.target).data('job-id');
if (confirm(TAPi18n.__('cron-job-delete-confirm'))) {
Meteor.call('deleteCronJob', jobId, (error, result) => {
this.setLoading(true);
Meteor.call('cron.removeJob', jobId, (error, result) => {
this.setLoading(false);
if (error) {
alert(TAPi18n.__('cron-job-delete-failed') + ': ' + error.reason);
} else {