Login via oauth2 flow (#725)

Reviewed-on: https://gitea.com/gitea/tea/pulls/725
Co-authored-by: techknowlogick <techknowlogick@gitea.com>
Co-committed-by: techknowlogick <techknowlogick@gitea.com>
This commit is contained in:
techknowlogick
2025-03-18 17:01:49 +00:00
committed by techknowlogick
parent e82dd9e08d
commit 62dc1dde95
10 changed files with 710 additions and 158 deletions

View File

@ -29,6 +29,7 @@ var CmdLogin = cli.Command{
&login.CmdLoginDelete,
&login.CmdLoginSetDefault,
&login.CmdLoginHelper,
&login.CmdLoginOAuthRefresh,
},
}

View File

@ -4,6 +4,7 @@
package login
import (
"code.gitea.io/tea/modules/auth"
"code.gitea.io/tea/modules/interact"
"code.gitea.io/tea/modules/task"
@ -89,6 +90,19 @@ var CmdLoginAdd = cli.Command{
Aliases: []string{"j"},
Usage: "Add helper",
},
&cli.BoolFlag{
Name: "oauth",
Aliases: []string{"o"},
Usage: "Use interactive OAuth2 flow for authentication",
},
&cli.StringFlag{
Name: "client-id",
Usage: "OAuth client ID (for use with --oauth)",
},
&cli.StringFlag{
Name: "redirect-url",
Usage: "OAuth redirect URL (for use with --oauth)",
},
},
Action: runLoginAdd,
}
@ -99,6 +113,27 @@ func runLoginAdd(ctx *cli.Context) error {
return interact.CreateLogin()
}
// if OAuth flag is provided, use OAuth2 PKCE flow
if ctx.Bool("oauth") {
opts := auth.OAuthOptions{
Name: ctx.String("name"),
URL: ctx.String("url"),
Insecure: ctx.Bool("insecure"),
}
// Only set clientID if provided
if ctx.String("client-id") != "" {
opts.ClientID = ctx.String("client-id")
}
// Only set redirect URL if provided
if ctx.String("redirect-url") != "" {
opts.RedirectURL = ctx.String("redirect-url")
}
return auth.OAuthLoginWithFullOptions(opts)
}
sshAgent := false
if ctx.String("ssh-agent-key") != "" || ctx.String("ssh-agent-principal") != "" {
sshAgent = true

View File

@ -10,7 +10,9 @@ import (
"net/url"
"os"
"strings"
"time"
"code.gitea.io/tea/modules/auth"
"code.gitea.io/tea/modules/config"
"code.gitea.io/tea/modules/task"
"github.com/urfave/cli/v2"
@ -102,6 +104,20 @@ var CmdLoginHelper = cli.Command{
return err
}
if userConfig.TokenExpiry > 0 && time.Now().Unix() > userConfig.TokenExpiry {
// Token is expired, refresh it
err = auth.RefreshAccessToken(userConfig)
if err != nil {
return err
}
// Once token is refreshed, get the latest from the updated config
refreshedConfig := config.GetLoginByHost(wants["host"])
if refreshedConfig != nil {
userConfig = refreshedConfig
}
}
_, err = fmt.Fprintf(os.Stdout, "protocol=%s\nhost=%s\nusername=%s\npassword=%s\n", host.Scheme, host.Host, userConfig.User, userConfig.Token)
if err != nil {
return err

View File

@ -0,0 +1,58 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package login
import (
"fmt"
"code.gitea.io/tea/modules/auth"
"code.gitea.io/tea/modules/config"
"github.com/urfave/cli/v2"
)
// CmdLoginOAuthRefresh represents a command to refresh an OAuth token
var CmdLoginOAuthRefresh = cli.Command{
Name: "oauth-refresh",
Usage: "Refresh an OAuth token",
Description: "Manually refresh an expired OAuth token. Usually only used when troubleshooting authentication.",
ArgsUsage: "[<login name>]",
Action: runLoginOAuthRefresh,
}
func runLoginOAuthRefresh(ctx *cli.Context) error {
var loginName string
// Get login name from args or use default
if ctx.Args().Len() > 0 {
loginName = ctx.Args().First()
} else {
// Get default login
login, err := config.GetDefaultLogin()
if err != nil {
return fmt.Errorf("no login specified and no default login found: %s", err)
}
loginName = login.Name
}
// Get the login from config
login := config.GetLoginByName(loginName)
if login == nil {
return fmt.Errorf("login '%s' not found", loginName)
}
// Check if the login has a refresh token
if login.RefreshToken == "" {
return fmt.Errorf("login '%s' does not have a refresh token. It may have been created using a different authentication method", loginName)
}
// Refresh the token
err := auth.RefreshAccessToken(login)
if err != nil {
return fmt.Errorf("failed to refresh token: %s", err)
}
fmt.Printf("Successfully refreshed OAuth token for %s\n", loginName)
return nil
}