web_api_store.go 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. package state
  2. import (
  3. "context"
  4. "database/sql"
  5. "encoding/json"
  6. "errors"
  7. "fmt"
  8. "time"
  9. )
  10. var (
  11. // ErrDupAPIKey is returned when attempting to insert a duplicate API key.
  12. ErrDupAPIKey = errors.New("API key already exists")
  13. // ErrNoAPIKey is returned when an API key is not found.
  14. ErrNoAPIKey = errors.New("API key not found")
  15. )
  16. // WebAPIKey represents a Web API authentication key.
  17. type WebAPIKey struct {
  18. DevID string `json:"dev_id"`
  19. DevKey string `json:"dev_key"`
  20. AppName string `json:"app_name"`
  21. CreatedAt time.Time `json:"created_at"`
  22. IsActive bool `json:"is_active"`
  23. RateLimit int `json:"rate_limit"`
  24. AllowedOrigins []string `json:"allowed_origins"`
  25. Capabilities []string `json:"capabilities"`
  26. }
  27. // WebAPIKeyUpdate represents fields that can be updated for an API key.
  28. type WebAPIKeyUpdate struct {
  29. AppName *string `json:"app_name,omitempty"`
  30. IsActive *bool `json:"is_active,omitempty"`
  31. RateLimit *int `json:"rate_limit,omitempty"`
  32. AllowedOrigins *[]string `json:"allowed_origins,omitempty"`
  33. Capabilities *[]string `json:"capabilities,omitempty"`
  34. }
  35. // CreateAPIKey inserts a new API key into the database.
  36. func (f SQLiteUserStore) CreateAPIKey(ctx context.Context, key WebAPIKey) error {
  37. originsJSON, err := json.Marshal(key.AllowedOrigins)
  38. if err != nil {
  39. return fmt.Errorf("failed to marshal allowed origins: %w", err)
  40. }
  41. capabilitiesJSON, err := json.Marshal(key.Capabilities)
  42. if err != nil {
  43. return fmt.Errorf("failed to marshal capabilities: %w", err)
  44. }
  45. q := `
  46. INSERT INTO web_api_keys (dev_id, dev_key, app_name, created_at, is_active, rate_limit, allowed_origins, capabilities)
  47. VALUES (?, ?, ?, ?, ?, ?, ?, ?)
  48. ON CONFLICT (dev_id) DO NOTHING
  49. `
  50. result, err := f.db.ExecContext(ctx,
  51. q,
  52. key.DevID,
  53. key.DevKey,
  54. key.AppName,
  55. key.CreatedAt.Unix(),
  56. key.IsActive,
  57. key.RateLimit,
  58. string(originsJSON),
  59. string(capabilitiesJSON),
  60. )
  61. if err != nil {
  62. return err
  63. }
  64. rowsAffected, err := result.RowsAffected()
  65. if err != nil {
  66. return err
  67. }
  68. if rowsAffected == 0 {
  69. return ErrDupAPIKey
  70. }
  71. return nil
  72. }
  73. // GetAPIKeyByDevKey retrieves an API key by its dev_key value.
  74. func (f *SQLiteUserStore) GetAPIKeyByDevKey(ctx context.Context, devKey string) (*WebAPIKey, error) {
  75. q := `
  76. SELECT dev_id, dev_key, app_name, created_at, is_active, rate_limit, allowed_origins, capabilities
  77. FROM web_api_keys
  78. WHERE dev_key = ? AND is_active = 1
  79. `
  80. var key WebAPIKey
  81. var createdAt sql.NullInt64
  82. var originsJSON, capabilitiesJSON string
  83. err := f.db.QueryRowContext(ctx, q, devKey).Scan(
  84. &key.DevID,
  85. &key.DevKey,
  86. &key.AppName,
  87. &createdAt,
  88. &key.IsActive,
  89. &key.RateLimit,
  90. &originsJSON,
  91. &capabilitiesJSON,
  92. )
  93. if err == sql.ErrNoRows {
  94. return nil, ErrNoAPIKey
  95. }
  96. if err != nil {
  97. return nil, err
  98. }
  99. key.CreatedAt = time.Unix(createdAt.Int64, 0)
  100. if err := json.Unmarshal([]byte(originsJSON), &key.AllowedOrigins); err != nil {
  101. return nil, fmt.Errorf("failed to unmarshal allowed origins: %w", err)
  102. }
  103. if err := json.Unmarshal([]byte(capabilitiesJSON), &key.Capabilities); err != nil {
  104. return nil, fmt.Errorf("failed to unmarshal capabilities: %w", err)
  105. }
  106. return &key, nil
  107. }
  108. // GetAPIKeyByDevID retrieves an API key by its dev_id value.
  109. func (f SQLiteUserStore) GetAPIKeyByDevID(ctx context.Context, devID string) (*WebAPIKey, error) {
  110. q := `
  111. SELECT dev_id, dev_key, app_name, created_at, is_active, rate_limit, allowed_origins, capabilities
  112. FROM web_api_keys
  113. WHERE dev_id = ?
  114. `
  115. var key WebAPIKey
  116. var createdAt sql.NullInt64
  117. var originsJSON, capabilitiesJSON string
  118. err := f.db.QueryRowContext(ctx, q, devID).Scan(
  119. &key.DevID,
  120. &key.DevKey,
  121. &key.AppName,
  122. &createdAt,
  123. &key.IsActive,
  124. &key.RateLimit,
  125. &originsJSON,
  126. &capabilitiesJSON,
  127. )
  128. if err == sql.ErrNoRows {
  129. return nil, ErrNoAPIKey
  130. }
  131. if err != nil {
  132. return nil, err
  133. }
  134. key.CreatedAt = time.Unix(createdAt.Int64, 0)
  135. if err := json.Unmarshal([]byte(originsJSON), &key.AllowedOrigins); err != nil {
  136. return nil, fmt.Errorf("failed to unmarshal allowed origins: %w", err)
  137. }
  138. if err := json.Unmarshal([]byte(capabilitiesJSON), &key.Capabilities); err != nil {
  139. return nil, fmt.Errorf("failed to unmarshal capabilities: %w", err)
  140. }
  141. return &key, nil
  142. }
  143. // ListAPIKeys retrieves all API keys from the database.
  144. func (f SQLiteUserStore) ListAPIKeys(ctx context.Context) ([]WebAPIKey, error) {
  145. q := `
  146. SELECT dev_id, dev_key, app_name, created_at, is_active, rate_limit, allowed_origins, capabilities
  147. FROM web_api_keys
  148. ORDER BY created_at DESC
  149. `
  150. rows, err := f.db.QueryContext(ctx, q)
  151. if err != nil {
  152. return nil, err
  153. }
  154. defer rows.Close()
  155. var keys []WebAPIKey
  156. for rows.Next() {
  157. var key WebAPIKey
  158. var createdAt sql.NullInt64
  159. var originsJSON, capabilitiesJSON string
  160. err := rows.Scan(
  161. &key.DevID,
  162. &key.DevKey,
  163. &key.AppName,
  164. &createdAt,
  165. &key.IsActive,
  166. &key.RateLimit,
  167. &originsJSON,
  168. &capabilitiesJSON,
  169. )
  170. if err != nil {
  171. return nil, err
  172. }
  173. key.CreatedAt = time.Unix(createdAt.Int64, 0)
  174. if err := json.Unmarshal([]byte(originsJSON), &key.AllowedOrigins); err != nil {
  175. return nil, fmt.Errorf("failed to unmarshal allowed origins: %w", err)
  176. }
  177. if err := json.Unmarshal([]byte(capabilitiesJSON), &key.Capabilities); err != nil {
  178. return nil, fmt.Errorf("failed to unmarshal capabilities: %w", err)
  179. }
  180. keys = append(keys, key)
  181. }
  182. if err = rows.Err(); err != nil {
  183. return nil, err
  184. }
  185. return keys, nil
  186. }
  187. // UpdateAPIKey updates an existing API key's fields.
  188. func (f SQLiteUserStore) UpdateAPIKey(ctx context.Context, devID string, updates WebAPIKeyUpdate) error {
  189. // Build dynamic UPDATE query based on provided fields
  190. var setClauses []string
  191. var args []interface{}
  192. if updates.AppName != nil {
  193. setClauses = append(setClauses, "app_name = ?")
  194. args = append(args, *updates.AppName)
  195. }
  196. if updates.IsActive != nil {
  197. setClauses = append(setClauses, "is_active = ?")
  198. args = append(args, *updates.IsActive)
  199. }
  200. if updates.RateLimit != nil {
  201. setClauses = append(setClauses, "rate_limit = ?")
  202. args = append(args, *updates.RateLimit)
  203. }
  204. if updates.AllowedOrigins != nil {
  205. originsJSON, err := json.Marshal(*updates.AllowedOrigins)
  206. if err != nil {
  207. return fmt.Errorf("failed to marshal allowed origins: %w", err)
  208. }
  209. setClauses = append(setClauses, "allowed_origins = ?")
  210. args = append(args, string(originsJSON))
  211. }
  212. if updates.Capabilities != nil {
  213. capabilitiesJSON, err := json.Marshal(*updates.Capabilities)
  214. if err != nil {
  215. return fmt.Errorf("failed to marshal capabilities: %w", err)
  216. }
  217. setClauses = append(setClauses, "capabilities = ?")
  218. args = append(args, string(capabilitiesJSON))
  219. }
  220. if len(setClauses) == 0 {
  221. return nil // No updates to perform
  222. }
  223. // Add WHERE clause argument
  224. args = append(args, devID)
  225. q := fmt.Sprintf(`
  226. UPDATE web_api_keys
  227. SET %s
  228. WHERE dev_id = ?
  229. `, joinStrings(setClauses, ", "))
  230. result, err := f.db.ExecContext(ctx, q, args...)
  231. if err != nil {
  232. return err
  233. }
  234. rowsAffected, err := result.RowsAffected()
  235. if err != nil {
  236. return err
  237. }
  238. if rowsAffected == 0 {
  239. return ErrNoAPIKey
  240. }
  241. return nil
  242. }
  243. // DeleteAPIKey removes an API key from the database.
  244. func (f SQLiteUserStore) DeleteAPIKey(ctx context.Context, devID string) error {
  245. q := `
  246. DELETE FROM web_api_keys WHERE dev_id = ?
  247. `
  248. result, err := f.db.ExecContext(ctx, q, devID)
  249. if err != nil {
  250. return err
  251. }
  252. rowsAffected, err := result.RowsAffected()
  253. if err != nil {
  254. return err
  255. }
  256. if rowsAffected == 0 {
  257. return ErrNoAPIKey
  258. }
  259. return nil
  260. }
  261. // joinStrings is a helper function to join strings with a separator.
  262. func joinStrings(strs []string, sep string) string {
  263. if len(strs) == 0 {
  264. return ""
  265. }
  266. result := strs[0]
  267. for i := 1; i < len(strs); i++ {
  268. result += sep + strs[i]
  269. }
  270. return result
  271. }