shelving 1.159.4 → 1.160.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/db/ValidationProvider.d.ts +1 -1
- package/db/ValidationProvider.js +16 -10
- package/package.json +1 -1
- package/util/jwt.d.ts +24 -1
- package/util/jwt.js +13 -8
- package/util/validate.d.ts +1 -5
- package/util/validate.js +41 -30
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type DataSchema, type DataSchemas } from "../schema/DataSchema.js";
|
|
2
2
|
import type { Schema } from "../schema/Schema.js";
|
|
3
3
|
import type { Database, DataKey } from "../util/data.js";
|
|
4
4
|
import type { Identifier, Items, OptionalItem } from "../util/item.js";
|
package/db/ValidationProvider.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ValueError } from "../error/ValueError.js";
|
|
2
2
|
import { Feedback } from "../feedback/Feedback.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { PARTIAL } from "../schema/DataSchema.js";
|
|
4
|
+
import { getNamedMessage } from "../util/error.js";
|
|
5
5
|
import { validateData } from "../util/validate.js";
|
|
6
6
|
import { AsyncThroughProvider, ThroughProvider } from "./ThroughProvider.js";
|
|
7
7
|
/** Validate a synchronous source provider (source can have any type because validation guarantees the type). */
|
|
@@ -32,8 +32,7 @@ export class ValidationProvider extends ThroughProvider {
|
|
|
32
32
|
super.setItem(collection, id, validateData(data, this.getSchema(collection).props));
|
|
33
33
|
}
|
|
34
34
|
updateItem(collection, id, updates) {
|
|
35
|
-
|
|
36
|
-
super.updateItem(collection, id, updates);
|
|
35
|
+
super.updateItem(collection, id, _validateUpdates(collection, updates, this.getSchema(collection), this.updateItem));
|
|
37
36
|
}
|
|
38
37
|
deleteItem(collection, id) {
|
|
39
38
|
super.deleteItem(collection, id);
|
|
@@ -48,8 +47,7 @@ export class ValidationProvider extends ThroughProvider {
|
|
|
48
47
|
super.setQuery(collection, query, this.getSchema(collection).validate(data));
|
|
49
48
|
}
|
|
50
49
|
updateQuery(collection, query, updates) {
|
|
51
|
-
|
|
52
|
-
super.updateQuery(collection, query, updates);
|
|
50
|
+
super.updateQuery(collection, query, _validateUpdates(collection, updates, this.getSchema(collection), this.updateQuery));
|
|
53
51
|
}
|
|
54
52
|
deleteQuery(collection, query) {
|
|
55
53
|
super.deleteQuery(collection, query);
|
|
@@ -88,8 +86,7 @@ export class AsyncValidationProvider extends AsyncThroughProvider {
|
|
|
88
86
|
return super.setItem(collection, id, validateData(data, this.getSchema(collection).props));
|
|
89
87
|
}
|
|
90
88
|
updateItem(collection, id, updates) {
|
|
91
|
-
|
|
92
|
-
return super.updateItem(collection, id, updates);
|
|
89
|
+
return super.updateItem(collection, id, _validateUpdates(collection, updates, this.getSchema(collection), this.updateItem));
|
|
93
90
|
}
|
|
94
91
|
deleteItem(collection, id) {
|
|
95
92
|
return super.deleteItem(collection, id);
|
|
@@ -109,8 +106,7 @@ export class AsyncValidationProvider extends AsyncThroughProvider {
|
|
|
109
106
|
return super.setQuery(collection, query, validateData(data, this.getSchema(collection).props));
|
|
110
107
|
}
|
|
111
108
|
updateQuery(collection, query, updates) {
|
|
112
|
-
|
|
113
|
-
return super.updateQuery(collection, query, updates);
|
|
109
|
+
return super.updateQuery(collection, query, _validateUpdates(collection, updates, this.getSchema(collection), this.updateQuery));
|
|
114
110
|
}
|
|
115
111
|
deleteQuery(collection, query) {
|
|
116
112
|
return super.deleteQuery(collection, query);
|
|
@@ -150,3 +146,13 @@ function* _yieldValidItems(collection, items, validators, caller) {
|
|
|
150
146
|
if (messages.length)
|
|
151
147
|
throw new ValueError(`Invalid data for "${collection}"\n${messages.join("\n")}`, { items, caller });
|
|
152
148
|
}
|
|
149
|
+
function _validateUpdates(collection, updates, schema, caller) {
|
|
150
|
+
try {
|
|
151
|
+
return validateData(updates, PARTIAL(schema).props);
|
|
152
|
+
}
|
|
153
|
+
catch (thrown) {
|
|
154
|
+
if (!(thrown instanceof Feedback))
|
|
155
|
+
throw thrown;
|
|
156
|
+
throw new ValueError(`Invalid updates for "${collection}"\n${thrown.message}`, { updates, caller });
|
|
157
|
+
}
|
|
158
|
+
}
|
package/package.json
CHANGED
package/util/jwt.d.ts
CHANGED
|
@@ -1,13 +1,36 @@
|
|
|
1
1
|
import { type Bytes, type PossibleBytes } from "./bytes.js";
|
|
2
2
|
import type { Data } from "./data.js";
|
|
3
|
+
import { type PossibleDate } from "./date.js";
|
|
3
4
|
import type { AnyCaller } from "./function.js";
|
|
5
|
+
export interface TokenClaims extends Data {
|
|
6
|
+
/**
|
|
7
|
+
* "Issued at" date (defaults to "now").
|
|
8
|
+
* - Not used for validation, but always set in the token payload.
|
|
9
|
+
* - Can be used to determine when the token was issued, and possibly revoke tokens issued before a certain date.
|
|
10
|
+
*/
|
|
11
|
+
readonly iat?: PossibleDate;
|
|
12
|
+
/**
|
|
13
|
+
* "Not before" date.
|
|
14
|
+
* - When validating the token, tokens before this date will be rejected
|
|
15
|
+
*/
|
|
16
|
+
readonly nbf?: PossibleDate;
|
|
17
|
+
/**
|
|
18
|
+
* Expiry in milliseconds (defaults to "30 days").
|
|
19
|
+
* - When validating the token, tokens after this date will be rejected
|
|
20
|
+
*/
|
|
21
|
+
readonly exp?: number;
|
|
22
|
+
}
|
|
4
23
|
/**
|
|
5
24
|
* Encode a JWT and return the string token.
|
|
6
25
|
* - Currently only supports HMAC SHA-512 signing.
|
|
7
26
|
*
|
|
27
|
+
* @param claims The payload claims to include in the JWT.
|
|
28
|
+
* @param secret The secret key to sign the JWT with.
|
|
29
|
+
* @param expiry The expiry time in milliseconds (defaults to 30 days).
|
|
30
|
+
*
|
|
8
31
|
* @throws ValueError If the input parameters, e.g. `secret` or `issuer`, are invalid.
|
|
9
32
|
*/
|
|
10
|
-
export declare function encodeToken(claims:
|
|
33
|
+
export declare function encodeToken({ nbf, iat, exp, ...claims }: TokenClaims, secret: PossibleBytes): Promise<string>;
|
|
11
34
|
/** Parts that make up a JSON Web Token. */
|
|
12
35
|
export type TokenData = {
|
|
13
36
|
header: string;
|
package/util/jwt.js
CHANGED
|
@@ -3,11 +3,11 @@ import { ValueError } from "../error/ValueError.js";
|
|
|
3
3
|
import { decodeBase64URLBytes, decodeBase64URLString, encodeBase64URL } from "./base64.js";
|
|
4
4
|
import { getBytes, requireBytes } from "./bytes.js";
|
|
5
5
|
import { DAY, MINUTE, SECOND } from "./constants.js";
|
|
6
|
+
import { requireDate } from "./date.js";
|
|
6
7
|
// Constants.
|
|
7
8
|
const HASH = "SHA-512";
|
|
8
9
|
const ALGORITHM = { name: "HMAC", hash: HASH };
|
|
9
10
|
const HEADER = { alg: "HS512", typ: "JWT" };
|
|
10
|
-
const EXPIRY_MS = DAY * 10;
|
|
11
11
|
const SKEW_MS = MINUTE; // Allow 1 minute clock skew.
|
|
12
12
|
const SECRET_BYTES = 64; // Minimum 64 bytes / 512 bits
|
|
13
13
|
function _getKey(caller, secret, ...usages) {
|
|
@@ -23,15 +23,22 @@ function _getKey(caller, secret, ...usages) {
|
|
|
23
23
|
* Encode a JWT and return the string token.
|
|
24
24
|
* - Currently only supports HMAC SHA-512 signing.
|
|
25
25
|
*
|
|
26
|
+
* @param claims The payload claims to include in the JWT.
|
|
27
|
+
* @param secret The secret key to sign the JWT with.
|
|
28
|
+
* @param expiry The expiry time in milliseconds (defaults to 30 days).
|
|
29
|
+
*
|
|
26
30
|
* @throws ValueError If the input parameters, e.g. `secret` or `issuer`, are invalid.
|
|
27
31
|
*/
|
|
28
|
-
export async function encodeToken(claims, secret) {
|
|
32
|
+
export async function encodeToken({ nbf = "now", iat = "now", exp = DAY * 30, ...claims }, secret) {
|
|
29
33
|
// Encode header.
|
|
30
34
|
const header = encodeBase64URL(JSON.stringify(HEADER));
|
|
31
35
|
// Encode payload.
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
36
|
+
const payload = encodeBase64URL(JSON.stringify({
|
|
37
|
+
nbf: requireDate(nbf).getTime() / 1000, // By JWT convention, times are in seconds.
|
|
38
|
+
iat: requireDate(iat).getTime() / 1000, // By JWT convention, times are in seconds.
|
|
39
|
+
exp: (Date.now() + exp) / 1000, // By JWT convention, times are in seconds.
|
|
40
|
+
...claims,
|
|
41
|
+
}));
|
|
35
42
|
// Create signature.
|
|
36
43
|
const key = await _getKey(encodeToken, secret, "sign");
|
|
37
44
|
const signature = encodeBase64URL(await crypto.subtle.sign("HMAC", key, requireBytes(`${header}.${payload}`)));
|
|
@@ -93,12 +100,10 @@ export async function verifyToken(token, secret, caller = verifyToken) {
|
|
|
93
100
|
if (!isValid)
|
|
94
101
|
throw new UnauthorizedError("JWT signature does not match", { received: token, caller });
|
|
95
102
|
// Validate payload.
|
|
96
|
-
const { nbf,
|
|
103
|
+
const { nbf, exp } = payloadData;
|
|
97
104
|
const now = Date.now();
|
|
98
105
|
if (typeof nbf === "number" && now < nbf * SECOND - SKEW_MS)
|
|
99
106
|
throw new UnauthorizedError("JWT cannot be used yet", { received: payloadData, expected: now, caller });
|
|
100
|
-
if (typeof iat === "number" && now < iat * SECOND - SKEW_MS)
|
|
101
|
-
throw new UnauthorizedError("JWT not issued yet", { received: payloadData, expected: now, caller });
|
|
102
107
|
if (typeof exp === "number" && now > exp * SECOND + SKEW_MS)
|
|
103
108
|
throw new UnauthorizedError("JWT has expired", { received: payloadData, expected: now, caller });
|
|
104
109
|
return payloadData;
|
package/util/validate.d.ts
CHANGED
|
@@ -4,7 +4,6 @@ import type { Constructor } from "./class.js";
|
|
|
4
4
|
import type { Data } from "./data.js";
|
|
5
5
|
import type { ImmutableDictionary } from "./dictionary.js";
|
|
6
6
|
import type { AnyCaller } from "./function.js";
|
|
7
|
-
import type { DeepPartial } from "./object.js";
|
|
8
7
|
/** Object that can validate an unknown value with its `validate()` method. */
|
|
9
8
|
export interface Validator<T> {
|
|
10
9
|
/**
|
|
@@ -57,10 +56,7 @@ export declare function validateDictionary<T>(unsafeDictionary: ImmutableDiction
|
|
|
57
56
|
* - `undefined` props in the object will be set to the default value of that prop.
|
|
58
57
|
* - `undefined` props after validation will not be set in the output object.
|
|
59
58
|
*
|
|
60
|
-
* @param partial Whether we're validating a partial match or not. This allows props to be missing without error.
|
|
61
|
-
*
|
|
62
59
|
* @return Valid object.
|
|
63
60
|
* @throw Feedback if one or more props did not validate.
|
|
64
61
|
*/
|
|
65
|
-
export declare function validateData<T extends Data>(unsafeData: Data, validators: Validators<T
|
|
66
|
-
export declare function validateData<T extends Data>(unsafeData: Data, validators: Validators<T>, partial?: false): T;
|
|
62
|
+
export declare function validateData<T extends Data>(unsafeData: Data, validators: Validators<T>): T;
|
package/util/validate.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ValueError } from "../error/ValueError.js";
|
|
2
2
|
import { Feedback, ValueFeedback } from "../feedback/Feedback.js";
|
|
3
3
|
import { isArray } from "./array.js";
|
|
4
|
-
import {
|
|
4
|
+
import { getDataProps } from "./data.js";
|
|
5
5
|
import { getDictionaryItems } from "./dictionary.js";
|
|
6
6
|
import { getNamedMessage } from "./error.js";
|
|
7
7
|
import { isIterable } from "./iterate.js";
|
|
@@ -96,39 +96,50 @@ export function validateDictionary(unsafeDictionary, validator) {
|
|
|
96
96
|
throw new ValueFeedback(messages.join("\n"), unsafeDictionary);
|
|
97
97
|
return changed || isIterable(unsafeDictionary) ? safeDictionary : unsafeDictionary;
|
|
98
98
|
}
|
|
99
|
-
/**
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
99
|
+
/**
|
|
100
|
+
* Validate a data object with a set of validators.
|
|
101
|
+
* - Defined props in the object will be validated against the corresponding validator.
|
|
102
|
+
* - `undefined` props in the object will be set to the default value of that prop.
|
|
103
|
+
* - `undefined` props after validation will not be set in the output object.
|
|
104
|
+
*
|
|
105
|
+
* @return Valid object.
|
|
106
|
+
* @throw Feedback if one or more props did not validate.
|
|
107
|
+
*/
|
|
108
|
+
export function validateData(unsafeData, validators) {
|
|
109
|
+
let changes = 0;
|
|
103
110
|
const safeData = {};
|
|
104
111
|
const messages = [];
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
safeData[key] = safeValue;
|
|
116
|
-
if (!changed && safeValue !== unsafeValue)
|
|
117
|
-
changed = true;
|
|
112
|
+
// Validate the props in `validators`.
|
|
113
|
+
const props = getDataProps(validators);
|
|
114
|
+
for (const [key, validator] of props) {
|
|
115
|
+
const unsafeValue = unsafeData[key];
|
|
116
|
+
try {
|
|
117
|
+
const safeValue = validator.validate(unsafeValue);
|
|
118
|
+
if (safeValue === undefined) {
|
|
119
|
+
// Undefined values are not included in the output object
|
|
120
|
+
if (key in unsafeData)
|
|
121
|
+
changes++;
|
|
118
122
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
+
else {
|
|
124
|
+
// Defined values are included in the output object.
|
|
125
|
+
safeData[key] = safeValue;
|
|
126
|
+
if (safeValue !== unsafeValue)
|
|
127
|
+
changes++;
|
|
123
128
|
}
|
|
124
129
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
}
|
|
131
|
-
finally {
|
|
132
|
-
isDeeplyPartial = false;
|
|
130
|
+
catch (thrown) {
|
|
131
|
+
if (!(thrown instanceof Feedback))
|
|
132
|
+
throw thrown;
|
|
133
|
+
messages.push(getNamedMessage(key, thrown.message));
|
|
134
|
+
}
|
|
133
135
|
}
|
|
136
|
+
if (messages.length)
|
|
137
|
+
throw new ValueFeedback(messages.join("\n"), unsafeData);
|
|
138
|
+
if (changes)
|
|
139
|
+
return safeData;
|
|
140
|
+
// Check that no excess keys exist.
|
|
141
|
+
for (const key of Object.keys(unsafeData))
|
|
142
|
+
if (!(key in validators))
|
|
143
|
+
return safeData;
|
|
144
|
+
return unsafeData;
|
|
134
145
|
}
|