forked from tangled.org/core
Monorepo for Tangled

appview: oauth: clean up router init

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

authored by anirudh.fi and committed by Tangled fdcb952d edcaac68

Changed files
+90 -78
appview
+1 -1
appview/consts.go appview/oauth/consts.go
··· 1 - package appview 1 + package oauth 2 2 3 3 const ( 4 4 SessionName = "appview-session"
+68 -46
appview/oauth/handler/handler.go
··· 29 29 ) 30 30 31 31 type OAuthHandler struct { 32 - Config *config.Config 33 - Pages *pages.Pages 34 - Idresolver *idresolver.Resolver 35 - Db *db.DB 36 - Store *sessions.CookieStore 37 - OAuth *oauth.OAuth 38 - Enforcer *rbac.Enforcer 39 - Posthog posthog.Client 32 + config *config.Config 33 + pages *pages.Pages 34 + idResolver *idresolver.Resolver 35 + db *db.DB 36 + store *sessions.CookieStore 37 + oauth *oauth.OAuth 38 + enforcer *rbac.Enforcer 39 + posthog posthog.Client 40 + } 41 + 42 + func New( 43 + config *config.Config, 44 + pages *pages.Pages, 45 + idResolver *idresolver.Resolver, 46 + db *db.DB, 47 + store *sessions.CookieStore, 48 + oauth *oauth.OAuth, 49 + enforcer *rbac.Enforcer, 50 + posthog posthog.Client, 51 + ) *OAuthHandler { 52 + return &OAuthHandler{ 53 + config: config, 54 + pages: pages, 55 + idResolver: idResolver, 56 + db: db, 57 + store: store, 58 + oauth: oauth, 59 + enforcer: enforcer, 60 + posthog: posthog, 61 + } 40 62 } 41 63 42 64 func (o *OAuthHandler) Router() http.Handler { ··· 45 67 r.Get("/login", o.login) 46 68 r.Post("/login", o.login) 47 69 48 - r.With(middleware.AuthMiddleware(o.OAuth)).Post("/logout", o.logout) 70 + r.With(middleware.AuthMiddleware(o.oauth)).Post("/logout", o.logout) 49 71 50 72 r.Get("/oauth/client-metadata.json", o.clientMetadata) 51 73 r.Get("/oauth/jwks.json", o.jwks) ··· 56 78 func (o *OAuthHandler) clientMetadata(w http.ResponseWriter, r *http.Request) { 57 79 w.Header().Set("Content-Type", "application/json") 58 80 w.WriteHeader(http.StatusOK) 59 - json.NewEncoder(w).Encode(o.OAuth.ClientMetadata()) 81 + json.NewEncoder(w).Encode(o.oauth.ClientMetadata()) 60 82 } 61 83 62 84 func (o *OAuthHandler) jwks(w http.ResponseWriter, r *http.Request) { 63 - jwks := o.Config.OAuth.Jwks 85 + jwks := o.config.OAuth.Jwks 64 86 pubKey, err := pubKeyFromJwk(jwks) 65 87 if err != nil { 66 88 log.Printf("error parsing public key: %v", err) ··· 78 100 func (o *OAuthHandler) login(w http.ResponseWriter, r *http.Request) { 79 101 switch r.Method { 80 102 case http.MethodGet: 81 - o.Pages.Login(w, pages.LoginParams{}) 103 + o.pages.Login(w, pages.LoginParams{}) 82 104 case http.MethodPost: 83 105 handle := strings.TrimPrefix(r.FormValue("handle"), "@") 84 106 85 - resolved, err := o.Idresolver.ResolveIdent(r.Context(), handle) 107 + resolved, err := o.idResolver.ResolveIdent(r.Context(), handle) 86 108 if err != nil { 87 109 log.Println("failed to resolve handle:", err) 88 - o.Pages.Notice(w, "login-msg", fmt.Sprintf("\"%s\" is an invalid handle.", handle)) 110 + o.pages.Notice(w, "login-msg", fmt.Sprintf("\"%s\" is an invalid handle.", handle)) 89 111 return 90 112 } 91 - self := o.OAuth.ClientMetadata() 113 + self := o.oauth.ClientMetadata() 92 114 oauthClient, err := client.NewClient( 93 115 self.ClientID, 94 - o.Config.OAuth.Jwks, 116 + o.config.OAuth.Jwks, 95 117 self.RedirectURIs[0], 96 118 ) 97 119 98 120 if err != nil { 99 121 log.Println("failed to create oauth client:", err) 100 - o.Pages.Notice(w, "login-msg", "Failed to authenticate. Try again later.") 122 + o.pages.Notice(w, "login-msg", "Failed to authenticate. Try again later.") 101 123 return 102 124 } 103 125 104 126 authServer, err := oauthClient.ResolvePdsAuthServer(r.Context(), resolved.PDSEndpoint()) 105 127 if err != nil { 106 128 log.Println("failed to resolve auth server:", err) 107 - o.Pages.Notice(w, "login-msg", "Failed to authenticate. Try again later.") 129 + o.pages.Notice(w, "login-msg", "Failed to authenticate. Try again later.") 108 130 return 109 131 } 110 132 111 133 authMeta, err := oauthClient.FetchAuthServerMetadata(r.Context(), authServer) 112 134 if err != nil { 113 135 log.Println("failed to fetch auth server metadata:", err) 114 - o.Pages.Notice(w, "login-msg", "Failed to authenticate. Try again later.") 136 + o.pages.Notice(w, "login-msg", "Failed to authenticate. Try again later.") 115 137 return 116 138 } 117 139 118 140 dpopKey, err := helpers.GenerateKey(nil) 119 141 if err != nil { 120 142 log.Println("failed to generate dpop key:", err) 121 - o.Pages.Notice(w, "login-msg", "Failed to authenticate. Try again later.") 143 + o.pages.Notice(w, "login-msg", "Failed to authenticate. Try again later.") 122 144 return 123 145 } 124 146 125 147 dpopKeyJson, err := json.Marshal(dpopKey) 126 148 if err != nil { 127 149 log.Println("failed to marshal dpop key:", err) 128 - o.Pages.Notice(w, "login-msg", "Failed to authenticate. Try again later.") 150 + o.pages.Notice(w, "login-msg", "Failed to authenticate. Try again later.") 129 151 return 130 152 } 131 153 132 154 parResp, err := oauthClient.SendParAuthRequest(r.Context(), authServer, authMeta, handle, oauthScope, dpopKey) 133 155 if err != nil { 134 156 log.Println("failed to send par auth request:", err) 135 - o.Pages.Notice(w, "login-msg", "Failed to authenticate. Try again later.") 157 + o.pages.Notice(w, "login-msg", "Failed to authenticate. Try again later.") 136 158 return 137 159 } 138 160 139 - err = db.SaveOAuthRequest(o.Db, db.OAuthRequest{ 161 + err = db.SaveOAuthRequest(o.db, db.OAuthRequest{ 140 162 Did: resolved.DID.String(), 141 163 PdsUrl: resolved.PDSEndpoint(), 142 164 Handle: handle, ··· 148 170 }) 149 171 if err != nil { 150 172 log.Println("failed to save oauth request:", err) 151 - o.Pages.Notice(w, "login-msg", "Failed to authenticate. Try again later.") 173 + o.pages.Notice(w, "login-msg", "Failed to authenticate. Try again later.") 152 174 return 153 175 } 154 176 ··· 157 179 query.Add("client_id", self.ClientID) 158 180 query.Add("request_uri", parResp.RequestUri) 159 181 u.RawQuery = query.Encode() 160 - o.Pages.HxRedirect(w, u.String()) 182 + o.pages.HxRedirect(w, u.String()) 161 183 } 162 184 } 163 185 164 186 func (o *OAuthHandler) callback(w http.ResponseWriter, r *http.Request) { 165 187 state := r.FormValue("state") 166 188 167 - oauthRequest, err := db.GetOAuthRequestByState(o.Db, state) 189 + oauthRequest, err := db.GetOAuthRequestByState(o.db, state) 168 190 if err != nil { 169 191 log.Println("failed to get oauth request:", err) 170 - o.Pages.Notice(w, "login-msg", "Failed to authenticate. Try again later.") 192 + o.pages.Notice(w, "login-msg", "Failed to authenticate. Try again later.") 171 193 return 172 194 } 173 195 174 196 defer func() { 175 - err := db.DeleteOAuthRequestByState(o.Db, state) 197 + err := db.DeleteOAuthRequestByState(o.db, state) 176 198 if err != nil { 177 199 log.Println("failed to delete oauth request for state:", state, err) 178 200 } ··· 182 204 errorDescription := r.FormValue("error_description") 183 205 if error != "" || errorDescription != "" { 184 206 log.Printf("error: %s, %s", error, errorDescription) 185 - o.Pages.Notice(w, "login-msg", "Failed to authenticate. Try again later.") 207 + o.pages.Notice(w, "login-msg", "Failed to authenticate. Try again later.") 186 208 return 187 209 } 188 210 189 211 code := r.FormValue("code") 190 212 if code == "" { 191 213 log.Println("missing code for state: ", state) 192 - o.Pages.Notice(w, "login-msg", "Failed to authenticate. Try again later.") 214 + o.pages.Notice(w, "login-msg", "Failed to authenticate. Try again later.") 193 215 return 194 216 } 195 217 196 218 iss := r.FormValue("iss") 197 219 if iss == "" { 198 220 log.Println("missing iss for state: ", state) 199 - o.Pages.Notice(w, "login-msg", "Failed to authenticate. Try again later.") 221 + o.pages.Notice(w, "login-msg", "Failed to authenticate. Try again later.") 200 222 return 201 223 } 202 224 203 - self := o.OAuth.ClientMetadata() 225 + self := o.oauth.ClientMetadata() 204 226 205 227 oauthClient, err := client.NewClient( 206 228 self.ClientID, 207 - o.Config.OAuth.Jwks, 229 + o.config.OAuth.Jwks, 208 230 self.RedirectURIs[0], 209 231 ) 210 232 211 233 if err != nil { 212 234 log.Println("failed to create oauth client:", err) 213 - o.Pages.Notice(w, "login-msg", "Failed to authenticate. Try again later.") 235 + o.pages.Notice(w, "login-msg", "Failed to authenticate. Try again later.") 214 236 return 215 237 } 216 238 217 239 jwk, err := helpers.ParseJWKFromBytes([]byte(oauthRequest.DpopPrivateJwk)) 218 240 if err != nil { 219 241 log.Println("failed to parse jwk:", err) 220 - o.Pages.Notice(w, "login-msg", "Failed to authenticate. Try again later.") 242 + o.pages.Notice(w, "login-msg", "Failed to authenticate. Try again later.") 221 243 return 222 244 } 223 245 ··· 231 253 ) 232 254 if err != nil { 233 255 log.Println("failed to get token:", err) 234 - o.Pages.Notice(w, "login-msg", "Failed to authenticate. Try again later.") 256 + o.pages.Notice(w, "login-msg", "Failed to authenticate. Try again later.") 235 257 return 236 258 } 237 259 238 260 if tokenResp.Scope != oauthScope { 239 261 log.Println("scope doesn't match:", tokenResp.Scope) 240 - o.Pages.Notice(w, "login-msg", "Failed to authenticate. Try again later.") 262 + o.pages.Notice(w, "login-msg", "Failed to authenticate. Try again later.") 241 263 return 242 264 } 243 265 244 - err = o.OAuth.SaveSession(w, r, oauthRequest, tokenResp) 266 + err = o.oauth.SaveSession(w, r, oauthRequest, tokenResp) 245 267 if err != nil { 246 268 log.Println("failed to save session:", err) 247 - o.Pages.Notice(w, "login-msg", "Failed to authenticate. Try again later.") 269 + o.pages.Notice(w, "login-msg", "Failed to authenticate. Try again later.") 248 270 return 249 271 } 250 272 251 273 log.Println("session saved successfully") 252 274 go o.addToDefaultKnot(oauthRequest.Did) 253 275 254 - if !o.Config.Core.Dev { 255 - err = o.Posthog.Enqueue(posthog.Capture{ 276 + if !o.config.Core.Dev { 277 + err = o.posthog.Enqueue(posthog.Capture{ 256 278 DistinctId: oauthRequest.Did, 257 279 Event: "signin", 258 280 }) ··· 265 287 } 266 288 267 289 func (o *OAuthHandler) logout(w http.ResponseWriter, r *http.Request) { 268 - err := o.OAuth.ClearSession(r, w) 290 + err := o.oauth.ClearSession(r, w) 269 291 if err != nil { 270 292 log.Println("failed to clear session:", err) 271 293 http.Redirect(w, r, "/", http.StatusFound) ··· 292 314 defaultKnot := "knot1.tangled.sh" 293 315 294 316 log.Printf("adding %s to default knot", did) 295 - err := o.Enforcer.AddMember(defaultKnot, did) 317 + err := o.enforcer.AddMember(defaultKnot, did) 296 318 if err != nil { 297 319 log.Println("failed to add user to knot1.tangled.sh: ", err) 298 320 return 299 321 } 300 - err = o.Enforcer.E.SavePolicy() 322 + err = o.enforcer.E.SavePolicy() 301 323 if err != nil { 302 324 log.Println("failed to add user to knot1.tangled.sh: ", err) 303 325 return 304 326 } 305 327 306 - secret, err := db.GetRegistrationKey(o.Db, defaultKnot) 328 + secret, err := db.GetRegistrationKey(o.db, defaultKnot) 307 329 if err != nil { 308 330 log.Println("failed to get registration key for knot1.tangled.sh") 309 331 return 310 332 } 311 - signedClient, err := knotclient.NewSignedClient(defaultKnot, secret, o.Config.Core.Dev) 333 + signedClient, err := knotclient.NewSignedClient(defaultKnot, secret, o.config.Core.Dev) 312 334 resp, err := signedClient.AddMember(did) 313 335 if err != nil { 314 336 log.Println("failed to add user to knot1.tangled.sh: ", err)
+16 -17
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 - "tangled.sh/tangled.sh/core/appview" 14 13 "tangled.sh/tangled.sh/core/appview/config" 15 14 "tangled.sh/tangled.sh/core/appview/db" 16 15 "tangled.sh/tangled.sh/core/appview/oauth/client" ··· 44 43 45 44 func (o *OAuth) SaveSession(w http.ResponseWriter, r *http.Request, oreq db.OAuthRequest, oresp *oauth.TokenResponse) error { 46 45 // first we save the did in the user session 47 - userSession, err := o.Store.Get(r, appview.SessionName) 46 + userSession, err := o.Store.Get(r, SessionName) 48 47 if err != nil { 49 48 return err 50 49 } 51 50 52 - userSession.Values[appview.SessionDid] = oreq.Did 53 - userSession.Values[appview.SessionHandle] = oreq.Handle 54 - userSession.Values[appview.SessionPds] = oreq.PdsUrl 55 - userSession.Values[appview.SessionAuthenticated] = true 51 + userSession.Values[SessionDid] = oreq.Did 52 + userSession.Values[SessionHandle] = oreq.Handle 53 + userSession.Values[SessionPds] = oreq.PdsUrl 54 + userSession.Values[SessionAuthenticated] = true 56 55 err = userSession.Save(r, w) 57 56 if err != nil { 58 57 return fmt.Errorf("error saving user session: %w", err) ··· 75 74 } 76 75 77 76 func (o *OAuth) ClearSession(r *http.Request, w http.ResponseWriter) error { 78 - userSession, err := o.Store.Get(r, appview.SessionName) 77 + userSession, err := o.Store.Get(r, SessionName) 79 78 if err != nil || userSession.IsNew { 80 79 return fmt.Errorf("error getting user session (or new session?): %w", err) 81 80 } 82 81 83 - did := userSession.Values[appview.SessionDid].(string) 82 + did := userSession.Values[SessionDid].(string) 84 83 85 84 err = db.DeleteOAuthSessionByDid(o.Db, did) 86 85 if err != nil { ··· 93 92 } 94 93 95 94 func (o *OAuth) GetSession(r *http.Request) (*db.OAuthSession, bool, error) { 96 - userSession, err := o.Store.Get(r, appview.SessionName) 95 + userSession, err := o.Store.Get(r, SessionName) 97 96 if err != nil || userSession.IsNew { 98 97 return nil, false, fmt.Errorf("error getting user session (or new session?): %w", err) 99 98 } 100 99 101 - did := userSession.Values[appview.SessionDid].(string) 102 - auth := userSession.Values[appview.SessionAuthenticated].(bool) 100 + did := userSession.Values[SessionDid].(string) 101 + auth := userSession.Values[SessionAuthenticated].(bool) 103 102 104 103 session, err := db.GetOAuthSessionByDid(o.Db, did) 105 104 if err != nil { ··· 156 155 } 157 156 158 157 func (a *OAuth) GetUser(r *http.Request) *User { 159 - clientSession, err := a.Store.Get(r, appview.SessionName) 158 + clientSession, err := a.Store.Get(r, SessionName) 160 159 161 160 if err != nil || clientSession.IsNew { 162 161 return nil 163 162 } 164 163 165 164 return &User{ 166 - Handle: clientSession.Values[appview.SessionHandle].(string), 167 - Did: clientSession.Values[appview.SessionDid].(string), 168 - Pds: clientSession.Values[appview.SessionPds].(string), 165 + Handle: clientSession.Values[SessionHandle].(string), 166 + Did: clientSession.Values[SessionDid].(string), 167 + Pds: clientSession.Values[SessionPds].(string), 169 168 } 170 169 } 171 170 172 171 func (a *OAuth) GetDid(r *http.Request) string { 173 - clientSession, err := a.Store.Get(r, appview.SessionName) 172 + clientSession, err := a.Store.Get(r, SessionName) 174 173 175 174 if err != nil || clientSession.IsNew { 176 175 return "" 177 176 } 178 177 179 - return clientSession.Values[appview.SessionDid].(string) 178 + return clientSession.Values[SessionDid].(string) 180 179 } 181 180 182 181 func (o *OAuth) AuthorizedClient(r *http.Request) (*xrpc.Client, error) {
+3 -12
appview/state/router.go
··· 7 7 "github.com/go-chi/chi/v5" 8 8 "github.com/gorilla/sessions" 9 9 "tangled.sh/tangled.sh/core/appview/middleware" 10 - oauthhandler "tangled.sh/tangled.sh/core/appview/oauth/handler" 10 + oauth "tangled.sh/tangled.sh/core/appview/oauth/handler" 11 11 "tangled.sh/tangled.sh/core/appview/pulls" 12 12 "tangled.sh/tangled.sh/core/appview/repo" 13 13 "tangled.sh/tangled.sh/core/appview/settings" ··· 154 154 } 155 155 156 156 func (s *State) OAuthRouter() http.Handler { 157 - oauth := &oauthhandler.OAuthHandler{ 158 - Config: s.config, 159 - Pages: s.pages, 160 - Idresolver: s.idResolver, 161 - Db: s.db, 162 - Store: sessions.NewCookieStore([]byte(s.config.Core.CookieSecret)), 163 - OAuth: s.oauth, 164 - Enforcer: s.enforcer, 165 - Posthog: s.posthog, 166 - } 167 - 157 + store := sessions.NewCookieStore([]byte(s.config.Core.CookieSecret)) 158 + oauth := oauth.New(s.config, s.pages, s.idResolver, s.db, store, s.oauth, s.enforcer, s.posthog) 168 159 return oauth.Router() 169 160 } 170 161
+2 -2
appview/state/state.go
··· 176 176 177 177 return 178 178 case http.MethodPost: 179 - session, err := s.oauth.Store.Get(r, appview.SessionName) 179 + session, err := s.oauth.Store.Get(r, oauth.SessionName) 180 180 if err != nil || session.IsNew { 181 181 log.Println("unauthorized attempt to generate registration key") 182 182 http.Error(w, "Forbidden", http.StatusUnauthorized) 183 183 return 184 184 } 185 185 186 - did := session.Values[appview.SessionDid].(string) 186 + did := session.Values[oauth.SessionDid].(string) 187 187 188 188 // check if domain is valid url, and strip extra bits down to just host 189 189 domain := r.FormValue("domain")