@sync-in/server 1.5.1 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +27 -0
- package/README.md +1 -0
- package/environment/environment.dist.min.yaml +1 -0
- package/environment/environment.dist.yaml +88 -30
- package/migrations/0002_sleepy_korath.sql +1 -0
- package/migrations/meta/0002_snapshot.json +2424 -0
- package/migrations/meta/_journal.json +7 -0
- package/package.json +6 -4
- package/server/app.bootstrap.js +1 -1
- package/server/app.bootstrap.js.map +1 -1
- package/server/applications/files/services/files-manager.service.js +1 -2
- package/server/applications/files/services/files-manager.service.js.map +1 -1
- package/server/applications/files/services/files-only-office-manager.service.js +5 -6
- package/server/applications/files/services/files-only-office-manager.service.js.map +1 -1
- package/server/applications/files/utils/files.js +6 -4
- package/server/applications/files/utils/files.js.map +1 -1
- package/server/applications/links/links.controller.js +2 -2
- package/server/applications/links/links.controller.js.map +1 -1
- package/server/applications/links/services/links-manager.service.js +2 -1
- package/server/applications/links/services/links-manager.service.js.map +1 -1
- package/server/applications/links/services/links-manager.service.spec.js +6 -3
- package/server/applications/links/services/links-manager.service.spec.js.map +1 -1
- package/server/applications/notifications/constants/notifications.js +9 -0
- package/server/applications/notifications/constants/notifications.js.map +1 -1
- package/server/applications/notifications/i18n/fr.js +10 -1
- package/server/applications/notifications/i18n/fr.js.map +1 -1
- package/server/applications/notifications/interfaces/notification-properties.interface.js.map +1 -1
- package/server/applications/notifications/mails/models.js +41 -3
- package/server/applications/notifications/mails/models.js.map +1 -1
- package/server/applications/notifications/mails/templates.js +1 -1
- package/server/applications/notifications/mails/templates.js.map +1 -1
- package/server/applications/notifications/schemas/notifications.schema.js +2 -1
- package/server/applications/notifications/schemas/notifications.schema.js.map +1 -1
- package/server/applications/notifications/services/notifications-manager.service.js +16 -13
- package/server/applications/notifications/services/notifications-manager.service.js.map +1 -1
- package/server/applications/notifications/services/notifications-manager.service.spec.js +9 -8
- package/server/applications/notifications/services/notifications-manager.service.spec.js.map +1 -1
- package/server/applications/notifications/services/notifications-queries.service.js +1 -1
- package/server/applications/notifications/services/notifications-queries.service.js.map +1 -1
- package/server/applications/shares/services/shares-manager.service.js +3 -2
- package/server/applications/shares/services/shares-manager.service.js.map +1 -1
- package/server/applications/sync/constants/auth.js +2 -2
- package/server/applications/sync/constants/auth.js.map +1 -1
- package/server/applications/sync/dtos/sync-client-registration.dto.js +5 -0
- package/server/applications/sync/dtos/sync-client-registration.dto.js.map +1 -1
- package/server/applications/sync/dtos/sync-operations.dto.js +1 -2
- package/server/applications/sync/dtos/sync-operations.dto.js.map +1 -1
- package/server/applications/sync/schemas/sync-clients.schema.js +2 -1
- package/server/applications/sync/schemas/sync-clients.schema.js.map +1 -1
- package/server/applications/sync/schemas/sync-paths.schema.js +2 -1
- package/server/applications/sync/schemas/sync-paths.schema.js.map +1 -1
- package/server/applications/sync/services/sync-clients-manager.service.js +28 -20
- package/server/applications/sync/services/sync-clients-manager.service.js.map +1 -1
- package/server/applications/sync/services/sync-clients-manager.service.spec.js +24 -18
- package/server/applications/sync/services/sync-clients-manager.service.spec.js.map +1 -1
- package/server/applications/sync/services/sync-queries.service.js +5 -5
- package/server/applications/sync/services/sync-queries.service.js.map +1 -1
- package/server/applications/users/admin-users.controller.js +48 -37
- package/server/applications/users/admin-users.controller.js.map +1 -1
- package/server/applications/users/admin-users.controller.spec.js +15 -0
- package/server/applications/users/admin-users.controller.spec.js.map +1 -1
- package/server/applications/users/constants/routes.js +5 -0
- package/server/applications/users/constants/routes.js.map +1 -1
- package/server/applications/users/constants/user.js +8 -0
- package/server/applications/users/constants/user.js.map +1 -1
- package/server/applications/users/dto/delete-user.dto.js +5 -23
- package/server/applications/users/dto/delete-user.dto.js.map +1 -1
- package/server/applications/users/dto/user-properties.dto.js +38 -3
- package/server/applications/users/dto/user-properties.dto.js.map +1 -1
- package/server/applications/users/interfaces/admin-user.interface.js.map +1 -1
- package/server/applications/users/interfaces/user-secrets.interface.js +10 -0
- package/server/applications/users/interfaces/user-secrets.interface.js.map +1 -0
- package/server/applications/users/models/user.model.js +84 -50
- package/server/applications/users/models/user.model.js.map +1 -1
- package/server/applications/users/schemas/user.interface.js.map +1 -1
- package/server/applications/users/schemas/users.schema.js +2 -0
- package/server/applications/users/schemas/users.schema.js.map +1 -1
- package/server/applications/users/services/admin-users-manager.service.js +7 -19
- package/server/applications/users/services/admin-users-manager.service.js.map +1 -1
- package/server/applications/users/services/admin-users-manager.service.spec.js +7 -26
- package/server/applications/users/services/admin-users-manager.service.spec.js.map +1 -1
- package/server/applications/users/services/admin-users-queries.service.js +1 -0
- package/server/applications/users/services/admin-users-queries.service.js.map +1 -1
- package/server/applications/users/services/users-manager.service.js +138 -28
- package/server/applications/users/services/users-manager.service.js.map +1 -1
- package/server/applications/users/services/users-manager.service.spec.js +11 -9
- package/server/applications/users/services/users-manager.service.spec.js.map +1 -1
- package/server/applications/users/services/users-queries.service.js +63 -57
- package/server/applications/users/services/users-queries.service.js.map +1 -1
- package/server/applications/users/users.controller.js +48 -1
- package/server/applications/users/users.controller.js.map +1 -1
- package/server/applications/users/users.controller.spec.js +8 -1
- package/server/applications/users/users.controller.spec.js.map +1 -1
- package/server/applications/users/users.e2e-spec.js +2 -1
- package/server/applications/users/users.e2e-spec.js.map +1 -1
- package/server/applications/users/utils/avatar.js +48 -0
- package/server/applications/users/utils/avatar.js.map +1 -0
- package/server/authentication/auth.config.js +85 -26
- package/server/authentication/auth.config.js.map +1 -1
- package/server/authentication/auth.controller.js +117 -9
- package/server/authentication/auth.controller.js.map +1 -1
- package/server/authentication/auth.controller.spec.js +16 -1
- package/server/authentication/auth.controller.spec.js.map +1 -1
- package/server/authentication/auth.e2e-spec.js +4 -3
- package/server/authentication/auth.e2e-spec.js.map +1 -1
- package/server/authentication/auth.module.js +4 -1
- package/server/authentication/auth.module.js.map +1 -1
- package/server/authentication/constants/auth.js +37 -4
- package/server/authentication/constants/auth.js.map +1 -1
- package/server/authentication/constants/routes.js +21 -0
- package/server/authentication/constants/routes.js.map +1 -1
- package/server/authentication/constants/scope.js +20 -0
- package/server/authentication/constants/scope.js.map +1 -0
- package/server/authentication/dto/login-response.dto.js +27 -4
- package/server/authentication/dto/login-response.dto.js.map +1 -1
- package/server/authentication/dto/token-response.dto.js +5 -0
- package/server/authentication/dto/token-response.dto.js.map +1 -1
- package/server/{applications/users/dto/user-password.dto.js → authentication/dto/two-fa-verify.dto.js} +27 -9
- package/server/authentication/dto/two-fa-verify.dto.js.map +1 -0
- package/server/authentication/guards/auth-basic.strategy.js +6 -5
- package/server/authentication/guards/auth-basic.strategy.js.map +1 -1
- package/server/authentication/guards/auth-token-access.strategy.js +3 -2
- package/server/authentication/guards/auth-token-access.strategy.js.map +1 -1
- package/server/authentication/guards/auth-token-refresh.strategy.js +3 -2
- package/server/authentication/guards/auth-token-refresh.strategy.js.map +1 -1
- package/server/authentication/guards/auth-two-fa-guard.js +81 -0
- package/server/authentication/guards/auth-two-fa-guard.js.map +1 -0
- package/server/authentication/interfaces/jwt-payload.interface.js +5 -0
- package/server/authentication/interfaces/jwt-payload.interface.js.map +1 -1
- package/server/authentication/interfaces/token.interface.js +2 -0
- package/server/authentication/interfaces/token.interface.js.map +1 -1
- package/server/authentication/interfaces/two-fa-setup.interface.js +10 -0
- package/server/authentication/interfaces/two-fa-setup.interface.js.map +1 -0
- package/server/authentication/models/auth-method.js.map +1 -1
- package/server/authentication/services/auth-manager.service.js +72 -49
- package/server/authentication/services/auth-manager.service.js.map +1 -1
- package/server/authentication/services/auth-methods/auth-method-database.service.js +3 -3
- package/server/authentication/services/auth-methods/auth-method-database.service.js.map +1 -1
- package/server/authentication/services/auth-methods/auth-method-database.service.spec.js +5 -0
- package/server/authentication/services/auth-methods/auth-method-database.service.spec.js.map +1 -1
- package/server/authentication/services/auth-methods/auth-method-ldap.service.js +100 -27
- package/server/authentication/services/auth-methods/auth-method-ldap.service.js.map +1 -1
- package/server/authentication/services/auth-methods/auth-method-ldap.service.spec.js +11 -12
- package/server/authentication/services/auth-methods/auth-method-ldap.service.spec.js.map +1 -1
- package/server/authentication/services/auth-methods/auth-method-two-fa.service.js +251 -0
- package/server/authentication/services/auth-methods/auth-method-two-fa.service.js.map +1 -0
- package/server/authentication/services/auth-methods/auth-method-two-fa.service.spec.js +41 -0
- package/server/authentication/services/auth-methods/auth-method-two-fa.service.spec.js.map +1 -0
- package/server/authentication/utils/crypt-secret.js +68 -0
- package/server/authentication/utils/crypt-secret.js.map +1 -0
- package/server/common/functions.js +18 -2
- package/server/common/functions.js.map +1 -1
- package/server/common/qrcode.js +34 -0
- package/server/common/qrcode.js.map +1 -0
- package/server/common/shared.js +18 -0
- package/server/common/shared.js.map +1 -1
- package/server/configuration/config.environment.js +23 -6
- package/server/configuration/config.environment.js.map +1 -1
- package/server/configuration/config.interfaces.js +10 -0
- package/server/configuration/config.interfaces.js.map +1 -0
- package/server/configuration/config.loader.js.map +1 -1
- package/server/configuration/config.validation.js +13 -13
- package/server/configuration/config.validation.js.map +1 -1
- package/server/infrastructure/cache/adapters/mysql-cache.adapter.js +6 -6
- package/server/infrastructure/cache/adapters/mysql-cache.adapter.js.map +1 -1
- package/server/infrastructure/cache/schemas/mysql-cache.schema.js +2 -1
- package/server/infrastructure/cache/schemas/mysql-cache.schema.js.map +1 -1
- package/server/infrastructure/cache/services/cache.service.js.map +1 -1
- package/server/infrastructure/database/columns.js +39 -0
- package/server/infrastructure/database/columns.js.map +1 -0
- package/server/infrastructure/database/database.config.js +0 -1
- package/server/infrastructure/database/database.config.js.map +1 -1
- package/server/infrastructure/mailer/interfaces/mail.interface.js.map +1 -1
- package/server/infrastructure/mailer/mailer.config.js +12 -0
- package/server/infrastructure/mailer/mailer.config.js.map +1 -1
- package/server/infrastructure/mailer/mailer.service.js +2 -1
- package/server/infrastructure/mailer/mailer.service.js.map +1 -1
- package/static/assets/mimes/text-x-c.svg +1 -0
- package/static/chunk-2TZUZMCM.js +4 -0
- package/static/chunk-2XJ5Z2GZ.js +1 -0
- package/static/{chunk-P7CTJ5BG.js → chunk-5M4YJZUB.js} +2 -2
- package/static/{chunk-5J4VRDKB.js → chunk-5ZGQYTS2.js} +1 -1
- package/static/chunk-6BFNMDUD.js +1 -0
- package/static/chunk-6IRL673W.js +559 -0
- package/static/{chunk-MHSCCXVL.js → chunk-ABGR5AYC.js} +1 -1
- package/static/chunk-CN27VAGB.js +1 -0
- package/static/{chunk-2LVCLKCK.js → chunk-DNMO47SY.js} +1 -1
- package/static/{chunk-J4ALHUDX.js → chunk-EI4PVI2W.js} +1 -1
- package/static/chunk-ET6QDNNM.js +1 -0
- package/static/{chunk-4UT5VH7R.js → chunk-G2TKYYWK.js} +1 -1
- package/static/chunk-G3FOG2QB.js +1 -0
- package/static/{chunk-DFQKHCDR.js → chunk-GCUWGVYT.js} +1 -1
- package/static/{chunk-RSS6GYNE.js → chunk-HME7LAEY.js} +1 -1
- package/static/chunk-IEUANP3Q.js +1 -0
- package/static/{chunk-2456KVFZ.js → chunk-IIFHIIC6.js} +1 -1
- package/static/{chunk-OMRQYBXV.js → chunk-KPZ7FEMO.js} +1 -1
- package/static/{chunk-LUZCOHFN.js → chunk-M57NVD4V.js} +1 -1
- package/static/chunk-NN3VQOS7.js +1 -0
- package/static/chunk-NW3CTYUW.js +1 -0
- package/static/{chunk-5GOMMRRE.js → chunk-O3ANXCPE.js} +1 -1
- package/static/{chunk-ZC5NIT55.js → chunk-QFOMEU3T.js} +1 -1
- package/static/{chunk-BIUNUYZ5.js → chunk-RKNTQYMU.js} +1 -1
- package/static/{chunk-UHD5XD3G.js → chunk-UQ4TRQCE.js} +1 -1
- package/static/{chunk-UPYYAJCJ.js → chunk-WINILGQN.js} +1 -1
- package/static/{chunk-IMB3C547.js → chunk-X7MFVDBY.js} +1 -1
- package/static/chunk-XCBLEI2E.js +1 -0
- package/static/{chunk-4LSJLWYV.js → chunk-XLWCV4HI.js} +1 -1
- package/static/chunk-XPIYOZBX.js +4 -0
- package/static/{chunk-KP6LSQTK.js → chunk-YD74UCFG.js} +1 -1
- package/static/{chunk-HR7KS5BR.js → chunk-YDFVKH2D.js} +1 -1
- package/static/{chunk-GDKKLLEU.js → chunk-YVJDYSDE.js} +1 -1
- package/static/index.html +2 -2
- package/static/main-QNBKYA6L.js +9 -0
- package/static/{styles-FYUSO6OJ.css → styles-A5VYX3CE.css} +1 -1
- package/server/applications/users/dto/user-password.dto.js.map +0 -1
- package/static/chunk-2V5S7DWD.js +0 -1
- package/static/chunk-44YDXGNZ.js +0 -1
- package/static/chunk-6PVKNZ7Q.js +0 -1
- package/static/chunk-EE2TDTY4.js +0 -1
- package/static/chunk-ESNDJ5T6.js +0 -1
- package/static/chunk-GSR2MCQG.js +0 -4
- package/static/chunk-HW2H3ISM.js +0 -559
- package/static/chunk-P7PX67IR.js +0 -4
- package/static/chunk-PPO7DBVO.js +0 -1
- package/static/chunk-SLGGINMR.js +0 -1
- package/static/chunk-VHYPQ3D4.js +0 -1
- package/static/chunk-YQSDS6BO.js +0 -1
- package/static/main-FYD34UEC.js +0 -7
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>
|
|
3
|
+
* This file is part of Sync-in | The open source file sync and share solution
|
|
4
|
+
* See the LICENSE file for licensing details
|
|
5
|
+
*/ "use strict";
|
|
6
|
+
Object.defineProperty(exports, "__esModule", {
|
|
7
|
+
value: true
|
|
8
|
+
});
|
|
9
|
+
Object.defineProperty(exports, "AuthMethod2FA", {
|
|
10
|
+
enumerable: true,
|
|
11
|
+
get: function() {
|
|
12
|
+
return AuthMethod2FA;
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
const _common = require("@nestjs/common");
|
|
16
|
+
const _time2fa = require("time2fa");
|
|
17
|
+
const _notifications = require("../../../applications/notifications/constants/notifications");
|
|
18
|
+
const _notificationsmanagerservice = require("../../../applications/notifications/services/notifications-manager.service");
|
|
19
|
+
const _usersmanagerservice = require("../../../applications/users/services/users-manager.service");
|
|
20
|
+
const _constants = require("../../../common/constants");
|
|
21
|
+
const _functions = require("../../../common/functions");
|
|
22
|
+
const _qrcode = require("../../../common/qrcode");
|
|
23
|
+
const _configenvironment = require("../../../configuration/config.environment");
|
|
24
|
+
const _cacheservice = require("../../../infrastructure/cache/services/cache.service");
|
|
25
|
+
const _auth = require("../../constants/auth");
|
|
26
|
+
const _cryptsecret = require("../../utils/crypt-secret");
|
|
27
|
+
function _ts_decorate(decorators, target, key, desc) {
|
|
28
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
29
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
30
|
+
else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
31
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
32
|
+
}
|
|
33
|
+
function _ts_metadata(k, v) {
|
|
34
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
35
|
+
}
|
|
36
|
+
let AuthMethod2FA = class AuthMethod2FA {
|
|
37
|
+
async initTwoFactor(user) {
|
|
38
|
+
const { secret, qrDataUrl } = this.generateSecretAndQr(user.email);
|
|
39
|
+
// store encrypted secret in cache for 5 minutes
|
|
40
|
+
await this.cache.set(this.getCacheKey(user.id), this.encryptSecret(secret), 300);
|
|
41
|
+
return {
|
|
42
|
+
secret,
|
|
43
|
+
qrDataUrl
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
async enableTwoFactor(body, req) {
|
|
47
|
+
// retrieve encrypted secret from cache
|
|
48
|
+
const secret = await this.cache.get(this.getCacheKey(req.user.id));
|
|
49
|
+
if (!secret) {
|
|
50
|
+
throw new _common.HttpException('The secret has expired', _common.HttpStatus.BAD_REQUEST);
|
|
51
|
+
}
|
|
52
|
+
// load user
|
|
53
|
+
const [auth, user] = await this.verify(body, req, true, secret);
|
|
54
|
+
if (!auth.success) {
|
|
55
|
+
throw new _common.HttpException(auth.message, _common.HttpStatus.FORBIDDEN);
|
|
56
|
+
}
|
|
57
|
+
// verify user password
|
|
58
|
+
await this.verifyUserPassword(user, body.password, req.ip);
|
|
59
|
+
// generate recovery codes
|
|
60
|
+
const recoveryCodes = this.generateRecoveryCodes();
|
|
61
|
+
// store and enable TwoFA & recovery codes
|
|
62
|
+
await this.usersManager.updateSecrets(user.id, {
|
|
63
|
+
twoFaSecret: secret,
|
|
64
|
+
recoveryCodes: recoveryCodes.map((code)=>this.encryptSecret(code))
|
|
65
|
+
});
|
|
66
|
+
this.sendEmailNotification(req, _constants.ACTION.ADD);
|
|
67
|
+
return {
|
|
68
|
+
...auth,
|
|
69
|
+
recoveryCodes: recoveryCodes
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
async disableTwoFactor(body, req) {
|
|
73
|
+
// load user
|
|
74
|
+
const [auth, user] = await this.verify(body, req, true);
|
|
75
|
+
if (!auth.success) {
|
|
76
|
+
throw new _common.HttpException(auth.message, _common.HttpStatus.FORBIDDEN);
|
|
77
|
+
}
|
|
78
|
+
// verify user password
|
|
79
|
+
await this.verifyUserPassword(user, body.password, req.ip);
|
|
80
|
+
// store and disable TwoFA & recovery codes
|
|
81
|
+
await this.usersManager.updateSecrets(user.id, {
|
|
82
|
+
twoFaSecret: undefined,
|
|
83
|
+
recoveryCodes: undefined
|
|
84
|
+
});
|
|
85
|
+
this.sendEmailNotification(req, _constants.ACTION.DELETE);
|
|
86
|
+
return auth;
|
|
87
|
+
}
|
|
88
|
+
async verify(verifyDto, req, fromLogin = false, secret) {
|
|
89
|
+
const user = await this.loadUser(req.user.id, req.ip);
|
|
90
|
+
secret = secret || user.secrets.twoFaSecret;
|
|
91
|
+
const auth = verifyDto.isRecoveryCode ? await this.validateRecoveryCode(req.user.id, verifyDto.code, user.secrets.recoveryCodes) : this.validateTwoFactorCode(verifyDto.code, secret);
|
|
92
|
+
this.usersManager.updateAccesses(user, req.ip, auth.success, true).catch((e)=>this.logger.error(`${this.verify.name} - ${e}`));
|
|
93
|
+
return fromLogin ? [
|
|
94
|
+
auth,
|
|
95
|
+
user
|
|
96
|
+
] : auth;
|
|
97
|
+
}
|
|
98
|
+
async adminResetUserTwoFa(userId) {
|
|
99
|
+
const auth = {
|
|
100
|
+
success: false,
|
|
101
|
+
message: ''
|
|
102
|
+
};
|
|
103
|
+
try {
|
|
104
|
+
await this.usersManager.updateSecrets(userId, {
|
|
105
|
+
twoFaSecret: undefined,
|
|
106
|
+
recoveryCodes: undefined
|
|
107
|
+
});
|
|
108
|
+
auth.success = true;
|
|
109
|
+
} catch (e) {
|
|
110
|
+
auth.success = false;
|
|
111
|
+
auth.message = e.message;
|
|
112
|
+
this.logger.error(`${this.adminResetUserTwoFa.name} - ${e}`);
|
|
113
|
+
}
|
|
114
|
+
return auth;
|
|
115
|
+
}
|
|
116
|
+
async loadUser(userId, ip) {
|
|
117
|
+
const user = await this.usersManager.fromUserId(userId);
|
|
118
|
+
if (!user) {
|
|
119
|
+
this.logger.warn(`User *${user.login}* (${user.id}) not found`);
|
|
120
|
+
throw new _common.HttpException(`User not found`, _common.HttpStatus.NOT_FOUND);
|
|
121
|
+
}
|
|
122
|
+
this.usersManager.validateUserAccess(user, ip);
|
|
123
|
+
return user;
|
|
124
|
+
}
|
|
125
|
+
async verifyUserPassword(user, password, ip) {
|
|
126
|
+
// This function works with any authentication method, provided that
|
|
127
|
+
// the authentication service implements proper user password updates in the database.
|
|
128
|
+
if (!await this.usersManager.compareUserPassword(user.id, password)) {
|
|
129
|
+
this.usersManager.updateAccesses(user, ip, false, true).catch((e)=>this.logger.error(`${this.enableTwoFactor.name} - ${e}`));
|
|
130
|
+
throw new _common.HttpException('Incorrect code or password', _common.HttpStatus.BAD_REQUEST);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
validateTwoFactorCode(code, encryptedSecret) {
|
|
134
|
+
const auth = {
|
|
135
|
+
success: false,
|
|
136
|
+
message: ''
|
|
137
|
+
};
|
|
138
|
+
if (!encryptedSecret) {
|
|
139
|
+
auth.message = 'Incorrect code or password';
|
|
140
|
+
return auth;
|
|
141
|
+
}
|
|
142
|
+
try {
|
|
143
|
+
auth.success = _time2fa.Totp.validate({
|
|
144
|
+
passcode: code,
|
|
145
|
+
secret: this.decryptSecret(encryptedSecret),
|
|
146
|
+
drift: 1
|
|
147
|
+
});
|
|
148
|
+
if (!auth.success) auth.message = 'Incorrect code or password';
|
|
149
|
+
} catch (e) {
|
|
150
|
+
this.logger.error(`${this.validateTwoFactorCode.name} - ${e}`);
|
|
151
|
+
auth.message = e.message;
|
|
152
|
+
}
|
|
153
|
+
return auth;
|
|
154
|
+
}
|
|
155
|
+
async validateRecoveryCode(userId, code, encryptedCodes) {
|
|
156
|
+
const auth = {
|
|
157
|
+
success: false,
|
|
158
|
+
message: ''
|
|
159
|
+
};
|
|
160
|
+
if (!encryptedCodes || encryptedCodes.length === 0) {
|
|
161
|
+
auth.message = 'Invalid code';
|
|
162
|
+
} else {
|
|
163
|
+
try {
|
|
164
|
+
for (const encCode of encryptedCodes){
|
|
165
|
+
if (code === this.decryptSecret(encCode)) {
|
|
166
|
+
auth.success = true;
|
|
167
|
+
// removed used code
|
|
168
|
+
encryptedCodes.splice(encryptedCodes.indexOf(encCode), 1);
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
if (auth.success) {
|
|
173
|
+
// update recovery codes
|
|
174
|
+
await this.usersManager.updateSecrets(userId, {
|
|
175
|
+
recoveryCodes: encryptedCodes
|
|
176
|
+
});
|
|
177
|
+
} else {
|
|
178
|
+
auth.message = 'Invalid code';
|
|
179
|
+
}
|
|
180
|
+
} catch (e) {
|
|
181
|
+
this.logger.error(`${this.validateRecoveryCode.name} - ${e}`);
|
|
182
|
+
auth.message = e.message;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return auth;
|
|
186
|
+
}
|
|
187
|
+
generateSecretAndQr(userEmail) {
|
|
188
|
+
// Generate secret + otpauth URL + QR (DataURL)
|
|
189
|
+
// Totp.generateKey returns { issuer, user, config, secret, url }
|
|
190
|
+
const key = _time2fa.Totp.generateKey({
|
|
191
|
+
issuer: _configenvironment.configuration.auth.mfa.totp.issuer,
|
|
192
|
+
user: userEmail
|
|
193
|
+
}, {
|
|
194
|
+
digits: _auth.TWO_FA_CODE_LENGTH
|
|
195
|
+
});
|
|
196
|
+
const qrDataUrl = (0, _qrcode.qrcodeToDataURL)(key.url);
|
|
197
|
+
return {
|
|
198
|
+
secret: key.secret,
|
|
199
|
+
qrDataUrl: qrDataUrl
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
getCacheKey(userId) {
|
|
203
|
+
return `${this.cacheKeyPrefix}${userId}`;
|
|
204
|
+
}
|
|
205
|
+
encryptSecret(secret) {
|
|
206
|
+
if (_configenvironment.configuration.auth.encryptionKey) {
|
|
207
|
+
return (0, _cryptsecret.encryptSecret)(secret, _configenvironment.configuration.auth.encryptionKey);
|
|
208
|
+
}
|
|
209
|
+
return secret;
|
|
210
|
+
}
|
|
211
|
+
decryptSecret(secret) {
|
|
212
|
+
if (_configenvironment.configuration.auth.encryptionKey) {
|
|
213
|
+
return (0, _cryptsecret.decryptSecret)(secret, _configenvironment.configuration.auth.encryptionKey);
|
|
214
|
+
}
|
|
215
|
+
return secret;
|
|
216
|
+
}
|
|
217
|
+
generateRecoveryCodes(count = 5) {
|
|
218
|
+
return Array.from({
|
|
219
|
+
length: count
|
|
220
|
+
}, ()=>(0, _functions.generateShortUUID)());
|
|
221
|
+
}
|
|
222
|
+
sendEmailNotification(req, action) {
|
|
223
|
+
const notification = {
|
|
224
|
+
app: _notifications.NOTIFICATION_APP.AUTH_2FA,
|
|
225
|
+
event: _notifications.NOTIFICATION_APP_EVENT.AUTH_2FA[action],
|
|
226
|
+
element: req.headers['user-agent'],
|
|
227
|
+
url: req.ip
|
|
228
|
+
};
|
|
229
|
+
this.notificationsManager.sendEmailNotification([
|
|
230
|
+
req.user
|
|
231
|
+
], notification).catch((e)=>this.logger.error(`${this.sendEmailNotification.name} - ${e}`));
|
|
232
|
+
}
|
|
233
|
+
constructor(cache, usersManager, notificationsManager){
|
|
234
|
+
this.cache = cache;
|
|
235
|
+
this.usersManager = usersManager;
|
|
236
|
+
this.notificationsManager = notificationsManager;
|
|
237
|
+
this.logger = new _common.Logger(AuthMethod2FA.name);
|
|
238
|
+
this.cacheKeyPrefix = 'auth-2fa-pending-user-';
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
AuthMethod2FA = _ts_decorate([
|
|
242
|
+
(0, _common.Injectable)(),
|
|
243
|
+
_ts_metadata("design:type", Function),
|
|
244
|
+
_ts_metadata("design:paramtypes", [
|
|
245
|
+
typeof _cacheservice.Cache === "undefined" ? Object : _cacheservice.Cache,
|
|
246
|
+
typeof _usersmanagerservice.UsersManager === "undefined" ? Object : _usersmanagerservice.UsersManager,
|
|
247
|
+
typeof _notificationsmanagerservice.NotificationsManager === "undefined" ? Object : _notificationsmanagerservice.NotificationsManager
|
|
248
|
+
])
|
|
249
|
+
], AuthMethod2FA);
|
|
250
|
+
|
|
251
|
+
//# sourceMappingURL=auth-method-two-fa.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../backend/src/authentication/services/auth-methods/auth-method-two-fa.service.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\nimport { HttpException, HttpStatus, Injectable, Logger } from '@nestjs/common'\nimport { Totp } from 'time2fa'\nimport { NOTIFICATION_APP, NOTIFICATION_APP_EVENT } from '../../../applications/notifications/constants/notifications'\nimport { NotificationContent } from '../../../applications/notifications/interfaces/notification-properties.interface'\nimport { NotificationsManager } from '../../../applications/notifications/services/notifications-manager.service'\nimport { UserModel } from '../../../applications/users/models/user.model'\nimport { UsersManager } from '../../../applications/users/services/users-manager.service'\nimport { ACTION } from '../../../common/constants'\nimport { generateShortUUID } from '../../../common/functions'\nimport { qrcodeToDataURL } from '../../../common/qrcode'\nimport { configuration } from '../../../configuration/config.environment'\nimport { Cache } from '../../../infrastructure/cache/services/cache.service'\nimport { TWO_FA_CODE_LENGTH } from '../../constants/auth'\nimport { TwoFaVerifyDto, TwoFaVerifyWithPasswordDto } from '../../dto/two-fa-verify.dto'\nimport { FastifyAuthenticatedRequest } from '../../interfaces/auth-request.interface'\nimport { TwoFaEnableResult, TwoFaSetup, TwoFaVerifyResult } from '../../interfaces/two-fa-setup.interface'\nimport { decryptSecret, encryptSecret } from '../../utils/crypt-secret'\n\n@Injectable()\nexport class AuthMethod2FA {\n private readonly logger = new Logger(AuthMethod2FA.name)\n private readonly cacheKeyPrefix = 'auth-2fa-pending-user-'\n\n constructor(\n private readonly cache: Cache,\n private readonly usersManager: UsersManager,\n private readonly notificationsManager: NotificationsManager\n ) {}\n\n async initTwoFactor(user: UserModel): Promise<TwoFaSetup> {\n const { secret, qrDataUrl } = this.generateSecretAndQr(user.email)\n // store encrypted secret in cache for 5 minutes\n await this.cache.set(this.getCacheKey(user.id), this.encryptSecret(secret), 300)\n return { secret, qrDataUrl }\n }\n\n async enableTwoFactor(body: TwoFaVerifyWithPasswordDto, req: FastifyAuthenticatedRequest): Promise<TwoFaEnableResult> {\n // retrieve encrypted secret from cache\n const secret: string = await this.cache.get(this.getCacheKey(req.user.id))\n if (!secret) {\n throw new HttpException('The secret has expired', HttpStatus.BAD_REQUEST)\n }\n // load user\n const [auth, user] = await this.verify(body, req, true, secret)\n if (!auth.success) {\n throw new HttpException(auth.message, HttpStatus.FORBIDDEN)\n }\n // verify user password\n await this.verifyUserPassword(user, body.password, req.ip)\n // generate recovery codes\n const recoveryCodes = this.generateRecoveryCodes()\n // store and enable TwoFA & recovery codes\n await this.usersManager.updateSecrets(user.id, {\n twoFaSecret: secret,\n recoveryCodes: recoveryCodes.map((code) => this.encryptSecret(code))\n })\n this.sendEmailNotification(req, ACTION.ADD)\n return { ...auth, recoveryCodes: recoveryCodes }\n }\n\n async disableTwoFactor(body: TwoFaVerifyWithPasswordDto, req: FastifyAuthenticatedRequest): Promise<TwoFaVerifyResult> {\n // load user\n const [auth, user] = await this.verify(body, req, true)\n if (!auth.success) {\n throw new HttpException(auth.message, HttpStatus.FORBIDDEN)\n }\n // verify user password\n await this.verifyUserPassword(user, body.password, req.ip)\n // store and disable TwoFA & recovery codes\n await this.usersManager.updateSecrets(user.id, { twoFaSecret: undefined, recoveryCodes: undefined })\n this.sendEmailNotification(req, ACTION.DELETE)\n return auth\n }\n\n async verify(verifyDto: TwoFaVerifyDto, req: FastifyAuthenticatedRequest, fromLogin?: false, secret?: string): Promise<TwoFaVerifyResult>\n async verify(verifyDto: TwoFaVerifyDto, req: FastifyAuthenticatedRequest, fromLogin: true, secret?: string): Promise<[TwoFaVerifyResult, UserModel]>\n async verify(\n verifyDto: TwoFaVerifyDto,\n req: FastifyAuthenticatedRequest,\n fromLogin = false,\n secret?: string\n ): Promise<TwoFaVerifyResult | [TwoFaVerifyResult, UserModel]> {\n const user = await this.loadUser(req.user.id, req.ip)\n secret = secret || user.secrets.twoFaSecret\n const auth = verifyDto.isRecoveryCode\n ? await this.validateRecoveryCode(req.user.id, verifyDto.code, user.secrets.recoveryCodes)\n : this.validateTwoFactorCode(verifyDto.code, secret)\n this.usersManager.updateAccesses(user, req.ip, auth.success, true).catch((e: Error) => this.logger.error(`${this.verify.name} - ${e}`))\n return fromLogin ? [auth, user] : auth\n }\n\n async adminResetUserTwoFa(userId: number) {\n const auth: TwoFaVerifyResult = { success: false, message: '' }\n try {\n await this.usersManager.updateSecrets(userId, { twoFaSecret: undefined, recoveryCodes: undefined })\n auth.success = true\n } catch (e) {\n auth.success = false\n auth.message = e.message\n this.logger.error(`${this.adminResetUserTwoFa.name} - ${e}`)\n }\n return auth\n }\n\n async loadUser(userId: number, ip: string) {\n const user: UserModel = await this.usersManager.fromUserId(userId)\n if (!user) {\n this.logger.warn(`User *${user.login}* (${user.id}) not found`)\n throw new HttpException(`User not found`, HttpStatus.NOT_FOUND)\n }\n this.usersManager.validateUserAccess(user, ip)\n return user\n }\n\n async verifyUserPassword(user: UserModel, password: string, ip: string) {\n // This function works with any authentication method, provided that\n // the authentication service implements proper user password updates in the database.\n if (!(await this.usersManager.compareUserPassword(user.id, password))) {\n this.usersManager.updateAccesses(user, ip, false, true).catch((e: Error) => this.logger.error(`${this.enableTwoFactor.name} - ${e}`))\n throw new HttpException('Incorrect code or password', HttpStatus.BAD_REQUEST)\n }\n }\n\n validateTwoFactorCode(code: string, encryptedSecret: string): TwoFaVerifyResult {\n const auth: TwoFaVerifyResult = { success: false, message: '' }\n if (!encryptedSecret) {\n auth.message = 'Incorrect code or password'\n return auth\n }\n try {\n auth.success = Totp.validate({ passcode: code, secret: this.decryptSecret(encryptedSecret), drift: 1 })\n if (!auth.success) auth.message = 'Incorrect code or password'\n } catch (e) {\n this.logger.error(`${this.validateTwoFactorCode.name} - ${e}`)\n auth.message = e.message\n }\n return auth\n }\n\n private async validateRecoveryCode(userId: number, code: string, encryptedCodes: string[]): Promise<TwoFaVerifyResult> {\n const auth: TwoFaVerifyResult = { success: false, message: '' }\n if (!encryptedCodes || encryptedCodes.length === 0) {\n auth.message = 'Invalid code'\n } else {\n try {\n for (const encCode of encryptedCodes) {\n if (code === this.decryptSecret(encCode)) {\n auth.success = true\n // removed used code\n encryptedCodes.splice(encryptedCodes.indexOf(encCode), 1)\n break\n }\n }\n if (auth.success) {\n // update recovery codes\n await this.usersManager.updateSecrets(userId, { recoveryCodes: encryptedCodes })\n } else {\n auth.message = 'Invalid code'\n }\n } catch (e) {\n this.logger.error(`${this.validateRecoveryCode.name} - ${e}`)\n auth.message = e.message\n }\n }\n return auth\n }\n\n private generateSecretAndQr(userEmail: string): TwoFaSetup {\n // Generate secret + otpauth URL + QR (DataURL)\n // Totp.generateKey returns { issuer, user, config, secret, url }\n const key = Totp.generateKey({ issuer: configuration.auth.mfa.totp.issuer, user: userEmail }, { digits: TWO_FA_CODE_LENGTH })\n const qrDataUrl = qrcodeToDataURL(key.url)\n return { secret: key.secret, qrDataUrl: qrDataUrl }\n }\n\n private getCacheKey(userId: number): string {\n return `${this.cacheKeyPrefix}${userId}`\n }\n\n private encryptSecret(secret: string): string {\n if (configuration.auth.encryptionKey) {\n return encryptSecret(secret, configuration.auth.encryptionKey)\n }\n return secret\n }\n\n private decryptSecret(secret: string): string {\n if (configuration.auth.encryptionKey) {\n return decryptSecret(secret, configuration.auth.encryptionKey)\n }\n return secret\n }\n\n private generateRecoveryCodes(count = 5): string[] {\n return Array.from({ length: count }, () => generateShortUUID())\n }\n\n private sendEmailNotification(req: FastifyAuthenticatedRequest, action: ACTION) {\n const notification: NotificationContent = {\n app: NOTIFICATION_APP.AUTH_2FA,\n event: NOTIFICATION_APP_EVENT.AUTH_2FA[action],\n element: req.headers['user-agent'],\n url: req.ip\n }\n this.notificationsManager\n .sendEmailNotification([req.user], notification)\n .catch((e: Error) => this.logger.error(`${this.sendEmailNotification.name} - ${e}`))\n }\n}\n"],"names":["AuthMethod2FA","initTwoFactor","user","secret","qrDataUrl","generateSecretAndQr","email","cache","set","getCacheKey","id","encryptSecret","enableTwoFactor","body","req","get","HttpException","HttpStatus","BAD_REQUEST","auth","verify","success","message","FORBIDDEN","verifyUserPassword","password","ip","recoveryCodes","generateRecoveryCodes","usersManager","updateSecrets","twoFaSecret","map","code","sendEmailNotification","ACTION","ADD","disableTwoFactor","undefined","DELETE","verifyDto","fromLogin","loadUser","secrets","isRecoveryCode","validateRecoveryCode","validateTwoFactorCode","updateAccesses","catch","e","logger","error","name","adminResetUserTwoFa","userId","fromUserId","warn","login","NOT_FOUND","validateUserAccess","compareUserPassword","encryptedSecret","Totp","validate","passcode","decryptSecret","drift","encryptedCodes","length","encCode","splice","indexOf","userEmail","key","generateKey","issuer","configuration","mfa","totp","digits","TWO_FA_CODE_LENGTH","qrcodeToDataURL","url","cacheKeyPrefix","encryptionKey","count","Array","from","generateShortUUID","action","notification","app","NOTIFICATION_APP","AUTH_2FA","event","NOTIFICATION_APP_EVENT","element","headers","notificationsManager","Logger"],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>
|
|
3
|
+
* This file is part of Sync-in | The open source file sync and share solution
|
|
4
|
+
* See the LICENSE file for licensing details
|
|
5
|
+
*/ "use strict";
|
|
6
|
+
Object.defineProperty(exports, "__esModule", {
|
|
7
|
+
value: true
|
|
8
|
+
});
|
|
9
|
+
const _testing = require("@nestjs/testing");
|
|
10
|
+
const _notificationsmanagerservice = require("../../../applications/notifications/services/notifications-manager.service");
|
|
11
|
+
const _usersmanagerservice = require("../../../applications/users/services/users-manager.service");
|
|
12
|
+
const _cacheservice = require("../../../infrastructure/cache/services/cache.service");
|
|
13
|
+
const _authmethodtwofaservice = require("./auth-method-two-fa.service");
|
|
14
|
+
describe(_authmethodtwofaservice.AuthMethod2FA.name, ()=>{
|
|
15
|
+
let service;
|
|
16
|
+
beforeAll(async ()=>{
|
|
17
|
+
const module = await _testing.Test.createTestingModule({
|
|
18
|
+
providers: [
|
|
19
|
+
_authmethodtwofaservice.AuthMethod2FA,
|
|
20
|
+
{
|
|
21
|
+
provide: _cacheservice.Cache,
|
|
22
|
+
useValue: {}
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
provide: _usersmanagerservice.UsersManager,
|
|
26
|
+
useValue: {}
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
provide: _notificationsmanagerservice.NotificationsManager,
|
|
30
|
+
useValue: {}
|
|
31
|
+
}
|
|
32
|
+
]
|
|
33
|
+
}).compile();
|
|
34
|
+
service = module.get(_authmethodtwofaservice.AuthMethod2FA);
|
|
35
|
+
});
|
|
36
|
+
it('should be defined', ()=>{
|
|
37
|
+
expect(service).toBeDefined();
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
//# sourceMappingURL=auth-method-two-fa.service.spec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../backend/src/authentication/services/auth-methods/auth-method-two-fa.service.spec.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\nimport { Test, TestingModule } from '@nestjs/testing'\nimport { NotificationsManager } from '../../../applications/notifications/services/notifications-manager.service'\nimport { UsersManager } from '../../../applications/users/services/users-manager.service'\nimport { Cache } from '../../../infrastructure/cache/services/cache.service'\nimport { AuthMethod2FA } from './auth-method-two-fa.service'\n\ndescribe(AuthMethod2FA.name, () => {\n let service: AuthMethod2FA\n\n beforeAll(async () => {\n const module: TestingModule = await Test.createTestingModule({\n providers: [\n AuthMethod2FA,\n { provide: Cache, useValue: {} },\n { provide: UsersManager, useValue: {} },\n { provide: NotificationsManager, useValue: {} }\n ]\n }).compile()\n\n service = module.get<AuthMethod2FA>(AuthMethod2FA)\n })\n\n it('should be defined', () => {\n expect(service).toBeDefined()\n })\n})\n"],"names":["describe","AuthMethod2FA","name","service","beforeAll","module","Test","createTestingModule","providers","provide","Cache","useValue","UsersManager","NotificationsManager","compile","get","it","expect","toBeDefined"],"mappings":"AAAA;;;;CAIC;;;;yBAEmC;6CACC;qCACR;8BACP;wCACQ;AAE9BA,SAASC,qCAAa,CAACC,IAAI,EAAE;IAC3B,IAAIC;IAEJC,UAAU;QACR,MAAMC,SAAwB,MAAMC,aAAI,CAACC,mBAAmB,CAAC;YAC3DC,WAAW;gBACTP,qCAAa;gBACb;oBAAEQ,SAASC,mBAAK;oBAAEC,UAAU,CAAC;gBAAE;gBAC/B;oBAAEF,SAASG,iCAAY;oBAAED,UAAU,CAAC;gBAAE;gBACtC;oBAAEF,SAASI,iDAAoB;oBAAEF,UAAU,CAAC;gBAAE;aAC/C;QACH,GAAGG,OAAO;QAEVX,UAAUE,OAAOU,GAAG,CAAgBd,qCAAa;IACnD;IAEAe,GAAG,qBAAqB;QACtBC,OAAOd,SAASe,WAAW;IAC7B;AACF"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>
|
|
3
|
+
* This file is part of Sync-in | The open source file sync and share solution
|
|
4
|
+
* See the LICENSE file for licensing details
|
|
5
|
+
*/ "use strict";
|
|
6
|
+
Object.defineProperty(exports, "__esModule", {
|
|
7
|
+
value: true
|
|
8
|
+
});
|
|
9
|
+
function _export(target, all) {
|
|
10
|
+
for(var name in all)Object.defineProperty(target, name, {
|
|
11
|
+
enumerable: true,
|
|
12
|
+
get: Object.getOwnPropertyDescriptor(all, name).get
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
_export(exports, {
|
|
16
|
+
get decryptSecret () {
|
|
17
|
+
return decryptSecret;
|
|
18
|
+
},
|
|
19
|
+
get encryptSecret () {
|
|
20
|
+
return encryptSecret;
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
const _crypto = /*#__PURE__*/ _interop_require_default(require("crypto"));
|
|
24
|
+
function _interop_require_default(obj) {
|
|
25
|
+
return obj && obj.__esModule ? obj : {
|
|
26
|
+
default: obj
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function encryptSecret(plaintext, passphrase) {
|
|
30
|
+
const salt = _crypto.default.randomBytes(16) // for key derivation (scrypt)
|
|
31
|
+
;
|
|
32
|
+
const iv = _crypto.default.randomBytes(12) // recommended IV length for GCM
|
|
33
|
+
;
|
|
34
|
+
const key = _crypto.default.scryptSync(passphrase, salt, 32);
|
|
35
|
+
const cipher = _crypto.default.createCipheriv('aes-256-gcm', key, iv);
|
|
36
|
+
const ciphertext = Buffer.concat([
|
|
37
|
+
cipher.update(plaintext, 'utf8'),
|
|
38
|
+
cipher.final()
|
|
39
|
+
]);
|
|
40
|
+
const tag = cipher.getAuthTag();
|
|
41
|
+
// Encode everything in base64 and concatenate: salt.iv.tag.cipher
|
|
42
|
+
return [
|
|
43
|
+
salt.toString('base64'),
|
|
44
|
+
iv.toString('base64'),
|
|
45
|
+
tag.toString('base64'),
|
|
46
|
+
ciphertext.toString('base64')
|
|
47
|
+
].join('.');
|
|
48
|
+
}
|
|
49
|
+
function decryptSecret(payload, passphrase) {
|
|
50
|
+
const [saltB64, ivB64, tagB64, ctB64] = payload.split('.');
|
|
51
|
+
if (!saltB64 || !ivB64 || !tagB64 || !ctB64) {
|
|
52
|
+
throw new Error('Invalid payload format');
|
|
53
|
+
}
|
|
54
|
+
const salt = Buffer.from(saltB64, 'base64');
|
|
55
|
+
const iv = Buffer.from(ivB64, 'base64');
|
|
56
|
+
const tag = Buffer.from(tagB64, 'base64');
|
|
57
|
+
const ct = Buffer.from(ctB64, 'base64');
|
|
58
|
+
const key = _crypto.default.scryptSync(passphrase, salt, 32);
|
|
59
|
+
const decipher = _crypto.default.createDecipheriv('aes-256-gcm', key, iv);
|
|
60
|
+
decipher.setAuthTag(tag);
|
|
61
|
+
const plaintext = Buffer.concat([
|
|
62
|
+
decipher.update(ct),
|
|
63
|
+
decipher.final()
|
|
64
|
+
]);
|
|
65
|
+
return plaintext.toString('utf8');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
//# sourceMappingURL=crypt-secret.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../backend/src/authentication/utils/crypt-secret.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\nimport crypto from 'crypto'\n\n// Encrypt a plaintext string with a passphrase. Returns a compact string.\nexport function encryptSecret(plaintext: string, passphrase: string) {\n const salt = crypto.randomBytes(16) // for key derivation (scrypt)\n const iv = crypto.randomBytes(12) // recommended IV length for GCM\n const key = crypto.scryptSync(passphrase, salt, 32)\n\n const cipher = crypto.createCipheriv('aes-256-gcm', key, iv)\n const ciphertext = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()])\n const tag = cipher.getAuthTag()\n\n // Encode everything in base64 and concatenate: salt.iv.tag.cipher\n return [salt.toString('base64'), iv.toString('base64'), tag.toString('base64'), ciphertext.toString('base64')].join('.')\n}\n\n// Decrypt the string generated by encryptSecret with the same passphrase.\nexport function decryptSecret(payload: string, passphrase: string) {\n const [saltB64, ivB64, tagB64, ctB64] = payload.split('.')\n if (!saltB64 || !ivB64 || !tagB64 || !ctB64) {\n throw new Error('Invalid payload format')\n }\n\n const salt = Buffer.from(saltB64, 'base64')\n const iv = Buffer.from(ivB64, 'base64')\n const tag = Buffer.from(tagB64, 'base64')\n const ct = Buffer.from(ctB64, 'base64')\n\n const key = crypto.scryptSync(passphrase, salt, 32)\n const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv)\n decipher.setAuthTag(tag)\n\n const plaintext = Buffer.concat([decipher.update(ct), decipher.final()])\n return plaintext.toString('utf8')\n}\n"],"names":["decryptSecret","encryptSecret","plaintext","passphrase","salt","crypto","randomBytes","iv","key","scryptSync","cipher","createCipheriv","ciphertext","Buffer","concat","update","final","tag","getAuthTag","toString","join","payload","saltB64","ivB64","tagB64","ctB64","split","Error","from","ct","decipher","createDecipheriv","setAuthTag"],"mappings":"AAAA;;;;CAIC;;;;;;;;;;;QAmBeA;eAAAA;;QAdAC;eAAAA;;;+DAHG;;;;;;AAGZ,SAASA,cAAcC,SAAiB,EAAEC,UAAkB;IACjE,MAAMC,OAAOC,eAAM,CAACC,WAAW,CAAC,IAAI,8BAA8B;;IAClE,MAAMC,KAAKF,eAAM,CAACC,WAAW,CAAC,IAAI,gCAAgC;;IAClE,MAAME,MAAMH,eAAM,CAACI,UAAU,CAACN,YAAYC,MAAM;IAEhD,MAAMM,SAASL,eAAM,CAACM,cAAc,CAAC,eAAeH,KAAKD;IACzD,MAAMK,aAAaC,OAAOC,MAAM,CAAC;QAACJ,OAAOK,MAAM,CAACb,WAAW;QAASQ,OAAOM,KAAK;KAAG;IACnF,MAAMC,MAAMP,OAAOQ,UAAU;IAE7B,kEAAkE;IAClE,OAAO;QAACd,KAAKe,QAAQ,CAAC;QAAWZ,GAAGY,QAAQ,CAAC;QAAWF,IAAIE,QAAQ,CAAC;QAAWP,WAAWO,QAAQ,CAAC;KAAU,CAACC,IAAI,CAAC;AACtH;AAGO,SAASpB,cAAcqB,OAAe,EAAElB,UAAkB;IAC/D,MAAM,CAACmB,SAASC,OAAOC,QAAQC,MAAM,GAAGJ,QAAQK,KAAK,CAAC;IACtD,IAAI,CAACJ,WAAW,CAACC,SAAS,CAACC,UAAU,CAACC,OAAO;QAC3C,MAAM,IAAIE,MAAM;IAClB;IAEA,MAAMvB,OAAOS,OAAOe,IAAI,CAACN,SAAS;IAClC,MAAMf,KAAKM,OAAOe,IAAI,CAACL,OAAO;IAC9B,MAAMN,MAAMJ,OAAOe,IAAI,CAACJ,QAAQ;IAChC,MAAMK,KAAKhB,OAAOe,IAAI,CAACH,OAAO;IAE9B,MAAMjB,MAAMH,eAAM,CAACI,UAAU,CAACN,YAAYC,MAAM;IAChD,MAAM0B,WAAWzB,eAAM,CAAC0B,gBAAgB,CAAC,eAAevB,KAAKD;IAC7DuB,SAASE,UAAU,CAACf;IAEpB,MAAMf,YAAYW,OAAOC,MAAM,CAAC;QAACgB,SAASf,MAAM,CAACc;QAAKC,SAASd,KAAK;KAAG;IACvE,OAAOd,UAAUiB,QAAQ,CAAC;AAC5B"}
|
|
@@ -188,19 +188,34 @@ async function hashPassword(password) {
|
|
|
188
188
|
async function comparePassword(password, hash) {
|
|
189
189
|
return await _bcryptjs.default.compare(password, hash);
|
|
190
190
|
}
|
|
191
|
-
function generateShortUUID(length =
|
|
192
|
-
|
|
191
|
+
function generateShortUUID(length = 32, encoding = 'base64url') {
|
|
192
|
+
const bytes = Math.ceil(length * 3 / 4) // adapt to real length
|
|
193
|
+
;
|
|
194
|
+
return _nodecrypto.default.randomBytes(bytes).toString(encoding);
|
|
193
195
|
}
|
|
194
196
|
function anonymizePassword(obj) {
|
|
195
197
|
return {
|
|
196
198
|
...obj,
|
|
197
199
|
...obj?.password && {
|
|
198
200
|
password: '********'
|
|
201
|
+
},
|
|
202
|
+
...obj?.secrets && {
|
|
203
|
+
secrets: '********'
|
|
199
204
|
}
|
|
200
205
|
};
|
|
201
206
|
}
|
|
202
207
|
function splitFullName(fullName) {
|
|
208
|
+
if (!fullName || !fullName.trim()) return {
|
|
209
|
+
firstName: '',
|
|
210
|
+
lastName: ''
|
|
211
|
+
};
|
|
203
212
|
const parts = fullName.trim().split(/\s+/);
|
|
213
|
+
if (parts.length === 1) {
|
|
214
|
+
return {
|
|
215
|
+
firstName: '',
|
|
216
|
+
lastName: parts[0]
|
|
217
|
+
};
|
|
218
|
+
}
|
|
204
219
|
const lastName = parts.pop();
|
|
205
220
|
const firstName = parts.join(' ');
|
|
206
221
|
return {
|
|
@@ -209,6 +224,7 @@ function splitFullName(fullName) {
|
|
|
209
224
|
};
|
|
210
225
|
}
|
|
211
226
|
function transformAndValidate(schema, object, transformOptions = {}, validatorOptions = {}) {
|
|
227
|
+
// warning: plainToInstance do not use constructor to instantiate class
|
|
212
228
|
const instance = (0, _classtransformer.plainToInstance)(schema, object, transformOptions);
|
|
213
229
|
const errors = (0, _classvalidator.validateSync)(instance, validatorOptions);
|
|
214
230
|
if (errors.length > 0) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../backend/src/common/functions.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\nimport { parse as parseMs } from '@lukeed/ms'\nimport bcrypt from 'bcryptjs'\nimport { ClassTransformOptions, plainToInstance } from 'class-transformer'\nimport { validateSync } from 'class-validator'\nimport { ValidationError } from 'class-validator/types/validation/ValidationError'\nimport { ValidatorOptions } from 'class-validator/types/validation/ValidatorOptions'\nimport crypto from 'node:crypto'\nimport { setTimeout } from 'node:timers/promises'\nimport { SPACE_PERMS_SEP } from '../applications/spaces/constants/spaces'\n\nexport const regexpEscape = /[.*+?^${}()|[\\]\\\\]/g\nexport const regexSpecialChars = /[-[\\]{}()*+!<=:?./\\\\^$|#,]/g\nexport const regexSpecialCharsWithSpace = /[-[\\]{}()*+!<=:?./\\\\^$|#\\s,]/g\n\nexport async function loadOptionalModule(moduleName: string): Promise<any> {\n return await import(moduleName)\n}\n\nexport async function sleep(ms: number): Promise<void> {\n await setTimeout(ms)\n}\n\nexport function escapeSQLRegexp(input: string): string {\n return input.replace(regexSpecialCharsWithSpace, '\\\\\\\\$&').replaceAll(\"'\", \"''\")\n}\n\nexport function escapeString(input: string): string {\n return input.replace(regexSpecialChars, '\\\\$&')\n}\n\nexport function escapePath(path: string): string {\n return path.replace(regexpEscape, '\\\\$&')\n}\n\nexport function regExpPathPattern(path: string): RegExp {\n return new RegExp(`^${escapePath(path)}[/\\\\\\\\]`)\n}\n\nexport function convertHumanTimeToSeconds(value: string): number {\n return parseMs(value) / 1000\n}\n\nexport function convertHumanTimeToMs(value: string): number {\n return parseMs(value)\n}\n\nexport function formatDateISOString(date: Date): string {\n return date.toISOString().replaceAll('-', '.').replaceAll(':', '-').replace('T', ' ').replace('Z', '')\n}\n\nexport function urlToPath(url: string): string {\n // transform https://sync-in.com/webdav/ to /webdav/\n try {\n // transform https://sync-in.com/webdav/ to /webdav/\n return new URL(url).pathname\n } catch {\n // or allows uri like : /webdav/\n return url\n }\n}\n\nexport async function hashPassword(password: string): Promise<string> {\n return await bcrypt.hash(password, 10)\n}\n\nexport async function comparePassword(password: string, hash: string): Promise<boolean> {\n return await bcrypt.compare(password, hash)\n}\n\nexport function generateShortUUID(length: number = 16): string {\n return crypto.randomBytes(length).toString('base64url')\n}\n\nexport function anonymizePassword(obj: { password?: string }) {\n return { ...obj, ...(obj?.password && { password: '********' }) }\n}\n\nexport function splitFullName(fullName: string): { firstName: string; lastName: string } {\n const parts = fullName.trim().split(/\\s+/)\n const lastName = parts.pop()\n const firstName = parts.join(' ')\n return { firstName, lastName }\n}\n\nexport function transformAndValidate<T extends object>(\n schema: new () => T,\n object: any,\n transformOptions: ClassTransformOptions = {},\n validatorOptions: ValidatorOptions = {}\n): T {\n const instance: T = plainToInstance(schema, object, transformOptions)\n const errors: ValidationError[] = validateSync(instance, validatorOptions)\n if (errors.length > 0) {\n throw new Error(errors.toString())\n }\n return instance\n}\n\nexport function uniquePermissions(permissions: string, permissionsSeparator: string = SPACE_PERMS_SEP) {\n /*\n Returns unique permissions : 'c:r:w:c:r' -> 'c:r:w'\n */\n if (permissions.length === 0) return permissions\n return [\n ...new Set(\n permissions\n .split(permissionsSeparator)\n .filter((p: string) => p && p !== 'null')\n .sort()\n )\n ].join(permissionsSeparator)\n}\n\nexport function intersectPermissions(aPermissions: string, bPermissions: string, permissionsSeparator: string = SPACE_PERMS_SEP): string {\n const aPerms = aPermissions.split(permissionsSeparator)\n const bPerms = bPermissions.split(permissionsSeparator)\n return aPerms\n .filter((p: string) => p !== '' && p !== 'null' && bPerms.indexOf(p) > -1)\n .sort()\n .join(permissionsSeparator)\n}\n\nexport function differencePermissions(aPermissions: string, bPermissions: string, permissionsSeparator: string = SPACE_PERMS_SEP): string[] {\n const aPerms = aPermissions.split(permissionsSeparator)\n const bPerms = bPermissions.split(permissionsSeparator)\n return aPerms.filter((p: string) => p !== '' && bPerms.indexOf(p) === -1).sort()\n}\n\nexport function sortObjByName(a: { name: string }, b: { name: string }, asc = false): 0 | 1 | -1 {\n const aN = a.name.toLowerCase()\n const bN = b.name.toLowerCase()\n if (asc) {\n return aN < bN ? 1 : aN > bN ? -1 : 0\n } else {\n return aN < bN ? -1 : aN > bN ? 1 : 0\n }\n}\n\nfunction diffProperties(a: any, b: any, props: string[]): boolean {\n for (const p of props) {\n if (a[p] !== b[p]) {\n return false\n }\n }\n return true\n}\n\nexport function diffCollection<T>(\n curCollection: T[],\n newCollection: T[],\n updateProps: string[],\n compareProps: string[] = ['id']\n): [T[], Record<string | 'object', { old: any; new: any } | T>[], T[]] {\n const toAdd: T[] = []\n const toUpdate: Record<string | 'object', { old: any; new: any } | T>[] = []\n const toRemove: T[] = curCollection.filter((c: T) => !newCollection.find((n: T) => diffProperties(c, n, compareProps)))\n for (const n of newCollection) {\n const o = curCollection.find((c: T) => diffProperties(c, n, compareProps))\n if (o) {\n const diff: Record<string | 'object', { old: any; new: any } | T> = {}\n for (const p of updateProps.filter((p: string) => n[p] !== o[p])) {\n diff[p] = { old: o[p], new: n[p] }\n }\n if (Object.keys(diff).length) {\n diff['object'] = n\n toUpdate.push(diff)\n }\n } else {\n toAdd.push(n)\n }\n }\n return [toAdd, toUpdate, toRemove]\n}\n\nexport function convertDiffUpdate(update: Record<string | 'object', { old: any; new: any } | any>[]): Record<string | 'object', any>[] {\n // only keep the new values\n return update.map((o) => Object.fromEntries(Object.entries(o).map(([p, v]) => [p, p === 'object' ? v : v.new])))\n}\n"],"names":["anonymizePassword","comparePassword","convertDiffUpdate","convertHumanTimeToMs","convertHumanTimeToSeconds","diffCollection","differencePermissions","escapePath","escapeSQLRegexp","escapeString","formatDateISOString","generateShortUUID","hashPassword","intersectPermissions","loadOptionalModule","regExpPathPattern","regexSpecialChars","regexSpecialCharsWithSpace","regexpEscape","sleep","sortObjByName","splitFullName","transformAndValidate","uniquePermissions","urlToPath","moduleName","ms","setTimeout","input","replace","replaceAll","path","RegExp","value","parseMs","date","toISOString","url","URL","pathname","password","bcrypt","hash","compare","length","crypto","randomBytes","toString","obj","fullName","parts","trim","split","lastName","pop","firstName","join","schema","object","transformOptions","validatorOptions","instance","plainToInstance","errors","validateSync","Error","permissions","permissionsSeparator","SPACE_PERMS_SEP","Set","filter","p","sort","aPermissions","bPermissions","aPerms","bPerms","indexOf","a","b","asc","aN","name","toLowerCase","bN","diffProperties","props","curCollection","newCollection","updateProps","compareProps","toAdd","toUpdate","toRemove","c","find","n","o","diff","old","new","Object","keys","push","update","map","fromEntries","entries","v"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../backend/src/common/functions.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\nimport { parse as parseMs } from '@lukeed/ms'\nimport bcrypt from 'bcryptjs'\nimport { ClassTransformOptions, plainToInstance } from 'class-transformer'\nimport { validateSync } from 'class-validator'\nimport { ValidationError } from 'class-validator/types/validation/ValidationError'\nimport { ValidatorOptions } from 'class-validator/types/validation/ValidatorOptions'\nimport crypto from 'node:crypto'\nimport { setTimeout } from 'node:timers/promises'\nimport { SPACE_PERMS_SEP } from '../applications/spaces/constants/spaces'\n\nexport const regexpEscape = /[.*+?^${}()|[\\]\\\\]/g\nexport const regexSpecialChars = /[-[\\]{}()*+!<=:?./\\\\^$|#,]/g\nexport const regexSpecialCharsWithSpace = /[-[\\]{}()*+!<=:?./\\\\^$|#\\s,]/g\n\nexport async function loadOptionalModule(moduleName: string): Promise<any> {\n return await import(moduleName)\n}\n\nexport async function sleep(ms: number): Promise<void> {\n await setTimeout(ms)\n}\n\nexport function escapeSQLRegexp(input: string): string {\n return input.replace(regexSpecialCharsWithSpace, '\\\\\\\\$&').replaceAll(\"'\", \"''\")\n}\n\nexport function escapeString(input: string): string {\n return input.replace(regexSpecialChars, '\\\\$&')\n}\n\nexport function escapePath(path: string): string {\n return path.replace(regexpEscape, '\\\\$&')\n}\n\nexport function regExpPathPattern(path: string): RegExp {\n return new RegExp(`^${escapePath(path)}[/\\\\\\\\]`)\n}\n\nexport function convertHumanTimeToSeconds(value: string): number {\n return parseMs(value) / 1000\n}\n\nexport function convertHumanTimeToMs(value: string): number {\n return parseMs(value)\n}\n\nexport function formatDateISOString(date: Date): string {\n return date.toISOString().replaceAll('-', '.').replaceAll(':', '-').replace('T', ' ').replace('Z', '')\n}\n\nexport function urlToPath(url: string): string {\n // transform https://sync-in.com/webdav/ to /webdav/\n try {\n // transform https://sync-in.com/webdav/ to /webdav/\n return new URL(url).pathname\n } catch {\n // or allows uri like : /webdav/\n return url\n }\n}\n\nexport async function hashPassword(password: string): Promise<string> {\n return await bcrypt.hash(password, 10)\n}\n\nexport async function comparePassword(password: string, hash: string): Promise<boolean> {\n return await bcrypt.compare(password, hash)\n}\n\nexport function generateShortUUID(length: number = 32, encoding: BufferEncoding = 'base64url'): string {\n const bytes = Math.ceil((length * 3) / 4) // adapt to real length\n return crypto.randomBytes(bytes).toString(encoding)\n}\n\nexport function anonymizePassword(obj: { password?: string; secrets?: string }) {\n return { ...obj, ...(obj?.password && { password: '********' }), ...(obj?.secrets && { secrets: '********' }) }\n}\n\nexport function splitFullName(fullName?: string): { firstName: string; lastName: string } {\n if (!fullName || !fullName.trim()) return { firstName: '', lastName: '' }\n const parts = fullName.trim().split(/\\s+/)\n if (parts.length === 1) {\n return { firstName: '', lastName: parts[0] }\n }\n const lastName = parts.pop()!\n const firstName = parts.join(' ')\n return { firstName, lastName }\n}\n\nexport function transformAndValidate<T extends object>(\n schema: new () => T,\n object: any,\n transformOptions: ClassTransformOptions = {},\n validatorOptions: ValidatorOptions = {}\n): T {\n // warning: plainToInstance do not use constructor to instantiate class\n const instance: T = plainToInstance(schema, object, transformOptions)\n const errors: ValidationError[] = validateSync(instance, validatorOptions)\n if (errors.length > 0) {\n throw new Error(errors.toString())\n }\n return instance\n}\n\nexport function uniquePermissions(permissions: string, permissionsSeparator: string = SPACE_PERMS_SEP) {\n /*\n Returns unique permissions : 'c:r:w:c:r' -> 'c:r:w'\n */\n if (permissions.length === 0) return permissions\n return [\n ...new Set(\n permissions\n .split(permissionsSeparator)\n .filter((p: string) => p && p !== 'null')\n .sort()\n )\n ].join(permissionsSeparator)\n}\n\nexport function intersectPermissions(aPermissions: string, bPermissions: string, permissionsSeparator: string = SPACE_PERMS_SEP): string {\n const aPerms = aPermissions.split(permissionsSeparator)\n const bPerms = bPermissions.split(permissionsSeparator)\n return aPerms\n .filter((p: string) => p !== '' && p !== 'null' && bPerms.indexOf(p) > -1)\n .sort()\n .join(permissionsSeparator)\n}\n\nexport function differencePermissions(aPermissions: string, bPermissions: string, permissionsSeparator: string = SPACE_PERMS_SEP): string[] {\n const aPerms = aPermissions.split(permissionsSeparator)\n const bPerms = bPermissions.split(permissionsSeparator)\n return aPerms.filter((p: string) => p !== '' && bPerms.indexOf(p) === -1).sort()\n}\n\nexport function sortObjByName(a: { name: string }, b: { name: string }, asc = false): 0 | 1 | -1 {\n const aN = a.name.toLowerCase()\n const bN = b.name.toLowerCase()\n if (asc) {\n return aN < bN ? 1 : aN > bN ? -1 : 0\n } else {\n return aN < bN ? -1 : aN > bN ? 1 : 0\n }\n}\n\nfunction diffProperties(a: any, b: any, props: string[]): boolean {\n for (const p of props) {\n if (a[p] !== b[p]) {\n return false\n }\n }\n return true\n}\n\nexport function diffCollection<T>(\n curCollection: T[],\n newCollection: T[],\n updateProps: string[],\n compareProps: string[] = ['id']\n): [T[], Record<string | 'object', { old: any; new: any } | T>[], T[]] {\n const toAdd: T[] = []\n const toUpdate: Record<string | 'object', { old: any; new: any } | T>[] = []\n const toRemove: T[] = curCollection.filter((c: T) => !newCollection.find((n: T) => diffProperties(c, n, compareProps)))\n for (const n of newCollection) {\n const o = curCollection.find((c: T) => diffProperties(c, n, compareProps))\n if (o) {\n const diff: Record<string | 'object', { old: any; new: any } | T> = {}\n for (const p of updateProps.filter((p: string) => n[p] !== o[p])) {\n diff[p] = { old: o[p], new: n[p] }\n }\n if (Object.keys(diff).length) {\n diff['object'] = n\n toUpdate.push(diff)\n }\n } else {\n toAdd.push(n)\n }\n }\n return [toAdd, toUpdate, toRemove]\n}\n\nexport function convertDiffUpdate(update: Record<string | 'object', { old: any; new: any } | any>[]): Record<string | 'object', any>[] {\n // only keep the new values\n return update.map((o) => Object.fromEntries(Object.entries(o).map(([p, v]) => [p, p === 'object' ? v : v.new])))\n}\n"],"names":["anonymizePassword","comparePassword","convertDiffUpdate","convertHumanTimeToMs","convertHumanTimeToSeconds","diffCollection","differencePermissions","escapePath","escapeSQLRegexp","escapeString","formatDateISOString","generateShortUUID","hashPassword","intersectPermissions","loadOptionalModule","regExpPathPattern","regexSpecialChars","regexSpecialCharsWithSpace","regexpEscape","sleep","sortObjByName","splitFullName","transformAndValidate","uniquePermissions","urlToPath","moduleName","ms","setTimeout","input","replace","replaceAll","path","RegExp","value","parseMs","date","toISOString","url","URL","pathname","password","bcrypt","hash","compare","length","encoding","bytes","Math","ceil","crypto","randomBytes","toString","obj","secrets","fullName","trim","firstName","lastName","parts","split","pop","join","schema","object","transformOptions","validatorOptions","instance","plainToInstance","errors","validateSync","Error","permissions","permissionsSeparator","SPACE_PERMS_SEP","Set","filter","p","sort","aPermissions","bPermissions","aPerms","bPerms","indexOf","a","b","asc","aN","name","toLowerCase","bN","diffProperties","props","curCollection","newCollection","updateProps","compareProps","toAdd","toUpdate","toRemove","c","find","n","o","diff","old","new","Object","keys","push","update","map","fromEntries","entries","v"],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>
|
|
3
|
+
* This file is part of Sync-in | The open source file sync and share solution
|
|
4
|
+
* See the LICENSE file for licensing details
|
|
5
|
+
*/ "use strict";
|
|
6
|
+
Object.defineProperty(exports, "__esModule", {
|
|
7
|
+
value: true
|
|
8
|
+
});
|
|
9
|
+
Object.defineProperty(exports, "qrcodeToDataURL", {
|
|
10
|
+
enumerable: true,
|
|
11
|
+
get: function() {
|
|
12
|
+
return qrcodeToDataURL;
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
const _qrcodegenerator = /*#__PURE__*/ _interop_require_default(require("qrcode-generator"));
|
|
16
|
+
function _interop_require_default(obj) {
|
|
17
|
+
return obj && obj.__esModule ? obj : {
|
|
18
|
+
default: obj
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function qrcodeToDataURL(text) {
|
|
22
|
+
const qr = (0, _qrcodegenerator.default)(0, 'M') // version auto, correction M
|
|
23
|
+
;
|
|
24
|
+
qr.addData(text);
|
|
25
|
+
qr.make();
|
|
26
|
+
const svg = qr.createSvgTag({
|
|
27
|
+
margin: 2,
|
|
28
|
+
scalable: true
|
|
29
|
+
});
|
|
30
|
+
const base64 = Buffer.from(svg).toString('base64');
|
|
31
|
+
return `data:image/svg+xml;base64,${base64}`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
//# sourceMappingURL=qrcode.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../backend/src/common/qrcode.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\nimport qrcode from 'qrcode-generator'\n\nexport function qrcodeToDataURL(text: string) {\n const qr = qrcode(0, 'M') // version auto, correction M\n qr.addData(text)\n qr.make()\n\n const svg = qr.createSvgTag({ margin: 2, scalable: true })\n\n const base64 = Buffer.from(svg).toString('base64')\n return `data:image/svg+xml;base64,${base64}`\n}\n"],"names":["qrcodeToDataURL","text","qr","qrcode","addData","make","svg","createSvgTag","margin","scalable","base64","Buffer","from","toString"],"mappings":"AAAA;;;;CAIC;;;;+BAIeA;;;eAAAA;;;wEAFG;;;;;;AAEZ,SAASA,gBAAgBC,IAAY;IAC1C,MAAMC,KAAKC,IAAAA,wBAAM,EAAC,GAAG,KAAK,6BAA6B;;IACvDD,GAAGE,OAAO,CAACH;IACXC,GAAGG,IAAI;IAEP,MAAMC,MAAMJ,GAAGK,YAAY,CAAC;QAAEC,QAAQ;QAAGC,UAAU;IAAK;IAExD,MAAMC,SAASC,OAAOC,IAAI,CAACN,KAAKO,QAAQ,CAAC;IACzC,OAAO,CAAC,0BAA0B,EAAEH,QAAQ;AAC9C"}
|
package/server/common/shared.js
CHANGED
|
@@ -17,6 +17,9 @@ _export(exports, {
|
|
|
17
17
|
get capitalizeString () {
|
|
18
18
|
return capitalizeString;
|
|
19
19
|
},
|
|
20
|
+
get createLightSlug () {
|
|
21
|
+
return createLightSlug;
|
|
22
|
+
},
|
|
20
23
|
get createSlug () {
|
|
21
24
|
return createSlug;
|
|
22
25
|
},
|
|
@@ -35,6 +38,9 @@ _export(exports, {
|
|
|
35
38
|
get forbiddenChars () {
|
|
36
39
|
return forbiddenChars;
|
|
37
40
|
},
|
|
41
|
+
get genPassword () {
|
|
42
|
+
return genPassword;
|
|
43
|
+
},
|
|
38
44
|
get isValidFileName () {
|
|
39
45
|
return isValidFileName;
|
|
40
46
|
},
|
|
@@ -74,6 +80,18 @@ function createSlug(input, replaceCount = false) {
|
|
|
74
80
|
if (replaceCount) return r.replace(regExpNumberSuffix, '');
|
|
75
81
|
return r;
|
|
76
82
|
}
|
|
83
|
+
function createLightSlug(input) {
|
|
84
|
+
return input.toLowerCase().trim().normalize('NFD').replace(/[\u0300-\u036f]/g, '');
|
|
85
|
+
}
|
|
86
|
+
function genPassword(length = 12) {
|
|
87
|
+
const chars = '0123456789abcdefghijklmnopqrstuvwxyz!@#$%^&*()ABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
|
88
|
+
let password = '';
|
|
89
|
+
for(let i = 0; i <= length; i++){
|
|
90
|
+
const randomNumber = Math.floor(Math.random() * chars.length);
|
|
91
|
+
password += chars.substring(randomNumber, randomNumber + 1);
|
|
92
|
+
}
|
|
93
|
+
return password;
|
|
94
|
+
}
|
|
77
95
|
function popFromObject(key, object) {
|
|
78
96
|
const item = object[key];
|
|
79
97
|
delete object[key];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../backend/src/common/shared.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\n// eslint-disable-next-line no-control-regex\nexport const regExpInvalidFileName = /^(?:CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])$|[<>:\"/\\\\|?*\\x00-\\x1f\\x80-\\x9f]/\nexport const regExpPreventPathTraversal = /^(\\.\\.(\\/|\\\\|$))+/\nexport const regExpNumberSuffix = /-\\d+$/\nexport const forbiddenChars = '\\\\ / : * ? \" < > |'\n\nexport function isValidFileName(fileName: string) {\n if (regExpInvalidFileName.test(fileName)) {\n throw new Error('Forbidden characters')\n }\n}\n\nexport function currentTimeStamp(date?: Date, ms = false): number {\n return Math.floor((date ? date : new Date()).getTime() / (ms ? 1 : 1000))\n}\n\nexport function currentDate(value?: string): Date {\n return new Date((value ? value : new Date().toISOString()).split('T')[0])\n}\n\nexport function createSlug(input: string, replaceCount = false): string {\n const r = input\n .toLowerCase()\n .trim()\n .replace(/[\\s_-]+/g, '-')\n .replace(/^-+|-+$/g, '')\n .normalize('NFD')\n .replace(/[\\u0300-\\u036f]/g, '')\n if (replaceCount) return r.replace(regExpNumberSuffix, '')\n return r\n}\n\nexport function popFromObject(key: string, object: any): any {\n const item = object[key]\n delete object[key]\n return item\n}\n\nexport function encodeUrl(url: string): string {\n return url\n .split('/')\n .map((e) => encodeURIComponent(e))\n .join('/')\n}\n\nexport function decodeUrl(url: string): string {\n return url\n .split('/')\n .map((e) => decodeURIComponent(e))\n .join('/')\n}\n\nexport function objectPropertyFromString(obj: any, property: string): any {\n const a = property.split('.')\n let o = obj\n for (let i = 0, n = a.length; i < n; i++) {\n const k = a[i]\n if (k in o) {\n o = o[k]\n } else {\n return null\n }\n }\n return o\n}\n\nexport function capitalizeString(value: string): string {\n return value.charAt(0).toUpperCase() + value.slice(1)\n}\n"],"names":["capitalizeString","createSlug","currentDate","currentTimeStamp","decodeUrl","encodeUrl","forbiddenChars","isValidFileName","objectPropertyFromString","popFromObject","regExpInvalidFileName","regExpNumberSuffix","regExpPreventPathTraversal","fileName","test","Error","date","ms","Math","floor","Date","getTime","value","toISOString","split","input","replaceCount","r","toLowerCase","trim","replace","normalize","key","object","item","url","map","e","encodeURIComponent","join","decodeURIComponent","obj","property","a","o","
|
|
1
|
+
{"version":3,"sources":["../../../backend/src/common/shared.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\n// eslint-disable-next-line no-control-regex\nexport const regExpInvalidFileName = /^(?:CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])$|[<>:\"/\\\\|?*\\x00-\\x1f\\x80-\\x9f]/\nexport const regExpPreventPathTraversal = /^(\\.\\.(\\/|\\\\|$))+/\nexport const regExpNumberSuffix = /-\\d+$/\nexport const forbiddenChars = '\\\\ / : * ? \" < > |'\n\nexport function isValidFileName(fileName: string) {\n if (regExpInvalidFileName.test(fileName)) {\n throw new Error('Forbidden characters')\n }\n}\n\nexport function currentTimeStamp(date?: Date, ms = false): number {\n return Math.floor((date ? date : new Date()).getTime() / (ms ? 1 : 1000))\n}\n\nexport function currentDate(value?: string): Date {\n return new Date((value ? value : new Date().toISOString()).split('T')[0])\n}\n\nexport function createSlug(input: string, replaceCount = false): string {\n const r = input\n .toLowerCase()\n .trim()\n .replace(/[\\s_-]+/g, '-')\n .replace(/^-+|-+$/g, '')\n .normalize('NFD')\n .replace(/[\\u0300-\\u036f]/g, '')\n if (replaceCount) return r.replace(regExpNumberSuffix, '')\n return r\n}\n\nexport function createLightSlug(input: string) {\n return input\n .toLowerCase()\n .trim()\n .normalize('NFD')\n .replace(/[\\u0300-\\u036f]/g, '')\n}\n\nexport function genPassword(length = 12) {\n const chars = '0123456789abcdefghijklmnopqrstuvwxyz!@#$%^&*()ABCDEFGHIJKLMNOPQRSTUVWXYZ'\n let password = ''\n for (let i = 0; i <= length; i++) {\n const randomNumber = Math.floor(Math.random() * chars.length)\n password += chars.substring(randomNumber, randomNumber + 1)\n }\n return password\n}\n\nexport function popFromObject(key: string, object: any): any {\n const item = object[key]\n delete object[key]\n return item\n}\n\nexport function encodeUrl(url: string): string {\n return url\n .split('/')\n .map((e) => encodeURIComponent(e))\n .join('/')\n}\n\nexport function decodeUrl(url: string): string {\n return url\n .split('/')\n .map((e) => decodeURIComponent(e))\n .join('/')\n}\n\nexport function objectPropertyFromString(obj: any, property: string): any {\n const a = property.split('.')\n let o = obj\n for (let i = 0, n = a.length; i < n; i++) {\n const k = a[i]\n if (k in o) {\n o = o[k]\n } else {\n return null\n }\n }\n return o\n}\n\nexport function capitalizeString(value: string): string {\n return value.charAt(0).toUpperCase() + value.slice(1)\n}\n"],"names":["capitalizeString","createLightSlug","createSlug","currentDate","currentTimeStamp","decodeUrl","encodeUrl","forbiddenChars","genPassword","isValidFileName","objectPropertyFromString","popFromObject","regExpInvalidFileName","regExpNumberSuffix","regExpPreventPathTraversal","fileName","test","Error","date","ms","Math","floor","Date","getTime","value","toISOString","split","input","replaceCount","r","toLowerCase","trim","replace","normalize","length","chars","password","i","randomNumber","random","substring","key","object","item","url","map","e","encodeURIComponent","join","decodeURIComponent","obj","property","a","o","n","k","charAt","toUpperCase","slice"],"mappings":"AAAA;;;;CAIC,GAED,4CAA4C;;;;;;;;;;;;QAoF5BA;eAAAA;;QApDAC;eAAAA;;QAZAC;eAAAA;;QAJAC;eAAAA;;QAJAC;eAAAA;;QAmDAC;eAAAA;;QAPAC;eAAAA;;QApDHC;eAAAA;;QAoCGC;eAAAA;;QAlCAC;eAAAA;;QAgEAC;eAAAA;;QApBAC;eAAAA;;QAjDHC;eAAAA;;QAEAC;eAAAA;;QADAC;eAAAA;;;AADN,MAAMF,wBAAwB;AAC9B,MAAME,6BAA6B;AACnC,MAAMD,qBAAqB;AAC3B,MAAMN,iBAAiB;AAEvB,SAASE,gBAAgBM,QAAgB;IAC9C,IAAIH,sBAAsBI,IAAI,CAACD,WAAW;QACxC,MAAM,IAAIE,MAAM;IAClB;AACF;AAEO,SAASb,iBAAiBc,IAAW,EAAEC,KAAK,KAAK;IACtD,OAAOC,KAAKC,KAAK,CAAC,AAACH,CAAAA,OAAOA,OAAO,IAAII,MAAK,EAAGC,OAAO,KAAMJ,CAAAA,KAAK,IAAI,IAAG;AACxE;AAEO,SAAShB,YAAYqB,KAAc;IACxC,OAAO,IAAIF,KAAK,AAACE,CAAAA,QAAQA,QAAQ,IAAIF,OAAOG,WAAW,EAAC,EAAGC,KAAK,CAAC,IAAI,CAAC,EAAE;AAC1E;AAEO,SAASxB,WAAWyB,KAAa,EAAEC,eAAe,KAAK;IAC5D,MAAMC,IAAIF,MACPG,WAAW,GACXC,IAAI,GACJC,OAAO,CAAC,YAAY,KACpBA,OAAO,CAAC,YAAY,IACpBC,SAAS,CAAC,OACVD,OAAO,CAAC,oBAAoB;IAC/B,IAAIJ,cAAc,OAAOC,EAAEG,OAAO,CAACnB,oBAAoB;IACvD,OAAOgB;AACT;AAEO,SAAS5B,gBAAgB0B,KAAa;IAC3C,OAAOA,MACJG,WAAW,GACXC,IAAI,GACJE,SAAS,CAAC,OACVD,OAAO,CAAC,oBAAoB;AACjC;AAEO,SAASxB,YAAY0B,SAAS,EAAE;IACrC,MAAMC,QAAQ;IACd,IAAIC,WAAW;IACf,IAAK,IAAIC,IAAI,GAAGA,KAAKH,QAAQG,IAAK;QAChC,MAAMC,eAAelB,KAAKC,KAAK,CAACD,KAAKmB,MAAM,KAAKJ,MAAMD,MAAM;QAC5DE,YAAYD,MAAMK,SAAS,CAACF,cAAcA,eAAe;IAC3D;IACA,OAAOF;AACT;AAEO,SAASzB,cAAc8B,GAAW,EAAEC,MAAW;IACpD,MAAMC,OAAOD,MAAM,CAACD,IAAI;IACxB,OAAOC,MAAM,CAACD,IAAI;IAClB,OAAOE;AACT;AAEO,SAASrC,UAAUsC,GAAW;IACnC,OAAOA,IACJlB,KAAK,CAAC,KACNmB,GAAG,CAAC,CAACC,IAAMC,mBAAmBD,IAC9BE,IAAI,CAAC;AACV;AAEO,SAAS3C,UAAUuC,GAAW;IACnC,OAAOA,IACJlB,KAAK,CAAC,KACNmB,GAAG,CAAC,CAACC,IAAMG,mBAAmBH,IAC9BE,IAAI,CAAC;AACV;AAEO,SAAStC,yBAAyBwC,GAAQ,EAAEC,QAAgB;IACjE,MAAMC,IAAID,SAASzB,KAAK,CAAC;IACzB,IAAI2B,IAAIH;IACR,IAAK,IAAIb,IAAI,GAAGiB,IAAIF,EAAElB,MAAM,EAAEG,IAAIiB,GAAGjB,IAAK;QACxC,MAAMkB,IAAIH,CAAC,CAACf,EAAE;QACd,IAAIkB,KAAKF,GAAG;YACVA,IAAIA,CAAC,CAACE,EAAE;QACV,OAAO;YACL,OAAO;QACT;IACF;IACA,OAAOF;AACT;AAEO,SAASrD,iBAAiBwB,KAAa;IAC5C,OAAOA,MAAMgC,MAAM,CAAC,GAAGC,WAAW,KAAKjC,MAAMkC,KAAK,CAAC;AACrD"}
|