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}