docs: document register/login and dual authentication model #77

Merged
sneak merged 3 commits from docs/update-readme-auth-model into main 2026-03-17 12:44:48 +01:00
Collaborator

closes #36

The README claimed "no accounts" and "no passwords" but the codebase has POST /api/v1/register and POST /api/v1/login endpoints with bcrypt password hashing. This PR updates the README to accurately describe the dual authentication model.

Changes

Identity & Sessions section

  • Renamed from "No Accounts" to "Dual Authentication Model"
  • Documented anonymous sessions (POST /api/v1/session) as the instant-access path
  • Documented optional account registration (POST /api/v1/register) with password requirements
  • Documented login (POST /api/v1/login) for returning to registered accounts
  • Updated rationale to explain why both paths exist

API Reference

  • Added POST /api/v1/register endpoint documentation: request/response format, field constraints (min 8 char password), error codes, curl example
  • Added POST /api/v1/login endpoint documentation: request/response format, channel state initialization behavior, error codes, curl example

Security Model → Authentication

  • Added password hashing details (bcrypt at default cost)
  • Documented that anonymous sessions have empty password_hash and cannot use /login
  • Distinguished between anonymous and registered auth paths

Design Principles

  • Changed principle #2 from "No accounts" to "Accounts optional" with updated description

Schema section

  • Updated from outdated users table to actual sessions table (with password_hash, signing_key, away_message, uuid columns)
  • Added clients table documentation (session_id FK, token, uuid)

Session Lifecycle

  • Added "Registered Account" flow diagram showing register → use → login-from-new-device

Multi-Client Model

  • Updated MVP note to document that POST /api/v1/login is the working multi-client mechanism

Client Development Guide

  • Added register and login curl examples alongside anonymous session creation
  • Updated error handling and reconnection guidance for registered accounts

Data Lifecycle

  • Documented that registered sessions persist across logouts (unlike anonymous)
  • Added client lifecycle documentation

Other

  • Fixed token storage description (SHA-256 hash, not raw)
  • Updated "What didn't change" section to reflect optional accounts
closes #36 The README claimed "no accounts" and "no passwords" but the codebase has `POST /api/v1/register` and `POST /api/v1/login` endpoints with bcrypt password hashing. This PR updates the README to accurately describe the dual authentication model. ## Changes ### Identity & Sessions section - Renamed from "No Accounts" to "Dual Authentication Model" - Documented anonymous sessions (`POST /api/v1/session`) as the instant-access path - Documented optional account registration (`POST /api/v1/register`) with password requirements - Documented login (`POST /api/v1/login`) for returning to registered accounts - Updated rationale to explain why both paths exist ### API Reference - Added `POST /api/v1/register` endpoint documentation: request/response format, field constraints (min 8 char password), error codes, curl example - Added `POST /api/v1/login` endpoint documentation: request/response format, channel state initialization behavior, error codes, curl example ### Security Model → Authentication - Added password hashing details (bcrypt at default cost) - Documented that anonymous sessions have empty `password_hash` and cannot use `/login` - Distinguished between anonymous and registered auth paths ### Design Principles - Changed principle #2 from "No accounts" to "Accounts optional" with updated description ### Schema section - Updated from outdated `users` table to actual `sessions` table (with `password_hash`, `signing_key`, `away_message`, `uuid` columns) - Added `clients` table documentation (session_id FK, token, uuid) ### Session Lifecycle - Added "Registered Account" flow diagram showing register → use → login-from-new-device ### Multi-Client Model - Updated MVP note to document that `POST /api/v1/login` is the working multi-client mechanism ### Client Development Guide - Added register and login curl examples alongside anonymous session creation - Updated error handling and reconnection guidance for registered accounts ### Data Lifecycle - Documented that registered sessions persist across logouts (unlike anonymous) - Added client lifecycle documentation ### Other - Fixed token storage description (SHA-256 hash, not raw) - Updated "What didn't change" section to reflect optional accounts
clawbot added 1 commit 2026-03-17 10:18:25 +01:00
docs: document register/login and dual authentication model
All checks were successful
check / check (push) Successful in 1m8s
0900289af5
Update README to accurately describe the authentication model:
- Anonymous sessions via POST /api/v1/session (no account required)
- Optional account registration via POST /api/v1/register (nick + password)
- Login to registered accounts via POST /api/v1/login

Sections updated:
- Identity & Sessions: renamed from 'No Accounts' to 'Dual Authentication Model'
- API Reference: added /register and /login endpoint documentation
- Security Model: added password hashing (bcrypt) details
- Design Principles: changed 'No accounts' to 'Accounts optional'
- Schema: updated from outdated 'users' table to actual sessions/clients tables
- Session Lifecycle: added registered account flow diagram
- Client Development Guide: added register/login curl examples
- Multi-Client Model: documented login as the multi-client mechanism
- Data Lifecycle: documented session/client persistence behavior
clawbot added the needs-reviewbot labels 2026-03-17 10:19:24 +01:00
clawbot reviewed 2026-03-17 10:40:08 +01:00
clawbot left a comment
Author
Collaborator

Review: PR #77 — docs: document register/login and dual authentication model

Closing issue #36.


1. Policy Divergences

Policy Status Notes
Prettier (proseWrap:always, 80 cols) ⚠️ Pre-existing The entire README is not prettier-formatted. This PR follows the same wrap style as existing content. Not introduced here — not blocking.
REPO_POLICIES — schema path README correctly references internal/db/schema/ (matches actual location).
Docker build docker build . passes — all tests green, build succeeds.

