this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

add session ID to API (for multiple device scenarios)

+45 -28
+15 -10
atproto/auth/oauth/cmd/oauth-web-demo/main.go
··· 139 139 return nil 140 140 } 141 141 142 - func (s *Server) currentSessionDID(r *http.Request) *syntax.DID { 142 + func (s *Server) currentSessionDID(r *http.Request) (*syntax.DID, string) { 143 143 sess, _ := s.CookieStore.Get(r, "oauth-demo") 144 144 accountDID, ok := sess.Values["account_did"].(string) 145 145 if !ok || accountDID == "" { 146 - return nil 146 + return nil, "" 147 147 } 148 148 did, err := syntax.ParseDID(accountDID) 149 149 if err != nil { 150 - return nil 150 + return nil, "" 151 + } 152 + sessionID, ok := sess.Values["session_id"].(string) 153 + if !ok || sessionID == "" { 154 + return nil, "" 151 155 } 152 156 153 - return &did 157 + return &did, sessionID 154 158 } 155 159 156 160 func strPtr(raw string) *string { ··· 232 236 // create signed cookie session, indicating account DID 233 237 sess, _ := s.CookieStore.Get(r, "oauth-demo") 234 238 sess.Values["account_did"] = sessData.AccountDID.String() 239 + sess.Values["session_id"] = sessData.SessionID 235 240 if err := sess.Save(r, w); err != nil { 236 241 http.Error(w, err.Error(), http.StatusInternalServerError) 237 242 return ··· 244 249 func (s *Server) OAuthRefresh(w http.ResponseWriter, r *http.Request) { 245 250 ctx := r.Context() 246 251 247 - did := s.currentSessionDID(r) 252 + did, sessionID := s.currentSessionDID(r) 248 253 if did == nil { 249 254 // TODO: supposed to set a WWW header; and could redirect? 250 255 http.Error(w, "not authenticated", http.StatusUnauthorized) 251 256 return 252 257 } 253 258 254 - oauthSess, err := s.OAuth.ResumeSession(ctx, *did) 259 + oauthSess, err := s.OAuth.ResumeSession(ctx, *did, sessionID) 255 260 if err != nil { 256 261 http.Error(w, "not authenticated", http.StatusUnauthorized) 257 262 return ··· 270 275 func (s *Server) OAuthLogout(w http.ResponseWriter, r *http.Request) { 271 276 272 277 // delete session from auth store 273 - did := s.currentSessionDID(r) 278 + did, sessionID := s.currentSessionDID(r) 274 279 if did != nil { 275 - if err := s.OAuth.Store.DeleteSession(r.Context(), *did); err != nil { 280 + if err := s.OAuth.Store.DeleteSession(r.Context(), *did, sessionID); err != nil { 276 281 slog.Error("failed to delete session", "did", did, "err", err) 277 282 } 278 283 } ··· 300 305 return 301 306 } 302 307 303 - did := s.currentSessionDID(r) 308 + did, sessionID := s.currentSessionDID(r) 304 309 if did == nil { 305 310 // TODO: supposed to set a WWW header; and could redirect? 306 311 http.Error(w, "not authenticated", http.StatusUnauthorized) 307 312 return 308 313 } 309 314 310 - oauthSess, err := s.OAuth.ResumeSession(ctx, *did) 315 + oauthSess, err := s.OAuth.ResumeSession(ctx, *did, sessionID) 311 316 if err != nil { 312 317 http.Error(w, "not authenticated", http.StatusUnauthorized) 313 318 return
+5 -3
atproto/auth/oauth/doc.go
··· 80 80 http.Error(w, err.Error(), http.StatusInternalServerError) 81 81 } 82 82 83 - // web services might record the DID in a secure session cookie 83 + // web services might record the DID and session ID in a secure session cookie 84 84 _ = sessData.AccountDID 85 + _ = sessData.SessionID 85 86 86 87 http.Redirect(w, r, "/app", http.StatusFound) 87 88 } ··· 90 91 91 92 // web services might use a secure session cookie to determine user's DID for a request 92 93 did := syntax.DID("did:plc:abc123") 94 + sessionID := "xyz" 93 95 94 - sess, err := oauthApp.ResumeSession(ctx, did) 96 + sess, err := oauthApp.ResumeSession(ctx, did, sessionID) 95 97 if err != nil { 96 98 return err 97 99 } ··· 116 118 117 119 To log out a user, delete their session from the [OAuthStore]: 118 120 119 - if err := oauthApp.Store.DeleteSession(r.Context(), did); err != nil { 121 + if err := oauthApp.Store.DeleteSession(r.Context(), did, sessionID); err != nil { 120 122 return err 121 123 } 122 124 */
+9 -5
atproto/auth/oauth/memstore.go
··· 27 27 } 28 28 } 29 29 30 - func (m *MemStore) GetSession(ctx context.Context, did syntax.DID) (*ClientSessionData, error) { 30 + func memKey(did syntax.DID, sessionID string) string { 31 + return fmt.Sprintf("%s/%s", did, sessionID) 32 + } 33 + 34 + func (m *MemStore) GetSession(ctx context.Context, did syntax.DID, sessionID string) (*ClientSessionData, error) { 31 35 m.lk.Lock() 32 36 defer m.lk.Unlock() 33 37 34 - sess, ok := m.sessions[did.String()] 38 + sess, ok := m.sessions[memKey(did, sessionID)] 35 39 if !ok { 36 40 return nil, fmt.Errorf("session not found: %s", did) 37 41 } ··· 42 46 m.lk.Lock() 43 47 defer m.lk.Unlock() 44 48 45 - m.sessions[sess.AccountDID.String()] = sess 49 + m.sessions[memKey(sess.AccountDID, sess.SessionID)] = sess 46 50 return nil 47 51 } 48 52 49 - func (m *MemStore) DeleteSession(ctx context.Context, did syntax.DID) error { 53 + func (m *MemStore) DeleteSession(ctx context.Context, did syntax.DID, sessionID string) error { 50 54 m.lk.Lock() 51 55 defer m.lk.Unlock() 52 56 53 - delete(m.sessions, did.String()) 57 + delete(m.sessions, memKey(did, sessionID)) 54 58 return nil 55 59 } 56 60
+5 -4
atproto/auth/oauth/oauth.go
··· 177 177 return m 178 178 } 179 179 180 - func (app *ClientApp) ResumeSession(ctx context.Context, did syntax.DID) (*ClientSession, error) { 180 + func (app *ClientApp) ResumeSession(ctx context.Context, did syntax.DID, sessionID string) (*ClientSession, error) { 181 181 182 - sd, err := app.Store.GetSession(ctx, did) 182 + sd, err := app.Store.GetSession(ctx, did, sessionID) 183 183 if err != nil { 184 184 return nil, err 185 185 } ··· 193 193 // configure callback for updating session data 194 194 if app.Store != nil { 195 195 sess.PersistSessionCallback = func(ctx context.Context, data *ClientSessionData) { 196 - slog.Debug("storing updated session data", "did", data.AccountDID) 196 + slog.Debug("storing updated session data", "did", data.AccountDID, "session_id", data.SessionID) 197 197 err := app.Store.SaveSession(ctx, *data) 198 198 if err != nil { 199 - slog.Error("failed to store updated session data", "did", data.AccountDID, "err", err) 199 + slog.Error("failed to store updated session data", "did", data.AccountDID, "session_id", data.SessionID, "err", err) 200 200 } 201 201 } 202 202 } ··· 633 633 634 634 sessData := ClientSessionData{ 635 635 AccountDID: accountDID, 636 + SessionID: info.State, 636 637 HostURL: hostURL, 637 638 AuthServerURL: info.AuthServerURL, 638 639 AccessToken: tokenResp.AccessToken,
+5 -2
atproto/auth/oauth/session.go
··· 27 27 // Account DID for this session. Assuming only one active session per account, this can be used as "primary key" for storing and retrieving this infromation. 28 28 AccountDID syntax.DID `json:"account_did"` 29 29 30 + // Identifier to distinguish this particular session for the account. Server backends generally support multiple sessions for the same account. This package will re-use the random 'state' token from the auth flow as the session ID. 31 + SessionID string `json:"session_id"` 32 + 30 33 // Base URL of the "resource server" (eg, PDS). Should include scheme, hostname, port; no path or auth info. 31 34 HostURL string `json:"host_url"` 32 35 ··· 157 160 if sess.PersistSessionCallback != nil { 158 161 sess.PersistSessionCallback(ctx, sess.Data) 159 162 } else { 160 - slog.Warn("not saving updated session data", "did", sess.Data.AccountDID) 163 + slog.Warn("not saving updated session data", "did", sess.Data.AccountDID, "session_id", sess.Data.SessionID) 161 164 } 162 165 163 166 return sess.Data.AccessToken, nil ··· 246 249 if sess.PersistSessionCallback != nil { 247 250 sess.PersistSessionCallback(ctx, sess.Data) 248 251 } else { 249 - slog.Warn("not saving updated host DPoP nonce", "did", sess.Data.AccountDID) 252 + slog.Warn("not saving updated host DPoP nonce", "did", sess.Data.AccountDID, "session_id", sess.Data.SessionID) 250 253 } 251 254 } 252 255
+6 -4
atproto/auth/oauth/store.go
··· 8 8 9 9 // Interface for persisting session data and auth request data, required as part of an OAuth client app. 10 10 // 11 - // Note that this interface assumes that there is only a single session per account (by DID). 11 + // This interface supports multiple sessions for a single account (DID). This is helpful for traditional web app backends where a single user might log in and have concurrent sessions from mutiple browsers/devices. For situations where multiple sessions are not required, implementations of this interface could ignore the `sessionID` parameters, though this could result in clobbering of active sessions. 12 + // 13 + // For authorization-only (authn-only) applications, the `SaveSession()` method could be a no-op. 12 14 // 13 - // Implementations should allow for concurrent access. 15 + // Implementations should generally allow for concurrent access. 14 16 type ClientAuthStore interface { 15 - GetSession(ctx context.Context, did syntax.DID) (*ClientSessionData, error) 17 + GetSession(ctx context.Context, did syntax.DID, sessionID string) (*ClientSessionData, error) 16 18 SaveSession(ctx context.Context, sess ClientSessionData) error 17 - DeleteSession(ctx context.Context, did syntax.DID) error 19 + DeleteSession(ctx context.Context, did syntax.DID, sessionID string) error 18 20 19 21 GetAuthRequestInfo(ctx context.Context, state string) (*AuthRequestData, error) 20 22 SaveAuthRequestInfo(ctx context.Context, info AuthRequestData) error