feat(oauth/handler): pass error codes back to login page #28

merged
opened by brookjeynes.dev targeting master from push-wlwouvqurvkz
Changed files
+41 -8
internal
server
handlers
oauth
views
+2
internal/server/handlers/login.go
··· 19 19 switch r.Method { 20 20 case http.MethodGet: 21 21 returnURL := r.URL.Query().Get("return_url") 22 + errorCode := r.URL.Query().Get("error") 22 23 views.LoginPage(views.LoginPageParams{ 23 24 ReturnUrl: returnURL, 25 + ErrorCode: errorCode, 24 26 }).Render(r.Context(), w) 25 27 case http.MethodPost: 26 28 handle := r.FormValue("handle")
+15 -8
internal/server/oauth/handler.go
··· 3 3 import ( 4 4 "context" 5 5 "encoding/json" 6 + "errors" 6 7 "fmt" 7 8 "net/http" 8 9 "time" 9 10 10 11 comatproto "github.com/bluesky-social/indigo/api/atproto" 12 + "github.com/bluesky-social/indigo/atproto/auth/oauth" 11 13 lexutil "github.com/bluesky-social/indigo/lex/util" 12 14 "github.com/go-chi/chi/v5" 13 15 "github.com/lestrrat-go/jwx/v2/jwk" ··· 16 18 "yoten.app/api/yoten" 17 19 ph "yoten.app/internal/clients/posthog" 18 20 "yoten.app/internal/db" 19 - "yoten.app/internal/server/htmx" 20 21 ) 21 22 22 23 func (o *OAuth) Router() http.Handler { ··· 71 72 } 72 73 73 74 func (o *OAuth) callback(w http.ResponseWriter, r *http.Request) { 74 - l := o.Logger.With("handler", "callback") 75 75 ctx := r.Context() 76 + l := o.Logger.With("handler", "callback").With("query", r.URL.Query()) 76 77 77 78 sessData, err := o.ClientApp.ProcessCallback(ctx, r.URL.Query()) 78 79 if err != nil { 79 - o.Logger.Error("failed to process callback", "err", err) 80 - http.Error(w, err.Error(), http.StatusInternalServerError) 80 + var callbackErr *oauth.AuthRequestCallbackError 81 + if errors.As(err, &callbackErr) { 82 + l.Debug("callback error", "err", callbackErr) 83 + http.Redirect(w, r, fmt.Sprintf("/login?error=%s", callbackErr.ErrorCode), http.StatusFound) 84 + return 85 + } 86 + l.Error("failed to process callback", "err", err) 87 + http.Redirect(w, r, "/login?error=oauth", http.StatusFound) 81 88 return 82 89 } 83 90 84 91 if err := o.SaveSession(w, r, sessData); err != nil { 85 92 l.Error("failed to save session", "err", err) 86 - http.Error(w, err.Error(), http.StatusInternalServerError) 93 + http.Redirect(w, r, "/login?error=session", http.StatusFound) 87 94 return 88 95 } 89 96 ··· 91 98 resolved, err := o.IdResolver.ResolveIdent(context.Background(), did) 92 99 if err != nil { 93 100 l.Error("failed to resolve handle", "handle", resolved.Handle.String(), "err", err) 94 - htmx.HxError(w, http.StatusBadRequest, fmt.Sprintf("'%s' is an invalid handle", resolved.Handle.String())) 101 + http.Redirect(w, r, "/login?error=handle", http.StatusFound) 95 102 return 96 103 } 97 104 98 105 client, err := o.AuthorizedClient(r) 99 106 if err != nil { 100 107 l.Error("failed to get authorized client", "err", err) 101 - http.Error(w, err.Error(), http.StatusInternalServerError) 108 + http.Redirect(w, r, "/login?error=client", http.StatusFound) 102 109 return 103 110 } 104 111 ··· 128 135 }) 129 136 if err != nil { 130 137 l.Error("failed to create profile record", "err", err) 131 - htmx.HxError(w, http.StatusInternalServerError, "Failed to announce profile creation, try again later") 138 + http.Redirect(w, r, "/login?error=profile-creation", http.StatusFound) 132 139 return 133 140 } 134 141
+23
internal/server/views/login.templ
··· 44 44 <i class="w-4 h-4" data-lucide="arrow-right"></i> 45 45 </button> 46 46 </form> 47 + if params.ErrorCode != "" { 48 + <div class="flex flex-col my-4 p-4 bg-red-50 border border-red-500 rounded-lg text-red-500"> 49 + <div class="flex items-center gap-1"> 50 + <i class="w-4 h-4" data-lucide="circle-alert"></i> 51 + <h5 class="font-medium">Login error</h5> 52 + </div> 53 + <div> 54 + <p class="text-sm"> 55 + switch (params.ErrorCode) { 56 + case "access_denied": 57 + You have not authorized the app. 58 + case "session": 59 + Server failed to create user session. 60 + case "handle": 61 + Server failed to validate your handle. 62 + default: 63 + Internal Server error. 64 + } 65 + Please try again. 66 + </p> 67 + </div> 68 + </div> 69 + } 47 70 <div class="mt-6 pt-6 border-t border-bg-dark"> 48 71 <div class="text-center"> 49 72 <p class="text-sm text-text-muted mb-3">New to the AT Protocol?</p>
+1
internal/server/views/views.go
··· 18 18 19 19 type LoginPageParams struct { 20 20 ReturnUrl string 21 + ErrorCode string 21 22 } 22 23 23 24 type ProfilePageParams struct {