2. Accuracy Checklist — Verified Against Source Code

Section Status Details
API endpoint paths (/register, /login) Match internal/server/routes.go lines 79-86.
Register request fields (nick, password) Match registerRequest struct in internal/handlers/auth.go.
Register response (201, {id, nick, token}) Match respondJSON call at auth.go:87-90 with http.StatusCreated.
Register errors (400 invalid nick, 400 short password, 409 nick taken) Exact error strings match auth.go:53, 62, 100.
Login request fields (nick, password) Match loginRequest struct in auth.go.
Login response (200, {id, nick, token}) Match auth.go:191-194 with http.StatusOK.
Login errors (400 nick/password required, 401 invalid credentials) Match auth.go:162, 174.
Login channel state init (MOTD + JOIN/TOPIC/NAMES) deliverMOTD + initChannelState called in auth.go:183-189. initChannelState at api.go:525 enqueues synthetic JOIN + TOPIC/NAMES per channel.
Schema: sessions table columns Matches internal/db/schema/001_initial.sql exactly (id, uuid, nick, password_hash, signing_key, away_message, created_at, last_seen).
Schema: clients table columns Matches schema (id, uuid, session_id FK, token, created_at, last_seen).
Token generation: 32 random bytes → hex → 64 chars tokenBytes = 32 in internal/db/queries.go:19, hex.EncodeToString at :32.
Token storage: SHA-256 hash hashToken at queries.go:37-40 uses sha256.Sum256.
bcrypt at default cost bcryptCost = bcrypt.DefaultCost in internal/db/auth.go:14.
Anonymous sessions: empty password_hash CreateSession at queries.go:103 INSERTs without password_hash, default is ''.
Login rejects empty password_hash if passwordHash == "" check at auth.go:123 returns errNoPassword.
Session persistence for registered accounts WRONG See critical finding below.

3. Critical Finding: Session Persistence Claim Is Incorrect

The README states in the Data Lifecycle section (line 1980-1985):

Sessions: Anonymous sessions are deleted on QUIT or when all clients have logged out. Registered sessions persist across logouts — the session remains so the user can log in again later.

This is factually incorrect. The code treats registered and anonymous sessions identically on cleanup:

handleQuit (internal/handlers/api.go:1796):

_ = hdlr.params.Database.DeleteSession(
    request.Context(), sessionID,
)

Unconditional delete. No check for password_hash.

HandleLogout (internal/handlers/api.go:2411-2415):

if remaining == 0 {
    hdlr.cleanupUser(
        ctx, sessionID, nick,
    )
}

cleanupUser (internal/handlers/api.go:2467):

_ = hdlr.params.Database.DeleteSession(ctx, sessionID)

Also unconditional. No distinction between registered and anonymous sessions.

Impact: A registered user who QUITs or whose last client logs out will have their session deleted, making it impossible to log in again. The registration is lost. This contradicts the README's claim that registered sessions "persist across logouts."

This false claim also propagates to:

  • The Registered Accounts subsection, which implies login can reclaim a session after all clients are gone
  • The Clients subsection in Data Lifecycle: "A session can have multiple clients; removing one doesn't affect others" — true, but removing the last one deletes the session, which isn't noted

The documentation must describe the actual behavior: both session types are deleted when the last client is removed.


4. Build Result

docker build . passes. All tests green. Build completes successfully.


5. Verdict: FAIL

The PR accurately documents the register/login endpoints, schema, error codes, and security model — all verified line-by-line against source code. However, the session persistence claim for registered accounts is demonstrably false based on the actual cleanup code paths. Since the entire purpose of this PR is to make the README accurately reflect the code (closing a BLOCKER about documentation lies), introducing a new false claim about session behavior is a failing condition.

Required fix: Update the Data Lifecycle section and any related text to state that both anonymous and registered sessions are deleted when the last client is removed (via QUIT or logout). If the intent is for registered sessions to persist, that's a code change for a separate issue — the README should document what the code actually does today.

