Skip to content

feat: send + cache verified author identity on share#371

Merged
tomasz-tomczyk merged 1 commit intomainfrom
share-author-attribution
Apr 27, 2026
Merged

feat: send + cache verified author identity on share#371
tomasz-tomczyk merged 1 commit intomainfrom
share-author-attribution

Conversation

@tomasz-tomczyk
Copy link
Copy Markdown
Owner

@tomasz-tomczyk tomasz-tomczyk commented Apr 27, 2026

Summary

  • Each shared comment now carries a cosmetic author_display_name (cfg.Author / git / GitHub / "Anonymous" fallback) and a verified user_id (stamped from cached cfg.AuthUserID). The server is the source of truth on whether to persist user_id.
  • ~/.crit.config.json gains auth_user_id alongside name/email; lazy-backfilled via /api/auth/whoami on first share if missing (5s timeout). Cleared atomically alongside the token via clearAuthIdentity on logout or 401.
  • errShareUnauthorized propagates from shareFilesToWeb / upsertShareToWeb / fetchWebComments; callers clear cached identity and exit.
  • Initial POST now sends external_id so subsequent PUT upserts can match existing rows server-side.
  • mergeWebComments preserves fetched user_id verbatim — never overwrites with the local sharer's id.
  • Server auth state is now mutex-guarded; config writes go through atomicWriteFile (temp + fsync + rename) so a crash mid-write can't truncate the bearer token.
  • gh api users/{login} lookups use exec.CommandContext with a 10s timeout to avoid hangs.

Backwards-compatible: omitempty on UserID means old .crit.json files deserialize cleanly and old CLIs talking to a new server resolve to anonymous.

Review

  • Code review: passed (go-expert fresh-eyes pass + targeted fixes for race + atomic-write + ctx timeout)
  • Parity audit: N/A (no frontend touched)

Test plan

  • go test -race -count=1 ./... — clean
  • gofmt -l . — clean
  • Full crit↔crit-web share integration suite (./scripts/e2e-share.sh) — all 24 attribution + share-sync tests pass

See also: tomasz-tomczyk/crit-web#131 (server side)

🤖 Generated with Claude Code

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 27, 2026

Codecov Report

❌ Patch coverage is 54.40613% with 119 lines in your changes missing coverage. Please review.
✅ Project coverage is 66.66%. Comparing base (22e730d) to head (ba9b7fd).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
auth.go 14.03% 49 Missing ⚠️
main.go 21.62% 28 Missing and 1 partial ⚠️
github.go 78.57% 14 Missing and 1 partial ⚠️
share.go 68.18% 8 Missing and 6 partials ⚠️
server.go 71.42% 11 Missing and 1 partial ⚠️

❌ Your patch status has failed because the patch coverage (54.40%) is below the target coverage (80.00%). You can increase the patch coverage or adjust the target coverage.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #371      +/-   ##
==========================================
- Coverage   67.10%   66.66%   -0.45%     
==========================================
  Files          18       18              
  Lines        7823     7964     +141     
==========================================
+ Hits         5250     5309      +59     
- Misses       2181     2257      +76     
- Partials      392      398       +6     
Flag Coverage Δ
e2e 34.80% <22.98%> (-0.20%) ⬇️
unit 62.56% <53.63%> (-0.38%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Adds proper author attribution for the crit -> crit-web share flow.
Each comment now carries:

- author_display_name (cosmetic): falls back to cfg.Author when the local
  comment has no author set; resolves friendlier GitHub display names
  (cached gh api users/<login>) instead of bare logins.
- user_id (verified identity): stamped at write time from cfg.AuthUserID
  and serialized in the share payload. The server is the source of truth
  on whether to persist it.

Other changes:
- ~/.crit.config.json gains auth_user_id alongside name/email; lazy-
  backfilled via /api/auth/whoami on first share if missing (5s timeout).
  Cleared atomically alongside the token via clearAuthIdentity on logout
  or 401.
- 401 propagation: shareFilesToWeb / upsertShareToWeb / fetchWebComments
  return errShareUnauthorized; callers clear cached identity and exit.
- Initial POST now sends external_id so subsequent PUT upserts can match
  existing rows server-side.
- mergeWebComments preserves fetched user_id verbatim — never overwrites
  with the local sharer's id.
- Server auth state is now mutex-guarded; config writes go through the
  existing atomicWriteFile (temp + fsync + rename) so a crash mid-write
  can't truncate the bearer token.

Backwards-compatible: omitempty on UserID means old .crit.json files
deserialise cleanly and old CLIs talking to a new server resolve to anon.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@tomasz-tomczyk tomasz-tomczyk force-pushed the share-author-attribution branch from b9adba6 to ba9b7fd Compare April 27, 2026 08:54
@tomasz-tomczyk tomasz-tomczyk merged commit 91b0b31 into main Apr 27, 2026
5 of 6 checks passed
@tomasz-tomczyk tomasz-tomczyk deleted the share-author-attribution branch April 27, 2026 09:03
tomasz-tomczyk added a commit that referenced this pull request May 5, 2026
* fix: atomically rewrite auth_token + identity on login

Login persisted credentials in two separate saveGlobalConfig calls
(saveAuthToken then saveAuthIdentity), leaving a window where a crash
between writes could pair a fresh token with stale identity from the
prior account — the failure mode that surfaced #371. Collapse them
into one saveAuthSession closure that writes all four auth_* fields
in a single atomic temp+rename.

Test plan
- Unit: TestSaveAuthSession_AtomicRewrite, _RemovesFieldsAbsentFromResponse
- Integration (e2e-share): TestAuthSessionReLoginEndToEnd seeds two
  users on crit-web, persists user A then user B via saveAuthSession,
  runs `crit share` with HOME pointed at the test home (no
  CRIT_AUTH_TOKEN env override), and asserts the server stamps user B
  on the resulting comment.

* refactor: drop dead saveAuthToken / saveAuthIdentity / removeAuthToken

These three helpers no longer have any production callers — saveAuthSession
is the sole login-persistence path, clearAuthIdentity owns logout/401, and
lazyBackfillAuthUserID writes its own narrower closure. The remaining tests
exercised them in isolation, which means they passed forever regardless of
whether anything called them. Repoint the two TestSaveGlobalConfig_* cases
at saveAuthSession (which still exercises saveGlobalConfig under the hood),
and drop TestSaveAndRemoveAuthToken plus the TestSaveAuthIdentity_* pair —
TestSaveAuthSession_AtomicRewrite and _RemovesFieldsAbsentFromResponse cover
the same ground plus auth_token.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant