forked from
tangled.org/core
Monorepo for Tangled — https://tangled.org
1package auth
2
3import (
4 "context"
5 "fmt"
6 "net/http"
7 "time"
8
9 comatproto "github.com/bluesky-social/indigo/api/atproto"
10 "github.com/bluesky-social/indigo/atproto/identity"
11 "github.com/bluesky-social/indigo/xrpc"
12 "github.com/gorilla/sessions"
13 "github.com/sotangled/tangled/appview"
14)
15
16const ExpiryDuration = 15 * time.Minute
17
18type Auth struct {
19 Store *sessions.CookieStore
20}
21
22type AtSessionCreate struct {
23 comatproto.ServerCreateSession_Output
24 PDSEndpoint string
25}
26
27type AtSessionRefresh struct {
28 comatproto.ServerRefreshSession_Output
29 PDSEndpoint string
30}
31
32func Make(secret string) (*Auth, error) {
33 store := sessions.NewCookieStore([]byte(secret))
34 return &Auth{store}, nil
35}
36
37func (a *Auth) CreateInitialSession(ctx context.Context, resolved *identity.Identity, appPassword string) (*comatproto.ServerCreateSession_Output, error) {
38
39 pdsUrl := resolved.PDSEndpoint()
40 client := xrpc.Client{
41 Host: pdsUrl,
42 }
43
44 atSession, err := comatproto.ServerCreateSession(ctx, &client, &comatproto.ServerCreateSession_Input{
45 Identifier: resolved.DID.String(),
46 Password: appPassword,
47 })
48 if err != nil {
49 return nil, fmt.Errorf("invalid app password")
50 }
51
52 return atSession, nil
53}
54
55// Sessionish is an interface that provides access to the common fields of both types.
56type Sessionish interface {
57 GetAccessJwt() string
58 GetActive() *bool
59 GetDid() string
60 GetDidDoc() *interface{}
61 GetHandle() string
62 GetRefreshJwt() string
63 GetStatus() *string
64}
65
66type ClientSessionish struct {
67 sessions.Session
68}
69
70func (c *ClientSessionish) GetAccessJwt() string {
71 return c.Values[appview.SessionAccessJwt].(string)
72}
73
74func (c *ClientSessionish) GetActive() *bool {
75 return c.Values[appview.SessionAuthenticated].(*bool)
76}
77
78func (c *ClientSessionish) GetDid() string {
79 return c.Values[appview.SessionDid].(string)
80}
81
82func (c *ClientSessionish) GetDidDoc() *interface{} {
83 return nil
84}
85
86func (c *ClientSessionish) GetHandle() string {
87 return c.Values[appview.SessionHandle].(string)
88}
89
90func (c *ClientSessionish) GetRefreshJwt() string {
91 return c.Values[appview.SessionRefreshJwt].(string)
92}
93
94func (c *ClientSessionish) GetStatus() *string {
95 return nil
96}
97
98// Create a wrapper type for ServerRefreshSession_Output
99type RefreshSessionWrapper struct {
100 *comatproto.ServerRefreshSession_Output
101}
102
103func (s *RefreshSessionWrapper) GetAccessJwt() string {
104 return s.AccessJwt
105}
106
107func (s *RefreshSessionWrapper) GetActive() *bool {
108 return s.Active
109}
110
111func (s *RefreshSessionWrapper) GetDid() string {
112 return s.Did
113}
114
115func (s *RefreshSessionWrapper) GetDidDoc() *interface{} {
116 return s.DidDoc
117}
118
119func (s *RefreshSessionWrapper) GetHandle() string {
120 return s.Handle
121}
122
123func (s *RefreshSessionWrapper) GetRefreshJwt() string {
124 return s.RefreshJwt
125}
126
127func (s *RefreshSessionWrapper) GetStatus() *string {
128 return s.Status
129}
130
131// Create a wrapper type for ServerRefreshSession_Output
132type CreateSessionWrapper struct {
133 *comatproto.ServerCreateSession_Output
134}
135
136func (s *CreateSessionWrapper) GetAccessJwt() string {
137 return s.AccessJwt
138}
139
140func (s *CreateSessionWrapper) GetActive() *bool {
141 return s.Active
142}
143
144func (s *CreateSessionWrapper) GetDid() string {
145 return s.Did
146}
147
148func (s *CreateSessionWrapper) GetDidDoc() *interface{} {
149 return s.DidDoc
150}
151
152func (s *CreateSessionWrapper) GetHandle() string {
153 return s.Handle
154}
155
156func (s *CreateSessionWrapper) GetRefreshJwt() string {
157 return s.RefreshJwt
158}
159
160func (s *CreateSessionWrapper) GetStatus() *string {
161 return s.Status
162}
163
164func (a *Auth) ClearSession(r *http.Request, w http.ResponseWriter) error {
165 clientSession, _ := a.Store.Get(r, appview.SessionName)
166 clientSession.Options.MaxAge = -1
167 return clientSession.Save(r, w)
168}
169
170func (a *Auth) StoreSession(r *http.Request, w http.ResponseWriter, atSessionish Sessionish, pdsEndpoint string) error {
171 clientSession, _ := a.Store.Get(r, appview.SessionName)
172 clientSession.Values[appview.SessionHandle] = atSessionish.GetHandle()
173 clientSession.Values[appview.SessionDid] = atSessionish.GetDid()
174 clientSession.Values[appview.SessionPds] = pdsEndpoint
175 clientSession.Values[appview.SessionAccessJwt] = atSessionish.GetAccessJwt()
176 clientSession.Values[appview.SessionRefreshJwt] = atSessionish.GetRefreshJwt()
177 clientSession.Values[appview.SessionExpiry] = time.Now().Add(ExpiryDuration).Format(time.RFC3339)
178 clientSession.Values[appview.SessionAuthenticated] = true
179 return clientSession.Save(r, w)
180}
181
182func (a *Auth) RefreshSession(ctx context.Context, r *http.Request, w http.ResponseWriter, atSessionish Sessionish, pdsEndpoint string) error {
183 client := xrpc.Client{
184 Host: pdsEndpoint,
185 Auth: &xrpc.AuthInfo{
186 Did: atSessionish.GetDid(),
187 AccessJwt: atSessionish.GetRefreshJwt(),
188 RefreshJwt: atSessionish.GetRefreshJwt(),
189 },
190 }
191
192 atSession, err := comatproto.ServerRefreshSession(ctx, &client)
193 if err != nil {
194 return fmt.Errorf("failed to refresh session: %w", err)
195 }
196
197 newAtSessionish := &RefreshSessionWrapper{atSession}
198 err = a.StoreSession(r, w, newAtSessionish, pdsEndpoint)
199 if err != nil {
200 return fmt.Errorf("failed to store refreshed session: %w", err)
201 }
202
203 return nil
204}
205
206func (a *Auth) AuthorizedClient(r *http.Request) (*xrpc.Client, error) {
207 clientSession, err := a.Store.Get(r, "appview-session")
208
209 if err != nil || clientSession.IsNew {
210 return nil, err
211 }
212
213 did := clientSession.Values["did"].(string)
214 pdsUrl := clientSession.Values["pds"].(string)
215 accessJwt := clientSession.Values["accessJwt"].(string)
216 refreshJwt := clientSession.Values["refreshJwt"].(string)
217
218 client := &xrpc.Client{
219 Host: pdsUrl,
220 Auth: &xrpc.AuthInfo{
221 AccessJwt: accessJwt,
222 RefreshJwt: refreshJwt,
223 Did: did,
224 },
225 }
226
227 return client, nil
228}
229
230func (a *Auth) GetSession(r *http.Request) (*sessions.Session, error) {
231 return a.Store.Get(r, appview.SessionName)
232}
233
234func (a *Auth) GetDid(r *http.Request) string {
235 clientSession, err := a.Store.Get(r, appview.SessionName)
236 if err != nil || clientSession.IsNew {
237 return ""
238 }
239
240 return clientSession.Values[appview.SessionDid].(string)
241}
242
243func (a *Auth) GetHandle(r *http.Request) string {
244 clientSession, err := a.Store.Get(r, appview.SessionName)
245 if err != nil || clientSession.IsNew {
246 return ""
247 }
248
249 return clientSession.Values[appview.SessionHandle].(string)
250}
251
252type User struct {
253 Handle string
254 Did string
255 Pds string
256}
257
258func (a *Auth) GetUser(r *http.Request) *User {
259 clientSession, err := a.Store.Get(r, appview.SessionName)
260
261 if err != nil || clientSession.IsNew {
262 return nil
263 }
264
265 return &User{
266 Handle: clientSession.Values[appview.SessionHandle].(string),
267 Did: clientSession.Values[appview.SessionDid].(string),
268 Pds: clientSession.Values[appview.SessionPds].(string),
269 }
270}