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