@ruiapp/rapid-core 0.1.6 → 0.1.7

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.
@@ -1,5 +1,6 @@
1
1
  import { RpdApplicationConfig } from "~/types";
2
2
  import { IRpdServer, RapidPlugin } from "./server";
3
+ import { RouteContext } from "./routeContext";
3
4
  declare class PluginManager {
4
5
  #private;
5
6
  constructor(server: IRpdServer);
@@ -28,5 +29,7 @@ declare class PluginManager {
28
29
  onApplicationLoaded(server: IRpdServer, applicationConfig: RpdApplicationConfig): Promise<void>;
29
30
  /** 在应用准备完成后调用。此时服务器已经可以处理网络请求。 */
30
31
  onApplicationReady(server: IRpdServer, applicationConfig: RpdApplicationConfig): Promise<void>;
32
+ /** 在接收到HTTP请求,准备路由上下文时调用。 */
33
+ onPrepareRouteContext(server: IRpdServer, routeContext: RouteContext): Promise<void>;
31
34
  }
32
35
  export default PluginManager;
@@ -10,9 +10,10 @@ export declare class RapidRequest {
10
10
  #private;
11
11
  method: string;
12
12
  url: URL;
13
- headers: Headers;
14
13
  constructor(req: Request);
15
14
  parseBody(): Promise<void>;
16
15
  get rawRequest(): Request;
16
+ get headers(): Headers;
17
+ get cookies(): Record<string, string>;
17
18
  get body(): RapidRequestBody;
18
19
  }
@@ -1,6 +1,6 @@
1
1
  import { GetDataAccessorOptions, GetModelOptions, IDatabaseConfig, IQueryBuilder, IRpdDataAccessor, RapidServerConfig, RpdApplicationConfig, RpdDataModel, RpdServerEventTypes } from "~/types";
2
2
  import { IPluginActionHandler, ActionHandler } from "./actionHandler";
3
- import { Next } from "./routeContext";
3
+ import { Next, RouteContext } from "./routeContext";
4
4
  export interface IRpdServer {
5
5
  config: RapidServerConfig;
6
6
  databaseConfig: IDatabaseConfig;
@@ -58,27 +58,29 @@ export interface RapidPlugin {
58
58
  /** 插件的全局配置项 */
59
59
  get configurations(): RpdConfigurationItemOptions[];
60
60
  /** 初始化插件时调用。插件可以在此时进行一些内部对象的初始化工作。 */
61
- initPlugin(server: IRpdServer): Promise<any>;
61
+ initPlugin?: (server: IRpdServer) => Promise<any>;
62
62
  /** 注册中间件 */
63
- registerMiddlewares(server: IRpdServer): Promise<any>;
63
+ registerMiddlewares?: (server: IRpdServer) => Promise<any>;
64
64
  /** 注册接口动作处理程序 */
65
- registerActionHandlers(server: IRpdServer): Promise<any>;
65
+ registerActionHandlers?: (server: IRpdServer) => Promise<any>;
66
66
  /** 注册事件处理程序 */
67
- registerEventHandlers(server: IRpdServer): Promise<any>;
67
+ registerEventHandlers?: (server: IRpdServer) => Promise<any>;
68
68
  /** 注册消息处理程序 */
69
- registerMessageHandlers(server: IRpdServer): Promise<any>;
69
+ registerMessageHandlers?: (server: IRpdServer) => Promise<any>;
70
70
  /** 注册任务处理程序 */
71
- registerTaskProcessors(server: IRpdServer): Promise<any>;
71
+ registerTaskProcessors?: (server: IRpdServer) => Promise<any>;
72
72
  /** 在加载应用前调用。 */
73
- onLoadingApplication(server: IRpdServer, applicationConfig: RpdApplicationConfig): Promise<any>;
73
+ onLoadingApplication?: (server: IRpdServer, applicationConfig: RpdApplicationConfig) => Promise<any>;
74
74
  /** 配置数据集合 */
75
- configureModels(server: IRpdServer, applicationConfig: RpdApplicationConfig): Promise<any>;
75
+ configureModels?: (server: IRpdServer, applicationConfig: RpdApplicationConfig) => Promise<any>;
76
76
  /** 配置模型属性 */
77
- configureModelProperties(server: IRpdServer, applicationConfig: RpdApplicationConfig): Promise<any>;
77
+ configureModelProperties?: (server: IRpdServer, applicationConfig: RpdApplicationConfig) => Promise<any>;
78
78
  /** 配置路由 */
79
- configureRoutes(server: IRpdServer, applicationConfig: RpdApplicationConfig): Promise<any>;
79
+ configureRoutes?: (server: IRpdServer, applicationConfig: RpdApplicationConfig) => Promise<any>;
80
80
  /** 在应用配置加载完成后调用。此时插件可以进行一些数据的初始化工作。 */
81
- onApplicationLoaded(server: IRpdServer, applicationConfig: RpdApplicationConfig): Promise<any>;
81
+ onApplicationLoaded?: (server: IRpdServer, applicationConfig: RpdApplicationConfig) => Promise<any>;
82
82
  /** 在应用准备完成后调用。此时服务器已经可以处理网络请求,可以对外广播消息。 */
83
- onApplicationReady(server: IRpdServer, applicationConfig: RpdApplicationConfig): Promise<any>;
83
+ onApplicationReady?: (server: IRpdServer, applicationConfig: RpdApplicationConfig) => Promise<any>;
84
+ /** 在接收到HTTP请求,准备路由上下文时调用。 */
85
+ onPrepareRouteContext?: (server: IRpdServer, routeContext: RouteContext) => Promise<any>;
84
86
  }
package/dist/index.d.ts CHANGED
@@ -7,9 +7,9 @@ export * from "./core/http-types";
7
7
  export * from "./core/actionHandler";
8
8
  export * from "./utilities/jwtUtility";
9
9
  export * as bootstrapApplicationConfig from "./bootstrapApplicationConfig";
10
- export { default as MetaManagePlugin } from "./plugins/metaManage/mod";
11
- export { default as DataManagePlugin } from "./plugins/dataManage/mod";
12
- export { default as RouteManagePlugin } from "./plugins/routeManage/mod";
13
- export { default as WebhooksPlugin } from "./plugins/webhooks/mod";
14
- export { default as AuthPlugin } from "./plugins/auth/mod";
15
- export { default as FileManagePlugin } from "./plugins/fileManage/mod";
10
+ export { default as MetaManagePlugin } from "./plugins/metaManage/MetaManagePlugin";
11
+ export { default as DataManagePlugin } from "./plugins/dataManage/DataManagePlugin";
12
+ export { default as RouteManagePlugin } from "./plugins/routeManage/RouteManagePlugin";
13
+ export { default as WebhooksPlugin } from "./plugins/webhooks/WebhooksPlugin";
14
+ export { default as AuthPlugin } from "./plugins/auth/AuthPlugin";
15
+ export { default as FileManagePlugin } from "./plugins/fileManage/FileManagePlugin";
package/dist/index.js CHANGED
@@ -544,6 +544,14 @@ class PluginManager {
544
544
  }
545
545
  }
546
546
  }
