Skip to content

fix(auth): decode the base64-encoded password in setupAdmin - #393

Merged
cristim merged 1 commit into
mainfrom
fix/224-setupadmin-base64-password
Sep 28, 2026
Merged

cristim merged 1 commit into
mainfrom
fix/224-setupadmin-base64-password

Conversation

@cristim

@cristim cristim commented Sep 28, 2026

Copy link
Copy Markdown
Member

Summary

setupAdmin forwarded the base64-encoded password straight to the auth
service instead of decoding it first, so the bootstrap admin's stored
bcrypt hash covered the base64 text instead of the real password. Every
later login (which does decode) then failed, locking the bootstrap admin
out permanently.

Root cause

The frontend base64-encodes the setup-admin password identically to how it
encodes the login password (frontend/src/api/auth.ts). login decodes
with decodeBase64Password before calling auth.Login
(internal/api/handler_auth.go), but setupAdmin passed setupReq
straight through to auth.SetupAdmin, which bcrypt-hashes whatever it is
given. The base64-password guard test (internal/api/base64_password_guard_test.go)
had a knownExemptFunctions entry for setupAdmin with a justification
that did not actually address the encoding mismatch.

Fix

  • internal/api/handler_auth.go: setupAdmin now calls
    decodeBase64Password(setupReq.Password) before calling
    h.auth.SetupAdmin, exactly like login and resetPassword.
  • internal/api/base64_password_guard_test.go: removed the stale
    setupAdmin exemption; knownExemptFunctions is now empty, and the
    guard test itself catches a future regression of this class.
  • Updated TestHandler_setupAdmin_Success and the router coverage test to
    send a base64-encoded password body, matching the real frontend contract.

Regression test

Added TestSetupAdminThenLogin_RoundTrip in internal/server/adapter_test.go.
It drives the real request path (api.Router.Route -> api.Handler.setupAdmin
/ login -> authServiceAdapter -> the real *auth.Service, backed by an
auth.MockStore that persists the user setupAdmin creates and hands it
back to login), so the actual bcrypt hash setupAdmin produces is
exercised by login's bcrypt compare.

Proof it fails pre-fix: stashed internal/api/handler_auth.go (reverting
just the decode call) and reran the test -- login failed with
ErrInvalidCredentials-class error because the stored hash covered the
base64 text. Restoring the fix makes it pass.

Verification

  • go build ./...
  • go vet ./internal/api/... ./internal/server/... ./internal/auth/...
  • go test -race -short ./internal/api/... ./internal/server/... ./internal/auth/... -- 3529 passed
  • golangci-lint run at the CI-pinned v2.10.1 -- 0 issues
  • go mod tidy -diff -- empty

Closes #224

The frontend base64-encodes the password sent to setup-admin, as it
does for login, but setupAdmin forwarded the encoded string straight
to the auth service, which bcrypt-hashed the base64 text. Every
subsequent login (which does decode) then compared the real plaintext
against that hash and failed, locking the bootstrap admin out
permanently.

Decode with decodeBase64Password before calling auth.SetupAdmin,
exactly like login and resetPassword, and drop the now-stale
setupAdmin exemption from the base64-password-guard test.

Closes #224
@coderabbitai

coderabbitai Bot commented Sep 28, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

  • Run on-demand review

This review includes 5 billable files and costs up to $1.25.

  • Ask an admin to make reviews automatic

Open in CodeRabbit

Reviews can continue after your included limit without a manual trigger. An admin must approve usage-based billing.

Or wait 3 minutes for your next included review.

Check out review usage here.

View limit details

Limit details: You’ve used all 4 included reviews currently available. Your 37 included PR review attempts over the past 7 days set your current allowance at 4 reviews per hour.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Repository: LeanerCloud/cloud-commitments-platform/.coderabbit.yaml

Review profile: CHILL

Plan: Essentials

Run ID: 16a1d9aa-fff7-45a7-b872-8194106cda8d

📥 Commits

Reviewing files that changed from the base of the PR and between 80088de and 3fa4ccc.

📒 Files selected for processing (5)
  • internal/api/base64_password_guard_test.go
  • internal/api/handler_auth.go
  • internal/api/handler_auth_test.go
  • internal/api/handler_coverage_test.go
  • internal/server/adapter_test.go

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

@cristim cristim added urgency/this-sprint Within the current sprint priority/p1 Next up; this sprint severity/high Significant harm impact/all-users Affects every user effort/xs Trivial / one-liner type/bug Defect triaged Item has been triaged labels Sep 28, 2026
@cristim

cristim commented Sep 28, 2026

Copy link
Copy Markdown
Member Author

Independent adversarial review + local verification: MERGE at 3fa4ccc. setupAdmin now decodes the base64 password with the same strict decoder login uses, so the bootstrap admin can log in; no double-decode path; CLI/MCP have no setup flow. Round-trip test fails pre-fix and passes; checks green incl. E2E.

@cristim
cristim merged commit ed2f826 into main Sep 28, 2026
25 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

effort/xs Trivial / one-liner impact/all-users Affects every user priority/p1 Next up; this sprint severity/high Significant harm triaged Item has been triaged type/bug Defect urgency/this-sprint Within the current sprint

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(auth): setupAdmin stores the base64 password, so the bootstrap admin cannot log in

1 participant