## Review: [PR #77](https://git.eeqj.de/sneak/chat/pulls/77) — docs: document register/login and dual authentication model Closing [issue #36](https://git.eeqj.de/sneak/chat/issues/36). --- ### 1. Policy Divergences | Policy | Status | Notes | |--------|--------|-------| | Prettier (proseWrap:always, 80 cols) | ⚠️ Pre-existing | The entire README is not prettier-formatted. This PR follows the same wrap style as existing content. Not introduced here — not blocking. | | REPO_POLICIES — schema path | ✅ | README correctly references `internal/db/schema/` (matches actual location). | | Docker build | ✅ | `docker build .` passes — all tests green, build succeeds. | --- ### 2. Accuracy Checklist — Verified Against Source Code | Section | Status | Details | |---------|--------|---------| | API endpoint paths (`/register`, `/login`) | ✅ | Match `internal/server/routes.go` lines 79-86. | | Register request fields (`nick`, `password`) | ✅ | Match `registerRequest` struct in `internal/handlers/auth.go`. | | Register response (201, `{id, nick, token}`) | ✅ | Match `respondJSON` call at auth.go:87-90 with `http.StatusCreated`. | | Register errors (400 invalid nick, 400 short password, 409 nick taken) | ✅ | Exact error strings match auth.go:53, 62, 100. | | Login request fields (`nick`, `password`) | ✅ | Match `loginRequest` struct in auth.go. | | Login response (200, `{id, nick, token}`) | ✅ | Match auth.go:191-194 with `http.StatusOK`. | | Login errors (400 nick/password required, 401 invalid credentials) | ✅ | Match auth.go:162, 174. | | Login channel state init (MOTD + JOIN/TOPIC/NAMES) | ✅ | `deliverMOTD` + `initChannelState` called in auth.go:183-189. `initChannelState` at api.go:525 enqueues synthetic JOIN + TOPIC/NAMES per channel. | | Schema: `sessions` table columns | ✅ | Matches `internal/db/schema/001_initial.sql` exactly (id, uuid, nick, password_hash, signing_key, away_message, created_at, last_seen). | | Schema: `clients` table columns | ✅ | Matches schema (id, uuid, session_id FK, token, created_at, last_seen). | | Token generation: 32 random bytes → hex → 64 chars | ✅ | `tokenBytes = 32` in `internal/db/queries.go:19`, `hex.EncodeToString` at :32. | | Token storage: SHA-256 hash | ✅ | `hashToken` at queries.go:37-40 uses `sha256.Sum256`. | | bcrypt at default cost | ✅ | `bcryptCost = bcrypt.DefaultCost` in `internal/db/auth.go:14`. | | Anonymous sessions: empty password_hash | ✅ | `CreateSession` at queries.go:103 INSERTs without password_hash, default is `''`. | | Login rejects empty password_hash | ✅ | `if passwordHash == ""` check at auth.go:123 returns `errNoPassword`. | | **Session persistence for registered accounts** | ❌ **WRONG** | **See critical finding below.** | --- ### 3. Critical Finding: Session Persistence Claim Is Incorrect The README states in the **Data Lifecycle** section (line 1980-1985): > **Sessions**: Anonymous sessions are deleted on `QUIT` or when all clients have logged out. **Registered sessions persist across logouts** — the session remains so the user can log in again later. This is **factually incorrect**. The code treats registered and anonymous sessions identically on cleanup: **`handleQuit`** (`internal/handlers/api.go:1796`): ```go _ = hdlr.params.Database.DeleteSession( request.Context(), sessionID, ) ``` Unconditional delete. No check for `password_hash`. **`HandleLogout`** (`internal/handlers/api.go:2411-2415`): ```go if remaining == 0 { hdlr.cleanupUser( ctx, sessionID, nick, ) } ``` **`cleanupUser`** (`internal/handlers/api.go:2467`): ```go _ = hdlr.params.Database.DeleteSession(ctx, sessionID) ``` Also unconditional. No distinction between registered and anonymous sessions. **Impact:** A registered user who QUITs or whose last client logs out will have their session **deleted**, making it impossible to log in again. The registration is lost. This contradicts the README's claim that registered sessions "persist across logouts." This false claim also propagates to: - The **Registered Accounts** subsection, which implies login can reclaim a session after all clients are gone - The **Clients** subsection in Data Lifecycle: "A session can have multiple clients; removing one doesn't affect others" — true, but removing the *last* one deletes the session, which isn't noted The documentation must describe the **actual** behavior: both session types are deleted when the last client is removed. --- ### 4. Build Result ✅ `docker build .` passes. All tests green. Build completes successfully. --- ### 5. Verdict: ❌ FAIL The PR accurately documents the register/login endpoints, schema, error codes, and security model — all verified line-by-line against source code. However, the session persistence claim for registered accounts is demonstrably false based on the actual cleanup code paths. Since the entire purpose of this PR is to make the README accurately reflect the code (closing a BLOCKER about documentation lies), introducing a new false claim about session behavior is a failing condition. **Required fix:** Update the Data Lifecycle section and any related text to state that both anonymous and registered sessions are deleted when the last client is removed (via QUIT or logout). If the *intent* is for registered sessions to persist, that's a code change for a separate issue — the README should document what the code actually does today.
clawbot added needs-rework and removed needs-review labels 2026-03-17 10:40:40 +01:00
clawbot added 1 commit 2026-03-17 10:47:09 +01:00
docs: fix false session persistence claim for registered accounts
All checks were successful
check / check (push) Successful in 1m4s
2c6e4673c1
The README incorrectly claimed that registered sessions persist across
logouts. The actual code (handleQuit and cleanupUser in api.go) deletes
ALL sessions unconditionally when the last client disconnects — no check
for password_hash.

Updated Data Lifecycle, Registered Accounts, Identity & Sessions rationale,
flow diagram, and Design Principles to accurately state that both anonymous
and registered sessions are deleted on QUIT or last-client-logout.
Registration enables multi-client access (login from another device while
session is active), not session persistence across all-client removal.
Author
Collaborator

Rework: Fixed false session persistence claim

The reviewer correctly identified that the README falsely claimed "Registered sessions persist across logouts" while the actual code (handleQuit at api.go:1796 and cleanupUser at api.go:2467) deletes ALL sessions unconditionally — no check for password_hash.

Changes made

Data Lifecycle → Sessions: Replaced the false claim with accurate text stating both anonymous and registered sessions are deleted on QUIT or when the last client logs out. Explicitly notes that handleQuit and cleanupUser call DeleteSession unconditionally.

Data Lifecycle → Clients: Added note that removing the last client triggers full session deletion (parted from channels, QUIT broadcast, nick released).

Registered Accounts subsection: Changed "persistent identity across sessions" to "multi-client access" and added explicit note that login only works while the session still exists.

Identity & Sessions rationale: Replaced "keep their nick across sessions" framing with "access the same session from multiple devices" and added note that registration does not make sessions survive all-client removal.

Registered Account flow diagram: Changed "Later, from a new device or after token expiry" to "From another device, while session is still active" and added note about session non-existence after all-client logout.

Design Principles: Changed "persistent identity" to "multi-client access".

Curl examples: Changed comment from "persistent identity" to "multi-client support".

All changes describe what the code actually does today. If session persistence for registered accounts is desired, that's a code change for a separate issue.

## Rework: Fixed false session persistence claim The reviewer correctly identified that the README falsely claimed "Registered sessions persist across logouts" while the actual code (`handleQuit` at api.go:1796 and `cleanupUser` at api.go:2467) deletes ALL sessions unconditionally — no check for `password_hash`. ### Changes made **Data Lifecycle → Sessions**: Replaced the false claim with accurate text stating both anonymous and registered sessions are deleted on QUIT or when the last client logs out. Explicitly notes that `handleQuit` and `cleanupUser` call `DeleteSession` unconditionally. **Data Lifecycle → Clients**: Added note that removing the last client triggers full session deletion (parted from channels, QUIT broadcast, nick released). **Registered Accounts subsection**: Changed "persistent identity across sessions" to "multi-client access" and added explicit note that login only works while the session still exists. **Identity & Sessions rationale**: Replaced "keep their nick across sessions" framing with "access the same session from multiple devices" and added note that registration does not make sessions survive all-client removal. **Registered Account flow diagram**: Changed "Later, from a new device or after token expiry" to "From another device, while session is still active" and added note about session non-existence after all-client logout. **Design Principles**: Changed "persistent identity" to "multi-client access". **Curl examples**: Changed comment from "persistent identity" to "multi-client support". All changes describe what the code actually does today. If session persistence for registered accounts is desired, that's a code change for a separate issue.
clawbot added needs-review and removed needs-rework labels 2026-03-17 10:48:09 +01:00
clawbot reviewed 2026-03-17 10:54:50 +01:00
clawbot left a comment
Author
Collaborator

Review: PR #77 — docs: document register/login and dual authentication model (post-rework)

Closing issue #36.


1. Rework Verification — Session Persistence Fix

The first review FAILED because the README falsely claimed "Registered sessions persist across logouts" while the code unconditionally deletes all sessions. The rework addressed this in six places:

Section Fix Applied Verified
Data Lifecycle → Sessions Replaced false persistence claim with accurate "both types deleted unconditionally" language referencing handleQuit and cleanupUser Matches api.go:1796 and api.go:2467
Data Lifecycle → Clients Added note that last-client removal triggers full session deletion Matches api.go:2410 (remaining == 0cleanupUser)
Identity & Sessions → Registered Accounts Changed "persistent identity" to "multi-client access", added explicit "login only works while session still exists" note Accurate
Identity & Sessions → Rationale Replaced "keep their nick across sessions" with "access the same session from multiple devices", added non-persistence caveat Accurate
Registered Account flow diagram Changed to "while session is still active" with note about session non-existence Accurate
Design Principles Changed "persistent identity" to "multi-client access" Accurate

The rework substantively corrected the critical finding. These six sections now accurately describe the code's behavior.


2. Remaining Accuracy Issue — Register Endpoint Description

The /register endpoint intro (line 1100-1101) states:

The nick is claimed and can be reclaimed later via POST /api/v1/login.

"Reclaimed later" implies the nick can be recovered after the session is gone — which is false. If all clients disconnect or the user sends QUIT, DeleteSession is called unconditionally (api.go:1796, api.go:2467), the nick is released, and LoginUser will fail with "get session for login" because the sessions row no longer exists.

The word "reclaimed" implies getting something back that was lost. But login doesn't reclaim a nick — it adds a client to an existing session that already holds the nick. The nick was never lost. And if the session IS gone, reclamation is impossible.

This is the same class of misleading statement the first review caught. The fix is a one-sentence rewrite, e.g.:

The password enables login from additional clients via POST /api/v1/login while the session remains active.


3. Full Accuracy Checklist (All Other Claims)

Claim Status Source
API paths: /register, /login routes.go:79-86
Register request: {nick, password} auth.go registerRequest struct
Register response: 201, {id, nick, token} auth.go:87-90
Register errors (400 invalid nick, 400 short password, 409 nick taken) auth.go:53, 62, 100
Login request: {nick, password} auth.go loginRequest struct
Login response: 200, {id, nick, token} auth.go:191-194
Login errors (400 nick/password required, 401 invalid credentials) auth.go:162, 174
Login channel state init (MOTD + JOIN/TOPIC/NAMES) auth.go:183-189, api.go:525 initChannelState
Schema: sessions table columns 001_initial.sql
Schema: clients table columns 001_initial.sql
Token: 32 random bytes → hex → 64 chars queries.go:19, 32
Token storage: SHA-256 hash queries.go:37-40
bcrypt at default cost auth.go:13
Anonymous sessions: empty password_hash queries.go:103 (INSERT without password_hash, DEFAULT '')
Login rejects empty password_hash auth.go:123
Idle timeout: default 30 days handlers.go:35
Session deletion unconditional (both types) Confirmed in rework verification above
Nick regex: 1-32 chars api.go:18-20 ({0,31} after first char = max 32)

4. Line Length (Minor)

Two new prose lines exceed 80 columns:

  • Line 205 (120 chars): logout); registration does not make a session survive all-client removal. Identity verification at the message layer via — should wrap after "removal."
  • Line 2592 (115 chars): with password is available — but never required. Identity verification at the message layer uses cryptographic — should wrap after "required."

