@tangle-network/sandbox 0.1.2 → 0.2.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.
@@ -1 +1,271 @@
1
- function a0_0xc30b(_0x13534a,_0x95c621){_0x13534a=_0x13534a-0x11c;const _0x4fc559=a0_0x4fc5();let _0xc30b97=_0x4fc559[_0x13534a];if(a0_0xc30b['\x78\x4a\x56\x4f\x4e\x61']===undefined){var _0x2d5015=function(_0x1851b5){const _0x1f9106='\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x2b\x2f\x3d';let _0x2e392f='',_0x431c99='';for(let _0x1543c7=0x0,_0x5293a5,_0x4128b9,_0x312c8d=0x0;_0x4128b9=_0x1851b5['\x63\x68\x61\x72\x41\x74'](_0x312c8d++);~_0x4128b9&&(_0x5293a5=_0x1543c7%0x4?_0x5293a5*0x40+_0x4128b9:_0x4128b9,_0x1543c7++%0x4)?_0x2e392f+=String['\x66\x72\x6f\x6d\x43\x68\x61\x72\x43\x6f\x64\x65'](0xff&_0x5293a5>>(-0x2*_0x1543c7&0x6)):0x0){_0x4128b9=_0x1f9106['\x69\x6e\x64\x65\x78\x4f\x66'](_0x4128b9);}for(let _0x2df13f=0x0,_0x98c804=_0x2e392f['\x6c\x65\x6e\x67\x74\x68'];_0x2df13f<_0x98c804;_0x2df13f++){_0x431c99+='\x25'+('\x30\x30'+_0x2e392f['\x63\x68\x61\x72\x43\x6f\x64\x65\x41\x74'](_0x2df13f)['\x74\x6f\x53\x74\x72\x69\x6e\x67'](0x10))['\x73\x6c\x69\x63\x65'](-0x2);}return decodeURIComponent(_0x431c99);};a0_0xc30b['\x44\x6f\x4c\x59\x49\x6a']=_0x2d5015,a0_0xc30b['\x61\x53\x51\x54\x64\x56']={},a0_0xc30b['\x78\x4a\x56\x4f\x4e\x61']=!![];}const _0x57a8fe=_0x4fc559[0x0],_0x5f5ab2=_0x13534a+_0x57a8fe,_0x1e5a6d=a0_0xc30b['\x61\x53\x51\x54\x64\x56'][_0x5f5ab2];return!_0x1e5a6d?(_0xc30b97=a0_0xc30b['\x44\x6f\x4c\x59\x49\x6a'](_0xc30b97),a0_0xc30b['\x61\x53\x51\x54\x64\x56'][_0x5f5ab2]=_0xc30b97):_0xc30b97=_0x1e5a6d,_0xc30b97;}const a0_0x25ee0e=a0_0xc30b;(function(_0x369480,_0x1abab9){const _0x494353=a0_0xc30b,_0x4d73c7=_0x369480();while(!![]){try{const _0x3db607=parseInt(_0x494353(0x12b))/0x1*(parseInt(_0x494353(0x135))/0x2)+-parseInt(_0x494353(0x12d))/0x3+parseInt(_0x494353(0x125))/0x4+-parseInt(_0x494353(0x132))/0x5*(-parseInt(_0x494353(0x14a))/0x6)+parseInt(_0x494353(0x13e))/0x7*(parseInt(_0x494353(0x130))/0x8)+-parseInt(_0x494353(0x144))/0x9+-parseInt(_0x494353(0x131))/0xa;if(_0x3db607===_0x1abab9)break;else _0x4d73c7['push'](_0x4d73c7['shift']());}catch(_0x241222){_0x4d73c7['push'](_0x4d73c7['shift']());}}}(a0_0x4fc5,0x68e5c));function a0_0x4fc5(){const _0x3bb16c=['\x7a\x78\x48\x57','\x44\x67\x39\x74\x44\x68\x6a\x50\x42\x4d\x43','\x44\x67\x4c\x4c\x43\x47','\x43\x67\x66\x59\x43\x32\x75','\x6d\x5a\x76\x4a\x45\x4b\x66\x33\x44\x77\x71','\x79\x77\x6e\x4a\x7a\x78\x6e\x5a','\x79\x4d\x66\x5a\x7a\x74\x79\x30','\x43\x68\x6a\x56\x7a\x68\x76\x4a\x44\x65\x4c\x4b','\x44\x78\x6e\x4c\x43\x4b\x4c\x4b','\x7a\x4e\x6a\x56\x42\x71','\x6d\x4a\x47\x57\x6e\x64\x79\x33\x6d\x67\x66\x53\x72\x33\x48\x51\x75\x71','\x43\x68\x6a\x56','\x7a\x67\x4c\x4e\x7a\x78\x6e\x30','\x7a\x4d\x58\x56\x42\x33\x69','\x44\x78\x62\x4b\x79\x78\x72\x4c','\x75\x75\x35\x6f\x76\x67\x4f','\x6e\x5a\x48\x48\x42\x31\x76\x4e\x75\x4b\x79','\x7a\x67\x39\x4a\x44\x77\x31\x4c\x42\x4e\x72\x6a\x7a\x61','\x43\x32\x4c\x4e\x42\x4d\x4c\x55\x7a\x31\x6e\x4c\x79\x33\x6a\x4c\x44\x61','\x44\x68\x72\x53\x74\x77\x4c\x55\x44\x78\x72\x4c\x43\x57','\x7a\x4e\x6a\x4c\x7a\x71','\x75\x75\x6a\x6e\x44\x65\x57','\x43\x4d\x76\x57\x42\x67\x66\x4a\x7a\x71','\x43\x33\x72\x59\x41\x77\x35\x4e','\x7a\x32\x76\x30\x76\x68\x72\x53\x74\x77\x4c\x55\x44\x78\x72\x4c\x43\x57','\x43\x32\x66\x55\x7a\x67\x6a\x56\x45\x65\x4c\x4b','\x42\x4d\x39\x33','\x73\x75\x6a\x7a\x76\x75\x43','\x6d\x74\x6d\x30\x6d\x5a\x4b\x33\x6d\x4d\x31\x54\x74\x65\x7a\x32\x76\x57','\x73\x4c\x44\x75','\x43\x68\x6a\x56\x41\x4d\x76\x4a\x44\x65\x4c\x4b','\x41\x78\x6e\x5a\x44\x77\x76\x64\x42\x32\x58\x53\x79\x77\x6a\x56\x43\x4d\x66\x30\x41\x77\x39\x55','\x7a\x4e\x62\x68\x7a\x78\x6d','\x7a\x77\x35\x30\x7a\x78\x6a\x57\x43\x4d\x4c\x5a\x7a\x71','\x6d\x74\x4b\x31\x7a\x65\x35\x78\x43\x33\x48\x6f','\x43\x4d\x76\x57\x7a\x77\x66\x30','\x6e\x5a\x6d\x35\x6e\x5a\x4b\x30\x74\x68\x6e\x4c\x77\x68\x7a\x77','\x41\x78\x6e\x5a\x44\x77\x75','\x43\x32\x76\x5a\x43\x32\x4c\x56\x42\x4b\x4c\x4b','\x6d\x4a\x69\x30\x6f\x64\x65\x32\x42\x68\x50\x53\x44\x77\x4c\x58','\x6d\x74\x61\x5a\x6d\x4a\x75\x59\x6d\x74\x62\x4c\x73\x68\x76\x34\x75\x66\x61','\x6d\x4a\x47\x32\x6f\x74\x47\x31\x71\x31\x62\x33\x44\x66\x72\x78','\x42\x67\x76\x55\x7a\x33\x72\x4f','\x43\x33\x62\x53\x41\x78\x71','\x6f\x64\x65\x34\x6d\x4d\x35\x52\x76\x31\x48\x76\x43\x71','\x77\x66\x7a\x75\x7a\x77\x69','\x43\x33\x72\x59\x41\x77\x35\x4e\x41\x77\x7a\x35','\x43\x32\x48\x48\x6d\x4a\x75\x32','\x73\x66\x6d\x59\x6e\x74\x79'];a0_0x4fc5=function(){return _0x3bb16c;};return a0_0x4fc5();}import{createHmac}from'\x6e\x6f\x64\x65\x3a\x63\x72\x79\x70\x74\x6f';function base64UrlEncode(_0x448f7f){const _0x166ed6=a0_0xc30b,_0x42c2a9={'\x70\x4d\x42\x59\x67':_0x166ed6(0x140)};return(typeof _0x448f7f===_0x166ed6(0x120)?Buffer[_0x166ed6(0x143)](_0x448f7f):_0x448f7f)['\x74\x6f\x53\x74\x72\x69\x6e\x67'](_0x42c2a9['\x70\x4d\x42\x59\x67'])[_0x166ed6(0x11f)](/\+/g,'\x2d')[_0x166ed6(0x11f)](/\//g,'\x5f')[_0x166ed6(0x11f)](/=+$/,'');}function createSignature(_0x37a236,_0x243d71){const _0x3f55ff=a0_0xc30b;return base64UrlEncode(createHmac(_0x3f55ff(0x138),_0x243d71)[_0x3f55ff(0x148)](_0x37a236)[_0x3f55ff(0x146)]());}const JWT_HEADER=base64UrlEncode(JSON[a0_0x25ee0e(0x137)]({'\x61\x6c\x67':a0_0x25ee0e(0x139),'\x74\x79\x70':a0_0x25ee0e(0x126)}));function issueToken(_0x481648,_0x297e31,_0x5b44b8){const _0x116900=a0_0x25ee0e,_0x1daa2a={'\x48\x63\x54\x4d\x78':function(_0x747286,_0x1c7ee5){return _0x747286*_0x1c7ee5;},'\x58\x56\x54\x65\x62':function(_0x3d9861,_0x4383b8){return _0x3d9861(_0x4383b8);},'\x55\x42\x45\x58\x68':function(_0x39a04f,_0x1328d5,_0x18b344){return _0x39a04f(_0x1328d5,_0x18b344);}},_0x120efa=Math[_0x116900(0x147)](Date['\x6e\x6f\x77']()/0x3e8),_0x1ef715={..._0x297e31,'\x69\x61\x74':_0x120efa,'\x65\x78\x70':_0x120efa+_0x1daa2a['\x48\x63\x54\x4d\x78'](_0x5b44b8,0x3c)},_0x43d3f2=JWT_HEADER+'\x2e'+_0x1daa2a[_0x116900(0x136)](base64UrlEncode,JSON[_0x116900(0x137)](_0x1ef715));return _0x43d3f2+'\x2e'+_0x1daa2a['\x55\x42\x45\x58\x68'](createSignature,_0x43d3f2,_0x481648);}function issueReadToken(_0x2f276a,_0x1b5584,_0x5bbde3){return issueToken(_0x2f276a,{..._0x1b5584,'\x74\x79\x70':'\x72\x65\x61\x64'},_0x5bbde3);}function issueSessionScopedToken(_0x215e0d,_0x358716,_0x159065){const _0x496bd5=a0_0x25ee0e,_0xbab22b={'\x66\x70\x47\x65\x73':function(_0x4ab7b7,_0x48b645,_0x5a4d2a,_0x7f8a13){return _0x4ab7b7(_0x48b645,_0x5a4d2a,_0x7f8a13);}};return _0xbab22b[_0x496bd5(0x129)](issueReadToken,_0x215e0d,_0x358716,_0x159065);}function issueProjectScopedToken(_0x38948b,_0x29549c,_0x11ad61){const _0x2c9256={'\x6e\x69\x58\x62\x68':function(_0xdc0b3a,_0x537af8,_0x5b2fb2,_0x2e4948){return _0xdc0b3a(_0x537af8,_0x5b2fb2,_0x2e4948);}};return _0x2c9256['\x6e\x69\x58\x62\x68'](issueReadToken,_0x38948b,_0x29549c,_0x11ad61);}function issueBatchScopedToken(_0xfd5013,_0x116e34,_0x6bf98f){return issueReadToken(_0xfd5013,_0x116e34,_0x6bf98f);}function issueCollaborationToken(_0x51154e,_0x35954b,_0x1baaee){const _0x3507e7=a0_0x25ee0e,_0x3f523b={'\x49\x42\x59\x55\x47':'\x63\x6f\x6c\x6c\x61\x62\x6f\x72\x61\x74\x69\x6f\x6e'};return issueToken(_0x51154e,{'\x73\x75\x62':_0x35954b['\x75\x73\x65\x72\x49\x64'],'\x73\x69\x64':_0x35954b[_0x3507e7(0x12f)],'\x70\x69\x64':_0x35954b[_0x3507e7(0x141)],'\x63\x69\x64':_0x35954b[_0x3507e7(0x122)],'\x74\x79\x70':_0x3f523b[_0x3507e7(0x124)],'\x70\x72\x6f\x6a\x65\x63\x74\x49\x64':_0x35954b['\x70\x72\x6f\x6a\x65\x63\x74\x49\x64'],'\x64\x6f\x63\x75\x6d\x65\x6e\x74\x49\x64':_0x35954b[_0x3507e7(0x14b)],'\x61\x63\x63\x65\x73\x73':_0x35954b[_0x3507e7(0x13f)]},_0x1baaee);}function decodeToken(_0x1bbdfd){const _0x41c8f8=a0_0x25ee0e,_0x2391b5={'\x51\x42\x4d\x74\x4c':function(_0x51a121,_0x959695){return _0x51a121+_0x959695;},'\x51\x4e\x4e\x54\x6a':function(_0xe5a62,_0x3f2ce5){return _0xe5a62%_0x3f2ce5;}};try{const _0x1a622d=_0x1bbdfd[_0x41c8f8(0x134)]('\x2e');if(_0x1a622d['\x6c\x65\x6e\x67\x74\x68']!==0x3)return null;const _0x42bb22=_0x2391b5[_0x41c8f8(0x11e)](_0x1a622d[0x1],'\x3d'[_0x41c8f8(0x12c)](_0x2391b5[_0x41c8f8(0x149)](0x4-_0x1a622d[0x1][_0x41c8f8(0x133)]%0x4,0x4))),_0x3c64c0=Buffer['\x66\x72\x6f\x6d'](_0x42bb22[_0x41c8f8(0x11f)](/-/g,'\x2b')[_0x41c8f8(0x11f)](/_/g,'\x2f'),_0x41c8f8(0x140))[_0x41c8f8(0x13b)]();return JSON[_0x41c8f8(0x13d)](_0x3c64c0);}catch{return null;}}function getTokenTTL(_0x321f06){const _0x34ad26=a0_0x25ee0e,_0x19ece1=Math['\x66\x6c\x6f\x6f\x72'](Date[_0x34ad26(0x123)]()/0x3e8);return _0x321f06[_0x34ad26(0x13a)]-_0x19ece1;}function isTokenExpiringSoon(_0x2f7c38,_0x5db7af=0x3c){return getTokenTTL(_0x2f7c38)<=_0x5db7af;}var ProductTokenIssuer=class{[a0_0x25ee0e(0x141)];[a0_0x25ee0e(0x14c)];['\x74\x74\x6c\x4d\x69\x6e\x75\x74\x65\x73'];constructor(_0x55659a){const _0x2e8eb1=a0_0x25ee0e;this[_0x2e8eb1(0x141)]=_0x55659a['\x70\x72\x6f\x64\x75\x63\x74\x49\x64'],this[_0x2e8eb1(0x14c)]=_0x55659a[_0x2e8eb1(0x14c)],this['\x74\x74\x6c\x4d\x69\x6e\x75\x74\x65\x73']={'\x66\x72\x65\x65':_0x55659a[_0x2e8eb1(0x11c)]?.[_0x2e8eb1(0x11d)]??0xf,'\x70\x72\x6f':_0x55659a[_0x2e8eb1(0x11c)]?.[_0x2e8eb1(0x145)]??0xf0,'\x65\x6e\x74\x65\x72\x70\x72\x69\x73\x65':_0x55659a[_0x2e8eb1(0x11c)]?.[_0x2e8eb1(0x12a)]??0x1e0};}[a0_0x25ee0e(0x12e)](_0x4d7c59){const _0x1616b7=a0_0x25ee0e,_0x15c829=_0x4d7c59[_0x1616b7(0x13c)]??_0x1616b7(0x11d),_0x2f6b22=this['\x74\x74\x6c\x4d\x69\x6e\x75\x74\x65\x73'][_0x15c829]??this[_0x1616b7(0x11c)][_0x1616b7(0x11d)];return{'\x74\x6f\x6b\x65\x6e':issueReadToken(this[_0x1616b7(0x14c)],{'\x73\x75\x62':_0x4d7c59['\x75\x73\x65\x72\x49\x64'],'\x73\x69\x64':_0x4d7c59['\x73\x65\x73\x73\x69\x6f\x6e\x49\x64'],'\x70\x69\x64':this[_0x1616b7(0x141)],'\x63\x69\x64':_0x4d7c59[_0x1616b7(0x122)]},_0x2f6b22),'\x65\x78\x70\x69\x72\x65\x73\x41\x74':Math['\x66\x6c\x6f\x6f\x72'](Date['\x6e\x6f\x77']()/0x3e8)+_0x2f6b22*0x3c};}[a0_0x25ee0e(0x128)](_0xa3944c){const _0x25a770=a0_0x25ee0e,_0x2c23a9={'\x63\x50\x58\x6f\x6d':_0x25a770(0x11d),'\x59\x71\x4c\x4d\x67':function(_0x5afaf2,_0x49a18f){return _0x5afaf2*_0x49a18f;}},_0x57aa48=_0xa3944c[_0x25a770(0x13c)]??_0x2c23a9['\x63\x50\x58\x6f\x6d'],_0x1ada4d=this[_0x25a770(0x11c)][_0x57aa48]??this[_0x25a770(0x11c)][_0x25a770(0x11d)];return{'\x74\x6f\x6b\x65\x6e':issueCollaborationToken(this['\x73\x69\x67\x6e\x69\x6e\x67\x53\x65\x63\x72\x65\x74'],{'\x75\x73\x65\x72\x49\x64':_0xa3944c[_0x25a770(0x142)],'\x73\x65\x73\x73\x69\x6f\x6e\x49\x64':_0xa3944c[_0x25a770(0x12f)],'\x70\x72\x6f\x64\x75\x63\x74\x49\x64':this[_0x25a770(0x141)],'\x70\x72\x6f\x6a\x65\x63\x74\x49\x64':_0xa3944c[_0x25a770(0x127)],'\x64\x6f\x63\x75\x6d\x65\x6e\x74\x49\x64':_0xa3944c[_0x25a770(0x14b)],'\x61\x63\x63\x65\x73\x73':_0xa3944c['\x61\x63\x63\x65\x73\x73'],'\x73\x61\x6e\x64\x62\x6f\x78\x49\x64':_0xa3944c[_0x25a770(0x122)]},_0x1ada4d),'\x65\x78\x70\x69\x72\x65\x73\x41\x74':Math[_0x25a770(0x147)](Date['\x6e\x6f\x77']()/0x3e8)+_0x2c23a9['\x59\x71\x4c\x4d\x67'](_0x1ada4d,0x3c)};}[a0_0x25ee0e(0x121)](_0x3dd0e5=a0_0x25ee0e(0x11d)){const _0x2095dd=a0_0x25ee0e;return this[_0x2095dd(0x11c)][_0x3dd0e5]??this['\x74\x74\x6c\x4d\x69\x6e\x75\x74\x65\x73'][_0x2095dd(0x11d)];}};export{ProductTokenIssuer,decodeToken,getTokenTTL,isTokenExpiringSoon,issueBatchScopedToken,issueCollaborationToken,issueProjectScopedToken,issueReadToken,issueSessionScopedToken};
1
+ import { createHmac, timingSafeEqual } from "node:crypto";
2
+ //#region src/auth/tokens.ts
3
+ /**
4
+ * JWT Token Utilities
5
+ *
6
+ * Token generation and verification using HMAC-SHA256. Server-only
7
+ * (uses Node.js `crypto`).
8
+ */
9
+ /**
10
+ * Base64URL encode (RFC 7515).
11
+ */
12
+ function base64UrlEncode(data) {
13
+ return (typeof data === "string" ? Buffer.from(data) : data).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
14
+ }
15
+ /**
16
+ * Base64URL decode (RFC 7515) to raw bytes. Returns `null` if the
17
+ * input contains characters outside the base64url alphabet.
18
+ */
19
+ function decodeBase64UrlToBuffer(input) {
20
+ if (!/^[A-Za-z0-9_-]*$/.test(input)) return null;
21
+ const padded = input + "=".repeat((4 - input.length % 4) % 4);
22
+ return Buffer.from(padded.replace(/-/g, "+").replace(/_/g, "/"), "base64");
23
+ }
24
+ /**
25
+ * Create HMAC-SHA256 signature.
26
+ */
27
+ function createSignature(data, secret) {
28
+ return base64UrlEncode(createHmac("sha256", secret).update(data).digest());
29
+ }
30
+ /**
31
+ * JWT header (always the same for our use case).
32
+ */
33
+ const JWT_HEADER = base64UrlEncode(JSON.stringify({
34
+ alg: "HS256",
35
+ typ: "JWT"
36
+ }));
37
+ function issueToken(signingSecret, payload, ttlMinutes) {
38
+ const now = Math.floor(Date.now() / 1e3);
39
+ const fullPayload = {
40
+ ...payload,
41
+ iat: now,
42
+ exp: now + ttlMinutes * 60
43
+ };
44
+ const data = `${JWT_HEADER}.${base64UrlEncode(JSON.stringify(fullPayload))}`;
45
+ return `${data}.${createSignature(data, signingSecret)}`;
46
+ }
47
+ /**
48
+ * Issue a read token (JWT) for WebSocket authentication.
49
+ *
50
+ * @param signingSecret - The product's signing secret
51
+ * @param payload - Token payload (without iat/exp/typ, those are added)
52
+ * @param ttlMinutes - Token TTL in minutes
53
+ */
54
+ function issueReadToken(signingSecret, payload, ttlMinutes) {
55
+ return issueToken(signingSecret, {
56
+ ...payload,
57
+ typ: "read"
58
+ }, ttlMinutes);
59
+ }
60
+ /**
61
+ * Issue a session-scoped token (JWT) for WebSocket authentication.
62
+ * Grants access to a single session's events.
63
+ */
64
+ function issueSessionScopedToken(signingSecret, payload, ttlMinutes) {
65
+ return issueReadToken(signingSecret, payload, ttlMinutes);
66
+ }
67
+ /**
68
+ * Issue a project-scoped token (JWT) for WebSocket authentication.
69
+ * Grants access to all sessions within a single project.
70
+ */
71
+ function issueProjectScopedToken(signingSecret, payload, ttlMinutes) {
72
+ return issueReadToken(signingSecret, payload, ttlMinutes);
73
+ }
74
+ /**
75
+ * Issue a batch-scoped token (JWT) for WebSocket authentication.
76
+ * Grants access to multiple projects (organization-level access).
77
+ */
78
+ function issueBatchScopedToken(signingSecret, payload, ttlMinutes) {
79
+ return issueReadToken(signingSecret, payload, ttlMinutes);
80
+ }
81
+ /**
82
+ * Issue a collaboration-scoped token (JWT) for collaborative document access.
83
+ * Grants read or write access to a single document in one project.
84
+ */
85
+ function issueCollaborationToken(signingSecret, payload, ttlMinutes) {
86
+ return issueToken(signingSecret, {
87
+ sub: payload.userId,
88
+ sid: payload.sessionId,
89
+ pid: payload.productId,
90
+ cid: payload.sandboxId,
91
+ typ: "collaboration",
92
+ projectId: payload.projectId,
93
+ documentId: payload.documentId,
94
+ access: payload.access
95
+ }, ttlMinutes);
96
+ }
97
+ /**
98
+ * Decode a JWT **without verifying its signature**.
99
+ *
100
+ * The deliberately scary name is the API contract: an HMAC-signed token
101
+ * whose signature has not been verified is a self-asserted blob of JSON,
102
+ * not an authenticated claim. Treating its fields as authoritative
103
+ * (e.g. `if (unsafeDecodeToken(t).sub === userId) grantAccess()`) is a
104
+ * straightforward authorization bypass — an attacker can mint any
105
+ * payload they like.
106
+ *
107
+ * Use this only when the signature has already been validated upstream
108
+ * (e.g. by an API gateway that strips the token after verification),
109
+ * for client-side `exp` peeking to decide whether to refresh, or for
110
+ * routing/logging keyed off non-security-sensitive claims.
111
+ *
112
+ * For any access-control decision, use {@link verifyToken} instead.
113
+ *
114
+ * Returns `null` if the token is malformed.
115
+ */
116
+ function unsafeDecodeToken(token) {
117
+ try {
118
+ const parts = token.split(".");
119
+ if (parts.length !== 3) return null;
120
+ const padded = parts[1] + "=".repeat((4 - parts[1].length % 4) % 4);
121
+ const decoded = Buffer.from(padded.replace(/-/g, "+").replace(/_/g, "/"), "base64").toString();
122
+ return JSON.parse(decoded);
123
+ } catch {
124
+ return null;
125
+ }
126
+ }
127
+ /**
128
+ * Verify a JWT's HMAC-SHA256 signature against `signingSecret` and
129
+ * check that it has not expired. Returns the decoded payload on
130
+ * success, or `null` on any failure (malformed token, bad signature,
131
+ * expired, or unexpected algorithm).
132
+ *
133
+ * Signature comparison is constant-time. Callers must use this — not
134
+ * {@link unsafeDecodeToken} — for any authorization decision.
135
+ *
136
+ * @param token - The JWT to verify
137
+ * @param signingSecret - The same secret used to issue the token
138
+ * @param options.clockSkewSeconds - Tolerance for `exp` checks; default `0`
139
+ */
140
+ function verifyToken(token, signingSecret, options = {}) {
141
+ try {
142
+ const parts = token.split(".");
143
+ if (parts.length !== 3) return null;
144
+ const [headerB64, payloadB64, signatureB64] = parts;
145
+ if (!headerB64 || !payloadB64 || !signatureB64) return null;
146
+ let header;
147
+ try {
148
+ const headerPadded = headerB64 + "=".repeat((4 - headerB64.length % 4) % 4);
149
+ const headerJson = Buffer.from(headerPadded.replace(/-/g, "+").replace(/_/g, "/"), "base64").toString();
150
+ header = JSON.parse(headerJson);
151
+ } catch {
152
+ return null;
153
+ }
154
+ if (header.alg !== "HS256") return null;
155
+ const expectedSig = createSignature(`${headerB64}.${payloadB64}`, signingSecret);
156
+ const providedRaw = decodeBase64UrlToBuffer(signatureB64);
157
+ const expectedRaw = decodeBase64UrlToBuffer(expectedSig);
158
+ if (!providedRaw || !expectedRaw) return null;
159
+ if (providedRaw.length !== expectedRaw.length) return null;
160
+ if (!timingSafeEqual(providedRaw, expectedRaw)) return null;
161
+ const payload = unsafeDecodeToken(token);
162
+ if (!payload) return null;
163
+ const now = Math.floor(Date.now() / 1e3);
164
+ const skew = Math.max(0, options.clockSkewSeconds ?? 0);
165
+ if (typeof payload.exp !== "number" || payload.exp + skew < now) return null;
166
+ return payload;
167
+ } catch {
168
+ return null;
169
+ }
170
+ }
171
+ /**
172
+ * Get time until token expires (in seconds).
173
+ * Returns negative if expired.
174
+ */
175
+ function getTokenTTL(payload) {
176
+ const now = Math.floor(Date.now() / 1e3);
177
+ return payload.exp - now;
178
+ }
179
+ /**
180
+ * Check if token is expiring soon (within buffer seconds).
181
+ */
182
+ function isTokenExpiringSoon(payload, bufferSeconds = 60) {
183
+ return getTokenTTL(payload) <= bufferSeconds;
184
+ }
185
+ //#endregion
186
+ //#region src/auth/index.ts
187
+ /**
188
+ * Authentication Utilities
189
+ *
190
+ * Token issuance for application backends. Server-only (uses Node.js crypto).
191
+ *
192
+ * @example
193
+ * ```typescript
194
+ * import { ProductTokenIssuer } from "@tangle-network/sandbox/auth";
195
+ *
196
+ * const issuer = new ProductTokenIssuer({
197
+ * productId: "my-product",
198
+ * signingSecret: process.env.SANDBOX_SIGNING_SECRET!,
199
+ * });
200
+ *
201
+ * const { token, expiresAt } = issuer.issue({
202
+ * userId: "user_123",
203
+ * sessionId: "sess_abc",
204
+ * tier: "pro",
205
+ * });
206
+ * ```
207
+ *
208
+ * @packageDocumentation
209
+ */
210
+ /**
211
+ * Token issuer for application backend services.
212
+ *
213
+ * Use this in your backend to issue read tokens for WebSocket connections.
214
+ */
215
+ var ProductTokenIssuer = class {
216
+ productId;
217
+ signingSecret;
218
+ ttlMinutes;
219
+ constructor(config) {
220
+ this.productId = config.productId;
221
+ this.signingSecret = config.signingSecret;
222
+ this.ttlMinutes = {
223
+ free: config.ttlMinutes?.free ?? 15,
224
+ pro: config.ttlMinutes?.pro ?? 240,
225
+ enterprise: config.ttlMinutes?.enterprise ?? 480
226
+ };
227
+ }
228
+ /**
229
+ * Issue a read token for a user session.
230
+ */
231
+ issue(params) {
232
+ const tier = params.tier ?? "free";
233
+ const ttl = this.ttlMinutes[tier] ?? this.ttlMinutes.free;
234
+ return {
235
+ token: issueReadToken(this.signingSecret, {
236
+ sub: params.userId,
237
+ sid: params.sessionId,
238
+ pid: this.productId,
239
+ cid: params.sandboxId
240
+ }, ttl),
241
+ expiresAt: Math.floor(Date.now() / 1e3) + ttl * 60
242
+ };
243
+ }
244
+ /**
245
+ * Issue a collaboration token for a single document.
246
+ */
247
+ issueCollaboration(params) {
248
+ const tier = params.tier ?? "free";
249
+ const ttl = this.ttlMinutes[tier] ?? this.ttlMinutes.free;
250
+ return {
251
+ token: issueCollaborationToken(this.signingSecret, {
252
+ userId: params.userId,
253
+ sessionId: params.sessionId,
254
+ productId: this.productId,
255
+ projectId: params.projectId,
256
+ documentId: params.documentId,
257
+ access: params.access,
258
+ sandboxId: params.sandboxId
259
+ }, ttl),
260
+ expiresAt: Math.floor(Date.now() / 1e3) + ttl * 60
261
+ };
262
+ }
263
+ /**
264
+ * Get the TTL in minutes for a tier.
265
+ */
266
+ getTtlMinutes(tier = "free") {
267
+ return this.ttlMinutes[tier] ?? this.ttlMinutes.free;
268
+ }
269
+ };
270
+ //#endregion
271
+ export { ProductTokenIssuer, getTokenTTL, isTokenExpiringSoon, issueBatchScopedToken, issueCollaborationToken, issueProjectScopedToken, issueReadToken, issueSessionScopedToken, unsafeDecodeToken, verifyToken };