Repository navigation
Add an admin area: login with emailed code, picture review, settings, account disabling - #501
Merged
Merged
Conversation
Every /admin address answers 404 until admin.username and admin.password-hash are set, sends sessions that haven't passed both login factors to /admin/login, and refuses form posts without the session's token. The login, picture review, settings and account pages build on this. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The admin can now change the value of any existing row in the settings table at /admin/settings. Rows are never created, renamed or removed. Values are trimmed and checked against the 100 character column and the types the loader parses (mailPort and tzOffset as integers, the flags as true/false), so a save can no longer leave the site unable to read its settings. Settings whose names look like credentials (pass, secret, key, token) are never sent to the browser or logged; posting them blank keeps the current value. core.Settings now loads through a public reload(), called at startup and after each committed change. A reload builds a complete new set of values and swaps it in, so readers never see a half loaded mix, and a table that can't be read keeps the current values. Getters and defaults are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
/admin/login checks the configured admin username and argon2id password hash (cost settings read from the hash, base64 with or without padding, as the argon2 CLI emits), then mails a six digit code to the site admin address. /admin/code accepts that code once, within ten minutes and five tries, and only then marks the session as admin under a new session id. A missing admin address or a failed send refuses the login outright. Login and code posts get their own bucket4j limit, and admin.username / admin.password-hash are read from ADMIN_USERNAME / ADMIN_PASSWORD_HASH. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Pictures the content filter held stayed visible only to their owners with no way to release them. /admin/images lists held album pictures, avatars, post pictures and link preview pictures (up to 50 of each), serves their files to the admin with no-store, and approves or rejects them. A decision only applies while the row is still REVIEW and keeps the model's category. Approving does what ModerationRetry does: a post picture on a post publishes its attachments, an avatar publishes the profile. Approving a post or link picture on a post also drops the cached recent posts feed after commit, since that feed carries them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adds a disabled flag to user (V1_32) and an Accounts page in the admin area to search accounts and disable or enable them. A disabled account can't log in by any route (web, second factor, XML-RPC/desktop clients, password change or reset), its open sessions are logged out on their next request, and its journal, feeds, pictures, avatar and fediverse actor answer 404 until it is enabled again. Nothing is deleted, so enabling the account restores everything. A request interceptor checks an in-memory list of disabled accounts, refreshed every 30 seconds and right after a change, so the per request check costs no query. Cross-journal listings (recent posts, friends pages, members, sitemap, site search, favorites) leave the account out, and its queued fediverse deliveries are dropped when it is disabled. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Sorry @laffer1, your pull request is larger than the review limit of 150,000 diff characters |
Collaborator
Author
✅ Snyk checks have passed. No issues have been found so far.
💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse. |
Contributor
|
|
Overall Grade |
Security Reliability Complexity Hygiene |
Code Review Summary
| Analyzer | Status | Updated (UTC) | Details |
|---|---|---|---|
| PHP | Nov 3, 2026 9:16p.m. | Review ↗ | |
| Java | Nov 3, 2026 9:16p.m. | Review ↗ | |
| Shell | Nov 3, 2026 9:16p.m. | Review ↗ | |
| JavaScript | Nov 3, 2026 9:16p.m. | Review ↗ |
Important
AI Review is run only on demand for your team. We're only showing results of static analysis review right now. To trigger AI Review, comment @deepsourcebot review on this thread.
| return; | ||
| } | ||
| if (user.isDisabled()) { | ||
| log.info("Password reset requested for disabled account {}", username); |
| private com.justjournal.model.User lookupUser(@NotNull final String userName, @NotNull final String password) { | ||
| final com.justjournal.model.User user = matchPassword(userName, password); | ||
| if (user != null && user.isDisabled()) { | ||
| log.warn("Login refused for {}: the account is disabled", userName); |
| final HttpSession session = request.getSession(false); | ||
| if ("POST".equalsIgnoreCase(request.getMethod()) | ||
| && !AdminSession.isValidCsrf(session, request.getParameter(AdminSession.CSRF_PARAM))) { | ||
| log.warn("Admin form post to {} refused: missing or wrong token", request.getRequestURI()); |
| return; | ||
| } | ||
| if (user.isDisabled()) { | ||
| log.info("Password reset requested for disabled account {}", username); |
| } else if (name.equalsIgnoreCase("mailEnable")) { | ||
| mailEnable = value.equalsIgnoreCase("true"); | ||
| } else if (name.equalsIgnoreCase("mailHost")) mailHost = value; | ||
| else if (name.equalsIgnoreCase("mailPort")) mailPort = Integer.parseInt(value); |
| commentMailEnable = value.equalsIgnoreCase("true"); | ||
| } else if (name.equalsIgnoreCase("commentEnableAnonymous")) { | ||
| commentEnableAnonymous = value.equalsIgnoreCase("true"); | ||
| } else if (name.equalsIgnoreCase("tzOffset")) tzOffset = Integer.parseInt(value); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds an admin area at
/adminso held pictures, site settings and problem accounts can be handled without editing the database by hand.Login
admin.usernameandadmin.password-hash, read from${ADMIN_USERNAME:}and${ADMIN_PASSWORD_HASH:}. The hash is an argon2id string of the password; the comment inapplication.ymlshows how to make one with theargon2CLI. With either value blank, every/adminaddress answers 404.siteAdminEmailsetting through the existing mail queue and sent straight away.AdminInterceptorguards every/adminroute:_csrf)./admin/loginand/admin/codeto 5 a minute and 20 an hour per IP.Pictures (
/admin/images)moderation_category, and buttons to approve or reject each.ModerationRetrydoes.entryAttachmentsChanged, and an approved avatar callsprofileChanged. Approving a post picture or link preview also clears the recent-posts RSS cache.no-store,nosniffand a sandbox CSP.Settings (
/admin/settings)settingsrows; rows can't be added or removed.mailPortandtzOffset,true/falsefor the boolean settings.mailPass) are never shown; leaving the field blank keeps the current value.core.Settingsgainedreload(), which runs after each save commits, so changes apply without a restart. The one exception is WebFinger, which copiesbaseuriat startup.Disabling accounts (
/admin/users)V1_32__user_disabled.sqladdsuser.disabled.Testing
./mvnw -o package: 1994 unit tests, 0 failures.IT*tests, the migration, the new JPQL queries, and the pages in a running app. Jenkins will run the ITs.Known limits, left for later
public, max-age=3600can stay in browser and proxy caches for up to an hour.🤖 Generated with Claude Code