Files
story/internal/onboarding/logout.go
T

85 lines
2.9 KiB
Go
Raw Normal View History

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
}