Both appear to be wrapping accidents where two sentences ended up on one line. The previous review noted 80-column compliance is a pre-existing README-wide issue, but these two lines were introduced by this PR and are easily fixable. Not blocking on its own, but should be fixed while the register description is being corrected.


5. Build Result

docker build . passes. All tests green.


6. Verdict: FAIL

The rework was thorough and correctly fixed the major false persistence claim in six locations. All endpoint documentation, schema references, error codes, and security model details are verified accurate against source code.

However, the /register endpoint description still contains "The nick is claimed and can be reclaimed later via POST /api/v1/login" — which implies post-disconnect nick recovery that doesn't work. This is the same class of misleading statement that caused the first review failure. Since the entire purpose of this PR is to make the README accurately describe the code, a remaining misleading statement about session lifecycle is a failing condition.

Required fix:

  1. Reword the register endpoint intro to remove the "reclaimed later" implication. Suggested: "The password enables login from additional clients via POST /api/v1/login while the session remains active."
  2. Fix the two line-length overruns at lines 205 and 2592 (wrap after the period).
## Review: [PR #77](https://git.eeqj.de/sneak/chat/pulls/77) — docs: document register/login and dual authentication model (post-rework) Closing [issue #36](https://git.eeqj.de/sneak/chat/issues/36). --- ### 1. Rework Verification — Session Persistence Fix The first review [FAILED](https://git.eeqj.de/sneak/chat/pulls/77#issuecomment-11753) because the README falsely claimed "Registered sessions persist across logouts" while the code unconditionally deletes all sessions. The rework addressed this in **six places**: | Section | Fix Applied | Verified | |---------|-------------|----------| | Data Lifecycle → Sessions | Replaced false persistence claim with accurate "both types deleted unconditionally" language referencing `handleQuit` and `cleanupUser` | ✅ Matches [api.go:1796](https://git.eeqj.de/sneak/chat/src/branch/docs/update-readme-auth-model/internal/handlers/api.go#L1796) and [api.go:2467](https://git.eeqj.de/sneak/chat/src/branch/docs/update-readme-auth-model/internal/handlers/api.go#L2467) | | Data Lifecycle → Clients | Added note that last-client removal triggers full session deletion | ✅ Matches [api.go:2410](https://git.eeqj.de/sneak/chat/src/branch/docs/update-readme-auth-model/internal/handlers/api.go#L2410) (`remaining == 0` → `cleanupUser`) | | Identity & Sessions → Registered Accounts | Changed "persistent identity" to "multi-client access", added explicit "login only works while session still exists" note | ✅ Accurate | | Identity & Sessions → Rationale | Replaced "keep their nick across sessions" with "access the same session from multiple devices", added non-persistence caveat | ✅ Accurate | | Registered Account flow diagram | Changed to "while session is still active" with note about session non-existence | ✅ Accurate | | Design Principles | Changed "persistent identity" to "multi-client access" | ✅ Accurate | The rework substantively corrected the critical finding. These six sections now accurately describe the code's behavior. --- ### 2. Remaining Accuracy Issue — Register Endpoint Description The `/register` endpoint intro (line 1100-1101) states: > The nick is claimed and can be reclaimed later via `POST /api/v1/login`. "Reclaimed later" implies the nick can be recovered after the session is gone — which is false. If all clients disconnect or the user sends QUIT, `DeleteSession` is called unconditionally ([api.go:1796](https://git.eeqj.de/sneak/chat/src/branch/docs/update-readme-auth-model/internal/handlers/api.go#L1796), [api.go:2467](https://git.eeqj.de/sneak/chat/src/branch/docs/update-readme-auth-model/internal/handlers/api.go#L2467)), the nick is released, and `LoginUser` will fail with "get session for login" because the `sessions` row no longer exists. The word "reclaimed" implies getting something back that was lost. But login doesn't reclaim a nick — it adds a client to an **existing** session that already holds the nick. The nick was never lost. And if the session IS gone, reclamation is impossible. This is the same class of misleading statement the first review caught. The fix is a one-sentence rewrite, e.g.: > The password enables login from additional clients via `POST /api/v1/login` while the session remains active. --- ### 3. Full Accuracy Checklist (All Other Claims) | Claim | Status | Source | |-------|--------|--------| | API paths: `/register`, `/login` | ✅ | [routes.go:79-86](https://git.eeqj.de/sneak/chat/src/branch/docs/update-readme-auth-model/internal/server/routes.go#L79) | | Register request: `{nick, password}` | ✅ | [auth.go `registerRequest` struct](https://git.eeqj.de/sneak/chat/src/branch/docs/update-readme-auth-model/internal/handlers/auth.go#L36) | | Register response: 201, `{id, nick, token}` | ✅ | [auth.go:87-90](https://git.eeqj.de/sneak/chat/src/branch/docs/update-readme-auth-model/internal/handlers/auth.go#L87) | | Register errors (400 invalid nick, 400 short password, 409 nick taken) | ✅ | [auth.go:53, 62, 100](https://git.eeqj.de/sneak/chat/src/branch/docs/update-readme-auth-model/internal/handlers/auth.go#L53) | | Login request: `{nick, password}` | ✅ | [auth.go `loginRequest` struct](https://git.eeqj.de/sneak/chat/src/branch/docs/update-readme-auth-model/internal/handlers/auth.go#L130) | | Login response: 200, `{id, nick, token}` | ✅ | [auth.go:191-194](https://git.eeqj.de/sneak/chat/src/branch/docs/update-readme-auth-model/internal/handlers/auth.go#L191) | | Login errors (400 nick/password required, 401 invalid credentials) | ✅ | [auth.go:162, 174](https://git.eeqj.de/sneak/chat/src/branch/docs/update-readme-auth-model/internal/handlers/auth.go#L162) | | Login channel state init (MOTD + JOIN/TOPIC/NAMES) | ✅ | [auth.go:183-189](https://git.eeqj.de/sneak/chat/src/branch/docs/update-readme-auth-model/internal/handlers/auth.go#L183), [api.go:525 `initChannelState`](https://git.eeqj.de/sneak/chat/src/branch/docs/update-readme-auth-model/internal/handlers/api.go#L525) | | Schema: `sessions` table columns | ✅ | [001_initial.sql](https://git.eeqj.de/sneak/chat/src/branch/docs/update-readme-auth-model/internal/db/schema/001_initial.sql#L5) | | Schema: `clients` table columns | ✅ | [001_initial.sql](https://git.eeqj.de/sneak/chat/src/branch/docs/update-readme-auth-model/internal/db/schema/001_initial.sql#L17) | | Token: 32 random bytes → hex → 64 chars | ✅ | [queries.go:19, 32](https://git.eeqj.de/sneak/chat/src/branch/docs/update-readme-auth-model/internal/db/queries.go#L19) | | Token storage: SHA-256 hash | ✅ | [queries.go:37-40](https://git.eeqj.de/sneak/chat/src/branch/docs/update-readme-auth-model/internal/db/queries.go#L37) | | bcrypt at default cost | ✅ | [auth.go:13](https://git.eeqj.de/sneak/chat/src/branch/docs/update-readme-auth-model/internal/db/auth.go#L13) | | Anonymous sessions: empty `password_hash` | ✅ | [queries.go:103](https://git.eeqj.de/sneak/chat/src/branch/docs/update-readme-auth-model/internal/db/queries.go#L103) (INSERT without `password_hash`, DEFAULT `''`) | | Login rejects empty `password_hash` | ✅ | [auth.go:123](https://git.eeqj.de/sneak/chat/src/branch/docs/update-readme-auth-model/internal/db/auth.go#L123) | | Idle timeout: default 30 days | ✅ | [handlers.go:35](https://git.eeqj.de/sneak/chat/src/branch/docs/update-readme-auth-model/internal/handlers/handlers.go#L35) | | Session deletion unconditional (both types) | ✅ | Confirmed in rework verification above | | Nick regex: 1-32 chars | ✅ | [api.go:18-20](https://git.eeqj.de/sneak/chat/src/branch/docs/update-readme-auth-model/internal/handlers/api.go#L18) (`{0,31}` after first char = max 32) | --- ### 4. Line Length (Minor) Two new prose lines exceed 80 columns: - **Line 205** (120 chars): `logout); registration does not make a session survive all-client removal. Identity verification at the message layer via` — should wrap after "removal." - **Line 2592** (115 chars): `with password is available — but never required. Identity verification at the message layer uses cryptographic` — should wrap after "required." Both appear to be wrapping accidents where two sentences ended up on one line. The previous review noted 80-column compliance is a pre-existing README-wide issue, but these two lines were **introduced by this PR** and are easily fixable. Not blocking on its own, but should be fixed while the register description is being corrected. --- ### 5. Build Result ✅ `docker build .` passes. All tests green. --- ### 6. Verdict: ❌ FAIL The rework was thorough and correctly fixed the major false persistence claim in six locations. All endpoint documentation, schema references, error codes, and security model details are verified accurate against source code. However, the `/register` endpoint description still contains "The nick is claimed and can be reclaimed later via `POST /api/v1/login`" — which implies post-disconnect nick recovery that doesn't work. This is the same class of misleading statement that caused the first review failure. Since the entire purpose of this PR is to make the README accurately describe the code, a remaining misleading statement about session lifecycle is a failing condition. **Required fix:** 1. Reword the register endpoint intro to remove the "reclaimed later" implication. Suggested: *"The password enables login from additional clients via `POST /api/v1/login` while the session remains active."* 2. Fix the two line-length overruns at lines 205 and 2592 (wrap after the period).
clawbot added needs-rework and removed needs-review labels 2026-03-17 10:55:44 +01:00
clawbot added 1 commit 2026-03-17 10:58:44 +01:00
docs: fix false reclaim implication and wrap long lines
All checks were successful
check / check (push) Successful in 1m4s
d91f925483
- Register endpoint: replace 'nick is claimed and can be reclaimed
  later' with accurate description that login adds clients while
  session remains active
