appview/oauth: return to original page after login-block #499

merged
opened by oppi.li targeting master from push-pkqzkqmxotyz

when attempting to do an authorized request while logged-out, the auth middleware boots the user to the login page. this page now keeps track of the 'Referer' in the oauth request. once the oauth callback is complete, the user is sent back to the page they came from.

Signed-off-by: oppiliappan me@oppi.li

Changed files
+26 -6
appview
cache
session
middleware
oauth
pages
templates
user
+1
appview/cache/session/store.go
··· 31 PkceVerifier string 32 DpopAuthserverNonce string 33 DpopPrivateJwk string 34 } 35 36 type SessionStore struct {
··· 31 PkceVerifier string 32 DpopAuthserverNonce string 33 DpopPrivateJwk string 34 + ReturnUrl string 35 } 36 37 type SessionStore struct {
+10 -2
appview/middleware/middleware.go
··· 5 "fmt" 6 "log" 7 "net/http" 8 "slices" 9 "strconv" 10 "strings" ··· 46 func AuthMiddleware(a *oauth.OAuth) middlewareFunc { 47 return func(next http.Handler) http.Handler { 48 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 49 redirectFunc := func(w http.ResponseWriter, r *http.Request) { 50 - http.Redirect(w, r, "/login", http.StatusTemporaryRedirect) 51 } 52 if r.Header.Get("HX-Request") == "true" { 53 redirectFunc = func(w http.ResponseWriter, _ *http.Request) { 54 - w.Header().Set("HX-Redirect", "/login") 55 w.WriteHeader(http.StatusOK) 56 } 57 }
··· 5 "fmt" 6 "log" 7 "net/http" 8 + "net/url" 9 "slices" 10 "strconv" 11 "strings" ··· 47 func AuthMiddleware(a *oauth.OAuth) middlewareFunc { 48 return func(next http.Handler) http.Handler { 49 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 50 + returnURL := "/" 51 + if u, err := url.Parse(r.Header.Get("Referer")); err == nil { 52 + returnURL = u.RequestURI() 53 + } 54 + 55 + loginURL := fmt.Sprintf("/login?return_url=%s", url.QueryEscape(returnURL)) 56 + 57 redirectFunc := func(w http.ResponseWriter, r *http.Request) { 58 + http.Redirect(w, r, loginURL, http.StatusTemporaryRedirect) 59 } 60 if r.Header.Get("HX-Request") == "true" { 61 redirectFunc = func(w http.ResponseWriter, _ *http.Request) { 62 + w.Header().Set("HX-Redirect", loginURL) 63 w.WriteHeader(http.StatusOK) 64 } 65 }
+11 -2
appview/oauth/handler/handler.go
··· 109 func (o *OAuthHandler) login(w http.ResponseWriter, r *http.Request) { 110 switch r.Method { 111 case http.MethodGet: 112 - o.pages.Login(w, pages.LoginParams{}) 113 case http.MethodPost: 114 handle := r.FormValue("handle") 115 ··· 194 DpopAuthserverNonce: parResp.DpopAuthserverNonce, 195 DpopPrivateJwk: string(dpopKeyJson), 196 State: parResp.State, 197 }) 198 if err != nil { 199 log.Println("failed to save oauth request:", err) ··· 311 } 312 } 313 314 - http.Redirect(w, r, "/", http.StatusFound) 315 } 316 317 func (o *OAuthHandler) logout(w http.ResponseWriter, r *http.Request) {
··· 109 func (o *OAuthHandler) login(w http.ResponseWriter, r *http.Request) { 110 switch r.Method { 111 case http.MethodGet: 112 + returnURL := r.URL.Query().Get("return_url") 113 + o.pages.Login(w, pages.LoginParams{ 114 + ReturnUrl: returnURL, 115 + }) 116 case http.MethodPost: 117 handle := r.FormValue("handle") 118 ··· 197 DpopAuthserverNonce: parResp.DpopAuthserverNonce, 198 DpopPrivateJwk: string(dpopKeyJson), 199 State: parResp.State, 200 + ReturnUrl: r.FormValue("return_url"), 201 }) 202 if err != nil { 203 log.Println("failed to save oauth request:", err) ··· 315 } 316 } 317 318 + returnUrl := oauthRequest.ReturnUrl 319 + if returnUrl == "" { 320 + returnUrl = "/" 321 + } 322 + 323 + http.Redirect(w, r, returnUrl, http.StatusFound) 324 } 325 326 func (o *OAuthHandler) logout(w http.ResponseWriter, r *http.Request) {
+2 -2
appview/oauth/oauth.go
··· 103 if err != nil { 104 return nil, false, fmt.Errorf("error parsing expiry time: %w", err) 105 } 106 - if expiry.Sub(time.Now()) <= 5*time.Minute { 107 privateJwk, err := helpers.ParseJWKFromBytes([]byte(session.DpopPrivateJwk)) 108 if err != nil { 109 return nil, false, err ··· 315 redirectURIs := makeRedirectURIs(clientURI) 316 317 if o.config.Core.Dev { 318 - clientURI = fmt.Sprintf("http://127.0.0.1:3000") 319 redirectURIs = makeRedirectURIs(clientURI) 320 321 query := url.Values{}
··· 103 if err != nil { 104 return nil, false, fmt.Errorf("error parsing expiry time: %w", err) 105 } 106 + if time.Until(expiry) <= 5*time.Minute { 107 privateJwk, err := helpers.ParseJWKFromBytes([]byte(session.DpopPrivateJwk)) 108 if err != nil { 109 return nil, false, err ··· 315 redirectURIs := makeRedirectURIs(clientURI) 316 317 if o.config.Core.Dev { 318 + clientURI = "http://127.0.0.1:3000" 319 redirectURIs = makeRedirectURIs(clientURI) 320 321 query := url.Values{}
+1
appview/pages/pages.go
··· 261 } 262 263 type LoginParams struct { 264 } 265 266 func (p *Pages) Login(w io.Writer, params LoginParams) error {
··· 261 } 262 263 type LoginParams struct { 264 + ReturnUrl string 265 } 266 267 func (p *Pages) Login(w io.Writer, params LoginParams) error {
+1
appview/pages/templates/user/login.html
··· 41 your Tangled (<code>.tngl.sh</code>) or <a href="https://bsky.app">Bluesky</a> (<code>.bsky.social</code>) account. 42 </span> 43 </div> 44 45 <button 46 class="btn w-full my-2 mt-6 text-base "
··· 41 your Tangled (<code>.tngl.sh</code>) or <a href="https://bsky.app">Bluesky</a> (<code>.bsky.social</code>) account. 42 </span> 43 </div> 44 + <input type="hidden" name="return_url" value="{{ .ReturnUrl }}"> 45 46 <button 47 class="btn w-full my-2 mt-6 text-base "