547
+ /** 在接收到HTTP请求,准备路由上下文时调用。 */
548
+ async onPrepareRouteContext(server, routeContext) {
549
+ for (const plugin of this.#plugins) {
550
+ if (plugin.onPrepareRouteContext) {
551
+ await plugin.onPrepareRouteContext(server, routeContext);
552
+ }
553
+ }
554
+ }
547
555
  }
548
556
 
549
557
  class EventManager {
@@ -769,19 +777,255 @@ const convertToNewArray = (form, key, value) => {
769
777
  form[key] = [form[key], value];
770
778
  };
771
779
 
780
+ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
781
+ class AssertionError extends Error {
782
+ name = "AssertionError";
783
+ constructor(message) {
784
+ super(message);
785
+ }
786
+ }
787
+
788
+ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
789
+ /** Make an assertion, error will be thrown if `expr` does not have truthy value. */
790
+ function assert(expr, msg = "") {
791
+ if (!expr) {
792
+ throw new AssertionError(msg);
793
+ }
794
+ }
795
+
796
+ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
797
+ // This module is browser compatible.
798
+ /**
799
+ * Formats the given date to IMF date time format. (Reference:
800
+ * https://tools.ietf.org/html/rfc7231#section-7.1.1.1).
801
+ * IMF is the time format to use when generating times in HTTP
802
+ * headers. The time being formatted must be in UTC for Format to
803
+ * generate the correct format.
804
+ *
805
+ * @example
806
+ * ```ts
807
+ * import { toIMF } from "https://deno.land/std@$STD_VERSION/datetime/to_imf.ts";
808
+ *
809
+ * toIMF(new Date(0)); // => returns "Thu, 01 Jan 1970 00:00:00 GMT"
810
+ * ```
811
+ * @param date Date to parse
812
+ * @return IMF date formatted string
813
+ */
814
+ function toIMF(date) {
815
+ function dtPad(v, lPad = 2) {
816
+ return v.padStart(lPad, "0");
817
+ }
818
+ const d = dtPad(date.getUTCDate().toString());
819
+ const h = dtPad(date.getUTCHours().toString());
820
+ const min = dtPad(date.getUTCMinutes().toString());
821
+ const s = dtPad(date.getUTCSeconds().toString());
822
+ const y = date.getUTCFullYear();
823
+ const days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
824
+ const months = [
825
+ "Jan",
826
+ "Feb",
827
+ "Mar",
828
+ "Apr",
829
+ "May",
830
+ "Jun",
831
+ "Jul",
832
+ "Aug",
833
+ "Sep",
834
+ "Oct",
835
+ "Nov",
836
+ "Dec",
837
+ ];
838
+ return `${days[date.getUTCDay()]}, ${d} ${months[date.getUTCMonth()]} ${y} ${h}:${min}:${s} GMT`;
839
+ }
840
+
841
+ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
842
+ const FIELD_CONTENT_REGEXP = /^(?=[\x20-\x7E]*$)[^()@<>,;:\\"\[\]?={}\s]+$/;
843
+ function toString(cookie) {
844
+ if (!cookie.name) {
845
+ return "";
846
+ }
847
+ const out = [];
848
+ validateName(cookie.name);
849
+ validateValue(cookie.name, cookie.value);
850
+ out.push(`${cookie.name}=${cookie.value}`);
851
+ // Fallback for invalid Set-Cookie
852
+ // ref: https://tools.ietf.org/html/draft-ietf-httpbis-cookie-prefixes-00#section-3.1
853
+ if (cookie.name.startsWith("__Secure")) {
854
+ cookie.secure = true;
855
+ }
856
+ if (cookie.name.startsWith("__Host")) {
857
+ cookie.path = "/";
858
+ cookie.secure = true;
859
+ delete cookie.domain;
860
+ }
861
+ if (cookie.secure) {
862
+ out.push("Secure");
863
+ }
864
+ if (cookie.httpOnly) {
865
+ out.push("HttpOnly");
866
+ }
867
+ if (typeof cookie.maxAge === "number" && Number.isInteger(cookie.maxAge)) {
868
+ assert(cookie.maxAge >= 0, "Max-Age must be an integer superior or equal to 0");
869
+ out.push(`Max-Age=${cookie.maxAge}`);
870
+ }
871
+ if (cookie.domain) {
872
+ validateDomain(cookie.domain);
873
+ out.push(`Domain=${cookie.domain}`);
874
+ }
875
+ if (cookie.sameSite) {
876
+ out.push(`SameSite=${cookie.sameSite}`);
877
+ }
878
+ if (cookie.path) {
879
+ validatePath(cookie.path);
880
+ out.push(`Path=${cookie.path}`);
881
+ }
882
+ if (cookie.expires) {
883
+ const { expires } = cookie;
884
+ const dateString = toIMF(typeof expires === "number" ? new Date(expires) : expires);
885
+ out.push(`Expires=${dateString}`);
886
+ }
887
+ if (cookie.unparsed) {
888
+ out.push(cookie.unparsed.join("; "));
889
+ }
890
+ return out.join("; ");
891
+ }
892
+ /**
893
+ * Validate Cookie Name.
894
+ * @param name Cookie name.
895
+ */
896
+ function validateName(name) {
897
+ if (name && !FIELD_CONTENT_REGEXP.test(name)) {
898
+ throw new TypeError(`Invalid cookie name: "${name}".`);
899
+ }
900
+ }
901
+ /**
902
+ * Validate Path Value.
903
+ * See {@link https://tools.ietf.org/html/rfc6265#section-4.1.2.4}.
904
+ * @param path Path value.
905
+ */
906
+ function validatePath(path) {
907
+ if (path == null) {
908
+ return;
909
+ }
910
+ for (let i = 0; i < path.length; i++) {
911
+ const c = path.charAt(i);
912
+ if (c < String.fromCharCode(0x20) || c > String.fromCharCode(0x7E) || c == ";") {
913
+ throw new Error(path + ": Invalid cookie path char '" + c + "'");
914
+ }
915
+ }
916
+ }
917
+ /**
918
+ * Validate Cookie Value.
919
+ * See {@link https://tools.ietf.org/html/rfc6265#section-4.1}.
920
+ * @param value Cookie value.
921
+ */
922
+ function validateValue(name, value) {
923
+ if (value == null || name == null)
924
+ return;
925
+ for (let i = 0; i < value.length; i++) {
926
+ const c = value.charAt(i);
927
+ if (c < String.fromCharCode(0x21) || c == String.fromCharCode(0x22) ||
928
+ c == String.fromCharCode(0x2c) || c == String.fromCharCode(0x3b) ||
929
+ c == String.fromCharCode(0x5c) || c == String.fromCharCode(0x7f)) {
930
+ throw new Error("RFC2616 cookie '" + name + "' cannot contain character '" + c + "'");
931
+ }
932
+ if (c > String.fromCharCode(0x80)) {
933
+ throw new Error("RFC2616 cookie '" + name + "' can only have US-ASCII chars as value" +
934
+ c.charCodeAt(0).toString(16));
935
+ }
936
+ }
937
+ }
938
+ /**
939
+ * Validate Cookie Domain.
940
+ * See {@link https://datatracker.ietf.org/doc/html/rfc6265#section-4.1.2.3}.
941
+ * @param domain Cookie domain.
942
+ */
943
+ function validateDomain(domain) {
944
+ if (domain == null) {
945
+ return;
946
+ }
947
+ const char1 = domain.charAt(0);
948
+ const charN = domain.charAt(domain.length - 1);
949
+ if (char1 == "-" || charN == "." || charN == "-") {
950
+ throw new Error("Invalid first/last char in cookie domain: " + domain);
951
+ }
952
+ }
953
+ /**
954
+ * Parse cookies of a header
955
+ *
956
+ * @example
957
+ * ```ts
958
+ * import { getCookies } from "https://deno.land/std@$STD_VERSION/http/cookie.ts";
959
+ *
960
+ * const headers = new Headers();
961
+ * headers.set("Cookie", "full=of; tasty=chocolate");
962
+ *
963
+ * const cookies = getCookies(headers);
964
+ * console.log(cookies); // { full: "of", tasty: "chocolate" }
965
+ * ```
966
+ *
967
+ * @param headers The headers instance to get cookies from
968
+ * @return Object with cookie names as keys
969
+ */
970
+ function getCookies(headers) {
971
+ const cookie = headers.get("Cookie");
972
+ if (cookie != null) {
973
+ const out = {};
974
+ const c = cookie.split(";");
975
+ for (const kv of c) {
976
+ const [cookieKey, ...cookieVal] = kv.split("=");
977
+ assert(cookieKey != null);
978
+ const key = cookieKey.trim();
979
+ out[key] = cookieVal.join("=");
980
+ }
981
+ return out;
982
+ }
983
+ return {};
984
+ }
985
+ /**
986
+ * Set the cookie header properly in the headers
987
+ *
988
+ * @example
989
+ * ```ts
990
+ * import {
991
+ * Cookie,
992
+ * setCookie,
993
+ * } from "https://deno.land/std@$STD_VERSION/http/cookie.ts";
994
+ *
995
+ * const headers = new Headers();
996
+ * const cookie: Cookie = { name: "Space", value: "Cat" };
997
+ * setCookie(headers, cookie);
998
+ *
999
+ * const cookieHeader = headers.get("set-cookie");
1000
+ * console.log(cookieHeader); // Space=Cat
1001
+ * ```
1002
+ *
1003
+ * @param headers The headers instance to set the cookie to
1004
+ * @param cookie Cookie to set
1005
+ */
1006
+ function setCookie(headers, cookie) {
1007
+ // Parsing cookie headers to make consistent set-cookie header
1008
+ // ref: https://tools.ietf.org/html/rfc6265#section-4.1.1
1009
+ const v = toString(cookie);
1010
+ if (v) {
1011
+ headers.append("Set-Cookie", v);
1012
+ }
1013
+ }
1014
+
772
1015
  const GlobalRequest = global.Request;
773
1016
  class RapidRequest {
774
1017
  #raw;
775
1018
  #bodyParsed;
776
1019
  #body;
1020
+ #headers;
1021
+ #parsedCookies;
777
1022
  method;
778
1023
  url;
779
- headers;
780
1024
  constructor(req) {
781
1025
  this.#raw = req;
782
1026
  this.method = req.method;
783
1027
  this.url = new URL(req.url);
784
- this.headers = req.headers;
1028
+ this.#headers = req.headers;
785
1029
  }
786
1030
  async parseBody() {
787
1031
  if (this.#bodyParsed) {
@@ -791,7 +1035,7 @@ class RapidRequest {
791
1035
  const requestMethod = this.method;
792
1036
  if (requestMethod === "POST" || requestMethod === "PUT" || requestMethod === "PATCH") {
793
1037
  const req = this.#raw;
794
- const contentType = this.headers.get("Content-Type");
1038
+ const contentType = this.#headers.get("Content-Type");
795
1039
  if (contentType.includes("json")) {
796
1040
  this.#body = {
797
1041
  type: "json",
@@ -820,6 +1064,15 @@ class RapidRequest {
820
1064
  get rawRequest() {
821
1065
  return this.#raw;
822
1066
  }
1067
+ get headers() {
1068
+ return this.#headers;
1069
+ }
1070
+ get cookies() {
1071
+ if (!this.#parsedCookies) {
1072
+ this.#parsedCookies = getCookies(this.#headers);
1073
+ }
1074
+ return this.#parsedCookies;
1075
+ }
823
1076
  get body() {
824
1077
  if (!this.#bodyParsed) {
825
1078
  throw new Error("Request body not parsed, you should call 'parseBody()' method before getting the body.");
@@ -3450,209 +3703,6 @@ class WebhooksPlugin {
3450
3703
  }
3451
3704
  }
3452
3705
 
3453
- // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
3454
- class AssertionError extends Error {
3455
- name = "AssertionError";
3456
- constructor(message) {
3457
- super(message);
3458
- }
3459
- }
3460
-
3461
- // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
3462
- /** Make an assertion, error will be thrown if `expr` does not have truthy value. */
3463
- function assert(expr, msg = "") {
3464
- if (!expr) {
3465
- throw new AssertionError(msg);
3466
- }
3467
- }
3468
-
3469
- // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
3470
- // This module is browser compatible.
3471
- /**
3472
- * Formats the given date to IMF date time format. (Reference:
3473
- * https://tools.ietf.org/html/rfc7231#section-7.1.1.1).
3474
- * IMF is the time format to use when generating times in HTTP
3475
- * headers. The time being formatted must be in UTC for Format to
3476
- * generate the correct format.
3477
- *
3478
- * @example
3479
- * ```ts
3480
- * import { toIMF } from "https://deno.land/std@$STD_VERSION/datetime/to_imf.ts";
3481
- *
3482
- * toIMF(new Date(0)); // => returns "Thu, 01 Jan 1970 00:00:00 GMT"
3483
- * ```
3484
- * @param date Date to parse
3485
- * @return IMF date formatted string
3486
- */
3487
- function toIMF(date) {
3488
- function dtPad(v, lPad = 2) {
3489
- return v.padStart(lPad, "0");
3490
- }
3491
- const d = dtPad(date.getUTCDate().toString());
3492
- const h = dtPad(date.getUTCHours().toString());
3493
- const min = dtPad(date.getUTCMinutes().toString());
3494
- const s = dtPad(date.getUTCSeconds().toString());
3495
- const y = date.getUTCFullYear();
3496
- const days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
3497
- const months = [
3498
- "Jan",
3499
- "Feb",
3500
- "Mar",
3501
- "Apr",
3502
- "May",
3503
- "Jun",
3504
- "Jul",
3505
- "Aug",
3506
- "Sep",
3507
- "Oct",
3508
- "Nov",
3509
- "Dec",
3510
- ];
3511
- return `${days[date.getUTCDay()]}, ${d} ${months[date.getUTCMonth()]} ${y} ${h}:${min}:${s} GMT`;
3512
- }
3513
-
3514
- // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
3515
- const FIELD_CONTENT_REGEXP = /^(?=[\x20-\x7E]*$)[^()@<>,;:\\"\[\]?={}\s]+$/;
3516
- function toString(cookie) {
3517
- if (!cookie.name) {
3518
- return "";
3519
- }
3520
- const out = [];
3521
- validateName(cookie.name);
3522
- validateValue(cookie.name, cookie.value);
3523
- out.push(`${cookie.name}=${cookie.value}`);
3524
- // Fallback for invalid Set-Cookie
3525
- // ref: https://tools.ietf.org/html/draft-ietf-httpbis-cookie-prefixes-00#section-3.1
3526
- if (cookie.name.startsWith("__Secure")) {
3527
- cookie.secure = true;
3528
- }
3529
- if (cookie.name.startsWith("__Host")) {
3530
- cookie.path = "/";
3531
- cookie.secure = true;
3532
- delete cookie.domain;
3533
- }
3534
- if (cookie.secure) {
3535
- out.push("Secure");
3536
- }
3537
- if (cookie.httpOnly) {
3538
- out.push("HttpOnly");
3539
- }
3540
- if (typeof cookie.maxAge === "number" && Number.isInteger(cookie.maxAge)) {
3541
- assert(cookie.maxAge >= 0, "Max-Age must be an integer superior or equal to 0");
3542
- out.push(`Max-Age=${cookie.maxAge}`);
3543
- }
3544
- if (cookie.domain) {
3545
- validateDomain(cookie.domain);
3546
- out.push(`Domain=${cookie.domain}`);
3547
- }
3548
- if (cookie.sameSite) {
3549
- out.push(`SameSite=${cookie.sameSite}`);
3550
- }
3551
- if (cookie.path) {
3552
- validatePath(cookie.path);
3553
- out.push(`Path=${cookie.path}`);
3554
- }
3555
- if (cookie.expires) {
3556
- const { expires } = cookie;
3557
- const dateString = toIMF(typeof expires === "number" ? new Date(expires) : expires);
3558
- out.push(`Expires=${dateString}`);
3559
- }
3560
- if (cookie.unparsed) {
3561
- out.push(cookie.unparsed.join("; "));
3562
- }
3563
- return out.join("; ");
3564
- }
3565
- /**
3566
- * Validate Cookie Name.
3567
- * @param name Cookie name.
3568
- */
3569
- function validateName(name) {
3570
- if (name && !FIELD_CONTENT_REGEXP.test(name)) {
3571
- throw new TypeError(`Invalid cookie name: "${name}".`);
3572
- }
3573
- }
3574
- /**
3575
- * Validate Path Value.
3576
- * See {@link https://tools.ietf.org/html/rfc6265#section-4.1.2.4}.
3577
- * @param path Path value.
3578
- */
3579
- function validatePath(path) {
3580
- if (path == null) {
3581
- return;
3582
- }
3583
- for (let i = 0; i < path.length; i++) {
3584
- const c = path.charAt(i);
3585
- if (c < String.fromCharCode(0x20) || c > String.fromCharCode(0x7E) || c == ";") {
3586
- throw new Error(path + ": Invalid cookie path char '" + c + "'");
3587
- }
3588
- }
3589
- }
3590
- /**
3591
- * Validate Cookie Value.
3592
- * See {@link https://tools.ietf.org/html/rfc6265#section-4.1}.
3593
- * @param value Cookie value.
3594
- */
3595
- function validateValue(name, value) {
3596
- if (value == null || name == null)
3597
- return;
3598
- for (let i = 0; i < value.length; i++) {
3599
- const c = value.charAt(i);
3600
- if (c < String.fromCharCode(0x21) || c == String.fromCharCode(0x22) ||
3601
- c == String.fromCharCode(0x2c) || c == String.fromCharCode(0x3b) ||
3602
- c == String.fromCharCode(0x5c) || c == String.fromCharCode(0x7f)) {
3603
- throw new Error("RFC2616 cookie '" + name + "' cannot contain character '" + c + "'");
3604
- }
3605
- if (c > String.fromCharCode(0x80)) {
3606
- throw new Error("RFC2616 cookie '" + name + "' can only have US-ASCII chars as value" +
3607
- c.charCodeAt(0).toString(16));
3608
- }
3609
- }
3610
- }
3611
- /**
3612
- * Validate Cookie Domain.
3613
- * See {@link https://datatracker.ietf.org/doc/html/rfc6265#section-4.1.2.3}.
3614
- * @param domain Cookie domain.
3615
- */
3616
- function validateDomain(domain) {
3617
- if (domain == null) {
3618
- return;
3619
- }
3620
- const char1 = domain.charAt(0);
3621
- const charN = domain.charAt(domain.length - 1);
3622
- if (char1 == "-" || charN == "." || charN == "-") {
3623
- throw new Error("Invalid first/last char in cookie domain: " + domain);
3624
- }
3625
- }
3626
- /**
3627
- * Set the cookie header properly in the headers
3628
- *
3629
- * @example
3630
- * ```ts
3631
- * import {
3632
- * Cookie,
3633
- * setCookie,
3634
- * } from "https://deno.land/std@$STD_VERSION/http/cookie.ts";
3635
- *
3636
- * const headers = new Headers();
3637
- * const cookie: Cookie = { name: "Space", value: "Cat" };
3638
- * setCookie(headers, cookie);
3639
- *
3640
- * const cookieHeader = headers.get("set-cookie");
3641
- * console.log(cookieHeader); // Space=Cat
3642
- * ```
3643
- *
3644
- * @param headers The headers instance to set the cookie to
3645
- * @param cookie Cookie to set
3646
- */
3647
- function setCookie(headers, cookie) {
3648
- // Parsing cookie headers to make consistent set-cookie header
3649
- // ref: https://tools.ietf.org/html/rfc6265#section-4.1.1
3650
- const v = toString(cookie);
3651
- if (v) {
3652
- headers.append("Set-Cookie", v);
3653
- }
3654
- }
3655
-
3656
3706
  const code$5 = "createSession";
3657
3707
  async function handler$5(plugin, ctx, options) {
3658
3708
  const { server, input, routerContext } = ctx;
@@ -3868,7 +3918,7 @@ var pluginRoutes = [
3868
3918
  /**
3869
3919
  * Auth manager plugin
3870
3920
  */
3871
- class AuthManager {
3921
+ class AuthPlugin {
3872
3922
  get code() {
3873
3923
  return "authManager";
3874
3924
  }
@@ -3914,6 +3964,26 @@ class AuthManager {
3914
3964
  }
3915
3965
  async onApplicationReady(server, applicationConfig) {
3916
3966
  }
3967
+ async onPrepareRouteContext(server, routeContext) {
3968
+ const request = routeContext.request;
3969
+ let token;
3970
+ const headers = request.headers;
3971
+ // No Authorization header
3972
+ if (headers.has("Authorization")) {
3973
+ // Authorization header has no Bearer or no token
3974
+ const authHeader = headers.get("Authorization");
3975
+ if (!authHeader.startsWith("Bearer ") || authHeader.length <= 7) {
3976
+ throw new Error('AUTHORIZATION_HEADER_INVALID');
3977
+ }
3978
+ token = authHeader.slice(7);
3979
+ }
3980
+ else {
3981
+ token = request.cookies[server.config.sessionCookieName];
3982
+ }
3983
+ const tokenPayload = verifyJwt(token, server.config.jwtKey);
3984
+ routeContext.state.userId = tokenPayload.aud;
3985
+ routeContext.state.userLogin = tokenPayload.act;
3986
+ }
3917
3987
  }
3918
3988
 
3919
3989
  async function readFile(path) {
@@ -4076,7 +4146,7 @@ class FileManager {
4076
4146
 
4077
4147
  fixBigIntJSONSerialize();
4078
4148
 
4079
- exports.AuthPlugin = AuthManager;
4149
+ exports.AuthPlugin = AuthPlugin;
4080
4150
  exports.DataManagePlugin = DataManager;
4081
4151
  exports.FileManagePlugin = FileManager;
4082
4152
  exports.GlobalRequest = GlobalRequest;
@@ -3,7 +3,8 @@
3
3
  */
4
4
  import { RpdApplicationConfig } from "~/types";
5
5
  import { IRpdServer, RapidPlugin, RpdConfigurationItemOptions, RpdServerPluginConfigurableTargetOptions, RpdServerPluginExtendingAbilities } from "~/core/server";
6
- declare class AuthManager implements RapidPlugin {
6
+ import { RouteContext } from "~/core/routeContext";
7
+ declare class AuthPlugin implements RapidPlugin {
7
8
  get code(): string;
8
9
  get description(): string;
9
10
  get extendingAbilities(): RpdServerPluginExtendingAbilities[];
@@ -21,5 +22,6 @@ declare class AuthManager implements RapidPlugin {
21
22
  configureRoutes(server: IRpdServer, applicationConfig: RpdApplicationConfig): Promise<any>;
22
23
  onApplicationLoaded(server: IRpdServer, applicationConfig: RpdApplicationConfig): Promise<any>;
23
24
  onApplicationReady(server: IRpdServer, applicationConfig: RpdApplicationConfig): Promise<any>;
25
+ onPrepareRouteContext(server: IRpdServer, routeContext: RouteContext): Promise<void>;
24
26
  }
25
- export default AuthManager;
27
+ export default AuthPlugin;
@@ -5,7 +5,7 @@ declare const _default: ({
5
5
  type: "RESTful";
6
6
  method: "GET";
7
7
  endpoint: string;
8
- handlers: {
8
+ actions: {
9
9
  code: string;
10
10
  }[];
11
11
  } | {
@@ -15,7 +15,7 @@ declare const _default: ({
15
15
  type: "RESTful";
16
16
  method: "POST";
17
17
  endpoint: string;
18
- handlers: {
18
+ actions: {
19
19
  code: string;
20
20
  }[];
21
21
  })[];
@@ -1,5 +1,5 @@
1
- import { Secret } from "jsonwebtoken";
1
+ import { Secret, JwtPayload } from "jsonwebtoken";
2
2
  export declare function createJwt(payload: Record<string, any>, secret: Secret): string;
3
- export declare function verifyJwt(token: string, secret: Secret): string | import("jsonwebtoken").JwtPayload;
3
+ export declare function verifyJwt(token: string, secret: Secret): JwtPayload;
4
4
  export declare function decodeJwt(token: string): import("jsonwebtoken").Jwt;
5
5
  export declare function generateJwtSecretKey(): Promise<string>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ruiapp/rapid-core",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "keywords": [],
@@ -1,5 +1,6 @@
1
1
  import { RpdApplicationConfig } from "~/types";
2
2
  import { IRpdServer, RapidPlugin } from "./server";
3
+ import { RouteContext } from "./routeContext";
3
4
 
4
5
  class PluginManager {
5
6
  #server: IRpdServer;
@@ -139,6 +140,18 @@ class PluginManager {
139
140
  }
140
141
  }
141
142
  }
143
+
144
+ /** 在接收到HTTP请求,准备路由上下文时调用。 */
145
+ async onPrepareRouteContext(
146
+ server: IRpdServer,
147
+ routeContext: RouteContext,
148
+ ) {
149
+ for (const plugin of this.#plugins) {
150
+ if (plugin.onPrepareRouteContext) {
151
+ await plugin.onPrepareRouteContext(server, routeContext);
152
+ }
153
+ }
154
+ }
142
155
  }
143
156
 
144
157
  export default PluginManager;
@@ -1,5 +1,6 @@
1
1
  import qs from "qs";
2
2
  import { parseFormDataBody } from "./http/formDataParser";
3
+ import { getCookies } from "~/deno-std/http/cookie";
3
4
 
4
5
  export const GlobalRequest = global.Request;
5
6
 
@@ -12,15 +13,16 @@ export class RapidRequest {
12
13
  #raw: Request;
13
14
  #bodyParsed: boolean;
14
15
  #body: RapidRequestBody;
16
+ #headers: Headers;
17
+ #parsedCookies: Record<string, string>;
15
18
  method: string;
16
19
  url: URL;
17
- headers: Headers;
18
20
 
19
21
  constructor(req: Request) {
20
22
  this.#raw = req;
21
23
  this.method = req.method;
22
24
  this.url = new URL(req.url);
23
- this.headers = req.headers;
25
+ this.#headers = req.headers;
24
26
  }
25
27
 
26
28
  async parseBody(): Promise<void> {
@@ -32,7 +34,7 @@ export class RapidRequest {
32
34
  const requestMethod = this.method;
33
35
  if (requestMethod === "POST" || requestMethod === "PUT" || requestMethod === "PATCH") {
34
36
  const req = this.#raw;
35
- const contentType = this.headers.get("Content-Type");
37
+ const contentType = this.#headers.get("Content-Type");
36
38
  if (contentType.includes("json")) {
37
39
  this.#body = {
38
40
  type: "json",
@@ -60,6 +62,17 @@ export class RapidRequest {
60
62
  return this.#raw;
61
63
  }
62
64
 
65
+ get headers(): Headers {
66
+ return this.#headers;
67
+ }
68
+
69
+ get cookies(): Record<string, string> {
70
+ if (!this.#parsedCookies) {
71
+ this.#parsedCookies = getCookies(this.#headers);
72
+ }
73
+ return this.#parsedCookies;
74
+ }
75
+
63
76
  get body(): RapidRequestBody {
64
77
  if (!this.#bodyParsed) {
65
78
  throw new Error("Request body not parsed, you should call 'parseBody()' method before getting the body.")
@@ -1,6 +1,6 @@
1
1
  import { GetDataAccessorOptions, GetModelOptions, IDatabaseConfig, IQueryBuilder, IRpdDataAccessor, RapidServerConfig, RpdApplicationConfig, RpdDataModel, RpdServerEventTypes } from "~/types";
2
2
  import { IPluginActionHandler, ActionHandler } from "./actionHandler";
3
- import { Next } from "./routeContext";
3
+ import { Next, RouteContext } from "./routeContext";
4
4
 
5
5
  export interface IRpdServer {
6
6
  config: RapidServerConfig;
@@ -100,28 +100,29 @@ export interface RapidPlugin {
100
100
  /** 插件的全局配置项 */
101
101
  get configurations(): RpdConfigurationItemOptions[];
102
102
  /** 初始化插件时调用。插件可以在此时进行一些内部对象的初始化工作。 */
103
- initPlugin(server: IRpdServer): Promise<any>;
103
+ initPlugin?: (server: IRpdServer) => Promise<any>;
104
104
  /** 注册中间件 */
105
- registerMiddlewares(server: IRpdServer): Promise<any>;
105
+ registerMiddlewares?: (server: IRpdServer) => Promise<any>;
106
106
  /** 注册接口动作处理程序 */
107
- registerActionHandlers(server: IRpdServer): Promise<any>;
107
+ registerActionHandlers?: (server: IRpdServer) => Promise<any>;
108
108
  /** 注册事件处理程序 */
109
- registerEventHandlers(server: IRpdServer): Promise<any>;
109
+ registerEventHandlers?: (server: IRpdServer) => Promise<any>;
110
110
  /** 注册消息处理程序 */
111
- registerMessageHandlers(server: IRpdServer): Promise<any>;
111
+ registerMessageHandlers?: (server: IRpdServer) => Promise<any>;
112
112
  /** 注册任务处理程序 */
113
- registerTaskProcessors(server: IRpdServer): Promise<any>;
113
+ registerTaskProcessors?: (server: IRpdServer) => Promise<any>;
114
114
  /** 在加载应用前调用。 */
115
- onLoadingApplication(server: IRpdServer, applicationConfig: RpdApplicationConfig): Promise<any>;
115
+ onLoadingApplication?: (server: IRpdServer, applicationConfig: RpdApplicationConfig) => Promise<any>;
116
116
  /** 配置数据集合 */
117
- configureModels(server: IRpdServer, applicationConfig: RpdApplicationConfig): Promise<any>;
117
+ configureModels?: (server: IRpdServer, applicationConfig: RpdApplicationConfig) => Promise<any>;
118
118
  /** 配置模型属性 */
119
- configureModelProperties(server: IRpdServer, applicationConfig: RpdApplicationConfig): Promise<any>;
119
+ configureModelProperties?: (server: IRpdServer, applicationConfig: RpdApplicationConfig) => Promise<any>;
120
120
  /** 配置路由 */
121
- configureRoutes(server: IRpdServer, applicationConfig: RpdApplicationConfig): Promise<any>;
121
+ configureRoutes?: (server: IRpdServer, applicationConfig: RpdApplicationConfig) => Promise<any>;
122
122
  /** 在应用配置加载完成后调用。此时插件可以进行一些数据的初始化工作。 */
123
- onApplicationLoaded(server: IRpdServer, applicationConfig: RpdApplicationConfig): Promise<any>;
123
+ onApplicationLoaded?: (server: IRpdServer, applicationConfig: RpdApplicationConfig) => Promise<any>;
124
124
  /** 在应用准备完成后调用。此时服务器已经可以处理网络请求,可以对外广播消息。 */
125
- onApplicationReady(server: IRpdServer, applicationConfig: RpdApplicationConfig): Promise<any>;
125
+ onApplicationReady?: (server: IRpdServer, applicationConfig: RpdApplicationConfig) => Promise<any>;
126
+ /** 在接收到HTTP请求,准备路由上下文时调用。 */
127
+ onPrepareRouteContext?: (server: IRpdServer, routeContext: RouteContext) => Promise<any>;
126
128
  }
127
-
package/src/index.ts CHANGED
@@ -14,9 +14,9 @@ export * from "./utilities/jwtUtility";
14
14
 
15
15
  export * as bootstrapApplicationConfig from "./bootstrapApplicationConfig";
16
16
 
17
- export { default as MetaManagePlugin } from "./plugins/metaManage/mod";
18
- export { default as DataManagePlugin } from "./plugins/dataManage/mod";
19
- export { default as RouteManagePlugin } from "./plugins/routeManage/mod";
20
- export { default as WebhooksPlugin } from "./plugins/webhooks/mod";
21
- export { default as AuthPlugin } from "./plugins/auth/mod";
22
- export { default as FileManagePlugin } from "./plugins/fileManage/mod";
17
+ export { default as MetaManagePlugin } from "./plugins/metaManage/MetaManagePlugin";
18
+ export { default as DataManagePlugin } from "./plugins/dataManage/DataManagePlugin";
19
+ export { default as RouteManagePlugin } from "./plugins/routeManage/RouteManagePlugin";
20
+ export { default as WebhooksPlugin } from "./plugins/webhooks/WebhooksPlugin";
21
+ export { default as AuthPlugin } from "./plugins/auth/AuthPlugin";
22
+ export { default as FileManagePlugin } from "./plugins/fileManage/FileManagePlugin";
@@ -11,9 +11,11 @@ import { IRpdServer, RapidPlugin, RpdConfigurationItemOptions, RpdServerPluginCo
11
11
  import pluginActionHandlers from "./actionHandlers";
12
12
  import pluginModels from "./models";
13
13
  import pluginRoutes from "./routes";
14
+ import { RouteContext } from "~/core/routeContext";
15
+ import { verifyJwt } from "~/utilities/jwtUtility";
14
16
 
15
17
 
16
- class AuthManager implements RapidPlugin {
18
+ class AuthPlugin implements RapidPlugin {
17
19
  get code(): string {
18
20
  return "authManager";
19
21
  }
@@ -75,6 +77,29 @@ class AuthManager implements RapidPlugin {
75
77
 
76
78
  async onApplicationReady(server: IRpdServer, applicationConfig: RpdApplicationConfig): Promise<any> {
77
79
  }
80
+
81
+ async onPrepareRouteContext(server: IRpdServer, routeContext: RouteContext) {
82
+ const request = routeContext.request;
83
+ let token: string;
84
+
85
+ const headers = request.headers;
86
+ // No Authorization header
87
+ if (headers.has("Authorization")) {
88
+ // Authorization header has no Bearer or no token
89
+ const authHeader = headers.get("Authorization")!;
90
+ if (!authHeader.startsWith("Bearer ") || authHeader.length <= 7) {
91
+ throw new Error('AUTHORIZATION_HEADER_INVALID');
92
+ }
93
+
94
+ token = authHeader.slice(7);
95
+ } else {
96
+ token = request.cookies[server.config.sessionCookieName];
97
+ }
98
+
99
+ const tokenPayload = verifyJwt(token, server.config.jwtKey);
100
+ routeContext.state.userId = tokenPayload.aud as string;
101
+ routeContext.state.userLogin = tokenPayload.act as string;
102
+ }
78
103
  }
79
104
 
80
- export default AuthManager;
105
+ export default AuthPlugin;
package/src/server.ts CHANGED
@@ -230,6 +230,7 @@ export class RapidServer implements IRpdServer {
230
230
  const rapidRequest = new RapidRequest(request);
231
231
  await rapidRequest.parseBody();
232
232
  const routeContext = new RouteContext(rapidRequest);
233
+
233
234
  await this.#buildedRoutes(routeContext, next);
234
235
  return routeContext.response.getResponse();
235
236
  }
@@ -1,4 +1,4 @@
1
- import { sign, verify, decode, Secret } from "jsonwebtoken";
1
+ import { sign, verify, decode, Secret, JwtPayload } from "jsonwebtoken";
2
2
  import { encode as base64Encode } from "~/deno-std/encoding/base64";
3
3
  import crypto from "crypto";
4
4
 
@@ -8,10 +8,10 @@ export function createJwt(payload: Record<string, any>, secret: Secret) {
8
8
  });
9
9
  }
10
10
 
11
- export function verifyJwt(token: string, secret: Secret) {
11
+ export function verifyJwt(token: string, secret: Secret): JwtPayload {
12
12
  return verify(token, secret, {
13
13
  algorithms: ['HS512'],
14
- });
14
+ }) as JwtPayload;
15
15
  }
16
16
 
17
17
  export function decodeJwt(token: string) {