Skip to content

feat(version): GitHub OAuth author verification for version commit#78

Merged
iamvirul merged 6 commits intomainfrom
feature/version-github-auth
Apr 4, 2026
Merged

feat(version): GitHub OAuth author verification for version commit#78
iamvirul merged 6 commits intomainfrom
feature/version-github-auth

Conversation

@iamvirul
Copy link
Copy Markdown
Owner

@iamvirul iamvirul commented Apr 4, 2026

Summary

Closes #77

Implements GitHub OAuth device flow authentication for the versioning system. Verified GitHub identity is stored in .deepdiffdb/config and used automatically as the commit author — no --author flag required.

Changes

Code

  • internal/version/auth.go — full GitHub device flow: RunGitHubDeviceFlow, LoadIdentity, SaveIdentity, ResolveClientID
  • cmd/deepdiffdb/main.goversion init prompts for GitHub auth; version commit resolves author from config first
  • version init --skip-auth flag for CI/scripted environments
  • Client ID via DEEPDIFFDB_GITHUB_CLIENT_ID env var or build-time -ldflags injection
  • Token never stored — used only to verify identity via GET /api.github.com/user

Tests

  • tests/version/auth_test.go — 5 new tests: round-trip save/load, missing file, 0o600 permissions, overwrite, corrupt JSON, ResolveClientID env var priority

Docs

  • CHANGELOG.md[Unreleased] entry
  • ROADMAP.md — GitHub Auth marked done
  • README.md — version workflow updated; no --author needed when authenticated
  • website/docs/commands/version.md--skip-auth flag, author resolution order, GitHub OAuth App setup guide
  • website/docs/features/git-versioning.md — verified authorship section, updated storage layout
  • website/docs/deployment/cicd.mdversion commit in CI example with --skip-auth

Test plan

  • go test ./tests/version/... — all 47+ tests pass
  • deepdiffdb version init — prompts for GitHub auth when DEEPDIFFDB_GITHUB_CLIENT_ID is set
  • deepdiffdb version init --skip-auth — skips prompt, no error
  • deepdiffdb version commit after auth — uses github:<username>, no --author needed
  • deepdiffdb version commit --author X after auth — warning printed, config identity wins
  • deepdiffdb version commit without auth and no --author — returns error

Setup required

Before end-to-end testing register a GitHub OAuth App with Device Flow enabled and set DEEPDIFFDB_GITHUB_CLIENT_ID. See the GitHub OAuth setup guide in the docs.

Summary by CodeRabbit

  • New Features

    • GitHub OAuth-based verified authorship for commits
    • version init performs interactive GitHub device-flow auth (can be skipped with --skip-auth)
    • version commit auto-attributes commits as github:<username> when verified; --author is ignored in that case
  • Documentation

    • Updated command docs, workflow examples, CI guidance, and roadmap to reflect auth and config behavior
    • Guidance that identity is stored locally and tokens are not persisted
  • Tests

    • Added unit tests covering identity persistence and client-ID resolution

iamvirul added 3 commits April 4, 2026 15:53
- Add internal/version/auth.go: GitHub device flow, LoadIdentity/SaveIdentity
- version init prompts for GitHub auth; stores verified username in .deepdiffdb/config
- version init --skip-auth bypasses prompt for CI/scripted environments
- version commit reads identity from config; github:<username> used as author automatically
- --author flag still works when no config identity exists (backward compat)
- DEEPDIFFDB_GITHUB_CLIENT_ID env var or build-time -ldflags injection for client ID
- 5 new unit tests in tests/version/auth_test.go

Closes #77
…eature

- CHANGELOG.md: add [Unreleased] entry for issue #77
- ROADMAP.md: mark GitHub auth as in-progress, strikethrough completed versioning items
- README.md: update version workflow — no --author needed when authenticated
- website/docs/commands/version.md: add --skip-auth flag, author resolution order,
  GitHub OAuth setup guide, updated storage layout with config file
- website/docs/features/git-versioning.md: add verified authorship section,
  update repo layout diagram and typical workflow
@github-actions github-actions bot added documentation Improvements or additions to documentation tests core cli labels Apr 4, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 4, 2026

📝 Walkthrough

Walkthrough

This change adds GitHub OAuth device-flow to version init, persists a verified GitHub username to .deepdiffdb/config (token not stored), and makes version commit automatically use github:<username> when identity exists; docs and tests accompany the implementation.

Changes

