Skip to content

Instantly share code, notes, and snippets.

@delaneyj
Created March 7, 2023 00:22
Show Gist options
  • Save delaneyj/4f93f9965d1996329f3caa6592fe6c87 to your computer and use it in GitHub Desktop.
Save delaneyj/4f93f9965d1996329f3caa6592fe6c87 to your computer and use it in GitHub Desktop.
package views
import (
"context"
"fmt"
"log"
"net/http"
"strings"
"time"
"github.com/eventgraph/monorepo/pkg/backend"
"github.com/eventgraph/monorepo/pkg/data"
"github.com/eventgraph/monorepo/pkg/toolbelt"
"github.com/go-chi/chi/v5"
e "github.com/gogoracer/racer/pkg/engine"
"github.com/gogoracer/racer/pkg/goggles/iconify/mdi"
. "github.com/gogoracer/racer/pkg/goggles/racer_attribute"
. "github.com/gogoracer/racer/pkg/goggles/racer_html"
"github.com/gorilla/sessions"
"github.com/markbates/goth"
"github.com/markbates/goth/gothic"
"github.com/markbates/goth/providers/github"
)
func NewAuthPage(db *toolbelt.Database) *e.UberElement {
authElement := Centerer(
"shadow-3xl", // outer
"", //inner
DIV_X(
"p-6 rounded shadow-lg rounded-lg flex flex-col gap-6 bg-stone-400",
IMG_X("rounded-full w-64 h-64 shadow-xl", "/static/eventgraph_logo.png"),
socialProvidersBox,
DIV_X("text-xs text-stone-500 text-right").Val().Str("&copy2023 EventGraph LLC"),
),
).SetMount(func(ctx context.Context) {
start := time.Now()
socialProviders := []data.SocialProvider{}
//REDACTED DB STUFF
list := DIV().Class("flex flex-col gap-2")
for _, sp := range socialProviders {
lowerName := strings.ToLower(sp.Name)
icon, err := mdi.ByName(lowerName)
if err != nil {
panic(fmt.Errorf("failed to get icon for %s: %w", lowerName, err))
}
providerButton := BUTTON().Attr(
CLASS(`
flex justify-center items-center gap-2
transition
bg-stone-500
hover:shadow
hover:bg-stone-600
hover:ring-2
hover:ring-stone-700
focus:bg-stone-700
text-stone-300
hover:text-stone-200
w-full px-4 py-2 uppercase rounded
disabled:hover:ring-0
disabled:hover:shadow-none
disabled:hover:bg-stone-500
disabled:opacity-25
disabled:cursor-not-allowed
`),
e.NewAttribute("x-data", fmt.Sprintf(`{
redirect(){
window.location.href = "/auth/%s"
}
}`, lowerName)),
e.NewAttribute("@click", "redirect()"),
).Element(
icon,
).Val().Str(sp.Name)
if !sp.Enabled {
providerButton.Attr(DISABLED())
}
list.Element(providerButton)
}
socialProvidersBox.Set(list)
log.Printf("read social providers in %s", time.Since(start))
})
return authElement
}
const sessionKey = "REDACTED"
const UserIDKey = "user"
func setupOAuth(ctx context.Context, router *chi.Mux, db *toolbelt.Database) error {
sessionStore := sessions.NewCookieStore([]byte("eventgraph dev"))
goth.UseProviders(
github.New("REDACTED", "REDACTED", "http://localhost:3334/auth/github/callback"),
)
gothic.Store = sessionStore
router.Use(func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
session, err := sessionStore.Get(r, sessionKey)
if err != nil {
fmt.Fprintln(w, err)
return
}
userIDRaw, ok := session.Values[UserIDKey]
if !ok {
r = r.WithContext(WithUser(r.Context(), nil))
next.ServeHTTP(w, r)
return
}
userID, ok := userIDRaw.(int64)
if !ok {
fmt.Fprintln(w, "invalid user id")
return
}
var (
user *data.User
provider *data.SocialProvider
)
ctx := r.Context()
// REDACTED DB STUFF
if user == nil {
fmt.Fprintln(w, "user not found")
return
}
if provider == nil {
fmt.Fprintln(w, "social provider not found")
return
}
ctx = WithUser(ctx, user)
ctx = WithSocialProvider(ctx, provider)
r = r.WithContext(ctx)
next.ServeHTTP(w, r)
})
})
socialProviders := map[string]data.SocialProvider{}
// REDACTED DB STUFF
reqWithProvider := func(r *http.Request) (*http.Request, error) {
provider := chi.URLParam(r, "provider")
if _, ok := socialProviders[provider]; !ok {
return nil, fmt.Errorf("unknown provider %s", provider)
}
ctx := r.Context()
//lint:ignore SA1029 goth needs the raw string
ctx = context.WithValue(ctx, "provider", provider)
return r.WithContext(ctx), nil
}
handleGothUser := func(w http.ResponseWriter, r *http.Request, shouldBeginGothHandlerOnError bool) {
if existingUser := UserFromContext(ctx); existingUser != nil {
http.Redirect(w, r, "/", http.StatusFound)
return
}
var err error
r, err = reqWithProvider(r)
if err != nil {
fmt.Fprintln(w, err)
return
}
if gothUser, err := gothic.CompleteUserAuth(w, r); err == nil {
var u *data.User
// REDACTED DB STUFF
sess, err := sessionStore.Get(r, sessionKey)
if err != nil {
fmt.Fprintln(w, err)
return
}
sess.Values[UserIDKey] = u.ID
if err := sess.Save(r, w); err != nil {
fmt.Fprintln(w, err)
return
}
// Redirect to the home page
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
} else {
if shouldBeginGothHandlerOnError {
gothic.BeginAuthHandler(w, r)
} else {
fmt.Fprintln(w, err)
}
}
}
router.Get("/auth/{provider}/logout", func(w http.ResponseWriter, r *http.Request) {
var err error
r, err = reqWithProvider(r)
if err != nil {
fmt.Fprintln(w, err)
return
}
gothic.Logout(w, r)
session, err := sessionStore.Get(r, sessionKey)
if err != nil {
fmt.Fprintln(w, err)
return
}
if session.Values[UserIDKey] != nil {
delete(session.Values, UserIDKey)
if err := session.Save(r, w); err != nil {
fmt.Fprintln(w, err)
return
}
}
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
})
router.Get("/auth/{provider}", func(w http.ResponseWriter, r *http.Request) {
// try to get the user without re-authenticating
handleGothUser(w, r, true)
})
router.Get("/auth/{provider}/callback", func(w http.ResponseWriter, r *http.Request) {
handleGothUser(w, r, false)
})
return nil
}
func WithUser(ctx context.Context, user *data.User) context.Context {
//lint:ignore SA1029 gorilla sessions needs the raw string
return context.WithValue(ctx, UserIDKey, user)
}
func UserFromContext(ctx context.Context) *data.User {
user, ok := ctx.Value(UserIDKey).(*data.User)
if !ok {
return nil
}
return user
}
func WithSocialProvider(ctx context.Context, provider *data.SocialProvider) context.Context {
//lint:ignore SA1029 goth needs the raw string
return context.WithValue(ctx, "provider", provider)
}
func SocialProviderFromContext(ctx context.Context) *data.SocialProvider {
provider, ok := ctx.Value("provider").(*data.SocialProvider)
if !ok {
return nil
}
return provider
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment