package auth import ( "context" "crypto/rand" "encoding/base64" "encoding/json" "fmt" "log" "net/http" urls "forgejo.gwairfelin.com/max/gispatcho" "forgejo.gwairfelin.com/max/gonotes/internal/conf" "golang.org/x/oauth2" ) var myurls urls.URLs var oauthConfig *oauth2.Config var loginFunction func(user string, w http.ResponseWriter) string type userInfo struct { preferred_username string } func GetRoutes(prefix string, _loginFunction func(user string, w http.ResponseWriter) string) *http.ServeMux { loginFunction = _loginFunction oauthConfig = &oauth2.Config{ ClientID: conf.Conf.OIDC.ClientID, ClientSecret: conf.Conf.OIDC.ClientSecret, Endpoint: oauth2.Endpoint{AuthURL: conf.Conf.OIDC.AuthURL, TokenURL: conf.Conf.OIDC.TokenURL}, RedirectURL: conf.Conf.OIDC.RedirectURL, } myurls = urls.URLs{ Prefix: prefix, URLs: map[string]urls.URL{ "login": {Path: "/oauth/login/", Protocol: "GET", Handler: oauthLogin}, "callback": {Path: "/oauth/callback/", Protocol: "GET", Handler: oauthCallback}, }, } return myurls.GetRouter() } func oauthLogin(w http.ResponseWriter, r *http.Request) { log.Printf("%+v", *oauthConfig) oauthState := generateStateOAUTHCookie(w) url := oauthConfig.AuthCodeURL(oauthState) log.Printf("Redirecting to %s", url) http.Redirect(w, r, url, http.StatusTemporaryRedirect) } func generateStateOAUTHCookie(w http.ResponseWriter) string { b := make([]byte, 16) rand.Read(b) state := base64.URLEncoding.EncodeToString(b) cookie := http.Cookie{ Name: "oauthstate", Value: state, MaxAge: 30, Secure: true, HttpOnly: true, Path: "/auth/oauth/", } http.SetCookie(w, &cookie) return state } func oauthCallback(w http.ResponseWriter, r *http.Request) { // Read oauthState from Cookie oauthState, err := r.Cookie("oauthstate") if err != nil { log.Printf("An error occured during login: %s", err) http.Redirect(w, r, "/", http.StatusTemporaryRedirect) return } log.Printf("%v", oauthState) if r.FormValue("state") != oauthState.Value { log.Println("invalid oauth state") http.Redirect(w, r, "/", http.StatusTemporaryRedirect) return } data, err := getUserData(r.FormValue("code")) if err != nil { log.Println(err.Error()) http.Redirect(w, r, "/", http.StatusTemporaryRedirect) return } username, ok := data["preferred_username"] if !ok { log.Println("No username in auth response") http.Redirect(w, r, "/", http.StatusTemporaryRedirect) return } userStr, ok := username.(string) if !ok { log.Println("Username not interpretable as string") http.Redirect(w, r, "/", http.StatusTemporaryRedirect) return } loginFunction(userStr, w) http.Redirect(w, r, "/", http.StatusTemporaryRedirect) } func getUserData(code string) (map[string]any, error) { // Use code to get token and get user info from Google. token, err := oauthConfig.Exchange(context.Background(), code) if err != nil { return nil, fmt.Errorf("code exchange wrong: %s", err.Error()) } request, err := http.NewRequest("GET", conf.Conf.OIDC.UserinfoURL, nil) if err != nil { return nil, fmt.Errorf("failed to init http client for userinfo: %s", err.Error()) } request.Header.Set("Authorization", fmt.Sprintf("token %s", token.AccessToken)) response, err := http.DefaultClient.Do(request) if err != nil { return nil, fmt.Errorf("failed getting user info: %s", err.Error()) } defer response.Body.Close() uInf := make(map[string]any) err = json.NewDecoder(response.Body).Decode(&uInf) if err != nil { return nil, fmt.Errorf("failed to parse response as json: %s", err.Error()) } log.Printf("Contents of user data response %s", uInf) return uInf, nil }