Mirror of @tangled.org/core. Running on a Raspberry Pi Zero 2

state: add account switch/remove endpoints and login flow

authored by lewis.moe and committed by tangled.org cea9f509 d47f275f

+143 -7
+83
appview/state/accounts.go
···
··· 1 + package state 2 + 3 + import ( 4 + "net/http" 5 + 6 + "github.com/go-chi/chi/v5" 7 + ) 8 + 9 + func (s *State) SwitchAccount(w http.ResponseWriter, r *http.Request) { 10 + l := s.logger.With("handler", "SwitchAccount") 11 + 12 + if err := r.ParseForm(); err != nil { 13 + l.Error("failed to parse form", "err", err) 14 + http.Error(w, "invalid request", http.StatusBadRequest) 15 + return 16 + } 17 + 18 + did := r.FormValue("did") 19 + if did == "" { 20 + http.Error(w, "missing did", http.StatusBadRequest) 21 + return 22 + } 23 + 24 + if err := s.oauth.SwitchAccount(w, r, did); err != nil { 25 + l.Error("failed to switch account", "err", err) 26 + s.pages.HxRedirect(w, "/login?error=session") 27 + return 28 + } 29 + 30 + l.Info("switched account", "did", did) 31 + s.pages.HxRedirect(w, "/") 32 + } 33 + 34 + func (s *State) RemoveAccount(w http.ResponseWriter, r *http.Request) { 35 + l := s.logger.With("handler", "RemoveAccount") 36 + 37 + did := chi.URLParam(r, "did") 38 + if did == "" { 39 + http.Error(w, "missing did", http.StatusBadRequest) 40 + return 41 + } 42 + 43 + currentUser := s.oauth.GetMultiAccountUser(r) 44 + isCurrentAccount := currentUser != nil && currentUser.Active.Did == did 45 + 46 + var remainingAccounts []string 47 + if currentUser != nil { 48 + for _, acc := range currentUser.Accounts { 49 + if acc.Did != did { 50 + remainingAccounts = append(remainingAccounts, acc.Did) 51 + } 52 + } 53 + } 54 + 55 + if err := s.oauth.RemoveAccount(w, r, did); err != nil { 56 + l.Error("failed to remove account", "err", err) 57 + http.Error(w, "failed to remove account", http.StatusInternalServerError) 58 + return 59 + } 60 + 61 + l.Info("removed account", "did", did) 62 + 63 + if isCurrentAccount { 64 + if len(remainingAccounts) > 0 { 65 + nextDid := remainingAccounts[0] 66 + if err := s.oauth.SwitchAccount(w, r, nextDid); err != nil { 67 + l.Error("failed to switch to next account", "err", err) 68 + s.pages.HxRedirect(w, "/login") 69 + return 70 + } 71 + s.pages.HxRefresh(w) 72 + return 73 + } 74 + 75 + if err := s.oauth.DeleteSession(w, r); err != nil { 76 + l.Error("failed to delete session", "err", err) 77 + } 78 + s.pages.HxRedirect(w, "/login") 79 + return 80 + } 81 + 82 + s.pages.HxRefresh(w) 83 + }
+57 -7
appview/state/login.go
··· 5 "net/http" 6 "strings" 7 8 "tangled.org/core/appview/pages" 9 ) 10 ··· 16 case http.MethodGet: 17 returnURL := r.URL.Query().Get("return_url") 18 errorCode := r.URL.Query().Get("error") 19 s.pages.Login(w, pages.LoginParams{ 20 - ReturnUrl: returnURL, 21 - ErrorCode: errorCode, 22 }) 23 case http.MethodPost: 24 handle := r.FormValue("handle") 25 26 // when users copy their handle from bsky.app, it tends to have these characters around it: 27 // ··· 61 return 62 } 63 64 redirectURL, err := s.oauth.ClientApp.StartAuthFlow(r.Context(), handle) 65 if err != nil { 66 l.Error("failed to start auth", "err", err) ··· 79 func (s *State) Logout(w http.ResponseWriter, r *http.Request) { 80 l := s.logger.With("handler", "Logout") 81 82 - err := s.oauth.DeleteSession(w, r) 83 - if err != nil { 84 - l.Error("failed to logout", "err", err) 85 - } else { 86 - l.Info("logged out successfully") 87 } 88 89 s.pages.HxRedirect(w, "/login") 90 }
··· 5 "net/http" 6 "strings" 7 8 + "tangled.org/core/appview/oauth" 9 "tangled.org/core/appview/pages" 10 ) 11 ··· 15 case http.MethodGet: 16 returnURL := r.URL.Query().Get("return_url") 17 errorCode := r.URL.Query().Get("error") 18 + addAccount := r.URL.Query().Get("mode") == "add_account" 19 + 20 + user := s.oauth.GetMultiAccountUser(r) 21 + if user == nil { 22 + registry := s.oauth.GetAccounts(r) 23 + if len(registry.Accounts) > 0 { 24 + user = &oauth.MultiAccountUser{ 25 + Active: nil, 26 + Accounts: registry.Accounts, 27 + } 28 + } 29 + } 30 s.pages.Login(w, pages.LoginParams{ 31 + ReturnUrl: returnURL, 32 + ErrorCode: errorCode, 33 + AddAccount: addAccount, 34 + LoggedInUser: user, 35 }) 36 case http.MethodPost: 37 handle := r.FormValue("handle") 38 + returnURL := r.FormValue("return_url") 39 + addAccount := r.FormValue("add_account") == "true" 40 41 // when users copy their handle from bsky.app, it tends to have these characters around it: 42 // ··· 44 return 45 } 46 47 + if err := s.oauth.SetAuthReturn(w, r, returnURL, addAccount); err != nil { 48 + l.Error("failed to set auth return", "err", err) 49 + } 50 + 51 redirectURL, err := s.oauth.ClientApp.StartAuthFlow(r.Context(), handle) 52 if err != nil { 53 l.Error("failed to start auth", "err", err) ··· 58 func (s *State) Logout(w http.ResponseWriter, r *http.Request) { 59 l := s.logger.With("handler", "Logout") 60 61 + currentUser := s.oauth.GetMultiAccountUser(r) 62 + if currentUser == nil || currentUser.Active == nil { 63 + s.pages.HxRedirect(w, "/login") 64 + return 65 } 66 67 + currentDid := currentUser.Active.Did 68 + 69 + var remainingAccounts []string 70 + for _, acc := range currentUser.Accounts { 71 + if acc.Did != currentDid { 72 + remainingAccounts = append(remainingAccounts, acc.Did) 73 + } 74 + } 75 + 76 + if err := s.oauth.RemoveAccount(w, r, currentDid); err != nil { 77 + l.Error("failed to remove account from registry", "err", err) 78 + } 79 + 80 + if err := s.oauth.DeleteSession(w, r); err != nil { 81 + l.Error("failed to delete session", "err", err) 82 + } 83 + 84 + if len(remainingAccounts) > 0 { 85 + nextDid := remainingAccounts[0] 86 + if err := s.oauth.SwitchAccount(w, r, nextDid); err != nil { 87 + l.Error("failed to switch to next account", "err", err) 88 + s.pages.HxRedirect(w, "/login") 89 + return 90 + } 91 + l.Info("switched to next account after logout", "did", nextDid) 92 + s.pages.HxRefresh(w) 93 + return 94 + } 95 + 96 + l.Info("logged out last account") 97 s.pages.HxRedirect(w, "/login") 98 }
+3
appview/state/router.go
··· 130 r.Post("/login", s.Login) 131 r.Post("/logout", s.Logout) 132 133 r.Route("/repo", func(r chi.Router) { 134 r.Route("/new", func(r chi.Router) { 135 r.Use(middleware.AuthMiddleware(s.oauth))
··· 130 r.Post("/login", s.Login) 131 r.Post("/logout", s.Logout) 132 133 + r.Post("/account/switch", s.SwitchAccount) 134 + r.With(middleware.AuthMiddleware(s.oauth)).Delete("/account/{did}", s.RemoveAccount) 135 + 136 r.Route("/repo", func(r chi.Router) { 137 r.Route("/new", func(r chi.Router) { 138 r.Use(middleware.AuthMiddleware(s.oauth))