ecwt 0.2.1-beta.6 → 0.2.5
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 +197 -157
- package/bun.lock +948 -0
- package/dist/main.cjs +249 -435
- package/dist/main.d.ts +160 -0
- package/dist/main.js +246 -0
- package/package.json +28 -23
- package/bun.lockb +0 -0
- package/dist/types/factory.d.ts +0 -97
- package/dist/types/main.d.ts +0 -4
- package/dist/types/token.d.ts +0 -66
- package/dist/types/utils/base62.d.ts +0 -2
- package/dist/types/utils/errors.d.ts +0 -38
- package/dist/types/utils/time.d.ts +0 -6
- package/src/factory.js +0 -385
- package/src/main.js +0 -13
- package/src/token.js +0 -106
- package/src/utils/base62.js +0 -4
- package/src/utils/errors.js +0 -58
- package/src/utils/time.js +0 -17
package/dist/main.d.ts
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { Snowflake, SnowflakeFactory } from "@kirick/snowflake";
|
|
2
|
+
import { LRUCache } from "lru-cache";
|
|
3
|
+
import { RedisClientType, RedisFunctions, RedisModules, RedisScripts } from "redis";
|
|
4
|
+
|
|
5
|
+
//#region src/token.d.ts
|
|
6
|
+
declare class Ecwt<D extends Record<string, unknown> = Record<string, unknown>> {
|
|
7
|
+
/** Token string representation. */
|
|
8
|
+
readonly token: string;
|
|
9
|
+
/** Token ID. */
|
|
10
|
+
readonly id: string;
|
|
11
|
+
/** Snowflake associated with token. */
|
|
12
|
+
readonly snowflake: Snowflake;
|
|
13
|
+
/** Data stored in token. */
|
|
14
|
+
readonly data: Readonly<D>;
|
|
15
|
+
private ecwtFactory;
|
|
16
|
+
private ttl_initial;
|
|
17
|
+
/**
|
|
18
|
+
* @param {EcwtFactory} ecwtFactory -
|
|
19
|
+
* @param {object} options -
|
|
20
|
+
* @param {string} options.token String representation of token.
|
|
21
|
+
* @param {Snowflake} options.snowflake -
|
|
22
|
+
* @param {number | null} options.ttl_initial Time to live in seconds at the moment of token creation.
|
|
23
|
+
* @param {D} options.data Data stored in token.
|
|
24
|
+
*/
|
|
25
|
+
constructor(ecwtFactory: EcwtFactory, options: {
|
|
26
|
+
token: string;
|
|
27
|
+
snowflake: Snowflake;
|
|
28
|
+
ttl_initial: number | null;
|
|
29
|
+
data: D;
|
|
30
|
+
});
|
|
31
|
+
/**
|
|
32
|
+
* Unix timestamp of token expiration in seconds.
|
|
33
|
+
* @returns -
|
|
34
|
+
*/
|
|
35
|
+
get ts_expired(): number | null;
|
|
36
|
+
/**
|
|
37
|
+
* Actual time to live in seconds.
|
|
38
|
+
* @returns -
|
|
39
|
+
*/
|
|
40
|
+
getTTL(): number | null;
|
|
41
|
+
/**
|
|
42
|
+
* Revokes token.
|
|
43
|
+
* @returns {} -
|
|
44
|
+
*/
|
|
45
|
+
revoke(): Promise<void>;
|
|
46
|
+
} //#endregion
|
|
47
|
+
//#region src/factory.d.ts
|
|
48
|
+
type LRUCacheValue = {
|
|
49
|
+
snowflake: Snowflake;
|
|
50
|
+
ttl_initial: number | null;
|
|
51
|
+
data: Record<string, unknown>;
|
|
52
|
+
};
|
|
53
|
+
type RedisClient = RedisClientType<RedisModules, RedisFunctions, RedisScripts>;
|
|
54
|
+
type EcwtFactoryArguments<D extends Record<string, unknown>> = {
|
|
55
|
+
/** RedisClient instance. If not provided, tokens can not be revoked and can not be checked for revocation. */
|
|
56
|
+
redisClient?: RedisClient;
|
|
57
|
+
/** LRUCache instance. If not provided, tokens will be decrypted every time they are verified. */
|
|
58
|
+
lruCache?: LRUCache<string, LRUCacheValue>;
|
|
59
|
+
/** SnowflakeFactory instance. Generates unique IDs for tokens. */
|
|
60
|
+
snowflakeFactory: SnowflakeFactory;
|
|
61
|
+
options: {
|
|
62
|
+
/** Namespace for Redis keys. */
|
|
63
|
+
namespace?: string;
|
|
64
|
+
/** Encryption key, 64 bytes. */
|
|
65
|
+
key: Buffer;
|
|
66
|
+
/** Validator for token data. Should return validated value or throw an error. */
|
|
67
|
+
validator?: (value: unknown) => D;
|
|
68
|
+
/** Payload object keys mapped for their SenML keys. */
|
|
69
|
+
senml_key_map?: Record<string, number>;
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
declare class EcwtFactory<D extends Record<string, unknown> = Record<string, unknown>> {
|
|
73
|
+
private redisClient;
|
|
74
|
+
private lruCache;
|
|
75
|
+
private snowflakeFactory;
|
|
76
|
+
private redis_key_revoked;
|
|
77
|
+
private encryption_key;
|
|
78
|
+
private validator;
|
|
79
|
+
private cborEncoder;
|
|
80
|
+
constructor({
|
|
81
|
+
redisClient,
|
|
82
|
+
lruCache,
|
|
83
|
+
snowflakeFactory,
|
|
84
|
+
options
|
|
85
|
+
}: EcwtFactoryArguments<D>);
|
|
86
|
+
/**
|
|
87
|
+
* Creates new token.
|
|
88
|
+
* @async
|
|
89
|
+
* @param data - Data to be stored in token.
|
|
90
|
+
* @param options -
|
|
91
|
+
* @param options.ttl - Time to live in seconds. If not defined, token will never expire.
|
|
92
|
+
* @returns -
|
|
93
|
+
*/
|
|
94
|
+
create(data: D, options?: {
|
|
95
|
+
/** Time to live in seconds. If not defined, token will never expire. */
|
|
96
|
+
ttl?: number;
|
|
97
|
+
}): Promise<Ecwt<D>>;
|
|
98
|
+
/**
|
|
99
|
+
* Sets data to cache.
|
|
100
|
+
* @param token - String representation of token.
|
|
101
|
+
* @param cache_value - Data to be stored in cache.
|
|
102
|
+
*/
|
|
103
|
+
private setCache;
|
|
104
|
+
/**
|
|
105
|
+
* Parses token.
|
|
106
|
+
* @param token String representation of token.
|
|
107
|
+
* @returns -
|
|
108
|
+
*/
|
|
109
|
+
verify(token: string): Promise<Ecwt<D>>;
|
|
110
|
+
/**
|
|
111
|
+
* Parses token without throwing errors.
|
|
112
|
+
* @param token - String representation of token.
|
|
113
|
+
* @returns Returns whether token was parsed and verified successfully and Ecwt if parsed.
|
|
114
|
+
*/
|
|
115
|
+
safeVerify(token: string): Promise<{
|
|
116
|
+
success: true;
|
|
117
|
+
ecwt: Ecwt<D>;
|
|
118
|
+
} | {
|
|
119
|
+
success: false;
|
|
120
|
+
ecwt: Ecwt<D> | null;
|
|
121
|
+
}>;
|
|
122
|
+
/**
|
|
123
|
+
* Revokes token.
|
|
124
|
+
* @param token_id -
|
|
125
|
+
* @param ts_ms_created -
|
|
126
|
+
* @param ttl_initial -
|
|
127
|
+
* @returns -
|
|
128
|
+
*/
|
|
129
|
+
private _revoke;
|
|
130
|
+
/**
|
|
131
|
+
* Purges LRU cache.
|
|
132
|
+
* @returns {void} -
|
|
133
|
+
*/
|
|
134
|
+
private _purgeCache;
|
|
135
|
+
} //#endregion
|
|
136
|
+
//#region src/errors.d.ts
|
|
137
|
+
/** Error thrown when string token cannot be parsed to Ecwt. */
|
|
138
|
+
declare class EcwtParseError extends Error {
|
|
139
|
+
constructor();
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** Error thrown when parsed Ecwt is invalid. */
|
|
143
|
+
declare class EcwtInvalidError extends Error {
|
|
144
|
+
readonly ecwt: Ecwt;
|
|
145
|
+
message: string;
|
|
146
|
+
constructor(ecwt: Ecwt);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** Error thrown when parsed Ecwt is expired. */
|
|
150
|
+
declare class EcwtExpiredError extends EcwtInvalidError {
|
|
151
|
+
message: string;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** Error thrown when parsed Ecwt is revoked. */
|
|
155
|
+
declare class EcwtRevokedError extends EcwtInvalidError {
|
|
156
|
+
message: string;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
//#endregion
|
|
160
|
+
export { Ecwt, EcwtExpiredError, EcwtFactory, EcwtInvalidError, EcwtParseError, EcwtRevokedError };
|
package/dist/main.js
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import { Encoder, decode, encode } from "cbor-x";
|
|
2
|
+
import { decrypt, v2 } from "evilcrypt";
|
|
3
|
+
import basex from "base-x";
|
|
4
|
+
|
|
5
|
+
//#region src/token.ts
|
|
6
|
+
var Ecwt = class {
|
|
7
|
+
/** Token string representation. */
|
|
8
|
+
token;
|
|
9
|
+
/** Token ID. */
|
|
10
|
+
id;
|
|
11
|
+
/** Snowflake associated with token. */
|
|
12
|
+
snowflake;
|
|
13
|
+
/** Data stored in token. */
|
|
14
|
+
data;
|
|
15
|
+
ecwtFactory;
|
|
16
|
+
ttl_initial;
|
|
17
|
+
/**
|
|
18
|
+
* @param {EcwtFactory} ecwtFactory -
|
|
19
|
+
* @param {object} options -
|
|
20
|
+
* @param {string} options.token String representation of token.
|
|
21
|
+
* @param {Snowflake} options.snowflake -
|
|
22
|
+
* @param {number | null} options.ttl_initial Time to live in seconds at the moment of token creation.
|
|
23
|
+
* @param {D} options.data Data stored in token.
|
|
24
|
+
*/
|
|
25
|
+
constructor(ecwtFactory, options) {
|
|
26
|
+
this.token = options.token;
|
|
27
|
+
this.id = options.snowflake.toBase62();
|
|
28
|
+
this.snowflake = options.snowflake;
|
|
29
|
+
this.data = Object.freeze(options.data);
|
|
30
|
+
this.ecwtFactory = ecwtFactory;
|
|
31
|
+
this.ttl_initial = options.ttl_initial;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Unix timestamp of token expiration in seconds.
|
|
35
|
+
* @returns -
|
|
36
|
+
*/
|
|
37
|
+
get ts_expired() {
|
|
38
|
+
if (this.ttl_initial === null) return null;
|
|
39
|
+
return Math.floor(this.snowflake.timestamp / 1e3) + this.ttl_initial;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Actual time to live in seconds.
|
|
43
|
+
* @returns -
|
|
44
|
+
*/
|
|
45
|
+
getTTL() {
|
|
46
|
+
if (this.ttl_initial === null) return null;
|
|
47
|
+
return this.ttl_initial - Math.floor((Date.now() - this.snowflake.timestamp) / 1e3);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Revokes token.
|
|
51
|
+
* @returns {} -
|
|
52
|
+
*/
|
|
53
|
+
revoke() {
|
|
54
|
+
return this.ecwtFactory._revoke(this.id, this.snowflake.timestamp, this.ttl_initial);
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
//#endregion
|
|
59
|
+
//#region src/utils.ts
|
|
60
|
+
const base62 = basex("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz");
|
|
61
|
+
|
|
62
|
+
//#endregion
|
|
63
|
+
//#region src/errors.ts
|
|
64
|
+
/** Error thrown when string token cannot be parsed to Ecwt. */
|
|
65
|
+
var EcwtParseError = class extends Error {
|
|
66
|
+
constructor() {
|
|
67
|
+
super("Cannot parse data to Ecwt token.");
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
/** Error thrown when parsed Ecwt is invalid. */
|
|
71
|
+
var EcwtInvalidError = class extends Error {
|
|
72
|
+
message = "Ecwt token is invalid.";
|
|
73
|
+
constructor(ecwt) {
|
|
74
|
+
super();
|
|
75
|
+
this.ecwt = ecwt;
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
/** Error thrown when parsed Ecwt is expired. */
|
|
79
|
+
var EcwtExpiredError = class extends EcwtInvalidError {
|
|
80
|
+
message = "Ecwt is expired.";
|
|
81
|
+
};
|
|
82
|
+
/** Error thrown when parsed Ecwt is revoked. */
|
|
83
|
+
var EcwtRevokedError = class extends EcwtInvalidError {
|
|
84
|
+
message = "Ecwt is revoked.";
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
//#endregion
|
|
88
|
+
//#region src/factory.ts
|
|
89
|
+
const REDIS_PREFIX = "@ecwt:";
|
|
90
|
+
var EcwtFactory = class {
|
|
91
|
+
redisClient;
|
|
92
|
+
lruCache;
|
|
93
|
+
snowflakeFactory;
|
|
94
|
+
redis_key_revoked;
|
|
95
|
+
encryption_key;
|
|
96
|
+
validator;
|
|
97
|
+
cborEncoder = null;
|
|
98
|
+
constructor({ redisClient, lruCache, snowflakeFactory, options }) {
|
|
99
|
+
this.redisClient = redisClient;
|
|
100
|
+
this.lruCache = lruCache;
|
|
101
|
+
this.snowflakeFactory = snowflakeFactory;
|
|
102
|
+
this.redis_key_revoked = `${REDIS_PREFIX}${options.namespace}:revoked`;
|
|
103
|
+
this.encryption_key = options.key;
|
|
104
|
+
this.validator = options.validator;
|
|
105
|
+
if (options.senml_key_map) this.cborEncoder = new Encoder({ keyMap: options.senml_key_map });
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Creates new token.
|
|
109
|
+
* @async
|
|
110
|
+
* @param data - Data to be stored in token.
|
|
111
|
+
* @param options -
|
|
112
|
+
* @param options.ttl - Time to live in seconds. If not defined, token will never expire.
|
|
113
|
+
* @returns -
|
|
114
|
+
*/
|
|
115
|
+
async create(data, options = {}) {
|
|
116
|
+
if (typeof this.validator === "function") data = this.validator(data);
|
|
117
|
+
const ttl = options.ttl ?? null;
|
|
118
|
+
const snowflake = await this.snowflakeFactory.createSafe();
|
|
119
|
+
const payload = [
|
|
120
|
+
snowflake.toBuffer(),
|
|
121
|
+
ttl,
|
|
122
|
+
data
|
|
123
|
+
];
|
|
124
|
+
const token_raw = this.cborEncoder ? this.cborEncoder.encode(payload) : encode(payload);
|
|
125
|
+
const token_encrypted = await v2.encrypt(token_raw, this.encryption_key);
|
|
126
|
+
const token = base62.encode(token_encrypted);
|
|
127
|
+
this.setCache(token, {
|
|
128
|
+
snowflake,
|
|
129
|
+
ttl_initial: ttl,
|
|
130
|
+
data
|
|
131
|
+
});
|
|
132
|
+
return new Ecwt(this, {
|
|
133
|
+
token,
|
|
134
|
+
snowflake,
|
|
135
|
+
ttl_initial: ttl,
|
|
136
|
+
data
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Sets data to cache.
|
|
141
|
+
* @param token - String representation of token.
|
|
142
|
+
* @param cache_value - Data to be stored in cache.
|
|
143
|
+
*/
|
|
144
|
+
setCache(token, cache_value) {
|
|
145
|
+
this.lruCache?.set(token, cache_value, cache_value.ttl_initial === null ? void 0 : { ttl: cache_value.ttl_initial * 1e3 });
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Parses token.
|
|
149
|
+
* @param token String representation of token.
|
|
150
|
+
* @returns -
|
|
151
|
+
*/
|
|
152
|
+
async verify(token) {
|
|
153
|
+
if (typeof token !== "string") throw new TypeError("Token must be a string.");
|
|
154
|
+
let snowflake;
|
|
155
|
+
let ttl_initial;
|
|
156
|
+
let data;
|
|
157
|
+
const cached_entry = this.lruCache?.info(token);
|
|
158
|
+
if (cached_entry === void 0) {
|
|
159
|
+
const token_encrypted = Buffer.from(base62.decode(token));
|
|
160
|
+
let token_raw;
|
|
161
|
+
try {
|
|
162
|
+
token_raw = await decrypt(token_encrypted, this.encryption_key);
|
|
163
|
+
} catch {
|
|
164
|
+
throw new EcwtParseError();
|
|
165
|
+
}
|
|
166
|
+
const payload = this.cborEncoder ? this.cborEncoder.decode(token_raw) : decode(token_raw);
|
|
167
|
+
const [snowflake_buffer] = payload;
|
|
168
|
+
[, ttl_initial, data] = payload;
|
|
169
|
+
snowflake = this.snowflakeFactory.parse(snowflake_buffer);
|
|
170
|
+
if (typeof this.validator === "function") try {
|
|
171
|
+
data = this.validator(data);
|
|
172
|
+
} catch {
|
|
173
|
+
throw new EcwtParseError();
|
|
174
|
+
}
|
|
175
|
+
this.setCache(token, {
|
|
176
|
+
snowflake,
|
|
177
|
+
ttl_initial,
|
|
178
|
+
data
|
|
179
|
+
});
|
|
180
|
+
} else ({snowflake, ttl_initial, data} = cached_entry.value);
|
|
181
|
+
const ecwt = new Ecwt(this, {
|
|
182
|
+
token,
|
|
183
|
+
snowflake,
|
|
184
|
+
ttl_initial,
|
|
185
|
+
data
|
|
186
|
+
});
|
|
187
|
+
if (typeof ttl_initial === "number" && Number.isNaN(ttl_initial) !== true && snowflake.timestamp + ttl_initial * 1e3 < Date.now()) throw new EcwtExpiredError(ecwt);
|
|
188
|
+
if (this.redisClient) {
|
|
189
|
+
const score = await this.redisClient.ZSCORE(this.redis_key_revoked, ecwt.id);
|
|
190
|
+
if (score !== null) throw new EcwtRevokedError(ecwt);
|
|
191
|
+
}
|
|
192
|
+
return ecwt;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Parses token without throwing errors.
|
|
196
|
+
* @param token - String representation of token.
|
|
197
|
+
* @returns Returns whether token was parsed and verified successfully and Ecwt if parsed.
|
|
198
|
+
*/
|
|
199
|
+
async safeVerify(token) {
|
|
200
|
+
let ecwt = null;
|
|
201
|
+
try {
|
|
202
|
+
ecwt = await this.verify(token);
|
|
203
|
+
return {
|
|
204
|
+
success: true,
|
|
205
|
+
ecwt
|
|
206
|
+
};
|
|
207
|
+
} catch (error) {
|
|
208
|
+
if (error instanceof EcwtParseError) return {
|
|
209
|
+
success: false,
|
|
210
|
+
ecwt: null
|
|
211
|
+
};
|
|
212
|
+
if (error instanceof EcwtInvalidError) return {
|
|
213
|
+
success: false,
|
|
214
|
+
ecwt
|
|
215
|
+
};
|
|
216
|
+
throw error;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Revokes token.
|
|
221
|
+
* @param token_id -
|
|
222
|
+
* @param ts_ms_created -
|
|
223
|
+
* @param ttl_initial -
|
|
224
|
+
* @returns -
|
|
225
|
+
*/
|
|
226
|
+
async _revoke(token_id, ts_ms_created, ttl_initial) {
|
|
227
|
+
if (this.redisClient) {
|
|
228
|
+
ttl_initial ??= Number.MAX_SAFE_INTEGER;
|
|
229
|
+
const ts_ms_expired = ts_ms_created + ttl_initial * 1e3;
|
|
230
|
+
if (ts_ms_expired > Date.now()) await this.redisClient.MULTI().ZADD(this.redis_key_revoked, {
|
|
231
|
+
score: ts_ms_expired,
|
|
232
|
+
value: token_id
|
|
233
|
+
}).ZREMRANGEBYSCORE(this.redis_key_revoked, "-inf", Date.now()).EXEC();
|
|
234
|
+
} else console.warn("[ecwt] Redis client is not provided. Tokens cannot be revoked.");
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Purges LRU cache.
|
|
238
|
+
* @returns {void} -
|
|
239
|
+
*/
|
|
240
|
+
_purgeCache() {
|
|
241
|
+
this.lruCache?.clear();
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
//#endregion
|
|
246
|
+
export { Ecwt, EcwtExpiredError, EcwtFactory, EcwtInvalidError, EcwtParseError, EcwtRevokedError };
|
package/package.json
CHANGED
|
@@ -1,49 +1,54 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ecwt",
|
|
3
|
-
"version": "0.2.
|
|
4
|
-
"description": "Encrypted CBOR
|
|
3
|
+
"version": "0.2.5",
|
|
4
|
+
"description": "Encrypted CBOR Web Token",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
5
8
|
"type": "module",
|
|
6
|
-
"main": "
|
|
7
|
-
"types": "dist/
|
|
9
|
+
"main": "dist/main.js",
|
|
10
|
+
"types": "dist/main.d.ts",
|
|
8
11
|
"exports": {
|
|
9
12
|
".": {
|
|
10
|
-
"import": "./
|
|
13
|
+
"import": "./dist/main.js",
|
|
11
14
|
"require": "./dist/main.cjs"
|
|
12
15
|
}
|
|
13
16
|
},
|
|
14
17
|
"engines": {
|
|
15
|
-
"node": ">=
|
|
18
|
+
"node": ">=16"
|
|
16
19
|
},
|
|
17
20
|
"dependencies": {
|
|
18
|
-
"base-x": "
|
|
21
|
+
"base-x": "5.0.1",
|
|
19
22
|
"cbor-x": "1.5.6",
|
|
20
|
-
"evilcrypt": "0.2.
|
|
23
|
+
"evilcrypt": "0.2.3"
|
|
21
24
|
},
|
|
22
25
|
"peerDependencies": {
|
|
23
|
-
"@kirick/snowflake": "^0.2.
|
|
26
|
+
"@kirick/snowflake": "^0.2.3 || ^0.3",
|
|
24
27
|
"lru-cache": "^9 || ^10",
|
|
25
|
-
"redis": "^4"
|
|
28
|
+
"redis": "^4.6"
|
|
26
29
|
},
|
|
27
30
|
"devDependencies": {
|
|
28
|
-
"@kirick/eslint-config": "
|
|
29
|
-
"@types/node": "^22.
|
|
30
|
-
"eslint": "9.
|
|
31
|
-
"
|
|
32
|
-
"
|
|
31
|
+
"@kirick/eslint-config": "0.1.30",
|
|
32
|
+
"@types/node": "^22.14.1",
|
|
33
|
+
"eslint": "9.10.0",
|
|
34
|
+
"publint": "^0.3.12",
|
|
35
|
+
"tsdown": "^0.10.0",
|
|
36
|
+
"typescript": "5.8.3",
|
|
37
|
+
"unplugin-unused": "^0.4.4",
|
|
38
|
+
"valibot": "^1.0.0",
|
|
39
|
+
"vitest": "3.1.2"
|
|
33
40
|
},
|
|
34
41
|
"scripts": {
|
|
35
|
-
"build": "
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"lint": "eslint . && bunx tsc --skipLibCheck --noemit",
|
|
39
|
-
"publish:npm": "bun run build && bun run lint && bun run test && npm publish",
|
|
42
|
+
"build": "tsdown src/main.ts --publint --unused --dts --format esm --format cjs && rm dist/main.d.cts",
|
|
43
|
+
"check": "bun run lint && bun run build && bun run test",
|
|
44
|
+
"lint": "eslint . && tsc --skipLibCheck --noemit",
|
|
40
45
|
"redis:up": "docker ps | grep test-redis >/dev/null || docker run --rm -d -p 16379:6379 --name test-redis redis:7-alpine",
|
|
41
46
|
"test": "bun run redis:up && npm run test:vitest && bun test --coverage",
|
|
42
47
|
"test:vitest": "vitest run --no-file-parallelism"
|
|
43
48
|
},
|
|
44
49
|
"repository": {
|
|
45
50
|
"type": "git",
|
|
46
|
-
"url": "git+https://github.com/
|
|
51
|
+
"url": "git+https://github.com/kirick-ts/ecwt.git"
|
|
47
52
|
},
|
|
48
53
|
"keywords": [
|
|
49
54
|
"ecwt",
|
|
@@ -56,7 +61,7 @@
|
|
|
56
61
|
"author": "Daniil Kirichenko (https://twitter.com/kirickme)",
|
|
57
62
|
"license": "MIT",
|
|
58
63
|
"bugs": {
|
|
59
|
-
"url": "https://github.com/
|
|
64
|
+
"url": "https://github.com/kirick-ts/ecwt/issues"
|
|
60
65
|
},
|
|
61
|
-
"homepage": "https://github.com/
|
|
66
|
+
"homepage": "https://github.com/kirick-ts/ecwt#readme"
|
|
62
67
|
}
|
package/bun.lockb
DELETED
|
Binary file
|
package/dist/types/factory.d.ts
DELETED
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @template {Record<string, any>} [D=Record<string, any>]
|
|
3
|
-
*/
|
|
4
|
-
export class EcwtFactory<D extends Record<string, any> = Record<string, any>> {
|
|
5
|
-
/**
|
|
6
|
-
* @param {object} param0 -
|
|
7
|
-
* @param {import('redis').RedisClientType<import('redis').RedisModules, import('redis').RedisFunctions, import('redis').RedisScripts>} [param0.redisClient] RedisClient instance. If not provided, tokens will not be revoked and cannot be checked for revocation.
|
|
8
|
-
* @param {LRUCache<string, CacheValue>} [param0.lruCache] LRUCache instance. If not provided, tokens will be decrypted every time they are verified.
|
|
9
|
-
* @param {SnowflakeFactory} param0.snowflakeFactory SnowflakeFactory instance.
|
|
10
|
-
* @param {object} param0.options -
|
|
11
|
-
* @param {string} [param0.options.namespace] Namespace for Redis keys.
|
|
12
|
-
* @param {Buffer} param0.options.key Encryption key, 64 bytes
|
|
13
|
-
* @param {(value: unknown) => D} [param0.options.validator] Validator for token data. Should return validated value or throw an error.
|
|
14
|
-
* @param {Record<string, number>} [param0.options.senml_key_map] Payload object keys mapped for their SenML keys.
|
|
15
|
-
*/
|
|
16
|
-
constructor({ redisClient, lruCache, snowflakeFactory, options: { namespace, key, validator, senml_key_map, }, }: {
|
|
17
|
-
redisClient?: import("redis").RedisClientType<import("redis").RedisModules, import("redis").RedisFunctions, import("redis").RedisScripts> | undefined;
|
|
18
|
-
lruCache?: LRUCache<string, CacheValue, any> | undefined;
|
|
19
|
-
snowflakeFactory: SnowflakeFactory;
|
|
20
|
-
options: {
|
|
21
|
-
namespace?: string | undefined;
|
|
22
|
-
key: Buffer;
|
|
23
|
-
validator?: ((value: unknown) => D) | undefined;
|
|
24
|
-
senml_key_map?: Record<string, number> | undefined;
|
|
25
|
-
};
|
|
26
|
-
});
|
|
27
|
-
/**
|
|
28
|
-
* Creates new token.
|
|
29
|
-
* @async
|
|
30
|
-
* @param {D} data Data to be stored in token.
|
|
31
|
-
* @param {object} [options] -
|
|
32
|
-
* @param {number | null} [options.ttl] Time to live in seconds. By default, token will never expire.
|
|
33
|
-
* @returns {Promise<Ecwt<D>>} -
|
|
34
|
-
*/
|
|
35
|
-
create(data: D, { ttl, }?: {
|
|
36
|
-
ttl?: number | null | undefined;
|
|
37
|
-
} | undefined): Promise<Ecwt<D>>;
|
|
38
|
-
/**
|
|
39
|
-
* Parses token.
|
|
40
|
-
* @async
|
|
41
|
-
* @param {string} token String representation of token.
|
|
42
|
-
* @returns {Promise<Ecwt<D>>} -
|
|
43
|
-
*/
|
|
44
|
-
verify(token: string): Promise<Ecwt<D>>;
|
|
45
|
-
/**
|
|
46
|
-
* Parses token without throwing errors.
|
|
47
|
-
* @async
|
|
48
|
-
* @param {string} token String representation of token.
|
|
49
|
-
* @returns {Promise<{ success: true, ecwt: Ecwt<D> } | { success: false, ecwt: Ecwt<D> | null }>} Returns whether token was parsed and verified successfully and Ecwt if parsed.
|
|
50
|
-
*/
|
|
51
|
-
safeVerify(token: string): Promise<{
|
|
52
|
-
success: true;
|
|
53
|
-
ecwt: Ecwt<D>;
|
|
54
|
-
} | {
|
|
55
|
-
success: false;
|
|
56
|
-
ecwt: Ecwt<D> | null;
|
|
57
|
-
}>;
|
|
58
|
-
/**
|
|
59
|
-
* Revokes token.
|
|
60
|
-
* @async
|
|
61
|
-
* @param {object} options -
|
|
62
|
-
* @param {string} options.token_id -
|
|
63
|
-
* @param {number} options.ts_ms_created -
|
|
64
|
-
* @param {number | null} options.ttl_initial -
|
|
65
|
-
* @returns {Promise<void>} -
|
|
66
|
-
*/
|
|
67
|
-
_revoke({ token_id, ts_ms_created, ttl_initial, }: {
|
|
68
|
-
token_id: string;
|
|
69
|
-
ts_ms_created: number;
|
|
70
|
-
ttl_initial: number | null;
|
|
71
|
-
}): Promise<void>;
|
|
72
|
-
/**
|
|
73
|
-
* Purges cache.
|
|
74
|
-
* @private
|
|
75
|
-
* @returns {void} -
|
|
76
|
-
*/
|
|
77
|
-
private _purgeCache;
|
|
78
|
-
#private;
|
|
79
|
-
}
|
|
80
|
-
export type Snowflake = import("@kirick/snowflake").Snowflake;
|
|
81
|
-
export type CacheValue = {
|
|
82
|
-
/**
|
|
83
|
-
* -
|
|
84
|
-
*/
|
|
85
|
-
snowflake: Snowflake;
|
|
86
|
-
/**
|
|
87
|
-
* -
|
|
88
|
-
*/
|
|
89
|
-
ttl_initial: number | null;
|
|
90
|
-
/**
|
|
91
|
-
* -
|
|
92
|
-
*/
|
|
93
|
-
data: Record<string, any>;
|
|
94
|
-
};
|
|
95
|
-
import { Ecwt } from './token.js';
|
|
96
|
-
import { LRUCache } from 'lru-cache';
|
|
97
|
-
import { SnowflakeFactory } from '@kirick/snowflake';
|
package/dist/types/main.d.ts
DELETED
package/dist/types/token.d.ts
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @typedef {import('@kirick/snowflake').Snowflake} Snowflake
|
|
3
|
-
* @typedef {import('./factory.js').EcwtFactory} EcwtFactory
|
|
4
|
-
*/
|
|
5
|
-
/**
|
|
6
|
-
* @template {Record<string, any>} [D=Record<string, any>]
|
|
7
|
-
*/
|
|
8
|
-
export class Ecwt<D extends Record<string, any> = Record<string, any>> {
|
|
9
|
-
/**
|
|
10
|
-
* @param {EcwtFactory} ecwtFactory -
|
|
11
|
-
* @param {object} options -
|
|
12
|
-
* @param {string} options.token String representation of token.
|
|
13
|
-
* @param {Snowflake} options.snowflake -
|
|
14
|
-
* @param {number | null} options.ttl_initial Time to live in seconds at the moment of token creation.
|
|
15
|
-
* @param {D} options.data Data stored in token.
|
|
16
|
-
*/
|
|
17
|
-
constructor(ecwtFactory: EcwtFactory, { token, snowflake, ttl_initial, data, }: {
|
|
18
|
-
token: string;
|
|
19
|
-
snowflake: Snowflake;
|
|
20
|
-
ttl_initial: number | null;
|
|
21
|
-
data: D;
|
|
22
|
-
});
|
|
23
|
-
/**
|
|
24
|
-
* Token string representation.
|
|
25
|
-
* @type {string}
|
|
26
|
-
* @readonly
|
|
27
|
-
*/
|
|
28
|
-
readonly token: string;
|
|
29
|
-
/**
|
|
30
|
-
* Token ID.
|
|
31
|
-
* @type {string}
|
|
32
|
-
* @readonly
|
|
33
|
-
*/
|
|
34
|
-
readonly id: string;
|
|
35
|
-
/**
|
|
36
|
-
* Snowflake associated with token.
|
|
37
|
-
* @type {Snowflake}
|
|
38
|
-
* @readonly
|
|
39
|
-
*/
|
|
40
|
-
readonly snowflake: Snowflake;
|
|
41
|
-
/**
|
|
42
|
-
* Timestamp when token expires in seconds.
|
|
43
|
-
* @type {number?}
|
|
44
|
-
* @readonly
|
|
45
|
-
*/
|
|
46
|
-
readonly ts_expired: number | null;
|
|
47
|
-
/**
|
|
48
|
-
* Data stored in token.
|
|
49
|
-
* @type {D}
|
|
50
|
-
* @readonly
|
|
51
|
-
*/
|
|
52
|
-
readonly data: D;
|
|
53
|
-
/**
|
|
54
|
-
* Actual time to live in seconds.
|
|
55
|
-
* @returns {number | null} -
|
|
56
|
-
*/
|
|
57
|
-
getTTL(): number | null;
|
|
58
|
-
/**
|
|
59
|
-
* Revokes token.
|
|
60
|
-
* @returns {Promise<void>} -
|
|
61
|
-
*/
|
|
62
|
-
revoke(): Promise<void>;
|
|
63
|
-
#private;
|
|
64
|
-
}
|
|
65
|
-
export type Snowflake = import("@kirick/snowflake").Snowflake;
|
|
66
|
-
export type EcwtFactory = import("./factory.js").EcwtFactory;
|