feat: added web based auth path
Go CI / build (push) Successful in 46s

This commit is contained in:
2025-09-20 00:29:41 -07:00
parent a95f355059
commit 55d4eb2e26
6 changed files with 432 additions and 31 deletions
+42 -17
View File
@@ -14,44 +14,69 @@ import (
// Authenticator handles the OAuth2 flow.
type Authenticator struct {
Config *oauth2.Config
WebConfig *oauth2.Config
CliConfig *oauth2.Config
}
// NewAuthenticator creates a new Authenticator.
// It requires GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET environment variables.
func NewAuthenticator() (*Authenticator, error) {
clientID := os.Getenv("GOOGLE_CLIENT_ID")
clientSecret := os.Getenv("GOOGLE_CLIENT_SECRET")
cliClientID := os.Getenv("CLI_GOOGLE_CLIENT_ID")
cliclientSecret := os.Getenv("CLI_GOOGLE_CLIENT_SECRET")
webClientID := os.Getenv("WEB_GOOGLE_CLIENT_ID")
webClientSecret := os.Getenv("WEB_GOOGLE_CLIENT_SECRET")
if clientID == "" || clientSecret == "" {
return nil, fmt.Errorf("GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET must be set")
if cliClientID == "" || cliclientSecret == "" {
return nil, fmt.Errorf("CLI_GOOGLE_CLIENT_ID and CLI_GOOGLE_CLIENT_SECRET must be set for CLI")
}
config := &oauth2.Config{
ClientID: clientID,
ClientSecret: clientSecret,
if webClientID == "" || webClientSecret == "" {
return nil, fmt.Errorf("WEB_GOOGLE_CLIENT_ID and WEB_GOOGLE_CLIENT_SECRET must be set for web")
}
webConfig := &oauth2.Config{
ClientID: webClientID,
ClientSecret: webClientSecret,
RedirectURL: "http://localhost:3001/api/auth/google/callback", // For web flow
Scopes: []string{"https://www.googleapis.com/auth/userinfo.email", "https://www.googleapis.com/auth/userinfo.profile"},
Endpoint: google.Endpoint,
}
cliConfig := &oauth2.Config{
ClientID: cliClientID,
ClientSecret: cliclientSecret,
RedirectURL: "urn:ietf:wg:oauth:2.0:oob", // Special redirect URI for desktop/CLI apps
Scopes: []string{"https://www.googleapis.com/auth/userinfo.email", "https://www.googleapis.com/auth/userinfo.profile"},
Endpoint: google.Endpoint,
}
return &Authenticator{Config: config}, nil
return &Authenticator{WebConfig: webConfig, CliConfig: cliConfig}, nil
}
// GetAuthURL generates the URL the user must visit to authorize the application.
func (a *Authenticator) GetAuthURL() string {
// We don't need a state for this simple CLI flow, but it's good practice for web apps.
return a.Config.AuthCodeURL("state-token", oauth2.AccessTypeOffline)
// GetWebAuthURL generates the URL for the web-based auth flow.
func (a *Authenticator) GetWebAuthURL() string {
return a.WebConfig.AuthCodeURL("state-token", oauth2.AccessTypeOffline)
}
// ExchangeCodeForToken takes an authorization code and exchanges it for a token.
func (a *Authenticator) ExchangeCodeForToken(code string) (*oauth2.Token, error) {
return a.Config.Exchange(context.Background(), code)
// GetCliAuthURL generates the URL for the CLI-based auth flow.
func (a *Authenticator) GetCliAuthURL() string {
return a.CliConfig.AuthCodeURL("state-token", oauth2.AccessTypeOffline)
}
// ExchangeWebCodeForToken takes an authorization code from the web flow and exchanges it for a token.
func (a *Authenticator) ExchangeWebCodeForToken(code string) (*oauth2.Token, error) {
return a.WebConfig.Exchange(context.Background(), code)
}
// ExchangeCliCodeForToken takes an authorization code from the CLI flow and exchanges it for a token.
func (a *Authenticator) ExchangeCliCodeForToken(code string) (*oauth2.Token, error) {
return a.CliConfig.Exchange(context.Background(), code)
}
// GetUserInfo uses the token to fetch the user's profile from Google.
func (a *Authenticator) GetUserInfo(token *oauth2.Token) (models.User, error) {
client := a.Config.Client(context.Background(), token)
// This client can be created from either WebConfig or CliConfig, it doesn't matter which.
client := a.WebConfig.Client(context.Background(), token)
resp, err := client.Get("https://www.googleapis.com/oauth2/v2/userinfo")
if err != nil {
return models.User{}, err