Cohort / File(s) Summary
Changelog & High-level Roadmap
CHANGELOG.md, ROADMAP.md
Documented GitHub OAuth author verification; marked related versioning work items as completed/in-progress and reordered roadmap sections.
CLI: init & commit
cmd/deepdiffdb/main.go
Added --skip-auth to version init; implemented device-flow auth path, client ID resolution, identity persistence, and updated version commit to prefer stored github:<username> and ignore --author when present.
Auth implementation
internal/version/auth.go
New module: GitHubClientID var, IdentityConfig type, LoadIdentity, SaveIdentity (writes config with 0o600), ResolveClientID, and RunGitHubDeviceFlow (device-code polling, error handling, fetch /user); tokens are not persisted.
Unit tests
tests/version/auth_test.go
New tests covering save/load roundtrip, file permissions (0600), overwrite behavior, malformed-config error, and client ID resolution precedence with env var.
Documentation: CLI & guides
README.md, website/docs/commands/version.md, website/docs/features/git-versioning.md
Updated docs to describe device-flow auth during version init, .deepdiffdb/config storage details (no token, 0o600), --skip-auth, and automatic github:<username> authoring; examples adjusted to remove explicit --author.
Documentation: CI / deployment
website/docs/deployment/cicd.md
Added CI guidance and GitHub Actions example using --skip-auth and recommending explicit --author in CI; tips updated accordingly.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant CLI as deepdiffdb CLI
    participant GitHub as GitHub API
    participant FS as File System

    User->>CLI: deepdiffdb version init
    CLI->>User: Prompt: "Authenticate with GitHub?"
    User->>CLI: Yes
    CLI->>GitHub: POST /login/device/code (client_id)
    GitHub-->>CLI: device_code, user_code, verification_uri
    CLI->>User: Print verification_uri & user_code
    Note over User,GitHub: User authorizes via verification_uri
    loop Poll token endpoint
        CLI->>GitHub: POST /login/oauth/access_token (device_code)
        GitHub-->>CLI: authorization_pending / slow_down / access_token / error
    end
    CLI->>GitHub: GET /user (Bearer access_token)
    GitHub-->>CLI: { login: username }
    CLI->>FS: Save { github_user: username } to .deepdiffdb/config (rgba(0,128,0,0.5))
    
    Note over CLI,FS: Later on commit
    User->>CLI: deepdiffdb version commit --message "..."
    CLI->>FS: Load .deepdiffdb/config
    FS-->>CLI: { github_user: username }
    CLI->>CLI: Set author = "github:username"
    CLI->>FS: Write version commit with author
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 I hopped to GitHub, device-code in paw,
Verified my name — no token to store.
Commits now signed as github:you,
init asks once, CI skips through,
A little rabbit cheered: "Trust restored!"

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The PR title clearly summarizes the main change: implementing GitHub OAuth-based author verification for the version commit workflow.
Linked Issues check ✅ Passed All core requirements from issue #77 are met: GitHub OAuth device-flow authentication during version init, verified username persistence in .deepdiffdb/config, automatic author attribution in version commit, backward compatibility with --skip-auth for CI, and no token persistence.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing GitHub OAuth author verification. No unrelated functionality or scope creep detected in code or documentation updates.
Docstring Coverage ✅ Passed Docstring coverage is 81.25% which is sufficient. The required threshold is 80.00%.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/version-github-auth

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov
Copy link
Copy Markdown

codecov bot commented Apr 4, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
cmd/deepdiffdb/main.go (1)

1574-1581: ⚠️ Potential issue | 🟠 Major

Don’t return before auth setup on an existing repo.

This makes the new identity flow unreachable once .deepdiffdb/ already exists. A cloned repo that shares .deepdiffdb/ but keeps .deepdiffdb/config local, a repo first initialized with --skip-auth, or even a first run that failed after creating the repo has no supported way to add a verified identity later.

