effc262382
fix: site name in top logo + server-list public + external API docs page
...
- app.js: set #logo-text (top bar) from window.__SITE_NAME__ - the
top-right site name stayed default because only #site-title and
#sidebar-title were set
- security.js: apiKeyGuard whitelist /api/polls/server-list so the
ticket create page can load sub-servers (was 401 without api key)
- external-api.js: management page now only creates/manages clients,
removed inline endpoint table
- new external-api-docs.js: full API doc page (auth flow, all 11
endpoints, request/response examples, Node sample) at
#/external-api-docs, linked from the management page
- index.html: load external-api-docs.js
2026-08-22 02:18:29 +08:00
c4363deb46
feat: ticket create - sub-server selector
...
- ticket-create.js: added 子服务器 dropdown (group/server from
/polls/server-list, supports anonymous users), sends 'server' field
- tickets.js POST: accept server param, resolve 'group/server' or
alias/server_name from server_groups, store into tickets.server_name
- polls.js: new public GET /server-list (group_name+server_name only,
no auth required) for ticket create page
- verified: 13 checks (frontend/backend/db/column/syntax)
2026-08-21 22:02:06 +08:00
cb5847d650
feat: game UID fully internal + remove multi-identity copy
...
- game_uid is now an internal param: auto-generated server-side
(U + 12 hex) on register/bind-identity/install/admin-create/external
register; player-supplied game_uid ignored
- Removed all player-facing UID inputs: register page, install page,
identity bind modal, ticket-create 'your UID' field
- Removed player-facing UID display: identity table column,
ticket detail/tracking reporter UID
- Removed '同一账号可绑定网易端 + 皮肤站身份' / '可绑定多个来源身份' copy
- Kept target_game_uid (reported player) and admin panel UID management
- settings-page skin-site placeholder updated
2026-08-21 21:52:40 +08:00
188e083719
refactor: auth expiry - sync JWT exp check, remove async validation
...
Previous async validateAuth() approach caused: recursion loop,
wrong /me path (404 -> session cleared), init() fetching auth-required
settings without token (cleared session every load).
New synchronous approach:
- Auth.expired(): decodes JWT payload locally, checks exp timestamp
- Auth.logged(): token+user exist AND not expired; auto-clears stale
session on expiry (no network, no async, no loops)
- route(): simple sync guard - if !Auth.logged() reset + redirect to
#/login; home page stays public (clears stale cookie only)
- API.req: 401 response -> Auth.reset() + redirect to login unless on
public page (fallback for server-side revocation/invalid signature)
- Removed validateAuth/_validating/_doRoute entirely
2026-08-21 21:01:30 +08:00
edf561729f
fix: backend pages redirect loop - wrong /me path + init() clearing auth
...
Root causes (both in app.js):
1. validateAuth() called API.get('/me') = /api/me, but the route is
mounted at /auth/me (businessRouter.use('/auth', authRoutes)).
/api/me returned 404 -> non-auth error was re-thrown -> outer
.catch() unconditionally Auth.reset() + redirect to #/login.
Fixed: call /auth/me; non-auth errors (network/404/500) now return
true (allow routing) instead of throwing.
2. init() fetched /api/settings/settings WITHOUT token, but that route
requires authenticate+requireRole. Always 401 -> __SITE_NAME__ never
set -> 'Auth.logged() && !window.__SITE_NAME__' always true ->
Auth.reset() on EVERY page load, wiping valid login.
Fixed: removed the fetch and the reset line - site_name is already
injected server-side as window.__SITE_NAME__ (getHtmlWithKey).
2026-08-21 20:57:25 +08:00
7b50b599cf
fix: infinite login loop from async auth validation
...
- Add _validating flag to prevent recursive hashchange calls during async
token validation
- route() checks _validating before calling validateAuth(), sets flag
before async call, clears in .then/.catch handlers
- route() returns early if still validating (waits for completion)
- Fixes: expired token causing infinite login redirect loop
2026-08-21 20:52:41 +08:00
e232f3b02e
fix: auth expiry - validate token before showing protected pages
...
- Add validateAuth() method: calls /api/me to verify token validity,
clears Auth state on 401/login error
- route() now calls validateAuth() async for ALL protected pages
BEFORE showing layout/sidebar/rendering content
- Extract _doRoute() for actual page rendering (runs after validation)
- init() uses API.get for install check (proper auth handling)
- Added reset() to Auth: clears localStorage without redirecting
This fixes: expired token showing login-required pages, and no feedback
when session expires mid-session.
2026-08-21 20:46:09 +08:00
6ac8a5cd11
fix: site_name not loaded + expired token not cleared
...
- app.js init(): use native fetch (no Bearer) to call /api/install/status
and /api/settings/settings, so site_name loads even before login
state is confirmed; call Auth.reset() if logged but no site_name set
- auth.js: add reset() method that clears TK/US without redirecting
- app.js route(): remove early return for uninstalled; let install check
run first so init() can fall through to normal routing when installed
- app.js renderMain(): catch 401/login errors from page render, clear
Auth state, show login prompt with button that redirects back after
login (target hash preserved)
2026-08-21 20:31:45 +08:00
e1cdff2b4a
security: backend validation for all inputs
...
- router.param('id'): all :id path params must be positive ints
(tickets/features/polls/auth/users; external/bans/notifications already
had parseInt - now consistent)
- notifications: type enum + webhook URL format + SSRF (isPrivateUrl
exported) + events whitelist + active boolean check on PUT
- bans: type enum + player_name length
- external: all-tickets type/status enums, bans status/type enums,
page/limit floor protection, ticket field length caps, clients active
boolean + id validation
- verified: 28 checks (syntax + validation coverage)
2026-08-21 20:10:05 +08:00
8c5ce78fd0
chore: add AGPLv3 copyright header to all source files
...
- 52 JS files (backend + public/js): header with
Copyright (C) 2026 Sea Network Technology Studio
Author: CangLan <admin@sea-studio.top >
+ AGPLv3 notice
- idempotent (skips if header present), all syntax-checked
2026-08-19 20:27:05 +08:00
356d7ab5ab
chore: remove plugin from repo, add plugin guide, docs + AGPLv3 license
...
- mc-report-plugin removed from git tracking (kept locally), gitignored;
plugin code no longer distributed (old x-external-key auth, outdated)
- docs/PLUGIN-GUIDE.md: Java plugin integration guide using new
ID+Secret -> SESSION auth (session mgmt, submit ticket, track, bans)
- README: updated external API table (SESSION), dynamic sources,
removed obsolete plugin commands/tech row, new dir structure, doc links
- INSTALL: drop restart requirement, add upgrade-migration/log/sources
sections, license section
- LICENSE: GNU AGPL v3 full text
- verified: 24 checks (git tracking, docs consistency, license integrity)
2026-08-19 20:23:04 +08:00
3441eb9478
chore: pure-random 32-char secret, no default source, legacy migration
...
- genSecret: drop SeaReport- prefix, plain 32-char random hex
- users.source: no default anywhere (register/admin/external), ADD COLUMN
migration now VARCHAR DEFAULT '' (was ENUM netease default)
- legacy upgrade path kept: ENUM->VARCHAR MODIFY + user_identities MODIFY
+ sources seeded netease/skin idempotently
- docs updated (credential format, no prefix)
2026-08-19 20:16:16 +08:00
65edbaf157
refactor: session-based external auth, dynamic sources, drop netease UID
...
Auth (external API):
- ID: 16-digit random (non-sequential); Secret: SeaReport- + 32 hex
- POST /auth/session: ID+Secret -> Bearer SESSION (24h, single-session,
old session invalidated on re-issue, disabled client invalidates)
- clientAuth now validates Bearer SESSION via api_sessions JOIN api_clients
Sources (dynamic, no default, open-source friendly):
- sources table + CRUD route (/api/sources, owner; delete guarded by usage)
- users/user_identities.source ENUM -> VARCHAR, seeded netease/skin
- register/admin create/identity bind: validate against enabled sources
- UI: 来源管理 page; source dropdowns loaded dynamically everywhere
(register, dashboard identity, users admin, bans), labels dynamic
UID removal:
- game_uid/reporter_game_uid no longer required (db default '', validations
dropped, frontend fields optional)
Docs: EXTERNAL-API.md session flow + new credential format; API.md updated
Verified: 37 checks (syntax, session logic, source CRUD, UID removal, docs)
2026-08-19 20:08:06 +08:00
6056153f57
security: external API - only ID+Secret auth, remove legacy key & JWT
...
- removed x-external-key (single key) auth path + getExternalKey
- removed JWT passthrough in clientAuth (Bearer no longer accepted)
- removed /auth/login (JWT endpoint) and /my-tickets (JWT-only)
- clientAuth now mandatory: missing/invalid/disabled client -> 401
(closed the 'no config = allow all' authorization bypass)
- moved /auth/register BEHIND clientAuth (was anonymous abuse surface)
- clients mgmt endpoints keep authenticate + role check (admin UI)
- docs + UI copy updated to single auth method
- verified: 30 checks incl. full-tree scan for legacy key refs
2026-08-19 19:44:34 +08:00
4dfc30ce89
docs: standalone external API doc for bots/third-party
...
- docs/EXTERNAL-API.md: auth matrix (ID+Secret / legacy key / JWT),
Python+Node quickstart, server alias syntax, 9 endpoints with
request/response examples, status flow, error codes, troubleshooting,
typical scenarios (QQ bot ticket flow, plugin ban sync)
- README + API.md link to the standalone doc
- verified: 24 checks, endpoints cross-checked doc vs code
2026-08-19 19:40:25 +08:00
257cd14051
feat: external API for QQ bot - client id+secret auth, ticket tracking, bans, per-server data
...
- db: api_clients table + server_groups.alias + tickets/bans server_name (auto-migrate)
- external.js: client_id+secret auth (multi-client, bcrypt, show-once secret),
legacy x-external-key compat, JWT pass-through
- POST /tickets (optional server), GET /tickets/track?token= (full lifecycle)
- GET/POST /bans, reported-players/stats/all-tickets with server filter
- GET /servers (alias list), client CRUD (owner/admin, audit logged)
- resolveServer: alias | 分组/子服 | server_name
- polls.js: server_groups CRUD with alias (add/edit, dup check)
- servers page: alias display + edit; new 外部API page: client mgmt + quick docs
- API.md: full external API section (auth matrix, server param syntax)
- verified: 37 checks incl. resolveServer sandbox (alias/group/fallback)
2026-08-19 19:12:34 +08:00
a11df22600
feat: system logs - email log + system log with admin UI
...
- db: email_logs (sent/failed) + system_logs (info/warn/error) tables,
auto-migrated for existing installs
- backend/logger.js: logEmail/logSystem with try/catch (never breaks flow)
- mailer: log send result (SMTP unconfigured/sent/failed + error msg)
- webhook: log blocked-internal, non-2xx response, send failure
- server: error middleware records 500 errors to system_logs
- routes/logs.js: GET /logs/emails + /logs/system (admin/owner, filters)
- UI: 系统日志 page (owner/admin) with system/email tabs + filters
- API docs updated
2026-08-17 05:14:41 +08:00
8d6c3c4a8d
fix: notifications page crash + email code dev-mode false error
...
- notifications.js: EventOptions(current) referenced undefined 'Current'
(case typo) -> ReferenceError crashed add/edit modal, page unusable
- auth.js send-verify-code: dev mode (SMTP down) deleted captcha row before
returning code, so register always failed with '验证码无效或已过期'
even with correct code. Keep row; it's deleted after successful verify.
2026-08-17 04:50:10 +08:00
1d669fd1d0
feat: skin site UUID lookup - auto-fetch UUID from player name
...
- GET /settings/uuid-lookup?site=&name= : proxy Yggdrasil API
(POST {site}/api/yggdrasil/api/profiles/minecraft), SSRF guard,
10s timeout, name regex, UUID formatting (with dashes)
- GET /settings/skin-site : public read of configured skin site
- settings page: 皮肤站地址 config
- bind identity modals (player dashboard + admin users): 获取UUID button,
auto-fill site from settings, auto-fill UID from lookup
- verified live against littleskin.cn (Steve -> df273bda...)
- API docs updated
2026-08-17 02:05:21 +08:00
a6a548b1ce
fix: identity bind row layout - select stole full width, inputs invisible
...
Root cause: .form-group select global width:100% inherited inside flex row,
pushing the two text inputs to zero width. Fix: fixed 92px select
(flex:0 0 92px), inputs flex:1 1 30% with min-width:0 + width:auto,
button flex:0 0 auto, no wrap.
2026-08-17 01:52:20 +08:00
25a766591e
feat: admin user management supports multi-identity bind/unbind
...
- users list: identity_count column (backend subquery)
- user detail: returns identities array
- POST /users/:id/identities, DELETE /users/:id/identities/:identityId
(owner protection, dup checks, keep >=1, audit logs)
- users page: identity column in table + manage section in edit modal
- API docs updated
2026-08-17 01:34:20 +08:00
4c221ad6f1
docs: full API documentation + data compat guard
...
- docs/API.md: complete API reference (auth/identities/tickets/bans/users/
settings/notifications/polls/features/export/captcha/uploads/external/
install/health) with auth, rate limits, roles, data migration notes
- auth.js register: also check user_identities for duplicate game_name
(multi-identity compat for old data backfilled into new table)
- README: link API docs, multi-identity section
2026-08-17 01:20:39 +08:00
559970c5b7
feat: one account can bind both netease + skin identities
...
- db: user_identities table (UNIQUE user_id+source), migrate backfill from users
- auth: GET/POST/DELETE /api/auth/identities - bind/unbind/list identities
(netease requires UID, one per source, name uniqueness, keep >=1)
- register/external/plugin/admin-created users auto-write primary identity
- tickets: submit uses selected identity (validated belongs to user)
- dashboard: 我的身份 card with bind/unbind UI
- ticket-create: identity selector when >1 identity
2026-08-17 01:17:09 +08:00
42d4cafedd
fix: async route rejection crashes process - global async error wrap + email dup check
...
- users.js: check email uniqueness before insert (was ER_DUP_ENTRY uncaught crash)
- server.js: wrapAsyncRouter - Express 4 doesn't catch async handler rejections,
route throws now go to error middleware (500 JSON) instead of uncaughtException
2026-08-17 01:03:45 +08:00
7e203d9a27
fix: 404 interface not found after login - business routes on dedicated router so dynamic loading precedes catch-all
2026-08-17 00:01:36 +08:00
cc02041b61
fix: api key cache invalidated by config mtime - server picks up new key after reinstall
2026-08-16 23:03:03 +08:00
a5019f1b11
fix: invalid API key after install - force page reload to pick up injected key
2026-08-16 23:01:31 +08:00
1740a52cda
fix: install page blank - init() early return skipped route registration
2026-08-16 21:27:46 +08:00
6e9101a506
fix: deploy crash + multiple bug fixes + cleanup
...
- server.js: fix getSiteName() returning string when not installed causing
'getSiteName(...).then is not a function' crash on homepage (deploy blocker)
- server.js: auto-load business routes after install completes (no restart needed),
HTML cache keyed by api_key
- install: validate db name/email/password, trigger route loading after complete
- app.js: fix forgot/reset/verify pages rendering HomePage (missing page mapping)
- verify.js: support URL token auto-verification for external registration links
- auth: new email_code template for 6-digit code, reset_password template
(forgot-password was using verify_email template)
- upload.js: fix MP4 magic-bytes check using undefined buf variable
- tickets.js: status enum validation, anonymous submission rate limit
- security.js: XSS whitelist preserves email template HTML, strips scripts,
blocks javascript:/data: hrefs; CORS reject returns 403
- bans.js: allow clearing reason/duration, status enum validation
- users.js: fix req.user.role ReferenceError in create user modal
- home.js: tracking results now have detail view button
- .gitignore: ignore data/ (db credentials), logs, session files, temp scripts
2026-08-16 21:21:25 +08:00
64199a0aaf
refactor: universal compatibility - spigot-api, no api-version lock
2026-07-23 12:48:50 +08:00
02aeab1e9c
docs: Paper 1.20-1.21+ compatibility, folia-supported
2026-07-22 21:49:55 +08:00
2a534718fd
refactor: plugin permissions to SeaStudio.Report.* namespace
2026-07-21 15:43:05 +08:00
37657a7ba4
docs: plugin README with build instructions
2026-07-19 03:06:23 +08:00
7bc22dee0c
fix: add source field to dashboard ban form
2026-07-19 00:40:54 +08:00
19615cfa56
feat: edit ban entries with modal form
2026-07-18 01:53:24 +08:00
12721d09ee
fix: duplicate res.json crash + missing try/catch in bans POST
2026-07-18 01:45:00 +08:00
5cb17228a8
fix: missing duration dropdown in bans add form
2026-07-18 01:41:37 +08:00
dce812aa2f
feat: bans show account source + ban source (ticket/manual)
2026-07-18 01:35:30 +08:00
140ff3e3ae
fix: remove duplicate closing brace in auth.js
2026-07-18 01:30:14 +08:00
ce51abc127
fix: add source field to bans + display in list
2026-07-18 01:26:37 +08:00
d242786a3a
feat: bans sidebar page, public view with masked names
2026-07-18 01:17:40 +08:00
2e09dd2d3a
fix: pass bans to renderContent, restore exports wrapper
2026-07-18 01:14:43 +08:00
2a12e7b740
fix: ban duration >= not >, auto-prefill min duration
2026-07-18 01:11:41 +08:00
aa2c296971
feat: escalating ban duration - each ban must be longer than previous
2026-07-18 01:09:14 +08:00
5ed109ec0e
feat: ban list with dashboard widget, owner add/edit bans
2026-07-18 00:52:33 +08:00
5497de9aac
feat: batch ticket operations (close/delete/change status)
2026-07-17 21:55:15 +08:00
ed6fe9dcf1
fix: admin/owner skip rate limiting, anon 10/min
2026-07-17 21:51:32 +08:00
6dfc6788e5
fix: show target UID when name is empty in ticket list
2026-07-17 19:25:39 +08:00
1e1e8282e6
style: add spacing between code input and submit button
2026-07-17 15:05:01 +08:00
771e28472e
fix: remove duplicate email field and extra send button
2026-07-17 14:57:33 +08:00