mirror of
https://github.com/stonith404/pingvin-share.git
synced 2024-11-15 11:50:34 +01:00
feat(ldap): Adding support for LDAP authentication (#554)
This commit is contained in:
parent
4924f76394
commit
4186a768b3
204
backend/package-lock.json
generated
204
backend/package-lock.json
generated
@ -20,6 +20,7 @@
|
|||||||
"@nestjs/throttler": "^5.2.0",
|
"@nestjs/throttler": "^5.2.0",
|
||||||
"@prisma/client": "^5.16.1",
|
"@prisma/client": "^5.16.1",
|
||||||
"@types/jmespath": "^0.15.2",
|
"@types/jmespath": "^0.15.2",
|
||||||
|
"@types/ldapjs": "^3.0.6",
|
||||||
"archiver": "^7.0.1",
|
"archiver": "^7.0.1",
|
||||||
"argon2": "^0.40.3",
|
"argon2": "^0.40.3",
|
||||||
"body-parser": "^1.20.2",
|
"body-parser": "^1.20.2",
|
||||||
@ -30,6 +31,7 @@
|
|||||||
"content-disposition": "^0.5.4",
|
"content-disposition": "^0.5.4",
|
||||||
"cookie-parser": "^1.4.6",
|
"cookie-parser": "^1.4.6",
|
||||||
"jmespath": "^0.16.0",
|
"jmespath": "^0.16.0",
|
||||||
|
"ldapjs": "^3.0.7",
|
||||||
"mime-types": "^2.1.35",
|
"mime-types": "^2.1.35",
|
||||||
"moment": "^2.30.1",
|
"moment": "^2.30.1",
|
||||||
"nanoid": "^3.3.7",
|
"nanoid": "^3.3.7",
|
||||||
@ -1126,6 +1128,101 @@
|
|||||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@ldapjs/asn1": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@ldapjs/asn1/-/asn1-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-G9+DkEOirNgdPmD0I8nu57ygQJKOOgFEMKknEuQvIHbGLwP3ny1mY+OTUYLCbCaGJP4sox5eYgBJRuSUpnAddA==",
|
||||||
|
"deprecated": "This package has been decomissioned. See https://github.com/ldapjs/node-ldapjs/blob/8ffd0bc9c149088a10ec4c1ec6a18450f76ad05d/README.md",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@ldapjs/attribute": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@ldapjs/attribute/-/attribute-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-ptMl2d/5xJ0q+RgmnqOi3Zgwk/TMJYG7dYMC0Keko+yZU6n+oFM59MjQOUht5pxJeS4FWrImhu/LebX24vJNRQ==",
|
||||||
|
"deprecated": "This package has been decomissioned. See https://github.com/ldapjs/node-ldapjs/blob/8ffd0bc9c149088a10ec4c1ec6a18450f76ad05d/README.md",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@ldapjs/asn1": "2.0.0",
|
||||||
|
"@ldapjs/protocol": "^1.2.1",
|
||||||
|
"process-warning": "^2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@ldapjs/change": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@ldapjs/change/-/change-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-EOQNFH1RIku3M1s0OAJOzGfAohuFYXFY4s73wOhRm4KFGhmQQ7MChOh2YtYu9Kwgvuq1B0xKciXVzHCGkB5V+Q==",
|
||||||
|
"deprecated": "This package has been decomissioned. See https://github.com/ldapjs/node-ldapjs/blob/8ffd0bc9c149088a10ec4c1ec6a18450f76ad05d/README.md",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@ldapjs/asn1": "2.0.0",
|
||||||
|
"@ldapjs/attribute": "1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@ldapjs/controls": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@ldapjs/controls/-/controls-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-2pFdD1yRC9V9hXfAWvCCO2RRWK9OdIEcJIos/9cCVP9O4k72BY1bLDQQ4KpUoJnl4y/JoD4iFgM+YWT3IfITWw==",
|
||||||
|
"deprecated": "This package has been decomissioned. See https://github.com/ldapjs/node-ldapjs/blob/8ffd0bc9c149088a10ec4c1ec6a18450f76ad05d/README.md",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@ldapjs/asn1": "^1.2.0",
|
||||||
|
"@ldapjs/protocol": "^1.2.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@ldapjs/controls/node_modules/@ldapjs/asn1": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@ldapjs/asn1/-/asn1-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-KX/qQJ2xxzvO2/WOvr1UdQ+8P5dVvuOLk/C9b1bIkXxZss8BaR28njXdPgFCpj5aHaf1t8PmuVnea+N9YG9YMw==",
|
||||||
|
"deprecated": "This package has been decomissioned. See https://github.com/ldapjs/node-ldapjs/blob/8ffd0bc9c149088a10ec4c1ec6a18450f76ad05d/README.md",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@ldapjs/dn": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@ldapjs/dn/-/dn-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-R72zH5ZeBj/Fujf/yBu78YzpJjJXG46YHFo5E4W1EqfNpo1UsVPqdLrRMXeKIsJT3x9dJVIfR6OpzgINlKpi0A==",
|
||||||
|
"deprecated": "This package has been decomissioned. See https://github.com/ldapjs/node-ldapjs/blob/8ffd0bc9c149088a10ec4c1ec6a18450f76ad05d/README.md",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@ldapjs/asn1": "2.0.0",
|
||||||
|
"process-warning": "^2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@ldapjs/filter": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@ldapjs/filter/-/filter-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-TwPK5eEgNdUO1ABPBUQabcZ+h9heDORE4V9WNZqCtYLKc06+6+UAJ3IAbr0L0bYTnkkWC/JEQD2F+zAFsuikNw==",
|
||||||
|
"deprecated": "This package has been decomissioned. See https://github.com/ldapjs/node-ldapjs/blob/8ffd0bc9c149088a10ec4c1ec6a18450f76ad05d/README.md",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@ldapjs/asn1": "2.0.0",
|
||||||
|
"@ldapjs/protocol": "^1.2.1",
|
||||||
|
"process-warning": "^2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@ldapjs/messages": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@ldapjs/messages/-/messages-1.3.0.tgz",
|
||||||
|
"integrity": "sha512-K7xZpXJ21bj92jS35wtRbdcNrwmxAtPwy4myeh9duy/eR3xQKvikVycbdWVzkYEAVE5Ce520VXNOwCHjomjCZw==",
|
||||||
|
"deprecated": "This package has been decomissioned. See https://github.com/ldapjs/node-ldapjs/blob/8ffd0bc9c149088a10ec4c1ec6a18450f76ad05d/README.md",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@ldapjs/asn1": "^2.0.0",
|
||||||
|
"@ldapjs/attribute": "^1.0.0",
|
||||||
|
"@ldapjs/change": "^1.0.0",
|
||||||
|
"@ldapjs/controls": "^2.1.0",
|
||||||
|
"@ldapjs/dn": "^1.1.0",
|
||||||
|
"@ldapjs/filter": "^2.1.1",
|
||||||
|
"@ldapjs/protocol": "^1.2.1",
|
||||||
|
"process-warning": "^2.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@ldapjs/protocol": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@ldapjs/protocol/-/protocol-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-O89xFDLW2gBoZWNXuXpBSM32/KealKCTb3JGtJdtUQc7RjAk8XzrRgyz02cPAwGKwKPxy0ivuC7UP9bmN87egQ==",
|
||||||
|
"deprecated": "This package has been decomissioned. See https://github.com/ldapjs/node-ldapjs/blob/8ffd0bc9c149088a10ec4c1ec6a18450f76ad05d/README.md",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@ljharb/through": {
|
"node_modules/@ljharb/through": {
|
||||||
"version": "2.3.13",
|
"version": "2.3.13",
|
||||||
"resolved": "https://registry.npmjs.org/@ljharb/through/-/through-2.3.13.tgz",
|
"resolved": "https://registry.npmjs.org/@ljharb/through/-/through-2.3.13.tgz",
|
||||||
@ -2016,6 +2113,15 @@
|
|||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/ldapjs": {
|
||||||
|
"version": "3.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/ldapjs/-/ldapjs-3.0.6.tgz",
|
||||||
|
"integrity": "sha512-E2Tn1ltJDYBsidOT9QG4engaQeQzRQ9aYNxVmjCkD33F7cIeLPgrRDXAYs0O35mK2YDU20c/+ZkNjeAPRGLM0Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/luxon": {
|
"node_modules/@types/luxon": {
|
||||||
"version": "3.4.2",
|
"version": "3.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.4.2.tgz",
|
||||||
@ -2550,6 +2656,12 @@
|
|||||||
"node": ">=6.5"
|
"node": ">=6.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/abstract-logging": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/abstract-logging/-/abstract-logging-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/accepts": {
|
"node_modules/accepts": {
|
||||||
"version": "1.3.8",
|
"version": "1.3.8",
|
||||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
|
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
|
||||||
@ -2952,7 +3064,6 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
|
||||||
"integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==",
|
"integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==",
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.8"
|
"node": ">=0.8"
|
||||||
}
|
}
|
||||||
@ -2988,6 +3099,18 @@
|
|||||||
"resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.6.tgz",
|
"resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.6.tgz",
|
||||||
"integrity": "sha512-5Tk1HLk6b6ctmjIkAcU/Ujv/1WqiDl0F0JdRCR80VsOcUlHcu7pWeWRlOqQLHfDEsVx9YH/aif5AG4ehoCtTmg=="
|
"integrity": "sha512-5Tk1HLk6b6ctmjIkAcU/Ujv/1WqiDl0F0JdRCR80VsOcUlHcu7pWeWRlOqQLHfDEsVx9YH/aif5AG4ehoCtTmg=="
|
||||||
},
|
},
|
||||||
|
"node_modules/backoff": {
|
||||||
|
"version": "2.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/backoff/-/backoff-2.5.0.tgz",
|
||||||
|
"integrity": "sha512-wC5ihrnUXmR2douXmXLCe5O3zg3GKIyvRi/hi58a/XyRxVI+3/yM0PYueQOZXPXQ9pxBislYkw+sF9b7C/RuMA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"precond": "0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/balanced-match": {
|
"node_modules/balanced-match": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||||
@ -4514,7 +4637,6 @@
|
|||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
|
||||||
"integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==",
|
"integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==",
|
||||||
"dev": true,
|
|
||||||
"engines": [
|
"engines": [
|
||||||
"node >=0.6.0"
|
"node >=0.6.0"
|
||||||
]
|
]
|
||||||
@ -5723,6 +5845,49 @@
|
|||||||
"node": ">= 0.6.3"
|
"node": ">= 0.6.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/ldapjs": {
|
||||||
|
"version": "3.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/ldapjs/-/ldapjs-3.0.7.tgz",
|
||||||
|
"integrity": "sha512-1ky+WrN+4CFMuoekUOv7Y1037XWdjKpu0xAPwSP+9KdvmV9PG+qOKlssDV6a+U32apwxdD3is/BZcWOYzN30cg==",
|
||||||
|
"deprecated": "This package has been decomissioned. See https://github.com/ldapjs/node-ldapjs/blob/8ffd0bc9c149088a10ec4c1ec6a18450f76ad05d/README.md",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@ldapjs/asn1": "^2.0.0",
|
||||||
|
"@ldapjs/attribute": "^1.0.0",
|
||||||
|
"@ldapjs/change": "^1.0.0",
|
||||||
|
"@ldapjs/controls": "^2.1.0",
|
||||||
|
"@ldapjs/dn": "^1.1.0",
|
||||||
|
"@ldapjs/filter": "^2.1.1",
|
||||||
|
"@ldapjs/messages": "^1.3.0",
|
||||||
|
"@ldapjs/protocol": "^1.2.1",
|
||||||
|
"abstract-logging": "^2.0.1",
|
||||||
|
"assert-plus": "^1.0.0",
|
||||||
|
"backoff": "^2.5.0",
|
||||||
|
"once": "^1.4.0",
|
||||||
|
"vasync": "^2.2.1",
|
||||||
|
"verror": "^1.10.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/ldapjs/node_modules/core-util-is": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/ldapjs/node_modules/verror": {
|
||||||
|
"version": "1.10.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/verror/-/verror-1.10.1.tgz",
|
||||||
|
"integrity": "sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"assert-plus": "^1.0.0",
|
||||||
|
"core-util-is": "1.0.2",
|
||||||
|
"extsprintf": "^1.2.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.6.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/levn": {
|
"node_modules/levn": {
|
||||||
"version": "0.4.1",
|
"version": "0.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
|
||||||
@ -6293,7 +6458,6 @@
|
|||||||
"version": "1.4.0",
|
"version": "1.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||||
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
|
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"wrappy": "1"
|
"wrappy": "1"
|
||||||
}
|
}
|
||||||
@ -6830,6 +6994,14 @@
|
|||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/precond": {
|
||||||
|
"version": "0.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/precond/-/precond-0.2.3.tgz",
|
||||||
|
"integrity": "sha512-QCYG84SgGyGzqJ/vlMsxeXd/pgL/I94ixdNFyh1PusWmTCyVfPJjZ1K1jvHtsbfnXQs2TSkEP2fR7QiMZAnKFQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/prelude-ls": {
|
"node_modules/prelude-ls": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
|
||||||
@ -6910,6 +7082,12 @@
|
|||||||
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
|
||||||
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
|
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
|
||||||
},
|
},
|
||||||
|
"node_modules/process-warning": {
|
||||||
|
"version": "2.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/process-warning/-/process-warning-2.3.2.tgz",
|
||||||
|
"integrity": "sha512-n9wh8tvBe5sFmsqlg+XQhaQLumwpqoAUruLwjCopgTmUBjJ/fjtBsJzKleCaIGBOMXYEhp1YfKl4d7rJ5ZKJGA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/promise-coalesce": {
|
"node_modules/promise-coalesce": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/promise-coalesce/-/promise-coalesce-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/promise-coalesce/-/promise-coalesce-1.1.2.tgz",
|
||||||
@ -7478,6 +7656,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.4.tgz",
|
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.4.tgz",
|
||||||
"integrity": "sha512-7i/dt5kGl7qR4gwPRD2biwD2/SvBn3O04J77XKFgL2OnZtQw+AG9wnuS/csmu80nPRHLYE9E41fyEiG8nhH6/Q==",
|
"integrity": "sha512-7i/dt5kGl7qR4gwPRD2biwD2/SvBn3O04J77XKFgL2OnZtQw+AG9wnuS/csmu80nPRHLYE9E41fyEiG8nhH6/Q==",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"color": "^4.2.3",
|
"color": "^4.2.3",
|
||||||
"detect-libc": "^2.0.3",
|
"detect-libc": "^2.0.3",
|
||||||
@ -8314,11 +8493,22 @@
|
|||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/vasync": {
|
||||||
|
"version": "2.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/vasync/-/vasync-2.2.1.tgz",
|
||||||
|
"integrity": "sha512-Hq72JaTpcTFdWiNA4Y22Amej2GH3BFmBaKPPlDZ4/oC8HNn2ISHLkFrJU4Ds8R3jcUi7oo5Y9jcMHKjES+N9wQ==",
|
||||||
|
"engines": [
|
||||||
|
"node >=0.6.0"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"verror": "1.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/verror": {
|
"node_modules/verror": {
|
||||||
"version": "1.10.0",
|
"version": "1.10.0",
|
||||||
"resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
|
"resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
|
||||||
"integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==",
|
"integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==",
|
||||||
"dev": true,
|
|
||||||
"engines": [
|
"engines": [
|
||||||
"node >=0.6.0"
|
"node >=0.6.0"
|
||||||
],
|
],
|
||||||
@ -8331,8 +8521,7 @@
|
|||||||
"node_modules/verror/node_modules/core-util-is": {
|
"node_modules/verror/node_modules/core-util-is": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
|
||||||
"integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==",
|
"integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"node_modules/wait-on": {
|
"node_modules/wait-on": {
|
||||||
"version": "7.2.0",
|
"version": "7.2.0",
|
||||||
@ -8528,8 +8717,7 @@
|
|||||||
"node_modules/wrappy": {
|
"node_modules/wrappy": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"node_modules/xmlbuilder": {
|
"node_modules/xmlbuilder": {
|
||||||
"version": "15.1.1",
|
"version": "15.1.1",
|
||||||
|
@ -25,6 +25,7 @@
|
|||||||
"@nestjs/throttler": "^5.2.0",
|
"@nestjs/throttler": "^5.2.0",
|
||||||
"@prisma/client": "^5.16.1",
|
"@prisma/client": "^5.16.1",
|
||||||
"@types/jmespath": "^0.15.2",
|
"@types/jmespath": "^0.15.2",
|
||||||
|
"@types/ldapjs": "^3.0.6",
|
||||||
"archiver": "^7.0.1",
|
"archiver": "^7.0.1",
|
||||||
"argon2": "^0.40.3",
|
"argon2": "^0.40.3",
|
||||||
"body-parser": "^1.20.2",
|
"body-parser": "^1.20.2",
|
||||||
@ -35,6 +36,7 @@
|
|||||||
"content-disposition": "^0.5.4",
|
"content-disposition": "^0.5.4",
|
||||||
"cookie-parser": "^1.4.6",
|
"cookie-parser": "^1.4.6",
|
||||||
"jmespath": "^0.16.0",
|
"jmespath": "^0.16.0",
|
||||||
|
"ldapjs": "^3.0.7",
|
||||||
"mime-types": "^2.1.35",
|
"mime-types": "^2.1.35",
|
||||||
"moment": "^2.30.1",
|
"moment": "^2.30.1",
|
||||||
"nanoid": "^3.3.7",
|
"nanoid": "^3.3.7",
|
||||||
|
@ -0,0 +1,11 @@
|
|||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- A unique constraint covering the columns `[ldapDN]` on the table `User` will be added. If there are existing duplicate values, this will fail.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "User" ADD COLUMN "ldapDN" TEXT;
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "User_ldapDN_key" ON "User"("ldapDN");
|
@ -16,6 +16,7 @@ model User {
|
|||||||
email String @unique
|
email String @unique
|
||||||
password String?
|
password String?
|
||||||
isAdmin Boolean @default(false)
|
isAdmin Boolean @default(false)
|
||||||
|
ldapDN String? @unique
|
||||||
|
|
||||||
shares Share[]
|
shares Share[]
|
||||||
refreshTokens RefreshToken[]
|
refreshTokens RefreshToken[]
|
||||||
|
@ -144,6 +144,42 @@ const configVariables: ConfigVariables = {
|
|||||||
obscured: true,
|
obscured: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
ldap: {
|
||||||
|
enabled: {
|
||||||
|
type: "boolean",
|
||||||
|
defaultValue: "false",
|
||||||
|
secret: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
url: {
|
||||||
|
type: "string",
|
||||||
|
defaultValue: "",
|
||||||
|
},
|
||||||
|
|
||||||
|
bindDn: {
|
||||||
|
type: "string",
|
||||||
|
defaultValue: "",
|
||||||
|
},
|
||||||
|
bindPassword: {
|
||||||
|
type: "string",
|
||||||
|
defaultValue: "",
|
||||||
|
obscured: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
searchBase: {
|
||||||
|
type: "string",
|
||||||
|
defaultValue: "",
|
||||||
|
},
|
||||||
|
searchQuery: {
|
||||||
|
type: "string",
|
||||||
|
defaultValue: ""
|
||||||
|
},
|
||||||
|
|
||||||
|
adminGroups: {
|
||||||
|
type: "string",
|
||||||
|
defaultValue: ""
|
||||||
|
}
|
||||||
|
},
|
||||||
oauth: {
|
oauth: {
|
||||||
"allowRegistration": {
|
"allowRegistration": {
|
||||||
type: "boolean",
|
type: "boolean",
|
||||||
@ -308,7 +344,7 @@ async function migrateConfigVariables() {
|
|||||||
for (const existingConfigVariable of existingConfigVariables) {
|
for (const existingConfigVariable of existingConfigVariables) {
|
||||||
const configVariable =
|
const configVariable =
|
||||||
configVariables[existingConfigVariable.category]?.[
|
configVariables[existingConfigVariable.category]?.[
|
||||||
existingConfigVariable.name
|
existingConfigVariable.name
|
||||||
];
|
];
|
||||||
|
|
||||||
// Delete the config variable if it doesn't exist in the seed
|
// Delete the config variable if it doesn't exist in the seed
|
||||||
|
@ -5,6 +5,8 @@ import { AuthController } from "./auth.controller";
|
|||||||
import { AuthService } from "./auth.service";
|
import { AuthService } from "./auth.service";
|
||||||
import { AuthTotpService } from "./authTotp.service";
|
import { AuthTotpService } from "./authTotp.service";
|
||||||
import { JwtStrategy } from "./strategy/jwt.strategy";
|
import { JwtStrategy } from "./strategy/jwt.strategy";
|
||||||
|
import { LdapService } from "./ldap.service";
|
||||||
|
import { UserModule } from "../user/user.module";
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
@ -12,9 +14,10 @@ import { JwtStrategy } from "./strategy/jwt.strategy";
|
|||||||
global: true,
|
global: true,
|
||||||
}),
|
}),
|
||||||
EmailModule,
|
EmailModule,
|
||||||
|
UserModule,
|
||||||
],
|
],
|
||||||
controllers: [AuthController],
|
controllers: [AuthController],
|
||||||
providers: [AuthService, AuthTotpService, JwtStrategy],
|
providers: [AuthService, AuthTotpService, JwtStrategy, LdapService],
|
||||||
exports: [AuthService],
|
exports: [AuthService],
|
||||||
})
|
})
|
||||||
export class AuthModule {}
|
export class AuthModule { }
|
||||||
|
@ -16,6 +16,9 @@ import { EmailService } from "src/email/email.service";
|
|||||||
import { PrismaService } from "src/prisma/prisma.service";
|
import { PrismaService } from "src/prisma/prisma.service";
|
||||||
import { AuthRegisterDTO } from "./dto/authRegister.dto";
|
import { AuthRegisterDTO } from "./dto/authRegister.dto";
|
||||||
import { AuthSignInDTO } from "./dto/authSignIn.dto";
|
import { AuthSignInDTO } from "./dto/authSignIn.dto";
|
||||||
|
import { LdapService } from "./ldap.service";
|
||||||
|
import { inspect } from "util";
|
||||||
|
import { UserSevice } from "../user/user.service";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AuthService {
|
export class AuthService {
|
||||||
@ -24,7 +27,9 @@ export class AuthService {
|
|||||||
private jwtService: JwtService,
|
private jwtService: JwtService,
|
||||||
private config: ConfigService,
|
private config: ConfigService,
|
||||||
private emailService: EmailService,
|
private emailService: EmailService,
|
||||||
) {}
|
private ldapService: LdapService,
|
||||||
|
private userService: UserSevice,
|
||||||
|
) { }
|
||||||
private readonly logger = new Logger(AuthService.name);
|
private readonly logger = new Logger(AuthService.name);
|
||||||
|
|
||||||
async signUp(dto: AuthRegisterDTO, ip: string, isAdmin?: boolean) {
|
async signUp(dto: AuthRegisterDTO, ip: string, isAdmin?: boolean) {
|
||||||
@ -64,24 +69,33 @@ export class AuthService {
|
|||||||
if (!dto.email && !dto.username)
|
if (!dto.email && !dto.username)
|
||||||
throw new BadRequestException("Email or username is required");
|
throw new BadRequestException("Email or username is required");
|
||||||
|
|
||||||
if (this.config.get("oauth.disablePassword"))
|
if (!this.config.get("oauth.disablePassword")) {
|
||||||
throw new ForbiddenException("Password sign in is disabled");
|
const user = await this.prisma.user.findFirst({
|
||||||
|
where: {
|
||||||
|
OR: [{ email: dto.email }, { username: dto.username }],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const user = await this.prisma.user.findFirst({
|
if (user && await argon.verify(user.password, dto.password)) {
|
||||||
where: {
|
this.logger.log(`Successful password login for user ${user.email} from IP ${ip}`);
|
||||||
OR: [{ email: dto.email }, { username: dto.username }],
|
return this.generateToken(user);
|
||||||
},
|
}
|
||||||
});
|
|
||||||
|
|
||||||
if (!user || !(await argon.verify(user.password, dto.password))) {
|
|
||||||
this.logger.log(
|
|
||||||
`Failed login attempt for user ${dto.email} from IP ${ip}`,
|
|
||||||
);
|
|
||||||
throw new UnauthorizedException("Wrong email or password");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.logger.log(`Successful login for user ${user.email} from IP ${ip}`);
|
if (this.config.get("ldap.enabled")) {
|
||||||
return this.generateToken(user);
|
this.logger.debug(`Trying LDAP login for user ${dto.username}`);
|
||||||
|
const ldapUser = await this.ldapService.authenticateUser(dto.username, dto.password);
|
||||||
|
if (ldapUser) {
|
||||||
|
const user = await this.userService.findOrCreateFromLDAP(dto.username, ldapUser);
|
||||||
|
this.logger.log(`Successful LDAP login for user ${user.email} from IP ${ip}`);
|
||||||
|
return this.generateToken(user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.log(
|
||||||
|
`Failed login attempt for user ${dto.email || dto.username} from IP ${ip}`,
|
||||||
|
);
|
||||||
|
throw new UnauthorizedException("Wrong email or password");
|
||||||
}
|
}
|
||||||
|
|
||||||
async generateToken(user: User, isOAuth = false) {
|
async generateToken(user: User, isOAuth = false) {
|
||||||
|
154
backend/src/auth/ldap.service.ts
Normal file
154
backend/src/auth/ldap.service.ts
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
import { Inject, Injectable, Logger } from "@nestjs/common";
|
||||||
|
import * as ldap from "ldapjs";
|
||||||
|
import { AttributeJson, InvalidCredentialsError, SearchCallbackResponse, SearchOptions } from "ldapjs";
|
||||||
|
import { inspect } from "node:util";
|
||||||
|
import { ConfigService } from "../config/config.service";
|
||||||
|
|
||||||
|
type LdapSearchEntry = {
|
||||||
|
objectName: string,
|
||||||
|
attributes: AttributeJson[],
|
||||||
|
};
|
||||||
|
|
||||||
|
async function ldapExecuteSearch(client: ldap.Client, base: string, options: SearchOptions): Promise<LdapSearchEntry[]> {
|
||||||
|
const searchResponse = await new Promise<SearchCallbackResponse>((resolve, reject) => {
|
||||||
|
client.search(base, options, (err, res) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
resolve(res);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return await new Promise<any[]>((resolve, reject) => {
|
||||||
|
const entries: LdapSearchEntry[] = [];
|
||||||
|
searchResponse.on("searchEntry", entry => entries.push({ attributes: entry.pojo.attributes, objectName: entry.pojo.objectName }));
|
||||||
|
searchResponse.once("error", reject);
|
||||||
|
searchResponse.once("end", () => resolve(entries));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function ldapBindUser(client: ldap.Client, dn: string, password: string): Promise<void> {
|
||||||
|
return new Promise<void>((resolve, reject) => {
|
||||||
|
client.bind(dn, password, error => {
|
||||||
|
if (error) {
|
||||||
|
reject(error);
|
||||||
|
} else {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function ldapCreateConnection(logger: Logger, url: string): Promise<ldap.Client> {
|
||||||
|
const ldapClient = ldap.createClient({
|
||||||
|
url: url.split(","),
|
||||||
|
connectTimeout: 10_000,
|
||||||
|
timeout: 10_000
|
||||||
|
});
|
||||||
|
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
ldapClient.once("error", reject);
|
||||||
|
ldapClient.on("setupError", reject);
|
||||||
|
ldapClient.on("socketTimeout", reject);
|
||||||
|
ldapClient.on("connectRefused", () => reject(new Error("connection has been refused")));
|
||||||
|
ldapClient.on("connectTimeout", () => reject(new Error("connect timed out")));
|
||||||
|
ldapClient.on("connectError", reject);
|
||||||
|
|
||||||
|
ldapClient.on("connect", resolve);
|
||||||
|
}).catch(error => {
|
||||||
|
logger.error(`Connect error: ${inspect(error)}`);
|
||||||
|
ldapClient.destroy();
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
|
||||||
|
return ldapClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type LdapAuthenticateResult = {
|
||||||
|
userDn: string,
|
||||||
|
attributes: Record<string, string[]>
|
||||||
|
};
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class LdapService {
|
||||||
|
private readonly logger = new Logger(LdapService.name);
|
||||||
|
constructor(
|
||||||
|
@Inject(ConfigService)
|
||||||
|
private readonly serviceConfig: ConfigService,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
private async createLdapConnection(): Promise<ldap.Client> {
|
||||||
|
const ldapUrl = this.serviceConfig.get("ldap.url");
|
||||||
|
if (!ldapUrl) {
|
||||||
|
throw new Error("LDAP server URL is not defined");
|
||||||
|
}
|
||||||
|
|
||||||
|
const ldapClient = await ldapCreateConnection(this.logger, ldapUrl);
|
||||||
|
try {
|
||||||
|
const bindDn = this.serviceConfig.get("ldap.bindDn") || null;
|
||||||
|
if (bindDn) {
|
||||||
|
try {
|
||||||
|
await ldapBindUser(ldapClient, bindDn, this.serviceConfig.get("ldap.bindPassword"))
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.warn(`Failed to bind to default user: ${error}`);
|
||||||
|
throw new Error("failed to bind to default user");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ldapClient;
|
||||||
|
} catch (error) {
|
||||||
|
ldapClient.destroy();
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async authenticateUser(username: string, password: string): Promise<LdapAuthenticateResult | null> {
|
||||||
|
if (!username.match(/^[a-zA-Z0-0]+$/)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const searchBase = this.serviceConfig.get("ldap.searchBase");
|
||||||
|
const searchQuery = this.serviceConfig.get("ldap.searchQuery")
|
||||||
|
.replaceAll("%username%", username);
|
||||||
|
|
||||||
|
const ldapClient = await this.createLdapConnection();
|
||||||
|
try {
|
||||||
|
const [result] = await ldapExecuteSearch(ldapClient, searchBase, {
|
||||||
|
filter: searchQuery,
|
||||||
|
scope: "sub"
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
/* user not found */
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await ldapBindUser(ldapClient, result.objectName, password);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* In theory we could query the user attributes now,
|
||||||
|
* but as we must query the user attributes for validation anyways
|
||||||
|
* we'll create a second ldap server connection.
|
||||||
|
*/
|
||||||
|
return {
|
||||||
|
userDn: result.objectName,
|
||||||
|
attributes: Object.fromEntries(result.attributes.map(attribute => [attribute.type, attribute.values])),
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof InvalidCredentialsError) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.warn(`LDAP user bind failure: ${inspect(error)}`);
|
||||||
|
return null;
|
||||||
|
} finally {
|
||||||
|
ldapClient.destroy();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.warn(`LDAP connect error: ${inspect(error)}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -25,16 +25,21 @@ export class UserDTO {
|
|||||||
@Expose()
|
@Expose()
|
||||||
isAdmin: boolean;
|
isAdmin: boolean;
|
||||||
|
|
||||||
|
@Expose()
|
||||||
|
isLdap: boolean;
|
||||||
|
|
||||||
|
ldapDN?: string;
|
||||||
|
|
||||||
@Expose()
|
@Expose()
|
||||||
totpVerified: boolean;
|
totpVerified: boolean;
|
||||||
|
|
||||||
from(partial: Partial<UserDTO>) {
|
from(partial: Partial<UserDTO>) {
|
||||||
return plainToClass(UserDTO, partial, { excludeExtraneousValues: true });
|
const result = plainToClass(UserDTO, partial, { excludeExtraneousValues: true });
|
||||||
|
result.isLdap = partial.ldapDN?.length > 0;
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
fromList(partial: Partial<UserDTO>[]) {
|
fromList(partial: Partial<UserDTO>[]) {
|
||||||
return partial.map((part) =>
|
return partial.map((part) => this.from(part));
|
||||||
plainToClass(UserDTO, part, { excludeExtraneousValues: true }),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,5 +8,6 @@ import { FileModule } from "src/file/file.module";
|
|||||||
imports: [EmailModule, FileModule],
|
imports: [EmailModule, FileModule],
|
||||||
providers: [UserSevice],
|
providers: [UserSevice],
|
||||||
controllers: [UserController],
|
controllers: [UserController],
|
||||||
|
exports: [UserSevice]
|
||||||
})
|
})
|
||||||
export class UserModule {}
|
export class UserModule { }
|
||||||
|
@ -7,6 +7,8 @@ import { PrismaService } from "src/prisma/prisma.service";
|
|||||||
import { FileService } from "../file/file.service";
|
import { FileService } from "../file/file.service";
|
||||||
import { CreateUserDTO } from "./dto/createUser.dto";
|
import { CreateUserDTO } from "./dto/createUser.dto";
|
||||||
import { UpdateUserDto } from "./dto/updateUser.dto";
|
import { UpdateUserDto } from "./dto/updateUser.dto";
|
||||||
|
import { ConfigService } from "../config/config.service";
|
||||||
|
import { LdapAuthenticateResult } from "../auth/ldap.service";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class UserSevice {
|
export class UserSevice {
|
||||||
@ -14,7 +16,8 @@ export class UserSevice {
|
|||||||
private prisma: PrismaService,
|
private prisma: PrismaService,
|
||||||
private emailService: EmailService,
|
private emailService: EmailService,
|
||||||
private fileService: FileService,
|
private fileService: FileService,
|
||||||
) {}
|
private configService: ConfigService,
|
||||||
|
) { }
|
||||||
|
|
||||||
async list() {
|
async list() {
|
||||||
return await this.prisma.user.findMany();
|
return await this.prisma.user.findMany();
|
||||||
@ -88,4 +91,41 @@ export class UserSevice {
|
|||||||
|
|
||||||
return await this.prisma.user.delete({ where: { id } });
|
return await this.prisma.user.delete({ where: { id } });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async findOrCreateFromLDAP(username: string, ldap: LdapAuthenticateResult) {
|
||||||
|
const passwordHash = await argon.hash(crypto.randomUUID());
|
||||||
|
const userEmail = ldap.attributes["userPrincipalName"]?.at(0) ?? `${crypto.randomUUID()}@ldap.local`;
|
||||||
|
const adminGroup = this.configService.get("ldap.adminGroups");
|
||||||
|
const isAdmin = ldap.attributes["memberOf"]?.includes(adminGroup) ?? false;
|
||||||
|
try {
|
||||||
|
return await this.prisma.user.upsert({
|
||||||
|
create: {
|
||||||
|
username,
|
||||||
|
email: userEmail,
|
||||||
|
password: passwordHash,
|
||||||
|
isAdmin,
|
||||||
|
ldapDN: ldap.userDn,
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
username,
|
||||||
|
email: userEmail,
|
||||||
|
|
||||||
|
isAdmin,
|
||||||
|
ldapDN: ldap.userDn,
|
||||||
|
},
|
||||||
|
where: {
|
||||||
|
ldapDN: ldap.userDn
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof PrismaClientKnownRequestError) {
|
||||||
|
if (e.code == "P2002") {
|
||||||
|
const duplicatedField: string = e.meta.target[0];
|
||||||
|
throw new BadRequestException(
|
||||||
|
`A user with this ${duplicatedField} already exists`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
44
frontend/package-lock.json
generated
44
frontend/package-lock.json
generated
@ -3145,11 +3145,6 @@
|
|||||||
"tslib": "^2.4.0"
|
"tslib": "^2.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/cookie": {
|
|
||||||
"version": "0.3.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.3.3.tgz",
|
|
||||||
"integrity": "sha512-LKVP3cgXBT9RYj+t+9FDKwS5tdI+rPBXaNSkma7hvqy35lc7mAokC2zsqWJH0LaqIt3B962nuYI77hsJoT1gow=="
|
|
||||||
},
|
|
||||||
"node_modules/@types/eslint": {
|
"node_modules/@types/eslint": {
|
||||||
"version": "8.4.1",
|
"version": "8.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.1.tgz",
|
||||||
@ -7238,14 +7233,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/next-cookies": {
|
|
||||||
"version": "2.0.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/next-cookies/-/next-cookies-2.0.3.tgz",
|
|
||||||
"integrity": "sha512-YVCQzwZx+sz+KqLO4y9niHH9jjz6jajlEQbAKfsYVT6DOfngb/0k5l6vFK4rmpExVug96pGag8OBsdSRL9FZhQ==",
|
|
||||||
"dependencies": {
|
|
||||||
"universal-cookie": "^4.0.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/next-http-proxy-middleware": {
|
"node_modules/next-http-proxy-middleware": {
|
||||||
"version": "1.2.6",
|
"version": "1.2.6",
|
||||||
"resolved": "https://registry.npmjs.org/next-http-proxy-middleware/-/next-http-proxy-middleware-1.2.6.tgz",
|
"resolved": "https://registry.npmjs.org/next-http-proxy-middleware/-/next-http-proxy-middleware-1.2.6.tgz",
|
||||||
@ -9141,15 +9128,6 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/universal-cookie": {
|
|
||||||
"version": "4.0.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/universal-cookie/-/universal-cookie-4.0.4.tgz",
|
|
||||||
"integrity": "sha512-lbRVHoOMtItjWbM7TwDLdl8wug7izB0tq3/YVKhT/ahB4VDvWMyvnADfnJI8y6fSvsjh51Ix7lTGC6Tn4rMPhw==",
|
|
||||||
"dependencies": {
|
|
||||||
"@types/cookie": "^0.3.3",
|
|
||||||
"cookie": "^0.4.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/universalify": {
|
"node_modules/universalify": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
|
||||||
@ -11837,11 +11815,6 @@
|
|||||||
"tslib": "^2.4.0"
|
"tslib": "^2.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@types/cookie": {
|
|
||||||
"version": "0.3.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.3.3.tgz",
|
|
||||||
"integrity": "sha512-LKVP3cgXBT9RYj+t+9FDKwS5tdI+rPBXaNSkma7hvqy35lc7mAokC2zsqWJH0LaqIt3B962nuYI77hsJoT1gow=="
|
|
||||||
},
|
|
||||||
"@types/eslint": {
|
"@types/eslint": {
|
||||||
"version": "8.4.1",
|
"version": "8.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.1.tgz",
|
||||||
@ -14856,14 +14829,6 @@
|
|||||||
"styled-jsx": "5.1.1"
|
"styled-jsx": "5.1.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"next-cookies": {
|
|
||||||
"version": "2.0.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/next-cookies/-/next-cookies-2.0.3.tgz",
|
|
||||||
"integrity": "sha512-YVCQzwZx+sz+KqLO4y9niHH9jjz6jajlEQbAKfsYVT6DOfngb/0k5l6vFK4rmpExVug96pGag8OBsdSRL9FZhQ==",
|
|
||||||
"requires": {
|
|
||||||
"universal-cookie": "^4.0.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"next-http-proxy-middleware": {
|
"next-http-proxy-middleware": {
|
||||||
"version": "1.2.6",
|
"version": "1.2.6",
|
||||||
"resolved": "https://registry.npmjs.org/next-http-proxy-middleware/-/next-http-proxy-middleware-1.2.6.tgz",
|
"resolved": "https://registry.npmjs.org/next-http-proxy-middleware/-/next-http-proxy-middleware-1.2.6.tgz",
|
||||||
@ -16185,15 +16150,6 @@
|
|||||||
"crypto-random-string": "^2.0.0"
|
"crypto-random-string": "^2.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"universal-cookie": {
|
|
||||||
"version": "4.0.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/universal-cookie/-/universal-cookie-4.0.4.tgz",
|
|
||||||
"integrity": "sha512-lbRVHoOMtItjWbM7TwDLdl8wug7izB0tq3/YVKhT/ahB4VDvWMyvnADfnJI8y6fSvsjh51Ix7lTGC6Tn4rMPhw==",
|
|
||||||
"requires": {
|
|
||||||
"@types/cookie": "^0.3.3",
|
|
||||||
"cookie": "^0.4.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"universalify": {
|
"universalify": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
|
||||||
|
@ -11,7 +11,14 @@ import {
|
|||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { Dispatch, SetStateAction } from "react";
|
import { Dispatch, SetStateAction } from "react";
|
||||||
import { TbAt, TbMail, TbShare, TbSocial, TbSquare } from "react-icons/tb";
|
import {
|
||||||
|
TbAt,
|
||||||
|
TbMail,
|
||||||
|
TbShare,
|
||||||
|
TbSocial,
|
||||||
|
TbSquare,
|
||||||
|
TbBinaryTree,
|
||||||
|
} from "react-icons/tb";
|
||||||
import { FormattedMessage } from "react-intl";
|
import { FormattedMessage } from "react-intl";
|
||||||
|
|
||||||
const categories = [
|
const categories = [
|
||||||
@ -20,6 +27,7 @@ const categories = [
|
|||||||
{ name: "Share", icon: <TbShare /> },
|
{ name: "Share", icon: <TbShare /> },
|
||||||
{ name: "SMTP", icon: <TbAt /> },
|
{ name: "SMTP", icon: <TbAt /> },
|
||||||
{ name: "OAuth", icon: <TbSocial /> },
|
{ name: "OAuth", icon: <TbSocial /> },
|
||||||
|
{ name: "LDAP", icon: <TbBinaryTree /> },
|
||||||
];
|
];
|
||||||
|
|
||||||
const useStyles = createStyles((theme) => ({
|
const useStyles = createStyles((theme) => ({
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { ActionIcon, Box, Group, Skeleton, Table } from "@mantine/core";
|
import { ActionIcon, Badge, Box, Group, Skeleton, Table } from "@mantine/core";
|
||||||
import { useModals } from "@mantine/modals";
|
import { useModals } from "@mantine/modals";
|
||||||
import { TbCheck, TbEdit, TbTrash } from "react-icons/tb";
|
import { TbCheck, TbEdit, TbTrash } from "react-icons/tb";
|
||||||
import User from "../../../types/user.type";
|
import User from "../../../types/user.type";
|
||||||
@ -40,21 +40,28 @@ const ManageUserTable = ({
|
|||||||
? skeletonRows
|
? skeletonRows
|
||||||
: users.map((user) => (
|
: users.map((user) => (
|
||||||
<tr key={user.id}>
|
<tr key={user.id}>
|
||||||
<td>{user.username}</td>
|
<td>
|
||||||
|
{user.username}{" "}
|
||||||
|
{user.isLdap ? (
|
||||||
|
<Badge style={{ marginLeft: "1em" }}>LDAP</Badge>
|
||||||
|
) : null}
|
||||||
|
</td>
|
||||||
<td>{user.email}</td>
|
<td>{user.email}</td>
|
||||||
<td>{user.isAdmin && <TbCheck />}</td>
|
<td>{user.isAdmin && <TbCheck />}</td>
|
||||||
<td>
|
<td>
|
||||||
<Group position="right">
|
<Group position="right">
|
||||||
<ActionIcon
|
{user.isLdap ? null : (
|
||||||
variant="light"
|
<ActionIcon
|
||||||
color="primary"
|
variant="light"
|
||||||
size="sm"
|
color="primary"
|
||||||
onClick={() =>
|
size="sm"
|
||||||
showUpdateUserModal(modals, user, getUsers)
|
onClick={() =>
|
||||||
}
|
showUpdateUserModal(modals, user, getUsers)
|
||||||
>
|
}
|
||||||
<TbEdit />
|
>
|
||||||
</ActionIcon>
|
<TbEdit />
|
||||||
|
</ActionIcon>
|
||||||
|
)}
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
variant="light"
|
variant="light"
|
||||||
color="red"
|
color="red"
|
||||||
|
@ -570,6 +570,21 @@ export default {
|
|||||||
"admin.config.oauth.oidc-client-secret.description":
|
"admin.config.oauth.oidc-client-secret.description":
|
||||||
"Client secret of the OpenID Connect OAuth app",
|
"Client secret of the OpenID Connect OAuth app",
|
||||||
|
|
||||||
|
"admin.config.category.ldap": "LDAP",
|
||||||
|
"admin.config.ldap.enabled": "Enabled LDAP",
|
||||||
|
"admin.config.ldap.enabled.description": "Use LDAP authentication for user login",
|
||||||
|
"admin.config.ldap.url": "Server URL",
|
||||||
|
"admin.config.ldap.url.description": "URL of the LDAP server",
|
||||||
|
"admin.config.ldap.bind-dn": "Bind DN",
|
||||||
|
"admin.config.ldap.bind-dn.description": "Default user which will be used to execute the user search",
|
||||||
|
"admin.config.ldap.bind-password": "Bind password",
|
||||||
|
"admin.config.ldap.bind-password.description": "Password for the user search user",
|
||||||
|
"admin.config.ldap.search-base": "User base",
|
||||||
|
"admin.config.ldap.search-base.description": "Base location, where the user search will be performed",
|
||||||
|
"admin.config.ldap.search-query": "User query",
|
||||||
|
"admin.config.ldap.search-query.description": "The user query will be used to search the 'User base' for the LDAP user. %username% can be used as the placeholder for the user given input.",
|
||||||
|
"admin.config.ldap.admin-groups": "Admin group",
|
||||||
|
|
||||||
// 404
|
// 404
|
||||||
"404.description": "Oops this page doesn't exist.",
|
"404.description": "Oops this page doesn't exist.",
|
||||||
"404.button.home": "Bring me back home",
|
"404.button.home": "Bring me back home",
|
||||||
|
@ -157,6 +157,7 @@ const Account = () => {
|
|||||||
<Stack>
|
<Stack>
|
||||||
<TextInput
|
<TextInput
|
||||||
label={t("account.card.info.username")}
|
label={t("account.card.info.username")}
|
||||||
|
disabled={user?.isLdap}
|
||||||
{...accountForm.getInputProps("username")}
|
{...accountForm.getInputProps("username")}
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
@ -171,45 +172,47 @@ const Account = () => {
|
|||||||
</Stack>
|
</Stack>
|
||||||
</form>
|
</form>
|
||||||
</Paper>
|
</Paper>
|
||||||
<Paper withBorder p="xl" mt="lg">
|
{user?.isLdap ? null : (
|
||||||
<Title order={5} mb="xs">
|
<Paper withBorder p="xl" mt="lg">
|
||||||
<FormattedMessage id="account.card.password.title" />
|
<Title order={5} mb="xs">
|
||||||
</Title>
|
<FormattedMessage id="account.card.password.title" />
|
||||||
<form
|
</Title>
|
||||||
onSubmit={passwordForm.onSubmit((values) =>
|
<form
|
||||||
authService
|
onSubmit={passwordForm.onSubmit((values) =>
|
||||||
.updatePassword(values.oldPassword, values.password)
|
authService
|
||||||
.then(async () => {
|
.updatePassword(values.oldPassword, values.password)
|
||||||
refreshUser();
|
.then(async () => {
|
||||||
toast.success(t("account.notify.password.success"));
|
refreshUser();
|
||||||
passwordForm.reset();
|
toast.success(t("account.notify.password.success"));
|
||||||
})
|
passwordForm.reset();
|
||||||
.catch(toast.axiosError),
|
})
|
||||||
)}
|
.catch(toast.axiosError),
|
||||||
>
|
|
||||||
<Stack>
|
|
||||||
{user?.hasPassword ? (
|
|
||||||
<PasswordInput
|
|
||||||
label={t("account.card.password.old")}
|
|
||||||
{...passwordForm.getInputProps("oldPassword")}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<Text size="sm" color="dimmed">
|
|
||||||
<FormattedMessage id="account.card.password.noPasswordSet" />
|
|
||||||
</Text>
|
|
||||||
)}
|
)}
|
||||||
<PasswordInput
|
>
|
||||||
label={t("account.card.password.new")}
|
<Stack>
|
||||||
{...passwordForm.getInputProps("password")}
|
{user?.hasPassword ? (
|
||||||
/>
|
<PasswordInput
|
||||||
<Group position="right">
|
label={t("account.card.password.old")}
|
||||||
<Button type="submit">
|
{...passwordForm.getInputProps("oldPassword")}
|
||||||
<FormattedMessage id="common.button.save" />
|
/>
|
||||||
</Button>
|
) : (
|
||||||
</Group>
|
<Text size="sm" color="dimmed">
|
||||||
</Stack>
|
<FormattedMessage id="account.card.password.noPasswordSet" />
|
||||||
</form>
|
</Text>
|
||||||
</Paper>
|
)}
|
||||||
|
<PasswordInput
|
||||||
|
label={t("account.card.password.new")}
|
||||||
|
{...passwordForm.getInputProps("password")}
|
||||||
|
/>
|
||||||
|
<Group position="right">
|
||||||
|
<Button type="submit">
|
||||||
|
<FormattedMessage id="common.button.save" />
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</form>
|
||||||
|
</Paper>
|
||||||
|
)}
|
||||||
{oauth.length > 0 && (
|
{oauth.length > 0 && (
|
||||||
<Paper withBorder p="xl" mt="lg">
|
<Paper withBorder p="xl" mt="lg">
|
||||||
<Title order={5} mb="xs">
|
<Title order={5} mb="xs">
|
||||||
|
@ -3,6 +3,7 @@ type User = {
|
|||||||
username: string;
|
username: string;
|
||||||
email: string;
|
email: string;
|
||||||
isAdmin: boolean;
|
isAdmin: boolean;
|
||||||
|
isLdap: boolean;
|
||||||
totpVerified: boolean;
|
totpVerified: boolean;
|
||||||
hasPassword: boolean;
|
hasPassword: boolean;
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user