990ebdb15826b69b67d6a9ef0ec1f744fbbb914a
3
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
179484642f |
feat: per-account login lockout, mail index, resend captcha, i18n scoping
Gitea Actions Runner Test / test-job (push) Successful in 2s
CI / check (push) Successful in 28s
CI / tests-integration (push) Successful in 1m42s
CI / tests-unit (push) Failing after 1m45s
CI / tests-ui (push) Successful in 2m29s
CI / preflight (push) Skipped
CI / deploy (push) Skipped
Closes the four HIGH/MEDIUM items left open after the previous pass. Login lockout - The only login limits were keyed on the client IP, so a distributed attempt could grind on one account indefinitely. Added a per-account lockout with a budget of 8 failures per 15 minutes. - The bucket is keyed on the RESOLVED account id, not on the submitted string: users may sign in with either username or e-mail and neither the lookup nor the input normaliser folds case, so an input-keyed bucket would hand out a fresh budget per spelling of the same account. - precheckLogin and NextAuth's authorize share the bucket, so the pre-check cannot be used to buy extra attempts and a client that skips it entirely is still bounded. Both check the lockout BEFORE verifying the password: the success path clears the counter, which would otherwise walk a locked account straight back in on the right password. - A successful login clears the failures, which needs two new primitives in rate-limit.ts: peekRateLimit (read-only, does not consume a unit) and clearRateLimit. - Fixed a latent inconsistency while doing so: the in-process bucket capped its counter at the limit while Redis' INCR kept climbing, so the two backends disagreed about how far over the limit a key was. Both now track the true count. Mail lookup index - Added an index on users.mail (0035). Password reset, e-mail verification and the resend cooldown all resolve a single account from a submitted address and were full table scans of `users`. Deliberately non-unique: legacy rows can hold the same address more than once, so a unique index would fail to apply. Resend captcha - /verify's resend form triggers real outbound mail and was reachable with only a cooldown. It now runs the configured captcha before the account lookup and before any send. Client message payload - The root layout serialised the whole catalogue into every page. pages.admin and admin are ~177 KB of the ~235 KB and are unreachable from the public route group, so that layout now installs its own provider with the staff namespaces removed. Nested providers replace rather than merge, which is why this has to live in the segment layout. /admin, /mod, /client and /admin-next keep the full set; a guard test fails if a public page ever references a staff namespace. |
||
|
|
6cc45d7413 |
feat: harden atoms-nexst against review findings (37 items)
Gitea Actions Runner Test / test-job (push) Successful in 2s
CI / check (push) Successful in 30s
CI / tests-integration (push) Successful in 1m42s
CI / tests-unit (push) Failing after 1m49s
CI / tests-ui (push) Successful in 2m31s
CI / preflight (push) Skipped
CI / deploy (push) Skipped
Second review pass covering security, performance, admin tooling and the public/room flows. All HIGH and MEDIUM findings from the audit are resolved; nothing in this commit changes the visible feature set. Authentication & session security - CSP is now set on the request headers in the proxy, which is what Next.js uses to derive the render nonce, so the nonce is effective. - 2FA: an already-enabled user cannot re-enroll, the setup endpoint is rate-limited per account, and confirmed codes are persisted so the second secret no longer silently never applies. - Password reset revokes the ticket, authTicket and all personal access tokens, and bumps the token version so existing sessions die. The same revocation is now wired into the staff-side password reset. - /reset and /verify return a stable error code instead of raw text; the mail lookups are ordered by id so duplicates cannot vary between runs. - Resending the verification mail gets a per-address cooldown on top of the per-user limit. - Issue API tokens with the narrower radio/ticket ability set instead of "*". Authorization & input handling - Mid-rank staff can no longer keep dynamically granted non-view admin.* permissions: existing grants are revoked by migration and the grant lookup is restricted to "%.view". Rank guards use the dynamic super-admin check. - Alerting a user is permission-checked and audited like the other tools. - Material mutations (giveCredits/giveDuckets/giveDiamonds, the admin user actions route, bulk user actions) are capped and rank-guarded, and bulk ids are bounded. - updateRoom / updateRoomItem write through a field allowlist, and items may only be edited through their own room. - Classnames reaching the filesystem are validated before use so a crafted value cannot escape the asset directories. - The word filter now also covers offline mails, guild forum threads and replies, and user mottos. - Media uploads are validated by magic bytes, /api/media requires the page edit permission, APP_URL must be configured once mail is enabled, and the diagnostics error route checks the fetch site header. Admin tooling - Secret settings render masked and cannot be overwritten with a blank or an arbitrary raw key; radio credentials are new password inputs. - Commandocentrum balance changes are audited. - Admin list pagination reads the caller's per-page instead of the max, and the log exporter caps offset and search length. Performance - Catalog translations are cached per module, with a cheap revision hash; the public online count uses a stale window instead of hammering the DB. - The cache warmup now primes the payload the home route actually reads. - TopHeader batches its queries into one round trip, and LCP avatars load eagerly. - motion/react and sonner are no longer part of the root layout; the nav dropdown and mobile nav panels are lazy client chunks. Anonymous visitors again get the navigation chrome, and public pages get an edge cacheable response. Accessibility - Nested <main> elements in phase pages became <section>; the page entrance and route progress animations are pure CSS that respect reduced motion. |
||
|
|
3933214953 |
feat(auth): implement all 16 homepage/login/register review items
Gitea Actions Runner Test / test-job (push) Successful in 1s
CI / check (push) Successful in 33s
CI / tests-integration (push) Successful in 1m42s
CI / tests-unit (push) Successful in 1m47s
CI / tests-ui (push) Successful in 2m30s
CI / preflight (push) Skipped
CI / deploy (push) Failing after 2m56s
- add countArticles() (published-only, mirrors news-list) and warm total_articles - localize homepage metadata; bind articleCount to both stats; unique photo alts - drop duplicate news date and the mascot preload priorities - extract shared AuthPageFrame/AuthUsersCards used by /login and /register - login: localized noindex metadata, session redirect via safeRedirectPath, ?from passthrough from proxy, unified auth roster cache keys, registered notice - register: localized metadata, session redirect to /me, unified cache keys - add resend-verification flow on /verify with rate-limited non-enumerable action - add safeRedirectPath() with unit tests - register form: live requirements checklist + password mismatch guard - login form: unverified state with resend-link CTA - honour prefers-reduced-motion in TypewriterText - add 6 translations across all 25 locales |