@lowerdeck/tokens 1.0.0

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.
@@ -0,0 +1,13 @@
1
+
2
+ $ microbundle
3
+ No name was provided for external module '@lowerdeck/base62' in output.globals – guessing 'base62'
4
+ No name was provided for external module '@lowerdeck/memo' in output.globals – guessing 'memo'
5
+ Build "@lowerdeck/tokens" to dist:
6
+ 932 B: index.cjs.gz
7
+ 834 B: index.cjs.br
8
+ 800 B: index.modern.js.gz
9
+ 713 B: index.modern.js.br
10
+ 936 B: index.module.js.gz
11
+ 839 B: index.module.js.br
12
+ 1015 B: index.umd.js.gz
13
+ 903 B: index.umd.js.br
@@ -0,0 +1,26 @@
1
+
2
+ $ vitest run --passWithNoTests
3
+ [?25l
4
+  RUN  v3.2.4 /Users/tobias/code/metorial/metorial-enterprise/oss/src/packages/backend/tokens
5
+
6
+ [?2026h
7
+  ❯ src/tokens.test.ts [queued]
8
+
9
+  Test Files 0 passed (1)
10
+  Tests 0 passed (0)
11
+  Start at 10:23:44
12
+  Duration 101ms
13
+ [?2026l ✓ src/tokens.test.ts (6 tests) 14ms
14
+ ✓ Tokens > should sign and verify tokens 4ms
15
+ ✓ Tokens > should not verify tokens with wrong type 1ms
16
+ ✓ Tokens > should not verify tokens with wrong version 2ms
17
+ ✓ Tokens > should not verify tokens with wrong signature 1ms
18
+ ✓ Tokens > should not verify tokens with too few parts 1ms
19
+ ✓ Tokens > should not verify expired tokens 2ms
20
+
21
+  Test Files  1 passed (1)
22
+  Tests  6 passed (6)
23
+  Start at  10:23:44
24
+  Duration  247ms (transform 47ms, setup 0ms, collect 52ms, tests 14ms, environment 0ms, prepare 45ms)
25
+
26
+ [?25h
package/README.md ADDED
@@ -0,0 +1,63 @@
1
+ # `@lowerdeck/tokens`
2
+
3
+ Create and verify signed tokens with HMAC or asymmetric cryptography. Supports expiration checking and type validation with a clean token format.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @lowerdeck/tokens
9
+ yarn add @lowerdeck/tokens
10
+ bun add @lowerdeck/tokens
11
+ pnpm add @lowerdeck/tokens
12
+ ```
13
+
14
+ ## Usage
15
+
16
+ ```typescript
17
+ import { Tokens } from '@lowerdeck/tokens';
18
+
19
+ // Create token manager with HMAC-SHA384
20
+ const tokens = new Tokens({
21
+ alg: 'HS384',
22
+ secret: 'your-secret-key'
23
+ });
24
+
25
+ // Sign a token
26
+ const token = await tokens.sign({
27
+ type: 'reset-password',
28
+ data: { userId: '123', email: 'user@example.com' },
29
+ expiresAt: new Date(Date.now() + 3600000) // 1 hour
30
+ });
31
+ console.log(token); // 'reset-password_v1_eyJ1c2VySWQi..._xyz123'
32
+
33
+ // Verify and decode
34
+ const result = await tokens.verify({
35
+ token,
36
+ expectedType: 'reset-password'
37
+ });
38
+
39
+ if (result.success) {
40
+ console.log(result.data); // { userId: '123', email: '...' }
41
+ } else {
42
+ console.log(result.error); // 'expired', 'invalid-signature', 'wrong-type'
43
+ }
44
+
45
+ // Asymmetric signing (RS256, ES384)
46
+ const rsaTokens = new Tokens({
47
+ alg: 'RS256',
48
+ privateKey: privateKeyPEM,
49
+ publicKey: publicKeyPEM
50
+ });
51
+ ```
52
+
53
+ ## Token Format
54
+
55
+ Tokens follow the format: `type_v1_data_signature`
56
+
57
+ ## License
58
+
59
+ This project is licensed under the Apache License 2.0.
60
+
61
+ <div align="center">
62
+ <sub>Built with ❤️ by <a href="https://metorial.com">Metorial</a></sub>
63
+ </div>
package/dist/index.cjs ADDED
@@ -0,0 +1,2 @@
1
+ var e=require("@lowerdeck/base62"),r=require("@lowerdeck/memo");exports.Tokens=/*#__PURE__*/function(){function t(t){this.keys=void 0,this.signer=void 0,this.keys=t,this.signer=function(t){if("secret"in t){var n=r.memo(function(){return crypto.subtle.importKey("raw",(new TextEncoder).encode(t.secret),{name:"HMAC",hash:{name:"SHA-384"}},!1,["sign","verify"])});return{sign:function(r){try{var t=crypto.subtle,i=t.sign;return Promise.resolve(n()).then(function(n){return Promise.resolve(i.call(t,{name:"HMAC",hash:{name:"SHA-384"}},n,(new TextEncoder).encode(r))).then(function(r){return e.base62.encode(new Uint8Array(r))})})}catch(e){return Promise.reject(e)}},verify:function(r,t){try{var i=crypto.subtle,o=i.verify;return Promise.resolve(n()).then(function(n){return o.call(i,{name:"HMAC",hash:{name:"SHA-384"}},n,new Uint8Array(e.base62.decodeRaw(t)),(new TextEncoder).encode(r))})}catch(e){return Promise.reject(e)}}}}var i=r.memo(function(){try{if("string"==typeof t.privateKey){var e=JSON.parse(t.privateKey);return Promise.resolve(crypto.subtle.importKey("jwk",e,{name:"EC"==e.kty?"ECDSA":e.kty,namedCurve:e.crv},!0,["sign"]))}return Promise.resolve(t.privateKey())}catch(e){return Promise.reject(e)}}),o=r.memo(function(){try{if("string"==typeof t.publicKey){var e=JSON.parse(t.publicKey);return Promise.resolve(crypto.subtle.importKey("jwk",JSON.parse(t.publicKey),{name:"EC"==e.kty?"ECDSA":e.kty,namedCurve:e.crv},!0,["verify"]))}return Promise.resolve(t.publicKey())}catch(e){return Promise.reject(e)}});return{sign:function(r){try{return Promise.resolve(i()).then(function(t){return Promise.resolve(crypto.subtle.sign({name:t.algorithm.name,hash:{name:"SHA-384"}},t,(new TextEncoder).encode(r))).then(function(r){return e.base62.encode(new Uint8Array(r))})})}catch(e){return Promise.reject(e)}},verify:function(r,t){try{var n=crypto.subtle,i=n.verify;return Promise.resolve(o()).then(function(o){return i.call(n,{name:"ECDSA",hash:{name:"SHA-384"}},o,new Uint8Array(e.base62.decodeRaw(t)),(new TextEncoder).encode(r))})}catch(e){return Promise.reject(e)}}}}(t)}var n=t.prototype;return n.sign=function(r){var t=r.type,n=r.data,i=r.expiresAt;try{var o=t+"_v1_"+e.base62.encode(JSON.stringify({d:n,e:null==i?void 0:i.getTime(),c:Date.now()}));return Promise.resolve(this.signer.sign(o)).then(function(e){return o+"_"+e})}catch(e){return Promise.reject(e)}},n.verify=function(r){var t=r.token,n=r.expectedType;try{var i=t.split("_");if(i.length<4)return Promise.resolve({verified:!1});var o=i.pop(),s=i.pop(),c=i.pop(),a=i.join("_");return a!=n||"v1"!=c?Promise.resolve({verified:!1}):Promise.resolve(this.signer.verify(a+"_"+c+"_"+s,o)).then(function(r){if(!r)return{verified:!1};var t=JSON.parse(e.base62.decode(s)),n=t.e?new Date(t.e):null;if(n&&n<new Date)return{verified:!1};var i=new Date(t.c);return{verified:!0,type:a,expiresAt:n,createdAt:i,data:t.d}})}catch(e){return Promise.reject(e)}},t.decode=function(r){var t=r.split("_");return t.length<4?null:JSON.parse(e.base62.decode(t[t.length-2]))},t}();
2
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","sources":["../src/tokens.ts"],"sourcesContent":["import { base62 } from '@lowerdeck/base62';\nimport { memo } from '@lowerdeck/memo';\n\nexport type TokenKeys =\n | {\n privateKey: string | (() => Promise<CryptoKey> | CryptoKey);\n publicKey: string | (() => Promise<CryptoKey> | CryptoKey);\n }\n | {\n secret: string;\n };\n\ntype Signer = {\n sign: (data: string) => Promise<string>;\n verify: (data: string, signature: string) => Promise<boolean>;\n};\n\nlet getSigner = (keys: TokenKeys): Signer => {\n if ('secret' in keys) {\n let keyMemo = memo(() =>\n crypto.subtle.importKey(\n 'raw',\n new TextEncoder().encode(keys.secret),\n { name: 'HMAC', hash: { name: 'SHA-384' } },\n false,\n ['sign', 'verify']\n )\n );\n\n return {\n async sign(data) {\n let signature = await crypto.subtle.sign(\n { name: 'HMAC', hash: { name: 'SHA-384' } },\n await keyMemo(),\n new TextEncoder().encode(data)\n );\n\n return base62.encode(new Uint8Array(signature));\n },\n\n async verify(data, signature) {\n return crypto.subtle.verify(\n { name: 'HMAC', hash: { name: 'SHA-384' } },\n await keyMemo(),\n new Uint8Array(base62.decodeRaw(signature)),\n new TextEncoder().encode(data)\n );\n }\n };\n } else {\n let privateKeyMemo = memo(async () => {\n if (typeof keys.privateKey == 'string') {\n let data = JSON.parse(keys.privateKey) as any;\n\n return await crypto.subtle.importKey(\n 'jwk',\n data,\n { name: data.kty == 'EC' ? 'ECDSA' : data.kty, namedCurve: data.crv },\n true,\n ['sign']\n );\n } else {\n return (await keys.privateKey()) as CryptoKey;\n }\n });\n\n let publicKeyMemo = memo(async () => {\n if (typeof keys.publicKey == 'string') {\n let data = JSON.parse(keys.publicKey) as any;\n\n return await crypto.subtle.importKey(\n 'jwk',\n JSON.parse(keys.publicKey) as any,\n { name: data.kty == 'EC' ? 'ECDSA' : data.kty, namedCurve: data.crv },\n true,\n ['verify']\n );\n } else {\n return (await keys.publicKey()) as CryptoKey;\n }\n });\n\n return {\n async sign(data) {\n let key = await privateKeyMemo();\n\n let signature = await crypto.subtle.sign(\n { name: key.algorithm.name, hash: { name: 'SHA-384' } },\n key,\n new TextEncoder().encode(data)\n );\n\n return base62.encode(new Uint8Array(signature));\n },\n\n async verify(data, signature) {\n return crypto.subtle.verify(\n { name: 'ECDSA', hash: { name: 'SHA-384' } },\n await publicKeyMemo(),\n new Uint8Array(base62.decodeRaw(signature)),\n new TextEncoder().encode(data)\n );\n }\n };\n }\n};\n\nexport class Tokens {\n private signer: Signer;\n constructor(readonly keys: TokenKeys) {\n this.signer = getSigner(keys);\n }\n\n async sign({ type, data, expiresAt }: { type: string; data: any; expiresAt?: Date }) {\n let token = `${type}_v1_${base62.encode(\n JSON.stringify({\n d: data,\n e: expiresAt?.getTime(),\n c: Date.now()\n })\n )}`;\n\n let signature = await this.signer.sign(token);\n return `${token}_${signature}`;\n }\n\n async verify({ token, expectedType }: { expectedType: string; token: string }) {\n let parts = token.split('_');\n if (parts.length < 4) return { verified: false as const };\n\n let signatureBase62 = parts.pop()!;\n let dataBase62 = parts.pop()!;\n let version = parts.pop()!;\n let type = parts.join('_');\n if (type != expectedType) return { verified: false as const };\n\n if (version != 'v1') return { verified: false as const };\n\n let main = `${type}_${version}_${dataBase62}`;\n\n let verified = await this.signer.verify(main, signatureBase62);\n if (!verified) return { verified: false as const };\n\n let data = JSON.parse(base62.decode(dataBase62));\n let expiresAt = data.e ? new Date(data.e) : null;\n if (expiresAt && expiresAt < new Date()) return { verified: false as const };\n\n let createdAt = new Date(data.c);\n\n return {\n verified: true as const,\n\n type,\n expiresAt,\n createdAt,\n data: data.d\n };\n }\n\n static decode(token: string) {\n let parts = token.split('_');\n if (parts.length < 4) return null;\n\n let dataBase62 = parts[parts.length - 2];\n return JSON.parse(base62.decode(dataBase62));\n }\n}\n"],"names":["Tokens","keys","signer","this","keyMemo","memo","crypto","subtle","importKey","TextEncoder","encode","secret","name","hash","sign","data","_crypto$subtle","_sign","Promise","resolve","then","_keyMemo","call","signature","base62","Uint8Array","e","reject","verify","_crypto$subtle2","_verify","_keyMemo2","decodeRaw","privateKeyMemo","privateKey","JSON","parse","kty","namedCurve","crv","publicKeyMemo","publicKey","key","algorithm","_crypto$subtle3","_verify2","_publicKeyMemo","getSigner","_proto","prototype","_ref","type","expiresAt","_this","token","stringify","d","getTime","c","Date","now","_ref2","expectedType","_this2","parts","split","length","verified","signatureBase62","pop","dataBase62","version","join","decode","createdAt"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export * from './tokens';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC"}
@@ -0,0 +1,2 @@
1
+ import{base62 as e}from"@lowerdeck/base62";import{memo as t}from"@lowerdeck/memo";class r{constructor(r){this.keys=void 0,this.signer=void 0,this.keys=r,this.signer=(r=>{if("secret"in r){let n=t(()=>crypto.subtle.importKey("raw",(new TextEncoder).encode(r.secret),{name:"HMAC",hash:{name:"SHA-384"}},!1,["sign","verify"]));return{async sign(t){let r=await crypto.subtle.sign({name:"HMAC",hash:{name:"SHA-384"}},await n(),(new TextEncoder).encode(t));return e.encode(new Uint8Array(r))},verify:async(t,r)=>crypto.subtle.verify({name:"HMAC",hash:{name:"SHA-384"}},await n(),new Uint8Array(e.decodeRaw(r)),(new TextEncoder).encode(t))}}{let n=t(async()=>{if("string"==typeof r.privateKey){let e=JSON.parse(r.privateKey);return await crypto.subtle.importKey("jwk",e,{name:"EC"==e.kty?"ECDSA":e.kty,namedCurve:e.crv},!0,["sign"])}return await r.privateKey()}),i=t(async()=>{if("string"==typeof r.publicKey){let e=JSON.parse(r.publicKey);return await crypto.subtle.importKey("jwk",JSON.parse(r.publicKey),{name:"EC"==e.kty?"ECDSA":e.kty,namedCurve:e.crv},!0,["verify"])}return await r.publicKey()});return{async sign(t){let r=await n(),i=await crypto.subtle.sign({name:r.algorithm.name,hash:{name:"SHA-384"}},r,(new TextEncoder).encode(t));return e.encode(new Uint8Array(i))},verify:async(t,r)=>crypto.subtle.verify({name:"ECDSA",hash:{name:"SHA-384"}},await i(),new Uint8Array(e.decodeRaw(r)),(new TextEncoder).encode(t))}}})(r)}async sign({type:t,data:r,expiresAt:n}){let i=`${t}_v1_${e.encode(JSON.stringify({d:r,e:null==n?void 0:n.getTime(),c:Date.now()}))}`;return`${i}_${await this.signer.sign(i)}`}async verify({token:t,expectedType:r}){let n=t.split("_");if(n.length<4)return{verified:!1};let i=n.pop(),a=n.pop(),s=n.pop(),c=n.join("_");if(c!=r)return{verified:!1};if("v1"!=s)return{verified:!1};let o=`${c}_${s}_${a}`;if(!await this.signer.verify(o,i))return{verified:!1};let y=JSON.parse(e.decode(a)),d=y.e?new Date(y.e):null;return d&&d<new Date?{verified:!1}:{verified:!0,type:c,expiresAt:d,createdAt:new Date(y.c),data:y.d}}static decode(t){let r=t.split("_");return r.length<4?null:JSON.parse(e.decode(r[r.length-2]))}}export{r as Tokens};
2
+ //# sourceMappingURL=index.modern.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.modern.js","sources":["../src/tokens.ts"],"sourcesContent":["import { base62 } from '@lowerdeck/base62';\nimport { memo } from '@lowerdeck/memo';\n\nexport type TokenKeys =\n | {\n privateKey: string | (() => Promise<CryptoKey> | CryptoKey);\n publicKey: string | (() => Promise<CryptoKey> | CryptoKey);\n }\n | {\n secret: string;\n };\n\ntype Signer = {\n sign: (data: string) => Promise<string>;\n verify: (data: string, signature: string) => Promise<boolean>;\n};\n\nlet getSigner = (keys: TokenKeys): Signer => {\n if ('secret' in keys) {\n let keyMemo = memo(() =>\n crypto.subtle.importKey(\n 'raw',\n new TextEncoder().encode(keys.secret),\n { name: 'HMAC', hash: { name: 'SHA-384' } },\n false,\n ['sign', 'verify']\n )\n );\n\n return {\n async sign(data) {\n let signature = await crypto.subtle.sign(\n { name: 'HMAC', hash: { name: 'SHA-384' } },\n await keyMemo(),\n new TextEncoder().encode(data)\n );\n\n return base62.encode(new Uint8Array(signature));\n },\n\n async verify(data, signature) {\n return crypto.subtle.verify(\n { name: 'HMAC', hash: { name: 'SHA-384' } },\n await keyMemo(),\n new Uint8Array(base62.decodeRaw(signature)),\n new TextEncoder().encode(data)\n );\n }\n };\n } else {\n let privateKeyMemo = memo(async () => {\n if (typeof keys.privateKey == 'string') {\n let data = JSON.parse(keys.privateKey) as any;\n\n return await crypto.subtle.importKey(\n 'jwk',\n data,\n { name: data.kty == 'EC' ? 'ECDSA' : data.kty, namedCurve: data.crv },\n true,\n ['sign']\n );\n } else {\n return (await keys.privateKey()) as CryptoKey;\n }\n });\n\n let publicKeyMemo = memo(async () => {\n if (typeof keys.publicKey == 'string') {\n let data = JSON.parse(keys.publicKey) as any;\n\n return await crypto.subtle.importKey(\n 'jwk',\n JSON.parse(keys.publicKey) as any,\n { name: data.kty == 'EC' ? 'ECDSA' : data.kty, namedCurve: data.crv },\n true,\n ['verify']\n );\n } else {\n return (await keys.publicKey()) as CryptoKey;\n }\n });\n\n return {\n async sign(data) {\n let key = await privateKeyMemo();\n\n let signature = await crypto.subtle.sign(\n { name: key.algorithm.name, hash: { name: 'SHA-384' } },\n key,\n new TextEncoder().encode(data)\n );\n\n return base62.encode(new Uint8Array(signature));\n },\n\n async verify(data, signature) {\n return crypto.subtle.verify(\n { name: 'ECDSA', hash: { name: 'SHA-384' } },\n await publicKeyMemo(),\n new Uint8Array(base62.decodeRaw(signature)),\n new TextEncoder().encode(data)\n );\n }\n };\n }\n};\n\nexport class Tokens {\n private signer: Signer;\n constructor(readonly keys: TokenKeys) {\n this.signer = getSigner(keys);\n }\n\n async sign({ type, data, expiresAt }: { type: string; data: any; expiresAt?: Date }) {\n let token = `${type}_v1_${base62.encode(\n JSON.stringify({\n d: data,\n e: expiresAt?.getTime(),\n c: Date.now()\n })\n )}`;\n\n let signature = await this.signer.sign(token);\n return `${token}_${signature}`;\n }\n\n async verify({ token, expectedType }: { expectedType: string; token: string }) {\n let parts = token.split('_');\n if (parts.length < 4) return { verified: false as const };\n\n let signatureBase62 = parts.pop()!;\n let dataBase62 = parts.pop()!;\n let version = parts.pop()!;\n let type = parts.join('_');\n if (type != expectedType) return { verified: false as const };\n\n if (version != 'v1') return { verified: false as const };\n\n let main = `${type}_${version}_${dataBase62}`;\n\n let verified = await this.signer.verify(main, signatureBase62);\n if (!verified) return { verified: false as const };\n\n let data = JSON.parse(base62.decode(dataBase62));\n let expiresAt = data.e ? new Date(data.e) : null;\n if (expiresAt && expiresAt < new Date()) return { verified: false as const };\n\n let createdAt = new Date(data.c);\n\n return {\n verified: true as const,\n\n type,\n expiresAt,\n createdAt,\n data: data.d\n };\n }\n\n static decode(token: string) {\n let parts = token.split('_');\n if (parts.length < 4) return null;\n\n let dataBase62 = parts[parts.length - 2];\n return JSON.parse(base62.decode(dataBase62));\n }\n}\n"],"names":["Tokens","constructor","keys","this","signer","keyMemo","memo","crypto","subtle","importKey","TextEncoder","encode","secret","name","hash","sign","data","signature","base62","Uint8Array","async","verify","decodeRaw","privateKeyMemo","privateKey","JSON","parse","kty","namedCurve","crv","publicKeyMemo","publicKey","key","algorithm","getSigner","type","expiresAt","token","stringify","d","e","getTime","c","Date","now","expectedType","parts","split","length","verified","signatureBase62","pop","dataBase62","version","join","main","decode","createdAt"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import{base62 as e}from"@lowerdeck/base62";import{memo as r}from"@lowerdeck/memo";var t=/*#__PURE__*/function(){function t(t){this.keys=void 0,this.signer=void 0,this.keys=t,this.signer=function(t){if("secret"in t){var n=r(function(){return crypto.subtle.importKey("raw",(new TextEncoder).encode(t.secret),{name:"HMAC",hash:{name:"SHA-384"}},!1,["sign","verify"])});return{sign:function(r){try{var t=crypto.subtle,i=t.sign;return Promise.resolve(n()).then(function(n){return Promise.resolve(i.call(t,{name:"HMAC",hash:{name:"SHA-384"}},n,(new TextEncoder).encode(r))).then(function(r){return e.encode(new Uint8Array(r))})})}catch(e){return Promise.reject(e)}},verify:function(r,t){try{var i=crypto.subtle,o=i.verify;return Promise.resolve(n()).then(function(n){return o.call(i,{name:"HMAC",hash:{name:"SHA-384"}},n,new Uint8Array(e.decodeRaw(t)),(new TextEncoder).encode(r))})}catch(e){return Promise.reject(e)}}}}var i=r(function(){try{if("string"==typeof t.privateKey){var e=JSON.parse(t.privateKey);return Promise.resolve(crypto.subtle.importKey("jwk",e,{name:"EC"==e.kty?"ECDSA":e.kty,namedCurve:e.crv},!0,["sign"]))}return Promise.resolve(t.privateKey())}catch(e){return Promise.reject(e)}}),o=r(function(){try{if("string"==typeof t.publicKey){var e=JSON.parse(t.publicKey);return Promise.resolve(crypto.subtle.importKey("jwk",JSON.parse(t.publicKey),{name:"EC"==e.kty?"ECDSA":e.kty,namedCurve:e.crv},!0,["verify"]))}return Promise.resolve(t.publicKey())}catch(e){return Promise.reject(e)}});return{sign:function(r){try{return Promise.resolve(i()).then(function(t){return Promise.resolve(crypto.subtle.sign({name:t.algorithm.name,hash:{name:"SHA-384"}},t,(new TextEncoder).encode(r))).then(function(r){return e.encode(new Uint8Array(r))})})}catch(e){return Promise.reject(e)}},verify:function(r,t){try{var n=crypto.subtle,i=n.verify;return Promise.resolve(o()).then(function(o){return i.call(n,{name:"ECDSA",hash:{name:"SHA-384"}},o,new Uint8Array(e.decodeRaw(t)),(new TextEncoder).encode(r))})}catch(e){return Promise.reject(e)}}}}(t)}var n=t.prototype;return n.sign=function(r){var t=r.type,n=r.data,i=r.expiresAt;try{var o=t+"_v1_"+e.encode(JSON.stringify({d:n,e:null==i?void 0:i.getTime(),c:Date.now()}));return Promise.resolve(this.signer.sign(o)).then(function(e){return o+"_"+e})}catch(e){return Promise.reject(e)}},n.verify=function(r){var t=r.token,n=r.expectedType;try{var i=t.split("_");if(i.length<4)return Promise.resolve({verified:!1});var o=i.pop(),c=i.pop(),s=i.pop(),a=i.join("_");return a!=n||"v1"!=s?Promise.resolve({verified:!1}):Promise.resolve(this.signer.verify(a+"_"+s+"_"+c,o)).then(function(r){if(!r)return{verified:!1};var t=JSON.parse(e.decode(c)),n=t.e?new Date(t.e):null;if(n&&n<new Date)return{verified:!1};var i=new Date(t.c);return{verified:!0,type:a,expiresAt:n,createdAt:i,data:t.d}})}catch(e){return Promise.reject(e)}},t.decode=function(r){var t=r.split("_");return t.length<4?null:JSON.parse(e.decode(t[t.length-2]))},t}();export{t as Tokens};
2
+ //# sourceMappingURL=index.module.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.module.js","sources":["../src/tokens.ts"],"sourcesContent":["import { base62 } from '@lowerdeck/base62';\nimport { memo } from '@lowerdeck/memo';\n\nexport type TokenKeys =\n | {\n privateKey: string | (() => Promise<CryptoKey> | CryptoKey);\n publicKey: string | (() => Promise<CryptoKey> | CryptoKey);\n }\n | {\n secret: string;\n };\n\ntype Signer = {\n sign: (data: string) => Promise<string>;\n verify: (data: string, signature: string) => Promise<boolean>;\n};\n\nlet getSigner = (keys: TokenKeys): Signer => {\n if ('secret' in keys) {\n let keyMemo = memo(() =>\n crypto.subtle.importKey(\n 'raw',\n new TextEncoder().encode(keys.secret),\n { name: 'HMAC', hash: { name: 'SHA-384' } },\n false,\n ['sign', 'verify']\n )\n );\n\n return {\n async sign(data) {\n let signature = await crypto.subtle.sign(\n { name: 'HMAC', hash: { name: 'SHA-384' } },\n await keyMemo(),\n new TextEncoder().encode(data)\n );\n\n return base62.encode(new Uint8Array(signature));\n },\n\n async verify(data, signature) {\n return crypto.subtle.verify(\n { name: 'HMAC', hash: { name: 'SHA-384' } },\n await keyMemo(),\n new Uint8Array(base62.decodeRaw(signature)),\n new TextEncoder().encode(data)\n );\n }\n };\n } else {\n let privateKeyMemo = memo(async () => {\n if (typeof keys.privateKey == 'string') {\n let data = JSON.parse(keys.privateKey) as any;\n\n return await crypto.subtle.importKey(\n 'jwk',\n data,\n { name: data.kty == 'EC' ? 'ECDSA' : data.kty, namedCurve: data.crv },\n true,\n ['sign']\n );\n } else {\n return (await keys.privateKey()) as CryptoKey;\n }\n });\n\n let publicKeyMemo = memo(async () => {\n if (typeof keys.publicKey == 'string') {\n let data = JSON.parse(keys.publicKey) as any;\n\n return await crypto.subtle.importKey(\n 'jwk',\n JSON.parse(keys.publicKey) as any,\n { name: data.kty == 'EC' ? 'ECDSA' : data.kty, namedCurve: data.crv },\n true,\n ['verify']\n );\n } else {\n return (await keys.publicKey()) as CryptoKey;\n }\n });\n\n return {\n async sign(data) {\n let key = await privateKeyMemo();\n\n let signature = await crypto.subtle.sign(\n { name: key.algorithm.name, hash: { name: 'SHA-384' } },\n key,\n new TextEncoder().encode(data)\n );\n\n return base62.encode(new Uint8Array(signature));\n },\n\n async verify(data, signature) {\n return crypto.subtle.verify(\n { name: 'ECDSA', hash: { name: 'SHA-384' } },\n await publicKeyMemo(),\n new Uint8Array(base62.decodeRaw(signature)),\n new TextEncoder().encode(data)\n );\n }\n };\n }\n};\n\nexport class Tokens {\n private signer: Signer;\n constructor(readonly keys: TokenKeys) {\n this.signer = getSigner(keys);\n }\n\n async sign({ type, data, expiresAt }: { type: string; data: any; expiresAt?: Date }) {\n let token = `${type}_v1_${base62.encode(\n JSON.stringify({\n d: data,\n e: expiresAt?.getTime(),\n c: Date.now()\n })\n )}`;\n\n let signature = await this.signer.sign(token);\n return `${token}_${signature}`;\n }\n\n async verify({ token, expectedType }: { expectedType: string; token: string }) {\n let parts = token.split('_');\n if (parts.length < 4) return { verified: false as const };\n\n let signatureBase62 = parts.pop()!;\n let dataBase62 = parts.pop()!;\n let version = parts.pop()!;\n let type = parts.join('_');\n if (type != expectedType) return { verified: false as const };\n\n if (version != 'v1') return { verified: false as const };\n\n let main = `${type}_${version}_${dataBase62}`;\n\n let verified = await this.signer.verify(main, signatureBase62);\n if (!verified) return { verified: false as const };\n\n let data = JSON.parse(base62.decode(dataBase62));\n let expiresAt = data.e ? new Date(data.e) : null;\n if (expiresAt && expiresAt < new Date()) return { verified: false as const };\n\n let createdAt = new Date(data.c);\n\n return {\n verified: true as const,\n\n type,\n expiresAt,\n createdAt,\n data: data.d\n };\n }\n\n static decode(token: string) {\n let parts = token.split('_');\n if (parts.length < 4) return null;\n\n let dataBase62 = parts[parts.length - 2];\n return JSON.parse(base62.decode(dataBase62));\n }\n}\n"],"names":["Tokens","keys","signer","this","keyMemo","memo","crypto","subtle","importKey","TextEncoder","encode","secret","name","hash","sign","data","_crypto$subtle","_sign","Promise","resolve","then","_keyMemo","call","signature","base62","Uint8Array","e","reject","verify","_crypto$subtle2","_verify","_keyMemo2","decodeRaw","privateKeyMemo","privateKey","JSON","parse","kty","namedCurve","crv","publicKeyMemo","publicKey","key","algorithm","_crypto$subtle3","_verify2","_publicKeyMemo","getSigner","_proto","prototype","_ref","type","expiresAt","_this","token","stringify","d","getTime","c","Date","now","_ref2","expectedType","_this2","parts","split","length","verified","signatureBase62","pop","dataBase62","version","join","decode","createdAt"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ !function(e,r){"object"==typeof exports&&"undefined"!=typeof module?r(exports,require("@lowerdeck/base62"),require("@lowerdeck/memo")):"function"==typeof define&&define.amd?define(["exports","@lowerdeck/base62","@lowerdeck/memo"],r):r((e||self).tokens={},e.base62,e.memo)}(this,function(e,r,t){e.Tokens=/*#__PURE__*/function(){function e(e){this.keys=void 0,this.signer=void 0,this.keys=e,this.signer=function(e){if("secret"in e){var n=t.memo(function(){return crypto.subtle.importKey("raw",(new TextEncoder).encode(e.secret),{name:"HMAC",hash:{name:"SHA-384"}},!1,["sign","verify"])});return{sign:function(e){try{var t=crypto.subtle,i=t.sign;return Promise.resolve(n()).then(function(n){return Promise.resolve(i.call(t,{name:"HMAC",hash:{name:"SHA-384"}},n,(new TextEncoder).encode(e))).then(function(e){return r.base62.encode(new Uint8Array(e))})})}catch(e){return Promise.reject(e)}},verify:function(e,t){try{var i=crypto.subtle,o=i.verify;return Promise.resolve(n()).then(function(n){return o.call(i,{name:"HMAC",hash:{name:"SHA-384"}},n,new Uint8Array(r.base62.decodeRaw(t)),(new TextEncoder).encode(e))})}catch(e){return Promise.reject(e)}}}}var i=t.memo(function(){try{if("string"==typeof e.privateKey){var r=JSON.parse(e.privateKey);return Promise.resolve(crypto.subtle.importKey("jwk",r,{name:"EC"==r.kty?"ECDSA":r.kty,namedCurve:r.crv},!0,["sign"]))}return Promise.resolve(e.privateKey())}catch(e){return Promise.reject(e)}}),o=t.memo(function(){try{if("string"==typeof e.publicKey){var r=JSON.parse(e.publicKey);return Promise.resolve(crypto.subtle.importKey("jwk",JSON.parse(e.publicKey),{name:"EC"==r.kty?"ECDSA":r.kty,namedCurve:r.crv},!0,["verify"]))}return Promise.resolve(e.publicKey())}catch(e){return Promise.reject(e)}});return{sign:function(e){try{return Promise.resolve(i()).then(function(t){return Promise.resolve(crypto.subtle.sign({name:t.algorithm.name,hash:{name:"SHA-384"}},t,(new TextEncoder).encode(e))).then(function(e){return r.base62.encode(new Uint8Array(e))})})}catch(e){return Promise.reject(e)}},verify:function(e,t){try{var n=crypto.subtle,i=n.verify;return Promise.resolve(o()).then(function(o){return i.call(n,{name:"ECDSA",hash:{name:"SHA-384"}},o,new Uint8Array(r.base62.decodeRaw(t)),(new TextEncoder).encode(e))})}catch(e){return Promise.reject(e)}}}}(e)}var n=e.prototype;return n.sign=function(e){var t=e.type,n=e.data,i=e.expiresAt;try{var o=t+"_v1_"+r.base62.encode(JSON.stringify({d:n,e:null==i?void 0:i.getTime(),c:Date.now()}));return Promise.resolve(this.signer.sign(o)).then(function(e){return o+"_"+e})}catch(e){return Promise.reject(e)}},n.verify=function(e){var t=e.token,n=e.expectedType;try{var i=t.split("_");if(i.length<4)return Promise.resolve({verified:!1});var o=i.pop(),s=i.pop(),c=i.pop(),a=i.join("_");return a!=n||"v1"!=c?Promise.resolve({verified:!1}):Promise.resolve(this.signer.verify(a+"_"+c+"_"+s,o)).then(function(e){if(!e)return{verified:!1};var t=JSON.parse(r.base62.decode(s)),n=t.e?new Date(t.e):null;if(n&&n<new Date)return{verified:!1};var i=new Date(t.c);return{verified:!0,type:a,expiresAt:n,createdAt:i,data:t.d}})}catch(e){return Promise.reject(e)}},e.decode=function(e){var t=e.split("_");return t.length<4?null:JSON.parse(r.base62.decode(t[t.length-2]))},e}()});
2
+ //# sourceMappingURL=index.umd.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.umd.js","sources":["../src/tokens.ts"],"sourcesContent":["import { base62 } from '@lowerdeck/base62';\nimport { memo } from '@lowerdeck/memo';\n\nexport type TokenKeys =\n | {\n privateKey: string | (() => Promise<CryptoKey> | CryptoKey);\n publicKey: string | (() => Promise<CryptoKey> | CryptoKey);\n }\n | {\n secret: string;\n };\n\ntype Signer = {\n sign: (data: string) => Promise<string>;\n verify: (data: string, signature: string) => Promise<boolean>;\n};\n\nlet getSigner = (keys: TokenKeys): Signer => {\n if ('secret' in keys) {\n let keyMemo = memo(() =>\n crypto.subtle.importKey(\n 'raw',\n new TextEncoder().encode(keys.secret),\n { name: 'HMAC', hash: { name: 'SHA-384' } },\n false,\n ['sign', 'verify']\n )\n );\n\n return {\n async sign(data) {\n let signature = await crypto.subtle.sign(\n { name: 'HMAC', hash: { name: 'SHA-384' } },\n await keyMemo(),\n new TextEncoder().encode(data)\n );\n\n return base62.encode(new Uint8Array(signature));\n },\n\n async verify(data, signature) {\n return crypto.subtle.verify(\n { name: 'HMAC', hash: { name: 'SHA-384' } },\n await keyMemo(),\n new Uint8Array(base62.decodeRaw(signature)),\n new TextEncoder().encode(data)\n );\n }\n };\n } else {\n let privateKeyMemo = memo(async () => {\n if (typeof keys.privateKey == 'string') {\n let data = JSON.parse(keys.privateKey) as any;\n\n return await crypto.subtle.importKey(\n 'jwk',\n data,\n { name: data.kty == 'EC' ? 'ECDSA' : data.kty, namedCurve: data.crv },\n true,\n ['sign']\n );\n } else {\n return (await keys.privateKey()) as CryptoKey;\n }\n });\n\n let publicKeyMemo = memo(async () => {\n if (typeof keys.publicKey == 'string') {\n let data = JSON.parse(keys.publicKey) as any;\n\n return await crypto.subtle.importKey(\n 'jwk',\n JSON.parse(keys.publicKey) as any,\n { name: data.kty == 'EC' ? 'ECDSA' : data.kty, namedCurve: data.crv },\n true,\n ['verify']\n );\n } else {\n return (await keys.publicKey()) as CryptoKey;\n }\n });\n\n return {\n async sign(data) {\n let key = await privateKeyMemo();\n\n let signature = await crypto.subtle.sign(\n { name: key.algorithm.name, hash: { name: 'SHA-384' } },\n key,\n new TextEncoder().encode(data)\n );\n\n return base62.encode(new Uint8Array(signature));\n },\n\n async verify(data, signature) {\n return crypto.subtle.verify(\n { name: 'ECDSA', hash: { name: 'SHA-384' } },\n await publicKeyMemo(),\n new Uint8Array(base62.decodeRaw(signature)),\n new TextEncoder().encode(data)\n );\n }\n };\n }\n};\n\nexport class Tokens {\n private signer: Signer;\n constructor(readonly keys: TokenKeys) {\n this.signer = getSigner(keys);\n }\n\n async sign({ type, data, expiresAt }: { type: string; data: any; expiresAt?: Date }) {\n let token = `${type}_v1_${base62.encode(\n JSON.stringify({\n d: data,\n e: expiresAt?.getTime(),\n c: Date.now()\n })\n )}`;\n\n let signature = await this.signer.sign(token);\n return `${token}_${signature}`;\n }\n\n async verify({ token, expectedType }: { expectedType: string; token: string }) {\n let parts = token.split('_');\n if (parts.length < 4) return { verified: false as const };\n\n let signatureBase62 = parts.pop()!;\n let dataBase62 = parts.pop()!;\n let version = parts.pop()!;\n let type = parts.join('_');\n if (type != expectedType) return { verified: false as const };\n\n if (version != 'v1') return { verified: false as const };\n\n let main = `${type}_${version}_${dataBase62}`;\n\n let verified = await this.signer.verify(main, signatureBase62);\n if (!verified) return { verified: false as const };\n\n let data = JSON.parse(base62.decode(dataBase62));\n let expiresAt = data.e ? new Date(data.e) : null;\n if (expiresAt && expiresAt < new Date()) return { verified: false as const };\n\n let createdAt = new Date(data.c);\n\n return {\n verified: true as const,\n\n type,\n expiresAt,\n createdAt,\n data: data.d\n };\n }\n\n static decode(token: string) {\n let parts = token.split('_');\n if (parts.length < 4) return null;\n\n let dataBase62 = parts[parts.length - 2];\n return JSON.parse(base62.decode(dataBase62));\n }\n}\n"],"names":["Tokens","keys","signer","this","keyMemo","memo","crypto","subtle","importKey","TextEncoder","encode","secret","name","hash","sign","data","_crypto$subtle","_sign","Promise","resolve","then","_keyMemo","call","signature","base62","Uint8Array","e","reject","verify","_crypto$subtle2","_verify","_keyMemo2","decodeRaw","privateKeyMemo","privateKey","JSON","parse","kty","namedCurve","crv","publicKeyMemo","publicKey","key","algorithm","_crypto$subtle3","_verify2","_publicKeyMemo","getSigner","_proto","prototype","_ref","type","expiresAt","_this","token","stringify","d","getTime","c","Date","now","_ref2","expectedType","_this2","parts","split","length","verified","signatureBase62","pop","dataBase62","version","join","decode","createdAt"],"mappings":"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"}
@@ -0,0 +1,34 @@
1
+ export type TokenKeys = {
2
+ privateKey: string | (() => Promise<CryptoKey> | CryptoKey);
3
+ publicKey: string | (() => Promise<CryptoKey> | CryptoKey);
4
+ } | {
5
+ secret: string;
6
+ };
7
+ export declare class Tokens {
8
+ readonly keys: TokenKeys;
9
+ private signer;
10
+ constructor(keys: TokenKeys);
11
+ sign({ type, data, expiresAt }: {
12
+ type: string;
13
+ data: any;
14
+ expiresAt?: Date;
15
+ }): Promise<string>;
16
+ verify({ token, expectedType }: {
17
+ expectedType: string;
18
+ token: string;
19
+ }): Promise<{
20
+ verified: false;
21
+ type?: undefined;
22
+ expiresAt?: undefined;
23
+ createdAt?: undefined;
24
+ data?: undefined;
25
+ } | {
26
+ verified: true;
27
+ type: string;
28
+ expiresAt: Date | null;
29
+ createdAt: Date;
30
+ data: any;
31
+ }>;
32
+ static decode(token: string): any;
33
+ }
34
+ //# sourceMappingURL=tokens.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../src/tokens.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,SAAS,GACjB;IACE,UAAU,EAAE,MAAM,GAAG,CAAC,MAAM,OAAO,CAAC,SAAS,CAAC,GAAG,SAAS,CAAC,CAAC;IAC5D,SAAS,EAAE,MAAM,GAAG,CAAC,MAAM,OAAO,CAAC,SAAS,CAAC,GAAG,SAAS,CAAC,CAAC;CAC5D,GACD;IACE,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAiGN,qBAAa,MAAM;IAEL,QAAQ,CAAC,IAAI,EAAE,SAAS;IADpC,OAAO,CAAC,MAAM,CAAS;gBACF,IAAI,EAAE,SAAS;IAI9B,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,GAAG,CAAC;QAAC,SAAS,CAAC,EAAE,IAAI,CAAA;KAAE;IAa7E,MAAM,CAAC,EAAE,KAAK,EAAE,YAAY,EAAE,EAAE;QAAE,YAAY,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE;;;;;;;;;;;;;IAiC7E,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM;CAO5B"}
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@lowerdeck/tokens",
3
+ "version": "1.0.0",
4
+ "publishConfig": {
5
+ "access": "public"
6
+ },
7
+ "author": "Tobias Herber",
8
+ "license": "Apache 2",
9
+ "type": "module",
10
+ "source": "src/index.ts",
11
+ "exports": {
12
+ "require": "./dist/index.cjs",
13
+ "default": "./dist/index.modern.js"
14
+ },
15
+ "main": "./dist/index.cjs",
16
+ "module": "./dist/index.module.js",
17
+ "types": "dist/index.d.ts",
18
+ "unpkg": "./dist/index.umd.js",
19
+ "scripts": {
20
+ "test": "vitest run --passWithNoTests",
21
+ "lint": "prettier src/**/*.ts --check",
22
+ "build": "microbundle"
23
+ },
24
+ "dependencies": {
25
+ "@lowerdeck/base62": "^1.0.0",
26
+ "@lowerdeck/memo": "^1.0.0"
27
+ },
28
+ "devDependencies": {
29
+ "microbundle": "^0.15.1",
30
+ "@lowerdeck/tsconfig": "^1.0.0",
31
+ "typescript": "5.8.2",
32
+ "vitest": "^3.1.2"
33
+ }
34
+ }
package/src/index.ts ADDED
@@ -0,0 +1 @@
1
+ export * from './tokens';
@@ -0,0 +1,92 @@
1
+ import { beforeAll, describe, expect, test } from 'vitest';
2
+ import { Tokens } from './tokens';
3
+
4
+ describe('Tokens', () => {
5
+ let privateKey: CryptoKey;
6
+ let publicKey: CryptoKey;
7
+ let tokens: Tokens;
8
+
9
+ beforeAll(async () => {
10
+ let pair = await crypto.subtle.generateKey({ name: 'ECDSA', namedCurve: 'P-384' }, true, [
11
+ 'sign',
12
+ 'verify'
13
+ ]);
14
+
15
+ privateKey = pair.privateKey;
16
+ publicKey = pair.publicKey;
17
+
18
+ tokens = new Tokens({
19
+ privateKey: async () => privateKey,
20
+ publicKey: async () => publicKey
21
+ });
22
+ });
23
+
24
+ test('should sign and verify tokens', async () => {
25
+ let data = { foo: 'bar' };
26
+ let type = 'test_token';
27
+ let token = await tokens.sign({ type, data });
28
+ let result = await tokens.verify({
29
+ expectedType: type,
30
+ token
31
+ });
32
+ expect(result.verified).toBe(true);
33
+ expect(result.type).toBe(type);
34
+ expect(result.data).toEqual(data);
35
+ });
36
+
37
+ test('should not verify tokens with wrong type', async () => {
38
+ let data = { foo: 'bar' };
39
+ let type = 'test_token';
40
+ let token = await tokens.sign({ type, data });
41
+ let result = await tokens.verify({
42
+ expectedType: 'wrong',
43
+ token
44
+ });
45
+ expect(result.verified).toBe(false);
46
+ });
47
+
48
+ test('should not verify tokens with wrong version', async () => {
49
+ let data = { foo: 'bar' };
50
+ let type = 'test_token';
51
+ let token = await tokens.sign({ type, data });
52
+ let parts = token.split('_');
53
+ parts[parts.length - 2] = 'wrong';
54
+ let modifiedToken = parts.join('_');
55
+ let result = await tokens.verify({ expectedType: type, token: modifiedToken });
56
+ expect(result.verified).toBe(false);
57
+ });
58
+
59
+ test('should not verify tokens with wrong signature', async () => {
60
+ let data = { foo: 'bar' };
61
+ let type = 'test_token';
62
+ let token = await tokens.sign({ type, data });
63
+ let parts = token.split('_');
64
+ parts[parts.length - 1] = 'wrong';
65
+ let modifiedToken = parts.join('_');
66
+ let result = await tokens.verify({ expectedType: type, token: modifiedToken });
67
+ expect(result.verified).toBe(false);
68
+ });
69
+
70
+ test('should not verify tokens with too few parts', async () => {
71
+ let data = { foo: 'bar' };
72
+ let type = 'test_token';
73
+ let token = await tokens.sign({ type, data });
74
+ let parts = token.split('_');
75
+ parts.pop();
76
+ let modifiedToken = parts.join('_');
77
+ let result = await tokens.verify({ expectedType: type, token: modifiedToken });
78
+ expect(result.verified).toBe(false);
79
+ });
80
+
81
+ test('should not verify expired tokens', async () => {
82
+ let data = { foo: 'bar' };
83
+ let type = 'test_token';
84
+ let token = await tokens.sign({
85
+ type,
86
+ data,
87
+ expiresAt: new Date(Date.now() - 1000)
88
+ });
89
+ let result = await tokens.verify({ expectedType: type, token });
90
+ expect(result.verified).toBe(false);
91
+ });
92
+ });
package/src/tokens.ts ADDED
@@ -0,0 +1,167 @@
1
+ import { base62 } from '@lowerdeck/base62';
2
+ import { memo } from '@lowerdeck/memo';
3
+
4
+ export type TokenKeys =
5
+ | {
6
+ privateKey: string | (() => Promise<CryptoKey> | CryptoKey);
7
+ publicKey: string | (() => Promise<CryptoKey> | CryptoKey);
8
+ }
9
+ | {
10
+ secret: string;
11
+ };
12
+
13
+ type Signer = {
14
+ sign: (data: string) => Promise<string>;
15
+ verify: (data: string, signature: string) => Promise<boolean>;
16
+ };
17
+
18
+ let getSigner = (keys: TokenKeys): Signer => {
19
+ if ('secret' in keys) {
20
+ let keyMemo = memo(() =>
21
+ crypto.subtle.importKey(
22
+ 'raw',
23
+ new TextEncoder().encode(keys.secret),
24
+ { name: 'HMAC', hash: { name: 'SHA-384' } },
25
+ false,
26
+ ['sign', 'verify']
27
+ )
28
+ );
29
+
30
+ return {
31
+ async sign(data) {
32
+ let signature = await crypto.subtle.sign(
33
+ { name: 'HMAC', hash: { name: 'SHA-384' } },
34
+ await keyMemo(),
35
+ new TextEncoder().encode(data)
36
+ );
37
+
38
+ return base62.encode(new Uint8Array(signature));
39
+ },
40
+
41
+ async verify(data, signature) {
42
+ return crypto.subtle.verify(
43
+ { name: 'HMAC', hash: { name: 'SHA-384' } },
44
+ await keyMemo(),
45
+ new Uint8Array(base62.decodeRaw(signature)),
46
+ new TextEncoder().encode(data)
47
+ );
48
+ }
49
+ };
50
+ } else {
51
+ let privateKeyMemo = memo(async () => {
52
+ if (typeof keys.privateKey == 'string') {
53
+ let data = JSON.parse(keys.privateKey) as any;
54
+
55
+ return await crypto.subtle.importKey(
56
+ 'jwk',
57
+ data,
58
+ { name: data.kty == 'EC' ? 'ECDSA' : data.kty, namedCurve: data.crv },
59
+ true,
60
+ ['sign']
61
+ );
62
+ } else {
63
+ return (await keys.privateKey()) as CryptoKey;
64
+ }
65
+ });
66
+
67
+ let publicKeyMemo = memo(async () => {
68
+ if (typeof keys.publicKey == 'string') {
69
+ let data = JSON.parse(keys.publicKey) as any;
70
+
71
+ return await crypto.subtle.importKey(
72
+ 'jwk',
73
+ JSON.parse(keys.publicKey) as any,
74
+ { name: data.kty == 'EC' ? 'ECDSA' : data.kty, namedCurve: data.crv },
75
+ true,
76
+ ['verify']
77
+ );
78
+ } else {
79
+ return (await keys.publicKey()) as CryptoKey;
80
+ }
81
+ });
82
+
83
+ return {
84
+ async sign(data) {
85
+ let key = await privateKeyMemo();
86
+
87
+ let signature = await crypto.subtle.sign(
88
+ { name: key.algorithm.name, hash: { name: 'SHA-384' } },
89
+ key,
90
+ new TextEncoder().encode(data)
91
+ );
92
+
93
+ return base62.encode(new Uint8Array(signature));
94
+ },
95
+
96
+ async verify(data, signature) {
97
+ return crypto.subtle.verify(
98
+ { name: 'ECDSA', hash: { name: 'SHA-384' } },
99
+ await publicKeyMemo(),
100
+ new Uint8Array(base62.decodeRaw(signature)),
101
+ new TextEncoder().encode(data)
102
+ );
103
+ }
104
+ };
105
+ }
106
+ };
107
+
108
+ export class Tokens {
109
+ private signer: Signer;
110
+ constructor(readonly keys: TokenKeys) {
111
+ this.signer = getSigner(keys);
112
+ }
113
+
114
+ async sign({ type, data, expiresAt }: { type: string; data: any; expiresAt?: Date }) {
115
+ let token = `${type}_v1_${base62.encode(
116
+ JSON.stringify({
117
+ d: data,
118
+ e: expiresAt?.getTime(),
119
+ c: Date.now()
120
+ })
121
+ )}`;
122
+
123
+ let signature = await this.signer.sign(token);
124
+ return `${token}_${signature}`;
125
+ }
126
+
127
+ async verify({ token, expectedType }: { expectedType: string; token: string }) {
128
+ let parts = token.split('_');
129
+ if (parts.length < 4) return { verified: false as const };
130
+
131
+ let signatureBase62 = parts.pop()!;
132
+ let dataBase62 = parts.pop()!;
133
+ let version = parts.pop()!;
134
+ let type = parts.join('_');
135
+ if (type != expectedType) return { verified: false as const };
136
+
137
+ if (version != 'v1') return { verified: false as const };
138
+
139
+ let main = `${type}_${version}_${dataBase62}`;
140
+
141
+ let verified = await this.signer.verify(main, signatureBase62);
142
+ if (!verified) return { verified: false as const };
143
+
144
+ let data = JSON.parse(base62.decode(dataBase62));
145
+ let expiresAt = data.e ? new Date(data.e) : null;
146
+ if (expiresAt && expiresAt < new Date()) return { verified: false as const };
147
+
148
+ let createdAt = new Date(data.c);
149
+
150
+ return {
151
+ verified: true as const,
152
+
153
+ type,
154
+ expiresAt,
155
+ createdAt,
156
+ data: data.d
157
+ };
158
+ }
159
+
160
+ static decode(token: string) {
161
+ let parts = token.split('_');
162
+ if (parts.length < 4) return null;
163
+
164
+ let dataBase62 = parts[parts.length - 2];
165
+ return JSON.parse(base62.decode(dataBase62));
166
+ }
167
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/tsconfig",
3
+ "extends": "@lowerdeck/tsconfig/base.json",
4
+ "exclude": ["dist"],
5
+ "compilerOptions": {
6
+ "outDir": "dist"
7
+ }
8
+ }