@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.
- package/.turbo/turbo-build.log +13 -0
- package/.turbo/turbo-test.log +26 -0
- package/README.md +63 -0
- package/dist/index.cjs +2 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.modern.js +2 -0
- package/dist/index.modern.js.map +1 -0
- package/dist/index.module.js +2 -0
- package/dist/index.module.js.map +1 -0
- package/dist/index.umd.js +2 -0
- package/dist/index.umd.js.map +1 -0
- package/dist/tokens.d.ts +34 -0
- package/dist/tokens.d.ts.map +1 -0
- package/package.json +34 -0
- package/src/index.ts +1 -0
- package/src/tokens.test.ts +92 -0
- package/src/tokens.ts +167 -0
- package/tsconfig.json +8 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
|
|
2
|
+
[0m[2m[35m$[0m [2m[1mmicrobundle[0m
|
|
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
|
+
[34mBuild "@lowerdeck/tokens" to dist:[39m
|
|
6
|
+
[32m932 B[39m: [37mindex.cjs[39m.gz
|
|
7
|
+
[32m834 B[39m: [37mindex.cjs[39m.br
|
|
8
|
+
[32m800 B[39m: [37mindex.modern.js[39m.gz
|
|
9
|
+
[32m713 B[39m: [37mindex.modern.js[39m.br
|
|
10
|
+
[32m936 B[39m: [37mindex.module.js[39m.gz
|
|
11
|
+
[32m839 B[39m: [37mindex.module.js[39m.br
|
|
12
|
+
[32m1015 B[39m: [37mindex.umd.js[39m.gz
|
|
13
|
+
[32m903 B[39m: [37mindex.umd.js[39m.br
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
|
|
2
|
+
[0m[2m[35m$[0m [2m[1mvitest run --passWithNoTests[0m
|
|
3
|
+
[?25l
|
|
4
|
+
[1m[46m RUN [49m[22m [36mv3.2.4 [39m[90m/Users/tobias/code/metorial/metorial-enterprise/oss/src/packages/backend/tokens[39m
|
|
5
|
+
|
|
6
|
+
[?2026h
|
|
7
|
+
[1m[33m ❯ [39m[22msrc/tokens.test.ts[2m [queued][22m
|
|
8
|
+
|
|
9
|
+
[2m Test Files [22m[1m[32m0 passed[39m[22m[90m (1)[39m
|
|
10
|
+
[2m Tests [22m[1m[32m0 passed[39m[22m[90m (0)[39m
|
|
11
|
+
[2m Start at [22m10:23:44
|
|
12
|
+
[2m Duration [22m101ms
|
|
13
|
+
[?2026l[K[1A[K[1A[K[1A[K[1A[K[1A[K[1A[K[1A[K [32m✓[39m src/tokens.test.ts [2m([22m[2m6 tests[22m[2m)[22m[32m 14[2mms[22m[39m
|
|
14
|
+
[32m✓[39m Tokens[2m > [22mshould sign and verify tokens[32m 4[2mms[22m[39m
|
|
15
|
+
[32m✓[39m Tokens[2m > [22mshould not verify tokens with wrong type[32m 1[2mms[22m[39m
|
|
16
|
+
[32m✓[39m Tokens[2m > [22mshould not verify tokens with wrong version[32m 2[2mms[22m[39m
|
|
17
|
+
[32m✓[39m Tokens[2m > [22mshould not verify tokens with wrong signature[32m 1[2mms[22m[39m
|
|
18
|
+
[32m✓[39m Tokens[2m > [22mshould not verify tokens with too few parts[32m 1[2mms[22m[39m
|
|
19
|
+
[32m✓[39m Tokens[2m > [22mshould not verify expired tokens[32m 2[2mms[22m[39m
|
|
20
|
+
|
|
21
|
+
[2m Test Files [22m [1m[32m1 passed[39m[22m[90m (1)[39m
|
|
22
|
+
[2m Tests [22m [1m[32m6 passed[39m[22m[90m (6)[39m
|
|
23
|
+
[2m Start at [22m 10:23:44
|
|
24
|
+
[2m Duration [22m 247ms[2m (transform 47ms, setup 0ms, collect 52ms, tests 14ms, environment 0ms, prepare 45ms)[22m
|
|
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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"}
|
package/dist/index.d.ts
ADDED
|
@@ -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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"}
|
package/dist/tokens.d.ts
ADDED
|
@@ -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
|
+
}
|