mirror of
https://gitea.com/gitea/tea.git
synced 2026-02-22 06:13:32 +01:00
Add locking to ensure safe concurrent access to config file (#881)
Reviewed-on: https://gitea.com/gitea/tea/pulls/881 Co-authored-by: techknowlogick <techknowlogick@gitea.com> Co-committed-by: techknowlogick <techknowlogick@gitea.com>
This commit is contained in:
committed by
techknowlogick
parent
0d5bf60632
commit
ae9eb4f2c0
@@ -71,12 +71,24 @@ func OAuthLoginWithOptions(name, giteaURL string, insecure bool) error {
|
||||
|
||||
// OAuthLoginWithFullOptions performs an OAuth2 PKCE login flow with full options control
|
||||
func OAuthLoginWithFullOptions(opts OAuthOptions) error {
|
||||
// Normalize URL
|
||||
serverURL, err := utils.NormalizeURL(opts.URL)
|
||||
serverURL, token, err := performBrowserOAuthFlow(opts)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to parse URL: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return createLoginFromToken(opts.Name, serverURL, token, opts.Insecure)
|
||||
}
|
||||
|
||||
// performBrowserOAuthFlow performs the browser-based OAuth2 PKCE flow and returns the token.
|
||||
// This is the shared implementation used by both new logins and re-authentication.
|
||||
func performBrowserOAuthFlow(opts OAuthOptions) (serverURL string, token *oauth2.Token, err error) {
|
||||
// Normalize URL
|
||||
normalizedURL, err := utils.NormalizeURL(opts.URL)
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("unable to parse URL: %s", err)
|
||||
}
|
||||
serverURL = normalizedURL.String()
|
||||
|
||||
// Set defaults if needed
|
||||
if opts.ClientID == "" {
|
||||
opts.ClientID = config.DefaultClientID
|
||||
@@ -107,7 +119,7 @@ func OAuthLoginWithFullOptions(opts OAuthOptions) error {
|
||||
// Generate code verifier (random string)
|
||||
codeVerifier, err := generateCodeVerifier(codeVerifierLength)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate code verifier: %s", err)
|
||||
return "", nil, fmt.Errorf("failed to generate code verifier: %s", err)
|
||||
}
|
||||
|
||||
// Generate code challenge (SHA256 hash of code verifier)
|
||||
@@ -118,8 +130,8 @@ func OAuthLoginWithFullOptions(opts OAuthOptions) error {
|
||||
ctx = context.WithValue(ctx, oauth2.HTTPClient, createHTTPClient(opts.Insecure))
|
||||
|
||||
// Configure the OAuth2 endpoints
|
||||
authURL := fmt.Sprintf("%s/login/oauth/authorize", serverURL)
|
||||
tokenURL := fmt.Sprintf("%s/login/oauth/access_token", serverURL)
|
||||
authURL := fmt.Sprintf("%s/login/oauth/authorize", normalizedURL)
|
||||
tokenURL := fmt.Sprintf("%s/login/oauth/access_token", normalizedURL)
|
||||
|
||||
oauth2Config := &oauth2.Config{
|
||||
ClientID: opts.ClientID,
|
||||
@@ -141,7 +153,7 @@ func OAuthLoginWithFullOptions(opts OAuthOptions) error {
|
||||
// Generate state parameter to protect against CSRF
|
||||
state, err := generateCodeVerifier(32)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate state: %s", err)
|
||||
return "", nil, fmt.Errorf("failed to generate state: %s", err)
|
||||
}
|
||||
|
||||
// Get the authorization URL
|
||||
@@ -156,7 +168,7 @@ func OAuthLoginWithFullOptions(opts OAuthOptions) error {
|
||||
strings.Contains(err.Error(), "redirect") {
|
||||
fmt.Println("\nError: Redirect URL not registered in Gitea")
|
||||
fmt.Println("\nTo fix this, you need to register the redirect URL in Gitea:")
|
||||
fmt.Printf("1. Go to your Gitea instance: %s\n", serverURL)
|
||||
fmt.Printf("1. Go to your Gitea instance: %s\n", normalizedURL)
|
||||
fmt.Println("2. Sign in and go to Settings > Applications")
|
||||
fmt.Println("3. Register a new OAuth2 application with:")
|
||||
fmt.Printf(" - Application Name: tea-cli (or any name)\n")
|
||||
@@ -165,22 +177,21 @@ func OAuthLoginWithFullOptions(opts OAuthOptions) error {
|
||||
fmt.Printf(" tea login add --oauth --client-id YOUR_CLIENT_ID --redirect-url %s\n", opts.RedirectURL)
|
||||
fmt.Println("\nAlternatively, you can use a token-based login: tea login add")
|
||||
}
|
||||
return fmt.Errorf("authorization failed: %s", err)
|
||||
return "", nil, fmt.Errorf("authorization failed: %s", err)
|
||||
}
|
||||
|
||||
// Verify state to prevent CSRF attacks
|
||||
if state != receivedState {
|
||||
return fmt.Errorf("state mismatch, possible CSRF attack")
|
||||
return "", nil, fmt.Errorf("state mismatch, possible CSRF attack")
|
||||
}
|
||||
|
||||
// Exchange authorization code for token
|
||||
token, err := oauth2Config.Exchange(ctx, code, oauth2.SetAuthURLParam("code_verifier", codeVerifier))
|
||||
token, err = oauth2Config.Exchange(ctx, code, oauth2.SetAuthURLParam("code_verifier", codeVerifier))
|
||||
if err != nil {
|
||||
return fmt.Errorf("token exchange failed: %s", err)
|
||||
return "", nil, fmt.Errorf("token exchange failed: %s", err)
|
||||
}
|
||||
|
||||
// Create login with token data
|
||||
return createLoginFromToken(opts.Name, serverURL.String(), token, opts.Insecure)
|
||||
return serverURL, token, nil
|
||||
}
|
||||
|
||||
// createHTTPClient creates an HTTP client with optional insecure setting
|
||||
@@ -417,3 +428,33 @@ func createLoginFromToken(name, serverURL string, token *oauth2.Token, insecure
|
||||
func RefreshAccessToken(login *config.Login) error {
|
||||
return login.RefreshOAuthToken()
|
||||
}
|
||||
|
||||
// ReauthenticateLogin performs a full browser-based OAuth flow to get new tokens
|
||||
// for an existing login. This is used when the refresh token is expired or invalid.
|
||||
func ReauthenticateLogin(login *config.Login) error {
|
||||
opts := OAuthOptions{
|
||||
Name: login.Name,
|
||||
URL: login.URL,
|
||||
Insecure: login.Insecure,
|
||||
ClientID: config.DefaultClientID,
|
||||
RedirectURL: fmt.Sprintf("http://%s:%d", redirectHost, redirectPort),
|
||||
Port: redirectPort,
|
||||
}
|
||||
|
||||
_, token, err := performBrowserOAuthFlow(opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Update the existing login with new token data
|
||||
login.Token = token.AccessToken
|
||||
if token.RefreshToken != "" {
|
||||
login.RefreshToken = token.RefreshToken
|
||||
}
|
||||
if !token.Expiry.IsZero() {
|
||||
login.TokenExpiry = token.Expiry.Unix()
|
||||
}
|
||||
|
||||
// Save updated login
|
||||
return config.SaveLoginTokens(login)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user