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