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
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, _ := a.Store.Get(r, appview.SessionName)
132 clientSession.Options.MaxAge = -1
133 return clientSession.Save(r, w)
134}
135
136func (a *Auth) StoreSession(r *http.Request, w http.ResponseWriter, atSessionish Sessionish, pdsEndpoint string) error {
137 clientSession, _ := a.Store.Get(r, appview.SessionName)
138 clientSession.Values[appview.SessionHandle] = atSessionish.GetHandle()
139 clientSession.Values[appview.SessionDid] = atSessionish.GetDid()
140 clientSession.Values[appview.SessionPds] = pdsEndpoint
141 clientSession.Values[appview.SessionAccessJwt] = atSessionish.GetAccessJwt()
142 clientSession.Values[appview.SessionRefreshJwt] = atSessionish.GetRefreshJwt()
143 clientSession.Values[appview.SessionExpiry] = time.Now().Add(time.Minute * 15).Format(time.RFC3339)
144 clientSession.Values[appview.SessionAuthenticated] = true
145 return clientSession.Save(r, w)
146}
147
148func (a *Auth) AuthorizedClient(r *http.Request) (*xrpc.Client, error) {
149 clientSession, err := a.Store.Get(r, "appview-session")
150 if err != nil || clientSession.IsNew {
151 return nil, err
152 }
153
154 did := clientSession.Values["did"].(string)
155 pdsUrl := clientSession.Values["pds"].(string)
156 accessJwt := clientSession.Values["accessJwt"].(string)
157 refreshJwt := clientSession.Values["refreshJwt"].(string)
158
159 client := &xrpc.Client{
160 Host: pdsUrl,
161 Auth: &xrpc.AuthInfo{
162 AccessJwt: accessJwt,
163 RefreshJwt: refreshJwt,
164 Did: did,
165 },
166 }
167
168 return client, nil
169}
170
171func (a *Auth) GetSession(r *http.Request) (*sessions.Session, error) {
172 return a.Store.Get(r, appview.SessionName)
173}
174
175func (a *Auth) GetDid(r *http.Request) string {
176 clientSession, err := a.Store.Get(r, appview.SessionName)
177 if err != nil || clientSession.IsNew {
178 return ""
179 }
180
181 return clientSession.Values[appview.SessionDid].(string)
182}
183
184func (a *Auth) GetHandle(r *http.Request) string {
185 clientSession, err := a.Store.Get(r, appview.SessionName)
186 if err != nil || clientSession.IsNew {
187 return ""
188 }
189
190 return clientSession.Values[appview.SessionHandle].(string)
191}
192
193type User struct {
194 Handle string
195 Did string
196 Pds string
197}
198
199func (a *Auth) GetUser(r *http.Request) *User {
200 clientSession, err := a.Store.Get(r, appview.SessionName)
201
202 if err != nil || clientSession.IsNew {
203 return nil
204 }
205
206 return &User{
207 Handle: clientSession.Values[appview.SessionHandle].(string),
208 Did: clientSession.Values[appview.SessionDid].(string),
209 Pds: clientSession.Values[appview.SessionPds].(string),
210 }
211}