uofgcal/auth/google.go

100 lines
2.2 KiB
Go

package auth
import (
"context"
_ "embed"
"fmt"
"math/rand"
"net/http"
"net/url"
"sync"
"github.com/charmbracelet/log"
"github.com/pkg/browser"
"golang.org/x/oauth2"
)
const listen_port = 3745
var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
//go:embed credentials.json
var GoogleOAuthCreds []byte
//go:embed index.html
var tab_html []byte
func randSeq(n int) string {
b := make([]rune, n)
for i := range b {
b[i] = letters[rand.Intn(len(letters))]
}
return string(b)
}
func fetch_google_token(config *oauth2.Config) *oauth2.Token {
// request authorization
var auth_code string
config.RedirectURL = fmt.Sprintf("http://localhost:%d", listen_port)
state_token := randSeq(16)
auth_url := config.AuthCodeURL(state_token, oauth2.AccessTypeOffline)
fmt.Printf("Go to the following link in your browser: \n%v", auth_url)
if err := browser.OpenURL(auth_url); err != nil {
log.Fatal("Unable to open browser: %v", err)
}
// setup http server and wait for auth code
srv := &http.Server{Addr: fmt.Sprintf(":%d", listen_port)}
srv_exit_done := &sync.WaitGroup{}
srv_exit_done.Add(1)
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
// parse query
query, err := url.ParseQuery(r.URL.RawQuery)
if err != nil {
log.Error("Unable to parse query: %v", err)
return
}
// ensure the state token is correct
if query.Get("state") != state_token {
log.Error("Invalid state token")
return
}
// get the auth code
auth_code = query.Get("code")
if auth_code == "" {
log.Error("No auth code provided")
return
}
// send back a response that automatically closes the tab
w.Header().Set("Content-Type", "text/html")
w.WriteHeader(http.StatusOK)
w.Write(tab_html)
// shutdown the http server
if err := srv.Shutdown(context.TODO()); err != nil {
log.Fatal(err)
}
})
go func() {
defer srv_exit_done.Done()
if err := srv.ListenAndServe(); err != http.ErrServerClosed {
log.Fatal("HTTP server error: %v", err)
}
}()
srv_exit_done.Wait()
// exchange auth code for token
token, err := config.Exchange(context.TODO(), auth_code)
if err != nil {
log.Fatalf("Unable to retrieve token from web: %v", err)
}
return token
}