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