package onboarding import "net/http" func (s *Service) accountSessionHTTP(r *http.Request) (any, error) { if r.Header.Get("Sec-Fetch-Site") == "cross-site" || r.URL.RawQuery != "" { return nil, errAccess } cookie, err := r.Cookie(s.cookieName(true)) if err != nil { return nil, fail(401, "LOGIN_REQUIRED", "请先登录") } p, err := s.AccountPrincipal(r.Context(), cookie.Value, "", false) if err != nil { return nil, err } token := s.sessionCSRF("ACCOUNT-CSRF1", cookie.Value) _, err = s.pool.Exec(r.Context(), `UPDATE `+s.accountSessionTable()+` SET csrf_hash=$2 WHERE token_hash=$1 AND revoked_at IS NULL AND expires_at>now()`, digest([]byte(cookie.Value)), digest([]byte(token))) if err != nil { return nil, err } if c, e := r.Cookie(s.cookieName(false)); !s.operatorLogin && e == nil && validB64(c.Value, 32) { _, err = s.pool.Exec(r.Context(), `UPDATE onboarding_web_session SET csrf_hash=$2 WHERE token_hash=$1 AND issuer=$3 AND subject=$4 AND revoked_at IS NULL AND expires_at>now()`, digest([]byte(c.Value)), digest([]byte(token)), p.Issuer, p.Subject) if err != nil { return nil, err } } return map[string]any{"csrf_token": token, "authenticated": true}, nil } func (s *Service) logoutHTTP(w http.ResponseWriter, r *http.Request) (any, error) { if !s.sameOrigin(r) { return nil, errAccess } raw, err := readBody(r) if err != nil { return nil, err } if err = StrictJSON(raw, &struct{}{}); err != nil { return nil, err } // Expired cookies can be cleared without treating them as a live login. for _, account := range s.logoutKinds() { c, e := r.Cookie(s.cookieName(account)) if e != nil { continue } table := "onboarding_web_session" if account { table = s.accountSessionTable() } var live bool err = s.pool.QueryRow(r.Context(), `SELECT EXISTS(SELECT 1 FROM `+table+` WHERE token_hash=$1 AND revoked_at IS NULL AND expires_at>now())`, digest([]byte(c.Value))).Scan(&live) if err != nil { return nil, err } if live { var valid bool err = s.pool.QueryRow(r.Context(), `SELECT EXISTS(SELECT 1 FROM `+table+` WHERE token_hash=$1 AND csrf_hash=$2)`, digest([]byte(c.Value)), digest([]byte(r.Header.Get("X-CSRF-Token")))).Scan(&valid) if err != nil { return nil, err } if !valid { return nil, errAccess } } } for _, account := range s.logoutKinds() { c, e := r.Cookie(s.cookieName(account)) if e != nil { continue } table := "onboarding_web_session" if account { table = s.accountSessionTable() } if _, err = s.pool.Exec(r.Context(), `UPDATE `+table+` SET revoked_at=COALESCE(revoked_at,now()) WHERE token_hash=$1`, digest([]byte(c.Value))); err != nil { return nil, err } } for _, account := range s.logoutKinds() { http.SetCookie(w, &http.Cookie{Name: s.cookieName(account), Value: "", Path: "/", HttpOnly: true, Secure: !s.options.AllowLocalHTTP, SameSite: http.SameSiteLaxMode, MaxAge: -1}) } return map[string]any{"signed_out": true}, nil }