fix(presence): drop status='online' from registration insert

Mirrors the REST-login change in f17c46c on the registration insert path.
A successful POST /api/auth/register does not by itself imply a live
WebSocket — the client may never connect (transient network, mobile
background, error path between the 201 and /ws), leaving a permanently
stuck-online row that no disconnect timer can clean up. The schema
default 'offline' is correct; ws/handler.ts flips it to 'online' on real
WS auth.

The federated-stub upgrade path in the same handler is unaffected: it
only updates passwordHash/username/homeUserId/displayName/avatarColor,
leaving the stub's pre-existing 'offline' status (set when the stub was
created via replication) untouched.

Updates docs/systems/auth.md step 7 to reflect the new behavior.
This commit is contained in:
Jannis Braun
2026-04-27 09:35:34 +02:00
parent f17c46c77f
commit 4fa2414933
2 changed files with 9 additions and 2 deletions
+1 -1
View File
@@ -149,7 +149,7 @@ If `requestedAvatarColor` is provided and is in `AVATAR_COLORS`, use it. Otherwi
4. Check username uniqueness (exact match on lowercased username)
5. Hash password (bcrypt, 12 rounds)
6. Generate Snowflake ID
7. Insert user row with `status: 'online'`, admin flag if first user
7. Insert user row (status defaults to `'offline'` at the schema level; it is set to `'online'` only when the client establishes a WebSocket via the WS auth path in `ws/handler.ts`). Admin flag set if first user.
8. Sign JWT with `{ userId, username }`
9. Return `{ token, user }` (user sanitized via `sanitizeUser(user, true)`)
+8 -1
View File
@@ -166,12 +166,19 @@ export async function authRoutes(app: FastifyInstance): Promise<void> {
? requestedAvatarColor
: AVATAR_COLORS[Math.floor(Math.random() * AVATAR_COLORS.length)];
// Note: status is left at the schema default ('offline') and is set to
// 'online' exclusively by the WebSocket auth path (ws/handler.ts). A
// successful REST /register does not by itself imply a live connection —
// the client may never establish a WS (transient network failure, mobile
// background, error path between this 201 response and /ws connect),
// which would otherwise produce a permanently stuck-online row that no
// disconnect timer can clean up. The WS handshake will flip it to
// 'online' once a real socket attaches.
db.insert(schema.users).values({
id: userId,
username: trimmedUsername,
displayName: displayName?.trim() || null,
passwordHash,
status: 'online',
isAdmin: isFirstUser ? 1 : 0,
homeInstance: homeInstance || null,
homeUserId: (homeInstance && homeUserId && typeof homeUserId === 'string') ? homeUserId : null,