ai cooking
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge pull request #176 from paulgmiller/pmiller/authrefactor

refactor more into auth make enable mocks still work

authored by

Paul Miller and committed by
GitHub
1e99d381 ef5cfbbd

+98 -50
+7 -32
cmd/careme/web.go
··· 37 37 return fmt.Errorf("failed to create cache: %w", err) 38 38 } 39 39 40 - authClient, err := auth.NewClient(cfg.Clerk.SecretKey) 40 + authClient, err := auth.NewFromConfig(cfg) 41 41 if err != nil { 42 - return fmt.Errorf("failed to create clerk client: %w", err) 42 + return fmt.Errorf("failed to create auth client: %w", err) 43 43 } 44 44 45 + mux := http.NewServeMux() 46 + authClient.Register(mux) 47 + 45 48 userStorage := users.NewStorage(cache) 46 49 47 50 generator, err := recipes.NewGenerator(cfg, cache) 48 51 if err != nil { 49 52 return fmt.Errorf("failed to create recipe generator: %w", err) 50 53 } 51 - tailwindETag := fmt.Sprintf(`"%x"`, sha256.Sum256(tailwindCSS)) 52 - mux := http.NewServeMux() 54 + 55 + tailwindETag := fmt.Sprintf(`"%x"`, sha256.Sum256(tailwindCSS)) 53 56 mux.HandleFunc("/static/tailwind.css", func(w http.ResponseWriter, r *http.Request) { 54 57 if r.Header.Get("If-None-Match") == tailwindETag { 55 58 w.WriteHeader(http.StatusNotModified) ··· 117 120 slog.ErrorContext(ctx, "home template execute error", "error", err) 118 121 http.Error(w, "template error", http.StatusInternalServerError) 119 122 } 120 - }) 121 - 122 - //TODO move signin/up/auth/establish/logout to auth package 123 - mux.HandleFunc("/sign-in", func(w http.ResponseWriter, r *http.Request) { 124 - http.Redirect(w, r, cfg.Clerk.Signin(), http.StatusSeeOther) 125 - }) 126 - mux.HandleFunc("/sign-up", func(w http.ResponseWriter, r *http.Request) { 127 - http.Redirect(w, r, cfg.Clerk.Signup(), http.StatusSeeOther) 128 - }) 129 - mux.HandleFunc("/auth/establish", func(w http.ResponseWriter, r *http.Request) { 130 - if cfg.Clerk.PublishableKey == "" { 131 - http.Error(w, "clerk publishable key missing", http.StatusInternalServerError) 132 - return 133 - } 134 - w.Header().Set("Content-Type", "text/html; charset=utf-8") 135 - data := struct { 136 - PublishableKey string 137 - }{ 138 - PublishableKey: cfg.Clerk.PublishableKey, 139 - } 140 - if err := templates.AuthEstablish.Execute(w, data); err != nil { 141 - slog.ErrorContext(r.Context(), "auth establish template execute error", "error", err) 142 - http.Error(w, "template error", http.StatusInternalServerError) 143 - } 144 - }) 145 - 146 - mux.HandleFunc("/logout", func(w http.ResponseWriter, r *http.Request) { 147 - authClient.Logout(w, r) 148 123 }) 149 124 150 125 ro := &readyOnce{}
+66 -18
internal/auth/clerk.go
··· 1 1 package auth 2 2 3 3 import ( 4 + "careme/internal/config" 5 + "careme/internal/templates" 4 6 "context" 5 7 "errors" 6 8 "fmt" ··· 10 12 11 13 "github.com/clerk/clerk-sdk-go/v2" 12 14 clerkhttp "github.com/clerk/clerk-sdk-go/v2/http" 15 + "github.com/clerk/clerk-sdk-go/v2/jwks" 13 16 "github.com/clerk/clerk-sdk-go/v2/session" 14 17 "github.com/clerk/clerk-sdk-go/v2/user" 15 18 ) ··· 18 21 ErrNoSession = errors.New("no valid session found") 19 22 ) 20 23 24 + type AuthClient interface { 25 + GetUserEmail(ctx context.Context, clerkUserID string) (string, error) 26 + GetUserIDFromRequest(r *http.Request) (string, error) 27 + WithAuthHTTP(handler http.Handler) http.Handler 28 + Register(mux *http.ServeMux) 29 + } 30 + 21 31 // Client wraps Clerk SDK functionality 22 32 // todo private 23 33 type clerkClient struct { 24 - secretKey string 25 - } 26 - 27 - type AuthClient interface { 28 - GetUserEmail(ctx context.Context, clerkUserID string) (string, error) 29 - GetUserIDFromRequest(r *http.Request) (string, error) 34 + cfg *config.Config 35 + userClient *user.Client 36 + sessionClient *session.Client 37 + jwksClient *jwks.Client 30 38 } 31 39 32 40 var _ AuthClient = (*clerkClient)(nil) 33 41 34 42 // NewClient creates a new Clerk client wrapper 35 - func NewClient(secretKey string) (*clerkClient, error) { 36 - if secretKey == "" { 43 + func NewClient(cfg *config.Config) (*clerkClient, error) { 44 + if cfg.Clerk.SecretKey == "" { 37 45 return nil, fmt.Errorf("clerk secret key is required") 38 46 } 39 47 40 - // Set the global Clerk secret key 41 - //TODO use a local client instead? GLOBALS BAD 42 - clerk.SetKey(secretKey) 48 + clientConfig := &clerk.ClientConfig{ 49 + BackendConfig: clerk.BackendConfig{ 50 + Key: clerk.String(cfg.Clerk.SecretKey), 51 + }, 52 + } 43 53 44 54 return &clerkClient{ 45 - secretKey: secretKey, 55 + userClient: user.NewClient(clientConfig), 56 + sessionClient: session.NewClient(clientConfig), 57 + jwksClient: jwks.NewClient(clientConfig), 58 + cfg: cfg, 46 59 }, nil 47 60 } 48 61 49 62 func (c *clerkClient) GetUserEmail(ctx context.Context, clerkUserID string) (string, error) { 50 - clerkUser, err := user.Get(ctx, clerkUserID) 63 + 64 + //todo can we pull this right off of claims? not woth bothering? 65 + clerkUser, err := c.userClient.Get(ctx, clerkUserID) 51 66 if err != nil { 52 67 return "", fmt.Errorf("failed to fetch clerk user: %w", err) 53 68 } ··· 94 109 slog.Info("authorization failure, purging cookies and redirecting") 95 110 // Clear any existing Clerk cookies by setting them to expired 96 111 clearCookie(w, "__session") 112 + clearCookie(w, "__clerk_db_jwt") // common in dev flows 113 + clearCookie(w, "__client") // if present in your setup 97 114 http.Redirect(w, r, r.RequestURI, http.StatusFound) 98 115 })) 99 116 ··· 105 122 return "" 106 123 }) 107 124 108 - wrapped := clerkhttp.WithHeaderAuthorization(purgeAndRedirect, useSessionCookie)(handler) 125 + wrapped := clerkhttp.WithHeaderAuthorization( 126 + purgeAndRedirect, 127 + useSessionCookie, 128 + clerkhttp.JWKSClient(c.jwksClient), 129 + )(handler) 109 130 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 110 131 wrapped.ServeHTTP(w, r) 111 132 }) 112 133 } 113 134 114 - func (c *clerkClient) Logout(w http.ResponseWriter, r *http.Request) { 135 + func (c *clerkClient) logout(w http.ResponseWriter, r *http.Request) { 115 136 claims, ok := clerk.SessionClaimsFromContext(r.Context()) 116 137 if ok && claims.SessionID != "" { 117 138 // Revoke the active Clerk session (sid claim). 118 - _, _ = session.Revoke(r.Context(), &session.RevokeParams{ID: claims.SessionID}) 139 + _, _ = c.sessionClient.Revoke(r.Context(), &session.RevokeParams{ID: claims.SessionID}) 119 140 } 120 141 121 142 // Clear app-domain cookies that can re-bootstrap auth. ··· 140 161 }) 141 162 } 142 163 143 - /* Toss this in if you're confused :) 144 - func debugAuth(next http.Handler) http.Handler { 164 + func (c *clerkClient) Register(mux *http.ServeMux) { 165 + mux.HandleFunc("/logout", c.logout) 166 + mux.HandleFunc("/sign-in", func(w http.ResponseWriter, r *http.Request) { 167 + http.Redirect(w, r, c.cfg.Clerk.Signin(), http.StatusSeeOther) 168 + }) 169 + mux.HandleFunc("/sign-up", func(w http.ResponseWriter, r *http.Request) { 170 + http.Redirect(w, r, c.cfg.Clerk.Signup(), http.StatusSeeOther) 171 + }) 172 + mux.HandleFunc("/auth/establish", func(w http.ResponseWriter, r *http.Request) { 173 + if c.cfg.Clerk.PublishableKey == "" { 174 + http.Error(w, "clerk publishable key missing", http.StatusInternalServerError) 175 + return 176 + } 177 + w.Header().Set("Content-Type", "text/html; charset=utf-8") 178 + data := struct { 179 + PublishableKey string 180 + }{ 181 + PublishableKey: c.cfg.Clerk.PublishableKey, 182 + } 183 + if err := templates.AuthEstablish.Execute(w, data); err != nil { 184 + slog.ErrorContext(r.Context(), "auth establish template execute error", "error", err) 185 + http.Error(w, "template error", http.StatusInternalServerError) 186 + } 187 + }) 188 + } 189 + 190 + // Toss this in if you're confused :) 191 + /* 192 + func DebugAuth(next http.Handler) http.Handler { 145 193 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 146 194 q := r.URL.Query() 147 195 hasDB := q.Has("__clerk_db_jwt")
+12
internal/auth/factory.go
··· 1 + package auth 2 + 3 + import "careme/internal/config" 4 + 5 + // NewFromConfig creates an AuthClient based on config settings. 6 + func NewFromConfig(cfg *config.Config) (AuthClient, error) { 7 + if cfg.Mocks.Enable { 8 + return Mock(cfg), nil 9 + } 10 + 11 + return NewClient(cfg) 12 + }
+12
internal/auth/mock.go
··· 32 32 func (c *mockClient) GetUserIDFromRequest(r *http.Request) (string, error) { 33 33 return "mock-clerk-user-id", nil 34 34 } 35 + 36 + func (c *mockClient) WithAuthHTTP(handler http.Handler) http.Handler { 37 + return handler 38 + } 39 + 40 + func (c *mockClient) logout(w http.ResponseWriter, r *http.Request) { 41 + http.Error(w, "not implemented in mock auth client", http.StatusNotImplemented) 42 + } 43 + 44 + func (c *mockClient) Register(mux *http.ServeMux) { 45 + mux.HandleFunc("/logout", c.logout) 46 + }
+1
internal/config/config.go
··· 40 40 41 41 var locahostredirect = "?redirect_url=http://localhost:8080/auth/establish" 42 42 43 + // move to auth pacakage? 43 44 func (c *ClerkConfig) Signin() string { 44 45 url := fmt.Sprintf("https://%s/sign-in", c.Domain) 45 46 if !c.Prod {