+83
appview/state/accounts.go
+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
+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
···
15
16
case http.MethodGet:
16
17
returnURL := r.URL.Query().Get("return_url")
17
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
+
}
18
31
s.pages.Login(w, pages.LoginParams{
19
-
ReturnUrl: returnURL,
20
-
ErrorCode: errorCode,
32
+
ReturnUrl: returnURL,
33
+
ErrorCode: errorCode,
34
+
AddAccount: addAccount,
35
+
LoggedInUser: user,
21
36
})
22
37
case http.MethodPost:
23
38
handle := r.FormValue("handle")
39
+
returnURL := r.FormValue("return_url")
40
+
addAccount := r.FormValue("add_account") == "true"
24
41
25
42
// when users copy their handle from bsky.app, it tends to have these characters around it:
26
43
//
···
44
61
return
45
62
}
46
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
+
47
68
redirectURL, err := s.oauth.ClientApp.StartAuthFlow(r.Context(), handle)
48
69
if err != nil {
49
70
l.Error("failed to start auth", "err", err)
···
58
79
func (s *State) Logout(w http.ResponseWriter, r *http.Request) {
59
80
l := s.logger.With("handler", "Logout")
60
81
61
-
err := s.oauth.DeleteSession(w, r)
62
-
if err != nil {
63
-
l.Error("failed to logout", "err", err)
64
-
} else {
65
-
l.Info("logged out successfully")
82
+
currentUser := s.oauth.GetMultiAccountUser(r)
83
+
if currentUser == nil || currentUser.Active == nil {
84
+
s.pages.HxRedirect(w, "/login")
85
+
return
66
86
}
67
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")
68
118
s.pages.HxRedirect(w, "/login")
69
119
}
+5
appview/state/router.go
+5
appview/state/router.go
···
132
132
r.Post("/login", s.Login)
133
133
r.Post("/logout", s.Logout)
134
134
135
+
r.With(middleware.AuthMiddleware(s.oauth)).Route("/account", func(r chi.Router) {
136
+
r.Post("/switch", s.SwitchAccount)
137
+
r.Delete("/{did}", s.RemoveAccount)
138
+
})
139
+
135
140
r.Route("/repo", func(r chi.Router) {
136
141
r.Route("/new", func(r chi.Router) {
137
142
r.Use(middleware.AuthMiddleware(s.oauth))