- Wrap line 205 (120 chars) after 'all-client removal.'
- Wrap line 2592 (115 chars) after 'never required.'
Author
Collaborator

Rework (2nd): Fixed false "reclaimed later" claim and line-length issues

Addressed both findings from the second review.

Changes

1. Register endpoint description (line ~1100): Replaced "The nick is claimed and can be reclaimed later via POST /api/v1/login" with accurate text: "The password enables login from additional clients via POST /api/v1/login while the session remains active." This removes the false implication that a nick can be recovered after all clients disconnect.

2. Line 205 wrap (was 120 chars): Wrapped after "all-client removal." — now 4 lines within 80 columns.

3. Line ~2592 wrap (was 115 chars): Wrapped after "never required." — now 3 lines within 80 columns.

Full README grep for remaining misleading claims

Searched for persist, reclaim, survive, recover, permanent, durable across the entire README:

  • Line 7: "persistent sessions" — refers to HTTP vs TCP architecture, accurate
  • Lines 89-90: "sessions persist independently of connections" — HTTP polling model description, accurate
  • Line 167: "no way to reclaim it" — about anonymous sessions, accurate
  • Line 205: "does not make a session survive" — our fix, explicitly denies persistence
  • Line 325: "persistent channel registration" — about channels, not sessions

No remaining false claims about session persistence or reclamation.

