devices.go 2.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889
  1. package auth
  2. import (
  3. "context"
  4. "database/sql"
  5. "errors"
  6. "fmt"
  7. "time"
  8. )
  9. // 授信设备管理(api.md 第四节 / auth.md 第四节)。
  10. //
  11. // 不提供创建设备接口:授信只能在登录时通过 trust_device=true 在那台
  12. // 浏览器上创建(api.md 4.2 说明)。
  13. // ErrTrustedDeviceNotFound 表示设备不存在或不属于当前用户(404)。
  14. var ErrTrustedDeviceNotFound = errors.New("授信设备不存在")
  15. // TrustedDeviceSummary 是 GET /api/trusted-devices 的列表项(api.md 4.1)。
  16. type TrustedDeviceSummary struct {
  17. ID string `json:"id"`
  18. DeviceName string `json:"device_name"`
  19. CreatedAt time.Time `json:"created_at"`
  20. LastUsedAt time.Time `json:"last_used_at"`
  21. ExpiresAt time.Time `json:"expires_at"`
  22. }
  23. // ListTrustedDevices 返回当前用户的全部授信设备,按最近使用倒序。
  24. func (s *Service) ListTrustedDevices(ctx context.Context, userID string) ([]TrustedDeviceSummary, error) {
  25. const q = `
  26. SELECT id, device_name, created_at, last_used_at, expires_at
  27. FROM trusted_devices
  28. WHERE user_id = $1
  29. ORDER BY last_used_at DESC`
  30. rows, err := s.db.QueryContext(ctx, q, userID)
  31. if err != nil {
  32. return nil, fmt.Errorf("查询授信设备失败: %w", err)
  33. }
  34. defer rows.Close()
  35. out := make([]TrustedDeviceSummary, 0, 4)
  36. for rows.Next() {
  37. var item TrustedDeviceSummary
  38. if err := rows.Scan(&item.ID, &item.DeviceName, &item.CreatedAt, &item.LastUsedAt, &item.ExpiresAt); err != nil {
  39. return nil, fmt.Errorf("扫描授信设备失败: %w", err)
  40. }
  41. out = append(out, item)
  42. }
  43. if err := rows.Err(); err != nil {
  44. return nil, fmt.Errorf("读取授信设备失败: %w", err)
  45. }
  46. return out, nil
  47. }
  48. // FindTrustedDevice 返回设备的展示名,用于撤销时写审计事件(audit.md 第三节)。
  49. func (s *Service) FindTrustedDevice(ctx context.Context, userID, deviceID string) (TrustedDeviceSummary, error) {
  50. const q = `
  51. SELECT id, device_name, created_at, last_used_at, expires_at
  52. FROM trusted_devices WHERE id = $1 AND user_id = $2`
  53. var item TrustedDeviceSummary
  54. err := s.db.QueryRowContext(ctx, q, deviceID, userID).Scan(
  55. &item.ID, &item.DeviceName, &item.CreatedAt, &item.LastUsedAt, &item.ExpiresAt)
  56. if errors.Is(err, sql.ErrNoRows) {
  57. return TrustedDeviceSummary{}, ErrTrustedDeviceNotFound
  58. }
  59. if err != nil {
  60. return TrustedDeviceSummary{}, fmt.Errorf("查询授信设备失败: %w", err)
  61. }
  62. return item, nil
  63. }
  64. // RevokeTrustedDevice 撤销即硬删除(api.md 4.2)。
  65. func (s *Service) RevokeTrustedDevice(ctx context.Context, userID, deviceID string) error {
  66. const q = `DELETE FROM trusted_devices WHERE id = $1 AND user_id = $2`
  67. res, err := s.db.ExecContext(ctx, q, deviceID, userID)
  68. if err != nil {
  69. return fmt.Errorf("删除授信设备失败: %w", err)
  70. }
  71. n, err := res.RowsAffected()
  72. if err != nil {
  73. return fmt.Errorf("读取删除结果失败: %w", err)
  74. }
  75. if n == 0 {
  76. return ErrTrustedDeviceNotFound
  77. }
  78. return nil
  79. }
苏ICP备2026082406号
苏公网安备 待审核