fix(events): apply the gallery password policy to publish (#1255)
Stable twin of #1253. This branch has only the publish door — send-gallery-email is #1235, main-only — so the same gap exists here in one place rather than two. /publish re-hashes password_hash from a plaintext the admin re-types in the publish dialog, validated with nothing but express-validator's isLength({min:6}). So the configured complexity — moderate by default, meaning 8 characters plus upper, lower and a digit — governed event creation and password reset while this door accepted 'aaaaaa' and made it the live gallery password. Not an escalation: it needs admin auth plus events.edit. It is a policy gap, the admin UI advertising a complexity level this write path did not enforce. BEHAVIOUR CHANGE: an API-only consumer publishing with a sub-policy password now gets 400, with the same body shape event creation returns (error, details, score, feedback). 3 tests, including that the rejection happens BEFORE the write — the gallery keeps its old hash and stays a draft — and that a publish carrying no password at all is untouched. Co-authored-by: Paul Nothaft <[email protected]>
This commit is contained in:
co-authored by
Paul Nothaft
parent
261e243070
commit
1d9f0b6c64
@@ -30,6 +30,29 @@ const { getFrontendBaseUrl } = require('../../utils/frontendUrl');
|
||||
const downloadZipService = require('../../services/downloadZipService');
|
||||
const { validateHeroImageAnchor, getEventFieldRequirements, readBooleanSetting, getDownloadProtectionDefaults, getBrandingDefaults, getCustomerNameFromPayload, getCustomerEmailFromPayload, getCustomerPhoneFromPayload, isPhoneFieldEnabled, mapEventForApi, hasCustomerContactColumns, deleteEventCascade, SLIDESHOW_TRANSITIONS, SLIDESHOW_COLORFILTERS } = require('./helpers');
|
||||
|
||||
/**
|
||||
* Validate a gallery password the admin re-typed, against the SAME policy
|
||||
* event creation applies.
|
||||
*
|
||||
* The publish dialog (#627) re-hashes `password_hash` from a plaintext the
|
||||
* admin types again, and validated it with nothing but express-validator's
|
||||
* `isLength({ min: 6 })`. So the configured complexity — moderate by default
|
||||
* — governed creation and reset while this door accepted `aaaaaa` and made it
|
||||
* the live gallery password.
|
||||
*
|
||||
* Returns null when the password passes; otherwise the response body to send.
|
||||
*/
|
||||
async function checkGalleryPasswordPolicy(password, eventName) {
|
||||
const result = await validatePasswordInContext(password, 'gallery', { eventName });
|
||||
if (result.valid) return null;
|
||||
return {
|
||||
error: 'Password does not meet security requirements',
|
||||
details: result.errors,
|
||||
score: result.score,
|
||||
feedback: result.feedback,
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = (router) => {
|
||||
|
||||
|
||||
@@ -854,6 +877,9 @@ module.exports = (router) => {
|
||||
// Re-hash so the stored hash matches what the email carries — even if
|
||||
// the admin mistypes vs. what was set at draft creation, the gallery
|
||||
// password the customer receives is the one that actually works.
|
||||
const policyError = await checkGalleryPasswordPolicy(password, event.event_name);
|
||||
if (policyError) return res.status(400).json(policyError);
|
||||
|
||||
publishUpdates.password_hash = await bcrypt.hash(password, getBcryptRounds());
|
||||
}
|
||||
await db('events').where('id', id).update(publishUpdates);
|
||||
|
||||
Reference in New Issue
Block a user