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 5 "net/http" 6 6 "strings" 7 7 8 + "tangled.org/core/appview/oauth" 8 9 "tangled.org/core/appview/pages" 9 10 ) 10 11 ··· 16 15 case http.MethodGet: 17 16 returnURL := r.URL.Query().Get("return_url") 18 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 + } 19 30 s.pages.Login(w, pages.LoginParams{ 20 - ReturnUrl: returnURL, 21 - ErrorCode: errorCode, 31 + ReturnUrl: returnURL, 32 + ErrorCode: errorCode, 33 + AddAccount: addAccount, 34 + LoggedInUser: user, 22 35 }) 23 36 case http.MethodPost: 24 37 handle := r.FormValue("handle") 38 + returnURL := r.FormValue("return_url") 39 + addAccount := r.FormValue("add_account") == "true" 25 40 26 41 // when users copy their handle from bsky.app, it tends to have these characters around it: 27 42 // ··· 61 44 return 62 45 } 63 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 + 64 51 redirectURL, err := s.oauth.ClientApp.StartAuthFlow(r.Context(), handle) 65 52 if err != nil { 66 53 l.Error("failed to start auth", "err", err) ··· 79 58 func (s *State) Logout(w http.ResponseWriter, r *http.Request) { 80 59 l := s.logger.With("handler", "Logout") 81 60 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") 61 + currentUser := s.oauth.GetMultiAccountUser(r) 62 + if currentUser == nil || currentUser.Active == nil { 63 + s.pages.HxRedirect(w, "/login") 64 + return 87 65 } 88 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") 89 97 s.pages.HxRedirect(w, "/login") 90 98 }
+3
appview/state/router.go
··· 130 130 r.Post("/login", s.Login) 131 131 r.Post("/logout", s.Logout) 132 132 133 + r.Post("/account/switch", s.SwitchAccount) 134 + r.With(middleware.AuthMiddleware(s.oauth)).Delete("/account/{did}", s.RemoveAccount) 135 + 133 136 r.Route("/repo", func(r chi.Router) { 134 137 r.Route("/new", func(r chi.Router) { 135 138 r.Use(middleware.AuthMiddleware(s.oauth))