Skip to content

command to retrieve token#124

Open
pigam wants to merge 10 commits into
git-pkgs:mainfrom
pigam:main
Open

command to retrieve token#124
pigam wants to merge 10 commits into
git-pkgs:mainfrom
pigam:main

Conversation

@pigam

@pigam pigam commented Jun 16, 2026

Copy link
Copy Markdown

closes #67

Instead of storing a token in plain text, a config entry can now reference a shell command prefixed with !. The command is executed at runtime and its stdout is used as the token, enabling integration with password managers such as rbw or pass.

forge auth login gains a Ctrl+E shortcut at the token prompt to enter a command interactively.

forge auth status displays the command.

@hramrach

Copy link
Copy Markdown

It would be nice to have an option to specify hostname or URL in the key command, and use the same command for all forges.

Some password databases come organized like that.

@pigam

pigam commented Jun 16, 2026

Copy link
Copy Markdown
Author

It would be nice to have an option to specify hostname or URL in the key command, and use the same command for all forges.

Some password databases come organized like that.

Can you give an example of what you want ?

For the moment :
token = value or token = !command. You want to add interpolation of other variables in the command, or is it something else ?
An example of what you image would be great !

@hramrach

Copy link
Copy Markdown

eg. in here

https://github.com/openSUSE/kernel-source/blob/b572de5eb963a125f55515c346881fd1e05caaf6/scripts/python/obsapi/obsapi.py#L121

You can see subprocess.check_output(['secret-tool', 'lookup', 'service', host, 'username', self.user])

That is same command used for every forge, and looked up by the 'service' tag. The 'host' argument is the host name of the forge.

This is a database that is arbitrarily created like this by another tool, the secret-tool is generic tagged storage that does not itself interpret the tags in any way.

@hramrach

Copy link
Copy Markdown

Your solution still makes it possible (hopefully) to manually configure for each forge something like

token = !secret-tool lookup service github.com type token

or somesuch but does not provide the option to configure such command globally replacing the 'github.com' with the hostname of the forge.

@pigam

pigam commented Jun 17, 2026

Copy link
Copy Markdown
Author

the command has now the environment FORGE_DOMAIN set to the domain, thanks @hramrach for the idea !

so your example can be written as:

token = !secret-tool lookup service $FORGE_DOMAIN type token

@andrew andrew 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.

Thanks for picking up #67. The ! prefix matches what people know from git's credential.helper, and the allowTokens=false guard correctly stops a checked-in .forge from running arbitrary commands, which is the obvious trap here. Good that there's an explicit test for it.

The main thing blocking this is the execution model: token commands run eagerly at config parse time, for every domain, on every invocation. See inline comments. Making resolution lazy (store the raw value, exec only when that domain's token is actually needed) fixes most of the issues at once.

Small thing: "retreive" → "retrieve" in the PR title.

Comment thread internal/config/config.go
Comment thread internal/config/config.go Outdated
Comment thread internal/cli/auth.go
Comment thread internal/cli/auth.go Outdated
Comment thread internal/cli/auth.go Outdated
@pigam pigam changed the title command to retreive token command to retrieve token Jun 27, 2026

@andrew andrew 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.

Thanks for the rework — all the points from the last round are addressed (lazy resolution, --token-cmd, stderr/stdin wiring, escape-sequence handling, ! stripped from display).

The branch doesn't compile though; looks like the rebase left some damage in auth.go — see inline. Once that's sorted I'm happy to merge.

Comment thread internal/cli/auth.go Outdated
Comment thread internal/cli/auth.go Outdated
Comment thread internal/resolve/resolve.go
@hramrach

hramrach commented Jun 28, 2026

Copy link
Copy Markdown

There is also the thing that storing the token retrieval command in the same field as the token is probably fairly bad design.

While there are no known tokens that can start with a ! at the moment it can change, or when passwords or other authentication methods are supported in the future it will change.

There is a possibility to use different field for this, or a type flag that tells how to interpret the existing field. Without either this is bound to break.

local stuff accidentaly added !
@pigam

pigam commented Jun 28, 2026

Copy link
Copy Markdown
Author

There is also the thing that storing the token retrieval command in the same field as the token is probably fairly bad design

There is a separate field :

func authLoginCmd() *cobra.Command {
 	var (
 		domain    string
 		token     string
+		tokenCmd  string
 		forgeType string
 	)

So maybe the design is not so fairly bad ? 😉

pigam added 2 commits June 28, 2026 11:17
`rootCmd`` is a package-level variable in interal/cli/root.go, shared across all tests
in the cli package.

Cobra does not reset flag values or their Changed
state between `Execute()` calls, so a flag set by one test leaks into the
next.

This commit adds a `resetCmd` helper that recursively walks the command tree and
restores each flag to its default value and `Changed=false`.
Let's call it at the start of each test that invokes `rootCmd.Execute()`.
@pigam

pigam commented Jun 28, 2026

Copy link
Copy Markdown
Author

In https://github.com/git-pkgs/forge/pull/124/commits/0a9d4554cba93eb14f196589d596b60ce731fe80` I also added a test helper to reset rootCmd in each test.

@hramrach

Copy link
Copy Markdown

There is also the thing that storing the token retrieval command in the same field as the token is probably fairly bad design

There is a separate field :

func authLoginCmd() *cobra.Command {
 	var (
 		domain    string
 		token     string
+		tokenCmd  string
 		forgeType string
 	)

So maybe the design is not so fairly bad ? 😉

That's not what the docs say:

[github.com]

token = !rbw get github-token

@pigam

pigam commented Jun 28, 2026

Copy link
Copy Markdown
Author

[github.com]

token = !rbw get github-token
Ok, you meant in the config file. What do you imagine ?
Something like

[github.com]
token = ghp_abc123

[gitlab.com]
token-cmd = rbw get gitlab-token 

?

@hramrach

Copy link
Copy Markdown

[github.com]
token = !rbw get github-token
Ok, you meant in the config file. What do you imagine ?
Something like

[github.com]
token = ghp_abc123

[gitlab.com]
token-cmd = rbw get gitlab-token 

?

Yes, the config file is the actual user-visible API, the internal representation can be changed without anyone noticing.

This is one way to do it.

There is the open question what happens when both token and token-cmd are specified.

@pigam

pigam commented Jun 28, 2026

Copy link
Copy Markdown
Author

There is the open question what happens when both token and token-cmd are specified.

Error, not permitted I suppose.

@hramrach

Copy link
Copy Markdown

Reporting an error is an option. Some priority scheme is another but that is not much better in the end, some weird patchy configuration file may contain data that would allow accessing the forge but it's not used in the end.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: execute command to retrieve token

3 participants