🔁 Suggested fix
-	if vcs.IsInitialized(*dir) {
-		fmt.Println("Version repository already initialised.")
-		return nil
-	}
-	if err := vcs.Init(*dir); err != nil {
-		return err
-	}
-	fmt.Printf("Initialised version repository in %s/%s\n", *dir, vcs.RepoDirName)
+	if vcs.IsInitialized(*dir) {
+		fmt.Println("Version repository already initialised.")
+	} else {
+		if err := vcs.Init(*dir); err != nil {
+			return err
+		}
+		fmt.Printf("Initialised version repository in %s/%s\n", *dir, vcs.RepoDirName)
+	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/deepdiffdb/main.go` around lines 1574 - 1581, The current branch returns
early when vcs.IsInitialized(*dir) is true, preventing the subsequent
auth/identity setup from running for existing repos; change the control flow so
that you do not return on an already-initialized repo—only call vcs.Init(*dir)
when !vcs.IsInitialized(*dir), otherwise print the "already initialised" message
and continue into the auth/identity setup logic; keep the existing error
handling for vcs.Init and still print the success message using vcs.RepoDirName
when Init runs.
🧹 Nitpick comments (2)
website/docs/commands/version.md (2)

402-402: Minor formatting inconsistency.

Line 402 is missing a colon after "identity" while line 72 has it. For consistency:

✨ Suggested fix
-  config                        ← verified identity {"github_user": "..."} (0o600)
+  config                        ← verified identity: {"github_user": "..."} (0o600)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@website/docs/commands/version.md` at line 402, Update the text entry "config 
← verified identity {"github_user": "..."} (0o600)" to include a colon after
"identity" so it reads "config                        ← verified identity:
{"github_user": "..."} (0o600)" to match the formatting used elsewhere (e.g.,
the line at 72).

117-117: Clarify the --author default value.

The "Default" column showing $USER could be misleading because the actual author resolution is a multi-step priority chain (documented in lines 123-131), not a simple default. Consider changing the default to something like _(resolved, see below)_ or _(see author resolution)_ to direct users to the detailed explanation.

📝 Suggested clarification
-| `--author` | `$USER` | Author name — **ignored** when GitHub identity is stored in `.deepdiffdb/config` |
+| `--author` | _(see author resolution)_ | Author name — **ignored** when GitHub identity is stored in `.deepdiffdb/config` |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@website/docs/commands/version.md` at line 117, Update the table row for the
`--author` flag so the "Default" column no longer shows a literal `$USER`;
replace it with a clarifying placeholder such as "_(resolved, see author
resolution below)_", "_(see author resolution)_", or similar that points readers
to the multi-step author resolution described later (lines describing author
resolution), and ensure the `--author` row text still notes that the flag is
ignored when GitHub identity is stored in `.deepdiffdb/config`.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@CHANGELOG.md`:
- Around line 16-17: Update the CHANGELOG entry that currently says "5 new unit
tests in `tests/version/auth_test.go`" to the correct count "6 new unit tests in
`tests/version/auth_test.go`" so the changelog matches the actual additions;
verify the line referencing `internal/version/auth.go` remains unchanged and
commit the corrected single-line entry.

In `@cmd/deepdiffdb/main.go`:
- Around line 1706-1725: The code currently falls back to os.Getenv("USER" )
when neither vcs.LoadIdentity(dir) returns a verified identity nor the --author
flag is provided; remove that implicit $USER fallback so the contract becomes
“verified identity or explicit --author”: in the block handling authorName
(after calling vcs.LoadIdentity and when authorName == ""), stop reading USER
from the environment and instead immediately return the existing error
fmt.Errorf("no author set: authenticate with 'version init' or pass --author
<name>") if *author == ""; ensure authorName is only set to *author when
provided and keep the "github:"+ prefix and warning behavior when a verified
identity exists.

In `@internal/version/auth.go`:
- Around line 69-70: The SaveIdentity write uses os.WriteFile(path,
append(data,'\n'), 0o600) which only applies mode on create—add an explicit
os.Chmod(path, 0o600) immediately after the WriteFile call (checking and
returning any error) to enforce owner-only perms on every overwrite; update the
TestSaveIdentityOverwrite test to assert file mode is 0600 after the second
SaveIdentity call.

In `@tests/version/auth_test.go`:
- Around line 123-132: After clearing the env var, explicitly assert the
fallback by setting the package-level GitHubClientID to a known value, call
vcs.ResolveClientID() and compare it to that known value; make sure to save the
original GitHubClientID before changing it and restore it after the assertion to
avoid test interference. Use the existing symbols envKey, vcs.ResolveClientID(),
and the GitHubClientID package variable to locate where to inject the
setup/assertion.

In `@website/docs/features/git-versioning.md`:
- Around line 63-66: Update the doc example to show that .deepdiffdb/config
stores the raw GitHub username under the key github_user (e.g., github_user:
"iamvirul") and clarify that the "github:" prefix is not stored on disk but is
added later by the version commit flow (deepdiffdb version init and deepdiffdb
version commit). Replace the current example line that implies "github:iamvirul"
is written to disk with the raw config value and a short note that the prefixed
author string is derived at commit time.

---

Outside diff comments:
In `@cmd/deepdiffdb/main.go`:
- Around line 1574-1581: The current branch returns early when
vcs.IsInitialized(*dir) is true, preventing the subsequent auth/identity setup
from running for existing repos; change the control flow so that you do not
return on an already-initialized repo—only call vcs.Init(*dir) when
!vcs.IsInitialized(*dir), otherwise print the "already initialised" message and
continue into the auth/identity setup logic; keep the existing error handling
for vcs.Init and still print the success message using vcs.RepoDirName when Init
runs.

---

Nitpick comments:
In `@website/docs/commands/version.md`:
- Line 402: Update the text entry "config                        ← verified
identity {"github_user": "..."} (0o600)" to include a colon after "identity" so
it reads "config                        ← verified identity: {"github_user":
"..."} (0o600)" to match the formatting used elsewhere (e.g., the line at 72).
- Line 117: Update the table row for the `--author` flag so the "Default" column
no longer shows a literal `$USER`; replace it with a clarifying placeholder such
as "_(resolved, see author resolution below)_", "_(see author resolution)_", or
similar that points readers to the multi-step author resolution described later
(lines describing author resolution), and ensure the `--author` row text still
notes that the flag is ignored when GitHub identity is stored in
`.deepdiffdb/config`.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7c4bc07b-2e4c-4522-88a9-4426aa3f2bde

