Skip to content

refactor(auth): extract modules/auth submodule and deprecate pkg/auth#429

Open
lml2468 wants to merge 2 commits into
feat/octo-auth-integrationfrom
refactor/modules-auth-skeleton
Open

refactor(auth): extract modules/auth submodule and deprecate pkg/auth#429
lml2468 wants to merge 2 commits into
feat/octo-auth-integrationfrom
refactor/modules-auth-skeleton

Conversation

@lml2468

@lml2468 lml2468 commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Summary

Establish modules/auth/ as the canonical home for octo-server's
Resource-Server-facing token contract — TokenInfo, Encode/Decode,
CacheTokenParser — so future PRs in Stage A can move the verify /
verify-bot / verify-api-key HTTP handlers out of modules/user/api.go
into a package that owns the wire contract.

This is PR-A1 of the six-PR Stage A refactor; subsequent PRs (A2–A5) add the
Lookup interfaces in bot_api / usersecret, port the verify handlers,
introduce verify-api-key, and migrate route registration — each independently
revertible.

This PR is a pure relocation: no HTTP behavior changes, no errcode changes,
no caller migration. The six existing pkg/auth importers (main.go,
modules/{group,message,user,qrcode}/api.go, modules/user/api_manager.go)
continue to compile unchanged through a Deprecated alias shim that will be
removed six months after this PR merges.

Related Issue

Refs #428

Linked Spec

.octospec/tasks/modules-auth-skeleton/brief.md
(journal: .octospec/journal/shared/modules-auth-skeleton.md,
rule injection record: .octospec/tasks/modules-auth-skeleton/context.yaml)

Changes

  • modules/auth/doc.go — package documentation establishing the OAuth2
    Authorization-Server / Resource-Server boundary and the dependency-direction
    invariant (modules/{user,bot_api,usersecret}modules/auth, never the
    reverse). PR-A2 will enforce via depguard.
  • modules/auth/{tokeninfo,parser}.go (+ _test.go) — byte-identical
    relocation of pkg/auth/{tokeninfo,parser}.go. v2 JSON envelope (v2:
    prefix) and legacy uid@name[@role] decode-fallback preserved; sentinel
    errors preserve identity for errors.Is; LanguageResolver /
    RoleResolver interfaces, option constructors, and panic-on-nil-cache
    contract unchanged. Tests cover: round trip, legacy decode, sentinel
    errors, V2-prefix-required guard, cache-error propagation, resolver
    upgrade / fail-keeps-snapshot / empty-drops-snapshot (language + role).
  • pkg/auth/aliases.go (new) — Deprecated alias shim re-exporting every
    symbol via type = (types), function variables (functions, preserving
    variadic signatures), and value re-export (sentinels, preserving
    errors.Is).
  • pkg/auth/aliases_test.go (new) — guard test pinning round-trip via
    shim names + sentinel-value identity. Fails loud if any alias drifts.
  • pkg/auth/{tokeninfo,parser,_test}.go — deleted (canonical copies
    live in modules/auth/).
  • .octospec/{tasks,journal,log} — task brief, context, journal entry,
    log entry per the repo's octospec 4-phase flow.

Testing

