Monorepo for Tangled
at e949a86d926b049287187fad78915dbc05492253 119 lines 3.2 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 // when users copy their handle from bsky.app, it tends to have these characters around it: 43 // 44 // @nelind.dk: 45 // \u202a ensures that the handle is always rendered left to right and 46 // \u202c reverts that so the rest of the page renders however it should 47 handle = strings.TrimPrefix(handle, "\u202a") 48 handle = strings.TrimSuffix(handle, "\u202c") 49 50 // `@` is harmless 51 handle = strings.TrimPrefix(handle, "@") 52 53 // basic handle validation 54 if !strings.Contains(handle, ".") { 55 l.Error("invalid handle format", "raw", handle) 56 s.pages.Notice( 57 w, 58 "login-msg", 59 fmt.Sprintf("\"%s\" is an invalid handle. Did you mean %s.bsky.social or %s.tngl.sh?", handle, handle, handle), 60 ) 61 return 62 } 63 64 if err := s.oauth.SetAuthReturn(w, r, returnURL, addAccount); err != nil { 65 l.Error("failed to set auth return", "err", err) 66 } 67 68 redirectURL, err := s.oauth.ClientApp.StartAuthFlow(r.Context(), handle) 69 if err != nil { 70 l.Error("failed to start auth", "err", err) 71 http.Error(w, err.Error(), http.StatusInternalServerError) 72 return 73 } 74 75 s.pages.HxRedirect(w, redirectURL) 76 } 77} 78 79func (s *State) Logout(w http.ResponseWriter, r *http.Request) { 80 l := s.logger.With("handler", "Logout") 81 82 currentUser := s.oauth.GetMultiAccountUser(r) 83 if currentUser == nil || currentUser.Active == nil { 84 s.pages.HxRedirect(w, "/login") 85 return 86 } 87 88 currentDid := currentUser.Active.Did 89 90 var remainingAccounts []string 91 for _, acc := range currentUser.Accounts { 92 if acc.Did != currentDid { 93 remainingAccounts = append(remainingAccounts, acc.Did) 94 } 95 } 96 97 if err := s.oauth.RemoveAccount(w, r, currentDid); err != nil { 98 l.Error("failed to remove account from registry", "err", err) 99 } 100 101 if err := s.oauth.DeleteSession(w, r); err != nil { 102 l.Error("failed to delete session", "err", err) 103 } 104 105 if len(remainingAccounts) > 0 { 106 nextDid := remainingAccounts[0] 107 if err := s.oauth.SwitchAccount(w, r, nextDid); err != nil { 108 l.Error("failed to switch to next account", "err", err) 109 s.pages.HxRedirect(w, "/login") 110 return 111 } 112 l.Info("switched to next account after logout", "did", nextDid) 113 s.pages.HxRefresh(w) 114 return 115 } 116 117 l.Info("logged out last account") 118 s.pages.HxRedirect(w, "/login") 119}