Monorepo for Tangled
at f887743001ec3f0f95f90c1a4d807cb37d9132d7 126 lines 3.4 kB view raw
1package state 2 3import ( 4 "fmt" 5 "net/http" 6 "strings" 7 8 "tangled.org/core/appview/oauth" 9 "tangled.org/core/appview/pages" 10) 11 12func (s *State) Login(w http.ResponseWriter, r *http.Request) { 13 l := s.logger.With("handler", "Login") 14 15 switch r.Method { 16 case http.MethodGet: 17 returnURL := r.URL.Query().Get("return_url") 18 errorCode := r.URL.Query().Get("error") 19 addAccount := r.URL.Query().Get("mode") == "add_account" 20 21 user := s.oauth.GetMultiAccountUser(r) 22 if user == nil { 23 registry := s.oauth.GetAccounts(r) 24 if len(registry.Accounts) > 0 { 25 user = &oauth.MultiAccountUser{ 26 Active: nil, 27 Accounts: registry.Accounts, 28 } 29 } 30 } 31 s.pages.Login(w, pages.LoginParams{ 32 ReturnUrl: returnURL, 33 ErrorCode: errorCode, 34 AddAccount: addAccount, 35 LoggedInUser: user, 36 }) 37 case http.MethodPost: 38 handle := r.FormValue("handle") 39 returnURL := r.FormValue("return_url") 40 addAccount := r.FormValue("add_account") == "true" 41 42 // remove spaces around the handle, handles can't have spaces around them 43 handle = strings.TrimSpace(handle) 44 45 // when users copy their handle from bsky.app, it tends to have these characters around it: 46 // 47 // @nelind.dk: 48 // \u202a ensures that the handle is always rendered left to right and 49 // \u202c reverts that so the rest of the page renders however it should 50 handle = strings.TrimPrefix(handle, "\u202a") 51 handle = strings.TrimSuffix(handle, "\u202c") 52 53 // `@` is harmless 54 handle = strings.TrimPrefix(handle, "@") 55 56 // basic handle validation 57 if !strings.Contains(handle, ".") { 58 l.Error("invalid handle format", "raw", handle) 59 s.pages.Notice( 60 w, 61 "login-msg", 62 fmt.Sprintf("\"%s\" is an invalid handle. Did you mean %s.bsky.social or %s.tngl.sh?", handle, handle, handle), 63 ) 64 return 65 } 66 67 if err := s.oauth.SetAuthReturn(w, r, returnURL, addAccount); err != nil { 68 l.Error("failed to set auth return", "err", err) 69 } 70 71 redirectURL, err := s.oauth.ClientApp.StartAuthFlow(r.Context(), handle) 72 if err != nil { 73 l.Error("failed to start auth", "err", err) 74 s.pages.Notice( 75 w, 76 "login-msg", 77 fmt.Sprintf("Failed to start auth flow: %v", err), 78 ) 79 return 80 } 81 82 s.pages.HxRedirect(w, redirectURL) 83 } 84} 85 86func (s *State) Logout(w http.ResponseWriter, r *http.Request) { 87 l := s.logger.With("handler", "Logout") 88 89 currentUser := s.oauth.GetMultiAccountUser(r) 90 if currentUser == nil || currentUser.Active == nil { 91 s.pages.HxRedirect(w, "/login") 92 return 93 } 94 95 currentDid := currentUser.Active.Did 96 97 var remainingAccounts []string 98 for _, acc := range currentUser.Accounts { 99 if acc.Did != currentDid { 100 remainingAccounts = append(remainingAccounts, acc.Did) 101 } 102 } 103 104 if err := s.oauth.RemoveAccount(w, r, currentDid); err != nil { 105 l.Error("failed to remove account from registry", "err", err) 106 } 107 108 if err := s.oauth.DeleteSession(w, r); err != nil { 109 l.Error("failed to delete session", "err", err) 110 } 111 112 if len(remainingAccounts) > 0 { 113 nextDid := remainingAccounts[0] 114 if err := s.oauth.SwitchAccount(w, r, nextDid); err != nil { 115 l.Error("failed to switch to next account", "err", err) 116 s.pages.HxRedirect(w, "/login") 117 return 118 } 119 l.Info("switched to next account after logout", "did", nextDid) 120 s.pages.HxRefresh(w) 121 return 122 } 123 124 l.Info("logged out last account") 125 s.pages.HxRedirect(w, "/login") 126}