package auth import ( "context" "encoding/json" "fmt" "log" "os" "git.pengzhan.dev/noteplace-server/internal/models" "golang.org/x/oauth2" "golang.org/x/oauth2/google" ) // Authenticator handles the OAuth2 flow. type Authenticator struct { Config *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") if clientID == "" || clientSecret == "" { return nil, fmt.Errorf("GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET must be set") } config := &oauth2.Config{ ClientID: clientID, ClientSecret: clientSecret, 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 } // 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) } // 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) } // 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) resp, err := client.Get("https://www.googleapis.com/oauth2/v2/userinfo") if err != nil { return models.User{}, err } defer func() { _ = resp.Body.Close() }() var userInfo struct { ID string `json:"id"` Email string `json:"email"` Name string `json:"name"` } if err := json.NewDecoder(resp.Body).Decode(&userInfo); err != nil { return models.User{}, err } log.Printf("Fetched user info from Google: ID=%s, Name=%s", userInfo.ID, userInfo.Name) // We create a new User model from the Google info. user := models.User{ GoogleID: userInfo.ID, DisplayName: userInfo.Name, Email: userInfo.Email, } return user, nil }