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 "tangled.sh/tangled.sh/core/appview"
14)
15
16type Auth struct {
17 Store *sessions.CookieStore
18}
19
20type AtSessionCreate struct {
21 comatproto.ServerCreateSession_Output
22 PDSEndpoint string
23}
24
25type AtSessionRefresh struct {
26 comatproto.ServerRefreshSession_Output
27 PDSEndpoint string
28}
29
30func Make(secret string) (*Auth, error) {
31 store := sessions.NewCookieStore([]byte(secret))
32 return &Auth{store}, nil
33}
34
35func (a *Auth) CreateInitialSession(ctx context.Context, resolved *identity.Identity, appPassword string) (*comatproto.ServerCreateSession_Output, error) {
36
37 pdsUrl := resolved.PDSEndpoint()
38 client := xrpc.Client{
39 Host: pdsUrl,
40 }
41
42 atSession, err := comatproto.ServerCreateSession(ctx, &client, &comatproto.ServerCreateSession_Input{
43 Identifier: resolved.DID.String(),
44 Password: appPassword,
45 })
46 if err != nil {
47 return nil, fmt.Errorf("invalid app password")
48 }
49
50 return atSession, nil
51}
52
53// Sessionish is an interface that provides access to the common fields of both types.
54type Sessionish interface {
55 GetAccessJwt() string
56 GetActive() *bool
57 GetDid() string
58 GetDidDoc() *interface{}
59 GetHandle() string
60 GetRefreshJwt() string
61 GetStatus() *string
62}
63
64// Create a wrapper type for ServerRefreshSession_Output
65type RefreshSessionWrapper struct {
66 *comatproto.ServerRefreshSession_Output
67}
68
69func (s *RefreshSessionWrapper) GetAccessJwt() string {
70 return s.AccessJwt
71}
72
73func (s *RefreshSessionWrapper) GetActive() *bool {
74 return s.Active
75}
76
77func (s *RefreshSessionWrapper) GetDid() string {
78 return s.Did
79}
80
81func (s *RefreshSessionWrapper) GetDidDoc() *interface{} {
82 return s.DidDoc
83}
84
85func (s *RefreshSessionWrapper) GetHandle() string {
86 return s.Handle
87}
88
89func (s *RefreshSessionWrapper) GetRefreshJwt() string {
90 return s.RefreshJwt
91}
92
93func (s *RefreshSessionWrapper) GetStatus() *string {
94 return s.Status
95}
96
97// Create a wrapper type for ServerRefreshSession_Output
98type CreateSessionWrapper struct {
99 *comatproto.ServerCreateSession_Output
100}
101
102func (s *CreateSessionWrapper) GetAccessJwt() string {
103 return s.AccessJwt
104}
105
106func (s *CreateSessionWrapper) GetActive() *bool {
107 return s.Active
108}
109
110func (s *CreateSessionWrapper) GetDid() string {
111 return s.Did
112}
113
114func (s *CreateSessionWrapper) GetDidDoc() *interface{} {
115 return s.DidDoc
116}
117
118func (s *CreateSessionWrapper) GetHandle() string {
119 return s.Handle
120}
121
122func (s *CreateSessionWrapper) GetRefreshJwt() string {
123 return s.RefreshJwt
124}
125
126func (s *CreateSessionWrapper) GetStatus() *string {
127 return s.Status
128}
129
130func (a *Auth) ClearSession(r *http.Request, w http.ResponseWriter) error {
131 clientSession, err := a.Store.Get(r, appview.SessionName)
132 if err != nil {
133 return fmt.Errorf("invalid session", err)
134 }
135 if clientSession.IsNew {
136 return fmt.Errorf("invalid session")
137 }
138 clientSession.Options.MaxAge = -1
139 return clientSession.Save(r, w)
140}
141
142func (a *Auth) StoreSession(r *http.Request, w http.ResponseWriter, atSessionish Sessionish, pdsEndpoint string) error {
143 clientSession, _ := a.Store.Get(r, appview.SessionName)
144 clientSession.Values[appview.SessionHandle] = atSessionish.GetHandle()
145 clientSession.Values[appview.SessionDid] = atSessionish.GetDid()
146 clientSession.Values[appview.SessionPds] = pdsEndpoint
147 clientSession.Values[appview.SessionAccessJwt] = atSessionish.GetAccessJwt()
148 clientSession.Values[appview.SessionRefreshJwt] = atSessionish.GetRefreshJwt()
149 clientSession.Values[appview.SessionExpiry] = time.Now().Add(time.Minute * 15).Format(time.RFC3339)
150 clientSession.Values[appview.SessionAuthenticated] = true
151 return clientSession.Save(r, w)
152}
153
154func (a *Auth) AuthorizedClient(r *http.Request) (*xrpc.Client, error) {
155 clientSession, err := a.Store.Get(r, "appview-session")
156 if err != nil || clientSession.IsNew {
157 return nil, err
158 }
159
160 did := clientSession.Values["did"].(string)
161 pdsUrl := clientSession.Values["pds"].(string)
162 accessJwt := clientSession.Values["accessJwt"].(string)
163 refreshJwt := clientSession.Values["refreshJwt"].(string)
164
165 client := &xrpc.Client{
166 Host: pdsUrl,
167 Auth: &xrpc.AuthInfo{
168 AccessJwt: accessJwt,
169 RefreshJwt: refreshJwt,
170 Did: did,
171 },
172 }
173
174 return client, nil
175}
176
177func (a *Auth) GetSession(r *http.Request) (*sessions.Session, error) {
178 return a.Store.Get(r, appview.SessionName)
179}
180
181func (a *Auth) GetDid(r *http.Request) string {
182 clientSession, err := a.Store.Get(r, appview.SessionName)
183 if err != nil || clientSession.IsNew {
184 return ""
185 }
186
187 return clientSession.Values[appview.SessionDid].(string)
188}
189
190func (a *Auth) GetHandle(r *http.Request) string {
191 clientSession, err := a.Store.Get(r, appview.SessionName)
192 if err != nil || clientSession.IsNew {
193 return ""
194 }
195
196 return clientSession.Values[appview.SessionHandle].(string)
197}
198
199type User struct {
200 Handle string
201 Did string
202 Pds string
203}
204
205func (a *Auth) GetUser(r *http.Request) *User {
206 clientSession, err := a.Store.Get(r, appview.SessionName)
207
208 if err != nil || clientSession.IsNew {
209 return nil
210 }
211
212 return &User{
213 Handle: clientSession.Values[appview.SessionHandle].(string),
214 Did: clientSession.Values[appview.SessionDid].(string),
215 Pds: clientSession.Values[appview.SessionPds].(string),
216 }
217}