feat: add credentials-init, which writes the credentials file - #195
Merged
Merged
Conversation
Answers #193: a command that prompts for the credentials, checks them, and writes the user's credentials file.
The pieces credentials-init (#193) needs, kept in internal/client beside the reader so one package owns the format: - CredentialsPath and ReadCredentials, exported, moved to credentials.go. ReadCredentials raises no permission warning, since its caller is about to rewrite the file 0600; loadDotenv is now readDotenv plus the warning. - WriteCredentials: temp file and rename at 0600, writing through a symbolic link, quoting a value exactly when readDotenv would not read it back unchanged, then reading the result back and comparing. - UnkeptLines, the lines a rewrite would drop, and DisplayPath. - FetchCloudID: the unauthenticated tenant_info request, outside send, which always sets basic auth. - HTTPError.ScopeMismatch and SiteRejectedAuth, the shapes hint already matched, so a caller classifying a failure cannot disagree with it. The missing-settings error now suggests credentials-init when there is a credentials file to write, and not in the half-pair forms, where a new file would fail the same-source rule.
Answers #193. credentials-init prompts for the site URL, username, and token (read without echo), fetches the cloud ID for an atlassian.net site, checks the credentials with the request user-info makes, and writes the user's credentials file 0600. It writes nothing in the working directory. A failed check is classified by the response's shape: a rejected credential, a site-domain HTML 401, another 401/403, or a non-credential 404 writes nothing; a scope mismatch, a server error, or no response asks whether to save anyway. An existing file prefills every prompt. It refuses without a terminal on stdin or stderr, and refuses --json and --env-file, each as a plain error so --json still reports it. charmbracelet/x/term becomes a direct dependency; lipgloss already built it in. The plan is amended where the implementation found a simpler shape.
Setup in the README and docs/credentials.md starts with the command, and the manual steps stay as the alternative. The cloud ID is no longer described as something to leave out for a personal token, which works with or without one. api.md records that the gateway answers a wrong token on /user/current with the same v1 403 as the site.
- The current token and cloud ID are offered only for the same site. A kept token after a new URL paired one site's token with another's URL, the thing the same-source rule exists to stop. - A same-site cloud ID is kept when the fetch fails, and for a host outside atlassian.net, where it was set by hand; a scoped-token user rotating a token no longer loses it. - The host is lowercased, so a capitalized atlassian.net site still gets its cloud ID. A URL's user name and password are dropped with a note, and URL errors quote what was typed. - A scope mismatch is a checked answer: the token authenticated. - The environment is reported before the first question, not after the write. - A rerun says first that it will drop comments, with "Ctrl-C to exit". ReadCredentials returns the values, the dropped-line counts, and the mode from one read, replacing UnkeptLines. - All output goes to stderr beside the prompts (ui.InfoStderr and SuccessStderr), so > log cannot hide a status line. - The site-rejected hint no longer names credentials-init, which cannot help in CI. - The setting names, CredentialsDoc, and LooseMode are exported from internal/client instead of copied. The plan records each change under "Amended after code review".
This clarifies where you can and can't use credentials-init and simplifies the section about it requiring a terminal.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #193.
Adds
markfluence credentials-init, which prompts for the site URL, username, and API token, checks them with Confluence, and writes the user's credentials file (~/.config/markfluence/credentials, or under an absolute$XDG_CONFIG_HOME) with mode0600. It writes only that file and nothing in the working directory; project setup stays with #5'sinit. The design and its reasoning are in_plans/051_credentials-init.md, including a section on what the code review changed.What it does
charmbracelet/x/term, which lipgloss already built in. It is now the seventh direct dependency. Ctrl-C at the token prompt restores the terminal.{site}/_edge/tenant_info, for*.atlassian.netsites only. An unscoped token also works through the gateway, so nobody has to know what kind of token they have. Government and isolated Cloud use a different gateway than the hardcodedapi.atlassian.com, so they get no cloud ID.user-infomakes. A failure is classified by the response's shape, reusing the markersHTTPError.hintmatches. A rejected credential, a site-domain HTML 401, another 401/403, or a non-credential 404 writes nothing. A scope mismatch counts as checked, with a warning. No response or a 5xx asksSave anyway?.Ctrl-C to exit). The token and cloud ID are kept only for the same site, so one site's token is never paired with another's URL.CONFLUENCE_*variable that changes what the file does.--json,--env-file, and no home directory are each a plain error, so--jsonstill gets anerrorObject.Supporting changes
internal/client:credentials.go:CredentialsPath,ReadCredentials(one read returning values, the lines a rewrite drops, and the mode),WriteCredentials,DisplayPath,FetchCloudID;HTTPError.ScopeMismatchandSiteRejectedAuth, whichhintnow uses;CredentialsDoc, andLooseMode.internal/ui:InfoStderrandSuccessStderr, so all of the command's output sits on stderr beside its prompts.docs/credentials.mdand the README start setup with the command, and the manual steps stay as the alternative. The cloud ID is no longer described as something to leave out for a personal token.docs/confluence/api.mdrecords that the gateway answers a wrong token on/user/currentwith the same v1 403 as the site.Checked
make check.0600in a0700directory;user-infoworks with the written file.