📥 Commits

Reviewing files that changed from the base of the PR and between 0a045b8 and 121d416.

📒 Files selected for processing (9)
  • CHANGELOG.md
  • README.md
  • ROADMAP.md
  • cmd/deepdiffdb/main.go
  • internal/version/auth.go
  • tests/version/auth_test.go
  • website/docs/commands/version.md
  • website/docs/deployment/cicd.md
  • website/docs/features/git-versioning.md

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
internal/version/auth.go (1)

68-71: ⚠️ Potential issue | 🟠 Major

Enforce 0600 on every write, not only file creation.

SaveIdentity currently relies on os.WriteFile(..., 0o600), which does not reset mode on overwrite. If permissions drift, Line 69 preserves insecure mode and breaks the owner-only guarantee.

Suggested fix
 path := filepath.Join(dir, RepoDirName, configFileName)
 if err := os.WriteFile(path, append(data, '\n'), 0o600); err != nil { // `#nosec` G306
 	return fmt.Errorf("writing identity config: %w", err)
 }
+if err := os.Chmod(path, 0o600); err != nil {
+	return fmt.Errorf("setting identity config permissions: %w", err)
+}
 return nil
#!/bin/bash
set -euo pipefail

# Verify SaveIdentity currently writes but does/doesn't enforce chmod afterward.
rg -n -C2 'func SaveIdentity|WriteFile|Chmod' internal/version/auth.go

# Verify overwrite test coverage includes permission assertion after overwrite.
rg -n -C3 'TestSaveIdentityOverwrite|0o600|Mode\(\)\.Perm\(\)' tests/version/auth_test.go
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/version/auth.go` around lines 68 - 71, SaveIdentity currently calls
os.WriteFile(path, ..., 0o600) which does not change the mode on an existing
file; ensure owner-only permissions are enforced on every write by calling
os.Chmod(path, 0o600) immediately after the WriteFile succeeds. Update the
SaveIdentity implementation (the block that constructs path using RepoDirName
and configFileName and writes the identity file) to perform a chmod on path
after successful write and return any chmod error wrapped similarly to the
existing fmt.Errorf pattern.
🧹 Nitpick comments (2)
internal/version/auth.go (2)

54-57: Normalize loaded username before returning it.

LoadIdentity returns raw cfg.GitHubUser; whitespace-only values are treated as authenticated downstream and become github: . Trim before returning.

Suggested fix
 	var cfg IdentityConfig
 	if err := json.Unmarshal(data, &cfg); err != nil {
 		return "", fmt.Errorf("parsing identity config: %w", err)
 	}
-	return cfg.GitHubUser, nil
+	return strings.TrimSpace(cfg.GitHubUser), nil
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/version/auth.go` around lines 54 - 57, LoadIdentity currently
returns the raw cfg.GitHubUser which can be whitespace-only; update LoadIdentity
to normalize the username by trimming surrounding whitespace (e.g.,
strings.TrimSpace) on cfg.GitHubUser before returning and treat an empty result
as unauthenticated (return empty string or error as existing semantics expect).
Ensure you modify the return flow in LoadIdentity so the trimmed value is
returned instead of the raw cfg.GitHubUser.

