forked from tangled.org/core
this repo has no description

appview: oauth: swap out db store for redis cache

Signed-off-by: Anirudh Oppiliappan <anirudh@tangled.sh>

authored by anirudh.fi and committed by Tangled a8baf2fa 0242bbcc

Changed files
+50 -54
appview
cache
session
oauth
state
+3 -3
appview/cache/session/store.go
··· 102 102 } 103 103 104 104 func (s *SessionStore) GetRequestByState(ctx context.Context, state string) (*OAuthRequest, error) { 105 - didKey, err := s.getRequestKey(ctx, state) 105 + didKey, err := s.getRequestKeyFromState(ctx, state) 106 106 if err != nil { 107 107 return nil, err 108 108 } ··· 127 127 } 128 128 129 129 func (s *SessionStore) DeleteRequestByState(ctx context.Context, state string) error { 130 - didKey, err := s.getRequestKey(ctx, state) 130 + didKey, err := s.getRequestKeyFromState(ctx, state) 131 131 if err != nil { 132 132 return err 133 133 } 134 134 135 - err = s.cache.Del(ctx, fmt.Sprintf(stateKey, "state")).Err() 135 + err = s.cache.Del(ctx, fmt.Sprintf(stateKey, state)).Err() 136 136 if err != nil { 137 137 return err 138 138 }
+9 -5
appview/oauth/handler/handler.go
··· 13 13 "github.com/lestrrat-go/jwx/v2/jwk" 14 14 "github.com/posthog/posthog-go" 15 15 "tangled.sh/icyphox.sh/atproto-oauth/helpers" 16 + sessioncache "tangled.sh/tangled.sh/core/appview/cache/session" 16 17 "tangled.sh/tangled.sh/core/appview/config" 17 18 "tangled.sh/tangled.sh/core/appview/db" 18 19 "tangled.sh/tangled.sh/core/appview/idresolver" ··· 32 33 config *config.Config 33 34 pages *pages.Pages 34 35 idResolver *idresolver.Resolver 36 + sess *sessioncache.SessionStore 35 37 db *db.DB 36 38 store *sessions.CookieStore 37 39 oauth *oauth.OAuth ··· 44 46 pages *pages.Pages, 45 47 idResolver *idresolver.Resolver, 46 48 db *db.DB, 49 + sess *sessioncache.SessionStore, 47 50 store *sessions.CookieStore, 48 51 oauth *oauth.OAuth, 49 52 enforcer *rbac.Enforcer, ··· 54 57 pages: pages, 55 58 idResolver: idResolver, 56 59 db: db, 60 + sess: sess, 57 61 store: store, 58 62 oauth: oauth, 59 63 enforcer: enforcer, ··· 176 180 return 177 181 } 178 182 179 - err = db.SaveOAuthRequest(o.db, db.OAuthRequest{ 183 + err = o.sess.SaveRequest(r.Context(), sessioncache.OAuthRequest{ 180 184 Did: resolved.DID.String(), 181 185 PdsUrl: resolved.PDSEndpoint(), 182 186 Handle: handle, ··· 204 208 func (o *OAuthHandler) callback(w http.ResponseWriter, r *http.Request) { 205 209 state := r.FormValue("state") 206 210 207 - oauthRequest, err := db.GetOAuthRequestByState(o.db, state) 211 + oauthRequest, err := o.sess.GetRequestByState(r.Context(), state) 208 212 if err != nil { 209 213 log.Println("failed to get oauth request:", err) 210 214 o.pages.Notice(w, "login-msg", "Failed to authenticate. Try again later.") ··· 212 216 } 213 217 214 218 defer func() { 215 - err := db.DeleteOAuthRequestByState(o.db, state) 219 + err := o.sess.DeleteRequestByState(r.Context(), state) 216 220 if err != nil { 217 221 log.Println("failed to delete oauth request for state:", state, err) 218 222 } ··· 281 285 return 282 286 } 283 287 284 - err = o.oauth.SaveSession(w, r, oauthRequest, tokenResp) 288 + err = o.oauth.SaveSession(w, r, *oauthRequest, tokenResp) 285 289 if err != nil { 286 290 log.Println("failed to save session:", err) 287 291 o.pages.Notice(w, "login-msg", "Failed to authenticate. Try again later.") ··· 313 317 } 314 318 315 319 log.Println("session cleared successfully") 316 - http.Redirect(w, r, "/", http.StatusFound) 320 + o.pages.HxRedirect(w, "/login") 317 321 } 318 322 319 323 func pubKeyFromJwk(jwks string) (jwk.Key, error) {
+28 -35
appview/oauth/oauth.go
··· 10 10 "github.com/gorilla/sessions" 11 11 oauth "tangled.sh/icyphox.sh/atproto-oauth" 12 12 "tangled.sh/icyphox.sh/atproto-oauth/helpers" 13 + sessioncache "tangled.sh/tangled.sh/core/appview/cache/session" 13 14 "tangled.sh/tangled.sh/core/appview/config" 14 - "tangled.sh/tangled.sh/core/appview/db" 15 15 "tangled.sh/tangled.sh/core/appview/oauth/client" 16 16 xrpc "tangled.sh/tangled.sh/core/appview/xrpcclient" 17 17 ) 18 18 19 - type OAuthRequest struct { 20 - ID uint 21 - AuthserverIss string 22 - State string 23 - Did string 24 - PdsUrl string 25 - PkceVerifier string 26 - DpopAuthserverNonce string 27 - DpopPrivateJwk string 28 - } 29 - 30 19 type OAuth struct { 31 - Store *sessions.CookieStore 32 - Db *db.DB 33 - Config *config.Config 20 + store *sessions.CookieStore 21 + config *config.Config 22 + sess *sessioncache.SessionStore 34 23 } 35 24 36 - func NewOAuth(db *db.DB, config *config.Config) *OAuth { 25 + func NewOAuth(config *config.Config, sess *sessioncache.SessionStore) *OAuth { 37 26 return &OAuth{ 38 - Store: sessions.NewCookieStore([]byte(config.Core.CookieSecret)), 39 - Db: db, 40 - Config: config, 27 + store: sessions.NewCookieStore([]byte(config.Core.CookieSecret)), 28 + config: config, 29 + sess: sess, 41 30 } 42 31 } 43 32 44 - func (o *OAuth) SaveSession(w http.ResponseWriter, r *http.Request, oreq db.OAuthRequest, oresp *oauth.TokenResponse) error { 33 + func (o *OAuth) Stores() *sessions.CookieStore { 34 + return o.store 35 + } 36 + 37 + func (o *OAuth) SaveSession(w http.ResponseWriter, r *http.Request, oreq sessioncache.OAuthRequest, oresp *oauth.TokenResponse) error { 45 38 // first we save the did in the user session 46 - userSession, err := o.Store.Get(r, SessionName) 39 + userSession, err := o.store.Get(r, SessionName) 47 40 if err != nil { 48 41 return err 49 42 } ··· 58 51 } 59 52 60 53 // then save the whole thing in the db 61 - session := db.OAuthSession{ 54 + session := sessioncache.OAuthSession{ 62 55 Did: oreq.Did, 63 56 Handle: oreq.Handle, 64 57 PdsUrl: oreq.PdsUrl, ··· 70 63 Expiry: time.Now().Add(time.Duration(oresp.ExpiresIn) * time.Second).Format(time.RFC3339), 71 64 } 72 65 73 - return db.SaveOAuthSession(o.Db, session) 66 + return o.sess.SaveSession(r.Context(), session) 74 67 } 75 68 76 69 func (o *OAuth) ClearSession(r *http.Request, w http.ResponseWriter) error { 77 - userSession, err := o.Store.Get(r, SessionName) 70 + userSession, err := o.store.Get(r, SessionName) 78 71 if err != nil || userSession.IsNew { 79 72 return fmt.Errorf("error getting user session (or new session?): %w", err) 80 73 } 81 74 82 75 did := userSession.Values[SessionDid].(string) 83 76 84 - err = db.DeleteOAuthSessionByDid(o.Db, did) 77 + err = o.sess.DeleteSession(r.Context(), did) 85 78 if err != nil { 86 79 return fmt.Errorf("error deleting oauth session: %w", err) 87 80 } ··· 91 84 return userSession.Save(r, w) 92 85 } 93 86 94 - func (o *OAuth) GetSession(r *http.Request) (*db.OAuthSession, bool, error) { 95 - userSession, err := o.Store.Get(r, SessionName) 87 + func (o *OAuth) GetSession(r *http.Request) (*sessioncache.OAuthSession, bool, error) { 88 + userSession, err := o.store.Get(r, SessionName) 96 89 if err != nil || userSession.IsNew { 97 90 return nil, false, fmt.Errorf("error getting user session (or new session?): %w", err) 98 91 } ··· 100 93 did := userSession.Values[SessionDid].(string) 101 94 auth := userSession.Values[SessionAuthenticated].(bool) 102 95 103 - session, err := db.GetOAuthSessionByDid(o.Db, did) 96 + session, err := o.sess.GetSession(r.Context(), did) 104 97 if err != nil { 105 98 return nil, false, fmt.Errorf("error getting oauth session: %w", err) 106 99 } ··· 119 112 120 113 oauthClient, err := client.NewClient( 121 114 self.ClientID, 122 - o.Config.OAuth.Jwks, 115 + o.config.OAuth.Jwks, 123 116 self.RedirectURIs[0], 124 117 ) 125 118 ··· 133 126 } 134 127 135 128 newExpiry := time.Now().Add(time.Duration(resp.ExpiresIn) * time.Second).Format(time.RFC3339) 136 - err = db.RefreshOAuthSession(o.Db, did, resp.AccessToken, resp.RefreshToken, newExpiry) 129 + err = o.sess.RefreshSession(r.Context(), did, resp.AccessToken, resp.RefreshToken, newExpiry) 137 130 if err != nil { 138 131 return nil, false, fmt.Errorf("error refreshing oauth session: %w", err) 139 132 } ··· 155 148 } 156 149 157 150 func (a *OAuth) GetUser(r *http.Request) *User { 158 - clientSession, err := a.Store.Get(r, SessionName) 151 + clientSession, err := a.store.Get(r, SessionName) 159 152 160 153 if err != nil || clientSession.IsNew { 161 154 return nil ··· 169 162 } 170 163 171 164 func (a *OAuth) GetDid(r *http.Request) string { 172 - clientSession, err := a.Store.Get(r, SessionName) 165 + clientSession, err := a.store.Get(r, SessionName) 173 166 174 167 if err != nil || clientSession.IsNew { 175 168 return "" ··· 189 182 190 183 client := &oauth.XrpcClient{ 191 184 OnDpopPdsNonceChanged: func(did, newNonce string) { 192 - err := db.UpdateDpopPdsNonce(o.Db, did, newNonce) 185 + err := o.sess.UpdateNonce(r.Context(), did, newNonce) 193 186 if err != nil { 194 187 log.Printf("error updating dpop pds nonce: %v", err) 195 188 } ··· 234 227 return []string{fmt.Sprintf("%s/oauth/callback", c)} 235 228 } 236 229 237 - clientURI := o.Config.Core.AppviewHost 230 + clientURI := o.config.Core.AppviewHost 238 231 clientID := fmt.Sprintf("%s/oauth/client-metadata.json", clientURI) 239 232 redirectURIs := makeRedirectURIs(clientURI) 240 233 241 - if o.Config.Core.Dev { 234 + if o.config.Core.Dev { 242 235 clientURI = fmt.Sprintf("http://127.0.0.1:3000") 243 236 redirectURIs = makeRedirectURIs(clientURI) 244 237
+1 -3
appview/state/router.go
··· 97 97 98 98 r.Get("/", s.Timeline) 99 99 100 - r.With(middleware.AuthMiddleware(s.oauth)).Post("/logout", s.Logout) 101 - 102 100 r.Route("/knots", func(r chi.Router) { 103 101 r.Use(middleware.AuthMiddleware(s.oauth)) 104 102 r.Get("/", s.Knots) ··· 156 154 157 155 func (s *State) OAuthRouter() http.Handler { 158 156 store := sessions.NewCookieStore([]byte(s.config.Core.CookieSecret)) 159 - oauth := oauthhandler.New(s.config, s.pages, s.idResolver, s.db, store, s.oauth, s.enforcer, s.posthog) 157 + oauth := oauthhandler.New(s.config, s.pages, s.idResolver, s.db, s.sess, store, s.oauth, s.enforcer, s.posthog) 160 158 return oauth.Router() 161 159 } 162 160
+9 -8
appview/state/state.go
··· 20 20 "github.com/posthog/posthog-go" 21 21 "tangled.sh/tangled.sh/core/api/tangled" 22 22 "tangled.sh/tangled.sh/core/appview" 23 + "tangled.sh/tangled.sh/core/appview/cache" 24 + "tangled.sh/tangled.sh/core/appview/cache/session" 23 25 "tangled.sh/tangled.sh/core/appview/config" 24 26 "tangled.sh/tangled.sh/core/appview/db" 25 27 "tangled.sh/tangled.sh/core/appview/idresolver" ··· 37 39 enforcer *rbac.Enforcer 38 40 tidClock syntax.TIDClock 39 41 pages *pages.Pages 42 + sess *session.SessionStore 40 43 idResolver *idresolver.Resolver 41 44 posthog posthog.Client 42 45 jc *jetstream.JetstreamClient ··· 65 68 res = idresolver.DefaultResolver() 66 69 } 67 70 68 - oauth := oauth.NewOAuth(d, config) 71 + cache := cache.New(config.Redis.Addr) 72 + sess := session.New(cache) 73 + 74 + oauth := oauth.NewOAuth(config, sess) 69 75 70 76 posthog, err := posthog.NewWithConfig(config.Posthog.ApiKey, posthog.Config{Endpoint: config.Posthog.Endpoint}) 71 77 if err != nil { ··· 104 110 enforcer, 105 111 clock, 106 112 pgs, 113 + sess, 107 114 res, 108 115 posthog, 109 116 jc, ··· 118 125 return c.Next().String() 119 126 } 120 127 121 - func (s *State) Logout(w http.ResponseWriter, r *http.Request) { 122 - s.oauth.ClearSession(r, w) 123 - w.Header().Set("HX-Redirect", "/login") 124 - w.WriteHeader(http.StatusSeeOther) 125 - } 126 - 127 128 func (s *State) Timeline(w http.ResponseWriter, r *http.Request) { 128 129 user := s.oauth.GetUser(r) 129 130 ··· 176 177 177 178 return 178 179 case http.MethodPost: 179 - session, err := s.oauth.Store.Get(r, oauth.SessionName) 180 + session, err := s.oauth.Stores().Get(r, oauth.SessionName) 180 181 if err != nil || session.IsNew { 181 182 log.Println("unauthorized attempt to generate registration key") 182 183 http.Error(w, "Forbidden", http.StatusUnauthorized)