hackathon/api/auth.go

124 lines
3.3 KiB
Go

package api
import (
"context"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"time"
"github.com/hhhapz/hackathon/auth"
"golang.org/x/oauth2"
)
type AuthService struct {
oauthConfig auth.OAuthConfig
oauthStore OAuthStore
userStore UserStore
}
func (as *AuthService) GenOauth(w http.ResponseWriter, r *http.Request, params GenOauthParams) {
// We enforce callback field in production.
if inProd && params.Callback != "https://hackathon.teamortix.com" {
serverError(w, http.StatusUnprocessableEntity, "invalid callback provided")
}
page := as.oauthConfig.AuthCodeURL(as.oauthStore, params.Callback)
w.WriteHeader(http.StatusOK)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(ConsentPage{page})
}
var couldNotValidate = "Unable to authorize. Please try again.\nIf this issue persists, please contact an admin."
func (as *AuthService) AuthorizeCallback(w http.ResponseWriter, r *http.Request, params AuthorizeCallbackParams) {
callback, token, err := as.oauthConfig.Exchange(r.Context(), as.oauthStore, params.State, params.Code)
if err == auth.ErrInvalidState {
serverError(w, http.StatusUnprocessableEntity, "Invalid state provided")
return
}
if err != nil {
log.Printf("error: could not perform token: %v", err)
serverError(w, http.StatusInternalServerError, couldNotValidate)
return
}
accessToken, err := as.consumeToken(r.Context(), token)
if err != nil {
log.Printf("error: could not consume oauth token: %v", err)
http.Redirect(w, r, callback+"/fail", http.StatusFound)
return
}
http.SetCookie(w, &http.Cookie{
Name: "token",
Value: accessToken,
Path: "/",
Expires: time.Now().Add(time.Hour * 24 * 14), // 2 weeks
SameSite: http.SameSiteNoneMode, // handled via CSRF
Secure: inProd,
})
http.Redirect(w, r, callback+"/success", http.StatusFound)
}
const userinfoEndpoint = "https://www.googleapis.com/oauth2/v2/userinfo?access_token="
func (as *AuthService) consumeToken(ctx context.Context, token *oauth2.Token) (string, error) {
endpoint := userinfoEndpoint + token.AccessToken
client := as.oauthConfig.Client(ctx, token)
res, err := client.Get(endpoint)
if err != nil {
return "", fmt.Errorf("could not get userinfo: %v", err)
}
defer res.Body.Close()
buf, err := io.ReadAll(res.Body)
if err != nil {
return "", fmt.Errorf("could not read request body: %v", err)
}
user, err := as.userStore.DecodeUser(ctx, buf)
if err != nil {
return "", fmt.Errorf("could not create or get user from body: %v", err)
}
tk, err := as.userStore.CreateToken(ctx, user)
if err != nil {
return "", fmt.Errorf("could not create user token: %v", err)
}
return tk.Token, nil
}
func (as *AuthService) DeleteToken(w http.ResponseWriter, r *http.Request, params DeleteTokenParams) {
var all bool
if params.All != nil {
all = *params.All
}
var err error
if all {
user, err := as.userStore.UserByToken(r.Context(), params.Token)
if err != nil {
serverError(w, http.StatusUnauthorized, "Invalid token provided.")
return
}
_, err = as.userStore.RevokeUserTokens(r.Context(), user)
} else {
err = as.userStore.RevokeToken(r.Context(), params.Token)
}
if err != nil {
log.Printf("Could not revoke user token %#v: %v", params, err)
serverError(w, http.StatusInternalServerError, "Could not revoke token.")
return
}
w.WriteHeader(http.StatusNoContent)
}