Local verification mirrored CI exactly (go test -race -shuffle=on -count=1 -timeout 5m per package against a fresh docker stack on default ports;
DROP DATABASE test; CREATE DATABASE test CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; + redis-cli FLUSHALL before each package, per
.github/workflows/ci.yml):

  • go test ./modules/auth/... ./pkg/auth/...PASS (both packages)

  • go build ./...clean

  • go vet ./...clean

  • golangci-lint run ./...0 issues across the whole repo

  • make i18n-extract-checkexit 0 (no marker diff)

  • make i18n-lintOK on both subchecks
    (lint-direct-error-response, lint-unregistered-code)

  • Full go test ./... per-package loop → 77 of 80 packages pass.
    The three failing packages (modules/{botfather,channel,robot}) fail
    identically on main (verified by branch-flip comparison) and are
    the pre-existing migration issues alluded to in .github/workflows/ci.yml
    (search OCTO migration TODO; tracker: OCTO migration test debt: ~20+ broken tests masked by stale main CI #17). modules/oidc was
    flaky once under shuffle but passed two subsequent runs (pre-existing,
    not auth-related).

  • Importer surface invariant: grep -rE '"github.com/Mininglamp-OSS/octo-server/pkg/auth"' --include='*.go' .
    still returns the same 6 caller files; modules/auth is imported only
    by pkg/auth's shim.

  • Unit tests added/updated (pkg/auth/aliases_test.go; existing
    tests moved with implementation)

  • Manually verified (full local CI-equivalent run, command-by-command
    above)

COMPREHENSION

  1. What does this change actually do to the load-bearing path?

    It relocates the package that owns the token cache codec (TokenInfo,
    Encode/Decode, v2: JSON envelope, legacy uid@name[@role]
    fallback) and the CacheTokenParser that octo-lib's AuthMiddleware
    uses to hydrate wkhttp.UserInfo on every authenticated request. The
    functional behavior is byte-identical to pkg/auth. What changes is
    the package boundary: modules/auth is now the destination
    package for the verify / verify-bot / verify-api-key handlers in
    later PRs, with a documented invariant that it must not import
    modules/{user,bot_api,usersecret} implementation packages.

  2. What could break because of it (dependents + failure mode)?

    The six existing pkg/auth consumers (main.go, modules/group/ api.go, modules/message/api.go, modules/user/api.go,
    modules/user/api_manager.go, modules/qrcode/api.go) read
    TokenInfo, write tokens via Encode, construct CacheTokenParser
    with the variadic WithLanguageResolver / WithRoleResolver options,
    and match ErrEmptyToken / ErrInvalidToken via errors.Is. The
    alias shim must preserve every name, type, function signature,
    and sentinel-error identity exactly.

    Highest-risk failure mode: sentinel-error re-export by value-copy
    (var X = errors.New(...)) would silently break errors.Is. Avoided
    by re-exporting the same sentinel value
    (var ErrEmptyToken = modulesauth.ErrEmptyToken); aliases_test.go
    pins this with a direct value-identity assertion plus an errors.Is
    round-trip via Decode. Second risk: variadic signature drift on
    NewCacheTokenParser — avoided by function-variable aliasing
    (var NewCacheTokenParser = modulesauth.NewCacheTokenParser) which
    preserves the full signature including variadic options.

    Downstream of the AuthMiddleware, Space isolation and per-route ACLs
    read the UserInfo this package produces. Because the resolver
    fail-open semantics (Redis/DB outage → snapshot fallback) and the
    "empty resolver result drops snapshot" invariants (both language and
    role) are preserved verbatim — including the unexported field shape
    that the test suite pins — there is no path by which this PR can
    weaken Space isolation.

  3. How do you know it works (specific test/repro/trace)?

    Three concentric proofs:

    1. Codec contract preservedmodules/auth/{tokeninfo,parser}_test.go
      (the migrated suite) covers v2 round-trip, legacy decode, sentinel
      errors (ErrEmptyToken, ErrInvalidToken, wkhttp.Err{TokenMissing, TokenNotFound,TokenInvalid}), V2-prefix-required, cache-error must
      not collapse to auth sentinel, resolver upgrade /
      failure-keeps-snapshot / empty-drops-snapshot for both language and
      role, and panic-on-nil-cache. All green.
    2. Alias shim preserves call surfacepkg/auth/aliases_test.go
      round-trips EncodeDecode via the shim names, cross-checks the
      shim's Encode output is byte-identical to the canonical package's,
      and pins ErrEmptyToken == modulesauth.ErrEmptyToken /
      ErrInvalidToken == modulesauth.ErrInvalidToken value-identity
      plus an errors.Is round-trip via Decode.
    3. Six existing consumers compile and passgo build ./...
      clean; the per-package go test ./... loop is green for 77 of 80
      packages, with the three remaining failures verified pre-existing
      via a branch-flip against main (same panics, same migration-state
      cause, tracked under OCTO migration test debt: ~20+ broken tests masked by stale main CI #17). golangci-lint run ./... reports 0
      issues, make i18n-extract-check produces no diff, make i18n-lint
      green.

Checklist

  • I have read CONTRIBUTING.md
  • PR description is in English
  • Added tests for my changes (pkg/auth/aliases_test.go; existing
    tests moved with implementation)
  • Updated documentation (modules/auth/doc.go; .octospec/
    brief + context + journal + log)
  • Followed commit message conventions (Conventional Commits)

Establish modules/auth/ as the canonical home for octo-server's
Resource-Server-facing token contract (TokenInfo + Encode/Decode +
CacheTokenParser). pkg/auth/ becomes a Deprecated alias shim with a
guard test so the six existing callers (main.go,
modules/{group,message,user,qrcode}/api.go, modules/user/api_manager.go)
keep compiling unchanged during the six-month deprecation window.

The relocation is byte-identical: v2 JSON envelope (v2: prefix) and
legacy "uid@name[@ROLE]" decode-fallback, sentinel error identity for
errors.Is, panic-on-nil-cache contract, resolver fail-open semantics,
and the "empty resolver result drops snapshot" UserLanguageResolver
invariant are all preserved verbatim. modules/auth/doc.go states the
OAuth2 Authorization-Server / Resource-Server boundary and the
dependency-direction invariant (modules/{user,bot_api,usersecret} ->
modules/auth, never the reverse) that PR-A2 will enforce via depguard.

This is PR-A1 of the Stage A refactor (epic #428) that culminates in
the verify / verify-bot / verify-api-key handlers moving out of
modules/user/api.go into modules/auth.

Brief: .octospec/tasks/modules-auth-skeleton/brief.md
Journal: .octospec/journal/shared/modules-auth-skeleton.md
Refs #428
@lml2468 lml2468 requested a review from a team as a code owner June 22, 2026 08:49
@github-actions github-actions Bot added the size/XL PR size: XL label Jun 22, 2026
Jerry-Xin
Jerry-Xin previously approved these changes Jun 22, 2026

@Jerry-Xin Jerry-Xin left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This PR is relevant to octo-server and is ready to merge; I found no blocking correctness, security, or behavior-regression issues.

💬 Non-blocking

🔵 Suggestion: pkg/auth/aliases.go:63, :68, :74, :80, :88 re-export functions as mutable package variables. That preserves call signatures, but it also makes formerly immutable functions assignable by importers. Wrapper functions would preserve the old API shape more exactly. Given this is a temporary compatibility shim and current in-repo callers do not mutate these, this is not blocking.

✅ Highlights

The relocation keeps the token codec and CacheTokenParser behavior equivalent to the prior pkg/auth implementation, including legacy decoding, sentinel error wrapping, resolver fallback semantics, and nil-cache panic behavior.

The shim preserves type identity and sentinel identity, and pkg/auth/aliases_test.go directly guards the errors.Is contract.

Focused verification passed locally: go test ./modules/auth/... ./pkg/auth/....

@lml2468 lml2468 left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

作者回应(我 @lml2468 是本 PR 作者,回避投票,仅认领 + 评估):

@Jerry-Xin 的 🟡 我同意,是合理的 API 卫生改进(nit 级,非阻塞):

事实var Encode = modulesauth.Encode / var Decode = ...可变包级变量,同包代码或测试理论上可 auth.Encode = someFn 重新赋值;改成包装函数 func Encode(i TokenInfo) (string, error) { return modulesauth.Encode(i) } 则不可被重新赋值。对 auth codec 这种安全敏感符号,不可变 func 形态更稳健,且 godoc/静态工具识别为真函数更准确(而非"碰巧是函数的变量")。

为何 nit 非阻塞

  • 调用形式 auth.Encode(x) 对 var 和 func 完全一致 → 改成 func 包装对 6 个调用点(main.go / group / message / user×5 / api_manager)零影响,纯内部形态变化。
  • "可被重新赋值"不构成真实攻击面——能执行 auth.Encode = evil 的代码已在进程内、本就能为所欲为,不是额外提权。所以是 API 卫生问题,不是安全漏洞。

我作为作者把 Encode/Decode 两个别名从 var 改成 thin wrapper func(type/error 别名保持 = 不变——那些本就该是 alias)。

补充:本 PR 的安全命门我已自查——old pkg/auth/tokeninfo.go vs new modules/auth/tokeninfo.go 的 Encode/Decode 实现体逐字节等价(diff 仅 package 注释差异),token codec 行为零偏移;TestAliasRoundTrip 钉死 shim==canonical 等价性;go build ./... 全过。这是纯结构提取 + 别名,行为完全保持。

mochashanyao
mochashanyao previously approved these changes Jun 22, 2026

@mochashanyao mochashanyao left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[Octo-Q · automated review]

Verdict: Approve — no blocking findings; notes below (data-flow traced).


octo-server PR#429 Review Report

Reviewer: Octo-Q (automated review)
PR: #429
Head SHA: 91b595f135fd935e28d55f5daf17e9c92bdda5e5
Title: refactor(auth): extract modules/auth submodule and deprecate pkg/auth
Routing: security_sensitive (automated review)


1. Verification Summary

Item Status Evidence
Code relocation fidelity Executable code byte-identical; only comments updated (pkg/auth → modules/auth) and one whitespace alignment in parser_test.go:131-132
Alias shim completeness All 12 exported symbols aliased: 5 types (type =), 5 functions (var =), 2 sentinels (value re-export) — pkg/auth/aliases.go:1-88
Sentinel error identity var ErrEmptyToken = modulesauth.ErrEmptyToken preserves pointer identity for errors.Is; pinned by aliases_test.go:52-56
Dependency direction modules/auth imports only stdlib + octo-lib; zero imports of pkg/auth or any modules/{user,bot_api,usersecret}
Consumer compatibility All 6 consumers (main.go, modules/{group,message,user,qrcode}/api.go, modules/user/api_manager.go) use patterns safe with type aliases + function-variable aliases
Test migration tokeninfo_test.go byte-identical; parser_test.go comment-only changes; full coverage of round-trip, legacy decode, sentinel errors, resolver fail-open, empty-drops-snapshot, panic-on-nil-cache
Old files deleted pkg/auth/ contains only aliases.go + aliases_test.go; 4 original files removed

2. Findings

No P0/P1 findings.

P2 — Minor observations (non-blocking)

P2-1: "byte-identical" claim slightly overstated

  • Diff-scope: pre-existing claim in PR description, not a code defect.
  • The PR description states "byte-identical relocation" but the actual diff includes: (a) tokeninfo.go package comment shortened from 3 lines to 1 line, (b) parser.go 3 comment blocks updated pkg/authmodules/auth, (c) parser_test.go struct field whitespace re-alignment. All changes are comment-only / whitespace-only — zero executable code changed. The claim is functionally correct but technically imprecise.
  • Severity: nit. No runtime impact.

P2-2: Function-variable aliases are theoretically mutable

  • Diff-scope: new (introduced by this PR's alias shim design).
  • var Encode = modulesauth.Encode in pkg/auth/aliases.go:72 creates a package-level variable that could be reassigned at runtime (unlike a func declaration). If any code did auth.Encode = myFunc, only the shim's copy would change, not the canonical package's.
  • Mitigation: No consumer takes addresses of or reassigns these function variables (verified by grep across all 6 call sites). The 6-month deprecation window limits exposure. Go convention treats package-level var function aliases as read-only.
  • Severity: P2 maintainability note. Not a defect.

P2-3: depguard enforcement deferred to PR-A2

  • Diff-scope: new (documented design decision).
  • modules/auth/doc.go:38-39 states the dependency-direction invariant but enforcement relies on a CI depguard rule "added alongside PR-A2". Until then, the invariant is documentation-only.
  • Mitigation: Explicitly documented in doc.go and the PR description. PR-A2 is the next PR in the series. The window is narrow and the team is aware.
  • Severity: P2 process note. Acceptable for a staged refactor.

3. Suggestions

  • Consider adding a //nolint:reassign or a simple init() guard that panics if Encode != modulesauth.Encode at boot, to make function-variable tampering fail-loud. Low priority — the existing aliases_test.go already catches drift at test time.
  • When PR-A2 lands the depguard rule, consider also adding a go vet check or staticcheck rule for package-level function-variable reassignment in pkg/auth/.

4. Extra Findings

None. The .octospec/ files (journal, brief, context.yaml, log) are process metadata documenting the task. They are internally consistent with the PR's actual changes and follow the repo's octospec 4-phase flow.

5. Data-Flow Tracing

Consumed Data Upstream Source Flows Correctly?
TokenInfo struct fields (UID, Name, Role, Language) Encode() → JSON marshal → cache → Decode() → struct ✅ Identical codec path; type TokenInfo = modulesauth.TokenInfo makes types interchangeable at compile time
ErrEmptyToken / ErrInvalidToken sentinels errors.New() in modules/auth/tokeninfo.go:35-39 → re-exported by var ErrEmptyToken = modulesauth.ErrEmptyToken in pkg/auth/aliases.go:53-57 ✅ Same pointer value; errors.Is works across packages. Verified by aliases_test.go:52-63
CacheTokenParser.Parse()wkhttp.UserInfo Cache.Get()Decode() → resolver hooks → struct ✅ Identical Parse() method; type alias CacheTokenParser = modulesauth.CacheTokenParser preserves method set
WithLanguageResolver / WithRoleResolver options Function-variable alias → same func(*CacheTokenParser) value ✅ Variadic signature preserved; main.go:128-129 call sites compile unchanged
NewCacheTokenParser constructor Function-variable alias → same func(cache.Cache, string, ...ParserOption) *CacheTokenParser ✅ Panic-on-nil-cache contract preserved; main.go:125 call site unchanged

6. Blind-Spot Checklist (R5 — security_sensitive PR, all items mandatory)

  • C1 — Dual-path parity: N/A. Pure package relocation with alias shim. No add/remove, subscribe/unsubscribe, or create/delete symmetric paths. No guard/lint tool is introduced or claimed.
  • C2 — Control-flow ordering / nested reuse: N/A. No new control flow. Existing Parse() logic is relocated verbatim. No safety controls (regex/escape/sanitize) are introduced or modified.
  • C3 — Authorization boundary ≠ capability boundary: N/A. No permissions, jails, tools, or endpoints are added/removed/modified. The token parser that hydrates wkhttp.UserInfo (consumed by downstream Space isolation + per-route ACLs) is functionally identical.
  • C4 — Authorization lifecycle / container-member state cascade: N/A. No auth logic changes. Resolver fail-open semantics (Redis/DB outage → snapshot fallback) and empty-drops-snapshot invariants (both language and role) are preserved verbatim.
  • C5 — Build/note ≠ runtime path: Clear. go build ./... clean. All imports resolve correctly. No relative paths, browser extensions, CLI packaging, or environment-dependent resolution. The pkg/authmodules/auth import chain uses absolute Go module paths that resolve identically at build and runtime.
  • C6 — Governance/policy/security document self-consistency: Clear. modules/auth/doc.go documents the dependency-direction invariant consistently with the PR's actual behavior. No conflict with existing SECURITY.md or disclosure policies. The .octospec/ brief and journal are internally consistent.

7. Cross-Round Blocker Recheck (R6)

N/A — first review of this PR.


[Octo-Q] verdict: APPROVE

This is a clean, well-tested package relocation. All executable code is preserved verbatim; the alias shim correctly maintains the full exported surface (types, functions, sentinel errors) for the six existing consumers. No P0/P1 findings. The three P2 observations are minor documentation/maintainability notes that do not block landing.

…shim

Address review feedback from @Jerry-Xin on #429: `var Encode =
modulesauth.Encode` and friends re-export functions as mutable package
variables, which would let any importer reassign the symbol
package-globally (`pkgauth.Encode = customFn`). Wrapper functions
preserve the exported call signature — including the variadic
`NewCacheTokenParser(c, prefix, opts...)` — while keeping the symbols
immutable, which is the more defensive shape for a shim package
consumed by other code during the six-month deprecation window.

Type aliases (`type X = modulesauth.X`) and sentinel-error re-exports
(`var Err... = modulesauth.Err...`) remain — types must be aliases for
assignability and sentinels must preserve value identity for errors.Is.

Refs #428
@lml2468 lml2468 dismissed stale reviews from mochashanyao and Jerry-Xin via 0c419fd June 22, 2026 09:02
@lml2468

lml2468 commented Jun 22, 2026

Copy link
Copy Markdown
Contributor Author

Thanks @Jerry-Xin for the careful review and the wrapper-vs-var call-out.

I've pushed a second commit that converts the five function re-exports (Encode, Decode, WithLanguageResolver, WithRoleResolver, NewCacheTokenParser) from var X = modulesauth.X to wrapper funcs that forward to modulesauth.*. Type aliases and sentinel-error var re-exports are kept — types must be type = aliases for assignability, sentinels must preserve value identity for errors.Is. The variadic signature on NewCacheTokenParser is preserved by the wrapper.

Diff is in 8c8c...0c419fdpkg/auth/aliases.go only (plus the journal entry updated to reflect the reasoning).

Verification re-run locally: go test ./modules/auth/... ./pkg/auth/... PASS, golangci-lint run ./pkg/auth/... 0 issues.

@yujiawei yujiawei left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review — PR #429 (octo-server)

Verdict: APPROVED — reviewed at head 91b595f135fd935e28d55f5daf17e9c92bdda5e5 (merge-base 34050f64, direct parent — clean diff).

This is a clean, well-scoped refactor: pkg/auth (token codec + CacheTokenParser) is relocated to modules/auth, and pkg/auth becomes a // Deprecated: alias shim re-exporting every symbol. No HTTP route, wire contract, or auth behavior changes in this PR. It is correctly scoped as step A1 of the staged migration.

1. Verification

  • Logic byte-identical after move. modules/auth/parser.go and modules/auth/tokeninfo.go differ from the originals only in doc comments (path pkg/authmodules/auth). Diffing the two with comments/blank lines stripped yields zero logic delta.
  • Build / vet / test pass. go build ./... (exit 0), go vet ./modules/auth/... ./pkg/auth/... (exit 0), go test ./modules/auth/... ./pkg/auth/... (both ok).
  • Complete alias coverage. All 12 originally-exported symbols are re-exported (pkg/auth/aliases.go): 5 types via immutable type X = aliases, 2 sentinel errors and 5 functions via var re-exports. Every symbol used by the 6 call sites (main.go, modules/{group,message,qrcode,user}/api.go, modules/user/api_manager.go) resolves and compiles.
  • Sentinel-error identity preserved. errors.Is(err, auth.ErrEmptyToken/ErrInvalidToken) still matches errors produced by the canonical package — re-exported by value, and pinned by aliases_test.go::TestAliasSentinelIdentity.
  • Parser wiring intact. main.go:125 still constructs auth.NewCacheTokenParser(...) with WithLanguageResolver + WithRoleResolver through the shim.
  • Dependency-direction invariant holds. go list -deps ./modules/auth shows zero octo-server/modules/* dependencies — modules/auth does not import user / bot_api / usersecret, matching the load-bearing invariant documented in modules/auth/doc.go.
  • gofmt clean on all changed Go files.

2. Security review (security-sensitive PR)

No security findings. The security-critical properties of the token parser are preserved exactly by the move:

  • Fail-open-to-snapshot on resolver error for both role and language (modules/auth/parser.go) — a Redis/DB outage degrades to the token snapshot rather than 5xx-ing authentication. Unchanged.
  • Role revocation — a resolver returning empty (no error) drops the baked-in role, so a demotion is honored within the resolver's cache TTL. Behavior and its test (TestCacheTokenParserRoleResolverEmptyDropsRole) carried over intact.
  • Error mapping — infra errors are wrapped with %w and must NOT collapse to ErrTokenNotFound/ErrTokenInvalid; TestCacheTokenParserPropagatesCacheError still guards this.
  • The deprecation shim adds no new trust boundary; it is a pure re-export consumed only by in-tree callers.

3. Non-blocking suggestions (P2 / nit — do NOT block merge)

  • nit — function aliases are var re-exports. var Encode = modulesauth.Encode (and the other four funcs) are package-level vars and are technically reassignable. This is the canonical Go idiom for aliasing functions (there is no func alias syntax) and the stdlib uses the same pattern for deprecation shims; for an internal binary with 6 trusted call sites and zero reassignments this is purely theoretical. No action needed — flagged only for completeness.
  • P2 — architecture docs still point at pkg/auth. CLAUDE.md (lines 36, 57) and AGENTS.md (lines 38, 62) describe the auth middleware / token parsing as living in pkg/auth/. The canonical home is now modules/auth. Since caller migration is intentionally deferred to later PRs, updating these docs now would create a doc-vs-code mismatch — best folded into PR-A2+ when call sites actually move. Calling it out so it isn't forgotten.

4. Additional notes

  • The .octospec brief / context / journal for modules-auth-skeleton accurately describe the diff (no route moved in A1; verify handlers land in A3+). The stated verification gates match the actual change set.
  • The legacy uid@name[@role] decode path and the v2 JSON envelope are both retained, so cached tokens from older binaries continue to decode during rollout. Good backward-compat hygiene.

Nothing here is a correctness, security, or build blocker. Approving.

@Jerry-Xin Jerry-Xin left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Re-affirming APPROVE on head 0c419fd.

This commit ("use wrapper funcs instead of var aliases in pkg/auth shim") implements exactly the 🟡 API-hygiene note from the prior round — and does it correctly with no behavior change.

Verified the shim delta (pkg/auth/aliases.go):

  • Encode / Decode / WithLanguageResolver / WithRoleResolver / NewCacheTokenParser are now thin forwarding funcs (return modulesauth.X(...)) instead of reassignable var = modulesauth.X. Exported call signatures preserved verbatim — notably NewCacheTokenParser(c cache.Cache, prefix string, opts ...ParserOption) keeps its variadic shape, so callers like NewCacheTokenParser(c, prefix, WithLanguageResolver(r), WithRoleResolver(rr)) compile unchanged.
  • Type aliases (TokenInfo / ParserOption / CacheTokenParser / resolver types) remain type = aliases → type identity preserved across packages.
  • Sentinel errors ErrEmptyToken / ErrInvalidToken are still re-exported by value (var = ...) → errors.Is(err, auth.ErrEmptyToken) keeps matching errors from the canonical package.

Net effect: the symbols callers can no longer reassign are exactly the function symbols (the API-hygiene goal), while value-identity contracts (sentinels, types) are untouched. The canonical token codec in modules/auth is unchanged from the previously-verified byte-equivalent state. No security regression, no caller breakage.

No blocking items.

Merge gate note: reviewDecision is REVIEW_REQUIRED (main needs 3 approvals; my prior approve + mochashanyao's were auto-dismissed by this new commit, yujiawei has re-approved on this head). Needs the other reviewers to re-approve the new head, and the check-sprint CI job (a process check) to go green.

@lml2468

lml2468 commented Jun 22, 2026

Copy link
Copy Markdown
Contributor Author

@mochashanyao would you mind re-running the review on the new head (0c419fdd5855)? The only delta vs the head you previously approved is pkg/auth/aliases.go: the five function re-exports (Encode, Decode, WithLanguageResolver, WithRoleResolver, NewCacheTokenParser) are now wrapper funcs forwarding to modulesauth.* instead of var X = modulesauth.X — addresses @Jerry-Xin's API-hygiene note from round 1. Type aliases and sentinel-error re-exports unchanged. All other CI is green; only check-sprint is red and that's a process gate I'm leaving as-is per project decision.

@lml2468 lml2468 requested a review from mochashanyao June 22, 2026 10:14
lml2468 added a commit that referenced this pull request Jun 22, 2026
The PR-A1 shim's Deprecated marker said "six months after this shim
was introduced" without giving the actual date — making it ambiguous
when reviewers grep for upcoming removals. This PR pins:

- An explicit removal date (2026-12-22 = 6 months from the alias
  introduction in PR-A1 #429).
- A pointer to the migration tracker (Stage A epic #428) so anyone
  touching pkg/auth in the interim can see who owns the migration.
- A concrete one-line migration recipe in the package doc, so a
  contributor reading the deprecation warning can act immediately
  (no need to dig through the broader epic to understand what the
  fix is).

Net effect on the wire: the per-symbol Deprecated lines now show
the removal date alongside the redirect, so godoc and the
golangci-lint deprecated check produce a more actionable warning
at every call site (already visible in the diagnostics on the six
pkg/auth importers).

No code change — comments only. Tests + lint + build all green.

Refs #428
@lml2468

lml2468 commented Jun 22, 2026

Copy link
Copy Markdown
Contributor Author

@mochashanyao gentle ping — your earlier review on #429 was auto-dismissed by the wrapper-funcs fix push (commit 0c419fd). The current head is unchanged since; would you mind re-reviewing? The delta from your prior review's head (91b595f) is just the alias shape change (var → wrapper funcs in pkg/auth/aliases.go) for API-hygiene per @Jerry-Xin's earlier note.

@lml2468 lml2468 changed the base branch from main to feat/octo-auth-integration June 22, 2026 13:42
@lml2468

lml2468 commented Jun 22, 2026

Copy link
Copy Markdown
Contributor Author

📌 Retargeted to feat/octo-auth-integration (per-repo long-lived integration branch).

Rationale: too many in-flight stacked PRs make cross-service integration testing on main hard. The new flow is:

  1. Each repo's PRs target the per-repo feat/octo-auth-integration branch (instead of main).
  2. As stacked PRs land, they accumulate on feat/octo-auth-integration.
  3. Once all PRs in this stage are merged into feat/octo-auth-integration AND cross-repo integration testing passes (matter + fleet against octo-server + octo-auth/sdk-go all on their respective feat branches), open a final PR feat/octo-auth-integrationmain per repo.
  4. The final feat→main PRs are reviewed once for the consolidated change set + merged together.

The stacked PRs above keep their existing base chain — when this PR merges, the next PR auto-updates.

lml2468 added a commit that referenced this pull request Jun 22, 2026
The PR-A1 shim's Deprecated marker said "six months after this shim
was introduced" without giving the actual date — making it ambiguous
when reviewers grep for upcoming removals. This PR pins:

- An explicit removal date (2026-12-22 = 6 months from the alias
  introduction in PR-A1 #429).
- A pointer to the migration tracker (Stage A epic #428) so anyone
  touching pkg/auth in the interim can see who owns the migration.
- A concrete one-line migration recipe in the package doc, so a
  contributor reading the deprecation warning can act immediately
  (no need to dig through the broader epic to understand what the
  fix is).

Net effect on the wire: the per-symbol Deprecated lines now show
the removal date alongside the redirect, so godoc and the
golangci-lint deprecated check produce a more actionable warning
at every call site (already visible in the diagnostics on the six
pkg/auth importers).

No code change — comments only. Tests + lint + build all green.

Refs #428
@lml2468 lml2468 requested a review from OctoBoooot June 24, 2026 08:44
@lml2468 lml2468 added stage:review Review phase - QA/security/code review review:running:qa qa-engineer review in progress and removed needs-human-review labels Jun 28, 2026
@lml2468

lml2468 commented Jun 28, 2026

Copy link
Copy Markdown
Contributor Author

QA Engineer Verdict: APPROVE

Reviewer: qa-engineer (review-lead loop, persona drain)
Head SHA: 0c419fdd5855cd73c558bb18a019b0ef64777376
Scope: test coverage, edge cases, verify completeness for the pkg/authmodules/auth relocation.

Verification (local, this tick)

  • go test ./modules/auth/... ./pkg/auth/...PASS (modules/auth 1.197s, pkg/auth 1.574s)
  • go vet ./modules/auth/... ./pkg/auth/...clean
  • go build ./...clean
  • Importer surface invariant: pkg/auth importers = 6 (main.go, modules/{group,message,qrcode,user}/api.go, modules/user/api_manager.go) — matches PR description; modules/auth imported only by the shim itself.

Test coverage assessment

The migrated suite (modules/auth/{tokeninfo,parser}_test.go) plus the new guard tests (pkg/auth/aliases_test.go) cover the load-bearing contracts of this PR:

  • Codec round-trip (v2 envelope + legacy uid@name[@role] decode fallback).
  • Sentinel identityaliases_test.go::TestAliasSentinelIdentity pins ErrEmptyToken == modulesauth.ErrEmptyToken and errors.Is(Decode(""), ErrEmptyToken) end-to-end through the shim. This is the right shape — value equality alone is insufficient if Decode were ever divergent.
  • Cross-package codec parityTestAliasRoundTrip asserts shim Encode output is byte-identical to canonical Encode output, not just functionally equivalent. Drift would fail loud.
  • Resolver fail-open (both language and role): resolver error → snapshot fallback; resolver empty (no error) → drops snapshot. Both invariants tested. The role-empty-drops-snapshot test is what makes admin demotion observable within resolver-TTL.
  • Cache-error propagationTestCacheTokenParserPropagatesCacheError ensures infra errors do not collapse to ErrTokenNotFound/ErrTokenInvalid.
  • Panic-on-nil-cache programmer-error contract preserved.

Edge cases / open items

  • Not blocking: No Decode("v2:<malformed-json>") table test in the migrated suite. The existing TestDecodeV2Required covers the "missing v2 prefix" case; a malformed-JSON-after-prefix case would round out the fuzz surface. Left as nit because behavior matches pre-relocation and is not introduced by this PR.
  • CI Test job green at this head; no flakiness observed across the migrated package.

Verdict

APPROVE — test coverage is at least as strong as the pre-relocation state, and the shim is guarded against the two highest-risk drift modes (sentinel-identity break and divergent Encode/Decode).

@lml2468 lml2468 added review:done:qa:approve qa-engineer PASS review:running:security security-engineer review in progress and removed review:running:qa qa-engineer review in progress labels Jun 28, 2026
@lml2468

lml2468 commented Jun 28, 2026

Copy link
Copy Markdown
Contributor Author

Security Engineer Verdict: APPROVE

Reviewer: security-engineer (review-lead loop, persona drain)
Head SHA: 0c419fdd5855cd73c558bb18a019b0ef64777376
Scope: STRIDE / OWASP / authN/authZ / crypto / dependency surface review of the pkg/authmodules/auth extraction (PR-A1 of staged auth refactor).

Threat-model verdict per dimension

STRIDE leg Status Evidence
Spoofing (token forgery) ✅ No regression TokenInfo codec (Encode/Decode, v2 JSON envelope, legacy uid@name[@role] decode) is byte-identical to the pre-relocation implementation. Diff of pkg/auth/{tokeninfo,parser}.go vs modules/auth/{tokeninfo,parser}.go shows comment-only / whitespace deltas; aliases_test.go::TestAliasRoundTrip asserts shim Encode output is byte-identical to canonical Encode output.
Tampering (cache poisoning / sentinel-error bypass) ✅ No regression Sentinel errors re-exported by value (var ErrEmptyToken = modulesauth.ErrEmptyToken), preserving pointer identity; errors.Is(err, auth.ErrEmptyToken) continues to match errors raised by the canonical package. Pinned by TestAliasSentinelIdentity. Functions were converted from var = modulesauth.X to wrapper funcs at 0c419fd — this removes an attack surface (formerly reassignable package-level vars) rather than introducing one.
Repudiation N/A No logging/audit surface change.
Information Disclosure ✅ No new surface No new HTTP routes, no new error-message text, no new logging. The .octospec/ brief / journal / context.yaml are internal process docs, no secrets / tokens leaked.
Denial of Service ✅ No regression Resolver fail-open semantics preserved verbatim: resolver err != nil → keep token snapshot (auth must not 5xx on cache/DB outage). Cache-error propagation (%w wrapping) preserved; infra error MUST NOT collapse to ErrTokenNotFound/ErrTokenInvalid (tested).
Elevation of Privilege ✅ No regression Critical for this PR: role resolver fail-open + role-empty-drops-snapshot invariants are preserved verbatim. A user demoted after token issuance still loses admin within resolver-TTL because resolved == "" (no error) drops the snapshot baked into the token. Verified in parser.go:154-159 (modules/auth) and matches the pre-relocation logic exactly.

Auth-specific spot-checks

  • Token codec wire compatibility: legacy uid@name[@role] decode-fallback retained — older binaries' cached tokens still hydrate correctly during rollout. Backward-compat hygiene is correct.
  • panic-on-nil-cache programmer-error contract: retained (fail-loud at boot rather than silently fail-open every request).
  • Dependency-direction invariant: documented in modules/auth/doc.go (modules/{user,bot_api,usersecret} → modules/auth; never the reverse). go list -deps ./modules/auth shows zero octo-server/modules/* reverse-deps (verified at this head).
  • Crypto: none introduced or modified by this PR.
  • Dependency-changed label: absent (only size/XL). go.mod not modified — verified.

Cleared-with-risk notes (non-blocking)

  • depguard enforcement deferred to PR-A2 (per modules/auth/doc.go and PR description). The dependency-direction invariant is documentation-only until PR-A2 lands. Window is narrow (one PR), the team is aware, and go list -deps confirms the invariant holds at this head. Acceptable for a staged refactor; flag again if PR-A2 slips.
  • Six-month deprecation shim window (pkg/auth). Risk if forgotten: dual-import drift. Mitigated by Deprecated: markers on every symbol and an explicit removal date stated in the PR description.

Verdict

APPROVE / CLEARED — pure relocation with no auth-behavior delta. Sentinel-identity, fail-open, role-revocation, and codec-wire contracts all preserved with test pinning. No new attack surface; the wrapper-func change actually shrinks one.

@lml2468 lml2468 added review:done:security:approve security-engineer CLEARED review:running:code code-reviewer review in progress and removed review:running:security security-engineer review in progress labels Jun 28, 2026
@lml2468

lml2468 commented Jun 28, 2026

Copy link
Copy Markdown
Contributor Author

Code Reviewer Verdict: APPROVE

Reviewer: code-reviewer (review-lead loop, persona drain)
Head SHA: 0c419fdd5855cd73c558bb18a019b0ef64777376
Scope: correctness, readability, maintainability, design fit for PR-A1 of the staged auth refactor.

Correctness

  • Pure relocation with documented invariants — verified by diffing executable code: modules/auth/{tokeninfo,parser}.go vs original pkg/auth/{tokeninfo,parser}.go differ only in package doc comments and one whitespace alignment in parser_test.go. Zero logic delta.
  • Alias shim completeness — all 12 exported symbols of the original pkg/auth re-exported in pkg/auth/aliases.go:
    • 5 types via type X = modulesauth.X (assignability across packages preserved).
    • 2 sentinel errors via var X = modulesauth.X value re-export (errors.Is contract preserved).
    • 5 functions via thin wrapper funcs (func X(...) { return modulesauth.X(...) }) — addresses @Jerry-Xin's API-hygiene note from round 1 by replacing the formerly reassignable var = ... form. Variadic shape of NewCacheTokenParser(cache.Cache, string, ...ParserOption) preserved.
  • All 6 in-tree consumers compile unchanged — verified by go build ./... clean at this head; grep "pkg/auth" still shows the documented 6-importer surface.
  • Local CI-equivalent gates (this tick): go test ./modules/auth/... ./pkg/auth/... PASS, go vet clean, go build ./... clean.

Readability

  • modules/auth/doc.go is excellent — explains the OAuth2 Resource-Server / Authorization-Server boundary, the dependency-direction invariant, and the migration plan. Future readers can land in this package and orient themselves without spelunking the PR history.
  • Comments on the wrapper funcs explicitly state the wrapper-vs-var rationale (immutability vs errors.Is value-identity). Useful breadcrumb when PR-A2+ touches the same file.
  • pkg/auth/aliases.go package doc is a model deprecation shim: states what it replaces, why it exists, when it will be removed, and points to the canonical location. Every symbol carries a Deprecated: marker.

Maintainability

  • Alias-drift guard: pkg/auth/aliases_test.go is intentionally minimal (no re-test of codec logic — that stays with the canonical impl) and pins exactly the two failure modes that would silently break callers: divergent Encode/Decode and broken sentinel-identity. This is the right scope for a shim test.
  • Six-month deprecation window with documented removal date — clean exit story for pkg/auth.
  • Independently revertible — pure code-move + shim, no consumer migration in this PR. PR-A2..A5 can land or be reverted without rolling back A1.

Design fit

  • Staged-refactor scoping is correct. A1 is the right first step: establish the destination package + invariants before adding any verify-handler relocation (A3+) or Lookup interfaces (A2). Each step gets its own review.
  • No HTTP route / wire contract change in this PR — matches the staged-rollout intent and bounds blast radius.
  • .octospec/ artifacts (brief, context.yaml, journal, log) follow the repo's existing octospec 4-phase flow; consistent with the actual diff.

Non-blocking nits

  • CLAUDE.md and AGENTS.md still reference pkg/auth as the auth middleware home (noted by @yujiawei in prior round). Correct call to defer the doc update to PR-A2+ when call sites actually move — fixing it now would create a doc-vs-code mismatch in the opposite direction. Flag retained for the staged plan.

CI state

  • check-sprint red — author confirmed this is a process gate accepted as-is per project decision (not a code defect).
  • All correctness gates green: Build, Test, Vet, Lint, i18n Extract Check, i18n Lint, Secret Scan, OSV-Scanner, Dependency Review.

Verdict

APPROVE — clean, well-scoped, well-documented PR-A1 with proper deprecation discipline. Wrapper-func conversion at 0c419fd correctly addresses the only round-1 API-hygiene note. No correctness, design, or maintainability blockers.

@lml2468 lml2468 added review:done:code:approve code-reviewer APPROVED and removed review:running:code code-reviewer review in progress labels Jun 28, 2026
@lml2468

lml2468 commented Jun 28, 2026

Copy link
Copy Markdown
Contributor Author

Aggregate Verdict: APPROVED — awaiting human merge

Head SHA: 0c419fdd5855cd73c558bb18a019b0ef64777376
Review tick: FIRST-FANOUT (CASE-A) → AGGREGATE (CASE-C) in one tick.

Reviewer Verdict Comment
qa-engineer ✅ APPROVE Tests cover round-trip, sentinel identity, fail-open semantics, panic-on-nil-cache. Local go test ./modules/auth/... ./pkg/auth/... PASS.
security-engineer ✅ APPROVE / CLEARED Pure relocation; sentinel-identity, fail-open, role-revocation, codec-wire contracts preserved with test pinning. depguard deferred to PR-A2 (documented, narrow window).
code-reviewer ✅ APPROVE Clean staged-refactor scoping; wrapper-func conversion at 0c419fd correctly addresses round-1 API-hygiene note. No correctness/design blockers.

Outcome

3 × APPROVE → APPROVED. Per the loop charter, review-lead will NOT merge — handing off to a human maintainer.

Merge-gate notes for the human merger

  • check-sprint CI is red but is a process gate the author has explicitly accepted as-is; all correctness CI (Build, Test, Vet, Lint, i18n, Secret Scan, OSV-Scanner, Dependency Review) is green at this head.
  • reviewDecision may show REVIEW_REQUIRED until the prior auto-dismissed human approvals are refreshed against the current head. Existing approvals from @Jerry-Xin and @yujiawei (with @yujiawei's re-approval at 0c419fd) plus this aggregated automated APPROVED should satisfy the gate.
  • Base branch is feat/octo-auth-integration (per-repo long-lived integration branch), not main — consistent with the staged-refactor plan documented in the PR thread.

Applying review:complete.

@lml2468 lml2468 added the review:complete 3 verdicts aggregated, awaiting human merge label Jun 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

review:complete 3 verdicts aggregated, awaiting human merge review:done:code:approve code-reviewer APPROVED review:done:qa:approve qa-engineer PASS review:done:security:approve security-engineer CLEARED size/XL PR size: XL stage:review Review phase - QA/security/code review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants