package store import ( "crypto/rand" "crypto/sha256" "database/sql" "encoding/hex" "errors" "time" ) // ---- 认证与租户(阶段 1:API key 认证,user 即租户 owner)---- // key 明文仅创建时返回一次;库中只存 SHA-256 摘要(数据可靠性标准:不落明文凭证)。 // User 是租户账户(owner 全局唯一;id 供后续扩展用户目录)。 type User struct { ID string `json:"id"` Owner string `json:"owner"` Name string `json:"name"` CreatedAt time.Time `json:"createdAt"` } // APIKeyInfo 是密钥的展示视图(不含摘要)。 type APIKeyInfo struct { Name string `json:"name"` Owner string `json:"owner"` CreatedAt time.Time `json:"createdAt"` LastUsedAt *time.Time `json:"lastUsedAt,omitempty"` Revoked bool `json:"revoked"` } func hashKey(plain string) string { sum := sha256.Sum256([]byte(plain)) return hex.EncodeToString(sum[:]) } // EnsureUser 幂等创建租户账户(owner 唯一)。 func (s *Store) EnsureUser(owner, name string) error { _, err := s.db.Exec(s.q(`INSERT INTO users(id,owner,name,created_at) VALUES(?,?,?,?) ON CONFLICT(owner) DO NOTHING`), newID("usr"), owner, name, nowISO()) return err } func (s *Store) GetUser(owner string) (User, error) { var u User var ca string err := s.db.QueryRow(s.q(`SELECT id,owner,name,created_at FROM users WHERE owner=?`), owner).Scan(&u.ID, &u.Owner, &u.Name, &ca) if errors.Is(err, sql.ErrNoRows) { return u, ErrNotFound } if err != nil { return u, err } u.CreatedAt, _ = parseTime(ca) return u, nil } func (s *Store) ListUsers() ([]User, error) { rows, err := s.db.Query(s.q(`SELECT id,owner,name,created_at FROM users ORDER BY created_at`)) if err != nil { return nil, err } defer rows.Close() var out []User for rows.Next() { var u User var ca string if err := rows.Scan(&u.ID, &u.Owner, &u.Name, &ca); err != nil { return nil, err } u.CreatedAt, _ = parseTime(ca) out = append(out, u) } return out, rows.Err() } // CreateAPIKey 创建密钥:明文 `ask_` 仅此一次返回。 func (s *Store) CreateAPIKey(owner, name string) (string, error) { if err := s.EnsureUser(owner, ""); err != nil { return "", err } raw := make([]byte, 16) if _, err := rand.Read(raw); err != nil { return "", err } plain := "ask_" + hex.EncodeToString(raw) if name == "" { name = "key-" + nowISO()[:10] } _, err := s.db.Exec(s.q(`INSERT INTO api_keys(key_hash,owner,name,created_at,revoked) VALUES(?,?,?,?,0)`), hashKey(plain), owner, name, nowISO()) if err != nil { return "", err } return plain, nil } // RevokeAPIKeyByName 撤销指定名称的未撤销密钥(返回是否命中)。 func (s *Store) RevokeAPIKeyByName(owner, name string) (bool, error) { res, err := s.db.Exec(s.q(`UPDATE api_keys SET revoked=1 WHERE owner=? AND name=? AND revoked=0`), owner, name) if err != nil { return false, err } n, _ := res.RowsAffected() return n > 0, nil } func (s *Store) ListAPIKeys(owner string) ([]APIKeyInfo, error) { rows, err := s.db.Query(s.q(`SELECT name,owner,created_at,last_used_at,revoked FROM api_keys WHERE owner=? ORDER BY created_at`), owner) if err != nil { return nil, err } defer rows.Close() var out []APIKeyInfo for rows.Next() { var k APIKeyInfo var ca string var last sql.NullString if err := rows.Scan(&k.Name, &k.Owner, &ca, &last, &k.Revoked); err != nil { return nil, err } k.CreatedAt, _ = parseTime(ca) if last.Valid && last.String != "" { t, _ := parseTime(last.String) k.LastUsedAt = &t } out = append(out, k) } return out, rows.Err() } // AuthOwner 校验 API key:命中返回 owner;未命中/已撤销返回 false。 // last_used_at 尽力刷新(失败不影响认证结果)。 func (s *Store) AuthOwner(plainKey string) (string, bool) { if plainKey == "" { return "", false } var owner string var revoked int err := s.db.QueryRow(s.q(`SELECT owner,revoked FROM api_keys WHERE key_hash=?`), hashKey(plainKey)).Scan(&owner, &revoked) if err != nil || revoked == 1 { return "", false } s.db.Exec(s.q(`UPDATE api_keys SET last_used_at=? WHERE key_hash=?`), nowISO(), hashKey(plainKey)) return owner, true }