@ruiapp/rapid-core 0.9.1 → 0.9.3

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,4 +1,4 @@
1
- import { CreateEntityOptions, RpdApplicationConfig, RpdDataModel, UpdateEntityByIdOptions } from "../types";
1
+ import { CreateEntityOptions, RpdApplicationConfig, RpdDataModel, RpdRouteActionConfig, UpdateEntityByIdOptions } from "../types";
2
2
  import { IRpdServer, RapidPlugin } from "./server";
3
3
  import { RouteContext } from "./routeContext";
4
4
  import { ActionHandlerContext } from "./actionHandler";
@@ -38,6 +38,8 @@ declare class PluginManager {
38
38
  onPrepareRouteContext(routeContext: RouteContext): Promise<void>;
39
39
  /** 在接收到HTTP请求,执行 actions 前调用。 */
40
40
  beforeRunRouteActions(handlerContext: ActionHandlerContext): Promise<void>;
41
+ /** 在执行 action hanlder 前调用。 */
42
+ beforeRunActionHandler(handlerContext: ActionHandlerContext, actionConfig: RpdRouteActionConfig): Promise<void>;
41
43
  /** 在创建实体前调用。 */
42
44
  beforeCreateEntity(model: RpdDataModel, options: CreateEntityOptions): Promise<void>;
43
45
  /** 在更新实体前调用。 */
@@ -2,7 +2,7 @@ import { RapidRequest } from "./request";
2
2
  import { RapidResponse } from "./response";
3
3
  import { HttpStatus } from "./http-types";
4
4
  import { IRpdServer } from "./server";
5
- import { IDatabaseAccessor, IDatabaseClient } from "../types";
5
+ import { IDatabaseAccessor, IDatabaseClient, RpdRoute } from "../types";
6
6
  export type Next = () => Promise<void>;
7
7
  export type TransactionState = "uninited" | "inited" | "started";
8
8
  export declare class RouteContext {
@@ -14,7 +14,7 @@ export declare class RouteContext {
14
14
  method: string;
15
15
  path: string;
16
16
  params: Record<string, string>;
17
- routeConfig: any;
17
+ routeConfig: RpdRoute;
18
18
  static newSystemOperationContext(server: IRpdServer): RouteContext;
19
19
  constructor(server: IRpdServer, request?: RapidRequest);
20
20
  clone(): RouteContext;
@@ -1,4 +1,4 @@
1
- import { CreateEntityOptions, EmitServerEventOptions, EntityWatcherType, GetDataAccessorOptions, GetModelOptions, IDatabaseAccessor, IDatabaseClient, IDatabaseConfig, IQueryBuilder, IRpdDataAccessor, RapidServerConfig, RpdApplicationConfig, RpdDataModel, RpdDataModelProperty, RpdServerEventTypes, UpdateEntityByIdOptions } from "../types";
1
+ import { CreateEntityOptions, EmitServerEventOptions, EntityWatcherType, GetDataAccessorOptions, GetModelOptions, IDatabaseAccessor, IDatabaseClient, IDatabaseConfig, IQueryBuilder, IRpdDataAccessor, RapidServerConfig, RpdApplicationConfig, RpdDataModel, RpdDataModelProperty, RpdRouteActionConfig, RpdServerEventTypes, UpdateEntityByIdOptions } from "../types";
2
2
  import { IPluginActionHandler, ActionHandler, ActionHandlerContext } from "./actionHandler";
3
3
  import { Next, RouteContext } from "./routeContext";
4
4
  import EntityManager from "../dataAccess/entityManager";
@@ -32,6 +32,7 @@ export interface IRpdServer {
32
32
  emitEvent<TEventName extends keyof RpdServerEventTypes>(event: EmitServerEventOptions<TEventName>): void;
33
33
  handleRequest(request: RapidRequest, next: Next): Promise<Response>;
34
34
  beforeRunRouteActions(handlerContext: ActionHandlerContext): Promise<void>;
35
+ beforeRunActionHandler(handlerContext: ActionHandlerContext, actionConfig: RpdRouteActionConfig): Promise<void>;
35
36
  beforeCreateEntity(model: RpdDataModel, options: CreateEntityOptions): Promise<void>;
36
37
  beforeUpdateEntity(model: RpdDataModel, options: UpdateEntityByIdOptions, currentEntity: any): Promise<void>;
37
38
  registerCronJob(job: CronJobConfiguration): void;
@@ -108,6 +109,8 @@ export interface RapidPlugin {
108
109
  onPrepareRouteContext?: (server: IRpdServer, routeContext: RouteContext) => Promise<any>;
109
110
  /** 在接收到HTTP请求,执行 actions 前调用。 */
110
111
  beforeRunRouteActions?: (server: IRpdServer, handlerContext: ActionHandlerContext) => Promise<any>;
112
+ /** 在执行 action hanlder 前调用。 */
113
+ beforeRunActionHandler?: (server: IRpdServer, handlerContext: ActionHandlerContext, actionConfig: RpdRouteActionConfig) => Promise<any>;
111
114
  /** 在创建实体前调用。 */
112
115
  beforeCreateEntity?: (server: IRpdServer, model: RpdDataModel, options: CreateEntityOptions) => Promise<any>;
113
116
  /** 在更新实体前调用。 */
package/dist/index.d.ts CHANGED
@@ -15,6 +15,7 @@ export * from "./utilities/accessControlUtility";
15
15
  export * from "./utilities/entityUtility";
16
16
  export * from "./utilities/jwtUtility";
17
17
  export * from "./utilities/timeUtility";
18
+ export * from "./utilities/passwordUtility";
18
19
  export * from "./helpers/entityHelpers";
19
20
  export * from "./helpers/licenseHelper";
20
21
  export * from "./deno-std/http/cookie";
@@ -29,6 +30,7 @@ export { default as SequencePlugin } from "./plugins/sequence/SequencePlugin";
29
30
  export * from "./plugins/sequence/SequencePluginTypes";
30
31
  export { default as WebhooksPlugin } from "./plugins/webhooks/WebhooksPlugin";
31
32
  export { default as AuthPlugin } from "./plugins/auth/AuthPlugin";
33
+ export * from "./plugins/auth/AuthPluginTypes";
32
34
  export { default as FileManagePlugin } from "./plugins/fileManage/FileManagePlugin";
33
35
  export { default as LicensePlugin } from "./plugins/license/LicensePlugin";
34
36
  export * from "./plugins/license/LicensePluginTypes";
package/dist/index.js CHANGED
@@ -9,7 +9,7 @@ var qs = require('qs');
9
9
  var dayjs = require('dayjs');
10
10
  var jsonwebtoken = require('jsonwebtoken');
11
11
  var crypto = require('crypto');
12
- var bcrypt = require('bcrypt');
12
+ var bcrypt = require('bcryptjs');
13
13
  var path = require('path');
14
14
  var fs = require('fs');
15
15
  var uuid = require('uuid');
@@ -928,6 +928,14 @@ class PluginManager {
928
928
  }
929
929
  }
930
930
  }
931
+ /** 在执行 action hanlder 前调用。 */
932
+ async beforeRunActionHandler(handlerContext, actionConfig) {
933
+ for (const plugin of this.#plugins) {
934
+ if (plugin.beforeRunActionHandler) {
935
+ await plugin.beforeRunActionHandler(this.#server, handlerContext, actionConfig);
936
+ }
937
+ }
938
+ }
931
939
  /** 在创建实体前调用。 */
932
940
  async beforeCreateEntity(model, options) {
933
941
  for (const plugin of this.#plugins) {
@@ -1030,6 +1038,7 @@ async function buildRoutes(server, applicationConfig) {
1030
1038
  if (!handler) {
1031
1039
  throw new Error("Unknown handler: " + actionCode);
1032
1040
  }
1041
+ await server.beforeRunActionHandler(handlerContext, actionConfig);
1033
1042
  const result = handler(handlerContext, actionConfig.config);
1034
1043
  if (result instanceof Promise) {
1035
1044
  await result;
@@ -4301,6 +4310,9 @@ class RapidServer {
4301
4310
  async beforeRunRouteActions(handlerContext) {
4302
4311
  await this.#pluginManager.beforeRunRouteActions(handlerContext);
4303
4312
  }
4313
+ async beforeRunActionHandler(handlerContext, actionConfig) {
4314
+ await this.#pluginManager.beforeRunActionHandler(handlerContext, actionConfig);
4315
+ }
4304
4316
  async beforeCreateEntity(model, options) {
4305
4317
  await this.#pluginManager.beforeCreateEntity(model, options);
4306
4318
  }
@@ -5015,6 +5027,30 @@ async function generateJwtSecretKey() {
5015
5027
  return encode(exportedKey);
5016
5028
  }
5017
5029
 
5030
+ /**
5031
+ * Generates password hash.
5032
+ * @param password
5033
+ * @param salt
5034
+ * @returns
5035
+ */
5036
+ async function generatePasswordHash(password, salt) {
5037
+ if (!salt) {
5038
+ salt = 10;
5039
+ }
5040
+ const passwordHash = await bcrypt__default["default"].hash(password, salt);
5041
+ return passwordHash;
5042
+ }
5043
+ /**
5044
+ * Validates the password against the hash.
5045
+ * @param password
5046
+ * @param passwordHash
5047
+ * @returns
5048
+ */
5049
+ async function validatePassword(password, passwordHash) {
5050
+ const isMatch = await bcrypt__default["default"].compare(password, passwordHash);
5051
+ return isMatch;
5052
+ }
5053
+
5018
5054
  function validateLicense(server) {
5019
5055
  const licenseService = server.getService("licenseService");
5020
5056
  const license = licenseService.getLicense();
@@ -6010,66 +6046,66 @@ var queryDatabase = /*#__PURE__*/Object.freeze({
6010
6046
  * This plugin provide:
6011
6047
  * - routes for manage data in database.
6012
6048
  */
6013
- const routeConfigs = [
6049
+ const entityOperationConfigs = [
6014
6050
  {
6015
- code: "createBatch",
6016
- method: "POST",
6017
- endpoint: "/operations/create_batch",
6018
- handlerCode: "createCollectionEntitiesBatch",
6051
+ operationCode: "createBatch",
6052
+ httpMethod: "POST",
6053
+ requestEndpoint: "/operations/create_batch",
6054
+ actionHandlerCode: "createCollectionEntitiesBatch",
6019
6055
  },
6020
6056
  {
6021
- code: "find",
6022
- method: "POST",
6023
- endpoint: "/operations/find",
6024
- handlerCode: "findCollectionEntities",
6057
+ operationCode: "find",
6058
+ httpMethod: "POST",
6059
+ requestEndpoint: "/operations/find",
6060
+ actionHandlerCode: "findCollectionEntities",
6025
6061
  },
6026
6062
  {
6027
- code: "count",
6028
- method: "POST",
6029
- endpoint: "/operations/count",
6030
- handlerCode: "countCollectionEntities",
6063
+ operationCode: "count",
6064
+ httpMethod: "POST",
6065
+ requestEndpoint: "/operations/count",
6066
+ actionHandlerCode: "countCollectionEntities",
6031
6067
  },
6032
6068
  {
6033
- code: "delete",
6034
- method: "POST",
6035
- endpoint: "/operations/delete",
6036
- handlerCode: "deleteCollectionEntities",
6069
+ operationCode: "delete",
6070
+ httpMethod: "POST",
6071
+ requestEndpoint: "/operations/delete",
6072
+ actionHandlerCode: "deleteCollectionEntities",
6037
6073
  },
6038
6074
  {
6039
- code: "addRelations",
6040
- method: "POST",
6041
- endpoint: "/operations/add_relations",
6042
- handlerCode: "addEntityRelations",
6075
+ operationCode: "addRelations",
6076
+ httpMethod: "POST",
6077
+ requestEndpoint: "/operations/add_relations",
6078
+ actionHandlerCode: "addEntityRelations",
6043
6079
  },
6044
6080
  {
6045
- code: "removeRelations",
6046
- method: "POST",
6047
- endpoint: "/operations/remove_relations",
6048
- handlerCode: "removeEntityRelations",
6081
+ operationCode: "removeRelations",
6082
+ httpMethod: "POST",
6083
+ requestEndpoint: "/operations/remove_relations",
6084
+ actionHandlerCode: "removeEntityRelations",
6049
6085
  },
6050
6086
  {
6051
- code: "getById",
6052
- method: "GET",
6053
- endpoint: "/:id",
6054
- handlerCode: "findCollectionEntityById",
6087
+ operationCode: "getById",
6088
+ httpMethod: "GET",
6089
+ requestEndpoint: "/:id",
6090
+ actionHandlerCode: "findCollectionEntityById",
6055
6091
  },
6056
6092
  {
6057
- code: "create",
6058
- method: "POST",
6059
- endpoint: "",
6060
- handlerCode: "createCollectionEntity",
6093
+ operationCode: "create",
6094
+ httpMethod: "POST",
6095
+ requestEndpoint: "",
6096
+ actionHandlerCode: "createCollectionEntity",
6061
6097
  },
6062
6098
  {
6063
- code: "updateById",
6064
- method: "PATCH",
6065
- endpoint: "/:id",
6066
- handlerCode: "updateCollectionEntityById",
6099
+ operationCode: "updateById",
6100
+ httpMethod: "PATCH",
6101
+ requestEndpoint: "/:id",
6102
+ actionHandlerCode: "updateCollectionEntityById",
6067
6103
  },
6068
6104
  {
6069
- code: "deleteById",
6070
- method: "DELETE",
6071
- endpoint: "/:id",
6072
- handlerCode: "deleteCollectionEntityById",
6105
+ operationCode: "deleteById",
6106
+ httpMethod: "DELETE",
6107
+ requestEndpoint: "/:id",
6108
+ actionHandlerCode: "deleteCollectionEntityById",
6073
6109
  },
6074
6110
  ];
6075
6111
  class DataManager {
@@ -6106,17 +6142,17 @@ class DataManager {
6106
6142
  const routes = [];
6107
6143
  models.forEach((model) => {
6108
6144
  const { namespace, singularCode, pluralCode } = model;
6109
- routeConfigs.forEach((routeConfig) => {
6145
+ entityOperationConfigs.forEach((entityOperationConfig) => {
6110
6146
  routes.push({
6111
6147
  namespace,
6112
- name: `${namespace}.${singularCode}.${routeConfig.code}`,
6113
- code: `${namespace}.${singularCode}.${routeConfig.code}`,
6148
+ name: `${namespace}.${singularCode}.${entityOperationConfig.operationCode}`,
6149
+ code: `${namespace}.${singularCode}.${entityOperationConfig.operationCode}`,
6114
6150
  type: "RESTful",
6115
- method: routeConfig.method,
6116
- endpoint: `/${namespace}/${pluralCode}${routeConfig.endpoint}`,
6151
+ method: entityOperationConfig.httpMethod,
6152
+ endpoint: `/${namespace}/${pluralCode}${entityOperationConfig.requestEndpoint}`,
6117
6153
  actions: [
6118
6154
  {
6119
- code: routeConfig.handlerCode,
6155
+ code: entityOperationConfig.actionHandlerCode,
6120
6156
  config: {
6121
6157
  namespace,
6122
6158
  singularCode,
@@ -6876,8 +6912,9 @@ async function handler$e(plugin, ctx, options) {
6876
6912
  };
6877
6913
  return;
6878
6914
  }
6915
+ const userEntitySingularCode = plugin.options?.userEntitySingularCode || "oc_user";
6879
6916
  const userDataAccessor = server.getDataAccessor({
6880
- singularCode: "oc_user",
6917
+ singularCode: userEntitySingularCode,
6881
6918
  });
6882
6919
  const user = await userDataAccessor.findOne({
6883
6920
  filters: [
@@ -6891,12 +6928,11 @@ async function handler$e(plugin, ctx, options) {
6891
6928
  if (!user) {
6892
6929
  throw new Error("User not found.");
6893
6930
  }
6894
- const isMatch = await bcrypt__default["default"].compare(oldPassword, user.password);
6931
+ const isMatch = await validatePassword(oldPassword, user.password);
6895
6932
  if (!isMatch) {
6896
6933
  throw new Error("旧密码错误。");
6897
6934
  }
6898
- const saltRounds = 10;
6899
- const passwordHash = await bcrypt__default["default"].hash(newPassword, saltRounds);
6935
+ const passwordHash = await generatePasswordHash(newPassword);
6900
6936
  await userDataAccessor.updateById(user.id, {
6901
6937
  password: passwordHash,
6902
6938
  }, routeContext?.getDbTransactionClient());
@@ -6915,8 +6951,9 @@ async function handler$d(plugin, ctx, options) {
6915
6951
  const { response } = routeContext;
6916
6952
  const { account, password } = input;
6917
6953
  validateLicense(server);
6954
+ const userEntitySingularCode = plugin.options?.userEntitySingularCode || "oc_user";
6918
6955
  const userDataAccessor = server.getDataAccessor({
6919
- singularCode: "oc_user",
6956
+ singularCode: userEntitySingularCode,
6920
6957
  });
6921
6958
  const user = await userDataAccessor.findOne({
6922
6959
  filters: [
@@ -6937,7 +6974,7 @@ async function handler$d(plugin, ctx, options) {
6937
6974
  if (user.state !== "enabled") {
6938
6975
  throw new Error("用户已被禁用,不允许登录。");
6939
6976
  }
6940
- const isMatch = await bcrypt__default["default"].compare(password, user.password);
6977
+ const isMatch = await validatePassword(password, user.password);
6941
6978
  if (!isMatch) {
6942
6979
  throw new Error("用户名或密码错误。");
6943
6980
  }
@@ -6994,7 +7031,9 @@ async function handler$b(plugin, ctx, options) {
6994
7031
  };
6995
7032
  return;
6996
7033
  }
6997
- const entityManager = server.getEntityManager("oc_user");
7034
+ const userEntitySingularCode = plugin.options?.userEntitySingularCode || "oc_user";
7035
+ const profilePropertyCodes = plugin.options?.profilePropertyCodes || ["id", "name", "login", "email", "department", "roles", "state", "createdAt"];
7036
+ const entityManager = server.getEntityManager(userEntitySingularCode);
6998
7037
  const user = await entityManager.findEntity({
6999
7038
  filters: [
7000
7039
  {
@@ -7003,7 +7042,7 @@ async function handler$b(plugin, ctx, options) {
7003
7042
  value: userId,
7004
7043
  },
7005
7044
  ],
7006
- properties: ["id", "name", "login", "email", "department", "roles", "state", "createdAt"],
7045
+ properties: profilePropertyCodes,
7007
7046
  });
7008
7047
  ctx.output = {
7009
7048
  user,
@@ -7020,8 +7059,9 @@ const code$a = "resetPassword";
7020
7059
  async function handler$a(plugin, ctx, options) {
7021
7060
  const { server, input, routerContext: routeContext } = ctx;
7022
7061
  const { userId, password } = input;
7062
+ const userEntitySingularCode = plugin.options?.userEntitySingularCode || "oc_user";
7023
7063
  const userDataAccessor = server.getDataAccessor({
7024
- singularCode: "oc_user",
7064
+ singularCode: userEntitySingularCode,
7025
7065
  });
7026
7066
  const user = await userDataAccessor.findOne({
7027
7067
  filters: [
@@ -7035,8 +7075,7 @@ async function handler$a(plugin, ctx, options) {
7035
7075
  if (!user) {
7036
7076
  throw new Error("User not found.");
7037
7077
  }
7038
- const saltRounds = 10;
7039
- const passwordHash = await bcrypt__default["default"].hash(password, saltRounds);
7078
+ const passwordHash = await generatePasswordHash(password);
7040
7079
  await userDataAccessor.updateById(user.id, {
7041
7080
  password: passwordHash,
7042
7081
  }, routeContext?.getDbTransactionClient());
@@ -7204,7 +7243,14 @@ class AuthService {
7204
7243
  * Auth manager plugin
7205
7244
  */
7206
7245
  class AuthPlugin {
7246
+ #options;
7207
7247
  #authService;
7248
+ constructor(options) {
7249
+ this.#options = Object.freeze(options);
7250
+ }
7251
+ get options() {
7252
+ return this.#options;
7253
+ }
7208
7254
  get code() {
7209
7255
  return "authManager";
7210
7256
  }
@@ -9558,6 +9604,7 @@ exports.deleteCookie = deleteCookie;
9558
9604
  exports.detectChangedFieldsOfEntity = detectChangedFieldsOfEntity;
9559
9605
  exports.formatDateTimeWithTimezone = formatDateTimeWithTimezone;
9560
9606
  exports.generateJwtSecretKey = generateJwtSecretKey;
9607
+ exports.generatePasswordHash = generatePasswordHash;
9561
9608
  exports.getCookies = getCookies;
9562
9609
  exports.getDateString = getDateString;
9563
9610
  exports.getEntityRelationTargetId = getEntityRelationTargetId;
@@ -9569,4 +9616,5 @@ exports.mapDbRowToEntity = mapDbRowToEntity;
9569
9616
  exports.setCookie = setCookie;
9570
9617
  exports.tryValidateLicense = tryValidateLicense;
9571
9618
  exports.validateLicense = validateLicense;
9619
+ exports.validatePassword = validatePassword;
9572
9620
  exports.verifyJwt = verifyJwt;
@@ -4,8 +4,11 @@
4
4
  import { RpdApplicationConfig } from "../../types";
5
5
  import { IRpdServer, RapidPlugin, RpdConfigurationItemOptions, RpdServerPluginConfigurableTargetOptions, RpdServerPluginExtendingAbilities } from "../../core/server";
6
6
  import { RouteContext } from "../../core/routeContext";
7
+ import { AuthPluginInitOptions } from "./AuthPluginTypes";
7
8
  declare class AuthPlugin implements RapidPlugin {
8
9
  #private;
10
+ constructor(options: AuthPluginInitOptions);
11
+ get options(): AuthPluginInitOptions;
9
12
  get code(): string;
10
13
  get description(): string;
11
14
  get extendingAbilities(): RpdServerPluginExtendingAbilities[];
@@ -0,0 +1,10 @@
1
+ export type AuthPluginInitOptions = {
2
+ /**
3
+ * 用户实体代号。默认为`oc_user`
4
+ */
5
+ userEntitySingularCode?: string;
6
+ /**
7
+ * 个人资料属性。默认为`["id", "name", "login", "email", "department", "roles", "state", "createdAt"]`
8
+ */
9
+ profilePropertyCodes?: string[];
10
+ };
@@ -1,4 +1,4 @@
1
1
  import { ActionHandlerContext } from "../../../core/actionHandler";
2
- import { RapidPlugin } from "../../../core/server";
2
+ import AuthPlugin from "../AuthPlugin";
3
3
  export declare const code = "changePassword";
4
- export declare function handler(plugin: RapidPlugin, ctx: ActionHandlerContext, options: any): Promise<void>;
4
+ export declare function handler(plugin: AuthPlugin, ctx: ActionHandlerContext, options: any): Promise<void>;
@@ -1,4 +1,4 @@
1
1
  import { ActionHandlerContext } from "../../../core/actionHandler";
2
- import { RapidPlugin } from "../../../core/server";
2
+ import AuthPlugin from "../AuthPlugin";
3
3
  export declare const code = "createSession";
4
- export declare function handler(plugin: RapidPlugin, ctx: ActionHandlerContext, options: any): Promise<void>;
4
+ export declare function handler(plugin: AuthPlugin, ctx: ActionHandlerContext, options: any): Promise<void>;
@@ -1,4 +1,4 @@
1
1
  import { ActionHandlerContext } from "../../../core/actionHandler";
2
- import { RapidPlugin } from "../../../core/server";
2
+ import AuthPlugin from "../AuthPlugin";
3
3
  export declare const code = "getMyProfile";
4
- export declare function handler(plugin: RapidPlugin, ctx: ActionHandlerContext, options: any): Promise<void>;
4
+ export declare function handler(plugin: AuthPlugin, ctx: ActionHandlerContext, options: any): Promise<void>;
@@ -1,4 +1,4 @@
1
1
  import { ActionHandlerContext } from "../../../core/actionHandler";
2
- import { RapidPlugin } from "../../../core/server";
2
+ import AuthPlugin from "../AuthPlugin";
3
3
  export declare const code = "resetPassword";
4
- export declare function handler(plugin: RapidPlugin, ctx: ActionHandlerContext, options: any): Promise<void>;
4
+ export declare function handler(plugin: AuthPlugin, ctx: ActionHandlerContext, options: any): Promise<void>;
package/dist/server.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { GetDataAccessorOptions, GetModelOptions, IDatabaseAccessor, IDatabaseConfig, IQueryBuilder, IRpdDataAccessor, RpdApplicationConfig, RpdDataModel, RpdServerEventTypes, RapidServerConfig, RpdDataModelProperty, CreateEntityOptions, UpdateEntityByIdOptions, EntityWatcherType, EmitServerEventOptions, IDatabaseClient } from "./types";
1
+ import { GetDataAccessorOptions, GetModelOptions, IDatabaseAccessor, IDatabaseConfig, IQueryBuilder, IRpdDataAccessor, RpdApplicationConfig, RpdDataModel, RpdServerEventTypes, RapidServerConfig, RpdDataModelProperty, CreateEntityOptions, UpdateEntityByIdOptions, EntityWatcherType, EmitServerEventOptions, IDatabaseClient, RpdRouteActionConfig } from "./types";
2
2
  import { ActionHandler, ActionHandlerContext, IPluginActionHandler } from "./core/actionHandler";
3
3
  import { IRpdServer, RapidPlugin } from "./core/server";
4
4
  import { Next } from "./core/routeContext";
@@ -58,6 +58,7 @@ export declare class RapidServer implements IRpdServer {
58
58
  get middlewares(): any[];
59
59
  handleRequest(rapidRequest: RapidRequest, next: Next): Promise<Response>;
60
60
  beforeRunRouteActions(handlerContext: ActionHandlerContext): Promise<void>;
61
+ beforeRunActionHandler(handlerContext: ActionHandlerContext, actionConfig: RpdRouteActionConfig): Promise<void>;
61
62
  beforeCreateEntity(model: RpdDataModel, options: CreateEntityOptions): Promise<void>;
62
63
  beforeUpdateEntity(model: RpdDataModel, options: UpdateEntityByIdOptions, currentEntity: any): Promise<void>;
63
64
  }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Generates password hash.
3
+ * @param password
4
+ * @param salt
5
+ * @returns
6
+ */
7
+ export declare function generatePasswordHash(password: string, salt?: number | string): Promise<string>;
8
+ /**
9
+ * Validates the password against the hash.
10
+ * @param password
11
+ * @param passwordHash
12
+ * @returns
13
+ */
14
+ export declare function validatePassword(password: string, passwordHash: string): Promise<boolean>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ruiapp/rapid-core",
3
- "version": "0.9.1",
3
+ "version": "0.9.3",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -19,7 +19,7 @@
19
19
  "typescript": "^4.8.4"
20
20
  },
21
21
  "dependencies": {
22
- "bcrypt": "^5.1.1",
22
+ "bcryptjs": "^3.0.2",
23
23
  "cron": "^3.1.7",
24
24
  "dayjs": "^1.11.7",
25
25
  "jsonwebtoken": "^9.0.2",
@@ -1,4 +1,4 @@
1
- import { CreateEntityOptions, RpdApplicationConfig, RpdDataModel, UpdateEntityByIdOptions } from "~/types";
1
+ import { CreateEntityOptions, RpdApplicationConfig, RpdDataModel, RpdRouteActionConfig, UpdateEntityByIdOptions } from "~/types";
2
2
  import { IRpdServer, RapidPlugin } from "./server";
3
3
  import { RouteContext } from "./routeContext";
4
4
  import { ActionHandlerContext } from "./actionHandler";
@@ -162,6 +162,15 @@ class PluginManager {
162
162
  }
163
163
  }
164
164
 
165
+ /** 在执行 action hanlder 前调用。 */
166
+ async beforeRunActionHandler(handlerContext: ActionHandlerContext, actionConfig: RpdRouteActionConfig) {
167
+ for (const plugin of this.#plugins) {
168
+ if (plugin.beforeRunActionHandler) {
169
+ await plugin.beforeRunActionHandler(this.#server, handlerContext, actionConfig);
170
+ }
171
+ }
172
+ }
173
+
165
174
  /** 在创建实体前调用。 */
166
175
  async beforeCreateEntity(model: RpdDataModel, options: CreateEntityOptions) {
167
176
  for (const plugin of this.#plugins) {
@@ -2,7 +2,7 @@ import { RapidRequest } from "./request";
2
2
  import { RapidResponse } from "./response";
3
3
  import { HttpStatus } from "./http-types";
4
4
  import { IRpdServer } from "./server";
5
- import { IDatabaseAccessor, IDatabaseClient } from "~/types";
5
+ import { IDatabaseAccessor, IDatabaseClient, RpdRoute } from "~/types";
6
6
 
7
7
  export type Next = () => Promise<void>;
8
8
 
@@ -18,7 +18,7 @@ export class RouteContext {
18
18
  method: string;
19
19
  path: string;
20
20
  params: Record<string, string>;
21
- routeConfig: any;
21
+ routeConfig: RpdRoute;
22
22
  #server: IRpdServer;
23
23
  #dbTransactionClient: IDatabaseClient | undefined;
24
24
  #dbTransactionState: TransactionState;
@@ -74,6 +74,8 @@ export async function buildRoutes(server: IRpdServer, applicationConfig: RpdAppl
74
74
  throw new Error("Unknown handler: " + actionCode);
75
75
  }
76
76
 
77
+ await server.beforeRunActionHandler(handlerContext, actionConfig);
78
+
77
79
  const result = handler(handlerContext, actionConfig.config);
78
80
  if (result instanceof Promise) {
79
81
  await result;
@@ -13,6 +13,7 @@ import {
13
13
  RpdApplicationConfig,
14
14
  RpdDataModel,
15
15
  RpdDataModelProperty,
16
+ RpdRouteActionConfig,
16
17
  RpdServerEventTypes,
17
18
  UpdateEntityByIdOptions,
18
19
  } from "~/types";
@@ -53,6 +54,7 @@ export interface IRpdServer {
53
54
  emitEvent<TEventName extends keyof RpdServerEventTypes>(event: EmitServerEventOptions<TEventName>): void;
54
55
  handleRequest(request: RapidRequest, next: Next): Promise<Response>;
55
56
  beforeRunRouteActions(handlerContext: ActionHandlerContext): Promise<void>;
57
+ beforeRunActionHandler(handlerContext: ActionHandlerContext, actionConfig: RpdRouteActionConfig): Promise<void>;
56
58
  beforeCreateEntity(model: RpdDataModel, options: CreateEntityOptions): Promise<void>;
57
59
  beforeUpdateEntity(model: RpdDataModel, options: UpdateEntityByIdOptions, currentEntity: any): Promise<void>;
58
60
 
@@ -145,6 +147,8 @@ export interface RapidPlugin {
145
147
  onPrepareRouteContext?: (server: IRpdServer, routeContext: RouteContext) => Promise<any>;
146
148
  /** 在接收到HTTP请求,执行 actions 前调用。 */
147
149
  beforeRunRouteActions?: (server: IRpdServer, handlerContext: ActionHandlerContext) => Promise<any>;
150
+ /** 在执行 action hanlder 前调用。 */
151
+ beforeRunActionHandler?: (server: IRpdServer, handlerContext: ActionHandlerContext, actionConfig: RpdRouteActionConfig) => Promise<any>;
148
152
  /** 在创建实体前调用。 */
149
153
  beforeCreateEntity?: (server: IRpdServer, model: RpdDataModel, options: CreateEntityOptions) => Promise<any>;
150
154
  /** 在更新实体前调用。 */
package/src/index.ts CHANGED
@@ -22,6 +22,7 @@ export * from "./utilities/accessControlUtility";
22
22
  export * from "./utilities/entityUtility";
23
23
  export * from "./utilities/jwtUtility";
24
24
  export * from "./utilities/timeUtility";
25
+ export * from "./utilities/passwordUtility";
25
26
 
26
27
  export * from "./helpers/entityHelpers";
27
28
  export * from "./helpers/licenseHelper";
@@ -47,6 +48,7 @@ export * from "./plugins/sequence/SequencePluginTypes";
47
48
  export { default as WebhooksPlugin } from "./plugins/webhooks/WebhooksPlugin";
48
49
 
49
50
  export { default as AuthPlugin } from "./plugins/auth/AuthPlugin";
51
+ export * from "./plugins/auth/AuthPluginTypes";
50
52
 
51
53
  export { default as FileManagePlugin } from "./plugins/fileManage/FileManagePlugin";
52
54
 
@@ -17,10 +17,20 @@ import pluginRoutes from "./routes";
17
17
  import { RouteContext } from "~/core/routeContext";
18
18
  import { verifyJwt } from "~/utilities/jwtUtility";
19
19
  import AuthService from "./services/AuthService";
20
+ import { AuthPluginInitOptions } from "./AuthPluginTypes";
20
21
 
21
22
  class AuthPlugin implements RapidPlugin {
23
+ #options: AuthPluginInitOptions;
22
24
  #authService!: AuthService;
23
25
 
26
+ constructor(options: AuthPluginInitOptions) {
27
+ this.#options = Object.freeze(options);
28
+ }
29
+
30
+ get options(): AuthPluginInitOptions {
31
+ return this.#options;
32
+ }
33
+
24
34
  get code(): string {
25
35
  return "authManager";
26
36
  }
@@ -0,0 +1,11 @@
1
+ export type AuthPluginInitOptions = {
2
+ /**
3
+ * 用户实体代号。默认为`oc_user`
4
+ */
5
+ userEntitySingularCode?: string;
6
+
7
+ /**
8
+ * 个人资料属性。默认为`["id", "name", "login", "email", "department", "roles", "state", "createdAt"]`
9
+ */
10
+ profilePropertyCodes?: string[];
11
+ };
@@ -1,10 +1,10 @@
1
- import bcrypt from "bcrypt";
2
1
  import { ActionHandlerContext } from "~/core/actionHandler";
3
- import { RapidPlugin } from "~/core/server";
2
+ import AuthPlugin from "../AuthPlugin";
3
+ import { generatePasswordHash, validatePassword } from "~/utilities/passwordUtility";
4
4
 
5
5
  export const code = "changePassword";
6
6
 
7
- export async function handler(plugin: RapidPlugin, ctx: ActionHandlerContext, options: any) {
7
+ export async function handler(plugin: AuthPlugin, ctx: ActionHandlerContext, options: any) {
8
8
  const { server, input, routerContext: routeContext } = ctx;
9
9
  const { response } = routeContext;
10
10
  const { id, oldPassword, newPassword } = input;
@@ -20,8 +20,9 @@ export async function handler(plugin: RapidPlugin, ctx: ActionHandlerContext, op
20
20
  return;
21
21
  }
22
22
 
23
+ const userEntitySingularCode = plugin.options?.userEntitySingularCode || "oc_user";
23
24
  const userDataAccessor = server.getDataAccessor({
24
- singularCode: "oc_user",
25
+ singularCode: userEntitySingularCode,
25
26
  });
26
27
 
27
28
  const user = await userDataAccessor.findOne(
@@ -41,13 +42,12 @@ export async function handler(plugin: RapidPlugin, ctx: ActionHandlerContext, op
41
42
  throw new Error("User not found.");
42
43
  }
43
44
 
44
- const isMatch = await bcrypt.compare(oldPassword, user.password);
45
+ const isMatch = await validatePassword(oldPassword, user.password);
45
46
  if (!isMatch) {
46
47
  throw new Error("旧密码错误。");
47
48
  }
48
49
 
49
- const saltRounds = 10;
50
- const passwordHash = await bcrypt.hash(newPassword, saltRounds);
50
+ const passwordHash = await generatePasswordHash(newPassword);
51
51
 
52
52
  await userDataAccessor.updateById(
53
53
  user.id,
@@ -1,21 +1,22 @@
1
- import bcrypt from "bcrypt";
2
1
  import { setCookie } from "~/deno-std/http/cookie";
3
2
  import { ActionHandlerContext } from "~/core/actionHandler";
4
- import { RapidPlugin } from "~/core/server";
5
3
  import AuthService from "../services/AuthService";
6
4
  import { validateLicense } from "~/helpers/licenseHelper";
5
+ import AuthPlugin from "../AuthPlugin";
6
+ import { validatePassword } from "~/utilities/passwordUtility";
7
7
 
8
8
  export const code = "createSession";
9
9
 
10
- export async function handler(plugin: RapidPlugin, ctx: ActionHandlerContext, options: any) {
10
+ export async function handler(plugin: AuthPlugin, ctx: ActionHandlerContext, options: any) {
11
11
  const { server, input, routerContext: routeContext, logger } = ctx;
12
12
  const { response } = routeContext;
13
13
  const { account, password } = input;
14
14
 
15
15
  validateLicense(server);
16
16
 
17
+ const userEntitySingularCode = plugin.options?.userEntitySingularCode || "oc_user";
17
18
  const userDataAccessor = server.getDataAccessor({
18
- singularCode: "oc_user",
19
+ singularCode: userEntitySingularCode,
19
20
  });
20
21
 
21
22
  const user = await userDataAccessor.findOne(
@@ -43,7 +44,7 @@ export async function handler(plugin: RapidPlugin, ctx: ActionHandlerContext, op
43
44
  throw new Error("用户已被禁用,不允许登录。");
44
45
  }
45
46
 
46
- const isMatch = await bcrypt.compare(password, user.password);
47
+ const isMatch = await validatePassword(password, user.password);
47
48
  if (!isMatch) {
48
49
  throw new Error("用户名或密码错误。");
49
50
  }
@@ -1,9 +1,10 @@
1
1
  import { ActionHandlerContext } from "~/core/actionHandler";
2
2
  import { RapidPlugin } from "~/core/server";
3
+ import AuthPlugin from "../AuthPlugin";
3
4
 
4
5
  export const code = "getMyProfile";
5
6
 
6
- export async function handler(plugin: RapidPlugin, ctx: ActionHandlerContext, options: any) {
7
+ export async function handler(plugin: AuthPlugin, ctx: ActionHandlerContext, options: any) {
7
8
  const { server, input, routerContext } = ctx;
8
9
 
9
10
  const userId = routerContext.state.userId;
@@ -17,7 +18,9 @@ export async function handler(plugin: RapidPlugin, ctx: ActionHandlerContext, op
17
18
  return;
18
19
  }
19
20
 
20
- const entityManager = server.getEntityManager("oc_user");
21
+ const userEntitySingularCode = plugin.options?.userEntitySingularCode || "oc_user";
22
+ const profilePropertyCodes = plugin.options?.profilePropertyCodes || ["id", "name", "login", "email", "department", "roles", "state", "createdAt"];
23
+ const entityManager = server.getEntityManager(userEntitySingularCode);
21
24
  const user = await entityManager.findEntity({
22
25
  filters: [
23
26
  {
@@ -26,7 +29,7 @@ export async function handler(plugin: RapidPlugin, ctx: ActionHandlerContext, op
26
29
  value: userId,
27
30
  },
28
31
  ],
29
- properties: ["id", "name", "login", "email", "department", "roles", "state", "createdAt"],
32
+ properties: profilePropertyCodes,
30
33
  });
31
34
 
32
35
  ctx.output = {
@@ -1,16 +1,17 @@
1
- import bcrypt from "bcrypt";
2
1
  import { ActionHandlerContext } from "~/core/actionHandler";
3
- import { RapidPlugin } from "~/core/server";
2
+ import AuthPlugin from "../AuthPlugin";
3
+ import { generatePasswordHash } from "~/utilities/passwordUtility";
4
4
 
5
5
  export const code = "resetPassword";
6
6
 
7
- export async function handler(plugin: RapidPlugin, ctx: ActionHandlerContext, options: any) {
7
+ export async function handler(plugin: AuthPlugin, ctx: ActionHandlerContext, options: any) {
8
8
  const { server, input, routerContext: routeContext } = ctx;
9
9
  const { response } = routeContext;
10
10
  const { userId, password } = input;
11
11
 
12
+ const userEntitySingularCode = plugin.options?.userEntitySingularCode || "oc_user";
12
13
  const userDataAccessor = server.getDataAccessor({
13
- singularCode: "oc_user",
14
+ singularCode: userEntitySingularCode,
14
15
  });
15
16
 
16
17
  const user = await userDataAccessor.findOne(
@@ -30,8 +31,7 @@ export async function handler(plugin: RapidPlugin, ctx: ActionHandlerContext, op
30
31
  throw new Error("User not found.");
31
32
  }
32
33
 
33
- const saltRounds = 10;
34
- const passwordHash = await bcrypt.hash(password, saltRounds);
34
+ const passwordHash = await generatePasswordHash(password);
35
35
 
36
36
  await userDataAccessor.updateById(
37
37
  user.id,
@@ -25,71 +25,71 @@ import {
25
25
  RapidPlugin,
26
26
  } from "~/core/server";
27
27
 
28
- const routeConfigs: {
29
- code: string;
30
- method: RpdHttpMethod;
31
- endpoint: string;
32
- handlerCode: string;
28
+ const entityOperationConfigs: {
29
+ operationCode: string;
30
+ httpMethod: RpdHttpMethod;
31
+ requestEndpoint: string;
32
+ actionHandlerCode: string;
33
33
  }[] = [
34
34
  {
35
- code: "createBatch",
36
- method: "POST",
37
- endpoint: "/operations/create_batch",
38
- handlerCode: "createCollectionEntitiesBatch",
35
+ operationCode: "createBatch",
36
+ httpMethod: "POST",
37
+ requestEndpoint: "/operations/create_batch",
38
+ actionHandlerCode: "createCollectionEntitiesBatch",
39
39
  },
40
40
  {
41
- code: "find",
42
- method: "POST",
43
- endpoint: "/operations/find",
44
- handlerCode: "findCollectionEntities",
41
+ operationCode: "find",
42
+ httpMethod: "POST",
43
+ requestEndpoint: "/operations/find",
44
+ actionHandlerCode: "findCollectionEntities",
45
45
  },
46
46
  {
47
- code: "count",
48
- method: "POST",
49
- endpoint: "/operations/count",
50
- handlerCode: "countCollectionEntities",
47
+ operationCode: "count",
48
+ httpMethod: "POST",
49
+ requestEndpoint: "/operations/count",
50
+ actionHandlerCode: "countCollectionEntities",
51
51
  },
52
52
  {
53
- code: "delete",
54
- method: "POST",
55
- endpoint: "/operations/delete",
56
- handlerCode: "deleteCollectionEntities",
53
+ operationCode: "delete",
54
+ httpMethod: "POST",
55
+ requestEndpoint: "/operations/delete",
56
+ actionHandlerCode: "deleteCollectionEntities",
57
57
  },
58
58
  {
59
- code: "addRelations",
60
- method: "POST",
61
- endpoint: "/operations/add_relations",
62
- handlerCode: "addEntityRelations",
59
+ operationCode: "addRelations",
60
+ httpMethod: "POST",
61
+ requestEndpoint: "/operations/add_relations",
62
+ actionHandlerCode: "addEntityRelations",
63
63
  },
64
64
  {
65
- code: "removeRelations",
66
- method: "POST",
67
- endpoint: "/operations/remove_relations",
68
- handlerCode: "removeEntityRelations",
65
+ operationCode: "removeRelations",
66
+ httpMethod: "POST",
67
+ requestEndpoint: "/operations/remove_relations",
68
+ actionHandlerCode: "removeEntityRelations",
69
69
  },
70
70
  {
71
- code: "getById",
72
- method: "GET",
73
- endpoint: "/:id",
74
- handlerCode: "findCollectionEntityById",
71
+ operationCode: "getById",
72
+ httpMethod: "GET",
73
+ requestEndpoint: "/:id",
74
+ actionHandlerCode: "findCollectionEntityById",
75
75
  },
76
76
  {
77
- code: "create",
78
- method: "POST",
79
- endpoint: "",
80
- handlerCode: "createCollectionEntity",
77
+ operationCode: "create",
78
+ httpMethod: "POST",
79
+ requestEndpoint: "",
80
+ actionHandlerCode: "createCollectionEntity",
81
81
  },
82
82
  {
83
- code: "updateById",
84
- method: "PATCH",
85
- endpoint: "/:id",
86
- handlerCode: "updateCollectionEntityById",
83
+ operationCode: "updateById",
84
+ httpMethod: "PATCH",
85
+ requestEndpoint: "/:id",
86
+ actionHandlerCode: "updateCollectionEntityById",
87
87
  },
88
88
  {
89
- code: "deleteById",
90
- method: "DELETE",
91
- endpoint: "/:id",
92
- handlerCode: "deleteCollectionEntityById",
89
+ operationCode: "deleteById",
90
+ httpMethod: "DELETE",
91
+ requestEndpoint: "/:id",
92
+ actionHandlerCode: "deleteCollectionEntityById",
93
93
  },
94
94
  ];
95
95
 
@@ -135,17 +135,17 @@ class DataManager implements RapidPlugin {
135
135
  models.forEach((model) => {
136
136
  const { namespace, singularCode, pluralCode } = model;
137
137
 
138
- routeConfigs.forEach((routeConfig) => {
138
+ entityOperationConfigs.forEach((entityOperationConfig) => {
139
139
  routes.push({
140
140
  namespace,
141
- name: `${namespace}.${singularCode}.${routeConfig.code}`,
142
- code: `${namespace}.${singularCode}.${routeConfig.code}`,
141
+ name: `${namespace}.${singularCode}.${entityOperationConfig.operationCode}`,
142
+ code: `${namespace}.${singularCode}.${entityOperationConfig.operationCode}`,
143
143
  type: "RESTful",
144
- method: routeConfig.method,
145
- endpoint: `/${namespace}/${pluralCode}${routeConfig.endpoint}`,
144
+ method: entityOperationConfig.httpMethod,
145
+ endpoint: `/${namespace}/${pluralCode}${entityOperationConfig.requestEndpoint}`,
146
146
  actions: [
147
147
  {
148
- code: routeConfig.handlerCode,
148
+ code: entityOperationConfig.actionHandlerCode,
149
149
  config: {
150
150
  namespace,
151
151
  singularCode,
package/src/server.ts CHANGED
@@ -18,6 +18,7 @@ import {
18
18
  RpdEntityCreateEventPayload,
19
19
  EmitServerEventOptions,
20
20
  IDatabaseClient,
21
+ RpdRouteActionConfig,
21
22
  } from "./types";
22
23
 
23
24
  import QueryBuilder from "./queryBuilder/queryBuilder";
@@ -475,6 +476,10 @@ export class RapidServer implements IRpdServer {
475
476
  await this.#pluginManager.beforeRunRouteActions(handlerContext);
476
477
  }
477
478
 
479
+ async beforeRunActionHandler(handlerContext: ActionHandlerContext, actionConfig: RpdRouteActionConfig) {
480
+ await this.#pluginManager.beforeRunActionHandler(handlerContext, actionConfig);
481
+ }
482
+
478
483
  async beforeCreateEntity(model: RpdDataModel, options: CreateEntityOptions) {
479
484
  await this.#pluginManager.beforeCreateEntity(model, options);
480
485
  }
@@ -0,0 +1,26 @@
1
+ import bcrypt from "bcryptjs";
2
+
3
+ /**
4
+ * Generates password hash.
5
+ * @param password
6
+ * @param salt
7
+ * @returns
8
+ */
9
+ export async function generatePasswordHash(password: string, salt?: number | string): Promise<string> {
10
+ if (!salt) {
11
+ salt = 10;
12
+ }
13
+ const passwordHash = await bcrypt.hash(password, salt);
14
+ return passwordHash;
15
+ }
16
+
17
+ /**
18
+ * Validates the password against the hash.
19
+ * @param password
20
+ * @param passwordHash
21
+ * @returns
22
+ */
23
+ export async function validatePassword(password: string, passwordHash: string): Promise<boolean> {
24
+ const isMatch = await bcrypt.compare(password, passwordHash);
25
+ return isMatch;
26
+ }