ecwt 0.1.0-beta.2 → 0.1.1-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +241 -1
- package/dist/ecwt.cjs +74 -33
- package/package.json +3 -3
- package/src/factory.js +10 -10
- package/src/token.js +78 -34
package/README.md
CHANGED
|
@@ -1,3 +1,243 @@
|
|
|
1
1
|
|
|
2
|
-
#
|
|
2
|
+
# ECWT
|
|
3
3
|
Encrypted CBOR-encoded Web Token
|
|
4
|
+
|
|
5
|
+
## What is it?
|
|
6
|
+
|
|
7
|
+
ECWT is module for creating and verifying encrypted CBOR-encoded Web Tokens. It is designed to be used in situations where JWT is used, but there are major differences:
|
|
8
|
+
|
|
9
|
+
| | JWT | ECWT |
|
|
10
|
+
| --- | --- | --- |
|
|
11
|
+
| Encoding | 🧐 JSON with base64 | ✅ CBOR <br> 2x smaller output |
|
|
12
|
+
| Binary data | 🧐 Double base64 encoding | ✅ Supported out of the box |
|
|
13
|
+
| Security | 📝 Signed <br> Payload is readable by everyone | 🔒 Encrypted <br> Payload is readable only by the private key possessor |
|
|
14
|
+
| Metadata | ➕ Type and algorithm, increases size | ✅ No unnecessary metadata |
|
|
15
|
+
| Revocation | 🧑💻 Requires additional implementation | ✅ Included with Redis |
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
ECWT depends on other modules, so you need to install them too.
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
npm install ecwt @kirick/snowflake
|
|
23
|
+
pnpm install ecwt @kirick/snowflake
|
|
24
|
+
bun install ecwt @kirick/snowflake
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Some dependencies
|
|
28
|
+
|
|
29
|
+
`EcwtFactory` depends on other modules, so you might be need to install them too.
|
|
30
|
+
|
|
31
|
+
#### `@kirick/snowflake` to create unique IDs (required)
|
|
32
|
+
|
|
33
|
+
For documentation, see [snowflake-js repository](https://github.com/kirick13/snowflake-js).
|
|
34
|
+
|
|
35
|
+
```javascript
|
|
36
|
+
import { SnowflakeFactory } from '@kirick/snowflake';
|
|
37
|
+
|
|
38
|
+
const snowflakeFactory = new SnowflakeFactory({
|
|
39
|
+
server_id: 0,
|
|
40
|
+
worker_id: 0,
|
|
41
|
+
});
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
#### `redis` to store revoked tokens (optional)
|
|
45
|
+
|
|
46
|
+
```javascript
|
|
47
|
+
import { createClient } from 'redis';
|
|
48
|
+
|
|
49
|
+
const redisClient = createClient({
|
|
50
|
+
socket: {
|
|
51
|
+
host: 'localhost',
|
|
52
|
+
port: 6379,
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
await redisClient.connect();
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
#### `lru` to avoid decrypt the same token multiple times (optional)
|
|
60
|
+
|
|
61
|
+
```javascript
|
|
62
|
+
import { LRUCache } from 'lru-cache';
|
|
63
|
+
|
|
64
|
+
const lruCache = new LRUCache({
|
|
65
|
+
max: 1000, // maximum of 1000 items
|
|
66
|
+
ttl: 60 * 60 * 1000, // 1 hour
|
|
67
|
+
});
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
#### Validation library of your choice (optional)
|
|
71
|
+
|
|
72
|
+
To reduce the size of the token, ECWT does not include object keys in the payload. By specifying the schema, you also can validate the payloads.
|
|
73
|
+
|
|
74
|
+
In our example, we use [valibot](https://valibot.dev) library.
|
|
75
|
+
|
|
76
|
+
```javascript
|
|
77
|
+
import {
|
|
78
|
+
number,
|
|
79
|
+
string,
|
|
80
|
+
maxValue,
|
|
81
|
+
maxLength,
|
|
82
|
+
safeParse } from 'valibot';
|
|
83
|
+
|
|
84
|
+
const schema = {
|
|
85
|
+
user_id: (value) => safeParse(
|
|
86
|
+
number([
|
|
87
|
+
maxValue(10),
|
|
88
|
+
]),
|
|
89
|
+
value,
|
|
90
|
+
).success,
|
|
91
|
+
nick: (value) => safeParse(
|
|
92
|
+
string([
|
|
93
|
+
maxLength(10),
|
|
94
|
+
]),
|
|
95
|
+
value,
|
|
96
|
+
).success,
|
|
97
|
+
};
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
That schema will prevent creating tokens for users with ID greater than 10 and nicknames longer than 10 characters.
|
|
101
|
+
|
|
102
|
+
## API
|
|
103
|
+
|
|
104
|
+
### `EcwtFactory`
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
constructor({
|
|
108
|
+
redisClient: RedisClientType?,
|
|
109
|
+
lruCache: LRU?,
|
|
110
|
+
snowflakeFactory: SnowflakeFactory,
|
|
111
|
+
options: {
|
|
112
|
+
namespace: string?,
|
|
113
|
+
key: Buffer,
|
|
114
|
+
schema: {
|
|
115
|
+
[key: string]: (value: any) => boolean,
|
|
116
|
+
} = {},
|
|
117
|
+
},
|
|
118
|
+
})
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Create your `EcwtFactory` instance to create, and parse tokens.
|
|
122
|
+
|
|
123
|
+
```javascript
|
|
124
|
+
import { EcwtFactory } from 'ecwt';
|
|
125
|
+
|
|
126
|
+
const ecwtFactory = new EcwtFactory({
|
|
127
|
+
redisClient,
|
|
128
|
+
lruCache,
|
|
129
|
+
snowflakeFactory,
|
|
130
|
+
options: {
|
|
131
|
+
// "options.namespace" is required to identify the storage of revoked tokens in Redis
|
|
132
|
+
namespace: 'test',
|
|
133
|
+
key: Buffer.from(
|
|
134
|
+
'54RoavO+7orGGCKqLXcMwNGFGbcnSEq22f9bJX3lT9lgEPSaRAMBaEnHgMQPTPXcifFvGZmDGzOFqUMfqXsAhQ==',
|
|
135
|
+
'base64',
|
|
136
|
+
),
|
|
137
|
+
schema,
|
|
138
|
+
},
|
|
139
|
+
});
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
#### Class method `create`
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
create(
|
|
146
|
+
payload: {
|
|
147
|
+
[key: string]: any,
|
|
148
|
+
},
|
|
149
|
+
options: {
|
|
150
|
+
ttl: number | null,
|
|
151
|
+
}
|
|
152
|
+
): Promise<Ecwt>
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Creates a token.
|
|
156
|
+
|
|
157
|
+
`options.ttl` specifies the time to live of the token in seconds. If set to null, token will never expire.
|
|
158
|
+
|
|
159
|
+
> **Be careful with `ttl=null`!**
|
|
160
|
+
>
|
|
161
|
+
> Revoked tokens are stored in Redis until they expire. But such tokens will be stored in Redis forever, which will lead to uncontrolled Redis database growth.
|
|
162
|
+
|
|
163
|
+
Returns `Ecwt` instance.
|
|
164
|
+
|
|
165
|
+
```javascript
|
|
166
|
+
// Example
|
|
167
|
+
const ecwt_token = await ecwtFactory.create(
|
|
168
|
+
{
|
|
169
|
+
user_id: 1,
|
|
170
|
+
nick: 'kirick',
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
ttl: 30 * 60,
|
|
174
|
+
}
|
|
175
|
+
);
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
#### Class method `verify`
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
verify(
|
|
182
|
+
token: string,
|
|
183
|
+
): Promise<Ecwt>
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Parses string representation of the token and verifies it:
|
|
187
|
+
|
|
188
|
+
- to be decrypted properly,
|
|
189
|
+
- for expiration,
|
|
190
|
+
- for revocation (if Redis client is provided),
|
|
191
|
+
- for schema.
|
|
192
|
+
|
|
193
|
+
Returns `Ecwt` instance.
|
|
194
|
+
|
|
195
|
+
If the token is invalid, throws `EcwtInvalidError` which contains `Ecwt` instance in the `ecwt` property.
|
|
196
|
+
|
|
197
|
+
```javascript
|
|
198
|
+
const ecwt_token = await ecwtFactory.verify(token);
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### `Ecwt`
|
|
202
|
+
|
|
203
|
+
Represents the token. It cannot be created by the user.
|
|
204
|
+
|
|
205
|
+
```javascript
|
|
206
|
+
import { Ecwt } from 'ecwt';
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
#### Class property `readonly token: string`
|
|
210
|
+
|
|
211
|
+
The string representation of the token.
|
|
212
|
+
|
|
213
|
+
#### Class property `readonly id: string`
|
|
214
|
+
|
|
215
|
+
The unique ID of the token.
|
|
216
|
+
|
|
217
|
+
#### Class property `readonly snowflake: Snowflake`
|
|
218
|
+
|
|
219
|
+
The `Snowflake` instance of the token. For documentation, see [snowflake-js repository](https://github.com/kirick13/snowflake-js).
|
|
220
|
+
|
|
221
|
+
#### Class property `readonly ts_expired: number | null`
|
|
222
|
+
|
|
223
|
+
The timestamp of the token expiration in seconds. Equals to `null` if the token does not expire.
|
|
224
|
+
|
|
225
|
+
#### Class property `readonly data: { [key: string]: any }`
|
|
226
|
+
|
|
227
|
+
The payload of the token.
|
|
228
|
+
|
|
229
|
+
#### Class method `getTTL`
|
|
230
|
+
|
|
231
|
+
```typescript
|
|
232
|
+
getTTL(): number | null
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
Returns current the time to live of the token in seconds. If the token does not expire, returns `null`.
|
|
236
|
+
|
|
237
|
+
#### Class method `revoke`
|
|
238
|
+
|
|
239
|
+
```typescript
|
|
240
|
+
revoke(): Promise<void>
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
Revokes the token. Attempts to verify the revoked token will throw `EcwtRevokedError`.
|
package/dist/ecwt.cjs
CHANGED
|
@@ -47,18 +47,57 @@ function toSeconds(value) {
|
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
// src/token.js
|
|
50
|
+
function assign(target, key, value) {
|
|
51
|
+
Object.defineProperty(
|
|
52
|
+
target,
|
|
53
|
+
key,
|
|
54
|
+
{
|
|
55
|
+
value,
|
|
56
|
+
enumerable: true,
|
|
57
|
+
writable: false,
|
|
58
|
+
configurable: false
|
|
59
|
+
}
|
|
60
|
+
);
|
|
61
|
+
}
|
|
50
62
|
var Ecwt = class {
|
|
51
63
|
#ecwtFactory;
|
|
52
|
-
#token;
|
|
53
|
-
#snowflake;
|
|
54
64
|
#ttl_initial;
|
|
55
|
-
|
|
65
|
+
/**
|
|
66
|
+
* Token string representation.
|
|
67
|
+
* @type {string}
|
|
68
|
+
* @readonly
|
|
69
|
+
*/
|
|
70
|
+
token;
|
|
71
|
+
/**
|
|
72
|
+
* Token ID.
|
|
73
|
+
* @type {string}
|
|
74
|
+
* @readonly
|
|
75
|
+
*/
|
|
76
|
+
id;
|
|
77
|
+
/**
|
|
78
|
+
* Snowflake associated with token.
|
|
79
|
+
* @type {Snowflake}
|
|
80
|
+
* @readonly
|
|
81
|
+
*/
|
|
82
|
+
snowflake;
|
|
83
|
+
/**
|
|
84
|
+
* Timestamp when token expires in seconds.
|
|
85
|
+
* @type {number?}
|
|
86
|
+
* @readonly
|
|
87
|
+
*/
|
|
88
|
+
ts_expired;
|
|
89
|
+
/**
|
|
90
|
+
* Data stored in token.
|
|
91
|
+
* @type {{ [key: string]: any }}
|
|
92
|
+
* @readonly
|
|
93
|
+
*/
|
|
94
|
+
data;
|
|
56
95
|
/**
|
|
57
96
|
* @param {EcwtFactory} ecwtFactory -
|
|
58
97
|
* @param {object} options -
|
|
59
98
|
* @param {string} options.token String representation of token.
|
|
60
99
|
* @param {Snowflake} options.snowflake -
|
|
61
|
-
* @param {number
|
|
100
|
+
* @param {number?} options.ttl_initial Time to live in seconds at the moment of token creation.
|
|
62
101
|
* @param {object} options.data Data stored in token.
|
|
63
102
|
*/
|
|
64
103
|
constructor(ecwtFactory, {
|
|
@@ -68,45 +107,46 @@ var Ecwt = class {
|
|
|
68
107
|
data
|
|
69
108
|
}) {
|
|
70
109
|
this.#ecwtFactory = ecwtFactory;
|
|
71
|
-
this.#token = token;
|
|
72
|
-
this.#snowflake = snowflake;
|
|
73
110
|
this.#ttl_initial = ttl_initial;
|
|
74
|
-
this
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
111
|
+
assign(this, "token", token);
|
|
112
|
+
assign(
|
|
113
|
+
this,
|
|
114
|
+
"id",
|
|
115
|
+
snowflake.toBase62()
|
|
116
|
+
);
|
|
117
|
+
assign(this, "snowflake", snowflake);
|
|
118
|
+
assign(
|
|
119
|
+
this,
|
|
120
|
+
"ts_expired",
|
|
121
|
+
this.#getTimestampExpired()
|
|
122
|
+
);
|
|
123
|
+
assign(
|
|
124
|
+
this,
|
|
125
|
+
"data",
|
|
126
|
+
Object.freeze(data)
|
|
127
|
+
);
|
|
84
128
|
}
|
|
85
|
-
|
|
129
|
+
#getTimestampExpired() {
|
|
86
130
|
if (this.#ttl_initial === null) {
|
|
87
|
-
return
|
|
131
|
+
return null;
|
|
88
132
|
}
|
|
89
|
-
return toSeconds(this
|
|
133
|
+
return toSeconds(this.snowflake.timestamp) + this.#ttl_initial;
|
|
90
134
|
}
|
|
91
135
|
/**
|
|
92
|
-
*
|
|
93
|
-
* @
|
|
94
|
-
* @readonly
|
|
136
|
+
* Actual time to live in seconds.
|
|
137
|
+
* @returns {number | null} -
|
|
95
138
|
*/
|
|
96
|
-
|
|
139
|
+
getTTL() {
|
|
97
140
|
if (this.#ttl_initial === null) {
|
|
98
|
-
return
|
|
141
|
+
return null;
|
|
99
142
|
}
|
|
100
|
-
return this.#ttl_initial - toSeconds(Date.now() - this
|
|
101
|
-
}
|
|
102
|
-
get data() {
|
|
103
|
-
return this.#data;
|
|
143
|
+
return this.#ttl_initial - toSeconds(Date.now() - this.snowflake.timestamp);
|
|
104
144
|
}
|
|
105
145
|
/* async */
|
|
106
146
|
revoke() {
|
|
107
147
|
return this.#ecwtFactory._revoke({
|
|
108
148
|
token_id: this.id,
|
|
109
|
-
ts_ms_created: this
|
|
149
|
+
ts_ms_created: this.snowflake.timestamp,
|
|
110
150
|
ttl_initial: this.#ttl_initial
|
|
111
151
|
});
|
|
112
152
|
}
|
|
@@ -233,16 +273,16 @@ var EcwtFactory = class {
|
|
|
233
273
|
}
|
|
234
274
|
payload.push(value);
|
|
235
275
|
}
|
|
236
|
-
if (typeof ttl !== "number" && Number.isNaN(ttl) !== true
|
|
237
|
-
|
|
276
|
+
if (typeof ttl !== "number" && Number.isNaN(ttl) !== true && ttl !== null) {
|
|
277
|
+
throw new TypeError("TTL must be a number or null.");
|
|
238
278
|
}
|
|
239
279
|
const snowflake = await this.#snowflakeFactory.createSafe();
|
|
240
280
|
const token_raw = (0, import_cbor_x.encode)([
|
|
241
|
-
snowflake.
|
|
281
|
+
snowflake.toBuffer(),
|
|
242
282
|
ttl,
|
|
243
283
|
payload
|
|
244
284
|
]);
|
|
245
|
-
const token_encrypted = await
|
|
285
|
+
const token_encrypted = await import_evilcrypt.v2.encrypt(
|
|
246
286
|
token_raw,
|
|
247
287
|
this.#encryption_key
|
|
248
288
|
);
|
|
@@ -360,6 +400,7 @@ var EcwtFactory = class {
|
|
|
360
400
|
ttl_initial
|
|
361
401
|
}) {
|
|
362
402
|
if (this.#redisClient) {
|
|
403
|
+
ttl_initial = ttl_initial ?? Number.MAX_SAFE_INTEGER;
|
|
363
404
|
const ts_ms_expired = ts_ms_created + ttl_initial * 1e3;
|
|
364
405
|
if (ts_ms_expired > Date.now()) {
|
|
365
406
|
await this.#redisClient.sendCommand([
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ecwt",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1-beta.1",
|
|
4
4
|
"description": "Encrypted CBOR-encoded Web Token",
|
|
5
5
|
"main": "src/main.js",
|
|
6
6
|
"type": "module",
|
|
@@ -16,10 +16,10 @@
|
|
|
16
16
|
"dependencies": {
|
|
17
17
|
"base-x": "4.0.0",
|
|
18
18
|
"cbor-x": "1.5.6",
|
|
19
|
-
"evilcrypt": "0.2.0-beta.
|
|
19
|
+
"evilcrypt": "0.2.0-beta.4"
|
|
20
20
|
},
|
|
21
21
|
"peerDependencies": {
|
|
22
|
-
"@kirick/snowflake": "^0.2.
|
|
22
|
+
"@kirick/snowflake": "^0.2.1-beta.1",
|
|
23
23
|
"lru-cache": "^7 || ^8 || ^9 || ^10",
|
|
24
24
|
"redis": "^4"
|
|
25
25
|
},
|
package/src/factory.js
CHANGED
|
@@ -4,8 +4,8 @@ import {
|
|
|
4
4
|
encode as cborEncode,
|
|
5
5
|
decode as cborDecode } from 'cbor-x';
|
|
6
6
|
import {
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
decrypt as evilcryptDecrypt,
|
|
8
|
+
v2 as evilcryptV2 } from 'evilcrypt';
|
|
9
9
|
import { LRUCache } from 'lru-cache';
|
|
10
10
|
import { createClient } from 'redis';
|
|
11
11
|
import { Ecwt } from './token.js';
|
|
@@ -137,24 +137,22 @@ export class EcwtFactory {
|
|
|
137
137
|
}
|
|
138
138
|
|
|
139
139
|
if (
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
)
|
|
144
|
-
|| ttl === Number.POSITIVE_INFINITY
|
|
140
|
+
typeof ttl !== 'number'
|
|
141
|
+
&& Number.isNaN(ttl) !== true
|
|
142
|
+
&& ttl !== null
|
|
145
143
|
) {
|
|
146
|
-
|
|
144
|
+
throw new TypeError('TTL must be a number or null.');
|
|
147
145
|
}
|
|
148
146
|
|
|
149
147
|
const snowflake = await this.#snowflakeFactory.createSafe();
|
|
150
148
|
|
|
151
149
|
const token_raw = cborEncode([
|
|
152
|
-
snowflake.
|
|
150
|
+
snowflake.toBuffer(),
|
|
153
151
|
ttl,
|
|
154
152
|
payload,
|
|
155
153
|
]);
|
|
156
154
|
|
|
157
|
-
const token_encrypted = await
|
|
155
|
+
const token_encrypted = await evilcryptV2.encrypt(
|
|
158
156
|
token_raw,
|
|
159
157
|
this.#encryption_key,
|
|
160
158
|
);
|
|
@@ -299,6 +297,8 @@ export class EcwtFactory {
|
|
|
299
297
|
ttl_initial,
|
|
300
298
|
}) {
|
|
301
299
|
if (this.#redisClient) {
|
|
300
|
+
ttl_initial = ttl_initial ?? Number.MAX_SAFE_INTEGER;
|
|
301
|
+
|
|
302
302
|
const ts_ms_expired = ts_ms_created + (ttl_initial * 1000);
|
|
303
303
|
if (ts_ms_expired > Date.now()) {
|
|
304
304
|
await this.#redisClient.sendCommand([
|
package/src/token.js
CHANGED
|
@@ -2,24 +2,70 @@
|
|
|
2
2
|
import { toSeconds } from './utils/time.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* @typedef {import('@kirick/snowflake
|
|
5
|
+
* @typedef {import('@kirick/snowflake').Snowflake} Snowflake
|
|
6
6
|
* @typedef {import('./factory.js').EcwtFactory} EcwtFactory
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Assigns property to object.
|
|
11
|
+
* @param {object} target -
|
|
12
|
+
* @param {string} key -
|
|
13
|
+
* @param {any} value -
|
|
14
|
+
*/
|
|
15
|
+
function assign(target, key, value) {
|
|
16
|
+
Object.defineProperty(
|
|
17
|
+
target,
|
|
18
|
+
key,
|
|
19
|
+
{
|
|
20
|
+
value,
|
|
21
|
+
enumerable: true,
|
|
22
|
+
writable: false,
|
|
23
|
+
configurable: false,
|
|
24
|
+
},
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
9
28
|
export class Ecwt {
|
|
10
29
|
#ecwtFactory;
|
|
11
|
-
|
|
12
|
-
#token;
|
|
13
|
-
#snowflake;
|
|
14
30
|
#ttl_initial;
|
|
15
|
-
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Token string representation.
|
|
34
|
+
* @type {string}
|
|
35
|
+
* @readonly
|
|
36
|
+
*/
|
|
37
|
+
token;
|
|
38
|
+
/**
|
|
39
|
+
* Token ID.
|
|
40
|
+
* @type {string}
|
|
41
|
+
* @readonly
|
|
42
|
+
*/
|
|
43
|
+
id;
|
|
44
|
+
/**
|
|
45
|
+
* Snowflake associated with token.
|
|
46
|
+
* @type {Snowflake}
|
|
47
|
+
* @readonly
|
|
48
|
+
*/
|
|
49
|
+
snowflake;
|
|
50
|
+
/**
|
|
51
|
+
* Timestamp when token expires in seconds.
|
|
52
|
+
* @type {number?}
|
|
53
|
+
* @readonly
|
|
54
|
+
*/
|
|
55
|
+
ts_expired;
|
|
56
|
+
/**
|
|
57
|
+
* Data stored in token.
|
|
58
|
+
* @type {{ [key: string]: any }}
|
|
59
|
+
* @readonly
|
|
60
|
+
*/
|
|
61
|
+
data;
|
|
16
62
|
|
|
17
63
|
/**
|
|
18
64
|
* @param {EcwtFactory} ecwtFactory -
|
|
19
65
|
* @param {object} options -
|
|
20
66
|
* @param {string} options.token String representation of token.
|
|
21
67
|
* @param {Snowflake} options.snowflake -
|
|
22
|
-
* @param {number
|
|
68
|
+
* @param {number?} options.ttl_initial Time to live in seconds at the moment of token creation.
|
|
23
69
|
* @param {object} options.data Data stored in token.
|
|
24
70
|
*/
|
|
25
71
|
constructor(
|
|
@@ -33,53 +79,51 @@ export class Ecwt {
|
|
|
33
79
|
) {
|
|
34
80
|
this.#ecwtFactory = ecwtFactory;
|
|
35
81
|
|
|
36
|
-
this.#token = token;
|
|
37
|
-
this.#snowflake = snowflake;
|
|
38
82
|
this.#ttl_initial = ttl_initial;
|
|
39
|
-
this.#data = Object.freeze(data);
|
|
40
|
-
}
|
|
41
83
|
|
|
42
|
-
|
|
43
|
-
|
|
84
|
+
assign(this, 'token', token);
|
|
85
|
+
assign(
|
|
86
|
+
this,
|
|
87
|
+
'id',
|
|
88
|
+
snowflake.toBase62(),
|
|
89
|
+
);
|
|
90
|
+
assign(this, 'snowflake', snowflake);
|
|
91
|
+
assign(
|
|
92
|
+
this,
|
|
93
|
+
'ts_expired',
|
|
94
|
+
this.#getTimestampExpired(),
|
|
95
|
+
);
|
|
96
|
+
assign(
|
|
97
|
+
this,
|
|
98
|
+
'data',
|
|
99
|
+
Object.freeze(data),
|
|
100
|
+
);
|
|
44
101
|
}
|
|
45
102
|
|
|
46
|
-
|
|
47
|
-
return this.#snowflake.base62;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
get snowflake() {
|
|
51
|
-
return this.#snowflake;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
get ts_expired() {
|
|
103
|
+
#getTimestampExpired() {
|
|
55
104
|
if (this.#ttl_initial === null) {
|
|
56
|
-
return
|
|
105
|
+
return null;
|
|
57
106
|
}
|
|
58
107
|
|
|
59
|
-
return toSeconds(this
|
|
108
|
+
return toSeconds(this.snowflake.timestamp) + this.#ttl_initial;
|
|
60
109
|
}
|
|
61
110
|
|
|
62
111
|
/**
|
|
63
|
-
*
|
|
64
|
-
* @
|
|
65
|
-
* @readonly
|
|
112
|
+
* Actual time to live in seconds.
|
|
113
|
+
* @returns {number | null} -
|
|
66
114
|
*/
|
|
67
|
-
|
|
115
|
+
getTTL() {
|
|
68
116
|
if (this.#ttl_initial === null) {
|
|
69
|
-
return
|
|
117
|
+
return null;
|
|
70
118
|
}
|
|
71
119
|
|
|
72
|
-
return this.#ttl_initial - toSeconds(Date.now() - this
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
get data() {
|
|
76
|
-
return this.#data;
|
|
120
|
+
return this.#ttl_initial - toSeconds(Date.now() - this.snowflake.timestamp);
|
|
77
121
|
}
|
|
78
122
|
|
|
79
123
|
/* async */ revoke() {
|
|
80
124
|
return this.#ecwtFactory._revoke({
|
|
81
125
|
token_id: this.id,
|
|
82
|
-
ts_ms_created: this
|
|
126
|
+
ts_ms_created: this.snowflake.timestamp,
|
|
83
127
|
ttl_initial: this.#ttl_initial,
|
|
84
128
|
});
|
|
85
129
|
}
|