package service import ( "crypto/sha256" "crypto/subtle" "encoding/hex" "errors" "strings" "time" "browser.local/platform/domain" "browser.local/platform/repo" "browser.local/platform/validator" ) const defaultAuthSessionTTL = 8 * time.Hour func (svc *CoreService) issueAuthSession(user domain.User, message string) (domain.AuthSession, error) { token, err := randomToken() if err != nil { return domain.AuthSession{}, err } hash := tokenHash(token) stamp := svc.now() generation := 1 existing, err := svc.store.AuthSessions().List(domain.AuthSessionFilter{UserID: user.ID}) if err != nil { return domain.AuthSession{}, err } for _, session := range existing { if session.Generation >= generation { generation = session.Generation + 1 } } record := domain.AuthSessionRecord{ ID: "auth-session-" + hash[:24], UserID: user.ID, TokenHash: hash, Status: domain.AuthSessionStatusActive, Generation: generation, IssuedAt: stamp, ExpiresAt: stamp.Add(defaultAuthSessionTTL), LastSeenAt: stamp, } if err := validator.ValidateAuthSessionRecord(record); err != nil { return domain.AuthSession{}, err } if err := svc.store.AuthSessions().Create(record); err != nil { return domain.AuthSession{}, err } svc.authMu.Lock() svc.authSessions[token] = user.ID svc.authMu.Unlock() return domain.AuthSession{ SessionID: token, User: domain.CopyUser(user), Status: "authenticated", Message: message, ExpiresAt: record.ExpiresAt, }, nil } func (svc *CoreService) authenticatedSession(token string) (domain.AuthSessionRecord, error) { token = strings.TrimSpace(token) if token == "" { return domain.AuthSessionRecord{}, ErrUnauthorized } hash := tokenHash(token) sessions, err := svc.store.AuthSessions().List(domain.AuthSessionFilter{TokenHash: hash}) if err != nil { return domain.AuthSessionRecord{}, err } if len(sessions) != 1 || subtle.ConstantTimeCompare([]byte(sessions[0].TokenHash), []byte(hash)) != 1 { return domain.AuthSessionRecord{}, ErrUnauthorized } session := sessions[0] stamp := svc.now() if session.Status != domain.AuthSessionStatusActive || !session.RevokedAt.IsZero() || !stamp.Before(session.ExpiresAt) { if session.Status == domain.AuthSessionStatusActive && !stamp.Before(session.ExpiresAt) { session.Status = domain.AuthSessionStatusRevoked session.RevokedAt = stamp _ = svc.store.AuthSessions().Update(session) } return domain.AuthSessionRecord{}, ErrUnauthorized } user, err := svc.store.Users().Get(session.UserID) if err != nil { if errors.Is(err, repo.ErrNotFound) { return domain.AuthSessionRecord{}, ErrUnauthorized } return domain.AuthSessionRecord{}, err } if user.Status != domain.UserStatusActive { return domain.AuthSessionRecord{}, ErrUnauthorized } if session.LastSeenAt.IsZero() || stamp.Sub(session.LastSeenAt) >= time.Minute { session.LastSeenAt = stamp if err := svc.store.AuthSessions().Update(session); err != nil { return domain.AuthSessionRecord{}, err } } return domain.CopyAuthSessionRecord(session), nil } func (svc *CoreService) revokeAuthSession(token string) error { session, err := svc.authenticatedSession(token) if err != nil { return err } stamp := svc.now() session.Status = domain.AuthSessionStatusRevoked session.RevokedAt = stamp session.LastSeenAt = stamp if err := validator.ValidateAuthSessionRecord(session); err != nil { return err } if err := svc.store.AuthSessions().Update(session); err != nil { return err } svc.authMu.Lock() delete(svc.authSessions, token) svc.authMu.Unlock() return nil } func (svc *CoreService) RotateUserSession(token string) (domain.AuthSession, error) { session, err := svc.authenticatedSession(token) if err != nil { return domain.AuthSession{}, err } user, err := svc.store.Users().Get(session.UserID) if err != nil { return domain.AuthSession{}, err } if err := svc.revokeAuthSession(token); err != nil { return domain.AuthSession{}, err } return svc.issueAuthSession(user, "会话已安全轮换") } func (svc *CoreService) revokeUserSessions(userID string) error { sessions, err := svc.store.AuthSessions().List(domain.AuthSessionFilter{UserID: userID, Status: domain.AuthSessionStatusActive}) if err != nil { return err } stamp := svc.now() for _, session := range sessions { session.Status = domain.AuthSessionStatusRevoked session.RevokedAt = stamp session.LastSeenAt = stamp if err := validator.ValidateAuthSessionRecord(session); err != nil { return err } if err := svc.store.AuthSessions().Update(session); err != nil { return err } } return nil } func tokenHash(token string) string { sum := sha256.Sum256([]byte(token)) return hex.EncodeToString(sum[:]) }