@sync-in/server 1.6.0 → 1.7.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 +16 -0
- package/README.md +1 -1
- package/environment/environment.dist.yaml +8 -2
- package/package.json +10 -10
- package/server/authentication/auth.config.js +23 -4
- package/server/authentication/auth.config.js.map +1 -1
- package/server/authentication/constants/auth-ldap.js +45 -0
- package/server/authentication/constants/auth-ldap.js.map +1 -0
- package/server/authentication/guards/auth-basic.strategy.js +2 -0
- package/server/authentication/guards/auth-basic.strategy.js.map +1 -1
- package/server/authentication/guards/auth-local.strategy.js +2 -0
- package/server/authentication/guards/auth-local.strategy.js.map +1 -1
- package/server/authentication/services/auth-methods/auth-method-ldap.service.js +138 -83
- 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 +52 -48
- package/server/authentication/services/auth-methods/auth-method-ldap.service.spec.js.map +1 -1
- package/static/assets/pdfjs/build/pdf.mjs +2522 -914
- package/static/assets/pdfjs/build/pdf.mjs.map +1 -1
- package/static/assets/pdfjs/build/pdf.sandbox.mjs +2 -2
- package/static/assets/pdfjs/build/pdf.worker.mjs +1024 -566
- package/static/assets/pdfjs/build/pdf.worker.mjs.map +1 -1
- package/static/assets/pdfjs/version +1 -1
- package/static/assets/pdfjs/web/debugger.mjs +116 -37
- package/static/assets/pdfjs/web/images/comment-popup-editButton.svg +5 -0
- package/static/assets/pdfjs/web/locale/ach/viewer.ftl +0 -12
- package/static/assets/pdfjs/web/locale/af/viewer.ftl +0 -12
- package/static/assets/pdfjs/web/locale/an/viewer.ftl +0 -16
- package/static/assets/pdfjs/web/locale/ar/viewer.ftl +0 -32
- package/static/assets/pdfjs/web/locale/ast/viewer.ftl +0 -19
- package/static/assets/pdfjs/web/locale/az/viewer.ftl +0 -16
- package/static/assets/pdfjs/web/locale/be/viewer.ftl +0 -32
- package/static/assets/pdfjs/web/locale/bg/viewer.ftl +0 -32
- package/static/assets/pdfjs/web/locale/bn/viewer.ftl +0 -16
- package/static/assets/pdfjs/web/locale/bo/viewer.ftl +0 -12
- package/static/assets/pdfjs/web/locale/br/viewer.ftl +0 -22
- package/static/assets/pdfjs/web/locale/brx/viewer.ftl +0 -16
- package/static/assets/pdfjs/web/locale/bs/viewer.ftl +0 -32
- package/static/assets/pdfjs/web/locale/ca/viewer.ftl +12 -23
- package/static/assets/pdfjs/web/locale/cak/viewer.ftl +0 -23
- package/static/assets/pdfjs/web/locale/ckb/viewer.ftl +0 -16
- package/static/assets/pdfjs/web/locale/cs/viewer.ftl +0 -32
- package/static/assets/pdfjs/web/locale/cy/viewer.ftl +0 -32
- package/static/assets/pdfjs/web/locale/da/viewer.ftl +3 -35
- package/static/assets/pdfjs/web/locale/de/viewer.ftl +0 -32
- package/static/assets/pdfjs/web/locale/dsb/viewer.ftl +0 -32
- package/static/assets/pdfjs/web/locale/el/viewer.ftl +0 -32
- package/static/assets/pdfjs/web/locale/en-CA/viewer.ftl +0 -32
- package/static/assets/pdfjs/web/locale/en-GB/viewer.ftl +0 -32
- package/static/assets/pdfjs/web/locale/en-US/viewer.ftl +25 -13
- package/static/assets/pdfjs/web/locale/eo/viewer.ftl +0 -32
- package/static/assets/pdfjs/web/locale/es-AR/viewer.ftl +0 -32
- package/static/assets/pdfjs/web/locale/es-CL/viewer.ftl +0 -32
- package/static/assets/pdfjs/web/locale/es-ES/viewer.ftl +5 -32
- package/static/assets/pdfjs/web/locale/es-MX/viewer.ftl +0 -32
- package/static/assets/pdfjs/web/locale/et/viewer.ftl +0 -16
- package/static/assets/pdfjs/web/locale/eu/viewer.ftl +38 -32
- package/static/assets/pdfjs/web/locale/fa/viewer.ftl +0 -19
- package/static/assets/pdfjs/web/locale/ff/viewer.ftl +0 -12
- package/static/assets/pdfjs/web/locale/fi/viewer.ftl +0 -32
- package/static/assets/pdfjs/web/locale/fr/viewer.ftl +0 -32
- package/static/assets/pdfjs/web/locale/fur/viewer.ftl +0 -32
- package/static/assets/pdfjs/web/locale/fy-NL/viewer.ftl +0 -32
- package/static/assets/pdfjs/web/locale/ga-IE/viewer.ftl +0 -12
- package/static/assets/pdfjs/web/locale/gd/viewer.ftl +0 -23
- package/static/assets/pdfjs/web/locale/gl/viewer.ftl +0 -32
- package/static/assets/pdfjs/web/locale/gn/viewer.ftl +0 -32
- package/static/assets/pdfjs/web/locale/gu-IN/viewer.ftl +0 -12
- package/static/assets/pdfjs/web/locale/he/viewer.ftl +0 -32
- package/static/assets/pdfjs/web/locale/hi-IN/viewer.ftl +0 -16
- package/static/assets/pdfjs/web/locale/hr/viewer.ftl +0 -32
- package/static/assets/pdfjs/web/locale/hsb/viewer.ftl +0 -32
- package/static/assets/pdfjs/web/locale/hu/viewer.ftl +0 -32
- package/static/assets/pdfjs/web/locale/hy-AM/viewer.ftl +372 -16
- package/static/assets/pdfjs/web/locale/hye/viewer.ftl +0 -16
- package/static/assets/pdfjs/web/locale/ia/viewer.ftl +0 -32
- package/static/assets/pdfjs/web/locale/id/viewer.ftl +38 -32
- package/static/assets/pdfjs/web/locale/is/viewer.ftl +27 -32
- package/static/assets/pdfjs/web/locale/it/viewer.ftl +0 -33
- package/static/assets/pdfjs/web/locale/ja/viewer.ftl +31 -33
- package/static/assets/pdfjs/web/locale/ka/viewer.ftl +0 -32
- package/static/assets/pdfjs/web/locale/kab/viewer.ftl +0 -32
- package/static/assets/pdfjs/web/locale/kk/viewer.ftl +31 -32
- package/static/assets/pdfjs/web/locale/km/viewer.ftl +0 -12
- package/static/assets/pdfjs/web/locale/kn/viewer.ftl +0 -12
- package/static/assets/pdfjs/web/locale/ko/viewer.ftl +0 -32
- package/static/assets/pdfjs/web/locale/lij/viewer.ftl +0 -12
- package/static/assets/pdfjs/web/locale/lo/viewer.ftl +0 -23
- package/static/assets/pdfjs/web/locale/lt/viewer.ftl +0 -16
- package/static/assets/pdfjs/web/locale/ltg/viewer.ftl +0 -12
- package/static/assets/pdfjs/web/locale/lv/viewer.ftl +0 -12
- package/static/assets/pdfjs/web/locale/meh/viewer.ftl +0 -14
- package/static/assets/pdfjs/web/locale/mk/viewer.ftl +0 -19
- package/static/assets/pdfjs/web/locale/ml/viewer.ftl +0 -31
- package/static/assets/pdfjs/web/locale/mr/viewer.ftl +0 -16
- package/static/assets/pdfjs/web/locale/ms/viewer.ftl +0 -12
- package/static/assets/pdfjs/web/locale/my/viewer.ftl +0 -12
- package/static/assets/pdfjs/web/locale/nb-NO/viewer.ftl +0 -32
- package/static/assets/pdfjs/web/locale/ne-NP/viewer.ftl +0 -12
- package/static/assets/pdfjs/web/locale/nl/viewer.ftl +0 -32
- package/static/assets/pdfjs/web/locale/nn-NO/viewer.ftl +0 -32
- package/static/assets/pdfjs/web/locale/oc/viewer.ftl +0 -24
- package/static/assets/pdfjs/web/locale/pa-IN/viewer.ftl +0 -32
- package/static/assets/pdfjs/web/locale/pl/viewer.ftl +0 -32
- package/static/assets/pdfjs/web/locale/pt-BR/viewer.ftl +0 -32
- package/static/assets/pdfjs/web/locale/pt-PT/viewer.ftl +0 -32
- package/static/assets/pdfjs/web/locale/rm/viewer.ftl +0 -32
- package/static/assets/pdfjs/web/locale/ro/viewer.ftl +5 -37
- package/static/assets/pdfjs/web/locale/ru/viewer.ftl +0 -32
- package/static/assets/pdfjs/web/locale/sat/viewer.ftl +0 -23
- package/static/assets/pdfjs/web/locale/sc/viewer.ftl +8 -27
- package/static/assets/pdfjs/web/locale/sco/viewer.ftl +0 -16
- package/static/assets/pdfjs/web/locale/si/viewer.ftl +0 -22
- package/static/assets/pdfjs/web/locale/sk/viewer.ftl +0 -32
- package/static/assets/pdfjs/web/locale/skr/viewer.ftl +0 -32
- package/static/assets/pdfjs/web/locale/sl/viewer.ftl +30 -32
- package/static/assets/pdfjs/web/locale/son/viewer.ftl +0 -12
- package/static/assets/pdfjs/web/locale/sq/viewer.ftl +0 -32
- package/static/assets/pdfjs/web/locale/sr/viewer.ftl +0 -32
- package/static/assets/pdfjs/web/locale/sv-SE/viewer.ftl +0 -32
- package/static/assets/pdfjs/web/locale/szl/viewer.ftl +0 -16
- package/static/assets/pdfjs/web/locale/ta/viewer.ftl +0 -12
- package/static/assets/pdfjs/web/locale/te/viewer.ftl +0 -16
- package/static/assets/pdfjs/web/locale/tg/viewer.ftl +0 -32
- package/static/assets/pdfjs/web/locale/th/viewer.ftl +38 -32
- package/static/assets/pdfjs/web/locale/tl/viewer.ftl +0 -16
- package/static/assets/pdfjs/web/locale/tr/viewer.ftl +0 -32
- package/static/assets/pdfjs/web/locale/trs/viewer.ftl +0 -12
- package/static/assets/pdfjs/web/locale/uk/viewer.ftl +0 -32
- package/static/assets/pdfjs/web/locale/ur/viewer.ftl +0 -16
- package/static/assets/pdfjs/web/locale/uz/viewer.ftl +0 -12
- package/static/assets/pdfjs/web/locale/vi/viewer.ftl +0 -32
- package/static/assets/pdfjs/web/locale/xh/viewer.ftl +0 -12
- package/static/assets/pdfjs/web/locale/zh-CN/viewer.ftl +0 -32
- package/static/assets/pdfjs/web/locale/zh-TW/viewer.ftl +0 -32
- package/static/assets/pdfjs/web/viewer.css +586 -437
- package/static/assets/pdfjs/web/viewer.html +12 -23
- package/static/assets/pdfjs/web/viewer.mjs +955 -514
- package/static/assets/pdfjs/web/viewer.mjs.map +1 -1
- package/static/assets/pdfjs/web/wasm/openjpeg.wasm +0 -0
- package/static/assets/pdfjs/web/wasm/openjpeg_nowasm_fallback.js +10 -22
- package/static/{chunk-NW3CTYUW.js → chunk-3GFGJYMK.js} +1 -1
- package/static/{chunk-DNMO47SY.js → chunk-4YGJGZZZ.js} +1 -1
- package/static/{chunk-5M4YJZUB.js → chunk-5K7HEX3C.js} +1 -1
- package/static/{chunk-GCUWGVYT.js → chunk-5KLMS6A4.js} +1 -1
- package/static/{chunk-HME7LAEY.js → chunk-7ITZXYYJ.js} +1 -1
- package/static/chunk-ATP3BFHV.js +562 -0
- package/static/{chunk-KPZ7FEMO.js → chunk-BB4G55KE.js} +1 -1
- package/static/{chunk-X7MFVDBY.js → chunk-DJYJ66UF.js} +1 -1
- package/static/{chunk-XCBLEI2E.js → chunk-EVIE5F2U.js} +1 -1
- package/static/{chunk-IEUANP3Q.js → chunk-EWKSX76T.js} +1 -1
- package/static/{chunk-CN27VAGB.js → chunk-FHLACA7V.js} +1 -1
- package/static/{chunk-ABGR5AYC.js → chunk-GCATNU55.js} +1 -1
- package/static/chunk-GYODPCIE.js +1 -0
- package/static/{chunk-YVJDYSDE.js → chunk-HZTFYLM5.js} +1 -1
- package/static/{chunk-G3FOG2QB.js → chunk-IPAC4VAF.js} +1 -1
- package/static/{chunk-QFOMEU3T.js → chunk-IQOALFYU.js} +1 -1
- package/static/{chunk-O3ANXCPE.js → chunk-JSUKJT6Z.js} +1 -1
- package/static/{chunk-NN3VQOS7.js → chunk-LTGFCQR7.js} +1 -1
- package/static/{chunk-6BFNMDUD.js → chunk-LV3PYKWO.js} +1 -1
- package/static/{chunk-5ZGQYTS2.js → chunk-N2WFNW6M.js} +1 -1
- package/static/{chunk-RKNTQYMU.js → chunk-ORMRCEGT.js} +1 -1
- package/static/{chunk-M57NVD4V.js → chunk-OUTBJSMW.js} +1 -1
- package/static/{chunk-WINILGQN.js → chunk-PTGDOWV3.js} +1 -1
- package/static/{chunk-EI4PVI2W.js → chunk-QNJFQVYI.js} +1 -1
- package/static/{chunk-YD74UCFG.js → chunk-RS2PX32L.js} +1 -1
- package/static/{chunk-2XJ5Z2GZ.js → chunk-RSSWH3S2.js} +1 -1
- package/static/{chunk-G2TKYYWK.js → chunk-SIPE37PA.js} +1 -1
- package/static/{chunk-ET6QDNNM.js → chunk-TKTCBDOG.js} +1 -1
- package/static/{chunk-YDFVKH2D.js → chunk-V6K2N46L.js} +1 -1
- package/static/{chunk-2TZUZMCM.js → chunk-XLCCZSQL.js} +3 -3
- package/static/{chunk-IIFHIIC6.js → chunk-YPEH66GG.js} +1 -1
- package/static/{chunk-XLWCV4HI.js → chunk-YPOIUQ57.js} +1 -1
- package/static/{chunk-XPIYOZBX.js → chunk-ZKCFO2OA.js} +1 -1
- package/static/index.html +1 -1
- package/static/main-MZ7HWZXO.js +9 -0
- package/static/chunk-6IRL673W.js +0 -559
- package/static/chunk-UQ4TRQCE.js +0 -1
- package/static/main-QNBKYA6L.js +0 -9
package/CHANGELOG.md
CHANGED
|
@@ -1,4 +1,20 @@
|
|
|
1
1
|
|
|
2
|
+
## [1.7.0](https://github.com/Sync-in/server/compare/v1.6.1...v1.7.0) (2025-10-09)
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
* **backend:auth:** add `adminGroup` support and improve LDAP user role assignment ([9074145](https://github.com/Sync-in/server/commit/9074145c9c86e023c73e0a5522f87441356bb240))
|
|
8
|
+
* **backend:auth:** enhance LDAP authentication configuration with upnSuffix and netbiosName parameters ([5a5d623](https://github.com/Sync-in/server/commit/5a5d62317198d3c1164bc6f9efe6bdb50bfe25f7))
|
|
9
|
+
|
|
10
|
+
## [1.6.1](https://github.com/Sync-in/server/compare/v1.6.0...v1.6.1) (2025-10-09)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Bug Fixes
|
|
14
|
+
|
|
15
|
+
* **backend:auth:** improve AD/LDAP authentication handling and normalization ([db1a9e3](https://github.com/Sync-in/server/commit/db1a9e3d4a02c6be5ef594b4a383e05d0bc50fc4))
|
|
16
|
+
* **frontend:links:** fallback to default MIME URL when origin MIME URL is not found ([5724f3a](https://github.com/Sync-in/server/commit/5724f3a730fc8d8b51268071b0d3370bc62f6901))
|
|
17
|
+
|
|
2
18
|
## [1.6.0](https://github.com/Sync-in/server/compare/v1.5.2...v1.6.0) (2025-09-26)
|
|
3
19
|
|
|
4
20
|
🔥🚀 Support for Multi-Factor Authentication (MFA) & App Passwords
|
package/README.md
CHANGED
|
@@ -102,7 +102,7 @@ Before submitting your pull request, please confirm the following:
|
|
|
102
102
|
This project is licensed under the **GNU Affero General Public License (AGPL-3.0-or-later)**.
|
|
103
103
|
See [LICENSE](LICENSE) for the full text.
|
|
104
104
|
|
|
105
|
-
Sync-in® is a registered trademark, see our [Trademark Policy](https://sync-in.com/
|
|
105
|
+
Sync-in® is a registered trademark, see our [Trademark Policy](https://sync-in.com/trademark).
|
|
106
106
|
|
|
107
107
|
---
|
|
108
108
|
|
|
@@ -115,17 +115,23 @@ auth:
|
|
|
115
115
|
ldap:
|
|
116
116
|
# e.g: [ldap://localhost:389, ldaps://localhost:636] (array required)
|
|
117
117
|
servers: []
|
|
118
|
-
# baseDN: distinguished name (
|
|
118
|
+
# baseDN: distinguished name (e.g., ou=people,dc=ldap,dc=sync-in,dc=com)
|
|
119
119
|
baseDN:
|
|
120
120
|
# filter, e.g: (acl=admin)
|
|
121
121
|
filter:
|
|
122
122
|
attributes:
|
|
123
|
-
# login attribute
|
|
123
|
+
# login attribute: `uid` | `sAMAccountName` | `userPrincipalName`, used to authenticate the user
|
|
124
124
|
# default: `uid`
|
|
125
125
|
login: uid
|
|
126
126
|
# email attribute: `mail` or `email`
|
|
127
127
|
# default: `mail`
|
|
128
128
|
email: mail
|
|
129
|
+
# adminGroup: The CN of a group containing Sync-in administrators (e.g., administrators)
|
|
130
|
+
adminGroup:
|
|
131
|
+
# upnSuffix: AD domain suffix used with `userPrincipalName` to build UPN-style logins (e.g., user@`sync-in.com`)
|
|
132
|
+
upnSuffix:
|
|
133
|
+
# netbiosName: NetBIOS domain name used with `sAMAccountName` to build legacy logins (e.g., `SYNC_IN`\user)
|
|
134
|
+
netbiosName:
|
|
129
135
|
applications:
|
|
130
136
|
files:
|
|
131
137
|
# required
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sync-in/server",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
4
4
|
"description": "The secure, open-source platform for file storage, sharing, collaboration, and sync",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Johan Legrand",
|
|
@@ -73,7 +73,7 @@
|
|
|
73
73
|
},
|
|
74
74
|
"dependencies": {
|
|
75
75
|
"@fastify/cookie": "11.0.2",
|
|
76
|
-
"@fastify/helmet": "13.0.
|
|
76
|
+
"@fastify/helmet": "13.0.2",
|
|
77
77
|
"@fastify/multipart": "9.2.1",
|
|
78
78
|
"@fastify/send": "4.1.0",
|
|
79
79
|
"@fastify/static": "8.2.0",
|
|
@@ -97,28 +97,28 @@
|
|
|
97
97
|
"class-transformer": "0.5.1",
|
|
98
98
|
"class-validator": "0.14.2",
|
|
99
99
|
"deepmerge": "4.3.1",
|
|
100
|
-
"drizzle-kit": "0.31.
|
|
101
|
-
"drizzle-orm": "0.44.
|
|
102
|
-
"fast-xml-parser": "5.
|
|
100
|
+
"drizzle-kit": "0.31.5",
|
|
101
|
+
"drizzle-orm": "0.44.6",
|
|
102
|
+
"fast-xml-parser": "5.3.0",
|
|
103
103
|
"fs-extra": "11.3.2",
|
|
104
104
|
"html-to-text": "9.0.5",
|
|
105
105
|
"js-yaml": "4.1.0",
|
|
106
106
|
"ldapts": "8.0.9",
|
|
107
107
|
"mime-types": "3.0.1",
|
|
108
|
-
"mysql2": "3.15.
|
|
109
|
-
"nestjs-pino": "4.4.
|
|
110
|
-
"nodemailer": "7.0.
|
|
108
|
+
"mysql2": "3.15.2",
|
|
109
|
+
"nestjs-pino": "4.4.1",
|
|
110
|
+
"nodemailer": "7.0.9",
|
|
111
111
|
"passport-http": "0.3.0",
|
|
112
112
|
"passport-jwt": "4.0.1",
|
|
113
113
|
"passport-local": "1.0.0",
|
|
114
114
|
"passport": "0.7.0",
|
|
115
|
-
"pdfjs-dist": "5.4.
|
|
115
|
+
"pdfjs-dist": "5.4.296",
|
|
116
116
|
"pino-pretty": "13.1.1",
|
|
117
117
|
"qrcode-generator": "2.0.4",
|
|
118
118
|
"redis": "4.7.1",
|
|
119
119
|
"sax": "1.4.1",
|
|
120
120
|
"socket.io": "4.8.1",
|
|
121
|
-
"tar": "7.
|
|
121
|
+
"tar": "7.5.1",
|
|
122
122
|
"time2fa": "1.4.2",
|
|
123
123
|
"yauzl": "3.2.0"
|
|
124
124
|
}
|
|
@@ -48,6 +48,7 @@ const _classtransformer = require("class-transformer");
|
|
|
48
48
|
const _classvalidator = require("class-validator");
|
|
49
49
|
const _appconstants = require("../app.constants");
|
|
50
50
|
const _auth = require("./constants/auth");
|
|
51
|
+
const _authldap = require("./constants/auth-ldap");
|
|
51
52
|
function _ts_decorate(decorators, target, key, desc) {
|
|
52
53
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
53
54
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
@@ -180,19 +181,22 @@ _ts_decorate([
|
|
|
180
181
|
], AuthTokenConfig.prototype, "ws", void 0);
|
|
181
182
|
let AuthMethodLdapAttributesConfig = class AuthMethodLdapAttributesConfig {
|
|
182
183
|
constructor(){
|
|
183
|
-
this.login =
|
|
184
|
-
this.email =
|
|
184
|
+
this.login = _authldap.LDAP_LOGIN_ATTR.UID;
|
|
185
|
+
this.email = _authldap.LDAP_COMMON_ATTR.MAIL;
|
|
185
186
|
}
|
|
186
187
|
};
|
|
187
188
|
_ts_decorate([
|
|
188
189
|
(0, _classvalidator.IsOptional)(),
|
|
189
190
|
(0, _classvalidator.IsString)(),
|
|
190
|
-
(0, _classtransformer.Transform)(({ value })=>value ||
|
|
191
|
+
(0, _classtransformer.Transform)(({ value })=>value || _authldap.LDAP_LOGIN_ATTR.UID),
|
|
192
|
+
(0, _classvalidator.IsEnum)(_authldap.LDAP_LOGIN_ATTR),
|
|
193
|
+
_ts_metadata("design:type", typeof _authldap.LDAP_LOGIN_ATTR === "undefined" ? Object : _authldap.LDAP_LOGIN_ATTR)
|
|
191
194
|
], AuthMethodLdapAttributesConfig.prototype, "login", void 0);
|
|
192
195
|
_ts_decorate([
|
|
193
196
|
(0, _classvalidator.IsOptional)(),
|
|
194
197
|
(0, _classvalidator.IsString)(),
|
|
195
|
-
(0, _classtransformer.Transform)(({ value })=>value ||
|
|
198
|
+
(0, _classtransformer.Transform)(({ value })=>value || _authldap.LDAP_COMMON_ATTR.MAIL),
|
|
199
|
+
_ts_metadata("design:type", String)
|
|
196
200
|
], AuthMethodLdapAttributesConfig.prototype, "email", void 0);
|
|
197
201
|
let AuthMethodLdapConfig = class AuthMethodLdapConfig {
|
|
198
202
|
constructor(){
|
|
@@ -226,6 +230,21 @@ _ts_decorate([
|
|
|
226
230
|
(0, _classtransformer.Type)(()=>AuthMethodLdapAttributesConfig),
|
|
227
231
|
_ts_metadata("design:type", typeof AuthMethodLdapAttributesConfig === "undefined" ? Object : AuthMethodLdapAttributesConfig)
|
|
228
232
|
], AuthMethodLdapConfig.prototype, "attributes", void 0);
|
|
233
|
+
_ts_decorate([
|
|
234
|
+
(0, _classvalidator.IsOptional)(),
|
|
235
|
+
(0, _classvalidator.IsString)(),
|
|
236
|
+
_ts_metadata("design:type", String)
|
|
237
|
+
], AuthMethodLdapConfig.prototype, "adminGroup", void 0);
|
|
238
|
+
_ts_decorate([
|
|
239
|
+
(0, _classvalidator.IsOptional)(),
|
|
240
|
+
(0, _classvalidator.IsString)(),
|
|
241
|
+
_ts_metadata("design:type", String)
|
|
242
|
+
], AuthMethodLdapConfig.prototype, "upnSuffix", void 0);
|
|
243
|
+
_ts_decorate([
|
|
244
|
+
(0, _classvalidator.IsOptional)(),
|
|
245
|
+
(0, _classvalidator.IsString)(),
|
|
246
|
+
_ts_metadata("design:type", String)
|
|
247
|
+
], AuthMethodLdapConfig.prototype, "netbiosName", void 0);
|
|
229
248
|
let AuthConfig = class AuthConfig {
|
|
230
249
|
constructor(){
|
|
231
250
|
this.method = 'mysql';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../backend/src/authentication/auth.config.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 { Exclude, Transform, Type } from 'class-transformer'\nimport {\n ArrayNotEmpty,\n IsArray,\n IsBoolean,\n IsDefined,\n IsIn,\n IsNotEmpty,\n IsNotEmptyObject,\n IsObject,\n IsOptional,\n IsString,\n ValidateIf,\n ValidateNested\n} from 'class-validator'\nimport { SERVER_NAME } from '../app.constants'\nimport { ACCESS_KEY, CSRF_KEY, REFRESH_KEY, WS_KEY } from './constants/auth'\n\nexport class AuthMfaTotpConfig {\n @IsBoolean()\n enabled = true\n\n @IsString()\n issuer = SERVER_NAME\n}\n\nexport class AuthMfaConfig {\n @IsDefined()\n @IsNotEmptyObject()\n @IsObject()\n @ValidateNested()\n @Type(() => AuthMfaTotpConfig)\n totp: AuthMfaTotpConfig = new AuthMfaTotpConfig()\n}\n\nexport class AuthTokenAccessConfig {\n @Exclude({ toClassOnly: true })\n // force default name\n name = ACCESS_KEY\n\n @IsString()\n @IsNotEmpty()\n secret: string\n\n @IsString()\n @IsNotEmpty()\n expiration = '30m'\n}\n\nexport class AuthTokenRefreshConfig {\n @Exclude({ toClassOnly: true })\n // force default name\n name = REFRESH_KEY\n\n @IsString()\n @IsNotEmpty()\n secret: string\n\n @IsString()\n @IsNotEmpty()\n expiration = '4h'\n}\n\nexport class AuthTokenCsrfConfig extends AuthTokenRefreshConfig {\n @IsString()\n @IsNotEmpty()\n override name: string = CSRF_KEY\n}\n\nexport class AuthTokenWSConfig extends AuthTokenRefreshConfig {\n @IsString()\n @IsNotEmpty()\n override name: string = WS_KEY\n}\n\nexport class AuthTokenConfig {\n @IsDefined()\n @IsNotEmptyObject()\n @IsObject()\n @ValidateNested()\n @Type(() => AuthTokenAccessConfig)\n access: AuthTokenAccessConfig\n\n @IsDefined()\n @IsNotEmptyObject()\n @IsObject()\n @ValidateNested()\n @Type(() => AuthTokenRefreshConfig)\n refresh: AuthTokenRefreshConfig\n\n @IsDefined()\n @IsNotEmptyObject()\n @IsObject()\n @ValidateNested()\n @Type(() => AuthTokenCsrfConfig)\n csrf: AuthTokenCsrfConfig\n\n @IsDefined()\n @IsNotEmptyObject()\n @IsObject()\n @ValidateNested()\n @Type(() => AuthTokenWSConfig)\n ws: AuthTokenWSConfig\n}\n\nexport class AuthMethodLdapAttributesConfig {\n @IsOptional()\n @IsString()\n @Transform(({ value }) => value ||
|
|
1
|
+
{"version":3,"sources":["../../../backend/src/authentication/auth.config.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 { Exclude, Transform, Type } from 'class-transformer'\nimport {\n ArrayNotEmpty,\n IsArray,\n IsBoolean,\n IsDefined,\n IsEnum,\n IsIn,\n IsNotEmpty,\n IsNotEmptyObject,\n IsObject,\n IsOptional,\n IsString,\n ValidateIf,\n ValidateNested\n} from 'class-validator'\nimport { SERVER_NAME } from '../app.constants'\nimport { ACCESS_KEY, CSRF_KEY, REFRESH_KEY, WS_KEY } from './constants/auth'\nimport { LDAP_COMMON_ATTR, LDAP_LOGIN_ATTR } from './constants/auth-ldap'\n\nexport class AuthMfaTotpConfig {\n @IsBoolean()\n enabled = true\n\n @IsString()\n issuer = SERVER_NAME\n}\n\nexport class AuthMfaConfig {\n @IsDefined()\n @IsNotEmptyObject()\n @IsObject()\n @ValidateNested()\n @Type(() => AuthMfaTotpConfig)\n totp: AuthMfaTotpConfig = new AuthMfaTotpConfig()\n}\n\nexport class AuthTokenAccessConfig {\n @Exclude({ toClassOnly: true })\n // force default name\n name = ACCESS_KEY\n\n @IsString()\n @IsNotEmpty()\n secret: string\n\n @IsString()\n @IsNotEmpty()\n expiration = '30m'\n}\n\nexport class AuthTokenRefreshConfig {\n @Exclude({ toClassOnly: true })\n // force default name\n name = REFRESH_KEY\n\n @IsString()\n @IsNotEmpty()\n secret: string\n\n @IsString()\n @IsNotEmpty()\n expiration = '4h'\n}\n\nexport class AuthTokenCsrfConfig extends AuthTokenRefreshConfig {\n @IsString()\n @IsNotEmpty()\n override name: string = CSRF_KEY\n}\n\nexport class AuthTokenWSConfig extends AuthTokenRefreshConfig {\n @IsString()\n @IsNotEmpty()\n override name: string = WS_KEY\n}\n\nexport class AuthTokenConfig {\n @IsDefined()\n @IsNotEmptyObject()\n @IsObject()\n @ValidateNested()\n @Type(() => AuthTokenAccessConfig)\n access: AuthTokenAccessConfig\n\n @IsDefined()\n @IsNotEmptyObject()\n @IsObject()\n @ValidateNested()\n @Type(() => AuthTokenRefreshConfig)\n refresh: AuthTokenRefreshConfig\n\n @IsDefined()\n @IsNotEmptyObject()\n @IsObject()\n @ValidateNested()\n @Type(() => AuthTokenCsrfConfig)\n csrf: AuthTokenCsrfConfig\n\n @IsDefined()\n @IsNotEmptyObject()\n @IsObject()\n @ValidateNested()\n @Type(() => AuthTokenWSConfig)\n ws: AuthTokenWSConfig\n}\n\nexport class AuthMethodLdapAttributesConfig {\n @IsOptional()\n @IsString()\n @Transform(({ value }) => value || LDAP_LOGIN_ATTR.UID)\n @IsEnum(LDAP_LOGIN_ATTR)\n login: LDAP_LOGIN_ATTR = LDAP_LOGIN_ATTR.UID\n\n @IsOptional()\n @IsString()\n @Transform(({ value }) => value || LDAP_COMMON_ATTR.MAIL)\n email: string = LDAP_COMMON_ATTR.MAIL\n}\n\nexport class AuthMethodLdapConfig {\n @Transform(({ value }) => (Array.isArray(value) ? value.filter((v: string) => Boolean(v)) : value))\n @ArrayNotEmpty()\n @IsArray()\n @IsString({ each: true })\n servers: string[]\n\n @IsString()\n @IsNotEmpty()\n baseDN: string\n\n @IsOptional()\n @IsString()\n filter?: string\n\n @IsDefined()\n @IsNotEmptyObject()\n @IsObject()\n @ValidateNested()\n @Type(() => AuthMethodLdapAttributesConfig)\n attributes: AuthMethodLdapAttributesConfig = new AuthMethodLdapAttributesConfig()\n\n @IsOptional()\n @IsString()\n adminGroup?: string\n\n @IsOptional()\n @IsString()\n upnSuffix?: string\n\n @IsOptional()\n @IsString()\n netbiosName?: string\n}\n\nexport class AuthConfig {\n @IsString()\n @IsIn(['mysql', 'ldap'])\n method: 'mysql' | 'ldap' = 'mysql'\n\n @IsOptional()\n @IsString()\n encryptionKey: string\n\n @IsDefined()\n @IsNotEmptyObject()\n @IsObject()\n @ValidateNested()\n @Type(() => AuthMfaConfig)\n mfa: AuthMfaConfig = new AuthMfaConfig()\n\n @IsString()\n @IsIn(['lax', 'strict'])\n cookieSameSite: 'lax' | 'strict' = 'strict'\n\n @IsDefined()\n @IsNotEmptyObject()\n @IsObject()\n @ValidateNested()\n @Type(() => AuthTokenConfig)\n token: AuthTokenConfig\n\n @ValidateIf((o: AuthConfig) => o.method === 'ldap')\n @IsDefined()\n @IsObject()\n @ValidateNested()\n @Type(() => AuthMethodLdapConfig)\n ldap: AuthMethodLdapConfig\n}\n"],"names":["AuthConfig","AuthMethodLdapAttributesConfig","AuthMethodLdapConfig","AuthMfaConfig","AuthMfaTotpConfig","AuthTokenAccessConfig","AuthTokenConfig","AuthTokenCsrfConfig","AuthTokenRefreshConfig","AuthTokenWSConfig","enabled","issuer","SERVER_NAME","totp","name","ACCESS_KEY","expiration","toClassOnly","REFRESH_KEY","CSRF_KEY","WS_KEY","login","LDAP_LOGIN_ATTR","UID","email","LDAP_COMMON_ATTR","MAIL","value","attributes","Array","isArray","filter","v","Boolean","each","method","mfa","cookieSameSite","o"],"mappings":"AAAA;;;;CAIC;;;;;;;;;;;QA6JYA;eAAAA;;QAhDAC;eAAAA;;QAaAC;eAAAA;;QA5FAC;eAAAA;;QARAC;eAAAA;;QAiBAC;eAAAA;;QAwCAC;eAAAA;;QAZAC;eAAAA;;QAdAC;eAAAA;;QAoBAC;eAAAA;;;kCAvE4B;gCAelC;8BACqB;sBAC8B;0BACR;;;;;;;;;;AAE3C,IAAA,AAAML,oBAAN,MAAMA;;aAEXM,UAAU;aAGVC,SAASC,yBAAW;;AACtB;;;;;;;AAEO,IAAA,AAAMT,gBAAN,MAAMA;;aAMXU,OAA0B,IAAIT;;AAChC;;;;;;oCAFcA;;;AAIP,IAAA,AAAMC,wBAAN,MAAMA;;aAEX,qBAAqB;QACrBS,OAAOC,gBAAU;aAQjBC,aAAa;;AACf;;;QAXaC,aAAa;;;;;;;;;;;;AAanB,IAAA,AAAMT,yBAAN,MAAMA;;aAEX,qBAAqB;QACrBM,OAAOI,iBAAW;aAQlBF,aAAa;;AACf;;;QAXaC,aAAa;;;;;;;;;;;;AAanB,IAAA,AAAMV,sBAAN,MAAMA,4BAA4BC;;QAAlC,qBAGIM,OAAeK,cAAQ;;AAClC;;;;;;AAEO,IAAA,AAAMV,oBAAN,MAAMA,0BAA0BD;;QAAhC,qBAGIM,OAAeM,YAAM;;AAChC;;;;;;AAEO,IAAA,AAAMd,kBAAN,MAAMA;AA4Bb;;;;;;oCAvBcD;;;;;;;;oCAOAG;;;;;;;;oCAOAD;;;;;;;;oCAOAE;;;AAIP,IAAA,AAAMR,iCAAN,MAAMA;;aAKXoB,QAAyBC,yBAAe,CAACC,GAAG;aAK5CC,QAAgBC,0BAAgB,CAACC,IAAI;;AACvC;;;;sCARc,EAAEC,KAAK,EAAE,GAAKA,SAASL,yBAAe,CAACC,GAAG;;;;;;;sCAM1C,EAAEI,KAAK,EAAE,GAAKA,SAASF,0BAAgB,CAACC,IAAI;;;AAInD,IAAA,AAAMxB,uBAAN,MAAMA;;aAoBX0B,aAA6C,IAAI3B;;AAanD;;sCAhCc,EAAE0B,KAAK,EAAE,GAAME,MAAMC,OAAO,CAACH,SAASA,MAAMI,MAAM,CAAC,CAACC,IAAcC,QAAQD,MAAML;;;;QAGhFO,MAAM;;;;;;;;;;;;;;;;;;;oCAeNjC;;;;;;;;;;;;;;;;;;AAgBP,IAAA,AAAMD,aAAN,MAAMA;;aAGXmC,SAA2B;aAW3BC,MAAqB,IAAIjC;aAIzBkC,iBAAmC;;AAerC;;;;QA/BS;QAAS;;;;;;;;;;;;;;oCAWJlC;;;;;;QAIL;QAAO;;;;;;;;;oCAOFG;;;;qCAGCgC,IAAkBA,EAAEH,MAAM,KAAK;;;;oCAIhCjC"}
|
|
@@ -0,0 +1,45 @@
|
|
|
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 ALL_LDAP_ATTRIBUTES () {
|
|
17
|
+
return ALL_LDAP_ATTRIBUTES;
|
|
18
|
+
},
|
|
19
|
+
get LDAP_COMMON_ATTR () {
|
|
20
|
+
return LDAP_COMMON_ATTR;
|
|
21
|
+
},
|
|
22
|
+
get LDAP_LOGIN_ATTR () {
|
|
23
|
+
return LDAP_LOGIN_ATTR;
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
var LDAP_LOGIN_ATTR = /*#__PURE__*/ function(LDAP_LOGIN_ATTR) {
|
|
27
|
+
LDAP_LOGIN_ATTR["UID"] = "uid";
|
|
28
|
+
LDAP_LOGIN_ATTR["SAM"] = "sAMAccountName";
|
|
29
|
+
LDAP_LOGIN_ATTR["UPN"] = "userPrincipalName";
|
|
30
|
+
return LDAP_LOGIN_ATTR;
|
|
31
|
+
}({});
|
|
32
|
+
const LDAP_COMMON_ATTR = {
|
|
33
|
+
MAIL: 'mail',
|
|
34
|
+
GIVEN_NAME: 'givenName',
|
|
35
|
+
SN: 'sn',
|
|
36
|
+
CN: 'cn',
|
|
37
|
+
DISPLAY_NAME: 'displayName',
|
|
38
|
+
MEMBER_OF: 'memberOf'
|
|
39
|
+
};
|
|
40
|
+
const ALL_LDAP_ATTRIBUTES = [
|
|
41
|
+
...Object.values(LDAP_LOGIN_ATTR),
|
|
42
|
+
...Object.values(LDAP_COMMON_ATTR)
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
//# sourceMappingURL=auth-ldap.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../backend/src/authentication/constants/auth-ldap.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\nexport enum LDAP_LOGIN_ATTR {\n UID = 'uid',\n SAM = 'sAMAccountName',\n UPN = 'userPrincipalName'\n}\n\nexport const LDAP_COMMON_ATTR = {\n MAIL: 'mail',\n GIVEN_NAME: 'givenName',\n SN: 'sn',\n CN: 'cn',\n DISPLAY_NAME: 'displayName',\n MEMBER_OF: 'memberOf'\n} as const\n\nexport const ALL_LDAP_ATTRIBUTES = [...Object.values(LDAP_LOGIN_ATTR), ...Object.values(LDAP_COMMON_ATTR)]\n"],"names":["ALL_LDAP_ATTRIBUTES","LDAP_COMMON_ATTR","LDAP_LOGIN_ATTR","MAIL","GIVEN_NAME","SN","CN","DISPLAY_NAME","MEMBER_OF","Object","values"],"mappings":"AAAA;;;;CAIC;;;;;;;;;;;QAiBYA;eAAAA;;QATAC;eAAAA;;QANDC;eAAAA;;;AAAL,IAAA,AAAKA,yCAAAA;;;;WAAAA;;AAML,MAAMD,mBAAmB;IAC9BE,MAAM;IACNC,YAAY;IACZC,IAAI;IACJC,IAAI;IACJC,cAAc;IACdC,WAAW;AACb;AAEO,MAAMR,sBAAsB;OAAIS,OAAOC,MAAM,CAACR;OAAqBO,OAAOC,MAAM,CAACT;CAAkB"}
|
|
@@ -33,6 +33,8 @@ function _ts_metadata(k, v) {
|
|
|
33
33
|
}
|
|
34
34
|
let AuthBasicStrategy = class AuthBasicStrategy extends (0, _passport.PassportStrategy)(_passporthttp.BasicStrategy, 'basic') {
|
|
35
35
|
async validate(req, loginOrEmail, password) {
|
|
36
|
+
loginOrEmail = loginOrEmail.trim();
|
|
37
|
+
password = password.trim();
|
|
36
38
|
this.logger.assign({
|
|
37
39
|
user: loginOrEmail
|
|
38
40
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../backend/src/authentication/guards/auth-basic.strategy.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 { Injectable } from '@nestjs/common'\nimport { AbstractStrategy, PassportStrategy } from '@nestjs/passport'\nimport { instanceToPlain, plainToInstance } from 'class-transformer'\nimport { FastifyRequest } from 'fastify'\nimport { PinoLogger } from 'nestjs-pino'\nimport { BasicStrategy } from 'passport-http'\nimport { SERVER_NAME } from '../../app.constants'\nimport { UserModel } from '../../applications/users/models/user.model'\nimport { Cache } from '../../infrastructure/cache/services/cache.service'\nimport { AUTH_SCOPE } from '../constants/scope'\nimport { AuthMethod } from '../models/auth-method'\n\n@Injectable()\nexport class AuthBasicStrategy extends PassportStrategy(BasicStrategy, 'basic') implements AbstractStrategy {\n private readonly CACHE_TTL = 900\n private readonly CACHE_KEY_PREFIX = 'auth-webdav'\n\n constructor(\n private readonly authMethod: AuthMethod,\n private readonly cache: Cache,\n private readonly logger: PinoLogger\n ) {\n super({ passReqToCallback: true, realm: SERVER_NAME })\n }\n\n async validate(req: FastifyRequest, loginOrEmail: string, password: string): Promise<Omit<UserModel, 'password'> | null> {\n this.logger.assign({ user: loginOrEmail })\n const authBasicUser = `${this.CACHE_KEY_PREFIX}-${req.headers['authorization'].split(' ').at(-1).toLowerCase()}`\n const userFromCache: any = await this.cache.get(authBasicUser)\n if (userFromCache === null) {\n // not authorized\n return null\n }\n if (userFromCache !== undefined) {\n // cached\n // warning: plainToInstance do not use constructor to instantiate class\n return plainToInstance(UserModel, userFromCache)\n }\n const userFromDB: UserModel = await this.authMethod.validateUser(loginOrEmail, password, req.ip, AUTH_SCOPE.WEBDAV)\n if (userFromDB !== null) {\n userFromDB.removePassword()\n }\n const userToCache: Record<string, any> | null = userFromDB ? instanceToPlain(userFromDB, { excludePrefixes: ['_'] }) : null\n this.cache.set(authBasicUser, userToCache, this.CACHE_TTL).catch((e: Error) => this.logger.error(`${this.validate.name} - ${e}`))\n return userFromDB\n }\n}\n"],"names":["AuthBasicStrategy","PassportStrategy","BasicStrategy","validate","req","loginOrEmail","password","logger","assign","user","authBasicUser","CACHE_KEY_PREFIX","headers","split","at","toLowerCase","userFromCache","cache","get","undefined","plainToInstance","UserModel","userFromDB","authMethod","validateUser","ip","AUTH_SCOPE","WEBDAV","removePassword","userToCache","instanceToPlain","excludePrefixes","set","CACHE_TTL","catch","e","error","name","passReqToCallback","realm","SERVER_NAME"],"mappings":"AAAA;;;;CAIC;;;;+BAeYA;;;eAAAA;;;wBAbc;0BACwB;kCACF;4BAEtB;8BACG;8BACF;2BACF;8BACJ;uBACK;4BACA;;;;;;;;;;AAGpB,IAAA,AAAMA,oBAAN,MAAMA,0BAA0BC,IAAAA,0BAAgB,EAACC,2BAAa,EAAE;IAYrE,MAAMC,SAASC,GAAmB,EAAEC,YAAoB,EAAEC,QAAgB,EAA+C;
|
|
1
|
+
{"version":3,"sources":["../../../../backend/src/authentication/guards/auth-basic.strategy.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 { Injectable } from '@nestjs/common'\nimport { AbstractStrategy, PassportStrategy } from '@nestjs/passport'\nimport { instanceToPlain, plainToInstance } from 'class-transformer'\nimport { FastifyRequest } from 'fastify'\nimport { PinoLogger } from 'nestjs-pino'\nimport { BasicStrategy } from 'passport-http'\nimport { SERVER_NAME } from '../../app.constants'\nimport { UserModel } from '../../applications/users/models/user.model'\nimport { Cache } from '../../infrastructure/cache/services/cache.service'\nimport { AUTH_SCOPE } from '../constants/scope'\nimport { AuthMethod } from '../models/auth-method'\n\n@Injectable()\nexport class AuthBasicStrategy extends PassportStrategy(BasicStrategy, 'basic') implements AbstractStrategy {\n private readonly CACHE_TTL = 900\n private readonly CACHE_KEY_PREFIX = 'auth-webdav'\n\n constructor(\n private readonly authMethod: AuthMethod,\n private readonly cache: Cache,\n private readonly logger: PinoLogger\n ) {\n super({ passReqToCallback: true, realm: SERVER_NAME })\n }\n\n async validate(req: FastifyRequest, loginOrEmail: string, password: string): Promise<Omit<UserModel, 'password'> | null> {\n loginOrEmail = loginOrEmail.trim()\n password = password.trim()\n this.logger.assign({ user: loginOrEmail })\n const authBasicUser = `${this.CACHE_KEY_PREFIX}-${req.headers['authorization'].split(' ').at(-1).toLowerCase()}`\n const userFromCache: any = await this.cache.get(authBasicUser)\n if (userFromCache === null) {\n // not authorized\n return null\n }\n if (userFromCache !== undefined) {\n // cached\n // warning: plainToInstance do not use constructor to instantiate class\n return plainToInstance(UserModel, userFromCache)\n }\n const userFromDB: UserModel = await this.authMethod.validateUser(loginOrEmail, password, req.ip, AUTH_SCOPE.WEBDAV)\n if (userFromDB !== null) {\n userFromDB.removePassword()\n }\n const userToCache: Record<string, any> | null = userFromDB ? instanceToPlain(userFromDB, { excludePrefixes: ['_'] }) : null\n this.cache.set(authBasicUser, userToCache, this.CACHE_TTL).catch((e: Error) => this.logger.error(`${this.validate.name} - ${e}`))\n return userFromDB\n }\n}\n"],"names":["AuthBasicStrategy","PassportStrategy","BasicStrategy","validate","req","loginOrEmail","password","trim","logger","assign","user","authBasicUser","CACHE_KEY_PREFIX","headers","split","at","toLowerCase","userFromCache","cache","get","undefined","plainToInstance","UserModel","userFromDB","authMethod","validateUser","ip","AUTH_SCOPE","WEBDAV","removePassword","userToCache","instanceToPlain","excludePrefixes","set","CACHE_TTL","catch","e","error","name","passReqToCallback","realm","SERVER_NAME"],"mappings":"AAAA;;;;CAIC;;;;+BAeYA;;;eAAAA;;;wBAbc;0BACwB;kCACF;4BAEtB;8BACG;8BACF;2BACF;8BACJ;uBACK;4BACA;;;;;;;;;;AAGpB,IAAA,AAAMA,oBAAN,MAAMA,0BAA0BC,IAAAA,0BAAgB,EAACC,2BAAa,EAAE;IAYrE,MAAMC,SAASC,GAAmB,EAAEC,YAAoB,EAAEC,QAAgB,EAA+C;QACvHD,eAAeA,aAAaE,IAAI;QAChCD,WAAWA,SAASC,IAAI;QACxB,IAAI,CAACC,MAAM,CAACC,MAAM,CAAC;YAAEC,MAAML;QAAa;QACxC,MAAMM,gBAAgB,GAAG,IAAI,CAACC,gBAAgB,CAAC,CAAC,EAAER,IAAIS,OAAO,CAAC,gBAAgB,CAACC,KAAK,CAAC,KAAKC,EAAE,CAAC,CAAC,GAAGC,WAAW,IAAI;QAChH,MAAMC,gBAAqB,MAAM,IAAI,CAACC,KAAK,CAACC,GAAG,CAACR;QAChD,IAAIM,kBAAkB,MAAM;YAC1B,iBAAiB;YACjB,OAAO;QACT;QACA,IAAIA,kBAAkBG,WAAW;YAC/B,SAAS;YACT,uEAAuE;YACvE,OAAOC,IAAAA,iCAAe,EAACC,oBAAS,EAAEL;QACpC;QACA,MAAMM,aAAwB,MAAM,IAAI,CAACC,UAAU,CAACC,YAAY,CAACpB,cAAcC,UAAUF,IAAIsB,EAAE,EAAEC,iBAAU,CAACC,MAAM;QAClH,IAAIL,eAAe,MAAM;YACvBA,WAAWM,cAAc;QAC3B;QACA,MAAMC,cAA0CP,aAAaQ,IAAAA,iCAAe,EAACR,YAAY;YAAES,iBAAiB;gBAAC;aAAI;QAAC,KAAK;QACvH,IAAI,CAACd,KAAK,CAACe,GAAG,CAACtB,eAAemB,aAAa,IAAI,CAACI,SAAS,EAAEC,KAAK,CAAC,CAACC,IAAa,IAAI,CAAC5B,MAAM,CAAC6B,KAAK,CAAC,GAAG,IAAI,CAAClC,QAAQ,CAACmC,IAAI,CAAC,GAAG,EAAEF,GAAG;QAC/H,OAAOb;IACT;IA9BA,YACE,AAAiBC,UAAsB,EACvC,AAAiBN,KAAY,EAC7B,AAAiBV,MAAkB,CACnC;QACA,KAAK,CAAC;YAAE+B,mBAAmB;YAAMC,OAAOC,yBAAW;QAAC,SAJnCjB,aAAAA,iBACAN,QAAAA,YACAV,SAAAA,aANF0B,YAAY,UACZtB,mBAAmB;IAQpC;AAyBF"}
|
|
@@ -28,6 +28,8 @@ function _ts_metadata(k, v) {
|
|
|
28
28
|
}
|
|
29
29
|
let AuthLocalStrategy = class AuthLocalStrategy extends (0, _passport.PassportStrategy)(_passportlocal.Strategy, 'local') {
|
|
30
30
|
async validate(req, loginOrEmail, password) {
|
|
31
|
+
loginOrEmail = loginOrEmail.trim();
|
|
32
|
+
password = password.trim();
|
|
31
33
|
this.logger.assign({
|
|
32
34
|
user: loginOrEmail
|
|
33
35
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../backend/src/authentication/guards/auth-local.strategy.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 { Injectable, UnauthorizedException } from '@nestjs/common'\nimport {
|
|
1
|
+
{"version":3,"sources":["../../../../backend/src/authentication/guards/auth-local.strategy.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 { Injectable, UnauthorizedException } from '@nestjs/common'\nimport { AbstractStrategy, PassportStrategy } from '@nestjs/passport'\nimport type { FastifyRequest } from 'fastify'\nimport { PinoLogger } from 'nestjs-pino'\nimport { Strategy } from 'passport-local'\nimport type { UserModel } from '../../applications/users/models/user.model'\nimport { AuthMethod } from '../models/auth-method'\n\n@Injectable()\nexport class AuthLocalStrategy extends PassportStrategy(Strategy, 'local') implements AbstractStrategy {\n constructor(\n private readonly authMethod: AuthMethod,\n private readonly logger: PinoLogger\n ) {\n super({ usernameField: 'login', passwordField: 'password', passReqToCallback: true })\n }\n\n async validate(req: FastifyRequest, loginOrEmail: string, password: string): Promise<UserModel> {\n loginOrEmail = loginOrEmail.trim()\n password = password.trim()\n this.logger.assign({ user: loginOrEmail })\n const user: UserModel = await this.authMethod.validateUser(loginOrEmail, password, req.ip)\n if (user) {\n user.removePassword()\n return user\n }\n throw new UnauthorizedException('Wrong login or password')\n }\n}\n"],"names":["AuthLocalStrategy","PassportStrategy","Strategy","validate","req","loginOrEmail","password","trim","logger","assign","user","authMethod","validateUser","ip","removePassword","UnauthorizedException","usernameField","passwordField","passReqToCallback"],"mappings":"AAAA;;;;CAIC;;;;+BAWYA;;;eAAAA;;;wBATqC;0BACC;4BAExB;+BACF;4BAEE;;;;;;;;;;AAGpB,IAAA,AAAMA,oBAAN,MAAMA,0BAA0BC,IAAAA,0BAAgB,EAACC,uBAAQ,EAAE;IAQhE,MAAMC,SAASC,GAAmB,EAAEC,YAAoB,EAAEC,QAAgB,EAAsB;QAC9FD,eAAeA,aAAaE,IAAI;QAChCD,WAAWA,SAASC,IAAI;QACxB,IAAI,CAACC,MAAM,CAACC,MAAM,CAAC;YAAEC,MAAML;QAAa;QACxC,MAAMK,OAAkB,MAAM,IAAI,CAACC,UAAU,CAACC,YAAY,CAACP,cAAcC,UAAUF,IAAIS,EAAE;QACzF,IAAIH,MAAM;YACRA,KAAKI,cAAc;YACnB,OAAOJ;QACT;QACA,MAAM,IAAIK,6BAAqB,CAAC;IAClC;IAjBA,YACE,AAAiBJ,UAAsB,EACvC,AAAiBH,MAAkB,CACnC;QACA,KAAK,CAAC;YAAEQ,eAAe;YAASC,eAAe;YAAYC,mBAAmB;QAAK,SAHlEP,aAAAA,iBACAH,SAAAA;IAGnB;AAaF"}
|
|
@@ -20,6 +20,7 @@ const _adminusersmanagerservice = require("../../../applications/users/services/
|
|
|
20
20
|
const _usersmanagerservice = require("../../../applications/users/services/users-manager.service");
|
|
21
21
|
const _functions = require("../../../common/functions");
|
|
22
22
|
const _configenvironment = require("../../../configuration/config.environment");
|
|
23
|
+
const _authldap = require("../../constants/auth-ldap");
|
|
23
24
|
function _ts_decorate(decorators, target, key, desc) {
|
|
24
25
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
25
26
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
@@ -29,31 +30,9 @@ function _ts_decorate(decorators, target, key, desc) {
|
|
|
29
30
|
function _ts_metadata(k, v) {
|
|
30
31
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
31
32
|
}
|
|
32
|
-
const LDAP_ATTRIBUTES = {
|
|
33
|
-
AD: {
|
|
34
|
-
SAM_ACCOUNT: 'sAMAccountName',
|
|
35
|
-
USER_NAME: 'userPrincipalName'
|
|
36
|
-
},
|
|
37
|
-
LDAP: {
|
|
38
|
-
UID: 'uid'
|
|
39
|
-
},
|
|
40
|
-
COMMON: {
|
|
41
|
-
MAIL: 'mail',
|
|
42
|
-
GIVEN_NAME: 'givenName',
|
|
43
|
-
SN: 'sn',
|
|
44
|
-
CN: 'cn',
|
|
45
|
-
DISPLAY_NAME: 'displayName'
|
|
46
|
-
}
|
|
47
|
-
};
|
|
48
|
-
const ALL_ATTRIBUTES = [
|
|
49
|
-
...Object.values(LDAP_ATTRIBUTES.COMMON),
|
|
50
|
-
...Object.values(LDAP_ATTRIBUTES.LDAP),
|
|
51
|
-
...Object.values(LDAP_ATTRIBUTES.AD)
|
|
52
|
-
];
|
|
53
33
|
let AuthMethodLdapService = class AuthMethodLdapService {
|
|
54
34
|
async validateUser(login, password, ip, scope) {
|
|
55
|
-
|
|
56
|
-
let user = await this.usersManager.findUser(login, false);
|
|
35
|
+
let user = await this.usersManager.findUser(this.dbLogin(login), false);
|
|
57
36
|
if (user) {
|
|
58
37
|
if (user.isGuest) {
|
|
59
38
|
// allow guests to be authenticated from db and check if the current user is defined as active
|
|
@@ -80,9 +59,9 @@ let AuthMethodLdapService = class AuthMethodLdapService {
|
|
|
80
59
|
}
|
|
81
60
|
}
|
|
82
61
|
return null;
|
|
83
|
-
} else if (!entry[
|
|
62
|
+
} else if (!entry[this.ldapConfig.attributes.login] || !entry[this.ldapConfig.attributes.email]) {
|
|
84
63
|
this.logger.error(`${this.validateUser.name} - required ldap fields are missing :
|
|
85
|
-
[${
|
|
64
|
+
[${this.ldapConfig.attributes.login}, ${this.ldapConfig.attributes.email}] =>
|
|
86
65
|
(${JSON.stringify(entry)})`);
|
|
87
66
|
return null;
|
|
88
67
|
}
|
|
@@ -91,30 +70,31 @@ let AuthMethodLdapService = class AuthMethodLdapService {
|
|
|
91
70
|
this.usersManager.updateAccesses(user, ip, true).catch((e)=>this.logger.error(`${this.validateUser.name} : ${e}`));
|
|
92
71
|
return user;
|
|
93
72
|
}
|
|
94
|
-
async checkAuth(
|
|
95
|
-
const
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
73
|
+
async checkAuth(login, password) {
|
|
74
|
+
const ldapLogin = this.buildLdapLogin(login);
|
|
75
|
+
const isAD = this.ldapConfig.attributes.login === _authldap.LDAP_LOGIN_ATTR.SAM || this.ldapConfig.attributes.login === _authldap.LDAP_LOGIN_ATTR.UPN;
|
|
76
|
+
// AD: bind directly with the user input (UPN or DOMAIN\user)
|
|
77
|
+
// Generic LDAP: build DN from login attribute + baseDN
|
|
78
|
+
const bindUserDN = isAD ? ldapLogin : `${this.ldapConfig.attributes.login}=${ldapLogin},${this.ldapConfig.baseDN}`;
|
|
99
79
|
let client;
|
|
100
80
|
let error;
|
|
101
|
-
for (const s of servers){
|
|
81
|
+
for (const s of this.ldapConfig.servers){
|
|
102
82
|
client = new _ldapts.Client({
|
|
103
83
|
...this.clientOptions,
|
|
104
84
|
url: s
|
|
105
85
|
});
|
|
106
86
|
try {
|
|
107
87
|
await client.bind(bindUserDN, password);
|
|
108
|
-
return await this.checkAccess(
|
|
88
|
+
return await this.checkAccess(ldapLogin, client);
|
|
109
89
|
} catch (e) {
|
|
110
90
|
if (e.errors?.length) {
|
|
111
91
|
for (const err of e.errors){
|
|
112
|
-
this.logger.warn(`${this.checkAuth.name} - ${
|
|
92
|
+
this.logger.warn(`${this.checkAuth.name} - ${ldapLogin} : ${err}`);
|
|
113
93
|
error = err;
|
|
114
94
|
}
|
|
115
95
|
} else {
|
|
116
96
|
error = e;
|
|
117
|
-
this.logger.warn(`${this.checkAuth.name} - ${
|
|
97
|
+
this.logger.warn(`${this.checkAuth.name} - ${ldapLogin} : ${e}`);
|
|
118
98
|
}
|
|
119
99
|
if (error instanceof _ldapts.InvalidCredentialsError) {
|
|
120
100
|
return false;
|
|
@@ -128,80 +108,106 @@ let AuthMethodLdapService = class AuthMethodLdapService {
|
|
|
128
108
|
}
|
|
129
109
|
return false;
|
|
130
110
|
}
|
|
131
|
-
async checkAccess(
|
|
132
|
-
const searchFilter =
|
|
111
|
+
async checkAccess(login, client) {
|
|
112
|
+
const searchFilter = this.buildUserFilter(login, this.ldapConfig.filter);
|
|
133
113
|
try {
|
|
134
|
-
const { searchEntries } = await client.search(
|
|
114
|
+
const { searchEntries } = await client.search(this.ldapConfig.baseDN, {
|
|
135
115
|
scope: 'sub',
|
|
136
116
|
filter: searchFilter,
|
|
137
|
-
attributes:
|
|
117
|
+
attributes: _authldap.ALL_LDAP_ATTRIBUTES
|
|
138
118
|
});
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
119
|
+
if (searchEntries.length === 0) {
|
|
120
|
+
this.logger.debug(`${this.checkAccess.name} - search filter : ${searchFilter}`);
|
|
121
|
+
this.logger.warn(`${this.checkAccess.name} - no LDAP entry found for : ${login}`);
|
|
122
|
+
return false;
|
|
143
123
|
}
|
|
144
|
-
|
|
145
|
-
|
|
124
|
+
if (searchEntries.length > 1) {
|
|
125
|
+
this.logger.warn(`${this.checkAccess.name} - multiple LDAP entries found for : ${login}, using first one`);
|
|
126
|
+
}
|
|
127
|
+
// Always return the first valid entry
|
|
128
|
+
return this.convertToLdapUserEntry(searchEntries[0]);
|
|
146
129
|
} catch (e) {
|
|
147
|
-
this.logger.
|
|
130
|
+
this.logger.debug(`${this.checkAccess.name} - search filter : ${searchFilter}`);
|
|
131
|
+
this.logger.error(`${this.checkAccess.name} - ${login} : ${e}`);
|
|
148
132
|
return false;
|
|
149
133
|
}
|
|
150
134
|
}
|
|
151
135
|
async updateOrCreateUser(identity, user) {
|
|
152
136
|
if (user === null) {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
137
|
+
// create
|
|
138
|
+
const createdUser = await this.adminUsersManager.createUserOrGuest(identity, identity.role);
|
|
139
|
+
const freshUser = await this.usersManager.fromUserId(createdUser.id);
|
|
140
|
+
if (!freshUser) {
|
|
141
|
+
this.logger.error(`${this.updateOrCreateUser.name} - user was not found : ${createdUser.login} (${createdUser.id})`);
|
|
142
|
+
throw new _common.HttpException('User not found', _common.HttpStatus.NOT_FOUND);
|
|
158
143
|
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
144
|
+
return freshUser;
|
|
145
|
+
}
|
|
146
|
+
if (identity.login !== user.login) {
|
|
147
|
+
this.logger.error(`${this.updateOrCreateUser.name} - user login mismatch : ${identity.login} !== ${user.login}`);
|
|
148
|
+
throw new _common.HttpException('Account matching error', _common.HttpStatus.FORBIDDEN);
|
|
149
|
+
}
|
|
150
|
+
// update: check if user information has changed
|
|
151
|
+
const identityHasChanged = Object.fromEntries((await Promise.all(Object.keys(identity).map(async (key)=>{
|
|
152
|
+
if (key === 'password') {
|
|
153
|
+
const isSame = await (0, _functions.comparePassword)(identity[key], user.password);
|
|
154
|
+
return isSame ? null : [
|
|
169
155
|
key,
|
|
170
156
|
identity[key]
|
|
171
|
-
]
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
157
|
+
];
|
|
158
|
+
}
|
|
159
|
+
return identity[key] !== user[key] ? [
|
|
160
|
+
key,
|
|
161
|
+
identity[key]
|
|
162
|
+
] : null;
|
|
163
|
+
}))).filter(Boolean));
|
|
164
|
+
if (Object.keys(identityHasChanged).length > 0) {
|
|
165
|
+
try {
|
|
166
|
+
if (identityHasChanged?.role != null) {
|
|
167
|
+
if (user.role === _user.USER_ROLE.ADMINISTRATOR && !this.ldapConfig.adminGroup) {
|
|
168
|
+
// Prevent removing admin role when adminGroup was removed or not defined
|
|
169
|
+
delete identityHasChanged.role;
|
|
183
170
|
}
|
|
184
|
-
} catch (e) {
|
|
185
|
-
this.logger.warn(`${this.updateOrCreateUser.name} - unable to update user *${user.login}* : ${e}`);
|
|
186
171
|
}
|
|
172
|
+
// Update user properties
|
|
173
|
+
await this.adminUsersManager.updateUserOrGuest(user.id, identityHasChanged);
|
|
174
|
+
// Extra stuff
|
|
175
|
+
if (identityHasChanged?.password) {
|
|
176
|
+
delete identityHasChanged.password;
|
|
177
|
+
}
|
|
178
|
+
Object.assign(user, identityHasChanged);
|
|
179
|
+
if ('lastName' in identityHasChanged || 'firstName' in identityHasChanged) {
|
|
180
|
+
// force fullName update in current user model
|
|
181
|
+
user.setFullName(true);
|
|
182
|
+
}
|
|
183
|
+
} catch (e) {
|
|
184
|
+
this.logger.warn(`${this.updateOrCreateUser.name} - unable to update user *${user.login}* : ${e}`);
|
|
187
185
|
}
|
|
188
|
-
await user.makePaths();
|
|
189
|
-
return user;
|
|
190
186
|
}
|
|
187
|
+
return user;
|
|
191
188
|
}
|
|
192
189
|
convertToLdapUserEntry(entry) {
|
|
193
|
-
for (const attr of
|
|
190
|
+
for (const attr of _authldap.ALL_LDAP_ATTRIBUTES){
|
|
191
|
+
if (attr === _authldap.LDAP_COMMON_ATTR.MEMBER_OF && entry[attr]) {
|
|
192
|
+
entry[attr] = (Array.isArray(entry[attr]) ? entry[attr] : entry[attr] ? [
|
|
193
|
+
entry[attr]
|
|
194
|
+
] : []).filter((v)=>typeof v === 'string').map((v)=>v.match(/cn\s*=\s*([^,]+)/i)?.[1]?.trim()).filter(Boolean);
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
194
197
|
if (Array.isArray(entry[attr])) {
|
|
198
|
+
// Keep only the first value for all other attributes (e.g., email)
|
|
195
199
|
entry[attr] = entry[attr].length > 0 ? entry[attr][0] : null;
|
|
196
200
|
}
|
|
197
201
|
}
|
|
198
202
|
return entry;
|
|
199
203
|
}
|
|
200
204
|
createIdentity(entry, password) {
|
|
205
|
+
const isAdmin = typeof this.ldapConfig.adminGroup === 'string' && this.ldapConfig.adminGroup && entry[_authldap.LDAP_COMMON_ATTR.MEMBER_OF]?.includes(this.ldapConfig.adminGroup);
|
|
201
206
|
return {
|
|
202
|
-
login: this.
|
|
203
|
-
email: entry[
|
|
207
|
+
login: this.dbLogin(entry[this.ldapConfig.attributes.login]),
|
|
208
|
+
email: entry[this.ldapConfig.attributes.email],
|
|
204
209
|
password: password,
|
|
210
|
+
role: isAdmin ? _user.USER_ROLE.ADMINISTRATOR : _user.USER_ROLE.USER,
|
|
205
211
|
...this.getFirstNameAndLastName(entry)
|
|
206
212
|
};
|
|
207
213
|
}
|
|
@@ -227,18 +233,67 @@ let AuthMethodLdapService = class AuthMethodLdapService {
|
|
|
227
233
|
lastName: ''
|
|
228
234
|
};
|
|
229
235
|
}
|
|
230
|
-
|
|
231
|
-
if (
|
|
236
|
+
dbLogin(login) {
|
|
237
|
+
if (login.includes('@')) {
|
|
232
238
|
return login.split('@')[0];
|
|
233
|
-
} else if (
|
|
234
|
-
return login.split('\\')[0];
|
|
239
|
+
} else if (login.includes('\\')) {
|
|
240
|
+
return login.split('\\').slice(-1)[0];
|
|
235
241
|
}
|
|
236
242
|
return login;
|
|
237
243
|
}
|
|
244
|
+
buildLdapLogin(login) {
|
|
245
|
+
if (this.ldapConfig.attributes.login === _authldap.LDAP_LOGIN_ATTR.UPN) {
|
|
246
|
+
if (this.ldapConfig.upnSuffix && !login.includes('@')) {
|
|
247
|
+
return `${login}@${this.ldapConfig.upnSuffix}`;
|
|
248
|
+
}
|
|
249
|
+
} else if (this.ldapConfig.attributes.login === _authldap.LDAP_LOGIN_ATTR.SAM) {
|
|
250
|
+
if (this.ldapConfig.netbiosName && !login.includes('\\')) {
|
|
251
|
+
return `${this.ldapConfig.netbiosName}\\${login}`;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
return login;
|
|
255
|
+
}
|
|
256
|
+
buildUserFilter(login, extraFilter) {
|
|
257
|
+
// Build a safe LDAP filter to search for a user.
|
|
258
|
+
// Important: - Values passed to EqualityFilter are auto-escaped by ldapts
|
|
259
|
+
// - extraFilter is appended as-is (assumed trusted configuration)
|
|
260
|
+
// Output: (&(|(userPrincipalName=john.doe@sync-in.com)(sAMAccountName=john.doe)(uid=john.doe))(*extraFilter*))
|
|
261
|
+
// Handle the case where the sAMAccountName is provided in domain-qualified format (e.g., SYNC_IN\\user)
|
|
262
|
+
// Note: sAMAccountName is always stored without the domain in Active Directory.
|
|
263
|
+
const uid = this.dbLogin(login);
|
|
264
|
+
const or = new _ldapts.OrFilter({
|
|
265
|
+
filters: [
|
|
266
|
+
new _ldapts.EqualityFilter({
|
|
267
|
+
attribute: _authldap.LDAP_LOGIN_ATTR.UPN,
|
|
268
|
+
value: login
|
|
269
|
+
}),
|
|
270
|
+
new _ldapts.EqualityFilter({
|
|
271
|
+
attribute: _authldap.LDAP_LOGIN_ATTR.SAM,
|
|
272
|
+
value: uid
|
|
273
|
+
}),
|
|
274
|
+
new _ldapts.EqualityFilter({
|
|
275
|
+
attribute: _authldap.LDAP_LOGIN_ATTR.UID,
|
|
276
|
+
value: uid
|
|
277
|
+
})
|
|
278
|
+
]
|
|
279
|
+
});
|
|
280
|
+
// Convert to LDAP filter string
|
|
281
|
+
let filterString = new _ldapts.AndFilter({
|
|
282
|
+
filters: [
|
|
283
|
+
or
|
|
284
|
+
]
|
|
285
|
+
}).toString();
|
|
286
|
+
// Optionally append an extra filter from config (trusted source)
|
|
287
|
+
if (extraFilter && extraFilter.trim()) {
|
|
288
|
+
filterString = `(&${filterString}${extraFilter})`;
|
|
289
|
+
}
|
|
290
|
+
return filterString;
|
|
291
|
+
}
|
|
238
292
|
constructor(usersManager, adminUsersManager){
|
|
239
293
|
this.usersManager = usersManager;
|
|
240
294
|
this.adminUsersManager = adminUsersManager;
|
|
241
295
|
this.logger = new _common.Logger(AuthMethodLdapService.name);
|
|
296
|
+
this.ldapConfig = _configenvironment.configuration.auth.ldap;
|
|
242
297
|
this.clientOptions = {
|
|
243
298
|
timeout: 6000,
|
|
244
299
|
connectTimeout: 6000,
|