85 lines
2.9 KiB
Go
85 lines
2.9 KiB
Go
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 := randomToken(32)
|
|
_, err = s.pool.Exec(r.Context(), `UPDATE onboarding_account_session 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)); 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 []bool{true, false} {
|
|
c, e := r.Cookie(s.cookieName(account))
|
|
if e != nil {
|
|
continue
|
|
}
|
|
table := "onboarding_web_session"
|
|
if account {
|
|
table = "onboarding_account_session"
|
|
}
|
|
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 []bool{true, false} {
|
|
c, e := r.Cookie(s.cookieName(account))
|
|
if e != nil {
|
|
continue
|
|
}
|
|
table := "onboarding_web_session"
|
|
if account {
|
|
table = "onboarding_account_session"
|
|
}
|
|
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 []bool{false, true} {
|
|
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
|
|
}
|