Add fizzy signup command - #71
Conversation
There was a problem hiding this comment.
Pull request overview
Adds a new fizzy signup command to support session/cookie-based onboarding before a user has an API token, including an interactive wizard and agent-friendly step-by-step subcommands. Updates the Fizzy skill documentation so agents can discover and drive the flow.
Changes:
- Add
fizzy signupinteractive wizard plussignup start|verify|completesubcommands for programmatic signup/token generation. - Implement cookie-jar based HTTP helpers to carry Rails signed cookies between steps.
- Document the new signup flow in
skills/fizzy/SKILL.md.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 11 comments.
| File | Description |
|---|---|
| skills/fizzy/SKILL.md | Documents interactive and step-by-step signup usage for agents. |
| internal/commands/signup.go | Implements signup wizard + step-by-step commands, cookie handling, and config persistence. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
jeremy
left a comment
There was a problem hiding this comment.
Can any of the tokens that'll land on the command line be replayed/reused? Should treat them as credentials and securely read from stdin if so.
627609c to
66692ac
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated 6 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Interactive wizard (fizzy signup) and step-by-step subcommands (start, verify, complete) for programmatic/agent use. Session token is read from stdin to avoid exposing credentials in shell history or process arguments.
66692ac to
5fe0a95
Compare
Matches the pattern used by `fizzy setup`. Returns a usage hint directing agents to the step-by-step subcommands instead.
Two bugs found during local testing: - Interactive wizard discarded the HTTP response, so the X-Magic-Link-Code header (set by Rails in development) was never shown to the user. - Token was saved only to config YAML, but the credstore takes precedence, so a stale keyring token would shadow the newly generated one.
- Return http.Header instead of *http.Response from signupPost to satisfy bodyclose - Use http.NewRequestWithContext instead of http.NewRequest (noctx) - Check MarkFlagRequired and huh.Confirm.Run error returns (errcheck/gosec) - Fix variable shadowing of err in nested blocks (govet)
- Clean expanded path with filepath.Clean to prevent path injection - Sanitize path output to prevent log injection
|
I like the stdin for the session token, but I do think we need to specify in the skill that the token should be written to a temp file (and cleaned up after signup) in order to keep it out of the agent session. |
Write the session token to a temp file and pipe via stdin instead of echoing directly, keeping it out of the agent's conversation context.
- Resolve paths with filepath.Abs to sanitize user input (path injection) - Strip control characters from output to prevent log injection
jeremy
left a comment
There was a problem hiding this comment.
Test coverage gaps:
- hosted signup clears previously saved self-hosted URL
- self-hosted signup preserves custom URL
- signupHTTPError for auth failures
- signupHTTPError for server errors
- plain error for network failures
The overwrite prompt and progress line still emitted the raw selectedPath, allowing newline/control-character injection via custom path input. Apply sanitizeLogValue consistently at all three output sinks.
os.Exit bypasses deferred cleanup and is untestable. Match the established setup.go convention of returning nil with nolint:nilerr when the user cancels a huh prompt.
The subcommand path (signupAPIURL) already trims trailing slashes, but the interactive wizard did not. Entering https://fizzy.example.com/ would produce double-slash URLs like //session.json.
The wizard's verify loop previously treated every failure as "Invalid code" and offered retry. Network errors, 5xx responses, and other operational failures now surface the real error immediately instead of misleading the user. Only 401 (wrong code) triggers the retry prompt. Introduces signupHTTPError to carry the HTTP status code from signupPost, letting callers discriminate by status.
saveSignupConfig only wrote APIURL when non-default, so a previously saved self-hosted URL survived a hosted signup. The fresh token and account would then be used against the wrong backend. Now explicitly clears APIURL when the signup used the default hosted URL.
The token is already sent as a cookie (set on the line above). The
interactive wizard path only sends {"code": ...} in the body. Make
the programmatic path consistent.
Restrict http:// URLs to loopback addresses only (localhost, 127.0.0.1, [::1]) and reject non-HTTP schemes entirely. Validation runs on every request URL in signupGet/signupPost and on every redirect hop via CheckRedirect, closing the open-redirector bypass.
- Cap response body reads at 1MB via io.LimitReader - Return *signupHTTPError from signupGet (consistent with signupPost) - Handle url.Parse errors in cookie helpers instead of ignoring - Set Secure and HttpOnly on synthesized cookies - Use url.PathEscape on account slugs in URL construction
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 7 out of 7 changed files in this pull request and generated 6 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Set Secure cookie flag conditionally based on URL scheme so http:// localhost works for local development - Remove hardcoded ~/.config/fizzy/config.yaml from output messages - Document credential store vs config file fallback in SKILL.md - Close pipe reader FD in test cleanup to prevent FD leak
Summary
fizzy signupinteractive wizard for humans and step-by-step subcommands (start,verify,complete) that LLM agents can drive programmaticallySKILL.mdfor agent discoverabilityMy::AccessTokensController#createin the fizzy Rails app (Add JSON response format to access token creation fizzy#2662)How it works
The signup flow uses session-based auth (cookies) since the user doesn't have an API token yet — that's what we're generating.
Interactive wizard
Walks through: hosting type → email → magic link code → name (if new) → account selection → token saved to config.
Step-by-step subcommands (for agents)
Each step returns the signed cookie values needed for the next step:
Cookie signing
The tokens passed between steps are actual Rails signed cookie values extracted from the HTTP response jar — not the raw JSON body values. Rails signs them on the server, the CLI passes them opaquely, and Rails unsigns them on the next request. The CLI never needs to know the signing secret.
Session token security
The session token (step 2 → step 3) is read from stdin rather than a CLI flag. This keeps it out of shell history and
psoutput. When stdin is a terminal, the CLI prompts with masked input; when stdin is a pipe, it reads silently.Token replay analysis
ps?pending_authentication_token--pending-tokenflag)MagicLink#consumecallsdestroy). The token alone just contains a signed email — useless without an unconsumed code. Expires with the magic link (15 min).session_tokensigned_idof a persistentSessionDB record.fizzy_...access token~/.config/fizzy/config.yaml.