Build

docker build . passes — all tests green, build succeeds.

## Rework (2nd): Fixed false "reclaimed later" claim and line-length issues Addressed both findings from the [second review](https://git.eeqj.de/sneak/chat/pulls/77#issuecomment-11757). ### Changes **1. Register endpoint description** (line ~1100): Replaced "The nick is claimed and can be reclaimed later via `POST /api/v1/login`" with accurate text: "The password enables login from additional clients via `POST /api/v1/login` while the session remains active." This removes the false implication that a nick can be recovered after all clients disconnect. **2. Line 205 wrap** (was 120 chars): Wrapped after "all-client removal." — now 4 lines within 80 columns. **3. Line ~2592 wrap** (was 115 chars): Wrapped after "never required." — now 3 lines within 80 columns. ### Full README grep for remaining misleading claims Searched for `persist`, `reclaim`, `survive`, `recover`, `permanent`, `durable` across the entire README: - Line 7: "persistent sessions" — refers to HTTP vs TCP architecture, accurate - Lines 89-90: "sessions persist independently of connections" — HTTP polling model description, accurate - Line 167: "no way to reclaim it" — about anonymous sessions, accurate - Line 205: "does not make a session survive" — our fix, explicitly denies persistence - Line 325: "persistent channel registration" — about channels, not sessions No remaining false claims about session persistence or reclamation. ### Build ✅ `docker build .` passes — all tests green, build succeeds.
clawbot added needs-review and removed needs-rework labels 2026-03-17 10:59:28 +01:00
clawbot reviewed 2026-03-17 11:03:34 +01:00
clawbot left a comment
Author
Collaborator

Review: PR #77 — docs: document register/login and dual authentication model (2nd rework)

Closing issue #36.


1. Critical Check: Session Persistence/Reclaim Claims

Grepped the entire README for persist, reclaim, survive, recover, later, permanent, durable. Every match verified:

Line Text Context Accurate?
7 "persistent sessions" HTTP transport description (sessions live in DB, not tied to TCP)
73/75 "persistent TCP" Describing traditional IRC's TCP requirement
89-90 "sessions persist independently of connections... survive disconnects" HTTP vs TCP transport model — sessions survive network interruptions, not all-client logout
167 "no way to reclaim it" Anonymous sessions after QUIT — accurately states nick is lost
205 "registration does not make a session survive all-client removal" Explicitly denies false persistence
326 "persistent channel registration" About channels, not sessions
2606 "persistent connections" About TCP connections

No remaining false claims about session persistence or nick reclamation. The recurring failure class from reviews 1 and 2 is fully resolved.


2. Rework Verification — "reclaimed later" Fix

The /register endpoint intro (line ~1101) now reads:

The password enables login from additional clients via POST /api/v1/login while the session remains active.

The false "reclaimed later" implication is gone.


3. Rework Verification — Line Length Fixes

Line Previous Now Status
205 120 chars 64 chars Fixed
~2594 115 chars 62 chars Fixed

All new prose lines introduced by this PR are within 80 columns. Lines over 80 in new content are exclusively in markdown tables (unavoidable).


4. Full Accuracy Checklist — Verified Against Source Code

Claim Status Source
API paths: /register, /login internal/server/routes.go
Register request: {nick, password} registerRequest struct in auth.go
Register response: 201, {id, nick, token} auth.go respondJSON with http.StatusCreated
Register errors (400 invalid nick, 400 short password, 409 nick taken) auth.go error paths
Login request: {nick, password} loginRequest struct in auth.go
Login response: 200, {id, nick, token} auth.go respondJSON with http.StatusOK
Login errors (400 nick/password required, 401 invalid credentials) auth.go error paths
Login calls deliverMOTD + initChannelState auth.go login handler
Schema: sessions table columns 001_initial.sql (id, uuid, nick, password_hash, signing_key, away_message, created_at, last_seen)
Schema: clients table (session_id FK, cascade delete) 001_initial.sql
Token: 32 random bytes → hex → 64 chars queries.go
Token storage: SHA-256 hash queries.go hashToken
bcrypt at default cost internal/db/auth.go
Anonymous sessions: empty password_hash queries.go INSERT default
Min password length: 8 chars const minPasswordLength = 8 in auth.go
Session deletion unconditional (both types) handleQuit (api.go:1796) and cleanupUser (api.go:2467) — no password_hash check
Last-client removal triggers session deletion remaining == 0cleanupUser (api.go:2409)
Registered account flow diagram says "while session is still active" Accurate
Reconnection guidance distinguishes anonymous vs registered Accurate

5. Build Result

docker build . passes. Lint, fmt-check, tests, and build all succeed.


6. Verdict: PASS

All three findings from the previous two reviews are resolved:

  1. The false "registered sessions persist across logouts" claim — removed and replaced with accurate text in 6+ locations across the first rework
  2. The false "reclaimed later" implication in /register — rewritten to "while the session remains active"
  3. The two line-length overruns — both wrapped well within 80 columns

