feat(auth): login 新增 --with-token 标准输入非交互登录(对标 gh)
This commit is contained in:
parent
b8e92895f4
commit
6ef51c7d5d
|
|
@ -852,6 +852,7 @@ To reuse the active token in scripts (e.g. raw `curl` calls against endpoints th
|
|||
```bash
|
||||
curl -H "Authorization: Bearer $(gitlink-cli auth token)" https://www.gitlink.org.cn/api/v1/...
|
||||
gitlink-cli auth status --show-token # Inspect the raw token (hidden by default)
|
||||
echo $MY_TOKEN | gitlink-cli auth login --with-token # Non-interactive login (CI, scripts)
|
||||
```
|
||||
|
||||
Priority: `GITLINK_TOKEN` env var > keyring/file stored token. When the env var is not set, the original interactive login flow works as before.
|
||||
|
|
|
|||
|
|
@ -724,6 +724,7 @@ gitlink-cli auth status # 显示 "✓ Logged in via GITLINK_TOKEN environment
|
|||
```bash
|
||||
curl -H "Authorization: Bearer $(gitlink-cli auth token)" https://www.gitlink.org.cn/api/v1/...
|
||||
gitlink-cli auth status --show-token # 查看原始 token(默认隐藏)
|
||||
echo $MY_TOKEN | gitlink-cli auth login --with-token # 非交互登录(CI/脚本)
|
||||
```
|
||||
|
||||
Token 优先级:`GITLINK_TOKEN` 环境变量 > keyring/文件存储的 token。不设置环境变量时完全兼容原有交互式登录。
|
||||
|
|
|
|||
|
|
@ -40,11 +40,15 @@ func NewAuthCmd(translators ...*i18n.Translator) *cobra.Command {
|
|||
|
||||
func newLoginCmd(tr *i18n.Translator) *cobra.Command {
|
||||
var tokenMode bool
|
||||
var withToken bool
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "login",
|
||||
Short: tr.T("cmd.auth.login.short"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if withToken {
|
||||
return loginWithTokenStdin(cmd.InOrStdin(), cmd.OutOrStdout(), tr)
|
||||
}
|
||||
if tokenMode {
|
||||
return loginWithToken(cmd.InOrStdin(), cmd.OutOrStdout(), tr)
|
||||
}
|
||||
|
|
@ -52,6 +56,7 @@ func newLoginCmd(tr *i18n.Translator) *cobra.Command {
|
|||
},
|
||||
}
|
||||
cmd.Flags().BoolVar(&tokenMode, "token", false, tr.T("flag.auth.token"))
|
||||
cmd.Flags().BoolVar(&withToken, "with-token", false, tr.T("flag.auth.with_token"))
|
||||
return cmd
|
||||
}
|
||||
|
||||
|
|
@ -118,6 +123,26 @@ func loginWithToken(in io.Reader, out io.Writer, tr *i18n.Translator) error {
|
|||
return err
|
||||
}
|
||||
|
||||
// loginWithTokenStdin reads a token from stdin without prompting, mirroring
|
||||
// `gh auth login --with-token` for non-interactive use (CI, scripts):
|
||||
//
|
||||
// echo $TOKEN | gitlink-cli auth login --with-token
|
||||
func loginWithTokenStdin(in io.Reader, out io.Writer, tr *i18n.Translator) error {
|
||||
data, err := io.ReadAll(io.LimitReader(in, 4096))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
token := strings.TrimSpace(string(data))
|
||||
if token == "" {
|
||||
return errors.New(tr.T("error.auth.token_empty"))
|
||||
}
|
||||
if err := storeToken(token); err != nil {
|
||||
return errors.New(tr.Tf("error.auth.store_token_failed", i18n.Args{"message": err.Error()}))
|
||||
}
|
||||
_, err = fmt.Fprintln(out, tr.T("success.auth.token_saved"))
|
||||
return err
|
||||
}
|
||||
|
||||
func newLogoutCmd(tr *i18n.Translator) *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "logout",
|
||||
|
|
|
|||
|
|
@ -335,3 +335,43 @@ func findSub(cmd *cobra.Command, name string) *cobra.Command {
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestLoginWithTokenStdin(t *testing.T) {
|
||||
keyring.MockInit()
|
||||
tempConfigDir(t)
|
||||
t.Setenv("GITLINK_TOKEN", "")
|
||||
_ = internalAuth.DeleteToken()
|
||||
|
||||
cmd := findSub(NewAuthCmd(), "login")
|
||||
if cmd == nil {
|
||||
t.Fatal("login subcommand not found")
|
||||
}
|
||||
if err := cmd.Flags().Set("with-token", "true"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
cmd.SetIn(strings.NewReader("stdin-token-456\n"))
|
||||
cmd.SetOut(&buf)
|
||||
if err := cmd.RunE(cmd, nil); err != nil {
|
||||
t.Fatalf("login --with-token error: %v", err)
|
||||
}
|
||||
stored, err := internalAuth.LoadToken()
|
||||
if err != nil || stored != "stdin-token-456" {
|
||||
t.Fatalf("stored token = %q (err %v), want stdin-token-456", stored, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoginWithTokenStdinEmpty(t *testing.T) {
|
||||
keyring.MockInit()
|
||||
tempConfigDir(t)
|
||||
|
||||
cmd := findSub(NewAuthCmd(), "login")
|
||||
if err := cmd.Flags().Set("with-token", "true"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cmd.SetIn(strings.NewReader("\n"))
|
||||
cmd.SetOut(&bytes.Buffer{})
|
||||
if err := cmd.RunE(cmd, nil); err == nil {
|
||||
t.Fatal("expected error for empty stdin token")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -128,6 +128,7 @@
|
|||
"flag.api.query": "Query parameters (key=val&key2=val2)",
|
||||
"flag.auth.show_token": "Display the raw token in the status output",
|
||||
"flag.auth.token": "Login by pasting an existing token",
|
||||
"flag.auth.with_token": "Read the token from standard input (non-interactive)",
|
||||
"flag.branch.from": "Source branch or commit",
|
||||
"flag.branch.name": "Branch name",
|
||||
"flag.ci.build": "Build number",
|
||||
|
|
|
|||
|
|
@ -128,6 +128,7 @@
|
|||
"flag.api.query": "查询参数(key=val&key2=val2)",
|
||||
"flag.auth.show_token": "在状态输出中显示原始 token",
|
||||
"flag.auth.token": "通过粘贴已有 Token 登录",
|
||||
"flag.auth.with_token": "从标准输入读取令牌(非交互,适合脚本/CI)",
|
||||
"flag.branch.from": "源分支或 Commit",
|
||||
"flag.branch.name": "分支名称",
|
||||
"flag.ci.build": "构建编号",
|
||||
|
|
|
|||
Loading…
Reference in New Issue