package auth import ( "context" "crypto/sha256" "encoding/hex" "errors" "time" ) var errSessionNotFound = errors.New("session not found or expired") const ( defaultIdleSessionTTL = 24 * time.Hour defaultAbsoluteSessionTTL = 7 * 24 * time.Hour ) // SessionMeta 创建会话时的客户端元数据(可选)。 type SessionMeta struct { IPHash string UserAgent string } // SessionRecord 用户可见的活跃会话摘要。 type SessionRecord struct { ID string `json:"id"` CreatedAt string `json:"created_at"` LastSeenAt string `json:"last_seen_at"` UserAgent string `json:"user_agent"` IsCurrent bool `json:"is_current"` } // SessionStore 持久化或内存会话存储。 type SessionStore interface { CreateSession(ctx context.Context, userID int64, tokenHash string, meta SessionMeta) error ResolveUserID(ctx context.Context, tokenHash string) (int64, error) TouchSession(ctx context.Context, tokenHash string) error RevokeByTokenHash(ctx context.Context, tokenHash string) error RevokeAllForUser(ctx context.Context, userID int64) error ListActiveSessions(ctx context.Context, userID int64, currentTokenHash string) ([]SessionRecord, error) RevokeSessionByID(ctx context.Context, userID int64, sessionID string) error } // HashToken 将会话 token 转为存储用 SHA-256 十六进制摘要。 func HashToken(token string) string { sum := sha256.Sum256([]byte(token)) return hex.EncodeToString(sum[:]) } type memorySessionEntry struct { id string userID int64 createdAt time.Time lastSeenAt time.Time userAgent string } // MemorySessionStore 进程内 token 映射(仅 dev/stub fallback)。 type MemorySessionStore struct { tokens map[string]memorySessionEntry } func NewMemorySessionStore() *MemorySessionStore { return &MemorySessionStore{tokens: make(map[string]memorySessionEntry)} } func (s *MemorySessionStore) CreateSession(_ context.Context, userID int64, tokenHash string, meta SessionMeta) error { now := time.Now() s.tokens[tokenHash] = memorySessionEntry{ id: tokenHash[:12], userID: userID, createdAt: now, lastSeenAt: now, userAgent: meta.UserAgent, } return nil } func (s *MemorySessionStore) ResolveUserID(_ context.Context, tokenHash string) (int64, error) { entry, ok := s.tokens[tokenHash] if !ok { return 0, errSessionNotFound } return entry.userID, nil } func (s *MemorySessionStore) TouchSession(_ context.Context, _ string) error { return nil } func (s *MemorySessionStore) RevokeByTokenHash(_ context.Context, tokenHash string) error { delete(s.tokens, tokenHash) return nil } func (s *MemorySessionStore) RevokeAllForUser(_ context.Context, userID int64) error { for tok, entry := range s.tokens { if entry.userID == userID { delete(s.tokens, tok) } } return nil } func (s *MemorySessionStore) ListActiveSessions(_ context.Context, userID int64, currentTokenHash string) ([]SessionRecord, error) { out := make([]SessionRecord, 0) for tok, entry := range s.tokens { if entry.userID != userID { continue } out = append(out, SessionRecord{ ID: entry.id, CreatedAt: entry.createdAt.Format("2006-01-02 15:04:05"), LastSeenAt: entry.lastSeenAt.Format("2006-01-02 15:04:05"), UserAgent: entry.userAgent, IsCurrent: currentTokenHash != "" && tok == currentTokenHash, }) } return out, nil } func (s *MemorySessionStore) RevokeSessionByID(_ context.Context, userID int64, sessionID string) error { for tok, entry := range s.tokens { if entry.userID == userID && entry.id == sessionID { delete(s.tokens, tok) return nil } } return errSessionNotFound }