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
···
switch r.Method {
case http.MethodGet:
returnURL := r.URL.Query().Get("return_url")
+
errorCode := r.URL.Query().Get("error")
views.LoginPage(views.LoginPageParams{
ReturnUrl: returnURL,
+
ErrorCode: errorCode,
}).Render(r.Context(), w)
case http.MethodPost:
handle := r.FormValue("handle")
+15 -8
internal/server/oauth/handler.go
···
import (
"context"
"encoding/json"
+
"errors"
"fmt"
"net/http"
"time"
comatproto "github.com/bluesky-social/indigo/api/atproto"
+
"github.com/bluesky-social/indigo/atproto/auth/oauth"
lexutil "github.com/bluesky-social/indigo/lex/util"
"github.com/go-chi/chi/v5"
"github.com/lestrrat-go/jwx/v2/jwk"
···
"yoten.app/api/yoten"
ph "yoten.app/internal/clients/posthog"
"yoten.app/internal/db"
-
"yoten.app/internal/server/htmx"
)
func (o *OAuth) Router() http.Handler {
···
}
func (o *OAuth) callback(w http.ResponseWriter, r *http.Request) {
-
l := o.Logger.With("handler", "callback")
ctx := r.Context()
+
l := o.Logger.With("handler", "callback").With("query", r.URL.Query())
sessData, err := o.ClientApp.ProcessCallback(ctx, r.URL.Query())
if err != nil {
-
o.Logger.Error("failed to process callback", "err", err)
-
http.Error(w, err.Error(), http.StatusInternalServerError)
+
var callbackErr *oauth.AuthRequestCallbackError
+
if errors.As(err, &callbackErr) {
+
l.Debug("callback error", "err", callbackErr)
+
http.Redirect(w, r, fmt.Sprintf("/login?error=%s", callbackErr.ErrorCode), http.StatusFound)
+
return
+
}
+
l.Error("failed to process callback", "err", err)
+
http.Redirect(w, r, "/login?error=oauth", http.StatusFound)
return
}
if err := o.SaveSession(w, r, sessData); err != nil {
l.Error("failed to save session", "err", err)
-
http.Error(w, err.Error(), http.StatusInternalServerError)
+
http.Redirect(w, r, "/login?error=session", http.StatusFound)
return
}
···
resolved, err := o.IdResolver.ResolveIdent(context.Background(), did)
if err != nil {
l.Error("failed to resolve handle", "handle", resolved.Handle.String(), "err", err)
-
htmx.HxError(w, http.StatusBadRequest, fmt.Sprintf("'%s' is an invalid handle", resolved.Handle.String()))
+
http.Redirect(w, r, "/login?error=handle", http.StatusFound)
return
}
client, err := o.AuthorizedClient(r)
if err != nil {
l.Error("failed to get authorized client", "err", err)
-
http.Error(w, err.Error(), http.StatusInternalServerError)
+
http.Redirect(w, r, "/login?error=client", http.StatusFound)
return
}
···
})
if err != nil {
l.Error("failed to create profile record", "err", err)
-
htmx.HxError(w, http.StatusInternalServerError, "Failed to announce profile creation, try again later")
+
http.Redirect(w, r, "/login?error=profile-creation", http.StatusFound)
return
}
+23
internal/server/views/login.templ
···
<i class="w-4 h-4" data-lucide="arrow-right"></i>
</button>
</form>
+
if params.ErrorCode != "" {
+
<div class="flex flex-col my-4 p-4 bg-red-50 border border-red-500 rounded-lg text-red-500">
+
<div class="flex items-center gap-1">
+
<i class="w-4 h-4" data-lucide="circle-alert"></i>
+
<h5 class="font-medium">Login error</h5>
+
</div>
+
<div>
+
<p class="text-sm">
+
switch (params.ErrorCode) {
+
case "access_denied":
+
You have not authorized the app.
+
case "session":
+
Server failed to create user session.
+
case "handle":
+
Server failed to validate your handle.
+
default:
+
Internal Server error.
+
}
+
Please try again.
+
</p>
+
</div>
+
</div>
+
}
<div class="mt-6 pt-6 border-t border-bg-dark">
<div class="text-center">
<p class="text-sm text-text-muted mb-3">New to the AT Protocol?</p>
+1
internal/server/views/views.go
···
type LoginPageParams struct {
ReturnUrl string
+
ErrorCode string
}
type ProfilePageParams struct {