@modular-rest/server 1.11.12 → 1.11.14

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.
Files changed (112) hide show
  1. package/.nvmrc +1 -0
  2. package/.prettierrc.json +9 -0
  3. package/.releaserc.json +24 -0
  4. package/README.md +79 -94
  5. package/dist/index.js +79 -0
  6. package/docs/.keep +0 -0
  7. package/docs/system-access-type.md +26 -0
  8. package/package.json +58 -45
  9. package/src/application.ts +206 -0
  10. package/src/class/cms_trigger.ts +68 -0
  11. package/src/class/collection_definition.ts +134 -0
  12. package/src/class/combinator.ts +176 -0
  13. package/src/class/database_trigger.ts +99 -0
  14. package/src/class/db_schemas.ts +44 -0
  15. package/src/class/{directory.js → directory.ts} +40 -18
  16. package/src/class/paginator.ts +51 -0
  17. package/src/class/reply.ts +59 -0
  18. package/src/class/security.ts +250 -0
  19. package/src/class/trigger_operator.ts +142 -0
  20. package/src/class/user.ts +199 -0
  21. package/src/class/validator.ts +123 -0
  22. package/src/config.ts +122 -0
  23. package/src/defult-permissions.ts +31 -0
  24. package/src/events.ts +59 -0
  25. package/src/helper/data_insertion.ts +94 -0
  26. package/src/helper/presetup_services.ts +96 -0
  27. package/src/index.ts +146 -0
  28. package/src/middlewares.ts +75 -0
  29. package/src/play-test.ts +8 -0
  30. package/src/services/data_provider/router.ts +191 -0
  31. package/src/services/data_provider/service.ts +305 -0
  32. package/src/services/data_provider/typeCasters.ts +15 -0
  33. package/src/services/file/db.ts +29 -0
  34. package/src/services/file/router.ts +88 -0
  35. package/src/services/file/service.ts +387 -0
  36. package/src/services/functions/router.ts +34 -0
  37. package/src/services/functions/service.ts +203 -0
  38. package/src/services/jwt/router.ts +73 -0
  39. package/src/services/jwt/service.ts +139 -0
  40. package/src/services/user_manager/db.ts +87 -0
  41. package/src/services/user_manager/permissionManager.ts +49 -0
  42. package/src/services/user_manager/router.ts +193 -0
  43. package/src/services/user_manager/service.ts +698 -0
  44. package/tsconfig.json +16 -9
  45. package/typedoc.mjs +41 -0
  46. package/LICENSE +0 -21
  47. package/package-lock.json +0 -1373
  48. package/src/application.js +0 -239
  49. package/src/class/cms_trigger.js +0 -20
  50. package/src/class/collection_definition.js +0 -33
  51. package/src/class/combinator.js +0 -133
  52. package/src/class/database_trigger.js +0 -20
  53. package/src/class/db_schemas.js +0 -18
  54. package/src/class/paginator.js +0 -31
  55. package/src/class/reply.js +0 -37
  56. package/src/class/security.js +0 -141
  57. package/src/class/trigger_operator.js +0 -39
  58. package/src/class/user.js +0 -112
  59. package/src/class/validator.js +0 -91
  60. package/src/config.js +0 -67
  61. package/src/events.js +0 -15
  62. package/src/helper/data_insertion.js +0 -64
  63. package/src/helper/presetup_services.js +0 -31
  64. package/src/index.js +0 -66
  65. package/src/middlewares.js +0 -44
  66. package/src/services/data_provider/router.js +0 -552
  67. package/src/services/data_provider/service.js +0 -262
  68. package/src/services/data_provider/typeCasters.js +0 -10
  69. package/src/services/file/db.js +0 -29
  70. package/src/services/file/router.js +0 -92
  71. package/src/services/file/service.js +0 -231
  72. package/src/services/functions/router.js +0 -37
  73. package/src/services/functions/service.js +0 -74
  74. package/src/services/jwt/router.js +0 -70
  75. package/src/services/jwt/service.js +0 -37
  76. package/src/services/user_manager/db.js +0 -83
  77. package/src/services/user_manager/permissionManager.js +0 -43
  78. package/src/services/user_manager/router.js +0 -176
  79. package/src/services/user_manager/service.js +0 -377
  80. package/types/application.d.ts +0 -97
  81. package/types/class/cms_trigger.d.ts +0 -24
  82. package/types/class/collection_definition.d.ts +0 -36
  83. package/types/class/combinator.d.ts +0 -30
  84. package/types/class/database_trigger.d.ts +0 -28
  85. package/types/class/db_schemas.d.ts +0 -2
  86. package/types/class/directory.d.ts +0 -2
  87. package/types/class/paginator.d.ts +0 -8
  88. package/types/class/reply.d.ts +0 -8
  89. package/types/class/security.d.ts +0 -109
  90. package/types/class/trigger_operator.d.ts +0 -19
  91. package/types/class/user.d.ts +0 -24
  92. package/types/class/validator.d.ts +0 -9
  93. package/types/config.d.ts +0 -101
  94. package/types/events.d.ts +0 -7
  95. package/types/helper/data_insertion.d.ts +0 -4
  96. package/types/helper/presetup_services.d.ts +0 -5
  97. package/types/index.d.ts +0 -72
  98. package/types/middlewares.d.ts +0 -9
  99. package/types/services/data_provider/router.d.ts +0 -3
  100. package/types/services/data_provider/service.d.ts +0 -40
  101. package/types/services/data_provider/typeCasters.d.ts +0 -3
  102. package/types/services/file/db.d.ts +0 -3
  103. package/types/services/file/router.d.ts +0 -3
  104. package/types/services/file/service.d.ts +0 -81
  105. package/types/services/functions/router.d.ts +0 -3
  106. package/types/services/functions/service.d.ts +0 -23
  107. package/types/services/jwt/router.d.ts +0 -3
  108. package/types/services/jwt/service.d.ts +0 -10
  109. package/types/services/user_manager/db.d.ts +0 -3
  110. package/types/services/user_manager/permissionManager.d.ts +0 -3
  111. package/types/services/user_manager/router.d.ts +0 -3
  112. package/types/services/user_manager/service.d.ts +0 -131
@@ -0,0 +1,73 @@
1
+ import Router from 'koa-router';
2
+ import { validateObject } from '../../class/validator';
3
+ import { create as reply } from '../../class/reply';
4
+ import { Context } from 'koa';
5
+ import * as service from './service';
6
+
7
+ const name = 'verify';
8
+ const verify = new Router();
9
+
10
+ verify.post('/token', async (ctx: Context) => {
11
+ const body = ctx.request.body;
12
+
13
+ // validate result
14
+ const bodyValidate = validateObject(body, 'token');
15
+
16
+ // fields validation
17
+ if (!bodyValidate.isValid) {
18
+ ctx.status = 412;
19
+ ctx.body = reply('e', {
20
+ e: bodyValidate.requires,
21
+ });
22
+ return;
23
+ }
24
+
25
+ await service.main
26
+ .verify(body.token)
27
+ .then(payload => (ctx.body = reply('s', { user: payload })))
28
+ .catch(err => {
29
+ ctx.status = 412;
30
+ ctx.body = reply('e', { e: err });
31
+ });
32
+ });
33
+
34
+ verify.post('/checkAccess', async (ctx: Context) => {
35
+ const body = ctx.request.body;
36
+
37
+ // validate result
38
+ const bodyValidate = validateObject(body, 'token permissionField');
39
+
40
+ // fields validation
41
+ if (!bodyValidate.isValid) {
42
+ ctx.status = 412;
43
+ ctx.body = reply('e', {
44
+ e: bodyValidate.requires,
45
+ });
46
+ return;
47
+ }
48
+
49
+ const payload = await service.main.verify(body.token).catch(err => {
50
+ console.log(err);
51
+ ctx.throw(412, err.message);
52
+ });
53
+
54
+ const userid = payload.id;
55
+
56
+ await (global as any).services.userManager.main
57
+ .getUserById(userid)
58
+ .then((user: any) => {
59
+ const key = user.hasPermission(body.permissionField);
60
+ ctx.body = reply('s', { access: key });
61
+ })
62
+ .catch((err: any) => {
63
+ ctx.status = 412;
64
+ ctx.body = reply('e', { e: err });
65
+ });
66
+ });
67
+
68
+ verify.get('/ready', async (ctx: Context) => {
69
+ // it's health check, so return success
70
+ ctx.body = reply('s', {});
71
+ });
72
+
73
+ export { name, verify as main };
@@ -0,0 +1,139 @@
1
+ import jwt from 'jsonwebtoken';
2
+
3
+ /**
4
+ * Service name constant
5
+ * @constant {string}
6
+ */
7
+ export const name = 'jwt';
8
+
9
+ /**
10
+ * JWT service class for handling JSON Web Token operations
11
+ * @class JWT
12
+ * @description
13
+ * This class provides methods for signing and verifying JSON Web Tokens using RS256 algorithm.
14
+ * It requires both private and public keys to be set before use.
15
+ *
16
+ * @example
17
+ * ```typescript
18
+ * // Set up keys
19
+ * main.setKies(privateKey, publicKey);
20
+ *
21
+ * // Sign a token
22
+ * const token = await main.sign({ userId: '123', role: 'admin' });
23
+ *
24
+ * // Verify a token
25
+ * const decoded = await main.verify(token);
26
+ * console.log(decoded.userId); // '123'
27
+ * ```
28
+ */
29
+ class JWT {
30
+ private privateKey?: string;
31
+ private publicKey?: string;
32
+
33
+ /**
34
+ * Sets the private and public keys for JWT operations
35
+ * @param {string} privateKey - Private key for signing tokens (PEM format)
36
+ * @param {string} publicKey - Public key for verifying tokens (PEM format)
37
+ * @throws {Error} If either key is invalid
38
+ * @example
39
+ * ```typescript
40
+ * // Using PEM format keys
41
+ * const privateKey = `-----BEGIN PRIVATE KEY-----
42
+ * MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSnAgEAAoIBAQC9QFi67s...
43
+ * -----END PRIVATE KEY-----`;
44
+ *
45
+ * const publicKey = `-----BEGIN PUBLIC KEY-----
46
+ * MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvUBYuu7...
47
+ * -----END PUBLIC KEY-----`;
48
+ *
49
+ * main.setKies(privateKey, publicKey);
50
+ * ```
51
+ */
52
+ setKies(privateKey: string, publicKey: string): void {
53
+ if (!privateKey || !publicKey) {
54
+ throw new Error('Both private and public keys are required');
55
+ }
56
+ this.privateKey = privateKey;
57
+ this.publicKey = publicKey;
58
+ }
59
+
60
+ /**
61
+ * Signs a payload and creates a JWT token using RS256 algorithm
62
+ * @param {Record<string, any>} payload - Data to be encoded in the token
63
+ * @returns {Promise<string>} A promise that resolves to the signed JWT token
64
+ * @throws {Error} If private key is not set or signing fails
65
+ * @example
66
+ * ```typescript
67
+ * // Sign a token with user data
68
+ * const token = await main.sign({
69
+ * userId: '123',
70
+ * role: 'admin',
71
+ * exp: Math.floor(Date.now() / 1000) + (60 * 60) // 1 hour expiry
72
+ * });
73
+ *
74
+ * // Sign a token with custom claims
75
+ * const token = await main.sign({
76
+ * sub: 'user123',
77
+ * iss: 'myapp.com',
78
+ * aud: 'myapp.com',
79
+ * iat: Math.floor(Date.now() / 1000)
80
+ * });
81
+ * ```
82
+ */
83
+ sign(payload: Record<string, any>): Promise<string> {
84
+ return new Promise((done, reject) => {
85
+ const option = { algorithm: 'RS256' as const };
86
+
87
+ if (!this.privateKey) {
88
+ return reject(new Error('Private key is not set. Call setKies() first.'));
89
+ }
90
+
91
+ try {
92
+ const token = jwt.sign(payload, this.privateKey, option);
93
+ done(token);
94
+ } catch (error) {
95
+ reject(error instanceof Error ? error.message : String(error));
96
+ }
97
+ });
98
+ }
99
+
100
+ /**
101
+ * Verifies a JWT token and returns its decoded payload
102
+ * @param {string} token - JWT token to verify
103
+ * @returns {Promise<Record<string, any>>} A promise that resolves to the decoded payload
104
+ * @throws {Error} If public key is not set, token is invalid, or verification fails
105
+ * @example
106
+ * ```typescript
107
+ * try {
108
+ * const decoded = await main.verify(token);
109
+ * console.log('Token is valid:', decoded);
110
+ * // Access decoded data
111
+ * const { userId, role } = decoded;
112
+ * } catch (error) {
113
+ * console.error('Token verification failed:', error);
114
+ * }
115
+ * ```
116
+ */
117
+ verify(token: string): Promise<Record<string, any>> {
118
+ return new Promise((done, reject) => {
119
+ const option = { algorithm: 'RS256' as const } as jwt.VerifyOptions;
120
+
121
+ if (!this.publicKey) {
122
+ return reject(new Error('Public key is not set. Call setKies() first.'));
123
+ }
124
+
125
+ try {
126
+ const decoded = jwt.verify(token, this.publicKey, option);
127
+ done(decoded as Record<string, any>);
128
+ } catch (error) {
129
+ reject(error instanceof Error ? error.message : String(error));
130
+ }
131
+ });
132
+ }
133
+ }
134
+
135
+ /**
136
+ * Main JWT service instance
137
+ * @constant {JWT}
138
+ */
139
+ export const main = new JWT();
@@ -0,0 +1,87 @@
1
+ import mongoose from 'mongoose';
2
+ import { Schema } from 'mongoose';
3
+ import { CollectionDefinition } from '../../class/collection_definition';
4
+ import { Permission, PermissionTypes } from '../../class/security';
5
+ import { config } from '../../config';
6
+ import triggerOperator from '../../class/trigger_operator';
7
+
8
+ interface AuthDocument extends mongoose.Document {
9
+ password: string;
10
+ isModified(path: string): boolean;
11
+ }
12
+
13
+ const authSchema = new Schema(
14
+ {
15
+ permissionGroup: String,
16
+ email: String,
17
+ phone: String,
18
+ password: String,
19
+ type: { type: String, default: 'user', enum: ['user', 'anonymous'] },
20
+ },
21
+ { timestamps: true }
22
+ );
23
+
24
+ authSchema.index({ email: 1 }, { unique: true });
25
+ authSchema.pre(['save', 'updateOne'], function (this: AuthDocument, next) {
26
+ // Encode the password before saving
27
+ if (this.isModified && this.isModified('password')) {
28
+ this.password = Buffer.from(this.password).toString('base64');
29
+ }
30
+ next();
31
+ });
32
+
33
+ authSchema.post('save', function (doc: any, next) {
34
+ triggerOperator.call('insert-one', 'cms', 'auth', {
35
+ query: null,
36
+ queryResult: doc._doc,
37
+ });
38
+ next();
39
+ });
40
+
41
+ authSchema.post('findOneAndUpdate', function (doc: any, next) {
42
+ triggerOperator.call('update-one', 'cms', 'auth', {
43
+ query: null,
44
+ queryResult: doc._doc,
45
+ });
46
+ next();
47
+ });
48
+
49
+ authSchema.post('updateOne', function (result: any, next) {
50
+ triggerOperator.call('update-one', 'cms', 'auth', {
51
+ query: null,
52
+ queryResult: result,
53
+ });
54
+ next();
55
+ });
56
+
57
+ authSchema.post('findOneAndDelete', function (doc: any, next) {
58
+ triggerOperator.call('remove-one', 'cms', 'auth', {
59
+ query: null,
60
+ queryResult: doc._doc,
61
+ });
62
+ next();
63
+ });
64
+
65
+ authSchema.post('deleteOne', function (result: any, next) {
66
+ triggerOperator.call('remove-one', 'cms', 'auth', {
67
+ query: null,
68
+ queryResult: result,
69
+ });
70
+ next();
71
+ });
72
+
73
+ module.exports = [
74
+ new CollectionDefinition({
75
+ database: 'cms',
76
+ collection: 'auth',
77
+ schema: authSchema,
78
+ permissions: [
79
+ new Permission({
80
+ accessType: PermissionTypes.advanced_settings,
81
+ read: true,
82
+ write: true,
83
+ }),
84
+ ],
85
+ triggers: config.authTriggers || [],
86
+ }),
87
+ ];
@@ -0,0 +1,49 @@
1
+ import { config } from '../../config';
2
+ import { PermissionGroup } from '../../class/security';
3
+
4
+ /**
5
+ * Get the default permission group
6
+ * @returns Default permission group
7
+ * @throws Error if default permission group not found
8
+ */
9
+ export function getDefaultPermissionGroups(): PermissionGroup {
10
+ const defaultPermissionGroups = config.permissionGroups?.find(group => group.isDefault);
11
+
12
+ if (defaultPermissionGroups == null) {
13
+ throw new Error('Default permission group not found');
14
+ }
15
+
16
+ return defaultPermissionGroups;
17
+ }
18
+
19
+ /**
20
+ * Get the anonymous permission group
21
+ * @returns Anonymous permission group
22
+ * @throws Error if anonymous permission group not found
23
+ */
24
+ export function getDefaultAnonymousPermissionGroup(): PermissionGroup {
25
+ const anonymousPermission = config.permissionGroups?.find(group => group.isAnonymous);
26
+
27
+ if (anonymousPermission == null) {
28
+ throw new Error('Anonymous permission group not found');
29
+ }
30
+
31
+ return anonymousPermission;
32
+ }
33
+
34
+ /**
35
+ * Get the administrator permission group
36
+ * @returns Administrator permission group
37
+ * @throws Error if administrator permission group not found
38
+ */
39
+ export function getDefaultAdministratorPermissionGroup(): PermissionGroup {
40
+ const administratorPermission = config.permissionGroups?.find(
41
+ group => group.title.toString() === 'administrator'
42
+ );
43
+
44
+ if (administratorPermission == null) {
45
+ throw new Error('Administrator permission group not found');
46
+ }
47
+
48
+ return administratorPermission;
49
+ }
@@ -0,0 +1,193 @@
1
+ import Router from 'koa-router';
2
+ import { validateObject } from '../../class/validator';
3
+ import { create as reply } from '../../class/reply';
4
+ import { Context } from 'koa';
5
+ import * as service from './service';
6
+
7
+ const name = 'user';
8
+ const userManager = new Router();
9
+
10
+ userManager.post('/register_id', async (ctx: Context) => {
11
+ const body = ctx.request.body;
12
+
13
+ const validateOption = {
14
+ id: '',
15
+ idType: 'phone email',
16
+ };
17
+
18
+ // validate result
19
+ const bodyValidate = validateObject(body, validateOption);
20
+
21
+ // fields validation
22
+ if (!bodyValidate.isValid) {
23
+ ctx.status = 412;
24
+ ctx.body = reply('e', { e: bodyValidate.requires });
25
+ return;
26
+ }
27
+
28
+ const serial = service.main.generateVerificationCode(body.id, body.idType);
29
+
30
+ if (serial) {
31
+ service.main.registerTemporaryID(body.id, body.idType, serial);
32
+ ctx.body = reply('s');
33
+ } else {
34
+ ctx.status = 412;
35
+ ctx.body = reply('e', { e: 'Could not generate verification code.' });
36
+ }
37
+ });
38
+
39
+ userManager.post('/validateCode', async (ctx: Context) => {
40
+ const body = ctx.request.body;
41
+
42
+ // validate result
43
+ const bodyValidate = validateObject(body, 'id code');
44
+
45
+ // fields validation
46
+ if (!bodyValidate.isValid) {
47
+ ctx.status = 412;
48
+ ctx.body = reply('e', { e: bodyValidate.requires });
49
+ return;
50
+ }
51
+
52
+ const isValid = service.main.isCodeValid(body.id, body.code);
53
+
54
+ if (!isValid) {
55
+ ctx.status = 412;
56
+ ctx.body = reply('e', {
57
+ e: 'Verification code is wrong',
58
+ isValid: isValid,
59
+ });
60
+ return;
61
+ }
62
+
63
+ ctx.body = reply('s', { isValid: isValid });
64
+ });
65
+
66
+ userManager.post('/submit_password', async (ctx: Context) => {
67
+ const body = ctx.request.body;
68
+
69
+ // validate result
70
+ const bodyValidate = validateObject(body, 'id password code');
71
+
72
+ // fields validation
73
+ if (!bodyValidate.isValid) {
74
+ ctx.status = 412;
75
+ ctx.body = reply('e', { e: bodyValidate.requires });
76
+ return;
77
+ }
78
+
79
+ try {
80
+ const userId = await service.main.submitPasswordForTemporaryID(
81
+ body.id,
82
+ body.password,
83
+ body.code
84
+ );
85
+ if (userId) {
86
+ ctx.body = reply('s');
87
+ } else {
88
+ ctx.status = 412;
89
+ ctx.body = reply('f');
90
+ }
91
+ } catch (error) {
92
+ ctx.status = 412;
93
+ ctx.body = reply('f');
94
+ }
95
+ });
96
+
97
+ userManager.post('/change_password', async (ctx: Context) => {
98
+ const body = ctx.request.body;
99
+
100
+ // validate result
101
+ const bodyValidate = validateObject(body, 'id password code');
102
+
103
+ // fields validation
104
+ if (!bodyValidate.isValid) {
105
+ ctx.status = 412;
106
+ ctx.body = reply('e', { e: bodyValidate.requires });
107
+ return;
108
+ }
109
+
110
+ try {
111
+ const userId = await service.main.changePasswordForTemporaryID(
112
+ body.id,
113
+ body.password,
114
+ body.code
115
+ );
116
+ if (userId) {
117
+ ctx.body = reply('s');
118
+ } else {
119
+ ctx.status = 412;
120
+ ctx.body = reply('f');
121
+ }
122
+ } catch (error) {
123
+ ctx.status = 412;
124
+ ctx.body = reply('f');
125
+ }
126
+ });
127
+
128
+ userManager.post('/login', async (ctx: Context) => {
129
+ const body = ctx.request.body;
130
+
131
+ const validateOption = {
132
+ id: '',
133
+ password: '',
134
+ idType: 'phone email',
135
+ };
136
+
137
+ // validate result
138
+ const bodyValidate = validateObject(body, validateOption);
139
+
140
+ // fields validation
141
+ if (!bodyValidate.isValid) {
142
+ ctx.status = 412;
143
+ ctx.body = reply('e', { e: bodyValidate.requires });
144
+ return;
145
+ }
146
+
147
+ await service.main
148
+ .loginUser(body.id, body.idType, body.password)
149
+ .then(token => (ctx.body = reply('s', { token: token })))
150
+ .catch(err => {
151
+ ctx.status = 412;
152
+ ctx.body = reply('e', { e: err });
153
+ });
154
+ });
155
+
156
+ userManager.get('/loginAnonymous', async (ctx: Context) => {
157
+ await service.main
158
+ .loginAnonymous()
159
+ .then(token => (ctx.body = reply('s', { token: token })))
160
+ .catch(err => {
161
+ ctx.status = 412;
162
+ ctx.body = reply('e', { e: err });
163
+ });
164
+ });
165
+
166
+ userManager.post('/getPermission', async (ctx: Context) => {
167
+ const body = ctx.request.body;
168
+
169
+ // validate result
170
+ const bodyValidate = validateObject(body, 'id');
171
+
172
+ // fields validation
173
+ if (!bodyValidate.isValid) {
174
+ ctx.status = 412;
175
+ ctx.body = reply('e', { e: bodyValidate.requires });
176
+ return;
177
+ }
178
+
179
+ const query = { _id: body.id };
180
+
181
+ const dataProvider = (global as any).services.dataProvider;
182
+ const permission = await dataProvider
183
+ .getCollection('cms', 'permission')
184
+ .findOne(query)
185
+ .catch((err: any) => {
186
+ ctx.status = 412;
187
+ ctx.body = reply('e', { e: err });
188
+ });
189
+
190
+ ctx.body = reply('s', { permission: permission });
191
+ });
192
+
193
+ export { name, userManager as main };