Summary
Bundle of small, low-risk backend cleanups that close validation gaps and remove hardcoded values. Each is XS individually; bundling avoids issue spam.
Items
1. Bump bcrypt rounds
SALT_ROUNDS = 10 (server/index.js:9) → bump to 12. Existing hashes stay valid; new hashes use the higher cost.
2. Server-side password validation
Frontend enforces minLength: 4 (Register.jsx:44, Profile.jsx:179, ResetPassword.jsx:56); backend enforces nothing. Add an explicit length+complexity check on POST /api/register, POST /api/reset-password, and PUT /api/user/:username/password. Recommend min 8 chars.
3. Custom RRULE validation
POST /api/events and PUT /api/events/:id (server/index.js:1076, :1106) accept an arbitrary recurrence string. Validate with rrule.js before persisting.
4. Atomic file rename in username migration
server/index.js:369-376 renames a profile photo file as best-effort; if the rename fails the URL points at a missing path. Wrap rename + DB update in a single try/rollback.
5. Refactor duplicated ICS generation
GET /api/calendar/subscribe (server/index.js:716-794) and GET /api/events/export/ics (:797-874) share ~70% of their string-building logic. Extract buildIcsCalendar(events, opts).
6. 12-factor config
Move out of code into env vars (and add to .env.example):
7. DB indexes
Add to existing tables (cheap, eliminates full scans):
CREATE INDEX idx_users_reset_token ON users(resetToken);
CREATE INDEX idx_events_date_visibility ON calendar_events(event_date, visibility);
CREATE INDEX idx_invites_created_by ON invite_tokens(created_by);
CREATE INDEX idx_invites_used_by ON invite_tokens(used_by);
Acceptance Criteria
Summary
Bundle of small, low-risk backend cleanups that close validation gaps and remove hardcoded values. Each is XS individually; bundling avoids issue spam.
Items
1. Bump bcrypt rounds
SALT_ROUNDS = 10(server/index.js:9) → bump to 12. Existing hashes stay valid; new hashes use the higher cost.2. Server-side password validation
Frontend enforces
minLength: 4(Register.jsx:44,Profile.jsx:179,ResetPassword.jsx:56); backend enforces nothing. Add an explicit length+complexity check onPOST /api/register,POST /api/reset-password, andPUT /api/user/:username/password. Recommend min 8 chars.3. Custom RRULE validation
POST /api/eventsandPUT /api/events/:id(server/index.js:1076,:1106) accept an arbitraryrecurrencestring. Validate withrrule.jsbefore persisting.4. Atomic file rename in username migration
server/index.js:369-376renames a profile photo file as best-effort; if the rename fails the URL points at a missing path. Wrap rename + DB update in a single try/rollback.5. Refactor duplicated ICS generation
GET /api/calendar/subscribe(server/index.js:716-794) andGET /api/events/export/ics(:797-874) share ~70% of their string-building logic. ExtractbuildIcsCalendar(events, opts).6. 12-factor config
Move out of code into env vars (and add to
.env.example):America/Chicagoliteral)NODE_ENV7. DB indexes
Add to existing tables (cheap, eliminates full scans):
CREATE INDEX idx_users_reset_token ON users(resetToken);CREATE INDEX idx_events_date_visibility ON calendar_events(event_date, visibility);CREATE INDEX idx_invites_created_by ON invite_tokens(created_by);CREATE INDEX idx_invites_used_by ON invite_tokens(used_by);Acceptance Criteria