opensentinel 2.1.1 → 3.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (268) hide show
  1. package/README.md +354 -283
  2. package/dist/archiver-AVNBYCKQ.js +15340 -0
  3. package/dist/archiver-AVNBYCKQ.js.map +1 -0
  4. package/dist/audit-logger-OBPR7CRO.js +22 -0
  5. package/dist/auth-UOX5K2BE.js +18 -0
  6. package/dist/autonomy-ZXDBDQUJ.js +86 -0
  7. package/dist/autonomy-ZXDBDQUJ.js.map +1 -0
  8. package/dist/aws-s3-Q4LLZZPD.js +146 -0
  9. package/dist/aws-s3-Q4LLZZPD.js.map +1 -0
  10. package/dist/backup-restore-PZ7CYYB7.js +16 -0
  11. package/dist/blocks-R3PODY47.js +23 -0
  12. package/dist/bot-QRARP4UN.js +36 -0
  13. package/dist/brain-7XLLM3KC.js +56 -0
  14. package/dist/camera-monitor-M5CYKUU4.js +335 -0
  15. package/dist/camera-monitor-M5CYKUU4.js.map +1 -0
  16. package/dist/{charts-MMXM6BWW.js → charts-V7ARZNKF.js} +2 -2
  17. package/dist/chunk-22VGGA7S.js +330 -0
  18. package/dist/chunk-22VGGA7S.js.map +1 -0
  19. package/dist/chunk-35WYTA3C.js +382 -0
  20. package/dist/chunk-35WYTA3C.js.map +1 -0
  21. package/dist/chunk-3E2PSU2C.js +146 -0
  22. package/dist/chunk-3E2PSU2C.js.map +1 -0
  23. package/dist/{chunk-L3F43VPB.js → chunk-4GLYY4NN.js} +2 -2
  24. package/dist/{chunk-L3F43VPB.js.map → chunk-4GLYY4NN.js.map} +1 -1
  25. package/dist/{chunk-L3PDU3XN.js → chunk-4UOE5TUZ.js} +4 -4
  26. package/dist/{chunk-6SNHU3CY.js → chunk-66OJ3WB4.js} +2 -2
  27. package/dist/chunk-6KONMXQ6.js +297 -0
  28. package/dist/chunk-6KONMXQ6.js.map +1 -0
  29. package/dist/chunk-6PMVAAA7.js +196 -0
  30. package/dist/chunk-6PMVAAA7.js.map +1 -0
  31. package/dist/chunk-766ASQWE.js +32620 -0
  32. package/dist/chunk-766ASQWE.js.map +1 -0
  33. package/dist/chunk-7WQO5J2M.js +29 -0
  34. package/dist/chunk-7WQO5J2M.js.map +1 -0
  35. package/dist/chunk-APHSRMBS.js +148 -0
  36. package/dist/chunk-APHSRMBS.js.map +1 -0
  37. package/dist/{chunk-4LVWXUNC.js → chunk-AYUKPTSM.js} +57 -39
  38. package/dist/chunk-AYUKPTSM.js.map +1 -0
  39. package/dist/chunk-BIPYADGB.js +84 -0
  40. package/dist/chunk-BIPYADGB.js.map +1 -0
  41. package/dist/chunk-BRBWNV65.js +457 -0
  42. package/dist/chunk-BRBWNV65.js.map +1 -0
  43. package/dist/chunk-BXZ6EA52.js +382 -0
  44. package/dist/chunk-BXZ6EA52.js.map +1 -0
  45. package/dist/chunk-EVE7MIIY.js +290 -0
  46. package/dist/chunk-EVE7MIIY.js.map +1 -0
  47. package/dist/chunk-F3TTNID2.js +138 -0
  48. package/dist/chunk-F3TTNID2.js.map +1 -0
  49. package/dist/chunk-H5RQOFO2.js +190 -0
  50. package/dist/chunk-H5RQOFO2.js.map +1 -0
  51. package/dist/chunk-HN3F4WSW.js +145 -0
  52. package/dist/chunk-HN3F4WSW.js.map +1 -0
  53. package/dist/{chunk-6DRDKB45.js → chunk-I6BDYQIG.js} +20 -9
  54. package/dist/chunk-I6BDYQIG.js.map +1 -0
  55. package/dist/chunk-IZJMVV7O.js +347 -0
  56. package/dist/chunk-IZJMVV7O.js.map +1 -0
  57. package/dist/chunk-KM22GV7G.js +211 -0
  58. package/dist/chunk-KM22GV7G.js.map +1 -0
  59. package/dist/chunk-MGFBLVR7.js +103 -0
  60. package/dist/chunk-MGFBLVR7.js.map +1 -0
  61. package/dist/chunk-MQJ2ECQT.js +228 -0
  62. package/dist/chunk-MQJ2ECQT.js.map +1 -0
  63. package/dist/{chunk-F6QUZQGI.js → chunk-MXAPLSJ5.js} +2 -2
  64. package/dist/{chunk-GK3E2I7A.js → chunk-NHMBTUMW.js} +2 -2
  65. package/dist/chunk-NPRTSZIF.js +131 -0
  66. package/dist/chunk-NPRTSZIF.js.map +1 -0
  67. package/dist/chunk-O7IH7JTI.js +1898 -0
  68. package/dist/chunk-O7IH7JTI.js.map +1 -0
  69. package/dist/chunk-OCVQGBJK.js +293 -0
  70. package/dist/chunk-OCVQGBJK.js.map +1 -0
  71. package/dist/chunk-P6QINGFL.js +332 -0
  72. package/dist/chunk-P6QINGFL.js.map +1 -0
  73. package/dist/chunk-PHDZKPNE.js +91 -0
  74. package/dist/chunk-PHDZKPNE.js.map +1 -0
  75. package/dist/chunk-PLDDJCW6.js +49 -0
  76. package/dist/chunk-PTGTGXV2.js +164 -0
  77. package/dist/chunk-PTGTGXV2.js.map +1 -0
  78. package/dist/chunk-REMIY4U2.js +171 -0
  79. package/dist/chunk-REMIY4U2.js.map +1 -0
  80. package/dist/chunk-RZ4YESBG.js +141 -0
  81. package/dist/chunk-RZ4YESBG.js.map +1 -0
  82. package/dist/chunk-SAX5MHK4.js +111 -0
  83. package/dist/chunk-SAX5MHK4.js.map +1 -0
  84. package/dist/{chunk-GVJVEWHI.js → chunk-SJSUSJ47.js} +2 -2
  85. package/dist/chunk-SPPMCAKG.js +777 -0
  86. package/dist/chunk-SPPMCAKG.js.map +1 -0
  87. package/dist/chunk-SVAPX2XN.js +2441 -0
  88. package/dist/chunk-SVAPX2XN.js.map +1 -0
  89. package/dist/chunk-TVEWKIK3.js +452 -0
  90. package/dist/chunk-TVEWKIK3.js.map +1 -0
  91. package/dist/{chunk-HH2HBTQM.js → chunk-TYAGMJNV.js} +5 -5
  92. package/dist/{chunk-JXUP2X7V.js → chunk-VEHFVBLI.js} +2 -2
  93. package/dist/chunk-VNX5GMTN.js +128 -0
  94. package/dist/chunk-VNX5GMTN.js.map +1 -0
  95. package/dist/chunk-VRD5CYRL.js +1568 -0
  96. package/dist/chunk-VRD5CYRL.js.map +1 -0
  97. package/dist/chunk-WLUHNG6X.js +122 -0
  98. package/dist/chunk-WLUHNG6X.js.map +1 -0
  99. package/dist/chunk-WRAKK6K6.js +265 -0
  100. package/dist/chunk-WRAKK6K6.js.map +1 -0
  101. package/dist/chunk-XKYRH4FM.js +681 -0
  102. package/dist/chunk-XKYRH4FM.js.map +1 -0
  103. package/dist/{chunk-GUBEEYDW.js → chunk-XMCVRVTF.js} +2 -2
  104. package/dist/{chunk-GUBEEYDW.js.map → chunk-XMCVRVTF.js.map} +1 -1
  105. package/dist/chunk-ZLZKF2PM.js +310 -0
  106. package/dist/chunk-ZLZKF2PM.js.map +1 -0
  107. package/dist/cli.js +5 -1
  108. package/dist/cli.js.map +1 -1
  109. package/dist/client-ZQSFPMOB.js +21 -0
  110. package/dist/clipboard-manager-TEO2GEDN.js +24 -0
  111. package/dist/commands/setup.js +3 -3
  112. package/dist/commands/setup.js.map +1 -1
  113. package/dist/commands/start.js +3 -3
  114. package/dist/commands/status.js +2 -2
  115. package/dist/commands/stop.js +2 -2
  116. package/dist/commands/utils.js +2 -2
  117. package/dist/cron-explain-HHQKPD3M.js +16 -0
  118. package/dist/crypto-4AP47IKC.js +14 -0
  119. package/dist/crypto-4AP47IKC.js.map +1 -0
  120. package/dist/databases-37X4CI2Y.js +21 -0
  121. package/dist/databases-37X4CI2Y.js.map +1 -0
  122. package/dist/discord-B3HUPGQ6.js +70 -0
  123. package/dist/discord-B3HUPGQ6.js.map +1 -0
  124. package/dist/dist-UISMLMFN.js +21847 -0
  125. package/dist/dist-UISMLMFN.js.map +1 -0
  126. package/dist/email-K7LO2IPB.js +268 -0
  127. package/dist/email-K7LO2IPB.js.map +1 -0
  128. package/dist/enhanced-retrieval-DNLLEM4Z.js +753 -0
  129. package/dist/enhanced-retrieval-DNLLEM4Z.js.map +1 -0
  130. package/dist/enrichment-pipeline-MNHNW65K.js +13 -0
  131. package/dist/enrichment-pipeline-MNHNW65K.js.map +1 -0
  132. package/dist/entity-resolution-Y3IUWEAT.js +24 -0
  133. package/dist/entity-resolution-Y3IUWEAT.js.map +1 -0
  134. package/dist/env-IWXUVTCB.js +12 -0
  135. package/dist/env-IWXUVTCB.js.map +1 -0
  136. package/dist/google-workspace-DKWUVNGC.js +169 -0
  137. package/dist/google-workspace-DKWUVNGC.js.map +1 -0
  138. package/dist/hash-tool-ULQYD7B5.js +22 -0
  139. package/dist/hash-tool-ULQYD7B5.js.map +1 -0
  140. package/dist/heartbeat-monitor-GCISLXI3.js +22 -0
  141. package/dist/heartbeat-monitor-GCISLXI3.js.map +1 -0
  142. package/dist/image-generation-OSU7FP6F.js +486 -0
  143. package/dist/image-generation-OSU7FP6F.js.map +1 -0
  144. package/dist/imessage-NGA2XF2V.js +35 -0
  145. package/dist/imessage-NGA2XF2V.js.map +1 -0
  146. package/dist/inbox-summarizer-NRI4S7IF.js +47 -0
  147. package/dist/inbox-summarizer-NRI4S7IF.js.map +1 -0
  148. package/dist/incident-response-C5J7Q6DT.js +244 -0
  149. package/dist/incident-response-C5J7Q6DT.js.map +1 -0
  150. package/dist/inventory-manager-352OHXWD.js +24 -0
  151. package/dist/inventory-manager-352OHXWD.js.map +1 -0
  152. package/dist/jira-GSGDBMIG.js +199 -0
  153. package/dist/jira-GSGDBMIG.js.map +1 -0
  154. package/dist/json-tool-QE2SYHEG.js +26 -0
  155. package/dist/json-tool-QE2SYHEG.js.map +1 -0
  156. package/dist/key-rotation-DPHU4ZTB.js +18 -0
  157. package/dist/key-rotation-DPHU4ZTB.js.map +1 -0
  158. package/dist/lib.d.ts +603 -11
  159. package/dist/lib.js +161 -35
  160. package/dist/lib.js.map +1 -1
  161. package/dist/mailchimp-KKNF6QJ7.js +152 -0
  162. package/dist/mailchimp-KKNF6QJ7.js.map +1 -0
  163. package/dist/matrix-QVHG76I7.js +279 -0
  164. package/dist/matrix-QVHG76I7.js.map +1 -0
  165. package/dist/{mcp-LS7Q3Z5W.js → mcp-3JI6W7ZE.js} +3 -3
  166. package/dist/mcp-3JI6W7ZE.js.map +1 -0
  167. package/dist/microsoft365-UCBKJHNX.js +164 -0
  168. package/dist/microsoft365-UCBKJHNX.js.map +1 -0
  169. package/dist/ocr-AC7NPX33.js +22 -0
  170. package/dist/ocr-AC7NPX33.js.map +1 -0
  171. package/dist/ollama-BOAMSPLJ.js +8 -0
  172. package/dist/ollama-BOAMSPLJ.js.map +1 -0
  173. package/dist/pages-MI523RB7.js +26 -0
  174. package/dist/pages-MI523RB7.js.map +1 -0
  175. package/dist/pair-JDFTERIK.js +24 -0
  176. package/dist/pair-JDFTERIK.js.map +1 -0
  177. package/dist/pairing-IFQYCPNS.js +10 -0
  178. package/dist/pairing-IFQYCPNS.js.map +1 -0
  179. package/dist/pdf-ALQVOEJR.js +17 -0
  180. package/dist/pdf-ALQVOEJR.js.map +1 -0
  181. package/dist/presentations-DSV5IHG5.js +1002 -0
  182. package/dist/presentations-DSV5IHG5.js.map +1 -0
  183. package/dist/prometheus-JNT2BD4L.js +10 -0
  184. package/dist/prometheus-JNT2BD4L.js.map +1 -0
  185. package/dist/providers-J4LYPHDR.js +19 -0
  186. package/dist/providers-J4LYPHDR.js.map +1 -0
  187. package/dist/qr-code-WIX4PB4U.js +16 -0
  188. package/dist/qr-code-WIX4PB4U.js.map +1 -0
  189. package/dist/quickbooks-XB4NII2S.js +190 -0
  190. package/dist/quickbooks-XB4NII2S.js.map +1 -0
  191. package/dist/regex-tool-W4ABRKGK.js +24 -0
  192. package/dist/regex-tool-W4ABRKGK.js.map +1 -0
  193. package/dist/scheduler-VK4WFERV.js +63 -0
  194. package/dist/scheduler-VK4WFERV.js.map +1 -0
  195. package/dist/search-BCLBO5E3.js +25 -0
  196. package/dist/search-BCLBO5E3.js.map +1 -0
  197. package/dist/sendgrid-RNXCAFKM.js +152 -0
  198. package/dist/sendgrid-RNXCAFKM.js.map +1 -0
  199. package/dist/shopify-NCXYJB4R.js +171 -0
  200. package/dist/shopify-NCXYJB4R.js.map +1 -0
  201. package/dist/signal-6CGDFYL2.js +35 -0
  202. package/dist/signal-6CGDFYL2.js.map +1 -0
  203. package/dist/slack-IZQWIKOH.js +75 -0
  204. package/dist/slack-IZQWIKOH.js.map +1 -0
  205. package/dist/sms-M3JIOTCW.js +23 -0
  206. package/dist/sms-M3JIOTCW.js.map +1 -0
  207. package/dist/{src-K7GASHRH.js → src-VYUE6LRA.js} +138 -32
  208. package/dist/src-VYUE6LRA.js.map +1 -0
  209. package/dist/stocks-XXWBPOCU.js +14 -0
  210. package/dist/stocks-XXWBPOCU.js.map +1 -0
  211. package/dist/text-transform-6SGUA5Z4.js +22 -0
  212. package/dist/text-transform-6SGUA5Z4.js.map +1 -0
  213. package/dist/tools-2RLEI2N6.js +38 -0
  214. package/dist/tools-2RLEI2N6.js.map +1 -0
  215. package/dist/tunnel-IWMXUML4.js +301 -0
  216. package/dist/tunnel-IWMXUML4.js.map +1 -0
  217. package/dist/twilio-53GEW5JT.js +139 -0
  218. package/dist/twilio-53GEW5JT.js.map +1 -0
  219. package/dist/unit-converter-ZYXMEZOE.js +14 -0
  220. package/dist/unit-converter-ZYXMEZOE.js.map +1 -0
  221. package/dist/whatsapp-LFX6YKCM.js +35 -0
  222. package/dist/whatsapp-LFX6YKCM.js.map +1 -0
  223. package/dist/word-document-7B6SJMAY.js +902 -0
  224. package/dist/word-document-7B6SJMAY.js.map +1 -0
  225. package/dist/xero-QYO66D45.js +162 -0
  226. package/dist/xero-QYO66D45.js.map +1 -0
  227. package/dist/zapier-webhook-TBZ5YF2A.js +106 -0
  228. package/dist/zapier-webhook-TBZ5YF2A.js.map +1 -0
  229. package/drizzle/0002_mushy_master_mold.sql +140 -0
  230. package/drizzle/meta/0002_snapshot.json +3637 -0
  231. package/drizzle/meta/_journal.json +7 -0
  232. package/package.json +100 -98
  233. package/dist/bot-KJ26BG56.js +0 -15
  234. package/dist/chunk-4LVWXUNC.js.map +0 -1
  235. package/dist/chunk-4TG2IG5K.js +0 -5249
  236. package/dist/chunk-4TG2IG5K.js.map +0 -1
  237. package/dist/chunk-6DRDKB45.js.map +0 -1
  238. package/dist/chunk-CI6Q63MM.js +0 -1613
  239. package/dist/chunk-CI6Q63MM.js.map +0 -1
  240. package/dist/chunk-KHNYJY2Z.js +0 -178
  241. package/dist/chunk-KHNYJY2Z.js.map +0 -1
  242. package/dist/chunk-NSBPE2FW.js +0 -17
  243. package/dist/discord-ZOJFTVTB.js +0 -49
  244. package/dist/imessage-JFRB6EJ7.js +0 -14
  245. package/dist/scheduler-EZ7CZMCS.js +0 -42
  246. package/dist/signal-T3MCSULM.js +0 -14
  247. package/dist/slack-N2M4FHAJ.js +0 -54
  248. package/dist/src-K7GASHRH.js.map +0 -1
  249. package/dist/tools-24GZHYRF.js +0 -16
  250. package/dist/whatsapp-VCRUPAO5.js +0 -14
  251. /package/dist/{bot-KJ26BG56.js.map → audit-logger-OBPR7CRO.js.map} +0 -0
  252. /package/dist/{chunk-NSBPE2FW.js.map → auth-UOX5K2BE.js.map} +0 -0
  253. /package/dist/{discord-ZOJFTVTB.js.map → backup-restore-PZ7CYYB7.js.map} +0 -0
  254. /package/dist/{imessage-JFRB6EJ7.js.map → blocks-R3PODY47.js.map} +0 -0
  255. /package/dist/{mcp-LS7Q3Z5W.js.map → bot-QRARP4UN.js.map} +0 -0
  256. /package/dist/{scheduler-EZ7CZMCS.js.map → brain-7XLLM3KC.js.map} +0 -0
  257. /package/dist/{charts-MMXM6BWW.js.map → charts-V7ARZNKF.js.map} +0 -0
  258. /package/dist/{chunk-L3PDU3XN.js.map → chunk-4UOE5TUZ.js.map} +0 -0
  259. /package/dist/{chunk-6SNHU3CY.js.map → chunk-66OJ3WB4.js.map} +0 -0
  260. /package/dist/{chunk-F6QUZQGI.js.map → chunk-MXAPLSJ5.js.map} +0 -0
  261. /package/dist/{chunk-GK3E2I7A.js.map → chunk-NHMBTUMW.js.map} +0 -0
  262. /package/dist/{signal-T3MCSULM.js.map → chunk-PLDDJCW6.js.map} +0 -0
  263. /package/dist/{chunk-GVJVEWHI.js.map → chunk-SJSUSJ47.js.map} +0 -0
  264. /package/dist/{chunk-HH2HBTQM.js.map → chunk-TYAGMJNV.js.map} +0 -0
  265. /package/dist/{chunk-JXUP2X7V.js.map → chunk-VEHFVBLI.js.map} +0 -0
  266. /package/dist/{slack-N2M4FHAJ.js.map → client-ZQSFPMOB.js.map} +0 -0
  267. /package/dist/{tools-24GZHYRF.js.map → clipboard-manager-TEO2GEDN.js.map} +0 -0
  268. /package/dist/{whatsapp-VCRUPAO5.js.map → cron-explain-HHQKPD3M.js.map} +0 -0