14-25: Wire build-time GitHubClientID injection into release pipelines.

This file supports ldflags injection, but release/build configs should consistently pass -X github.com/iamvirul/deepdiff-db/internal/version.GitHubClientID=...; otherwise binaries default to auth-disabled unless env vars are set at runtime.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/version/auth.go` around lines 14 - 25, Build pipelines aren't
injecting the GitHubClientID via ldflags, leaving GitHubClientID empty at
runtime; update your release/build configs to pass -X
github.com/iamvirul/deepdiff-db/internal/version.GitHubClientID=<id> into go
build (or go build -ldflags) for every build target and CI job that produces
binaries so the injected value replaces the package-level GitHubClientID
variable; ensure quoting/escaping is correct for your shell/CI, and keep the
runtime environment variable behavior as the precedence mechanism if desired.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/version/auth.go`:
- Line 218: The error message string "device code expired — run `version init
--auth` again" is incorrect because the CLI uses an opt-out flag `--skip-auth`;
update the message in internal/version/auth.go to instruct users to run the
default auth flow (e.g., "run `version init`" or "run `version init` and do not
use --skip-auth") instead of referencing `--auth`, so replace the existing
literal with a corrected hint that mentions `version init` (and optionally
explicitly note to omit `--skip-auth`).

---

Duplicate comments:
In `@internal/version/auth.go`:
- Around line 68-71: SaveIdentity currently calls os.WriteFile(path, ..., 0o600)
which does not change the mode on an existing file; ensure owner-only
permissions are enforced on every write by calling os.Chmod(path, 0o600)
immediately after the WriteFile succeeds. Update the SaveIdentity implementation
(the block that constructs path using RepoDirName and configFileName and writes
the identity file) to perform a chmod on path after successful write and return
any chmod error wrapped similarly to the existing fmt.Errorf pattern.

---

Nitpick comments:
In `@internal/version/auth.go`:
- Around line 54-57: LoadIdentity currently returns the raw cfg.GitHubUser which
can be whitespace-only; update LoadIdentity to normalize the username by
trimming surrounding whitespace (e.g., strings.TrimSpace) on cfg.GitHubUser
before returning and treat an empty result as unauthenticated (return empty
string or error as existing semantics expect). Ensure you modify the return flow
in LoadIdentity so the trimmed value is returned instead of the raw
cfg.GitHubUser.
- Around line 14-25: Build pipelines aren't injecting the GitHubClientID via
ldflags, leaving GitHubClientID empty at runtime; update your release/build
configs to pass -X
github.com/iamvirul/deepdiff-db/internal/version.GitHubClientID=<id> into go
build (or go build -ldflags) for every build target and CI job that produces
binaries so the injected value replaces the package-level GitHubClientID
variable; ensure quoting/escaping is correct for your shell/CI, and keep the
runtime environment variable behavior as the precedence mechanism if desired.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 16c8c049-44c3-4148-a9f8-47410bbfe3b6

📥 Commits

Reviewing files that changed from the base of the PR and between 121d416 and d8378fd.

📒 Files selected for processing (1)
  • internal/version/auth.go

iamvirul added 2 commits April 4, 2026 16:51
- CHANGELOG: correct test count from 5 → 6 unit tests
- auth.go: add os.Chmod(0o600) after WriteFile so overwrites preserve permissions
- main.go: remove $USER fallback in author resolution — explicit author or verified
  identity required; ambiguous fallback undermined the verification contract
- main.go: fix runVersionInit early return — existing repo now continues to auth
  prompt, enabling users to add GitHub identity to a repo initialised with --skip-auth
- auth_test.go: TestResolveClientID now explicitly asserts GitHubClientID fallback
  value instead of just asserting no panic; TestSaveIdentityOverwrite now verifies
  0600 permissions are preserved after an overwrite
- git-versioning.md: fix misleading comment — config stores plain username JSON,
  not "github:<username>" prefix (prefix is added at commit time)
- blog: add closing line
@iamvirul iamvirul merged commit 8abb276 into main Apr 4, 2026
15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cli core documentation Improvements or additions to documentation tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE] Authenticate author via GitHub OAuth during version init

1 participant