tangled
alpha
login
or
join now
lewis.moe
/
tangled-core
1
fork
atom
Monorepo for Tangled
1
fork
atom
overview
issues
1
pulls
pipelines
state: add account switch/remove endpoints and login flow
lewis.moe
1 month ago
4e610649
d7d0b7bb
+143
-7
3 changed files
expand all
collapse all
unified
split
appview
state
accounts.go
login.go
router.go
+83
appview/state/accounts.go
···
1
1
+
package state
2
2
+
3
3
+
import (
4
4
+
"net/http"
5
5
+
6
6
+
"github.com/go-chi/chi/v5"
7
7
+
)
8
8
+
9
9
+
func (s *State) SwitchAccount(w http.ResponseWriter, r *http.Request) {
10
10
+
l := s.logger.With("handler", "SwitchAccount")
11
11
+
12
12
+
if err := r.ParseForm(); err != nil {
13
13
+
l.Error("failed to parse form", "err", err)
14
14
+
http.Error(w, "invalid request", http.StatusBadRequest)
15
15
+
return
16
16
+
}
17
17
+
18
18
+
did := r.FormValue("did")
19
19
+
if did == "" {
20
20
+
http.Error(w, "missing did", http.StatusBadRequest)
21
21
+
return
22
22
+
}
23
23
+
24
24
+
if err := s.oauth.SwitchAccount(w, r, did); err != nil {
25
25
+
l.Error("failed to switch account", "err", err)
26
26
+
s.pages.HxRedirect(w, "/login?error=session")
27
27
+
return
28
28
+
}
29
29
+
30
30
+
l.Info("switched account", "did", did)
31
31
+
s.pages.HxRedirect(w, "/")
32
32
+
}
33
33
+
34
34
+
func (s *State) RemoveAccount(w http.ResponseWriter, r *http.Request) {
35
35
+
l := s.logger.With("handler", "RemoveAccount")
36
36
+
37
37
+
did := chi.URLParam(r, "did")
38
38
+
if did == "" {
39
39
+
http.Error(w, "missing did", http.StatusBadRequest)
40
40
+
return
41
41
+
}
42
42
+
43
43
+
currentUser := s.oauth.GetMultiAccountUser(r)
44
44
+
isCurrentAccount := currentUser != nil && currentUser.Active.Did == did
45
45
+
46
46
+
var remainingAccounts []string
47
47
+
if currentUser != nil {
48
48
+
for _, acc := range currentUser.Accounts {
49
49
+
if acc.Did != did {
50
50
+
remainingAccounts = append(remainingAccounts, acc.Did)
51
51
+
}
52
52
+
}
53
53
+
}
54
54
+
55
55
+
if err := s.oauth.RemoveAccount(w, r, did); err != nil {
56
56
+
l.Error("failed to remove account", "err", err)
57
57
+
http.Error(w, "failed to remove account", http.StatusInternalServerError)
58
58
+
return
59
59
+
}
60
60
+
61
61
+
l.Info("removed account", "did", did)
62
62
+
63
63
+
if isCurrentAccount {
64
64
+
if len(remainingAccounts) > 0 {
65
65
+
nextDid := remainingAccounts[0]
66
66
+
if err := s.oauth.SwitchAccount(w, r, nextDid); err != nil {
67
67
+
l.Error("failed to switch to next account", "err", err)
68
68
+
s.pages.HxRedirect(w, "/login")
69
69
+
return
70
70
+
}
71
71
+
s.pages.HxRefresh(w)
72
72
+
return
73
73
+
}
74
74
+
75
75
+
if err := s.oauth.DeleteSession(w, r); err != nil {
76
76
+
l.Error("failed to delete session", "err", err)
77
77
+
}
78
78
+
s.pages.HxRedirect(w, "/login")
79
79
+
return
80
80
+
}
81
81
+
82
82
+
s.pages.HxRefresh(w)
83
83
+
}
+57
-7
appview/state/login.go
···
5
5
"net/http"
6
6
"strings"
7
7
8
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
19
+
addAccount := r.URL.Query().Get("mode") == "add_account"
20
20
+
21
21
+
user := s.oauth.GetMultiAccountUser(r)
22
22
+
if user == nil {
23
23
+
registry := s.oauth.GetAccounts(r)
24
24
+
if len(registry.Accounts) > 0 {
25
25
+
user = &oauth.MultiAccountUser{
26
26
+
Active: nil,
27
27
+
Accounts: registry.Accounts,
28
28
+
}
29
29
+
}
30
30
+
}
18
31
s.pages.Login(w, pages.LoginParams{
19
19
-
ReturnUrl: returnURL,
20
20
-
ErrorCode: errorCode,
32
32
+
ReturnUrl: returnURL,
33
33
+
ErrorCode: errorCode,
34
34
+
AddAccount: addAccount,
35
35
+
LoggedInUser: user,
21
36
})
22
37
case http.MethodPost:
23
38
handle := r.FormValue("handle")
39
39
+
returnURL := r.FormValue("return_url")
40
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
64
+
if err := s.oauth.SetAuthReturn(w, r, returnURL, addAccount); err != nil {
65
65
+
l.Error("failed to set auth return", "err", err)
66
66
+
}
67
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
61
-
err := s.oauth.DeleteSession(w, r)
62
62
-
if err != nil {
63
63
-
l.Error("failed to logout", "err", err)
64
64
-
} else {
65
65
-
l.Info("logged out successfully")
82
82
+
currentUser := s.oauth.GetMultiAccountUser(r)
83
83
+
if currentUser == nil || currentUser.Active == nil {
84
84
+
s.pages.HxRedirect(w, "/login")
85
85
+
return
66
86
}
67
87
88
88
+
currentDid := currentUser.Active.Did
89
89
+
90
90
+
var remainingAccounts []string
91
91
+
for _, acc := range currentUser.Accounts {
92
92
+
if acc.Did != currentDid {
93
93
+
remainingAccounts = append(remainingAccounts, acc.Did)
94
94
+
}
95
95
+
}
96
96
+
97
97
+
if err := s.oauth.RemoveAccount(w, r, currentDid); err != nil {
98
98
+
l.Error("failed to remove account from registry", "err", err)
99
99
+
}
100
100
+
101
101
+
if err := s.oauth.DeleteSession(w, r); err != nil {
102
102
+
l.Error("failed to delete session", "err", err)
103
103
+
}
104
104
+
105
105
+
if len(remainingAccounts) > 0 {
106
106
+
nextDid := remainingAccounts[0]
107
107
+
if err := s.oauth.SwitchAccount(w, r, nextDid); err != nil {
108
108
+
l.Error("failed to switch to next account", "err", err)
109
109
+
s.pages.HxRedirect(w, "/login")
110
110
+
return
111
111
+
}
112
112
+
l.Info("switched to next account after logout", "did", nextDid)
113
113
+
s.pages.HxRefresh(w)
114
114
+
return
115
115
+
}
116
116
+
117
117
+
l.Info("logged out last account")
68
118
s.pages.HxRedirect(w, "/login")
69
119
}
+3
appview/state/router.go
···
130
130
r.Post("/login", s.Login)
131
131
r.Post("/logout", s.Logout)
132
132
133
133
+
r.Post("/account/switch", s.SwitchAccount)
134
134
+
r.With(middleware.AuthMiddleware(s.oauth)).Delete("/account/{did}", s.RemoveAccount)
135
135
+
133
136
r.Route("/repo", func(r chi.Router) {
134
137
r.Route("/new", func(r chi.Router) {
135
138
r.Use(middleware.AuthMiddleware(s.oauth))