@@ -0,0 +1,293 @@
1
+ import {
2
+ auditLogs,
3
+ db
4
+ } from "./chunk-XKYRH4FM.js";
5
+ import {
6
+ env
7
+ } from "./chunk-ZLZKF2PM.js";
8
+
9
+ // src/core/security/audit-logger.ts
10
+ import { createHmac, randomUUID } from "crypto";
11
+ import { eq, and, gte, lte, desc, count, min, max, asc } from "drizzle-orm";
12
+ var _cachedSigningKey = null;
13
+ function getAuditSigningKey() {
14
+ if (_cachedSigningKey) return _cachedSigningKey;
15
+ if (env.AUDIT_SIGNING_KEY) {
16
+ _cachedSigningKey = env.AUDIT_SIGNING_KEY;
17
+ return _cachedSigningKey;
18
+ }
19
+ _cachedSigningKey = randomUUID();
20
+ console.warn(
21
+ "[audit-logger] AUDIT_SIGNING_KEY not set \u2014 using a random ephemeral key. Set AUDIT_SIGNING_KEY in .env for persistent tamper-proof audit chains."
22
+ );
23
+ return _cachedSigningKey;
24
+ }
25
+ function signAuditEntry(sequenceNumber, action, userId, resource, detailsJson, timestamp, previousHash) {
26
+ const key = getAuditSigningKey();
27
+ const data = [
28
+ String(sequenceNumber),
29
+ action,
30
+ userId ?? "",
31
+ resource ?? "",
32
+ detailsJson,
33
+ timestamp,
34
+ previousHash ?? ""
35
+ ].join("|");
36
+ return createHmac("sha256", key).update(data).digest("hex");
37
+ }
38
+ async function getLastAuditEntry() {
39
+ const [last] = await db.select({
40
+ sequenceNumber: auditLogs.sequenceNumber,
41
+ entryHash: auditLogs.entryHash
42
+ }).from(auditLogs).orderBy(desc(auditLogs.sequenceNumber)).limit(1);
43
+ if (!last || last.sequenceNumber == null || last.entryHash == null) {
44
+ return null;
45
+ }
46
+ return {
47
+ sequenceNumber: last.sequenceNumber,
48
+ entryHash: last.entryHash
49
+ };
50
+ }
51
+ async function logAudit(entry) {
52
+ const last = await getLastAuditEntry();
53
+ const sequenceNumber = (last?.sequenceNumber ?? 0) + 1;
54
+ const previousHash = last?.entryHash ?? null;
55
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
56
+ const detailsJson = entry.details ? JSON.stringify(entry.details) : "{}";
57
+ const entryHash = signAuditEntry(
58
+ sequenceNumber,
59
+ entry.action,
60
+ entry.userId,
61
+ entry.resource,
62
+ detailsJson,
63
+ timestamp,
64
+ previousHash
65
+ );
66
+ const [log] = await db.insert(auditLogs).values({
67
+ userId: entry.userId,
68
+ sessionId: entry.sessionId,
69
+ action: entry.action,
70
+ resource: entry.resource,
71
+ resourceId: entry.resourceId,
72
+ details: entry.details,
73
+ ipAddress: entry.ipAddress,
74
+ userAgent: entry.userAgent,
75
+ success: entry.success ?? true,
76
+ createdAt: new Date(timestamp),
77
+ sequenceNumber,
78
+ entryHash,
79
+ previousHash
80
+ }).returning();
81
+ return log.id;
82
+ }
83
+ async function queryAuditLogs(options = {}) {
84
+ const {
85
+ userId,
86
+ action,
87
+ resource,
88
+ startDate,
89
+ endDate,
90
+ limit = 100,
91
+ offset = 0
92
+ } = options;
93
+ let query = db.select().from(auditLogs);
94
+ const conditions = [];
95
+ if (userId) {
96
+ conditions.push(eq(auditLogs.userId, userId));
97
+ }
98
+ if (action) {
99
+ conditions.push(eq(auditLogs.action, action));
100
+ }
101
+ if (resource) {
102
+ conditions.push(eq(auditLogs.resource, resource));
103
+ }
104
+ if (startDate) {
105
+ conditions.push(gte(auditLogs.createdAt, startDate));
106
+ }
107
+ if (endDate) {
108
+ conditions.push(lte(auditLogs.createdAt, endDate));
109
+ }
110
+ if (conditions.length > 0) {
111
+ query = query.where(and(...conditions));
112
+ }
113
+ const logs = await query.orderBy(desc(auditLogs.createdAt)).limit(limit).offset(offset);
114
+ return logs;
115
+ }
116
+ async function getRecentUserActivity(userId, hours = 24) {
117
+ const since = new Date(Date.now() - hours * 60 * 60 * 1e3);
118
+ return db.select().from(auditLogs).where(and(eq(auditLogs.userId, userId), gte(auditLogs.createdAt, since))).orderBy(desc(auditLogs.createdAt)).limit(100);
119
+ }
120
+ async function countActionsByType(userId, startDate, endDate) {
121
+ const logs = await db.select().from(auditLogs).where(
122
+ and(
123
+ eq(auditLogs.userId, userId),
124
+ gte(auditLogs.createdAt, startDate),
125
+ lte(auditLogs.createdAt, endDate)
126
+ )
127
+ );
128
+ const counts = {};
129
+ for (const log of logs) {
130
+ counts[log.action] = (counts[log.action] || 0) + 1;
131
+ }
132
+ return counts;
133
+ }
134
+ async function verifyAuditChain(options) {
135
+ const fromSequence = options?.fromSequence ?? 1;
136
+ const batchLimit = options?.limit ?? 1e4;
137
+ let expectedPreviousHash = null;
138
+ let expectedSequence = fromSequence;
139
+ if (fromSequence > 1) {
140
+ const [prev] = await db.select({
141
+ sequenceNumber: auditLogs.sequenceNumber,
142
+ entryHash: auditLogs.entryHash
143
+ }).from(auditLogs).where(eq(auditLogs.sequenceNumber, fromSequence - 1)).limit(1);
144
+ expectedPreviousHash = prev?.entryHash ?? null;
145
+ }
146
+ const entries = await db.select().from(auditLogs).where(gte(auditLogs.sequenceNumber, fromSequence)).orderBy(asc(auditLogs.sequenceNumber)).limit(batchLimit);
147
+ const errors = [];
148
+ for (const entry of entries) {
149
+ const seq = entry.sequenceNumber;
150
+ if (seq == null) {
151
+ errors.push({
152
+ sequenceNumber: expectedSequence,
153
+ error: "Missing sequence number"
154
+ });
155
+ expectedSequence++;
156
+ continue;
157
+ }
158
+ if (seq !== expectedSequence) {
159
+ errors.push({
160
+ sequenceNumber: expectedSequence,
161
+ error: `Sequence gap: expected ${expectedSequence}, got ${seq}`
162
+ });
163
+ expectedSequence = seq;
164
+ }
165
+ if ((entry.previousHash ?? null) !== expectedPreviousHash) {
166
+ errors.push({
167
+ sequenceNumber: seq,
168
+ error: `Previous hash mismatch: expected ${expectedPreviousHash ?? "(null)"}, got ${entry.previousHash ?? "(null)"}`
169
+ });
170
+ }
171
+ const detailsJson = entry.details ? JSON.stringify(entry.details) : "{}";
172
+ const timestamp = entry.createdAt.toISOString();
173
+ const recomputed = signAuditEntry(
174
+ seq,
175
+ entry.action,
176
+ entry.userId ?? void 0,
177
+ entry.resource ?? void 0,
178
+ detailsJson,
179
+ timestamp,
180
+ entry.previousHash
181
+ );
182
+ if (recomputed !== entry.entryHash) {
183
+ errors.push({
184
+ sequenceNumber: seq,
185
+ error: "Entry hash mismatch \u2014 record may have been tampered with"
186
+ });
187
+ }
188
+ expectedPreviousHash = entry.entryHash;
189
+ expectedSequence = seq + 1;
190
+ }
191
+ return {
192
+ valid: errors.length === 0,
193
+ totalChecked: entries.length,
194
+ firstInvalid: errors.length > 0 ? errors[0].sequenceNumber : void 0,
195
+ errors
196
+ };
197
+ }
198
+ async function getAuditChainIntegrity() {
199
+ const [stats] = await db.select({
200
+ totalEntries: count(auditLogs.id),
201
+ oldestEntry: min(auditLogs.createdAt),
202
+ newestEntry: max(auditLogs.createdAt),
203
+ lastSequence: max(auditLogs.sequenceNumber)
204
+ }).from(auditLogs);
205
+ const totalEntries = Number(stats.totalEntries ?? 0);
206
+ const lastSequence = stats.lastSequence ?? 0;
207
+ const verifyFrom = Math.max(1, lastSequence - 999);
208
+ const verification = await verifyAuditChain({
209
+ fromSequence: verifyFrom,
210
+ limit: 1e3
211
+ });
212
+ return {
213
+ totalEntries,
214
+ oldestEntry: stats.oldestEntry ? new Date(stats.oldestEntry) : null,
215
+ newestEntry: stats.newestEntry ? new Date(stats.newestEntry) : null,
216
+ lastVerified: verification.totalChecked,
217
+ chainValid: verification.valid,
218
+ lastSequence
219
+ };
220
+ }
221
+ var audit = {
222
+ login: (userId, ipAddress, userAgent) => logAudit({
223
+ userId,
224
+ action: "login",
225
+ resource: "session",
226
+ ipAddress,
227
+ userAgent
228
+ }),
229
+ logout: (userId, sessionId) => logAudit({
230
+ userId,
231
+ sessionId,
232
+ action: "logout",
233
+ resource: "session"
234
+ }),
235
+ toolUse: (userId, toolName, input, success) => logAudit({
236
+ userId,
237
+ action: "tool_use",
238
+ resource: "tool",
239
+ resourceId: toolName,
240
+ details: { input },
241
+ success
242
+ }),
243
+ shellExecute: (userId, command, exitCode, durationMs) => logAudit({
244
+ userId,
245
+ action: "shell_execute",
246
+ resource: "shell",
247
+ details: { command, exitCode, durationMs },
248
+ success: exitCode === 0
249
+ }),
250
+ fileAccess: (userId, action, filePath) => logAudit({
251
+ userId,
252
+ action,
253
+ resource: "file",
254
+ resourceId: filePath
255
+ }),
256
+ memoryCreate: (userId, memoryId, memoryType) => logAudit({
257
+ userId,
258
+ action: "memory_create",
259
+ resource: "memory",
260
+ resourceId: memoryId,
261
+ details: { type: memoryType }
262
+ }),
263
+ modeChange: (userId, fromMode, toMode) => logAudit({
264
+ userId,
265
+ action: "mode_change",
266
+ resource: "mode",
267
+ details: { fromMode, toMode }
268
+ }),
269
+ agentSpawn: (userId, agentId, agentType) => logAudit({
270
+ userId,
271
+ action: "agent_spawn",
272
+ resource: "agent",
273
+ resourceId: agentId,
274
+ details: { type: agentType }
275
+ }),
276
+ error: (userId, errorType, message, context) => logAudit({
277
+ userId,
278
+ action: "error",
279
+ details: { errorType, message, ...context },
280
+ success: false
281
+ })
282
+ };
283
+
284
+ export {
285
+ logAudit,
286
+ queryAuditLogs,
287
+ getRecentUserActivity,
288
+ countActionsByType,
289
+ verifyAuditChain,
290
+ getAuditChainIntegrity,
291
+ audit
292
+ };
293
+ //# sourceMappingURL=chunk-OCVQGBJK.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/security/audit-logger.ts"],"sourcesContent":["import { createHmac, randomUUID } from \"crypto\";\nimport { db } from \"../../db\";\nimport { auditLogs, NewAuditLog } from \"../../db/schema\";\nimport { eq, and, gte, lte, desc, count, min, max, asc } from \"drizzle-orm\";\nimport { env } from \"../../config/env\";\n\nexport type AuditAction =\n | \"login\"\n | \"logout\"\n | \"session_create\"\n | \"session_invalidate\"\n | \"api_key_create\"\n | \"api_key_revoke\"\n | \"tool_use\"\n | \"chat_message\"\n | \"memory_create\"\n | \"memory_delete\"\n | \"memory_archive\"\n | \"settings_change\"\n | \"mode_change\"\n | \"agent_spawn\"\n | \"agent_complete\"\n | \"file_read\"\n | \"file_write\"\n | \"shell_execute\"\n | \"web_browse\"\n | \"error\";\n\nexport type AuditResource =\n | \"session\"\n | \"api_key\"\n | \"tool\"\n | \"chat\"\n | \"memory\"\n | \"settings\"\n | \"mode\"\n | \"agent\"\n | \"file\"\n | \"shell\"\n | \"browser\";\n\nexport interface AuditLogEntry {\n userId?: string;\n sessionId?: string;\n action: AuditAction;\n resource?: AuditResource;\n resourceId?: string;\n details?: Record<string, unknown>;\n ipAddress?: string;\n userAgent?: string;\n success?: boolean;\n}\n\n// --- Tamper-proof chain hashing helpers (SOC 2 compliance) ---\n\nlet _cachedSigningKey: string | null = null;\n\nfunction getAuditSigningKey(): string {\n if (_cachedSigningKey) return _cachedSigningKey;\n if (env.AUDIT_SIGNING_KEY) {\n _cachedSigningKey = env.AUDIT_SIGNING_KEY;\n return _cachedSigningKey;\n }\n _cachedSigningKey = randomUUID();\n console.warn(\n \"[audit-logger] AUDIT_SIGNING_KEY not set — using a random ephemeral key. \" +\n \"Set AUDIT_SIGNING_KEY in .env for persistent tamper-proof audit chains.\"\n );\n return _cachedSigningKey;\n}\n\nfunction signAuditEntry(\n sequenceNumber: number,\n action: string,\n userId: string | undefined,\n resource: string | undefined,\n detailsJson: string,\n timestamp: string,\n previousHash: string | null\n): string {\n const key = getAuditSigningKey();\n const data = [\n String(sequenceNumber),\n action,\n userId ?? \"\",\n resource ?? \"\",\n detailsJson,\n timestamp,\n previousHash ?? \"\",\n ].join(\"|\");\n return createHmac(\"sha256\", key).update(data).digest(\"hex\");\n}\n\nasync function getLastAuditEntry(): Promise<{\n sequenceNumber: number;\n entryHash: string;\n} | null> {\n const [last] = await db\n .select({\n sequenceNumber: auditLogs.sequenceNumber,\n entryHash: auditLogs.entryHash,\n })\n .from(auditLogs)\n .orderBy(desc(auditLogs.sequenceNumber))\n .limit(1);\n\n if (!last || last.sequenceNumber == null || last.entryHash == null) {\n return null;\n }\n\n return {\n sequenceNumber: last.sequenceNumber,\n entryHash: last.entryHash,\n };\n}\n\nexport async function logAudit(entry: AuditLogEntry): Promise<string> {\n // Fetch previous chain entry for tamper-proof linking\n const last = await getLastAuditEntry();\n const sequenceNumber = (last?.sequenceNumber ?? 0) + 1;\n const previousHash = last?.entryHash ?? null;\n\n const timestamp = new Date().toISOString();\n const detailsJson = entry.details ? JSON.stringify(entry.details) : \"{}\";\n\n const entryHash = signAuditEntry(\n sequenceNumber,\n entry.action,\n entry.userId,\n entry.resource,\n detailsJson,\n timestamp,\n previousHash\n );\n\n const [log] = await db\n .insert(auditLogs)\n .values({\n userId: entry.userId,\n sessionId: entry.sessionId,\n action: entry.action,\n resource: entry.resource,\n resourceId: entry.resourceId,\n details: entry.details,\n ipAddress: entry.ipAddress,\n userAgent: entry.userAgent,\n success: entry.success ?? true,\n createdAt: new Date(timestamp),\n sequenceNumber,\n entryHash,\n previousHash,\n })\n .returning();\n\n return log.id;\n}\n\nexport interface AuditQueryOptions {\n userId?: string;\n action?: AuditAction;\n resource?: AuditResource;\n startDate?: Date;\n endDate?: Date;\n limit?: number;\n offset?: number;\n}\n\nexport async function queryAuditLogs(options: AuditQueryOptions = {}) {\n const {\n userId,\n action,\n resource,\n startDate,\n endDate,\n limit = 100,\n offset = 0,\n } = options;\n\n let query = db.select().from(auditLogs);\n\n const conditions = [];\n\n if (userId) {\n conditions.push(eq(auditLogs.userId, userId));\n }\n\n if (action) {\n conditions.push(eq(auditLogs.action, action));\n }\n\n if (resource) {\n conditions.push(eq(auditLogs.resource, resource));\n }\n\n if (startDate) {\n conditions.push(gte(auditLogs.createdAt, startDate));\n }\n\n if (endDate) {\n conditions.push(lte(auditLogs.createdAt, endDate));\n }\n\n if (conditions.length > 0) {\n query = query.where(and(...conditions)) as typeof query;\n }\n\n const logs = await query\n .orderBy(desc(auditLogs.createdAt))\n .limit(limit)\n .offset(offset);\n\n return logs;\n}\n\nexport async function getRecentUserActivity(\n userId: string,\n hours = 24\n): Promise<typeof auditLogs.$inferSelect[]> {\n const since = new Date(Date.now() - hours * 60 * 60 * 1000);\n\n return db\n .select()\n .from(auditLogs)\n .where(and(eq(auditLogs.userId, userId), gte(auditLogs.createdAt, since)))\n .orderBy(desc(auditLogs.createdAt))\n .limit(100);\n}\n\nexport async function countActionsByType(\n userId: string,\n startDate: Date,\n endDate: Date\n): Promise<Record<string, number>> {\n const logs = await db\n .select()\n .from(auditLogs)\n .where(\n and(\n eq(auditLogs.userId, userId),\n gte(auditLogs.createdAt, startDate),\n lte(auditLogs.createdAt, endDate)\n )\n );\n\n const counts: Record<string, number> = {};\n for (const log of logs) {\n counts[log.action] = (counts[log.action] || 0) + 1;\n }\n\n return counts;\n}\n\n// --- Audit chain verification (SOC 2 compliance) ---\n\nexport async function verifyAuditChain(\n options?: { fromSequence?: number; limit?: number }\n): Promise<{\n valid: boolean;\n totalChecked: number;\n firstInvalid?: number;\n errors: Array<{ sequenceNumber: number; error: string }>;\n}> {\n const fromSequence = options?.fromSequence ?? 1;\n const batchLimit = options?.limit ?? 10000;\n\n // Fetch the entry just before fromSequence to get its hash for linkage check\n let expectedPreviousHash: string | null = null;\n let expectedSequence = fromSequence;\n\n if (fromSequence > 1) {\n const [prev] = await db\n .select({\n sequenceNumber: auditLogs.sequenceNumber,\n entryHash: auditLogs.entryHash,\n })\n .from(auditLogs)\n .where(eq(auditLogs.sequenceNumber, fromSequence - 1))\n .limit(1);\n\n expectedPreviousHash = prev?.entryHash ?? null;\n }\n\n const entries = await db\n .select()\n .from(auditLogs)\n .where(gte(auditLogs.sequenceNumber, fromSequence))\n .orderBy(asc(auditLogs.sequenceNumber))\n .limit(batchLimit);\n\n const errors: Array<{ sequenceNumber: number; error: string }> = [];\n\n for (const entry of entries) {\n const seq = entry.sequenceNumber;\n\n if (seq == null) {\n errors.push({\n sequenceNumber: expectedSequence,\n error: \"Missing sequence number\",\n });\n expectedSequence++;\n continue;\n }\n\n // Check sequence continuity\n if (seq !== expectedSequence) {\n errors.push({\n sequenceNumber: expectedSequence,\n error: `Sequence gap: expected ${expectedSequence}, got ${seq}`,\n });\n expectedSequence = seq; // re-sync\n }\n\n // Check previousHash linkage\n if ((entry.previousHash ?? null) !== expectedPreviousHash) {\n errors.push({\n sequenceNumber: seq,\n error: `Previous hash mismatch: expected ${expectedPreviousHash ?? \"(null)\"}, got ${entry.previousHash ?? \"(null)\"}`,\n });\n }\n\n // Recompute and verify entryHash\n const detailsJson = entry.details ? JSON.stringify(entry.details) : \"{}\";\n const timestamp = entry.createdAt.toISOString();\n\n const recomputed = signAuditEntry(\n seq,\n entry.action,\n entry.userId ?? undefined,\n entry.resource ?? undefined,\n detailsJson,\n timestamp,\n entry.previousHash\n );\n\n if (recomputed !== entry.entryHash) {\n errors.push({\n sequenceNumber: seq,\n error: \"Entry hash mismatch — record may have been tampered with\",\n });\n }\n\n // Advance expectations\n expectedPreviousHash = entry.entryHash;\n expectedSequence = seq + 1;\n }\n\n return {\n valid: errors.length === 0,\n totalChecked: entries.length,\n firstInvalid: errors.length > 0 ? errors[0].sequenceNumber : undefined,\n errors,\n };\n}\n\nexport async function getAuditChainIntegrity(): Promise<{\n totalEntries: number;\n oldestEntry: Date | null;\n newestEntry: Date | null;\n lastVerified: number;\n chainValid: boolean;\n lastSequence: number;\n}> {\n const [stats] = await db\n .select({\n totalEntries: count(auditLogs.id),\n oldestEntry: min(auditLogs.createdAt),\n newestEntry: max(auditLogs.createdAt),\n lastSequence: max(auditLogs.sequenceNumber),\n })\n .from(auditLogs);\n\n const totalEntries = Number(stats.totalEntries ?? 0);\n const lastSequence = stats.lastSequence ?? 0;\n\n // Verify the last 1000 entries\n const verifyFrom = Math.max(1, lastSequence - 999);\n const verification = await verifyAuditChain({\n fromSequence: verifyFrom,\n limit: 1000,\n });\n\n return {\n totalEntries,\n oldestEntry: stats.oldestEntry ? new Date(stats.oldestEntry) : null,\n newestEntry: stats.newestEntry ? new Date(stats.newestEntry) : null,\n lastVerified: verification.totalChecked,\n chainValid: verification.valid,\n lastSequence,\n };\n}\n\n// Convenience functions for common audit events\nexport const audit = {\n login: (userId: string, ipAddress?: string, userAgent?: string) =>\n logAudit({\n userId,\n action: \"login\",\n resource: \"session\",\n ipAddress,\n userAgent,\n }),\n\n logout: (userId: string, sessionId: string) =>\n logAudit({\n userId,\n sessionId,\n action: \"logout\",\n resource: \"session\",\n }),\n\n toolUse: (\n userId: string,\n toolName: string,\n input: Record<string, unknown>,\n success: boolean\n ) =>\n logAudit({\n userId,\n action: \"tool_use\",\n resource: \"tool\",\n resourceId: toolName,\n details: { input },\n success,\n }),\n\n shellExecute: (\n userId: string,\n command: string,\n exitCode: number,\n durationMs: number\n ) =>\n logAudit({\n userId,\n action: \"shell_execute\",\n resource: \"shell\",\n details: { command, exitCode, durationMs },\n success: exitCode === 0,\n }),\n\n fileAccess: (\n userId: string,\n action: \"file_read\" | \"file_write\",\n filePath: string\n ) =>\n logAudit({\n userId,\n action,\n resource: \"file\",\n resourceId: filePath,\n }),\n\n memoryCreate: (userId: string, memoryId: string, memoryType: string) =>\n logAudit({\n userId,\n action: \"memory_create\",\n resource: \"memory\",\n resourceId: memoryId,\n details: { type: memoryType },\n }),\n\n modeChange: (\n userId: string,\n fromMode: string | null,\n toMode: string\n ) =>\n logAudit({\n userId,\n action: \"mode_change\",\n resource: \"mode\",\n details: { fromMode, toMode },\n }),\n\n agentSpawn: (userId: string, agentId: string, agentType: string) =>\n logAudit({\n userId,\n action: \"agent_spawn\",\n resource: \"agent\",\n resourceId: agentId,\n details: { type: agentType },\n }),\n\n error: (\n userId: string | undefined,\n errorType: string,\n message: string,\n context?: Record<string, unknown>\n ) =>\n logAudit({\n userId,\n action: \"error\",\n details: { errorType, message, ...context },\n success: false,\n }),\n};\n"],"mappings":"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","names":[]}
@@ -0,0 +1,332 @@
1
+ // src/integrations/spotify/auth.ts
2
+ var SpotifyAuthError = class extends Error {
3
+ statusCode;
4
+ spotifyError;
5
+ constructor(message, statusCode, spotifyError) {
6
+ super(message);
7
+ this.name = "SpotifyAuthError";
8
+ this.statusCode = statusCode;
9
+ this.spotifyError = spotifyError;
10
+ }
11
+ };
12
+ var SPOTIFY_SCOPES = {
13
+ // Images
14
+ UGC_IMAGE_UPLOAD: "ugc-image-upload",
15
+ // Spotify Connect
16
+ USER_READ_PLAYBACK_STATE: "user-read-playback-state",
17
+ USER_MODIFY_PLAYBACK_STATE: "user-modify-playback-state",
18
+ USER_READ_CURRENTLY_PLAYING: "user-read-currently-playing",
19
+ // Playback
20
+ STREAMING: "streaming",
21
+ APP_REMOTE_CONTROL: "app-remote-control",
22
+ // Users
23
+ USER_READ_EMAIL: "user-read-email",
24
+ USER_READ_PRIVATE: "user-read-private",
25
+ // Follow
26
+ USER_FOLLOW_READ: "user-follow-read",
27
+ USER_FOLLOW_MODIFY: "user-follow-modify",
28
+ // Library
29
+ USER_LIBRARY_MODIFY: "user-library-modify",
30
+ USER_LIBRARY_READ: "user-library-read",
31
+ // Listening History
32
+ USER_READ_PLAYBACK_POSITION: "user-read-playback-position",
33
+ USER_TOP_READ: "user-top-read",
34
+ USER_READ_RECENTLY_PLAYED: "user-read-recently-played",
35
+ // Playlists
36
+ PLAYLIST_MODIFY_PRIVATE: "playlist-modify-private",
37
+ PLAYLIST_READ_COLLABORATIVE: "playlist-read-collaborative",
38
+ PLAYLIST_READ_PRIVATE: "playlist-read-private",
39
+ PLAYLIST_MODIFY_PUBLIC: "playlist-modify-public"
40
+ };
41
+ var DEFAULT_SCOPES = [
42
+ SPOTIFY_SCOPES.USER_READ_PLAYBACK_STATE,
43
+ SPOTIFY_SCOPES.USER_MODIFY_PLAYBACK_STATE,
44
+ SPOTIFY_SCOPES.USER_READ_CURRENTLY_PLAYING,
45
+ SPOTIFY_SCOPES.USER_READ_EMAIL,
46
+ SPOTIFY_SCOPES.USER_READ_PRIVATE,
47
+ SPOTIFY_SCOPES.USER_LIBRARY_MODIFY,
48
+ SPOTIFY_SCOPES.USER_LIBRARY_READ,
49
+ SPOTIFY_SCOPES.USER_TOP_READ,
50
+ SPOTIFY_SCOPES.USER_READ_RECENTLY_PLAYED,
51
+ SPOTIFY_SCOPES.PLAYLIST_MODIFY_PRIVATE,
52
+ SPOTIFY_SCOPES.PLAYLIST_READ_COLLABORATIVE,
53
+ SPOTIFY_SCOPES.PLAYLIST_READ_PRIVATE,
54
+ SPOTIFY_SCOPES.PLAYLIST_MODIFY_PUBLIC
55
+ ];
56
+ var SPOTIFY_ACCOUNTS_URL = "https://accounts.spotify.com";
57
+ var SPOTIFY_API_URL = "https://api.spotify.com/v1";
58
+ var SpotifyAuth = class {
59
+ config;
60
+ tokens = null;
61
+ tokenRefreshPromise = null;
62
+ constructor(config) {
63
+ this.config = config;
64
+ }
65
+ /**
66
+ * Get the Base64 encoded client credentials
67
+ */
68
+ getBasicAuthHeader() {
69
+ const credentials = `${this.config.clientId}:${this.config.clientSecret}`;
70
+ return `Basic ${Buffer.from(credentials).toString("base64")}`;
71
+ }
72
+ /**
73
+ * Generate the authorization URL for the OAuth2 flow
74
+ */
75
+ getAuthorizationUrl(scopes = DEFAULT_SCOPES, state, showDialog = false) {
76
+ const params = new URLSearchParams({
77
+ client_id: this.config.clientId,
78
+ response_type: "code",
79
+ redirect_uri: this.config.redirectUri,
80
+ scope: scopes.join(" "),
81
+ show_dialog: String(showDialog)
82
+ });
83
+ if (state) {
84
+ params.set("state", state);
85
+ }
86
+ return `${SPOTIFY_ACCOUNTS_URL}/authorize?${params.toString()}`;
87
+ }
88
+ /**
89
+ * Exchange an authorization code for tokens
90
+ */
91
+ async exchangeCode(code) {
92
+ const response = await fetch(`${SPOTIFY_ACCOUNTS_URL}/api/token`, {
93
+ method: "POST",
94
+ headers: {
95
+ Authorization: this.getBasicAuthHeader(),
96
+ "Content-Type": "application/x-www-form-urlencoded"
97
+ },
98
+ body: new URLSearchParams({
99
+ grant_type: "authorization_code",
100
+ code,
101
+ redirect_uri: this.config.redirectUri
102
+ })
103
+ });
104
+ if (!response.ok) {
105
+ const error = await this.parseErrorResponse(response);
106
+ throw new SpotifyAuthError(
107
+ `Failed to exchange code: ${error.message}`,
108
+ response.status,
109
+ error
110
+ );
111
+ }
112
+ const data = await response.json();
113
+ this.tokens = this.parseTokenResponse(data);
114
+ return this.tokens;
115
+ }
116
+ /**
117
+ * Refresh the access token using the refresh token
118
+ */
119
+ async refreshAccessToken(refreshToken) {
120
+ const tokenToRefresh = refreshToken ?? this.tokens?.refreshToken;
121
+ if (!tokenToRefresh) {
122
+ throw new SpotifyAuthError("No refresh token available");
123
+ }
124
+ if (this.tokenRefreshPromise) {
125
+ return this.tokenRefreshPromise;
126
+ }
127
+ this.tokenRefreshPromise = this.performTokenRefresh(tokenToRefresh);
128
+ try {
129
+ const tokens = await this.tokenRefreshPromise;
130
+ return tokens;
131
+ } finally {
132
+ this.tokenRefreshPromise = null;
133
+ }
134
+ }
135
+ async performTokenRefresh(refreshToken) {
136
+ const response = await fetch(`${SPOTIFY_ACCOUNTS_URL}/api/token`, {
137
+ method: "POST",
138
+ headers: {
139
+ Authorization: this.getBasicAuthHeader(),
140
+ "Content-Type": "application/x-www-form-urlencoded"
141
+ },
142
+ body: new URLSearchParams({
143
+ grant_type: "refresh_token",
144
+ refresh_token: refreshToken
145
+ })
146
+ });
147
+ if (!response.ok) {
148
+ const error = await this.parseErrorResponse(response);
149
+ throw new SpotifyAuthError(
150
+ `Failed to refresh token: ${error.message}`,
151
+ response.status,
152
+ error
153
+ );
154
+ }
155
+ const data = await response.json();
156
+ this.tokens = {
157
+ ...this.parseTokenResponse(data),
158
+ refreshToken: data.refresh_token ?? refreshToken
159
+ };
160
+ return this.tokens;
161
+ }
162
+ /**
163
+ * Parse the token response into our internal format
164
+ */
165
+ parseTokenResponse(data) {
166
+ return {
167
+ accessToken: data.access_token,
168
+ tokenType: data.token_type,
169
+ scope: data.scope,
170
+ expiresAt: Date.now() + data.expires_in * 1e3,
171
+ refreshToken: data.refresh_token ?? ""
172
+ };
173
+ }
174
+ /**
175
+ * Parse an error response from Spotify
176
+ */
177
+ async parseErrorResponse(response) {
178
+ try {
179
+ const data = await response.json();
180
+ if (data.error) {
181
+ return data.error;
182
+ }
183
+ return {
184
+ status: response.status,
185
+ message: data.error_description ?? response.statusText
186
+ };
187
+ } catch {
188
+ return {
189
+ status: response.status,
190
+ message: response.statusText
191
+ };
192
+ }
193
+ }
194
+ /**
195
+ * Check if the current access token is expired (or about to expire)
196
+ */
197
+ isTokenExpired(bufferMs = 6e4) {
198
+ if (!this.tokens) {
199
+ return true;
200
+ }
201
+ return Date.now() >= this.tokens.expiresAt - bufferMs;
202
+ }
203
+ /**
204
+ * Get a valid access token, refreshing if necessary
205
+ */
206
+ async getAccessToken() {
207
+ if (!this.tokens) {
208
+ throw new SpotifyAuthError("Not authenticated. Please authenticate first.");
209
+ }
210
+ if (this.isTokenExpired()) {
211
+ await this.refreshAccessToken();
212
+ }
213
+ return this.tokens.accessToken;
214
+ }
215
+ /**
216
+ * Get the current tokens
217
+ */
218
+ getTokens() {
219
+ return this.tokens;
220
+ }
221
+ /**
222
+ * Set tokens (useful for restoring from storage)
223
+ */
224
+ setTokens(tokens) {
225
+ this.tokens = tokens;
226
+ }
227
+ /**
228
+ * Clear stored tokens
229
+ */
230
+ clearTokens() {
231
+ this.tokens = null;
232
+ }
233
+ /**
234
+ * Check if authenticated
235
+ */
236
+ isAuthenticated() {
237
+ return this.tokens !== null && !!this.tokens.refreshToken;
238
+ }
239
+ /**
240
+ * Make an authenticated request to the Spotify API
241
+ */
242
+ async request(endpoint, options = {}) {
243
+ const accessToken = await this.getAccessToken();
244
+ const url = endpoint.startsWith("http") ? endpoint : `${SPOTIFY_API_URL}${endpoint}`;
245
+ const response = await fetch(url, {
246
+ ...options,
247
+ headers: {
248
+ Authorization: `Bearer ${accessToken}`,
249
+ "Content-Type": "application/json",
250
+ ...options.headers
251
+ }
252
+ });
253
+ if (response.status === 204) {
254
+ return void 0;
255
+ }
256
+ if (!response.ok) {
257
+ if (response.status === 401) {
258
+ await this.refreshAccessToken();
259
+ return this.request(endpoint, options);
260
+ }
261
+ const error = await this.parseErrorResponse(response);
262
+ throw new SpotifyAuthError(
263
+ `API request failed: ${error.message}`,
264
+ response.status,
265
+ error
266
+ );
267
+ }
268
+ const text = await response.text();
269
+ if (!text) {
270
+ return void 0;
271
+ }
272
+ return JSON.parse(text);
273
+ }
274
+ /**
275
+ * GET request helper
276
+ */
277
+ async get(endpoint, params) {
278
+ let url = endpoint;
279
+ if (params) {
280
+ const searchParams = new URLSearchParams(params);
281
+ url = `${endpoint}?${searchParams.toString()}`;
282
+ }
283
+ return this.request(url, { method: "GET" });
284
+ }
285
+ /**
286
+ * POST request helper
287
+ */
288
+ async post(endpoint, body) {
289
+ return this.request(endpoint, {
290
+ method: "POST",
291
+ body: body ? JSON.stringify(body) : void 0
292
+ });
293
+ }
294
+ /**
295
+ * PUT request helper
296
+ */
297
+ async put(endpoint, body) {
298
+ return this.request(endpoint, {
299
+ method: "PUT",
300
+ body: body ? JSON.stringify(body) : void 0
301
+ });
302
+ }
303
+ /**
304
+ * DELETE request helper
305
+ */
306
+ async delete(endpoint, body) {
307
+ return this.request(endpoint, {
308
+ method: "DELETE",
309
+ body: body ? JSON.stringify(body) : void 0
310
+ });
311
+ }
312
+ /**
313
+ * Get the current user's profile
314
+ */
315
+ async getCurrentUser() {
316
+ return this.get("/me");
317
+ }
318
+ };
319
+ function createSpotifyAuth(config) {
320
+ return new SpotifyAuth(config);
321
+ }
322
+ var auth_default = SpotifyAuth;
323
+
324
+ export {
325
+ SpotifyAuthError,
326
+ SPOTIFY_SCOPES,
327
+ DEFAULT_SCOPES,
328
+ SpotifyAuth,
329
+ createSpotifyAuth,
330
+ auth_default
331
+ };
332
+ //# sourceMappingURL=chunk-P6QINGFL.js.map