shelving 1.138.0 → 1.139.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/db/Provider.js +3 -3
- package/error/BaseError.d.ts +3 -1
- package/error/index.d.ts +0 -1
- package/error/index.js +0 -1
- package/markup/rule/link.js +3 -2
- package/package.json +1 -1
- package/schema/AllowSchema.js +4 -4
- package/schema/DateSchema.js +2 -1
- package/schema/EntitySchema.js +4 -4
- package/schema/NumberSchema.js +3 -2
- package/store/ArrayStore.js +5 -5
- package/util/array.d.ts +67 -66
- package/util/array.js +90 -30
- package/util/async.js +4 -4
- package/util/base64.d.ts +7 -7
- package/util/base64.js +40 -31
- package/util/boolean.d.ts +6 -5
- package/util/boolean.js +11 -11
- package/util/bytes.d.ts +4 -1
- package/util/bytes.js +7 -3
- package/util/class.js +2 -2
- package/util/color.d.ts +4 -3
- package/util/color.js +5 -6
- package/util/crypto.d.ts +21 -0
- package/util/crypto.js +80 -0
- package/util/data.d.ts +6 -13
- package/util/data.js +8 -12
- package/util/date.d.ts +35 -27
- package/util/date.js +90 -82
- package/util/dictionary.d.ts +13 -7
- package/util/dictionary.js +12 -12
- package/util/entity.d.ts +8 -7
- package/util/entity.js +8 -8
- package/util/equal.d.ts +3 -2
- package/util/equal.js +5 -5
- package/util/file.d.ts +2 -1
- package/util/file.js +3 -3
- package/util/format.d.ts +51 -0
- package/util/format.js +113 -0
- package/util/function.js +2 -2
- package/util/index.d.ts +2 -0
- package/util/index.js +2 -0
- package/util/iterate.d.ts +0 -15
- package/util/iterate.js +0 -55
- package/util/jwt.d.ts +4 -2
- package/util/jwt.js +50 -41
- package/util/link.d.ts +26 -10
- package/util/link.js +34 -21
- package/util/map.d.ts +8 -7
- package/util/map.js +17 -18
- package/util/null.d.ts +7 -6
- package/util/null.js +13 -13
- package/util/number.d.ts +36 -42
- package/util/number.js +60 -82
- package/util/object.d.ts +0 -7
- package/util/object.js +4 -24
- package/util/optional.d.ts +2 -1
- package/util/optional.js +2 -2
- package/util/path.d.ts +7 -6
- package/util/path.js +10 -10
- package/util/query.js +3 -3
- package/util/random.js +2 -2
- package/util/regexp.js +2 -2
- package/util/set.d.ts +7 -6
- package/util/set.js +13 -13
- package/util/string.d.ts +26 -37
- package/util/string.js +40 -69
- package/util/template.d.ts +5 -4
- package/util/template.js +14 -13
- package/util/time.d.ts +7 -8
- package/util/time.js +14 -21
- package/util/transform.d.ts +3 -2
- package/util/transform.js +0 -1
- package/util/undefined.d.ts +4 -3
- package/util/undefined.js +8 -6
- package/util/units.d.ts +1 -2
- package/util/units.js +1 -1
- package/util/update.js +3 -3
- package/util/url.d.ts +6 -7
- package/util/url.js +7 -17
- package/util/validate.d.ts +2 -2
- package/error/AssertionError.d.ts +0 -8
- package/error/AssertionError.js +0 -12
package/util/format.js
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { isArray } from "./array.js";
|
|
2
|
+
import { NNBSP } from "./constants.js";
|
|
3
|
+
import { isDate, requireDate } from "./date.js";
|
|
4
|
+
import { getPercent } from "./number.js";
|
|
5
|
+
import { isObject } from "./object.js";
|
|
6
|
+
import { requireTime } from "./time.js";
|
|
7
|
+
import { requireURL } from "./url.js";
|
|
8
|
+
/** Format a number range (based on the user's browser language settings). */
|
|
9
|
+
export function formatRange(min, max, options) {
|
|
10
|
+
return `${formatNumber(min, options)}${NNBSP}–${NNBSP}${formatNumber(max, options)}`;
|
|
11
|
+
}
|
|
12
|
+
/** Format a number with a short suffix, e.g. `1,000 kg` */
|
|
13
|
+
export function formatQuantity(num, suffix, options) {
|
|
14
|
+
const o = { unitDisplay: "short", ...options, style: "decimal" };
|
|
15
|
+
const str = formatNumber(num, o);
|
|
16
|
+
const sep = o.unitDisplay === "narrow" ? "" : NNBSP;
|
|
17
|
+
return `${str}${sep}${suffix}`;
|
|
18
|
+
}
|
|
19
|
+
/** Format a number (based on the user's browser language settings). */
|
|
20
|
+
export function formatNumber(num, options) {
|
|
21
|
+
if (!Number.isFinite(num))
|
|
22
|
+
return Number.isNaN(num) ? "-" : "∞";
|
|
23
|
+
return new Intl.NumberFormat(undefined, options).format(num).replace(/ /, NNBSP);
|
|
24
|
+
}
|
|
25
|
+
/** Format a number with a longer full-word suffix. */
|
|
26
|
+
export function pluralizeQuantity(num, singular, plural, options) {
|
|
27
|
+
const o = { ...options, style: "decimal" };
|
|
28
|
+
const qty = formatNumber(num, o);
|
|
29
|
+
return `${qty}${NNBSP}${num === 1 ? singular : plural}`;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Format a percentage (combines `getPercent()` and `formatQuantity()` for convenience).
|
|
33
|
+
* - Defaults to showing no decimal places.
|
|
34
|
+
* - Defaults to rounding closer to zero (so that 99.99% is shown as 99%).
|
|
35
|
+
*
|
|
36
|
+
* @param numerator Number representing the amount of progress.
|
|
37
|
+
* @param denumerator The number representing the whole amount.
|
|
38
|
+
*/
|
|
39
|
+
export function formatPercent(numerator, denumerator, options) {
|
|
40
|
+
const fullOptions = { style: "percent", maximumFractionDigits: 0, roundingMode: "trunc", ...options };
|
|
41
|
+
return formatNumber(getPercent(numerator, denumerator), fullOptions);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Format an unknown object as a string.
|
|
45
|
+
* - Use the custom `.toString()` function if it exists (don't use built in `Object.prototype.toString` because it's useless.
|
|
46
|
+
* - Use `.title` or `.name` or `.id` if they exist and are strings.
|
|
47
|
+
* - Use `Object` otherwise.
|
|
48
|
+
*/
|
|
49
|
+
export function formatObject(obj) {
|
|
50
|
+
if (typeof obj.toString === "function" && obj.toString !== Object.prototype.toString)
|
|
51
|
+
return obj.toString();
|
|
52
|
+
const name = obj.name;
|
|
53
|
+
if (typeof name === "string")
|
|
54
|
+
return name;
|
|
55
|
+
const title = obj.title;
|
|
56
|
+
if (typeof title === "string")
|
|
57
|
+
return title;
|
|
58
|
+
const id = obj.id;
|
|
59
|
+
if (typeof id === "string")
|
|
60
|
+
return id;
|
|
61
|
+
return "Object";
|
|
62
|
+
}
|
|
63
|
+
/** Format an unknown array as a string. */
|
|
64
|
+
export function formatArray(arr, separator = ", ") {
|
|
65
|
+
return arr.map(formatValue).join(separator);
|
|
66
|
+
}
|
|
67
|
+
/** Format a date in the browser locale. */
|
|
68
|
+
export function formatDate(date) {
|
|
69
|
+
return requireDate(date, formatDate).toLocaleDateString();
|
|
70
|
+
}
|
|
71
|
+
/** Format a time as a string based on the browser locale settings. */
|
|
72
|
+
export function formatTime(time, precision = 2) {
|
|
73
|
+
return requireTime(time, formatTime).format(precision);
|
|
74
|
+
}
|
|
75
|
+
/** Format a URL as a user-friendly string, e.g. `http://shax.com/test?uid=129483` → `shax.com/test` */
|
|
76
|
+
export function formatURL(possible, base) {
|
|
77
|
+
const { host, pathname } = requireURL(possible, base, formatURL);
|
|
78
|
+
return `${host}${pathname.length > 1 ? pathname : ""}`;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Convert any unknown value into a friendly string for user-facing use.
|
|
82
|
+
* - Strings return the string.
|
|
83
|
+
* - Booleans return `"Yes"` or `"No"`
|
|
84
|
+
* - Numbers return formatted number with commas etc (e.g. `formatNumber()`).
|
|
85
|
+
* - Dates return formatted date (e.g. `formatDate()`).
|
|
86
|
+
* - Arrays return the array items converted to string (with `toTitle()`), and joined with a comma.
|
|
87
|
+
* - Objects return...
|
|
88
|
+
* 1. `object.name` if it exists, or
|
|
89
|
+
* 2. `object.title` if it exists.
|
|
90
|
+
* - Falsy values like `null` and `undefined` return `"None"`
|
|
91
|
+
* - Everything else returns `"Unknown"`
|
|
92
|
+
*/
|
|
93
|
+
export function formatValue(value) {
|
|
94
|
+
if (value === null || value === undefined)
|
|
95
|
+
return "None";
|
|
96
|
+
if (typeof value === "boolean")
|
|
97
|
+
return value ? "Yes" : "No";
|
|
98
|
+
if (typeof value === "string")
|
|
99
|
+
return value || "None";
|
|
100
|
+
if (typeof value === "number")
|
|
101
|
+
return formatNumber(value);
|
|
102
|
+
if (typeof value === "symbol")
|
|
103
|
+
return value.description || "Symbol";
|
|
104
|
+
if (typeof value === "function")
|
|
105
|
+
return "Function";
|
|
106
|
+
if (isDate(value))
|
|
107
|
+
return formatDate(value);
|
|
108
|
+
if (isArray(value))
|
|
109
|
+
return formatArray(value);
|
|
110
|
+
if (isObject(value))
|
|
111
|
+
return formatObject(value);
|
|
112
|
+
return "Unknown";
|
|
113
|
+
}
|
package/util/function.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { RequiredError } from "../error/RequiredError.js";
|
|
2
2
|
/** Is a value a function? */
|
|
3
3
|
export function isFunction(value) {
|
|
4
4
|
return typeof value === "function";
|
|
@@ -6,7 +6,7 @@ export function isFunction(value) {
|
|
|
6
6
|
/** Assert that a value is a function. */
|
|
7
7
|
export function assertFunction(value) {
|
|
8
8
|
if (typeof value !== "function")
|
|
9
|
-
throw new
|
|
9
|
+
throw new RequiredError("Must be function", { received: value, caller: assertFunction });
|
|
10
10
|
}
|
|
11
11
|
/** Function that just passes through the first argument. */
|
|
12
12
|
export function PASSTHROUGH(value) {
|
package/util/index.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ export * from "./callback.js";
|
|
|
8
8
|
export * from "./class.js";
|
|
9
9
|
export * from "./color.js";
|
|
10
10
|
export * from "./constants.js";
|
|
11
|
+
export * from "./crypto.js";
|
|
11
12
|
export * from "./data.js";
|
|
12
13
|
export * from "./date.js";
|
|
13
14
|
export * from "./debug.js";
|
|
@@ -21,6 +22,7 @@ export * from "./error.js";
|
|
|
21
22
|
export * from "./file.js";
|
|
22
23
|
export * from "./filter.js";
|
|
23
24
|
export * from "./focus.js";
|
|
25
|
+
export * from "./format.js";
|
|
24
26
|
export * from "./function.js";
|
|
25
27
|
export * from "./hash.js";
|
|
26
28
|
export * from "./hydrate.js";
|
package/util/index.js
CHANGED
|
@@ -8,6 +8,7 @@ export * from "./callback.js";
|
|
|
8
8
|
export * from "./class.js";
|
|
9
9
|
export * from "./color.js";
|
|
10
10
|
export * from "./constants.js";
|
|
11
|
+
export * from "./crypto.js";
|
|
11
12
|
export * from "./data.js";
|
|
12
13
|
export * from "./date.js";
|
|
13
14
|
export * from "./debug.js";
|
|
@@ -21,6 +22,7 @@ export * from "./error.js";
|
|
|
21
22
|
export * from "./file.js";
|
|
22
23
|
export * from "./filter.js";
|
|
23
24
|
export * from "./focus.js";
|
|
25
|
+
export * from "./format.js";
|
|
24
26
|
export * from "./function.js";
|
|
25
27
|
export * from "./hash.js";
|
|
26
28
|
export * from "./hydrate.js";
|
package/util/iterate.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { type PossibleArray } from "./array.js";
|
|
2
1
|
/** Is an unknown value an iterable? */
|
|
3
2
|
export declare const isIterable: (value: unknown) => value is Iterable<unknown>;
|
|
4
3
|
/** An iterable containing items or nested iterables of items. */
|
|
@@ -10,8 +9,6 @@ export declare function flattenItems<T>(items: DeepIterable<T>): Iterable<T>;
|
|
|
10
9
|
* - Checks `items.size` or `items.length` first, or consumes the iterable and counts its iterations.
|
|
11
10
|
*/
|
|
12
11
|
export declare function hasItems(items: Iterable<unknown>): boolean;
|
|
13
|
-
/** Is an unknown value one of the values of an iterable? */
|
|
14
|
-
export declare function isItem<T>(items: Iterable<T>, value: unknown): value is T;
|
|
15
12
|
/** Count the number of items in an iterable. */
|
|
16
13
|
export declare function countItems(items: Iterable<unknown>): number;
|
|
17
14
|
/**
|
|
@@ -37,15 +34,3 @@ export declare function reduceItems<I, O>(items: Iterable<I>, reducer: (previous
|
|
|
37
34
|
export declare function getChunks<T>(items: Iterable<T>, size: number): Iterable<readonly T[]>;
|
|
38
35
|
/** Merge two or more iterables into a single iterable set. */
|
|
39
36
|
export declare function mergeItems<T>(...inputs: [Iterable<T>, Iterable<T>, ...Iterable<T>[]]): Iterable<T>;
|
|
40
|
-
/** Get the first item from an array or iterable, or `undefined` if it didn't exist. */
|
|
41
|
-
export declare function getFirstItem<T>(items: PossibleArray<T>): T | undefined;
|
|
42
|
-
/** Get the first item from an array or iterable. */
|
|
43
|
-
export declare function requireFirstItem<T>(items: PossibleArray<T>): T;
|
|
44
|
-
/** Get the last item from an array or iterable, or `undefined` if it didn't exist. */
|
|
45
|
-
export declare function getLastItem<T>(items: PossibleArray<T>): T | undefined;
|
|
46
|
-
/** Get the last item from an array or iterable. */
|
|
47
|
-
export declare function requireLastItem<T>(items: PossibleArray<T>): T;
|
|
48
|
-
/** Get the next item in an array or iterable. */
|
|
49
|
-
export declare function getNextItem<T>(items: PossibleArray<T>, value: T): T | undefined;
|
|
50
|
-
/** Get the previous item in an array or iterable. */
|
|
51
|
-
export declare function getPrevItem<T>(items: PossibleArray<T>, value: T): T | undefined;
|
package/util/iterate.js
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { RequiredError } from "../error/RequiredError.js";
|
|
2
|
-
import { getArray } from "./array.js";
|
|
3
1
|
/** Is an unknown value an iterable? */
|
|
4
2
|
export const isIterable = (value) => typeof value === "object" && !!value && Symbol.iterator in value;
|
|
5
3
|
/** Flatten one or more iterables. */
|
|
@@ -19,13 +17,6 @@ export function hasItems(items) {
|
|
|
19
17
|
return true;
|
|
20
18
|
return false;
|
|
21
19
|
}
|
|
22
|
-
/** Is an unknown value one of the values of an iterable? */
|
|
23
|
-
export function isItem(items, value) {
|
|
24
|
-
for (const item of items)
|
|
25
|
-
if (value === item)
|
|
26
|
-
return true;
|
|
27
|
-
return false;
|
|
28
|
-
}
|
|
29
20
|
/** Count the number of items in an iterable. */
|
|
30
21
|
export function countItems(items) {
|
|
31
22
|
let count = 0;
|
|
@@ -96,49 +87,3 @@ export function* mergeItems(...inputs) {
|
|
|
96
87
|
for (const input of inputs)
|
|
97
88
|
yield* input;
|
|
98
89
|
}
|
|
99
|
-
/** Get the first item from an array or iterable, or `undefined` if it didn't exist. */
|
|
100
|
-
export function getFirstItem(items) {
|
|
101
|
-
const array = getArray(items);
|
|
102
|
-
return 0 in array ? array[0] : undefined;
|
|
103
|
-
}
|
|
104
|
-
/** Get the first item from an array or iterable. */
|
|
105
|
-
export function requireFirstItem(items) {
|
|
106
|
-
const item = getFirstItem(items);
|
|
107
|
-
if (item === undefined)
|
|
108
|
-
throw new RequiredError("First item is required", { items: items, caller: requireFirstItem });
|
|
109
|
-
return item;
|
|
110
|
-
}
|
|
111
|
-
/** Get the last item from an array or iterable, or `undefined` if it didn't exist. */
|
|
112
|
-
export function getLastItem(items) {
|
|
113
|
-
const arr = getArray(items);
|
|
114
|
-
const j = arr.length - 1;
|
|
115
|
-
if (j in arr)
|
|
116
|
-
return arr[j];
|
|
117
|
-
}
|
|
118
|
-
/** Get the last item from an array or iterable. */
|
|
119
|
-
export function requireLastItem(items) {
|
|
120
|
-
const item = getLastItem(items);
|
|
121
|
-
if (item === undefined)
|
|
122
|
-
throw new RequiredError("Last item is required", { items, caller: requireLastItem });
|
|
123
|
-
return item;
|
|
124
|
-
}
|
|
125
|
-
/** Get the next item in an array or iterable. */
|
|
126
|
-
export function getNextItem(items, value) {
|
|
127
|
-
const arr = getArray(items);
|
|
128
|
-
const i = arr.indexOf(value);
|
|
129
|
-
if (i >= 0) {
|
|
130
|
-
const j = i + 1;
|
|
131
|
-
if (j in arr)
|
|
132
|
-
return arr[j];
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
/** Get the previous item in an array or iterable. */
|
|
136
|
-
export function getPrevItem(items, value) {
|
|
137
|
-
const arr = getArray(items);
|
|
138
|
-
const i = arr.indexOf(value);
|
|
139
|
-
if (i >= 1) {
|
|
140
|
-
const j = i - 1;
|
|
141
|
-
if (j in arr)
|
|
142
|
-
return arr[j];
|
|
143
|
-
}
|
|
144
|
-
}
|
package/util/jwt.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type PossibleBytes } from "./bytes.js";
|
|
1
2
|
import type { Data } from "./data.js";
|
|
2
3
|
import type { AnyFunction } from "./function.js";
|
|
3
4
|
/**
|
|
@@ -6,7 +7,7 @@ import type { AnyFunction } from "./function.js";
|
|
|
6
7
|
*
|
|
7
8
|
* @throws ValueError If the input parameters, e.g. `secret` or `issuer`, are invalid.
|
|
8
9
|
*/
|
|
9
|
-
export declare function encodeToken(claims: Data, secret:
|
|
10
|
+
export declare function encodeToken(claims: Data, secret: PossibleBytes): Promise<string>;
|
|
10
11
|
/** Parts that make up a JSON Web Token. */
|
|
11
12
|
export type TokenData = {
|
|
12
13
|
header: string;
|
|
@@ -14,6 +15,7 @@ export type TokenData = {
|
|
|
14
15
|
signature: string;
|
|
15
16
|
headerData: Data;
|
|
16
17
|
payloadData: Data;
|
|
18
|
+
signatureBytes: Uint8Array;
|
|
17
19
|
};
|
|
18
20
|
/**
|
|
19
21
|
* Split a JSON Web Token into its header, payload, and signature, and decode and parse the JSON.
|
|
@@ -28,4 +30,4 @@ export declare function _splitToken(caller: AnyFunction, token: unknown): TokenD
|
|
|
28
30
|
* @throws RequestError If the token is invalid or malformed.
|
|
29
31
|
* @throws UnauthorizedError If the token signature is incorrect, token is expired or not issued yet.
|
|
30
32
|
*/
|
|
31
|
-
export declare function verifyToken(token: unknown, secret:
|
|
33
|
+
export declare function verifyToken(token: unknown, secret: PossibleBytes): Promise<Data>;
|
package/util/jwt.js
CHANGED
|
@@ -1,14 +1,23 @@
|
|
|
1
1
|
import { RequestError, UnauthorizedError } from "../error/RequestError.js";
|
|
2
2
|
import { ValueError } from "../error/ValueError.js";
|
|
3
|
-
import {
|
|
4
|
-
import { requireBytes } from "./bytes.js";
|
|
3
|
+
import { decodeBase64URLBytes, decodeBase64URLString, encodeBase64URL } from "./base64.js";
|
|
4
|
+
import { getBytes, requireBytes } from "./bytes.js";
|
|
5
5
|
import { DAY } from "./constants.js";
|
|
6
6
|
// Constants.
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
const HASH = "SHA-512";
|
|
8
|
+
const ALGORITHM = { name: "HMAC", hash: HASH };
|
|
9
|
+
const HEADER = { alg: "HS512", typ: "JWT" };
|
|
10
|
+
const EXPIRY_MS = DAY * 10;
|
|
11
|
+
const SKEW_MS = 60; // Allow 1 minute clock skew.
|
|
12
|
+
const SECRET_BYTES = 64; // Minimum 64 bytes / 512 bits
|
|
13
|
+
function _getKey(caller, secret, ...usages) {
|
|
14
|
+
const bytes = getBytes(secret);
|
|
15
|
+
if (!bytes || bytes.length < SECRET_BYTES)
|
|
16
|
+
throw new ValueError(`JWT secret must be byte sequence with mininum ${SECRET_BYTES} bytes`, {
|
|
17
|
+
received: secret,
|
|
18
|
+
caller,
|
|
19
|
+
});
|
|
20
|
+
return crypto.subtle.importKey("raw", requireBytes(secret), ALGORITHM, false, usages);
|
|
12
21
|
}
|
|
13
22
|
/**
|
|
14
23
|
* Encode a JWT and return the string token.
|
|
@@ -17,20 +26,15 @@ function _getKey(secret, ...usages) {
|
|
|
17
26
|
* @throws ValueError If the input parameters, e.g. `secret` or `issuer`, are invalid.
|
|
18
27
|
*/
|
|
19
28
|
export async function encodeToken(claims, secret) {
|
|
20
|
-
if (typeof secret !== "string" || secret.length < TOKEN_MINIMUM_SECRET)
|
|
21
|
-
throw new ValueError(`JWT secret must be string with minimum ${TOKEN_MINIMUM_SECRET} characters`, {
|
|
22
|
-
received: secret,
|
|
23
|
-
caller: encodeToken,
|
|
24
|
-
});
|
|
25
29
|
// Encode header.
|
|
26
|
-
const header =
|
|
30
|
+
const header = encodeBase64URL(JSON.stringify(HEADER));
|
|
27
31
|
// Encode payload.
|
|
28
|
-
const
|
|
29
|
-
const exp = Math.floor(
|
|
30
|
-
const payload =
|
|
32
|
+
const now = Math.floor(Date.now() / 1000);
|
|
33
|
+
const exp = Math.floor(now + EXPIRY_MS / 1000);
|
|
34
|
+
const payload = encodeBase64URL(JSON.stringify({ nbf: now, iat: now, exp, ...claims }));
|
|
31
35
|
// Create signature.
|
|
32
|
-
const key = await _getKey(secret, "sign");
|
|
33
|
-
const signature =
|
|
36
|
+
const key = await _getKey(encodeToken, secret, "sign");
|
|
37
|
+
const signature = encodeBase64URL(await crypto.subtle.sign("HMAC", key, requireBytes(`${header}.${payload}`)));
|
|
34
38
|
// Combine token.
|
|
35
39
|
return `${header}.${payload}.${signature}`;
|
|
36
40
|
}
|
|
@@ -47,23 +51,31 @@ export function _splitToken(caller, token) {
|
|
|
47
51
|
const [header, payload, signature] = token.split(".");
|
|
48
52
|
if (!header || !payload || !signature)
|
|
49
53
|
throw new RequestError("JWT token must have header, payload, and signature", { received: token, caller });
|
|
54
|
+
// Decode signature.
|
|
55
|
+
let signatureBytes;
|
|
56
|
+
try {
|
|
57
|
+
signatureBytes = decodeBase64URLBytes(signature);
|
|
58
|
+
}
|
|
59
|
+
catch (cause) {
|
|
60
|
+
throw new RequestError("JWT token signature must be Base64URL encoded", { received: signature, cause, caller });
|
|
61
|
+
}
|
|
50
62
|
// Decode header.
|
|
51
63
|
let headerData;
|
|
52
64
|
try {
|
|
53
|
-
headerData = JSON.parse(
|
|
65
|
+
headerData = JSON.parse(decodeBase64URLString(header));
|
|
54
66
|
}
|
|
55
|
-
catch {
|
|
56
|
-
throw new RequestError("JWT token header must be
|
|
67
|
+
catch (cause) {
|
|
68
|
+
throw new RequestError("JWT token header must be Base64URL encoded JSON", { received: header, cause, caller });
|
|
57
69
|
}
|
|
58
70
|
// Decode payload.
|
|
59
71
|
let payloadData;
|
|
60
72
|
try {
|
|
61
|
-
payloadData = JSON.parse(
|
|
73
|
+
payloadData = JSON.parse(decodeBase64URLString(payload));
|
|
62
74
|
}
|
|
63
|
-
catch {
|
|
64
|
-
throw new RequestError("JWT token payload must be
|
|
75
|
+
catch (cause) {
|
|
76
|
+
throw new RequestError("JWT token payload must be Base64URL encoded JSON", { received: payload, cause, caller });
|
|
65
77
|
}
|
|
66
|
-
return { header, payload, headerData, payloadData,
|
|
78
|
+
return { header, payload, signature, headerData, payloadData, signatureBytes };
|
|
67
79
|
}
|
|
68
80
|
/**
|
|
69
81
|
* Decode a JWT, verify it, and return the full payload data.
|
|
@@ -74,28 +86,25 @@ export function _splitToken(caller, token) {
|
|
|
74
86
|
* @throws UnauthorizedError If the token signature is incorrect, token is expired or not issued yet.
|
|
75
87
|
*/
|
|
76
88
|
export async function verifyToken(token, secret) {
|
|
77
|
-
if (typeof secret !== "string" || secret.length < TOKEN_MINIMUM_SECRET)
|
|
78
|
-
throw new ValueError(`JWT secret must be string with minimum ${TOKEN_MINIMUM_SECRET} characters`, {
|
|
79
|
-
received: secret,
|
|
80
|
-
caller: verifyToken,
|
|
81
|
-
});
|
|
82
89
|
const { header, payload, signature, headerData, payloadData } = _splitToken(verifyToken, token);
|
|
83
90
|
// Validate header.
|
|
84
|
-
if (headerData.typ !==
|
|
85
|
-
throw new RequestError(`JWT header type must be \"${
|
|
86
|
-
if (headerData.alg !==
|
|
87
|
-
throw new RequestError(`JWT header algorithm must be \"${
|
|
91
|
+
if (headerData.typ !== HEADER.typ)
|
|
92
|
+
throw new RequestError(`JWT header type must be \"${HEADER.typ}\"`, { received: headerData.typ, caller: verifyToken });
|
|
93
|
+
if (headerData.alg !== HEADER.alg)
|
|
94
|
+
throw new RequestError(`JWT header algorithm must be \"${HEADER.alg}\"`, { received: headerData.alg, caller: verifyToken });
|
|
88
95
|
// Validate signature.
|
|
89
|
-
const key = await _getKey(secret, "verify");
|
|
90
|
-
const isValid = await crypto.subtle.verify("HMAC", key,
|
|
96
|
+
const key = await _getKey(verifyToken, secret, "verify");
|
|
97
|
+
const isValid = await crypto.subtle.verify("HMAC", key, decodeBase64URLBytes(signature), requireBytes(`${header}.${payload}`));
|
|
91
98
|
if (!isValid)
|
|
92
99
|
throw new UnauthorizedError("JWT signature does not match", { received: token, caller: verifyToken });
|
|
93
100
|
// Validate payload.
|
|
94
|
-
const { iat, exp } = payloadData;
|
|
101
|
+
const { nbf, iat, exp } = payloadData;
|
|
95
102
|
const now = Math.floor(Date.now() / 1000);
|
|
96
|
-
if (typeof
|
|
97
|
-
throw new UnauthorizedError("JWT
|
|
98
|
-
if (typeof
|
|
99
|
-
throw new UnauthorizedError("JWT
|
|
103
|
+
if (typeof nbf === "number" && nbf < now - SKEW_MS)
|
|
104
|
+
throw new UnauthorizedError("JWT cannot be used yet", { received: payloadData, expected: now, caller: verifyToken });
|
|
105
|
+
if (typeof iat === "number" && iat > now + SKEW_MS)
|
|
106
|
+
throw new UnauthorizedError("JWT not issued yet", { received: payloadData, expected: now, caller: verifyToken });
|
|
107
|
+
if (typeof exp === "number" && exp < now - SKEW_MS)
|
|
108
|
+
throw new UnauthorizedError("JWT has expired", { received: payloadData, expected: now, caller: verifyToken });
|
|
100
109
|
return payloadData;
|
|
101
110
|
}
|
package/util/link.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { AnyCaller } from "../error/BaseError.js";
|
|
1
2
|
import type { ImmutableArray } from "./array.js";
|
|
2
3
|
import type { Optional } from "./optional.js";
|
|
3
4
|
import type { Path } from "./path.js";
|
|
@@ -43,13 +44,28 @@ export type LinkHosts = ImmutableArray<string>;
|
|
|
43
44
|
export type AbsoluteLinkURL = URL & {
|
|
44
45
|
href: AbsoluteLink;
|
|
45
46
|
};
|
|
46
|
-
/**
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
export declare function
|
|
47
|
+
/**
|
|
48
|
+
* Is an unknown value a link URL?
|
|
49
|
+
* - A valid link URL is a `URL` instance with a scheme matching the `schemes` array, and `host` matching the optional `hosts` array.
|
|
50
|
+
*/
|
|
51
|
+
export declare function isLinkURL(value: unknown, schemes?: LinkSchemes, hosts?: LinkHosts): value is AbsoluteLinkURL;
|
|
52
|
+
/**
|
|
53
|
+
* Convert a possible URL to a link URL, or return `undefined` if conversion fails.
|
|
54
|
+
* - A valid link URL is a `URL` instance with a scheme matching the `schemes` array, and `host` matching the optional `hosts` array.
|
|
55
|
+
*/
|
|
56
|
+
export declare function getLinkURL(value: Optional<PossibleURL>, base?: AbsoluteLinkURL | AbsoluteLink, schemes?: LinkSchemes, hosts?: LinkHosts): AbsoluteLinkURL | undefined;
|
|
57
|
+
/**
|
|
58
|
+
* Convert a possible URL to a link URL, or throw `RequiredError` if conversion fails.
|
|
59
|
+
* - A valid link URL is a `URL` instance with a scheme matching the `schemes` array, and `host` matching the optional `hosts` array.
|
|
60
|
+
*/
|
|
61
|
+
export declare function requireLinkURL(value: PossibleURL, base?: AbsoluteLinkURL | AbsoluteLink, schemes?: LinkSchemes, hosts?: LinkHosts, caller?: AnyCaller): AbsoluteLinkURL;
|
|
62
|
+
/**
|
|
63
|
+
* Convert a possible URL to an link URL string, or return `undefined` if conversion fails.
|
|
64
|
+
* - A valid link URL string is an absolute URL string with a scheme matching the `schemes` array, and `host` matching the optional `hosts` array.
|
|
65
|
+
*/
|
|
66
|
+
export declare function getLink(value: Optional<PossibleURL>, base?: AbsoluteLinkURL | AbsoluteLink, schemes?: LinkSchemes, hosts?: LinkHosts): AbsoluteLink | undefined;
|
|
67
|
+
/**
|
|
68
|
+
* Convert a possible URL to an link URL string, or throw `RequiredError` if conversion fails.
|
|
69
|
+
* - A valid link URL string is an absolute URL string with a scheme matching the `schemes` array, and `host` matching the optional `hosts` array.
|
|
70
|
+
*/
|
|
71
|
+
export declare function requireLink(value: PossibleURL, base?: AbsoluteLinkURL | AbsoluteLink, schemes?: LinkSchemes, hosts?: LinkHosts, caller?: AnyCaller): AbsoluteLink;
|
package/util/link.js
CHANGED
|
@@ -1,31 +1,44 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { RequiredError } from "../error/RequiredError.js";
|
|
2
|
+
import { getURL, isURL } from "./url.js";
|
|
3
3
|
/** Default whitelist for URL schemes. */
|
|
4
4
|
const SCHEMES = ["http:", "https:"];
|
|
5
|
-
/**
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Is an unknown value a link URL?
|
|
7
|
+
* - A valid link URL is a `URL` instance with a scheme matching the `schemes` array, and `host` matching the optional `hosts` array.
|
|
8
|
+
*/
|
|
9
|
+
export function isLinkURL(value, schemes = SCHEMES, hosts) {
|
|
10
|
+
return isURL(value) && schemes.includes(value.protocol) && (!hosts || hosts.includes(value.host));
|
|
8
11
|
}
|
|
9
|
-
/**
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
12
|
+
/**
|
|
13
|
+
* Convert a possible URL to a link URL, or return `undefined` if conversion fails.
|
|
14
|
+
* - A valid link URL is a `URL` instance with a scheme matching the `schemes` array, and `host` matching the optional `hosts` array.
|
|
15
|
+
*/
|
|
16
|
+
export function getLinkURL(value, base, schemes = SCHEMES, hosts) {
|
|
17
|
+
const url = getURL(value, base);
|
|
18
|
+
if (isLinkURL(url, schemes, hosts))
|
|
13
19
|
return url;
|
|
14
20
|
}
|
|
15
|
-
/**
|
|
16
|
-
|
|
17
|
-
|
|
21
|
+
/**
|
|
22
|
+
* Convert a possible URL to a link URL, or throw `RequiredError` if conversion fails.
|
|
23
|
+
* - A valid link URL is a `URL` instance with a scheme matching the `schemes` array, and `host` matching the optional `hosts` array.
|
|
24
|
+
*/
|
|
25
|
+
export function requireLinkURL(value, base, schemes, hosts, caller = requireLinkURL) {
|
|
26
|
+
const url = getLinkURL(value, base, schemes, hosts);
|
|
18
27
|
if (!url)
|
|
19
|
-
throw new
|
|
28
|
+
throw new RequiredError("Invalid link", { received: value, base, schemes, hosts, caller });
|
|
20
29
|
return url;
|
|
21
30
|
}
|
|
22
|
-
/**
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
31
|
+
/**
|
|
32
|
+
* Convert a possible URL to an link URL string, or return `undefined` if conversion fails.
|
|
33
|
+
* - A valid link URL string is an absolute URL string with a scheme matching the `schemes` array, and `host` matching the optional `hosts` array.
|
|
34
|
+
*/
|
|
35
|
+
export function getLink(value, base, schemes = SCHEMES, hosts) {
|
|
36
|
+
return getLinkURL(value, base, schemes, hosts)?.href;
|
|
27
37
|
}
|
|
28
|
-
/**
|
|
29
|
-
|
|
30
|
-
|
|
38
|
+
/**
|
|
39
|
+
* Convert a possible URL to an link URL string, or throw `RequiredError` if conversion fails.
|
|
40
|
+
* - A valid link URL string is an absolute URL string with a scheme matching the `schemes` array, and `host` matching the optional `hosts` array.
|
|
41
|
+
*/
|
|
42
|
+
export function requireLink(value, base, schemes, hosts, caller = requireLink) {
|
|
43
|
+
return requireLinkURL(value, base, schemes, hosts, caller).href;
|
|
31
44
|
}
|
package/util/map.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { AnyCaller } from "../error/BaseError.js";
|
|
1
2
|
import type { Entry } from "./entry.js";
|
|
2
3
|
/** `Map` that cannot be changed. */
|
|
3
4
|
export type ImmutableMap<K = unknown, T = unknown> = ReadonlyMap<K, T>;
|
|
@@ -22,23 +23,23 @@ export type PossibleStringMap<K extends string, T> = PossibleMap<K, T> | {
|
|
|
22
23
|
/** Is an unknown value a map? */
|
|
23
24
|
export declare function isMap(value: unknown): value is ImmutableMap;
|
|
24
25
|
/** Assert that a value is a `Map` instance. */
|
|
25
|
-
export declare function assertMap(value: unknown): asserts value is ImmutableMap;
|
|
26
|
-
/** Is an unknown value a key for an item in a map? */
|
|
27
|
-
export declare function isMapItem<K, V>(map: ImmutableMap<K, V>, key: unknown): key is K;
|
|
28
|
-
/** Assert that an unknown value is a key for an item in a map. */
|
|
29
|
-
export declare function assertMapItem<K, V>(map: ImmutableMap<K, V>, key: unknown): asserts key is K;
|
|
26
|
+
export declare function assertMap(value: unknown, caller?: AnyCaller): asserts value is ImmutableMap;
|
|
30
27
|
/** Convert an iterable to a `Map` (if it's already a `Map` it passes through unchanged). */
|
|
31
28
|
export declare function getMap<K extends string, T>(input: PossibleStringMap<K, T>): ImmutableMap<K, T>;
|
|
32
29
|
export declare function getMap<K, T>(input: PossibleMap<K, T>): ImmutableMap<K, T>;
|
|
33
30
|
/** Apply a limit to a map. */
|
|
34
31
|
export declare function limitMap<T>(map: ImmutableMap<T>, limit: number): ImmutableMap<T>;
|
|
32
|
+
/** Is an unknown value a key for an item in a map? */
|
|
33
|
+
export declare function isMapItem<K, V>(map: ImmutableMap<K, V>, key: unknown): key is K;
|
|
34
|
+
/** Assert that an unknown value is a key for an item in a map. */
|
|
35
|
+
export declare function assertMapItem<K, V>(map: ImmutableMap<K, V>, key: unknown, caller?: AnyCaller): asserts key is K;
|
|
35
36
|
/** Function that lets new items in a map be created and updated by calling a `reduce()` callback that receives the existing value. */
|
|
36
37
|
export declare function setMapItem<K, T>(map: MutableMap<K, T>, key: K, value: T): T;
|
|
37
38
|
/** Add multiple items to a set (by reference). */
|
|
38
39
|
export declare function setMapItems<K, T>(map: MutableMap<K, T>, items: Iterable<MapItem<ImmutableMap<K, T>>>): void;
|
|
39
40
|
/** Remove multiple items from a set (by reference). */
|
|
40
41
|
export declare function removeMapItems<K, T>(map: MutableMap<K, T>, ...keys: K[]): void;
|
|
41
|
-
/** Get an item in a map, or throw `RequiredError` if it doesn't exist. */
|
|
42
|
-
export declare function requireMapItem<K, T>(map: ImmutableMap<K, T>, key: K): T;
|
|
43
42
|
/** Get an item in a map, or `undefined` if it doesn't exist. */
|
|
44
43
|
export declare function getMapItem<K, T>(map: ImmutableMap<K, T>, key: K): T | undefined;
|
|
44
|
+
/** Get an item in a map, or throw `RequiredError` if it doesn't exist. */
|
|
45
|
+
export declare function requireMapItem<K, T>(map: ImmutableMap<K, T>, key: K, caller?: AnyCaller): T;
|
package/util/map.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { AssertionError } from "../error/AssertionError.js";
|
|
2
1
|
import { RequiredError } from "../error/RequiredError.js";
|
|
3
2
|
import { isIterable, limitItems } from "./iterate.js";
|
|
4
3
|
/** Class for a `Map` that cannot be changed (so you can extend `Map` while implementing `ImmutableMap`). */
|
|
@@ -8,18 +7,9 @@ export function isMap(value) {
|
|
|
8
7
|
return value instanceof Map;
|
|
9
8
|
}
|
|
10
9
|
/** Assert that a value is a `Map` instance. */
|
|
11
|
-
export function assertMap(value) {
|
|
10
|
+
export function assertMap(value, caller = assertMap) {
|
|
12
11
|
if (!isMap(value))
|
|
13
|
-
throw new
|
|
14
|
-
}
|
|
15
|
-
/** Is an unknown value a key for an item in a map? */
|
|
16
|
-
export function isMapItem(map, key) {
|
|
17
|
-
return map.has(key);
|
|
18
|
-
}
|
|
19
|
-
/** Assert that an unknown value is a key for an item in a map. */
|
|
20
|
-
export function assertMapItem(map, key) {
|
|
21
|
-
if (!isMapItem(map, key))
|
|
22
|
-
throw new AssertionError("Key must exist in map", { key, map, caller: assertMapItem });
|
|
12
|
+
throw new RequiredError("Must be map", { received: value, caller });
|
|
23
13
|
}
|
|
24
14
|
export function getMap(input) {
|
|
25
15
|
return isMap(input) ? input : new Map(isIterable(input) ? input : Object.entries(input));
|
|
@@ -28,6 +18,15 @@ export function getMap(input) {
|
|
|
28
18
|
export function limitMap(map, limit) {
|
|
29
19
|
return limit > map.size ? map : new Map(limitItems(map, limit));
|
|
30
20
|
}
|
|
21
|
+
/** Is an unknown value a key for an item in a map? */
|
|
22
|
+
export function isMapItem(map, key) {
|
|
23
|
+
return map.has(key);
|
|
24
|
+
}
|
|
25
|
+
/** Assert that an unknown value is a key for an item in a map. */
|
|
26
|
+
export function assertMapItem(map, key, caller = assertMapItem) {
|
|
27
|
+
if (!isMapItem(map, key))
|
|
28
|
+
throw new RequiredError("Key must exist in map", { key, map, caller });
|
|
29
|
+
}
|
|
31
30
|
/** Function that lets new items in a map be created and updated by calling a `reduce()` callback that receives the existing value. */
|
|
32
31
|
export function setMapItem(map, key, value) {
|
|
33
32
|
map.set(key, value);
|
|
@@ -43,13 +42,13 @@ export function removeMapItems(map, ...keys) {
|
|
|
43
42
|
for (const key of keys)
|
|
44
43
|
map.delete(key);
|
|
45
44
|
}
|
|
46
|
-
/** Get an item in a map, or throw `RequiredError` if it doesn't exist. */
|
|
47
|
-
export function requireMapItem(map, key) {
|
|
48
|
-
if (!map.has(key))
|
|
49
|
-
throw new RequiredError("Key must exist in map", { key, map, caller: requireMapItem });
|
|
50
|
-
return map.get(key);
|
|
51
|
-
}
|
|
52
45
|
/** Get an item in a map, or `undefined` if it doesn't exist. */
|
|
53
46
|
export function getMapItem(map, key) {
|
|
54
47
|
return map.get(key);
|
|
55
48
|
}
|
|
49
|
+
/** Get an item in a map, or throw `RequiredError` if it doesn't exist. */
|
|
50
|
+
export function requireMapItem(map, key, caller = requireMapItem) {
|
|
51
|
+
if (!map.has(key))
|
|
52
|
+
throw new RequiredError("Key must exist in map", { key, map, caller });
|
|
53
|
+
return map.get(key);
|
|
54
|
+
}
|