Created
March 7, 2023 00:22
-
-
Save delaneyj/4f93f9965d1996329f3caa6592fe6c87 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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("©2023 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