@etsoo/appscript 1.1.63 → 1.1.67

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.
@@ -0,0 +1,124 @@
1
+ import {
2
+ INotificaseBase,
3
+ INotification,
4
+ Notification,
5
+ NotificationCallProps,
6
+ NotificationContainer,
7
+ NotificationRenderProps
8
+ } from '@etsoo/notificationbase';
9
+ import { ApiAuthorizationScheme, createClient } from '@etsoo/restclient';
10
+ import { DataTypes, DomUtils, Utils } from '@etsoo/shared';
11
+ import { AddressUtils } from '../../src/address/AddressUtils';
12
+ import { IAppSettings } from '../../src/app/AppSettings';
13
+ import { CoreApp } from '../../src/app/CoreApp';
14
+ import { zhCN } from '../../src/i18n/zhCN';
15
+
16
+ // Detected country or region
17
+ const { detectedCountry } = DomUtils;
18
+
19
+ // Detected culture
20
+ const { detectedCulture } = DomUtils;
21
+
22
+ // Supported cultures
23
+ const supportedCultures: DataTypes.CultureDefinition[] = [zhCN({})];
24
+
25
+ // Supported regions
26
+ const supportedRegions = ['CN'];
27
+
28
+ // Class implementation for tests
29
+ class NotificationTest extends Notification<any, NotificationCallProps> {
30
+ render(props: NotificationRenderProps, className?: string, options?: any) {
31
+ throw new Error('Method not implemented.');
32
+ }
33
+ }
34
+
35
+ class NotificationContainerTest extends NotificationContainer<
36
+ any,
37
+ NotificationCallProps
38
+ > {
39
+ protected addRaw(
40
+ data: INotificaseBase<any, NotificationCallProps>
41
+ ): INotification<any, NotificationCallProps> {
42
+ throw new Error('Method not implemented.');
43
+ }
44
+ }
45
+
46
+ // Container
47
+ var container = new NotificationContainerTest((update) => {});
48
+
49
+ // Arrange
50
+ class CoreAppTest extends CoreApp<IAppSettings, {}, NotificationCallProps> {
51
+ /**
52
+ * Constructor
53
+ * @param settings Settings
54
+ * @param name Application name
55
+ */
56
+ constructor() {
57
+ super(
58
+ {
59
+ /**
60
+ * Endpoint of the API service
61
+ */
62
+ endpoint: 'http://{hostname}/com.etsoo.SmartERPApi/api/',
63
+
64
+ /**
65
+ * App root url
66
+ */
67
+ homepage: '',
68
+
69
+ /**
70
+ * Web url of the cloud
71
+ */
72
+ webUrl: 'http://localhost',
73
+
74
+ // Authorization scheme
75
+ authScheme: ApiAuthorizationScheme.Bearer,
76
+
77
+ // Detected culture
78
+ detectedCulture,
79
+
80
+ // Supported cultures
81
+ cultures: supportedCultures,
82
+
83
+ // Supported regions
84
+ regions: supportedRegions,
85
+
86
+ // Browser's time zone
87
+ timeZone: Utils.getTimeZone(),
88
+
89
+ // Current country or region
90
+ currentRegion: AddressUtils.getRegion(
91
+ supportedRegions,
92
+ detectedCountry,
93
+ detectedCulture
94
+ ),
95
+
96
+ // Current culture
97
+ currentCulture: DomUtils.getCulture(
98
+ supportedCultures,
99
+ detectedCulture
100
+ )!
101
+ },
102
+ createClient(),
103
+ container,
104
+ 'SmartERP'
105
+ );
106
+ }
107
+
108
+ freshCountdownUI(callback?: () => PromiseLike<unknown>): void {
109
+ throw new Error('Method not implemented.');
110
+ }
111
+ }
112
+
113
+ const app = new CoreAppTest();
114
+
115
+ test('Tests for encrypt / decrypt', () => {
116
+ // Arrange
117
+ const input = 'Hello, world!';
118
+ const passphrase = 'My password';
119
+
120
+ // Act
121
+ const encrypted = app.encrypt(input, passphrase);
122
+ const plain = app.decrypt(encrypted, passphrase);
123
+ expect(plain).toEqual(input);
124
+ });
@@ -133,9 +133,10 @@ export interface ICoreApp<S extends IAppSettings, N, C extends NotificationCallP
133
133
  * Encrypt message
134
134
  * @param message Message
135
135
  * @param passphrase Secret passphrase
136
+ * @param iterations Iterations, 1000 times, 1 - 99
136
137
  * @returns Result
137
138
  */
138
- encrypt(message: string, passphrase: string): string;
139
+ encrypt(message: string, passphrase: string, iterations?: number): string;
139
140
  /**
140
141
  * Format date to string
141
142
  * @param input Input date
@@ -216,6 +217,13 @@ export interface ICoreApp<S extends IAppSettings, N, C extends NotificationCallP
216
217
  * @returns Time zone
217
218
  */
218
219
  getTimeZone(): string | undefined;
220
+ /**
221
+ * Hash message, SHA3 or HmacSHA512, 512 as Base64
222
+ * https://cryptojs.gitbook.io/docs/
223
+ * @param message Message
224
+ * @param passphrase Secret passphrase
225
+ */
226
+ hash(message: string, passphrase?: string): string;
219
227
  /**
220
228
  * Check use has the specific role permission or not
221
229
  * @param roles Roles to check
@@ -403,16 +411,17 @@ export declare abstract class CoreApp<S extends IAppSettings, N, C extends Notif
403
411
  * Encrypt message
404
412
  * @param message Message
405
413
  * @param passphrase Secret passphrase
414
+ * @param iterations Iterations, 1000 times, 1 - 99
406
415
  * @returns Result
407
416
  */
408
- encrypt(message: string, passphrase: string): string;
417
+ encrypt(message: string, passphrase: string, iterations?: number): string;
409
418
  /**
410
419
  * Enchance secret passphrase
411
420
  * @param passphrase Secret passphrase
412
- * @param miliseconds Miliseconds
421
+ * @param timestamp Timestamp
413
422
  * @returns Enhanced passphrase
414
423
  */
415
- protected encryptionEnhance(passphrase: string, miliseconds: number): string;
424
+ protected encryptionEnhance(passphrase: string, timestamp: string): string;
416
425
  /**
417
426
  * Format date to string
418
427
  * @param input Input date
@@ -488,6 +497,13 @@ export declare abstract class CoreApp<S extends IAppSettings, N, C extends Notif
488
497
  * @returns Time zone
489
498
  */
490
499
  getTimeZone(): string | undefined;
500
+ /**
501
+ * Hash message, SHA3 or HmacSHA512, 512 as Base64
502
+ * https://cryptojs.gitbook.io/docs/
503
+ * @param message Message
504
+ * @param passphrase Secret passphrase
505
+ */
506
+ hash(message: string, passphrase?: string): string;
491
507
  /**
492
508
  * Check use has the specific role permission or not
493
509
  * @param roles Roles to check
@@ -203,10 +203,25 @@ class CoreApp {
203
203
  * @returns Pure text
204
204
  */
205
205
  decrypt(messageEncrypted, passphrase) {
206
- const pos = messageEncrypted.indexOf('M');
207
- const miliseconds = parseInt(messageEncrypted.substring(0, pos));
206
+ // Timestamp splitter
207
+ const pos = messageEncrypted.indexOf('+');
208
+ const timestamp = messageEncrypted.substring(0, pos);
208
209
  const message = messageEncrypted.substring(pos + 1);
209
- return crypto_js_1.AES.decrypt(message, this.encryptionEnhance(passphrase, miliseconds)).toString();
210
+ // Iterations
211
+ const iterations = parseInt(message.substring(0, 2), 10);
212
+ const salt = crypto_js_1.enc.Hex.parse(message.substring(2, 34));
213
+ const iv = crypto_js_1.enc.Hex.parse(message.substring(34, 66));
214
+ const encrypted = message.substring(66);
215
+ const key = (0, crypto_js_1.PBKDF2)(this.encryptionEnhance(passphrase, timestamp), salt, {
216
+ keySize: 8,
217
+ hasher: crypto_js_1.algo.SHA256,
218
+ iterations: 1000 * iterations
219
+ });
220
+ return crypto_js_1.AES.decrypt(encrypted, key, {
221
+ iv,
222
+ padding: crypto_js_1.pad.Pkcs7,
223
+ mode: crypto_js_1.mode.CBC
224
+ }).toString(crypto_js_1.enc.Utf8);
210
225
  }
211
226
  /**
212
227
  * Detect IP data, call only one time
@@ -245,23 +260,43 @@ class CoreApp {
245
260
  * Encrypt message
246
261
  * @param message Message
247
262
  * @param passphrase Secret passphrase
263
+ * @param iterations Iterations, 1000 times, 1 - 99
248
264
  * @returns Result
249
265
  */
250
- encrypt(message, passphrase) {
251
- const miliseconds = new Date().getUTCMilliseconds();
252
- return (miliseconds +
253
- 'M' +
254
- crypto_js_1.AES.encrypt(message, this.encryptionEnhance(passphrase, miliseconds)).toString());
266
+ encrypt(message, passphrase, iterations) {
267
+ // Default 1 * 1000
268
+ iterations !== null && iterations !== void 0 ? iterations : (iterations = 1);
269
+ // Timestamp
270
+ const timestamp = shared_1.Utils.numberToChars(new Date().getTime());
271
+ const bits = 16; // 128 / 8
272
+ const salt = crypto_js_1.lib.WordArray.random(bits);
273
+ const key = (0, crypto_js_1.PBKDF2)(this.encryptionEnhance(passphrase, timestamp), salt, {
274
+ keySize: 8,
275
+ hasher: crypto_js_1.algo.SHA256,
276
+ iterations: 1000 * iterations
277
+ });
278
+ const iv = crypto_js_1.lib.WordArray.random(bits);
279
+ return (timestamp +
280
+ '+' +
281
+ iterations.toString().padStart(2, '0') +
282
+ salt.toString(crypto_js_1.enc.Hex) +
283
+ iv.toString(crypto_js_1.enc.Hex) +
284
+ crypto_js_1.AES.encrypt(message, key, {
285
+ iv,
286
+ padding: crypto_js_1.pad.Pkcs7,
287
+ mode: crypto_js_1.mode.CBC
288
+ }).toString() // enc.Base64
289
+ );
255
290
  }
256
291
  /**
257
292
  * Enchance secret passphrase
258
293
  * @param passphrase Secret passphrase
259
- * @param miliseconds Miliseconds
294
+ * @param timestamp Timestamp
260
295
  * @returns Enhanced passphrase
261
296
  */
262
- encryptionEnhance(passphrase, miliseconds) {
297
+ encryptionEnhance(passphrase, timestamp) {
263
298
  var _a;
264
- passphrase += miliseconds.toString();
299
+ passphrase += timestamp;
265
300
  passphrase += passphrase.length.toString();
266
301
  return passphrase + ((_a = this.passphrase) !== null && _a !== void 0 ? _a : '');
267
302
  }
@@ -391,6 +426,18 @@ class CoreApp {
391
426
  // settings.timeZone = Utils.getTimeZone()
392
427
  return (_a = this.settings.timeZone) !== null && _a !== void 0 ? _a : (_b = this.ipData) === null || _b === void 0 ? void 0 : _b.timezone;
393
428
  }
429
+ /**
430
+ * Hash message, SHA3 or HmacSHA512, 512 as Base64
431
+ * https://cryptojs.gitbook.io/docs/
432
+ * @param message Message
433
+ * @param passphrase Secret passphrase
434
+ */
435
+ hash(message, passphrase) {
436
+ if (passphrase == null)
437
+ return (0, crypto_js_1.SHA3)(message, { outputLength: 512 }).toString(crypto_js_1.enc.Base64);
438
+ else
439
+ return (0, crypto_js_1.HmacSHA512)(message, passphrase).toString(crypto_js_1.enc.Base64);
440
+ }
394
441
  /**
395
442
  * Check use has the specific role permission or not
396
443
  * @param roles Roles to check
@@ -540,6 +587,7 @@ class CoreApp {
540
587
  */
541
588
  userLogin(user, refreshToken, keep = false) {
542
589
  this.userData = user;
590
+ this.passphrase = user.passphrase;
543
591
  this.authorize(user.token, refreshToken, keep);
544
592
  }
545
593
  /**
@@ -59,6 +59,7 @@
59
59
  "status": "Status",
60
60
  "submit": "Submit",
61
61
  "success": "Success",
62
+ "timeDifferenceInvalid": "The time difference between the device and the server is {0}, which exceeds the limit of {1} seconds. Please adjust the device time. If it is abnormal, please inform the administrator",
62
63
  "tokenExpiry": "Your session is about to expire. Click the Cancel button to continue",
63
64
  "yes": "Yes",
64
65
  "unknownError": "Unknown Error",
@@ -59,6 +59,7 @@
59
59
  "status": "状态",
60
60
  "submit": "提交",
61
61
  "success": "成功",
62
+ "timeDifferenceInvalid": "设备时间和服务器时间差为{0},超过{1}秒的限制,请调整设备时间,如果异常请告知管理员",
62
63
  "tokenExpiry": "您的会话即将过期。点击 取消 按钮继续使用",
63
64
  "yes": "是",
64
65
  "unknownError": "未知错误",
@@ -59,6 +59,7 @@
59
59
  "status": "狀態",
60
60
  "submit": "提交",
61
61
  "success": "成功",
62
+ "timeDifferenceInvalid": "設備時間和服務器時間差為{0},超過{1}秒的限制,請調整設備時間,如果異常請告知管理員",
62
63
  "tokenExpiry": "您的會話即將過期。點擊 取消 按鈕繼續使用",
63
64
  "yes": "是",
64
65
  "unknownError": "未知錯誤",
@@ -25,6 +25,7 @@ export type { IApi, IApiPayload } from '@etsoo/restclient';
25
25
  export * from './result/ActionResult';
26
26
  export * from './result/ActionResultError';
27
27
  export * from './result/IActionResult';
28
+ export * from './result/InitCallResult';
28
29
  export * from './state/Culture';
29
30
  export * from './state/State';
30
31
  export * from './state/User';
package/lib/cjs/index.js CHANGED
@@ -48,6 +48,7 @@ Object.defineProperty(exports, "createClient", { enumerable: true, get: function
48
48
  __exportStar(require("./result/ActionResult"), exports);
49
49
  __exportStar(require("./result/ActionResultError"), exports);
50
50
  __exportStar(require("./result/IActionResult"), exports);
51
+ __exportStar(require("./result/InitCallResult"), exports);
51
52
  // state
52
53
  __exportStar(require("./state/Culture"), exports);
53
54
  __exportStar(require("./state/State"), exports);
@@ -0,0 +1,22 @@
1
+ import { IActionResult, IResultData } from './IActionResult';
2
+ /**
3
+ * Result data with id, follow this style to extend for specific model
4
+ */
5
+ export interface InitCallResultData extends IResultData {
6
+ /**
7
+ * Secret passphrase
8
+ */
9
+ passphrase: string;
10
+ /**
11
+ * Actual seconds gap
12
+ */
13
+ seconds: number;
14
+ /**
15
+ * Valid seconds gap
16
+ */
17
+ validSeconds: number;
18
+ }
19
+ /**
20
+ * Init call result
21
+ */
22
+ export declare type InitCallResult = IActionResult<InitCallResultData>;
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -27,6 +27,10 @@ export interface IUserData {
27
27
  * Access token
28
28
  */
29
29
  readonly token: string;
30
+ /**
31
+ * Secret passphrase
32
+ */
33
+ readonly passphrase: string;
30
34
  }
31
35
  /**
32
36
  * User interface
@@ -133,9 +133,10 @@ export interface ICoreApp<S extends IAppSettings, N, C extends NotificationCallP
133
133
  * Encrypt message
134
134
  * @param message Message
135
135
  * @param passphrase Secret passphrase
136
+ * @param iterations Iterations, 1000 times, 1 - 99
136
137
  * @returns Result
137
138
  */
138
- encrypt(message: string, passphrase: string): string;
139
+ encrypt(message: string, passphrase: string, iterations?: number): string;
139
140
  /**
140
141
  * Format date to string
141
142
  * @param input Input date
@@ -216,6 +217,13 @@ export interface ICoreApp<S extends IAppSettings, N, C extends NotificationCallP
216
217
  * @returns Time zone
217
218
  */
218
219
  getTimeZone(): string | undefined;
220
+ /**
221
+ * Hash message, SHA3 or HmacSHA512, 512 as Base64
222
+ * https://cryptojs.gitbook.io/docs/
223
+ * @param message Message
224
+ * @param passphrase Secret passphrase
225
+ */
226
+ hash(message: string, passphrase?: string): string;
219
227
  /**
220
228
  * Check use has the specific role permission or not
221
229
  * @param roles Roles to check
@@ -403,16 +411,17 @@ export declare abstract class CoreApp<S extends IAppSettings, N, C extends Notif
403
411
  * Encrypt message
404
412
  * @param message Message
405
413
  * @param passphrase Secret passphrase
414
+ * @param iterations Iterations, 1000 times, 1 - 99
406
415
  * @returns Result
407
416
  */
408
- encrypt(message: string, passphrase: string): string;
417
+ encrypt(message: string, passphrase: string, iterations?: number): string;
409
418
  /**
410
419
  * Enchance secret passphrase
411
420
  * @param passphrase Secret passphrase
412
- * @param miliseconds Miliseconds
421
+ * @param timestamp Timestamp
413
422
  * @returns Enhanced passphrase
414
423
  */
415
- protected encryptionEnhance(passphrase: string, miliseconds: number): string;
424
+ protected encryptionEnhance(passphrase: string, timestamp: string): string;
416
425
  /**
417
426
  * Format date to string
418
427
  * @param input Input date
@@ -488,6 +497,13 @@ export declare abstract class CoreApp<S extends IAppSettings, N, C extends Notif
488
497
  * @returns Time zone
489
498
  */
490
499
  getTimeZone(): string | undefined;
500
+ /**
501
+ * Hash message, SHA3 or HmacSHA512, 512 as Base64
502
+ * https://cryptojs.gitbook.io/docs/
503
+ * @param message Message
504
+ * @param passphrase Secret passphrase
505
+ */
506
+ hash(message: string, passphrase?: string): string;
491
507
  /**
492
508
  * Check use has the specific role permission or not
493
509
  * @param roles Roles to check
@@ -1,7 +1,7 @@
1
1
  import { NotificationAlign, NotificationMessageType } from '@etsoo/notificationbase';
2
2
  import { ApiDataError } from '@etsoo/restclient';
3
- import { DateUtils, DomUtils, NumberUtils, StorageUtils } from '@etsoo/shared';
4
- import { AES } from 'crypto-js';
3
+ import { DateUtils, DomUtils, NumberUtils, StorageUtils, Utils } from '@etsoo/shared';
4
+ import { AES, algo, enc, HmacSHA512, lib, mode, pad, PBKDF2, SHA3 } from 'crypto-js';
5
5
  import { AddressRegion } from '../address/AddressRegion';
6
6
  import { AddressUtils } from '../address/AddressUtils';
7
7
  import { ActionResultError } from '../result/ActionResultError';
@@ -200,10 +200,25 @@ export class CoreApp {
200
200
  * @returns Pure text
201
201
  */
202
202
  decrypt(messageEncrypted, passphrase) {
203
- const pos = messageEncrypted.indexOf('M');
204
- const miliseconds = parseInt(messageEncrypted.substring(0, pos));
203
+ // Timestamp splitter
204
+ const pos = messageEncrypted.indexOf('+');
205
+ const timestamp = messageEncrypted.substring(0, pos);
205
206
  const message = messageEncrypted.substring(pos + 1);
206
- return AES.decrypt(message, this.encryptionEnhance(passphrase, miliseconds)).toString();
207
+ // Iterations
208
+ const iterations = parseInt(message.substring(0, 2), 10);
209
+ const salt = enc.Hex.parse(message.substring(2, 34));
210
+ const iv = enc.Hex.parse(message.substring(34, 66));
211
+ const encrypted = message.substring(66);
212
+ const key = PBKDF2(this.encryptionEnhance(passphrase, timestamp), salt, {
213
+ keySize: 8,
214
+ hasher: algo.SHA256,
215
+ iterations: 1000 * iterations
216
+ });
217
+ return AES.decrypt(encrypted, key, {
218
+ iv,
219
+ padding: pad.Pkcs7,
220
+ mode: mode.CBC
221
+ }).toString(enc.Utf8);
207
222
  }
208
223
  /**
209
224
  * Detect IP data, call only one time
@@ -242,23 +257,43 @@ export class CoreApp {
242
257
  * Encrypt message
243
258
  * @param message Message
244
259
  * @param passphrase Secret passphrase
260
+ * @param iterations Iterations, 1000 times, 1 - 99
245
261
  * @returns Result
246
262
  */
247
- encrypt(message, passphrase) {
248
- const miliseconds = new Date().getUTCMilliseconds();
249
- return (miliseconds +
250
- 'M' +
251
- AES.encrypt(message, this.encryptionEnhance(passphrase, miliseconds)).toString());
263
+ encrypt(message, passphrase, iterations) {
264
+ // Default 1 * 1000
265
+ iterations !== null && iterations !== void 0 ? iterations : (iterations = 1);
266
+ // Timestamp
267
+ const timestamp = Utils.numberToChars(new Date().getTime());
268
+ const bits = 16; // 128 / 8
269
+ const salt = lib.WordArray.random(bits);
270
+ const key = PBKDF2(this.encryptionEnhance(passphrase, timestamp), salt, {
271
+ keySize: 8,
272
+ hasher: algo.SHA256,
273
+ iterations: 1000 * iterations
274
+ });
275
+ const iv = lib.WordArray.random(bits);
276
+ return (timestamp +
277
+ '+' +
278
+ iterations.toString().padStart(2, '0') +
279
+ salt.toString(enc.Hex) +
280
+ iv.toString(enc.Hex) +
281
+ AES.encrypt(message, key, {
282
+ iv,
283
+ padding: pad.Pkcs7,
284
+ mode: mode.CBC
285
+ }).toString() // enc.Base64
286
+ );
252
287
  }
253
288
  /**
254
289
  * Enchance secret passphrase
255
290
  * @param passphrase Secret passphrase
256
- * @param miliseconds Miliseconds
291
+ * @param timestamp Timestamp
257
292
  * @returns Enhanced passphrase
258
293
  */
259
- encryptionEnhance(passphrase, miliseconds) {
294
+ encryptionEnhance(passphrase, timestamp) {
260
295
  var _a;
261
- passphrase += miliseconds.toString();
296
+ passphrase += timestamp;
262
297
  passphrase += passphrase.length.toString();
263
298
  return passphrase + ((_a = this.passphrase) !== null && _a !== void 0 ? _a : '');
264
299
  }
@@ -388,6 +423,18 @@ export class CoreApp {
388
423
  // settings.timeZone = Utils.getTimeZone()
389
424
  return (_a = this.settings.timeZone) !== null && _a !== void 0 ? _a : (_b = this.ipData) === null || _b === void 0 ? void 0 : _b.timezone;
390
425
  }
426
+ /**
427
+ * Hash message, SHA3 or HmacSHA512, 512 as Base64
428
+ * https://cryptojs.gitbook.io/docs/
429
+ * @param message Message
430
+ * @param passphrase Secret passphrase
431
+ */
432
+ hash(message, passphrase) {
433
+ if (passphrase == null)
434
+ return SHA3(message, { outputLength: 512 }).toString(enc.Base64);
435
+ else
436
+ return HmacSHA512(message, passphrase).toString(enc.Base64);
437
+ }
391
438
  /**
392
439
  * Check use has the specific role permission or not
393
440
  * @param roles Roles to check
@@ -537,6 +584,7 @@ export class CoreApp {
537
584
  */
538
585
  userLogin(user, refreshToken, keep = false) {
539
586
  this.userData = user;
587
+ this.passphrase = user.passphrase;
540
588
  this.authorize(user.token, refreshToken, keep);
541
589
  }
542
590
  /**
@@ -59,6 +59,7 @@
59
59
  "status": "Status",
60
60
  "submit": "Submit",
61
61
  "success": "Success",
62
+ "timeDifferenceInvalid": "The time difference between the device and the server is {0}, which exceeds the limit of {1} seconds. Please adjust the device time. If it is abnormal, please inform the administrator",
62
63
  "tokenExpiry": "Your session is about to expire. Click the Cancel button to continue",
63
64
  "yes": "Yes",
64
65
  "unknownError": "Unknown Error",
@@ -59,6 +59,7 @@
59
59
  "status": "状态",
60
60
  "submit": "提交",
61
61
  "success": "成功",
62
+ "timeDifferenceInvalid": "设备时间和服务器时间差为{0},超过{1}秒的限制,请调整设备时间,如果异常请告知管理员",
62
63
  "tokenExpiry": "您的会话即将过期。点击 取消 按钮继续使用",
63
64
  "yes": "是",
64
65
  "unknownError": "未知错误",
@@ -59,6 +59,7 @@
59
59
  "status": "狀態",
60
60
  "submit": "提交",
61
61
  "success": "成功",
62
+ "timeDifferenceInvalid": "設備時間和服務器時間差為{0},超過{1}秒的限制,請調整設備時間,如果異常請告知管理員",
62
63
  "tokenExpiry": "您的會話即將過期。點擊 取消 按鈕繼續使用",
63
64
  "yes": "是",
64
65
  "unknownError": "未知錯誤",
@@ -25,6 +25,7 @@ export type { IApi, IApiPayload } from '@etsoo/restclient';
25
25
  export * from './result/ActionResult';
26
26
  export * from './result/ActionResultError';
27
27
  export * from './result/IActionResult';
28
+ export * from './result/InitCallResult';
28
29
  export * from './state/Culture';
29
30
  export * from './state/State';
30
31
  export * from './state/User';
package/lib/mjs/index.js CHANGED
@@ -33,6 +33,7 @@ export { ApiAuthorizationScheme, createClient } from '@etsoo/restclient';
33
33
  export * from './result/ActionResult';
34
34
  export * from './result/ActionResultError';
35
35
  export * from './result/IActionResult';
36
+ export * from './result/InitCallResult';
36
37
  // state
37
38
  export * from './state/Culture';
38
39
  export * from './state/State';
@@ -0,0 +1,22 @@
1
+ import { IActionResult, IResultData } from './IActionResult';
2
+ /**
3
+ * Result data with id, follow this style to extend for specific model
4
+ */
5
+ export interface InitCallResultData extends IResultData {
6
+ /**
7
+ * Secret passphrase
8
+ */
9
+ passphrase: string;
10
+ /**
11
+ * Actual seconds gap
12
+ */
13
+ seconds: number;
14
+ /**
15
+ * Valid seconds gap
16
+ */
17
+ validSeconds: number;
18
+ }
19
+ /**
20
+ * Init call result
21
+ */
22
+ export declare type InitCallResult = IActionResult<InitCallResultData>;
@@ -0,0 +1 @@
1
+ export {};
@@ -27,6 +27,10 @@ export interface IUserData {
27
27
  * Access token
28
28
  */
29
29
  readonly token: string;
30
+ /**
31
+ * Secret passphrase
32
+ */
33
+ readonly passphrase: string;
30
34
  }
31
35
  /**
32
36
  * User interface
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@etsoo/appscript",
3
- "version": "1.1.63",
3
+ "version": "1.1.67",
4
4
  "description": "Applications shared TypeScript framework",
5
5
  "main": "lib/cjs/index.js",
6
6
  "module": "lib/mjs/index.js",
@@ -23,7 +23,7 @@
23
23
  "testMatch": [
24
24
  "<rootDir>/__tests__/**/*.ts"
25
25
  ],
26
- "testEnvironment": "node",
26
+ "testEnvironment": "jsdom",
27
27
  "transform": {
28
28
  ".+\\.jsx?$": "babel-jest",
29
29
  ".+\\.tsx?$": "ts-jest"
@@ -54,7 +54,7 @@
54
54
  "dependencies": {
55
55
  "@etsoo/notificationbase": "^1.0.94",
56
56
  "@etsoo/restclient": "^1.0.62",
57
- "@etsoo/shared": "^1.0.75",
57
+ "@etsoo/shared": "^1.0.76",
58
58
  "@types/crypto-js": "^4.0.2",
59
59
  "crypto-js": "^4.1.1"
60
60
  },
@@ -65,13 +65,13 @@
65
65
  "@babel/preset-env": "^7.16.4",
66
66
  "@babel/runtime-corejs3": "^7.16.3",
67
67
  "@types/jest": "^27.0.3",
68
- "@typescript-eslint/eslint-plugin": "^5.5.0",
69
- "@typescript-eslint/parser": "^5.5.0",
70
- "eslint": "^8.4.0",
68
+ "@typescript-eslint/eslint-plugin": "^5.6.0",
69
+ "@typescript-eslint/parser": "^5.6.0",
70
+ "eslint": "^8.4.1",
71
71
  "eslint-config-airbnb-base": "^15.0.0",
72
72
  "eslint-plugin-import": "^2.25.3",
73
73
  "jest": "^27.4.3",
74
- "ts-jest": "^27.0.7",
74
+ "ts-jest": "^27.1.0",
75
75
  "typescript": "^4.5.2"
76
76
  }
77
77
  }
@@ -12,9 +12,20 @@ import {
12
12
  DateUtils,
13
13
  DomUtils,
14
14
  NumberUtils,
15
- StorageUtils
15
+ StorageUtils,
16
+ Utils
16
17
  } from '@etsoo/shared';
17
- import { AES } from 'crypto-js';
18
+ import {
19
+ AES,
20
+ algo,
21
+ enc,
22
+ HmacSHA512,
23
+ lib,
24
+ mode,
25
+ pad,
26
+ PBKDF2,
27
+ SHA3
28
+ } from 'crypto-js';
18
29
  import { AddressRegion } from '../address/AddressRegion';
19
30
  import { AddressUtils } from '../address/AddressUtils';
20
31
  import { ActionResultError } from '../result/ActionResultError';
@@ -183,9 +194,10 @@ export interface ICoreApp<
183
194
  * Encrypt message
184
195
  * @param message Message
185
196
  * @param passphrase Secret passphrase
197
+ * @param iterations Iterations, 1000 times, 1 - 99
186
198
  * @returns Result
187
199
  */
188
- encrypt(message: string, passphrase: string): string;
200
+ encrypt(message: string, passphrase: string, iterations?: number): string;
189
201
 
190
202
  /**
191
203
  * Format date to string
@@ -289,6 +301,14 @@ export interface ICoreApp<
289
301
  */
290
302
  getTimeZone(): string | undefined;
291
303
 
304
+ /**
305
+ * Hash message, SHA3 or HmacSHA512, 512 as Base64
306
+ * https://cryptojs.gitbook.io/docs/
307
+ * @param message Message
308
+ * @param passphrase Secret passphrase
309
+ */
310
+ hash(message: string, passphrase?: string): string;
311
+
292
312
  /**
293
313
  * Check use has the specific role permission or not
294
314
  * @param roles Roles to check
@@ -659,13 +679,33 @@ export abstract class CoreApp<
659
679
  * @returns Pure text
660
680
  */
661
681
  decrypt(messageEncrypted: string, passphrase: string) {
662
- const pos = messageEncrypted.indexOf('M');
663
- const miliseconds = parseInt(messageEncrypted.substring(0, pos));
682
+ // Timestamp splitter
683
+ const pos = messageEncrypted.indexOf('+');
684
+ const timestamp = messageEncrypted.substring(0, pos);
664
685
  const message = messageEncrypted.substring(pos + 1);
665
- return AES.decrypt(
666
- message,
667
- this.encryptionEnhance(passphrase, miliseconds)
668
- ).toString();
686
+
687
+ // Iterations
688
+ const iterations = parseInt(message.substring(0, 2), 10);
689
+
690
+ const salt = enc.Hex.parse(message.substring(2, 34));
691
+ const iv = enc.Hex.parse(message.substring(34, 66));
692
+ const encrypted = message.substring(66);
693
+
694
+ const key = PBKDF2(
695
+ this.encryptionEnhance(passphrase, timestamp),
696
+ salt,
697
+ {
698
+ keySize: 8, // 256 / 32
699
+ hasher: algo.SHA256,
700
+ iterations: 1000 * iterations
701
+ }
702
+ );
703
+
704
+ return AES.decrypt(encrypted, key, {
705
+ iv,
706
+ padding: pad.Pkcs7,
707
+ mode: mode.CBC
708
+ }).toString(enc.Utf8);
669
709
  }
670
710
 
671
711
  /**
@@ -712,28 +752,51 @@ export abstract class CoreApp<
712
752
  * Encrypt message
713
753
  * @param message Message
714
754
  * @param passphrase Secret passphrase
755
+ * @param iterations Iterations, 1000 times, 1 - 99
715
756
  * @returns Result
716
757
  */
717
- encrypt(message: string, passphrase: string) {
718
- const miliseconds = new Date().getUTCMilliseconds();
758
+ encrypt(message: string, passphrase: string, iterations?: number) {
759
+ // Default 1 * 1000
760
+ iterations ??= 1;
761
+
762
+ // Timestamp
763
+ const timestamp = Utils.numberToChars(new Date().getTime());
764
+
765
+ const bits = 16; // 128 / 8
766
+ const salt = lib.WordArray.random(bits);
767
+ const key = PBKDF2(
768
+ this.encryptionEnhance(passphrase, timestamp),
769
+ salt,
770
+ {
771
+ keySize: 8, // 256 / 32
772
+ hasher: algo.SHA256,
773
+ iterations: 1000 * iterations
774
+ }
775
+ );
776
+ const iv = lib.WordArray.random(bits);
777
+
719
778
  return (
720
- miliseconds +
721
- 'M' +
722
- AES.encrypt(
723
- message,
724
- this.encryptionEnhance(passphrase, miliseconds)
725
- ).toString()
779
+ timestamp +
780
+ '+' +
781
+ iterations.toString().padStart(2, '0') +
782
+ salt.toString(enc.Hex) +
783
+ iv.toString(enc.Hex) +
784
+ AES.encrypt(message, key, {
785
+ iv,
786
+ padding: pad.Pkcs7,
787
+ mode: mode.CBC
788
+ }).toString() // enc.Base64
726
789
  );
727
790
  }
728
791
 
729
792
  /**
730
793
  * Enchance secret passphrase
731
794
  * @param passphrase Secret passphrase
732
- * @param miliseconds Miliseconds
795
+ * @param timestamp Timestamp
733
796
  * @returns Enhanced passphrase
734
797
  */
735
- protected encryptionEnhance(passphrase: string, miliseconds: number) {
736
- passphrase += miliseconds.toString();
798
+ protected encryptionEnhance(passphrase: string, timestamp: string) {
799
+ passphrase += timestamp;
737
800
  passphrase += passphrase.length.toString();
738
801
  return passphrase + (this.passphrase ?? '');
739
802
  }
@@ -896,6 +959,18 @@ export abstract class CoreApp<
896
959
  return this.settings.timeZone ?? this.ipData?.timezone;
897
960
  }
898
961
 
962
+ /**
963
+ * Hash message, SHA3 or HmacSHA512, 512 as Base64
964
+ * https://cryptojs.gitbook.io/docs/
965
+ * @param message Message
966
+ * @param passphrase Secret passphrase
967
+ */
968
+ hash(message: string, passphrase?: string) {
969
+ if (passphrase == null)
970
+ return SHA3(message, { outputLength: 512 }).toString(enc.Base64);
971
+ else return HmacSHA512(message, passphrase).toString(enc.Base64);
972
+ }
973
+
899
974
  /**
900
975
  * Check use has the specific role permission or not
901
976
  * @param roles Roles to check
@@ -1064,6 +1139,7 @@ export abstract class CoreApp<
1064
1139
  */
1065
1140
  userLogin(user: IUserData, refreshToken: string, keep: boolean = false) {
1066
1141
  this.userData = user;
1142
+ this.passphrase = user.passphrase;
1067
1143
  this.authorize(user.token, refreshToken, keep);
1068
1144
  }
1069
1145
 
@@ -59,6 +59,7 @@
59
59
  "status": "Status",
60
60
  "submit": "Submit",
61
61
  "success": "Success",
62
+ "timeDifferenceInvalid": "The time difference between the device and the server is {0}, which exceeds the limit of {1} seconds. Please adjust the device time. If it is abnormal, please inform the administrator",
62
63
  "tokenExpiry": "Your session is about to expire. Click the Cancel button to continue",
63
64
  "yes": "Yes",
64
65
  "unknownError": "Unknown Error",
@@ -59,6 +59,7 @@
59
59
  "status": "状态",
60
60
  "submit": "提交",
61
61
  "success": "成功",
62
+ "timeDifferenceInvalid": "设备时间和服务器时间差为{0},超过{1}秒的限制,请调整设备时间,如果异常请告知管理员",
62
63
  "tokenExpiry": "您的会话即将过期。点击 取消 按钮继续使用",
63
64
  "yes": "是",
64
65
  "unknownError": "未知错误",
@@ -59,6 +59,7 @@
59
59
  "status": "狀態",
60
60
  "submit": "提交",
61
61
  "success": "成功",
62
+ "timeDifferenceInvalid": "設備時間和服務器時間差為{0},超過{1}秒的限制,請調整設備時間,如果異常請告知管理員",
62
63
  "tokenExpiry": "您的會話即將過期。點擊 取消 按鈕繼續使用",
63
64
  "yes": "是",
64
65
  "unknownError": "未知錯誤",
package/src/index.ts CHANGED
@@ -42,6 +42,7 @@ export type { IApi, IApiPayload } from '@etsoo/restclient';
42
42
  export * from './result/ActionResult';
43
43
  export * from './result/ActionResultError';
44
44
  export * from './result/IActionResult';
45
+ export * from './result/InitCallResult';
45
46
 
46
47
  // state
47
48
  export * from './state/Culture';
@@ -0,0 +1,26 @@
1
+ import { IActionResult, IResultData } from './IActionResult';
2
+
3
+ /**
4
+ * Result data with id, follow this style to extend for specific model
5
+ */
6
+ export interface InitCallResultData extends IResultData {
7
+ /**
8
+ * Secret passphrase
9
+ */
10
+ passphrase: string;
11
+
12
+ /**
13
+ * Actual seconds gap
14
+ */
15
+ seconds: number;
16
+
17
+ /**
18
+ * Valid seconds gap
19
+ */
20
+ validSeconds: number;
21
+ }
22
+
23
+ /**
24
+ * Init call result
25
+ */
26
+ export type InitCallResult = IActionResult<InitCallResultData>;
package/src/state/User.ts CHANGED
@@ -33,6 +33,11 @@ export interface IUserData {
33
33
  * Access token
34
34
  */
35
35
  readonly token: string;
36
+
37
+ /**
38
+ * Secret passphrase
39
+ */
40
+ readonly passphrase: string;
36
41
  }
37
42
 
38
43
  /**