A vibe coded tangled fork which supports pijul.
1package oauth
2
3import (
4 "encoding/json"
5 "errors"
6 "net/http"
7 "time"
8)
9
10const MaxAccounts = 20
11
12var ErrMaxAccountsReached = errors.New("maximum number of linked accounts reached")
13
14type AccountInfo struct {
15 Did string `json:"did"`
16 Handle string `json:"handle"`
17 SessionId string `json:"session_id"`
18 AddedAt int64 `json:"added_at"`
19}
20
21type AccountRegistry struct {
22 Accounts []AccountInfo `json:"accounts"`
23}
24
25type MultiAccountUser struct {
26 Active *User
27 Accounts []AccountInfo
28}
29
30func (m *MultiAccountUser) Did() string {
31 if m.Active == nil {
32 return ""
33 }
34 return m.Active.Did
35}
36
37func (o *OAuth) GetAccounts(r *http.Request) *AccountRegistry {
38 session, err := o.SessStore.Get(r, AccountsName)
39 if err != nil || session.IsNew {
40 return &AccountRegistry{Accounts: []AccountInfo{}}
41 }
42
43 data, ok := session.Values["accounts"].(string)
44 if !ok {
45 return &AccountRegistry{Accounts: []AccountInfo{}}
46 }
47
48 var registry AccountRegistry
49 if err := json.Unmarshal([]byte(data), ®istry); err != nil {
50 return &AccountRegistry{Accounts: []AccountInfo{}}
51 }
52
53 return ®istry
54}
55
56func (o *OAuth) SaveAccounts(w http.ResponseWriter, r *http.Request, registry *AccountRegistry) error {
57 session, err := o.SessStore.Get(r, AccountsName)
58 if err != nil {
59 o.Logger.Warn("failed to decode existing accounts cookie, will create new", "err", err)
60 }
61
62 data, err := json.Marshal(registry)
63 if err != nil {
64 return err
65 }
66
67 session.Values["accounts"] = string(data)
68 session.Options.MaxAge = 60 * 60 * 24 * 365
69 session.Options.HttpOnly = true
70 session.Options.Secure = !o.Config.Core.Dev
71 session.Options.SameSite = http.SameSiteLaxMode
72
73 return session.Save(r, w)
74}
75
76func (r *AccountRegistry) AddAccount(did, handle, sessionId string) error {
77 for i, acc := range r.Accounts {
78 if acc.Did == did {
79 r.Accounts[i].SessionId = sessionId
80 r.Accounts[i].Handle = handle
81 return nil
82 }
83 }
84
85 if len(r.Accounts) >= MaxAccounts {
86 return ErrMaxAccountsReached
87 }
88
89 r.Accounts = append(r.Accounts, AccountInfo{
90 Did: did,
91 Handle: handle,
92 SessionId: sessionId,
93 AddedAt: time.Now().Unix(),
94 })
95 return nil
96}
97
98func (r *AccountRegistry) RemoveAccount(did string) {
99 filtered := make([]AccountInfo, 0, len(r.Accounts))
100 for _, acc := range r.Accounts {
101 if acc.Did != did {
102 filtered = append(filtered, acc)
103 }
104 }
105 r.Accounts = filtered
106}
107
108func (r *AccountRegistry) FindAccount(did string) *AccountInfo {
109 for i := range r.Accounts {
110 if r.Accounts[i].Did == did {
111 return &r.Accounts[i]
112 }
113 }
114 return nil
115}
116
117func (r *AccountRegistry) OtherAccounts(activeDid string) []AccountInfo {
118 result := make([]AccountInfo, 0, len(r.Accounts))
119 for _, acc := range r.Accounts {
120 if acc.Did != activeDid {
121 result = append(result, acc)
122 }
123 }
124 return result
125}
126
127func (o *OAuth) GetMultiAccountUser(r *http.Request) *MultiAccountUser {
128 user := o.GetUser(r)
129 if user == nil {
130 return nil
131 }
132
133 registry := o.GetAccounts(r)
134 return &MultiAccountUser{
135 Active: user,
136 Accounts: registry.Accounts,
137 }
138}
139
140type AuthReturnInfo struct {
141 ReturnURL string
142 AddAccount bool
143}
144
145func (o *OAuth) SetAuthReturn(w http.ResponseWriter, r *http.Request, returnURL string, addAccount bool) error {
146 session, err := o.SessStore.Get(r, AuthReturnName)
147 if err != nil {
148 return err
149 }
150
151 session.Values[AuthReturnURL] = returnURL
152 session.Values[AuthAddAccount] = addAccount
153 session.Options.MaxAge = 60 * 30
154 session.Options.HttpOnly = true
155 session.Options.Secure = !o.Config.Core.Dev
156 session.Options.SameSite = http.SameSiteLaxMode
157
158 return session.Save(r, w)
159}
160
161func (o *OAuth) GetAuthReturn(r *http.Request) *AuthReturnInfo {
162 session, err := o.SessStore.Get(r, AuthReturnName)
163 if err != nil || session.IsNew {
164 return &AuthReturnInfo{}
165 }
166
167 returnURL, _ := session.Values[AuthReturnURL].(string)
168 addAccount, _ := session.Values[AuthAddAccount].(bool)
169
170 return &AuthReturnInfo{
171 ReturnURL: returnURL,
172 AddAccount: addAccount,
173 }
174}
175
176func (o *OAuth) ClearAuthReturn(w http.ResponseWriter, r *http.Request) error {
177 session, err := o.SessStore.Get(r, AuthReturnName)
178 if err != nil {
179 return err
180 }
181
182 session.Options.MaxAge = -1
183 return session.Save(r, w)
184}