@ruiapp/rapid-core 0.10.11 → 0.10.12
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/dist/core/response.d.ts +2 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +548 -544
- package/package.json +1 -1
- package/src/core/response.ts +5 -0
- package/src/index.ts +1 -0
package/dist/core/response.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { HttpStatus } from "./http-types";
|
|
2
|
+
import { Cookie } from "../deno-std/http/cookie";
|
|
2
3
|
export declare const GlobalResponse: {
|
|
3
4
|
new (body?: BodyInit, init?: ResponseInit): Response;
|
|
4
5
|
prototype: Response;
|
|
@@ -13,5 +14,6 @@ export declare class RapidResponse {
|
|
|
13
14
|
constructor(body?: BodyInit, init?: ResponseInit);
|
|
14
15
|
json(obj: any, status?: HttpStatus, headers?: HeadersInit): void;
|
|
15
16
|
redirect(location: string, status?: HttpStatus): void;
|
|
17
|
+
setCookie(cookie: Cookie): void;
|
|
16
18
|
getResponse(): Response;
|
|
17
19
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -37,6 +37,7 @@ export * from "./plugins/sequence/SequencePluginTypes";
|
|
|
37
37
|
export { default as WebhooksPlugin } from "./plugins/webhooks/WebhooksPlugin";
|
|
38
38
|
export { default as AuthPlugin } from "./plugins/auth/AuthPlugin";
|
|
39
39
|
export * from "./plugins/auth/AuthPluginTypes";
|
|
40
|
+
export { default as AuthService } from "./plugins/auth/services/AuthService";
|
|
40
41
|
export { default as FileManagePlugin } from "./plugins/fileManage/FileManagePlugin";
|
|
41
42
|
export { default as LicensePlugin } from "./plugins/license/LicensePlugin";
|
|
42
43
|
export * from "./plugins/license/LicensePluginTypes";
|
package/dist/index.js
CHANGED
|
@@ -1072,158 +1072,509 @@ async function buildRoutes(server, applicationConfig) {
|
|
|
1072
1072
|
return router.routes();
|
|
1073
1073
|
}
|
|
1074
1074
|
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1075
|
+
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
|
1076
|
+
class AssertionError extends Error {
|
|
1077
|
+
name = "AssertionError";
|
|
1078
|
+
constructor(message) {
|
|
1079
|
+
super(message);
|
|
1080
1080
|
}
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
|
1084
|
+
/** Make an assertion, error will be thrown if `expr` does not have truthy value. */
|
|
1085
|
+
function assert(expr, msg = "") {
|
|
1086
|
+
if (!expr) {
|
|
1087
|
+
throw new AssertionError(msg);
|
|
1085
1088
|
}
|
|
1086
|
-
|
|
1087
|
-
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
|
1092
|
+
// This module is browser compatible.
|
|
1093
|
+
/**
|
|
1094
|
+
* Formats the given date to IMF date time format. (Reference:
|
|
1095
|
+
* https://tools.ietf.org/html/rfc7231#section-7.1.1.1).
|
|
1096
|
+
* IMF is the time format to use when generating times in HTTP
|
|
1097
|
+
* headers. The time being formatted must be in UTC for Format to
|
|
1098
|
+
* generate the correct format.
|
|
1099
|
+
*
|
|
1100
|
+
* @example
|
|
1101
|
+
* ```ts
|
|
1102
|
+
* import { toIMF } from "https://deno.land/std@$STD_VERSION/datetime/to_imf.ts";
|
|
1103
|
+
*
|
|
1104
|
+
* toIMF(new Date(0)); // => returns "Thu, 01 Jan 1970 00:00:00 GMT"
|
|
1105
|
+
* ```
|
|
1106
|
+
* @param date Date to parse
|
|
1107
|
+
* @return IMF date formatted string
|
|
1108
|
+
*/
|
|
1109
|
+
function toIMF(date) {
|
|
1110
|
+
function dtPad(v, lPad = 2) {
|
|
1111
|
+
return v.padStart(lPad, "0");
|
|
1088
1112
|
}
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
this.headers = new Headers(init?.headers);
|
|
1105
|
-
this.status = init?.status;
|
|
1113
|
+
const d = dtPad(date.getUTCDate().toString());
|
|
1114
|
+
const h = dtPad(date.getUTCHours().toString());
|
|
1115
|
+
const min = dtPad(date.getUTCMinutes().toString());
|
|
1116
|
+
const s = dtPad(date.getUTCSeconds().toString());
|
|
1117
|
+
const y = date.getUTCFullYear();
|
|
1118
|
+
const days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
|
1119
|
+
const months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
|
1120
|
+
return `${days[date.getUTCDay()]}, ${d} ${months[date.getUTCMonth()]} ${y} ${h}:${min}:${s} GMT`;
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
|
1124
|
+
const FIELD_CONTENT_REGEXP = /^(?=[\x20-\x7E]*$)[^()@<>,;:\\"\[\]?={}\s]+$/;
|
|
1125
|
+
function toString(cookie) {
|
|
1126
|
+
if (!cookie.name) {
|
|
1127
|
+
return "";
|
|
1106
1128
|
}
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
mergeHeaders(responseHeaders, headers);
|
|
1116
|
-
}
|
|
1117
|
-
this.status = status || 200;
|
|
1118
|
-
this.body = body;
|
|
1119
|
-
this.#response = newResponse({ body, status: this.status, headers: responseHeaders });
|
|
1129
|
+
const out = [];
|
|
1130
|
+
validateName(cookie.name);
|
|
1131
|
+
validateValue(cookie.name, cookie.value);
|
|
1132
|
+
out.push(`${cookie.name}=${cookie.value}`);
|
|
1133
|
+
// Fallback for invalid Set-Cookie
|
|
1134
|
+
// ref: https://tools.ietf.org/html/draft-ietf-httpbis-cookie-prefixes-00#section-3.1
|
|
1135
|
+
if (cookie.name.startsWith("__Secure")) {
|
|
1136
|
+
cookie.secure = true;
|
|
1120
1137
|
}
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
headers: this.headers,
|
|
1126
|
-
status: this.status,
|
|
1127
|
-
});
|
|
1138
|
+
if (cookie.name.startsWith("__Host")) {
|
|
1139
|
+
cookie.path = "/";
|
|
1140
|
+
cookie.secure = true;
|
|
1141
|
+
delete cookie.domain;
|
|
1128
1142
|
}
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
this.#response = new Response(this.body, {
|
|
1132
|
-
status: this.status || 200,
|
|
1133
|
-
headers: this.headers,
|
|
1134
|
-
});
|
|
1135
|
-
}
|
|
1136
|
-
return this.#response;
|
|
1143
|
+
if (cookie.secure) {
|
|
1144
|
+
out.push("Secure");
|
|
1137
1145
|
}
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
// TODO: should divide to RequestContext and OperationContext
|
|
1141
|
-
class RouteContext {
|
|
1142
|
-
request;
|
|
1143
|
-
response;
|
|
1144
|
-
state;
|
|
1145
|
-
databaseAccessor;
|
|
1146
|
-
method;
|
|
1147
|
-
path;
|
|
1148
|
-
params;
|
|
1149
|
-
routeConfig;
|
|
1150
|
-
#server;
|
|
1151
|
-
#dbTransactionClient;
|
|
1152
|
-
#dbTransactionState;
|
|
1153
|
-
static newSystemOperationContext(server) {
|
|
1154
|
-
return new RouteContext(server);
|
|
1146
|
+
if (cookie.httpOnly) {
|
|
1147
|
+
out.push("HttpOnly");
|
|
1155
1148
|
}
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
this.#dbTransactionState = "uninited";
|
|
1160
|
-
this.request = request;
|
|
1161
|
-
this.state = {};
|
|
1162
|
-
this.response = new RapidResponse();
|
|
1163
|
-
// `method` and `path` are used by `koa-tree-router` to match route
|
|
1164
|
-
if (this.request) {
|
|
1165
|
-
this.method = request.method;
|
|
1166
|
-
this.path = request.url.pathname;
|
|
1167
|
-
}
|
|
1149
|
+
if (typeof cookie.maxAge === "number" && Number.isInteger(cookie.maxAge)) {
|
|
1150
|
+
assert(cookie.maxAge >= 0, "Max-Age must be an integer superior or equal to 0");
|
|
1151
|
+
out.push(`Max-Age=${cookie.maxAge}`);
|
|
1168
1152
|
}
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
clonedContext.path = this.path;
|
|
1173
|
-
clonedContext.params = this.params;
|
|
1174
|
-
clonedContext.setState(this.state);
|
|
1175
|
-
return clonedContext;
|
|
1153
|
+
if (cookie.domain) {
|
|
1154
|
+
validateDomain(cookie.domain);
|
|
1155
|
+
out.push(`Domain=${cookie.domain}`);
|
|
1176
1156
|
}
|
|
1177
|
-
|
|
1178
|
-
|
|
1157
|
+
if (cookie.sameSite) {
|
|
1158
|
+
out.push(`SameSite=${cookie.sameSite}`);
|
|
1179
1159
|
}
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1160
|
+
if (cookie.path) {
|
|
1161
|
+
validatePath(cookie.path);
|
|
1162
|
+
out.push(`Path=${cookie.path}`);
|
|
1183
1163
|
}
|
|
1184
|
-
|
|
1185
|
-
|
|
1164
|
+
if (cookie.expires) {
|
|
1165
|
+
const { expires } = cookie;
|
|
1166
|
+
const dateString = toIMF(typeof expires === "number" ? new Date(expires) : expires);
|
|
1167
|
+
out.push(`Expires=${dateString}`);
|
|
1186
1168
|
}
|
|
1187
|
-
|
|
1188
|
-
|
|
1169
|
+
if (cookie.unparsed) {
|
|
1170
|
+
out.push(cookie.unparsed.join("; "));
|
|
1189
1171
|
}
|
|
1190
|
-
|
|
1191
|
-
|
|
1172
|
+
return out.join("; ");
|
|
1173
|
+
}
|
|
1174
|
+
/**
|
|
1175
|
+
* Validate Cookie Name.
|
|
1176
|
+
* @param name Cookie name.
|
|
1177
|
+
*/
|
|
1178
|
+
function validateName(name) {
|
|
1179
|
+
if (name && !FIELD_CONTENT_REGEXP.test(name)) {
|
|
1180
|
+
throw new TypeError(`Invalid cookie name: "${name}".`);
|
|
1192
1181
|
}
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
return
|
|
1182
|
+
}
|
|
1183
|
+
/**
|
|
1184
|
+
* Validate Path Value.
|
|
1185
|
+
* See {@link https://tools.ietf.org/html/rfc6265#section-4.1.2.4}.
|
|
1186
|
+
* @param path Path value.
|
|
1187
|
+
*/
|
|
1188
|
+
function validatePath(path) {
|
|
1189
|
+
if (path == null) {
|
|
1190
|
+
return;
|
|
1202
1191
|
}
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
if (this.#dbTransactionState === "started") {
|
|
1208
|
-
throw new Error("Database transaction has been started. You can not begin a new transaction before you commit or rollback it.");
|
|
1192
|
+
for (let i = 0; i < path.length; i++) {
|
|
1193
|
+
const c = path.charAt(i);
|
|
1194
|
+
if (c < String.fromCharCode(0x20) || c > String.fromCharCode(0x7e) || c == ";") {
|
|
1195
|
+
throw new Error(path + ": Invalid cookie path char '" + c + "'");
|
|
1209
1196
|
}
|
|
1210
|
-
await this.databaseAccessor.queryDatabaseObject("BEGIN", [], this.#dbTransactionClient);
|
|
1211
|
-
this.#dbTransactionState = "started";
|
|
1212
1197
|
}
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1198
|
+
}
|
|
1199
|
+
/**
|
|
1200
|
+
* Validate Cookie Value.
|
|
1201
|
+
* See {@link https://tools.ietf.org/html/rfc6265#section-4.1}.
|
|
1202
|
+
* @param value Cookie value.
|
|
1203
|
+
*/
|
|
1204
|
+
function validateValue(name, value) {
|
|
1205
|
+
if (value == null || name == null)
|
|
1206
|
+
return;
|
|
1207
|
+
for (let i = 0; i < value.length; i++) {
|
|
1208
|
+
const c = value.charAt(i);
|
|
1209
|
+
if (c < String.fromCharCode(0x21) ||
|
|
1210
|
+
c == String.fromCharCode(0x22) ||
|
|
1211
|
+
c == String.fromCharCode(0x2c) ||
|
|
1212
|
+
c == String.fromCharCode(0x3b) ||
|
|
1213
|
+
c == String.fromCharCode(0x5c) ||
|
|
1214
|
+
c == String.fromCharCode(0x7f)) {
|
|
1215
|
+
throw new Error("RFC2616 cookie '" + name + "' cannot contain character '" + c + "'");
|
|
1216
1216
|
}
|
|
1217
|
-
if (
|
|
1218
|
-
throw new Error("
|
|
1217
|
+
if (c > String.fromCharCode(0x80)) {
|
|
1218
|
+
throw new Error("RFC2616 cookie '" + name + "' can only have US-ASCII chars as value" + c.charCodeAt(0).toString(16));
|
|
1219
1219
|
}
|
|
1220
|
-
await this.databaseAccessor.queryDatabaseObject("COMMIT", [], this.#dbTransactionClient);
|
|
1221
|
-
this.#dbTransactionState = "inited";
|
|
1222
1220
|
}
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1221
|
+
}
|
|
1222
|
+
/**
|
|
1223
|
+
* Validate Cookie Domain.
|
|
1224
|
+
* See {@link https://datatracker.ietf.org/doc/html/rfc6265#section-4.1.2.3}.
|
|
1225
|
+
* @param domain Cookie domain.
|
|
1226
|
+
*/
|
|
1227
|
+
function validateDomain(domain) {
|
|
1228
|
+
if (domain == null) {
|
|
1229
|
+
return;
|
|
1230
|
+
}
|
|
1231
|
+
const char1 = domain.charAt(0);
|
|
1232
|
+
const charN = domain.charAt(domain.length - 1);
|
|
1233
|
+
if (char1 == "-" || charN == "." || charN == "-") {
|
|
1234
|
+
throw new Error("Invalid first/last char in cookie domain: " + domain);
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
/**
|
|
1238
|
+
* Parse cookies of a header
|
|
1239
|
+
*
|
|
1240
|
+
* @example
|
|
1241
|
+
* ```ts
|
|
1242
|
+
* import { getCookies } from "https://deno.land/std@$STD_VERSION/http/cookie.ts";
|
|
1243
|
+
*
|
|
1244
|
+
* const headers = new Headers();
|
|
1245
|
+
* headers.set("Cookie", "full=of; tasty=chocolate");
|
|
1246
|
+
*
|
|
1247
|
+
* const cookies = getCookies(headers);
|
|
1248
|
+
* console.log(cookies); // { full: "of", tasty: "chocolate" }
|
|
1249
|
+
* ```
|
|
1250
|
+
*
|
|
1251
|
+
* @param headers The headers instance to get cookies from
|
|
1252
|
+
* @return Object with cookie names as keys
|
|
1253
|
+
*/
|
|
1254
|
+
function getCookies(headers) {
|
|
1255
|
+
const cookie = headers.get("Cookie");
|
|
1256
|
+
if (cookie != null) {
|
|
1257
|
+
const out = {};
|
|
1258
|
+
const c = cookie.split(";");
|
|
1259
|
+
for (const kv of c) {
|
|
1260
|
+
const [cookieKey, ...cookieVal] = kv.split("=");
|
|
1261
|
+
assert(cookieKey != null);
|
|
1262
|
+
const key = cookieKey.trim();
|
|
1263
|
+
out[key] = cookieVal.join("=");
|
|
1264
|
+
}
|
|
1265
|
+
return out;
|
|
1266
|
+
}
|
|
1267
|
+
return {};
|
|
1268
|
+
}
|
|
1269
|
+
/**
|
|
1270
|
+
* Set the cookie header properly in the headers
|
|
1271
|
+
*
|
|
1272
|
+
* @example
|
|
1273
|
+
* ```ts
|
|
1274
|
+
* import {
|
|
1275
|
+
* Cookie,
|
|
1276
|
+
* setCookie,
|
|
1277
|
+
* } from "https://deno.land/std@$STD_VERSION/http/cookie.ts";
|
|
1278
|
+
*
|
|
1279
|
+
* const headers = new Headers();
|
|
1280
|
+
* const cookie: Cookie = { name: "Space", value: "Cat" };
|
|
1281
|
+
* setCookie(headers, cookie);
|
|
1282
|
+
*
|
|
1283
|
+
* const cookieHeader = headers.get("set-cookie");
|
|
1284
|
+
* console.log(cookieHeader); // Space=Cat
|
|
1285
|
+
* ```
|
|
1286
|
+
*
|
|
1287
|
+
* @param headers The headers instance to set the cookie to
|
|
1288
|
+
* @param cookie Cookie to set
|
|
1289
|
+
*/
|
|
1290
|
+
function setCookie(headers, cookie) {
|
|
1291
|
+
// Parsing cookie headers to make consistent set-cookie header
|
|
1292
|
+
// ref: https://tools.ietf.org/html/rfc6265#section-4.1.1
|
|
1293
|
+
const v = toString(cookie);
|
|
1294
|
+
if (v) {
|
|
1295
|
+
headers.append("Set-Cookie", v);
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
/**
|
|
1299
|
+
* Set the cookie header with empty value in the headers to delete it
|
|
1300
|
+
*
|
|
1301
|
+
* > Note: Deleting a `Cookie` will set its expiration date before now. Forcing
|
|
1302
|
+
* > the browser to delete it.
|
|
1303
|
+
*
|
|
1304
|
+
* @example
|
|
1305
|
+
* ```ts
|
|
1306
|
+
* import { deleteCookie } from "https://deno.land/std@$STD_VERSION/http/cookie.ts";
|
|
1307
|
+
*
|
|
1308
|
+
* const headers = new Headers();
|
|
1309
|
+
* deleteCookie(headers, "deno");
|
|
1310
|
+
*
|
|
1311
|
+
* const cookieHeader = headers.get("set-cookie");
|
|
1312
|
+
* console.log(cookieHeader); // deno=; Expires=Thus, 01 Jan 1970 00:00:00 GMT
|
|
1313
|
+
* ```
|
|
1314
|
+
*
|
|
1315
|
+
* @param headers The headers instance to delete the cookie from
|
|
1316
|
+
* @param name Name of cookie
|
|
1317
|
+
* @param attributes Additional cookie attributes
|
|
1318
|
+
*/
|
|
1319
|
+
function deleteCookie(headers, name, attributes) {
|
|
1320
|
+
setCookie(headers, {
|
|
1321
|
+
name: name,
|
|
1322
|
+
value: "",
|
|
1323
|
+
expires: new Date(0),
|
|
1324
|
+
...attributes,
|
|
1325
|
+
});
|
|
1326
|
+
}
|
|
1327
|
+
function parseSetCookie(value) {
|
|
1328
|
+
const attrs = value.split(";").map((attr) => {
|
|
1329
|
+
const [key, ...values] = attr.trim().split("=");
|
|
1330
|
+
return [key, values.join("=")];
|
|
1331
|
+
});
|
|
1332
|
+
const cookie = {
|
|
1333
|
+
name: attrs[0][0],
|
|
1334
|
+
value: attrs[0][1],
|
|
1335
|
+
};
|
|
1336
|
+
for (const [key, value] of attrs.slice(1)) {
|
|
1337
|
+
switch (key.toLocaleLowerCase()) {
|
|
1338
|
+
case "expires":
|
|
1339
|
+
cookie.expires = new Date(value);
|
|
1340
|
+
break;
|
|
1341
|
+
case "max-age":
|
|
1342
|
+
cookie.maxAge = Number(value);
|
|
1343
|
+
if (cookie.maxAge < 0) {
|
|
1344
|
+
console.warn("Max-Age must be an integer superior or equal to 0. Cookie ignored.");
|
|
1345
|
+
return null;
|
|
1346
|
+
}
|
|
1347
|
+
break;
|
|
1348
|
+
case "domain":
|
|
1349
|
+
cookie.domain = value;
|
|
1350
|
+
break;
|
|
1351
|
+
case "path":
|
|
1352
|
+
cookie.path = value;
|
|
1353
|
+
break;
|
|
1354
|
+
case "secure":
|
|
1355
|
+
cookie.secure = true;
|
|
1356
|
+
break;
|
|
1357
|
+
case "httponly":
|
|
1358
|
+
cookie.httpOnly = true;
|
|
1359
|
+
break;
|
|
1360
|
+
case "samesite":
|
|
1361
|
+
cookie.sameSite = value;
|
|
1362
|
+
break;
|
|
1363
|
+
default:
|
|
1364
|
+
if (!Array.isArray(cookie.unparsed)) {
|
|
1365
|
+
cookie.unparsed = [];
|
|
1366
|
+
}
|
|
1367
|
+
cookie.unparsed.push([key, value].join("="));
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
if (cookie.name.startsWith("__Secure-")) {
|
|
1371
|
+
/** This requirement is mentioned in https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie but not the RFC. */
|
|
1372
|
+
if (!cookie.secure) {
|
|
1373
|
+
console.warn("Cookies with names starting with `__Secure-` must be set with the secure flag. Cookie ignored.");
|
|
1374
|
+
return null;
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
if (cookie.name.startsWith("__Host-")) {
|
|
1378
|
+
if (!cookie.secure) {
|
|
1379
|
+
console.warn("Cookies with names starting with `__Host-` must be set with the secure flag. Cookie ignored.");
|
|
1380
|
+
return null;
|
|
1381
|
+
}
|
|
1382
|
+
if (cookie.domain !== undefined) {
|
|
1383
|
+
console.warn("Cookies with names starting with `__Host-` must not have a domain specified. Cookie ignored.");
|
|
1384
|
+
return null;
|
|
1385
|
+
}
|
|
1386
|
+
if (cookie.path !== "/") {
|
|
1387
|
+
console.warn("Cookies with names starting with `__Host-` must have path be `/`. Cookie has been ignored.");
|
|
1388
|
+
return null;
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
return cookie;
|
|
1392
|
+
}
|
|
1393
|
+
/**
|
|
1394
|
+
* Parse set-cookies of a header
|
|
1395
|
+
*
|
|
1396
|
+
* @example
|
|
1397
|
+
* ```ts
|
|
1398
|
+
* import { getSetCookies } from "https://deno.land/std@$STD_VERSION/http/cookie.ts";
|
|
1399
|
+
*
|
|
1400
|
+
* const headers = new Headers([
|
|
1401
|
+
* ["Set-Cookie", "lulu=meow; Secure; Max-Age=3600"],
|
|
1402
|
+
* ["Set-Cookie", "booya=kasha; HttpOnly; Path=/"],
|
|
1403
|
+
* ]);
|
|
1404
|
+
*
|
|
1405
|
+
* const cookies = getSetCookies(headers);
|
|
1406
|
+
* console.log(cookies); // [{ name: "lulu", value: "meow", secure: true, maxAge: 3600 }, { name: "booya", value: "kahsa", httpOnly: true, path: "/ }]
|
|
1407
|
+
* ```
|
|
1408
|
+
*
|
|
1409
|
+
* @param headers The headers instance to get set-cookies from
|
|
1410
|
+
* @return List of cookies
|
|
1411
|
+
*/
|
|
1412
|
+
function getSetCookies(headers) {
|
|
1413
|
+
// TODO(lino-levan): remove this ts-ignore when Typescript 5.2 lands in Deno
|
|
1414
|
+
// @ts-ignore Typescript's TS Dom types will be out of date until 5.2
|
|
1415
|
+
return headers
|
|
1416
|
+
.getSetCookie()
|
|
1417
|
+
/** Parse each `set-cookie` header separately */
|
|
1418
|
+
.map(parseSetCookie)
|
|
1419
|
+
/** Skip empty cookies */
|
|
1420
|
+
.filter(Boolean);
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
function mergeHeaders(target, source) {
|
|
1424
|
+
if (source instanceof Headers) {
|
|
1425
|
+
for (const keyValuePair of source.entries()) {
|
|
1426
|
+
target.set(keyValuePair[0], keyValuePair[1]);
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
else if (lodash.isArray(source)) {
|
|
1430
|
+
for (const keyValuePair of source) {
|
|
1431
|
+
target.set(keyValuePair[0], keyValuePair[1]);
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
else if (lodash.isObject(source)) {
|
|
1435
|
+
Object.entries(source).forEach(([key, value]) => target.set(key, value));
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
function newResponse(options) {
|
|
1439
|
+
return new Response(options.body, {
|
|
1440
|
+
headers: options.headers,
|
|
1441
|
+
status: options.status || 200,
|
|
1442
|
+
});
|
|
1443
|
+
}
|
|
1444
|
+
class RapidResponse {
|
|
1445
|
+
// TODO: remove this field.
|
|
1446
|
+
#response;
|
|
1447
|
+
status;
|
|
1448
|
+
body;
|
|
1449
|
+
headers;
|
|
1450
|
+
constructor(body, init) {
|
|
1451
|
+
this.body = body;
|
|
1452
|
+
this.headers = new Headers(init?.headers);
|
|
1453
|
+
this.status = init?.status;
|
|
1454
|
+
}
|
|
1455
|
+
json(obj, status, headers) {
|
|
1456
|
+
let body = null;
|
|
1457
|
+
if (obj) {
|
|
1458
|
+
body = JSON.stringify(obj);
|
|
1459
|
+
}
|
|
1460
|
+
this.headers.set("Content-Type", "application/json");
|
|
1461
|
+
const responseHeaders = new Headers(this.headers);
|
|
1462
|
+
if (headers) {
|
|
1463
|
+
mergeHeaders(responseHeaders, headers);
|
|
1464
|
+
}
|
|
1465
|
+
this.status = status || 200;
|
|
1466
|
+
this.body = body;
|
|
1467
|
+
this.#response = newResponse({ body, status: this.status, headers: responseHeaders });
|
|
1468
|
+
}
|
|
1469
|
+
redirect(location, status) {
|
|
1470
|
+
this.headers.set("Location", location);
|
|
1471
|
+
this.status = status || 302;
|
|
1472
|
+
this.#response = newResponse({
|
|
1473
|
+
headers: this.headers,
|
|
1474
|
+
status: this.status,
|
|
1475
|
+
});
|
|
1476
|
+
}
|
|
1477
|
+
setCookie(cookie) {
|
|
1478
|
+
setCookie(this.headers, cookie);
|
|
1479
|
+
}
|
|
1480
|
+
getResponse() {
|
|
1481
|
+
if (!this.#response) {
|
|
1482
|
+
this.#response = new Response(this.body, {
|
|
1483
|
+
status: this.status || 200,
|
|
1484
|
+
headers: this.headers,
|
|
1485
|
+
});
|
|
1486
|
+
}
|
|
1487
|
+
return this.#response;
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
|
|
1491
|
+
// TODO: should divide to RequestContext and OperationContext
|
|
1492
|
+
class RouteContext {
|
|
1493
|
+
request;
|
|
1494
|
+
response;
|
|
1495
|
+
state;
|
|
1496
|
+
databaseAccessor;
|
|
1497
|
+
method;
|
|
1498
|
+
path;
|
|
1499
|
+
params;
|
|
1500
|
+
routeConfig;
|
|
1501
|
+
#server;
|
|
1502
|
+
#dbTransactionClient;
|
|
1503
|
+
#dbTransactionState;
|
|
1504
|
+
static newSystemOperationContext(server) {
|
|
1505
|
+
return new RouteContext(server);
|
|
1506
|
+
}
|
|
1507
|
+
constructor(server, request) {
|
|
1508
|
+
this.#server = server;
|
|
1509
|
+
this.databaseAccessor = server.getDatabaseAccessor();
|
|
1510
|
+
this.#dbTransactionState = "uninited";
|
|
1511
|
+
this.request = request;
|
|
1512
|
+
this.state = {};
|
|
1513
|
+
this.response = new RapidResponse();
|
|
1514
|
+
// `method` and `path` are used by `koa-tree-router` to match route
|
|
1515
|
+
if (this.request) {
|
|
1516
|
+
this.method = request.method;
|
|
1517
|
+
this.path = request.url.pathname;
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
clone() {
|
|
1521
|
+
const clonedContext = new RouteContext(this.#server);
|
|
1522
|
+
clonedContext.method = this.method;
|
|
1523
|
+
clonedContext.path = this.path;
|
|
1524
|
+
clonedContext.params = this.params;
|
|
1525
|
+
clonedContext.setState(this.state);
|
|
1526
|
+
return clonedContext;
|
|
1527
|
+
}
|
|
1528
|
+
setState(state) {
|
|
1529
|
+
Object.assign(this.state, state);
|
|
1530
|
+
}
|
|
1531
|
+
// `koa-tree-router` uses this method to set headers
|
|
1532
|
+
set(headerName, headerValue) {
|
|
1533
|
+
this.response.headers.set(headerName, headerValue);
|
|
1534
|
+
}
|
|
1535
|
+
json(obj, status, headers) {
|
|
1536
|
+
this.response.json(obj, status, headers);
|
|
1537
|
+
}
|
|
1538
|
+
redirect(url, status) {
|
|
1539
|
+
this.response.redirect(url, status);
|
|
1540
|
+
}
|
|
1541
|
+
getDbTransactionClient() {
|
|
1542
|
+
return this.#dbTransactionClient;
|
|
1543
|
+
}
|
|
1544
|
+
async initDbTransactionClient() {
|
|
1545
|
+
let dbClient = this.#dbTransactionClient;
|
|
1546
|
+
if (dbClient) {
|
|
1547
|
+
return dbClient;
|
|
1548
|
+
}
|
|
1549
|
+
dbClient = await this.databaseAccessor.getClient();
|
|
1550
|
+
this.#dbTransactionState = "inited";
|
|
1551
|
+
this.#dbTransactionClient = dbClient;
|
|
1552
|
+
return dbClient;
|
|
1553
|
+
}
|
|
1554
|
+
async beginDbTransaction() {
|
|
1555
|
+
if (!this.#dbTransactionClient) {
|
|
1556
|
+
throw new Error("Database transaction has not been inited. You should call initDbTransactionClient() first.");
|
|
1557
|
+
}
|
|
1558
|
+
if (this.#dbTransactionState === "started") {
|
|
1559
|
+
throw new Error("Database transaction has been started. You can not begin a new transaction before you commit or rollback it.");
|
|
1560
|
+
}
|
|
1561
|
+
await this.databaseAccessor.queryDatabaseObject("BEGIN", [], this.#dbTransactionClient);
|
|
1562
|
+
this.#dbTransactionState = "started";
|
|
1563
|
+
}
|
|
1564
|
+
async commitDbTransaction() {
|
|
1565
|
+
if (!this.#dbTransactionClient) {
|
|
1566
|
+
throw new Error("Database transaction has not been inited. You should call initDbTransactionClient() first.");
|
|
1567
|
+
}
|
|
1568
|
+
if (this.#dbTransactionState !== "started") {
|
|
1569
|
+
throw new Error("Database transaction has not been started. You should call beginDbTransaction() first.");
|
|
1570
|
+
}
|
|
1571
|
+
await this.databaseAccessor.queryDatabaseObject("COMMIT", [], this.#dbTransactionClient);
|
|
1572
|
+
this.#dbTransactionState = "inited";
|
|
1573
|
+
}
|
|
1574
|
+
async rollbackDbTransaction() {
|
|
1575
|
+
if (!this.#dbTransactionClient) {
|
|
1576
|
+
throw new Error("Database transaction has not been inited. You should call initDbTransactionClient() first.");
|
|
1577
|
+
}
|
|
1227
1578
|
if (this.#dbTransactionState !== "started") {
|
|
1228
1579
|
throw new Error("Database transaction has not been started. You should call beginDbTransaction() first.");
|
|
1229
1580
|
}
|
|
@@ -4584,434 +4935,86 @@ class RapidServer {
|
|
|
4584
4935
|
emitter = this.#entityCreateEventEmitters;
|
|
4585
4936
|
}
|
|
4586
4937
|
else if (eventName === "entity.beforeUpdate") {
|
|
4587
|
-
emitter = this.#entityBeforeUpdateEventEmitters;
|
|
4588
|
-
}
|
|
4589
|
-
else if (eventName === "entity.update") {
|
|
4590
|
-
emitter = this.#entityUpdateEventEmitters;
|
|
4591
|
-
}
|
|
4592
|
-
else if (eventName === "entity.beforeDelete") {
|
|
4593
|
-
emitter = this.#entityBeforeDeleteEventEmitters;
|
|
4594
|
-
}
|
|
4595
|
-
else if (eventName === "entity.delete") {
|
|
4596
|
-
emitter = this.#entityDeleteEventEmitters;
|
|
4597
|
-
}
|
|
4598
|
-
else if (eventName === "entity.addRelations") {
|
|
4599
|
-
emitter = this.#entityAddRelationsEventEmitters;
|
|
4600
|
-
}
|
|
4601
|
-
else if (eventName === "entity.removeRelations") {
|
|
4602
|
-
emitter = this.#entityRemoveRelationsEventEmitters;
|
|
4603
|
-
}
|
|
4604
|
-
else if (eventName === "entity.beforeResponse") {
|
|
4605
|
-
emitter = this.#entityBeforeResponseEventEmitters;
|
|
4606
|
-
}
|
|
4607
|
-
await emitter.emit(modelSingularCode, entityWatchHandlerContext);
|
|
4608
|
-
if (baseModelSingularCode) {
|
|
4609
|
-
await emitter.emit(baseModelSingularCode, entityWatchHandlerContext);
|
|
4610
|
-
}
|
|
4611
|
-
}
|
|
4612
|
-
}
|
|
4613
|
-
|
|
4614
|
-
const parseFormDataBody = async (request, options = { all: false }) => {
|
|
4615
|
-
const contentType = request.headers.get("Content-Type");
|
|
4616
|
-
if (isFormDataContent(contentType)) {
|
|
4617
|
-
return parseFormData(request, options);
|
|
4618
|
-
}
|
|
4619
|
-
return {};
|
|
4620
|
-
};
|
|
4621
|
-
function isFormDataContent(contentType) {
|
|
4622
|
-
if (contentType === null) {
|
|
4623
|
-
return false;
|
|
4624
|
-
}
|
|
4625
|
-
return contentType.startsWith("multipart/form-data") || contentType.startsWith("application/x-www-form-urlencoded");
|
|
4626
|
-
}
|
|
4627
|
-
async function parseFormData(request, options) {
|
|
4628
|
-
const formData = await request.formData();
|
|
4629
|
-
if (formData) {
|
|
4630
|
-
return convertFormDataToBodyData(formData, options);
|
|
4631
|
-
}
|
|
4632
|
-
return {};
|
|
4633
|
-
}
|
|
4634
|
-
function convertFormDataToBodyData(formData, options) {
|
|
4635
|
-
const form = {};
|
|
4636
|
-
formData.forEach((value, key) => {
|
|
4637
|
-
const shouldParseAllValues = options.all || key.endsWith("[]");
|
|
4638
|
-
if (!shouldParseAllValues) {
|
|
4639
|
-
form[key] = value;
|
|
4640
|
-
}
|
|
4641
|
-
else {
|
|
4642
|
-
handleParsingAllValues(form, key, value);
|
|
4643
|
-
}
|
|
4644
|
-
});
|
|
4645
|
-
return form;
|
|
4646
|
-
}
|
|
4647
|
-
const handleParsingAllValues = (form, key, value) => {
|
|
4648
|
-
if (form[key] && isArrayField(form[key])) {
|
|
4649
|
-
appendToExistingArray(form[key], value);
|
|
4650
|
-
}
|
|
4651
|
-
else if (form[key]) {
|
|
4652
|
-
convertToNewArray(form, key, value);
|
|
4653
|
-
}
|
|
4654
|
-
else {
|
|
4655
|
-
form[key] = value;
|
|
4656
|
-
}
|
|
4657
|
-
};
|
|
4658
|
-
function isArrayField(field) {
|
|
4659
|
-
return Array.isArray(field);
|
|
4660
|
-
}
|
|
4661
|
-
const appendToExistingArray = (arr, value) => {
|
|
4662
|
-
arr.push(value);
|
|
4663
|
-
};
|
|
4664
|
-
const convertToNewArray = (form, key, value) => {
|
|
4665
|
-
form[key] = [form[key], value];
|
|
4666
|
-
};
|
|
4667
|
-
|
|
4668
|
-
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
|
4669
|
-
class AssertionError extends Error {
|
|
4670
|
-
name = "AssertionError";
|
|
4671
|
-
constructor(message) {
|
|
4672
|
-
super(message);
|
|
4673
|
-
}
|
|
4674
|
-
}
|
|
4675
|
-
|
|
4676
|
-
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
|
4677
|
-
/** Make an assertion, error will be thrown if `expr` does not have truthy value. */
|
|
4678
|
-
function assert(expr, msg = "") {
|
|
4679
|
-
if (!expr) {
|
|
4680
|
-
throw new AssertionError(msg);
|
|
4681
|
-
}
|
|
4682
|
-
}
|
|
4683
|
-
|
|
4684
|
-
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
|
4685
|
-
// This module is browser compatible.
|
|
4686
|
-
/**
|
|
4687
|
-
* Formats the given date to IMF date time format. (Reference:
|
|
4688
|
-
* https://tools.ietf.org/html/rfc7231#section-7.1.1.1).
|
|
4689
|
-
* IMF is the time format to use when generating times in HTTP
|
|
4690
|
-
* headers. The time being formatted must be in UTC for Format to
|
|
4691
|
-
* generate the correct format.
|
|
4692
|
-
*
|
|
4693
|
-
* @example
|
|
4694
|
-
* ```ts
|
|
4695
|
-
* import { toIMF } from "https://deno.land/std@$STD_VERSION/datetime/to_imf.ts";
|
|
4696
|
-
*
|
|
4697
|
-
* toIMF(new Date(0)); // => returns "Thu, 01 Jan 1970 00:00:00 GMT"
|
|
4698
|
-
* ```
|
|
4699
|
-
* @param date Date to parse
|
|
4700
|
-
* @return IMF date formatted string
|
|
4701
|
-
*/
|
|
4702
|
-
function toIMF(date) {
|
|
4703
|
-
function dtPad(v, lPad = 2) {
|
|
4704
|
-
return v.padStart(lPad, "0");
|
|
4705
|
-
}
|
|
4706
|
-
const d = dtPad(date.getUTCDate().toString());
|
|
4707
|
-
const h = dtPad(date.getUTCHours().toString());
|
|
4708
|
-
const min = dtPad(date.getUTCMinutes().toString());
|
|
4709
|
-
const s = dtPad(date.getUTCSeconds().toString());
|
|
4710
|
-
const y = date.getUTCFullYear();
|
|
4711
|
-
const days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
|
4712
|
-
const months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
|
4713
|
-
return `${days[date.getUTCDay()]}, ${d} ${months[date.getUTCMonth()]} ${y} ${h}:${min}:${s} GMT`;
|
|
4714
|
-
}
|
|
4715
|
-
|
|
4716
|
-
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
|
4717
|
-
const FIELD_CONTENT_REGEXP = /^(?=[\x20-\x7E]*$)[^()@<>,;:\\"\[\]?={}\s]+$/;
|
|
4718
|
-
function toString(cookie) {
|
|
4719
|
-
if (!cookie.name) {
|
|
4720
|
-
return "";
|
|
4721
|
-
}
|
|
4722
|
-
const out = [];
|
|
4723
|
-
validateName(cookie.name);
|
|
4724
|
-
validateValue(cookie.name, cookie.value);
|
|
4725
|
-
out.push(`${cookie.name}=${cookie.value}`);
|
|
4726
|
-
// Fallback for invalid Set-Cookie
|
|
4727
|
-
// ref: https://tools.ietf.org/html/draft-ietf-httpbis-cookie-prefixes-00#section-3.1
|
|
4728
|
-
if (cookie.name.startsWith("__Secure")) {
|
|
4729
|
-
cookie.secure = true;
|
|
4730
|
-
}
|
|
4731
|
-
if (cookie.name.startsWith("__Host")) {
|
|
4732
|
-
cookie.path = "/";
|
|
4733
|
-
cookie.secure = true;
|
|
4734
|
-
delete cookie.domain;
|
|
4735
|
-
}
|
|
4736
|
-
if (cookie.secure) {
|
|
4737
|
-
out.push("Secure");
|
|
4738
|
-
}
|
|
4739
|
-
if (cookie.httpOnly) {
|
|
4740
|
-
out.push("HttpOnly");
|
|
4741
|
-
}
|
|
4742
|
-
if (typeof cookie.maxAge === "number" && Number.isInteger(cookie.maxAge)) {
|
|
4743
|
-
assert(cookie.maxAge >= 0, "Max-Age must be an integer superior or equal to 0");
|
|
4744
|
-
out.push(`Max-Age=${cookie.maxAge}`);
|
|
4745
|
-
}
|
|
4746
|
-
if (cookie.domain) {
|
|
4747
|
-
validateDomain(cookie.domain);
|
|
4748
|
-
out.push(`Domain=${cookie.domain}`);
|
|
4749
|
-
}
|
|
4750
|
-
if (cookie.sameSite) {
|
|
4751
|
-
out.push(`SameSite=${cookie.sameSite}`);
|
|
4752
|
-
}
|
|
4753
|
-
if (cookie.path) {
|
|
4754
|
-
validatePath(cookie.path);
|
|
4755
|
-
out.push(`Path=${cookie.path}`);
|
|
4756
|
-
}
|
|
4757
|
-
if (cookie.expires) {
|
|
4758
|
-
const { expires } = cookie;
|
|
4759
|
-
const dateString = toIMF(typeof expires === "number" ? new Date(expires) : expires);
|
|
4760
|
-
out.push(`Expires=${dateString}`);
|
|
4761
|
-
}
|
|
4762
|
-
if (cookie.unparsed) {
|
|
4763
|
-
out.push(cookie.unparsed.join("; "));
|
|
4764
|
-
}
|
|
4765
|
-
return out.join("; ");
|
|
4766
|
-
}
|
|
4767
|
-
/**
|
|
4768
|
-
* Validate Cookie Name.
|
|
4769
|
-
* @param name Cookie name.
|
|
4770
|
-
*/
|
|
4771
|
-
function validateName(name) {
|
|
4772
|
-
if (name && !FIELD_CONTENT_REGEXP.test(name)) {
|
|
4773
|
-
throw new TypeError(`Invalid cookie name: "${name}".`);
|
|
4774
|
-
}
|
|
4775
|
-
}
|
|
4776
|
-
/**
|
|
4777
|
-
* Validate Path Value.
|
|
4778
|
-
* See {@link https://tools.ietf.org/html/rfc6265#section-4.1.2.4}.
|
|
4779
|
-
* @param path Path value.
|
|
4780
|
-
*/
|
|
4781
|
-
function validatePath(path) {
|
|
4782
|
-
if (path == null) {
|
|
4783
|
-
return;
|
|
4784
|
-
}
|
|
4785
|
-
for (let i = 0; i < path.length; i++) {
|
|
4786
|
-
const c = path.charAt(i);
|
|
4787
|
-
if (c < String.fromCharCode(0x20) || c > String.fromCharCode(0x7e) || c == ";") {
|
|
4788
|
-
throw new Error(path + ": Invalid cookie path char '" + c + "'");
|
|
4789
|
-
}
|
|
4790
|
-
}
|
|
4791
|
-
}
|
|
4792
|
-
/**
|
|
4793
|
-
* Validate Cookie Value.
|
|
4794
|
-
* See {@link https://tools.ietf.org/html/rfc6265#section-4.1}.
|
|
4795
|
-
* @param value Cookie value.
|
|
4796
|
-
*/
|
|
4797
|
-
function validateValue(name, value) {
|
|
4798
|
-
if (value == null || name == null)
|
|
4799
|
-
return;
|
|
4800
|
-
for (let i = 0; i < value.length; i++) {
|
|
4801
|
-
const c = value.charAt(i);
|
|
4802
|
-
if (c < String.fromCharCode(0x21) ||
|
|
4803
|
-
c == String.fromCharCode(0x22) ||
|
|
4804
|
-
c == String.fromCharCode(0x2c) ||
|
|
4805
|
-
c == String.fromCharCode(0x3b) ||
|
|
4806
|
-
c == String.fromCharCode(0x5c) ||
|
|
4807
|
-
c == String.fromCharCode(0x7f)) {
|
|
4808
|
-
throw new Error("RFC2616 cookie '" + name + "' cannot contain character '" + c + "'");
|
|
4938
|
+
emitter = this.#entityBeforeUpdateEventEmitters;
|
|
4809
4939
|
}
|
|
4810
|
-
if (
|
|
4811
|
-
|
|
4940
|
+
else if (eventName === "entity.update") {
|
|
4941
|
+
emitter = this.#entityUpdateEventEmitters;
|
|
4942
|
+
}
|
|
4943
|
+
else if (eventName === "entity.beforeDelete") {
|
|
4944
|
+
emitter = this.#entityBeforeDeleteEventEmitters;
|
|
4945
|
+
}
|
|
4946
|
+
else if (eventName === "entity.delete") {
|
|
4947
|
+
emitter = this.#entityDeleteEventEmitters;
|
|
4948
|
+
}
|
|
4949
|
+
else if (eventName === "entity.addRelations") {
|
|
4950
|
+
emitter = this.#entityAddRelationsEventEmitters;
|
|
4951
|
+
}
|
|
4952
|
+
else if (eventName === "entity.removeRelations") {
|
|
4953
|
+
emitter = this.#entityRemoveRelationsEventEmitters;
|
|
4954
|
+
}
|
|
4955
|
+
else if (eventName === "entity.beforeResponse") {
|
|
4956
|
+
emitter = this.#entityBeforeResponseEventEmitters;
|
|
4957
|
+
}
|
|
4958
|
+
await emitter.emit(modelSingularCode, entityWatchHandlerContext);
|
|
4959
|
+
if (baseModelSingularCode) {
|
|
4960
|
+
await emitter.emit(baseModelSingularCode, entityWatchHandlerContext);
|
|
4812
4961
|
}
|
|
4813
4962
|
}
|
|
4814
|
-
}
|
|
4815
|
-
|
|
4816
|
-
|
|
4817
|
-
|
|
4818
|
-
|
|
4819
|
-
|
|
4820
|
-
function validateDomain(domain) {
|
|
4821
|
-
if (domain == null) {
|
|
4822
|
-
return;
|
|
4963
|
+
}
|
|
4964
|
+
|
|
4965
|
+
const parseFormDataBody = async (request, options = { all: false }) => {
|
|
4966
|
+
const contentType = request.headers.get("Content-Type");
|
|
4967
|
+
if (isFormDataContent(contentType)) {
|
|
4968
|
+
return parseFormData(request, options);
|
|
4823
4969
|
}
|
|
4824
|
-
|
|
4825
|
-
|
|
4826
|
-
|
|
4827
|
-
|
|
4970
|
+
return {};
|
|
4971
|
+
};
|
|
4972
|
+
function isFormDataContent(contentType) {
|
|
4973
|
+
if (contentType === null) {
|
|
4974
|
+
return false;
|
|
4828
4975
|
}
|
|
4976
|
+
return contentType.startsWith("multipart/form-data") || contentType.startsWith("application/x-www-form-urlencoded");
|
|
4829
4977
|
}
|
|
4830
|
-
|
|
4831
|
-
|
|
4832
|
-
|
|
4833
|
-
|
|
4834
|
-
* ```ts
|
|
4835
|
-
* import { getCookies } from "https://deno.land/std@$STD_VERSION/http/cookie.ts";
|
|
4836
|
-
*
|
|
4837
|
-
* const headers = new Headers();
|
|
4838
|
-
* headers.set("Cookie", "full=of; tasty=chocolate");
|
|
4839
|
-
*
|
|
4840
|
-
* const cookies = getCookies(headers);
|
|
4841
|
-
* console.log(cookies); // { full: "of", tasty: "chocolate" }
|
|
4842
|
-
* ```
|
|
4843
|
-
*
|
|
4844
|
-
* @param headers The headers instance to get cookies from
|
|
4845
|
-
* @return Object with cookie names as keys
|
|
4846
|
-
*/
|
|
4847
|
-
function getCookies(headers) {
|
|
4848
|
-
const cookie = headers.get("Cookie");
|
|
4849
|
-
if (cookie != null) {
|
|
4850
|
-
const out = {};
|
|
4851
|
-
const c = cookie.split(";");
|
|
4852
|
-
for (const kv of c) {
|
|
4853
|
-
const [cookieKey, ...cookieVal] = kv.split("=");
|
|
4854
|
-
assert(cookieKey != null);
|
|
4855
|
-
const key = cookieKey.trim();
|
|
4856
|
-
out[key] = cookieVal.join("=");
|
|
4857
|
-
}
|
|
4858
|
-
return out;
|
|
4978
|
+
async function parseFormData(request, options) {
|
|
4979
|
+
const formData = await request.formData();
|
|
4980
|
+
if (formData) {
|
|
4981
|
+
return convertFormDataToBodyData(formData, options);
|
|
4859
4982
|
}
|
|
4860
4983
|
return {};
|
|
4861
4984
|
}
|
|
4862
|
-
|
|
4863
|
-
|
|
4864
|
-
|
|
4865
|
-
|
|
4866
|
-
|
|
4867
|
-
|
|
4868
|
-
|
|
4869
|
-
|
|
4870
|
-
|
|
4871
|
-
|
|
4872
|
-
* const headers = new Headers();
|
|
4873
|
-
* const cookie: Cookie = { name: "Space", value: "Cat" };
|
|
4874
|
-
* setCookie(headers, cookie);
|
|
4875
|
-
*
|
|
4876
|
-
* const cookieHeader = headers.get("set-cookie");
|
|
4877
|
-
* console.log(cookieHeader); // Space=Cat
|
|
4878
|
-
* ```
|
|
4879
|
-
*
|
|
4880
|
-
* @param headers The headers instance to set the cookie to
|
|
4881
|
-
* @param cookie Cookie to set
|
|
4882
|
-
*/
|
|
4883
|
-
function setCookie(headers, cookie) {
|
|
4884
|
-
// Parsing cookie headers to make consistent set-cookie header
|
|
4885
|
-
// ref: https://tools.ietf.org/html/rfc6265#section-4.1.1
|
|
4886
|
-
const v = toString(cookie);
|
|
4887
|
-
if (v) {
|
|
4888
|
-
headers.append("Set-Cookie", v);
|
|
4889
|
-
}
|
|
4890
|
-
}
|
|
4891
|
-
/**
|
|
4892
|
-
* Set the cookie header with empty value in the headers to delete it
|
|
4893
|
-
*
|
|
4894
|
-
* > Note: Deleting a `Cookie` will set its expiration date before now. Forcing
|
|
4895
|
-
* > the browser to delete it.
|
|
4896
|
-
*
|
|
4897
|
-
* @example
|
|
4898
|
-
* ```ts
|
|
4899
|
-
* import { deleteCookie } from "https://deno.land/std@$STD_VERSION/http/cookie.ts";
|
|
4900
|
-
*
|
|
4901
|
-
* const headers = new Headers();
|
|
4902
|
-
* deleteCookie(headers, "deno");
|
|
4903
|
-
*
|
|
4904
|
-
* const cookieHeader = headers.get("set-cookie");
|
|
4905
|
-
* console.log(cookieHeader); // deno=; Expires=Thus, 01 Jan 1970 00:00:00 GMT
|
|
4906
|
-
* ```
|
|
4907
|
-
*
|
|
4908
|
-
* @param headers The headers instance to delete the cookie from
|
|
4909
|
-
* @param name Name of cookie
|
|
4910
|
-
* @param attributes Additional cookie attributes
|
|
4911
|
-
*/
|
|
4912
|
-
function deleteCookie(headers, name, attributes) {
|
|
4913
|
-
setCookie(headers, {
|
|
4914
|
-
name: name,
|
|
4915
|
-
value: "",
|
|
4916
|
-
expires: new Date(0),
|
|
4917
|
-
...attributes,
|
|
4985
|
+
function convertFormDataToBodyData(formData, options) {
|
|
4986
|
+
const form = {};
|
|
4987
|
+
formData.forEach((value, key) => {
|
|
4988
|
+
const shouldParseAllValues = options.all || key.endsWith("[]");
|
|
4989
|
+
if (!shouldParseAllValues) {
|
|
4990
|
+
form[key] = value;
|
|
4991
|
+
}
|
|
4992
|
+
else {
|
|
4993
|
+
handleParsingAllValues(form, key, value);
|
|
4994
|
+
}
|
|
4918
4995
|
});
|
|
4996
|
+
return form;
|
|
4919
4997
|
}
|
|
4920
|
-
|
|
4921
|
-
|
|
4922
|
-
|
|
4923
|
-
return [key, values.join("=")];
|
|
4924
|
-
});
|
|
4925
|
-
const cookie = {
|
|
4926
|
-
name: attrs[0][0],
|
|
4927
|
-
value: attrs[0][1],
|
|
4928
|
-
};
|
|
4929
|
-
for (const [key, value] of attrs.slice(1)) {
|
|
4930
|
-
switch (key.toLocaleLowerCase()) {
|
|
4931
|
-
case "expires":
|
|
4932
|
-
cookie.expires = new Date(value);
|
|
4933
|
-
break;
|
|
4934
|
-
case "max-age":
|
|
4935
|
-
cookie.maxAge = Number(value);
|
|
4936
|
-
if (cookie.maxAge < 0) {
|
|
4937
|
-
console.warn("Max-Age must be an integer superior or equal to 0. Cookie ignored.");
|
|
4938
|
-
return null;
|
|
4939
|
-
}
|
|
4940
|
-
break;
|
|
4941
|
-
case "domain":
|
|
4942
|
-
cookie.domain = value;
|
|
4943
|
-
break;
|
|
4944
|
-
case "path":
|
|
4945
|
-
cookie.path = value;
|
|
4946
|
-
break;
|
|
4947
|
-
case "secure":
|
|
4948
|
-
cookie.secure = true;
|
|
4949
|
-
break;
|
|
4950
|
-
case "httponly":
|
|
4951
|
-
cookie.httpOnly = true;
|
|
4952
|
-
break;
|
|
4953
|
-
case "samesite":
|
|
4954
|
-
cookie.sameSite = value;
|
|
4955
|
-
break;
|
|
4956
|
-
default:
|
|
4957
|
-
if (!Array.isArray(cookie.unparsed)) {
|
|
4958
|
-
cookie.unparsed = [];
|
|
4959
|
-
}
|
|
4960
|
-
cookie.unparsed.push([key, value].join("="));
|
|
4961
|
-
}
|
|
4998
|
+
const handleParsingAllValues = (form, key, value) => {
|
|
4999
|
+
if (form[key] && isArrayField(form[key])) {
|
|
5000
|
+
appendToExistingArray(form[key], value);
|
|
4962
5001
|
}
|
|
4963
|
-
if (
|
|
4964
|
-
|
|
4965
|
-
if (!cookie.secure) {
|
|
4966
|
-
console.warn("Cookies with names starting with `__Secure-` must be set with the secure flag. Cookie ignored.");
|
|
4967
|
-
return null;
|
|
4968
|
-
}
|
|
5002
|
+
else if (form[key]) {
|
|
5003
|
+
convertToNewArray(form, key, value);
|
|
4969
5004
|
}
|
|
4970
|
-
|
|
4971
|
-
|
|
4972
|
-
console.warn("Cookies with names starting with `__Host-` must be set with the secure flag. Cookie ignored.");
|
|
4973
|
-
return null;
|
|
4974
|
-
}
|
|
4975
|
-
if (cookie.domain !== undefined) {
|
|
4976
|
-
console.warn("Cookies with names starting with `__Host-` must not have a domain specified. Cookie ignored.");
|
|
4977
|
-
return null;
|
|
4978
|
-
}
|
|
4979
|
-
if (cookie.path !== "/") {
|
|
4980
|
-
console.warn("Cookies with names starting with `__Host-` must have path be `/`. Cookie has been ignored.");
|
|
4981
|
-
return null;
|
|
4982
|
-
}
|
|
5005
|
+
else {
|
|
5006
|
+
form[key] = value;
|
|
4983
5007
|
}
|
|
4984
|
-
|
|
5008
|
+
};
|
|
5009
|
+
function isArrayField(field) {
|
|
5010
|
+
return Array.isArray(field);
|
|
4985
5011
|
}
|
|
4986
|
-
|
|
4987
|
-
|
|
4988
|
-
|
|
4989
|
-
|
|
4990
|
-
|
|
4991
|
-
|
|
4992
|
-
*
|
|
4993
|
-
* const headers = new Headers([
|
|
4994
|
-
* ["Set-Cookie", "lulu=meow; Secure; Max-Age=3600"],
|
|
4995
|
-
* ["Set-Cookie", "booya=kasha; HttpOnly; Path=/"],
|
|
4996
|
-
* ]);
|
|
4997
|
-
*
|
|
4998
|
-
* const cookies = getSetCookies(headers);
|
|
4999
|
-
* console.log(cookies); // [{ name: "lulu", value: "meow", secure: true, maxAge: 3600 }, { name: "booya", value: "kahsa", httpOnly: true, path: "/ }]
|
|
5000
|
-
* ```
|
|
5001
|
-
*
|
|
5002
|
-
* @param headers The headers instance to get set-cookies from
|
|
5003
|
-
* @return List of cookies
|
|
5004
|
-
*/
|
|
5005
|
-
function getSetCookies(headers) {
|
|
5006
|
-
// TODO(lino-levan): remove this ts-ignore when Typescript 5.2 lands in Deno
|
|
5007
|
-
// @ts-ignore Typescript's TS Dom types will be out of date until 5.2
|
|
5008
|
-
return headers
|
|
5009
|
-
.getSetCookie()
|
|
5010
|
-
/** Parse each `set-cookie` header separately */
|
|
5011
|
-
.map(parseSetCookie)
|
|
5012
|
-
/** Skip empty cookies */
|
|
5013
|
-
.filter(Boolean);
|
|
5014
|
-
}
|
|
5012
|
+
const appendToExistingArray = (arr, value) => {
|
|
5013
|
+
arr.push(value);
|
|
5014
|
+
};
|
|
5015
|
+
const convertToNewArray = (form, key, value) => {
|
|
5016
|
+
form[key] = [form[key], value];
|
|
5017
|
+
};
|
|
5015
5018
|
|
|
5016
5019
|
const GlobalRequest = global.Request;
|
|
5017
5020
|
class RapidRequest {
|
|
@@ -10075,6 +10078,7 @@ class EntityAccessControlPlugin {
|
|
|
10075
10078
|
fixBigIntJSONSerialize();
|
|
10076
10079
|
|
|
10077
10080
|
exports.AuthPlugin = AuthPlugin;
|
|
10081
|
+
exports.AuthService = AuthService;
|
|
10078
10082
|
exports.CacheFactory = CacheFactory;
|
|
10079
10083
|
exports.CronJobPlugin = CronJobPlugin;
|
|
10080
10084
|
exports.DataAccessor = DataAccessor;
|
package/package.json
CHANGED
package/src/core/response.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { isArray, isObject } from "lodash";
|
|
2
2
|
import { HttpStatus, ResponseData } from "./http-types";
|
|
3
|
+
import { Cookie, setCookie } from "~/deno-std/http/cookie";
|
|
3
4
|
|
|
4
5
|
export const GlobalResponse = global.Response;
|
|
5
6
|
|
|
@@ -67,6 +68,10 @@ export class RapidResponse {
|
|
|
67
68
|
});
|
|
68
69
|
}
|
|
69
70
|
|
|
71
|
+
setCookie(cookie: Cookie) {
|
|
72
|
+
setCookie(this.headers, cookie);
|
|
73
|
+
}
|
|
74
|
+
|
|
70
75
|
getResponse() {
|
|
71
76
|
if (!this.#response) {
|
|
72
77
|
this.#response = new Response(this.body, {
|
package/src/index.ts
CHANGED
|
@@ -55,6 +55,7 @@ export { default as WebhooksPlugin } from "./plugins/webhooks/WebhooksPlugin";
|
|
|
55
55
|
|
|
56
56
|
export { default as AuthPlugin } from "./plugins/auth/AuthPlugin";
|
|
57
57
|
export * from "./plugins/auth/AuthPluginTypes";
|
|
58
|
+
export { default as AuthService } from "./plugins/auth/services/AuthService";
|
|
58
59
|
|
|
59
60
|
export { default as FileManagePlugin } from "./plugins/fileManage/FileManagePlugin";
|
|
60
61
|
|