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 return m.Active.Did
32}
33
34func (o *OAuth) GetAccounts(r *http.Request) *AccountRegistry {
35 session, err := o.SessStore.Get(r, AccountsName)
36 if err != nil || session.IsNew {
37 return &AccountRegistry{Accounts: []AccountInfo{}}
38 }
39
40 data, ok := session.Values["accounts"].(string)
41 if !ok {
42 return &AccountRegistry{Accounts: []AccountInfo{}}
43 }
44
45 var registry AccountRegistry
46 if err := json.Unmarshal([]byte(data), ®istry); err != nil {
47 return &AccountRegistry{Accounts: []AccountInfo{}}
48 }
49
50 return ®istry
51}
52
53func (o *OAuth) saveAccounts(w http.ResponseWriter, r *http.Request, registry *AccountRegistry) error {
54 session, err := o.SessStore.Get(r, AccountsName)
55 if err != nil {
56 return err
57 }
58
59 data, err := json.Marshal(registry)
60 if err != nil {
61 return err
62 }
63
64 session.Values["accounts"] = string(data)
65 session.Options.MaxAge = 60 * 60 * 24 * 365
66 session.Options.HttpOnly = true
67 session.Options.Secure = !o.Config.Core.Dev
68 session.Options.SameSite = http.SameSiteLaxMode
69
70 return session.Save(r, w)
71}
72
73func (r *AccountRegistry) AddAccount(did, handle, sessionId string) error {
74 for i, acc := range r.Accounts {
75 if acc.Did == did {
76 r.Accounts[i].SessionId = sessionId
77 r.Accounts[i].Handle = handle
78 return nil
79 }
80 }
81
82 if len(r.Accounts) >= MaxAccounts {
83 return ErrMaxAccountsReached
84 }
85
86 r.Accounts = append(r.Accounts, AccountInfo{
87 Did: did,
88 Handle: handle,
89 SessionId: sessionId,
90 AddedAt: time.Now().Unix(),
91 })
92 return nil
93}
94
95func (r *AccountRegistry) RemoveAccount(did string) {
96 filtered := make([]AccountInfo, 0, len(r.Accounts))
97 for _, acc := range r.Accounts {
98 if acc.Did != did {
99 filtered = append(filtered, acc)
100 }
101 }
102 r.Accounts = filtered
103}
104
105func (r *AccountRegistry) FindAccount(did string) *AccountInfo {
106 for i := range r.Accounts {
107 if r.Accounts[i].Did == did {
108 return &r.Accounts[i]
109 }
110 }
111 return nil
112}
113
114func (o *OAuth) GetMultiAccountUser(r *http.Request) *MultiAccountUser {
115 sess, err := o.ResumeSession(r)
116 if err != nil {
117 return nil
118 }
119
120 registry := o.GetAccounts(r)
121 return &MultiAccountUser{
122 Active: User{
123 Did: sess.Data.AccountDID.String(),
124 },
125 Accounts: registry.Accounts,
126 }
127}
128
129type AuthReturnInfo struct {
130 ReturnURL string
131}
132
133func (o *OAuth) SetAuthReturn(w http.ResponseWriter, r *http.Request, returnURL string) error {
134 session, err := o.SessStore.Get(r, AuthReturnName)
135 if err != nil {
136 return err
137 }
138
139 session.Values[AuthReturnURL] = returnURL
140 session.Options.MaxAge = 60 * 30
141 session.Options.HttpOnly = true
142 session.Options.Secure = !o.Config.Core.Dev
143 session.Options.SameSite = http.SameSiteLaxMode
144
145 return session.Save(r, w)
146}
147
148func (o *OAuth) GetAuthReturn(r *http.Request) *AuthReturnInfo {
149 session, err := o.SessStore.Get(r, AuthReturnName)
150 if err != nil || session.IsNew {
151 return &AuthReturnInfo{}
152 }
153
154 returnURL, _ := session.Values[AuthReturnURL].(string)
155
156 return &AuthReturnInfo{
157 ReturnURL: returnURL,
158 }
159}
160
161func (o *OAuth) ClearAuthReturn(w http.ResponseWriter, r *http.Request) error {
162 session, err := o.SessStore.Get(r, AuthReturnName)
163 if err != nil {
164 return err
165 }
166
167 session.Options.MaxAge = -1
168 return session.Save(r, w)
169}