The full-README grep for persistence/reclaim keywords returns zero false claims. Every documentation statement has been verified against the actual source code. The PR accurately documents the register/login endpoints, dual authentication model, schema, error codes, security model, and session lifecycle — including the critical fact that both session types are deleted unconditionally on cleanup.

## Review: [PR #77](https://git.eeqj.de/sneak/chat/pulls/77) — docs: document register/login and dual authentication model (2nd rework) Closing [issue #36](https://git.eeqj.de/sneak/chat/issues/36). --- ### 1. Critical Check: Session Persistence/Reclaim Claims Grepped the entire README for `persist`, `reclaim`, `survive`, `recover`, `later`, `permanent`, `durable`. Every match verified: | Line | Text | Context | Accurate? | |------|------|---------|-----------| | 7 | "persistent sessions" | HTTP transport description (sessions live in DB, not tied to TCP) | ✅ | | 73/75 | "persistent TCP" | Describing traditional IRC's TCP requirement | ✅ | | 89-90 | "sessions persist independently of connections... survive disconnects" | HTTP vs TCP transport model — sessions survive *network interruptions*, not *all-client logout* | ✅ | | 167 | "no way to reclaim it" | Anonymous sessions after QUIT — accurately states nick is lost | ✅ | | 205 | "registration does not make a session survive all-client removal" | Explicitly denies false persistence | ✅ | | 326 | "persistent channel registration" | About channels, not sessions | ✅ | | 2606 | "persistent connections" | About TCP connections | ✅ | **No remaining false claims about session persistence or nick reclamation.** The recurring failure class from reviews 1 and 2 is fully resolved. --- ### 2. Rework Verification — "reclaimed later" Fix The `/register` endpoint intro (line ~1101) now reads: > The password enables login from additional clients via `POST /api/v1/login` while the session remains active. The false "reclaimed later" implication is gone. ✅ --- ### 3. Rework Verification — Line Length Fixes | Line | Previous | Now | Status | |------|----------|-----|--------| | 205 | 120 chars | 64 chars | ✅ Fixed | | ~2594 | 115 chars | 62 chars | ✅ Fixed | All new prose lines introduced by this PR are within 80 columns. Lines over 80 in new content are exclusively in markdown tables (unavoidable). ✅ --- ### 4. Full Accuracy Checklist — Verified Against Source Code | Claim | Status | Source | |-------|--------|--------| | API paths: `/register`, `/login` | ✅ | `internal/server/routes.go` | | Register request: `{nick, password}` | ✅ | `registerRequest` struct in `auth.go` | | Register response: 201, `{id, nick, token}` | ✅ | `auth.go` `respondJSON` with `http.StatusCreated` | | Register errors (400 invalid nick, 400 short password, 409 nick taken) | ✅ | `auth.go` error paths | | Login request: `{nick, password}` | ✅ | `loginRequest` struct in `auth.go` | | Login response: 200, `{id, nick, token}` | ✅ | `auth.go` `respondJSON` with `http.StatusOK` | | Login errors (400 nick/password required, 401 invalid credentials) | ✅ | `auth.go` error paths | | Login calls `deliverMOTD` + `initChannelState` | ✅ | `auth.go` login handler | | Schema: `sessions` table columns | ✅ | `001_initial.sql` (id, uuid, nick, password_hash, signing_key, away_message, created_at, last_seen) | | Schema: `clients` table (session_id FK, cascade delete) | ✅ | `001_initial.sql` | | Token: 32 random bytes → hex → 64 chars | ✅ | `queries.go` | | Token storage: SHA-256 hash | ✅ | `queries.go` `hashToken` | | bcrypt at default cost | ✅ | `internal/db/auth.go` | | Anonymous sessions: empty `password_hash` | ✅ | `queries.go` INSERT default | | Min password length: 8 chars | ✅ | `const minPasswordLength = 8` in `auth.go` | | Session deletion unconditional (both types) | ✅ | `handleQuit` (api.go:1796) and `cleanupUser` (api.go:2467) — no `password_hash` check | | Last-client removal triggers session deletion | ✅ | `remaining == 0` → `cleanupUser` (api.go:2409) | | Registered account flow diagram says "while session is still active" | ✅ | Accurate | | Reconnection guidance distinguishes anonymous vs registered | ✅ | Accurate | --- ### 5. Build Result ✅ `docker build .` passes. Lint, fmt-check, tests, and build all succeed. --- ### 6. Verdict: ✅ PASS All three findings from the previous two reviews are resolved: 1. The false "registered sessions persist across logouts" claim — removed and replaced with accurate text in 6+ locations across the first rework 2. The false "reclaimed later" implication in `/register` — rewritten to "while the session remains active" 3. The two line-length overruns — both wrapped well within 80 columns The full-README grep for persistence/reclaim keywords returns zero false claims. Every documentation statement has been verified against the actual source code. The PR accurately documents the register/login endpoints, dual authentication model, schema, error codes, security model, and session lifecycle — including the critical fact that both session types are deleted unconditionally on cleanup.
clawbot added merge-ready and removed needs-review labels 2026-03-17 11:04:07 +01:00
sneak was assigned by clawbot 2026-03-17 11:04:08 +01:00
sneak merged commit e9d794764b into main 2026-03-17 12:44:48 +01:00
sneak deleted branch docs/update-readme-auth-model 2026-03-17 12:44:49 +01:00
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: sneak/chat#77