17 Commits
3.3.2 ... 4.1.1

Author SHA1 Message Date
sym
c5e8e6f79a Update README.md 2023-11-21 12:20:20 +01:00
sym
d049e4534a Update README.md 2023-11-21 12:20:04 +01:00
sym
ed63dd0527 Merge pull request #31 from yzx9/feature/sharelatex411
Update sharelatex to v4.1.1:
- v.4.1.1 works but i have still troubles migrating the projects from 3.3.2 (even after running the migrations scripts from 3.5.10)
2023-11-21 12:17:01 +01:00
Zexin Yuan
062f17e0f7 Fix wrong indentation 2023-11-08 10:18:32 +08:00
Zexin Yuan
48b1623c95 Merge branch 'master' into feature/sharelatex411 2023-09-18 23:21:06 +08:00
yzx9
7ac46ad430 Remove placeholder of share.pug 2023-09-18 23:05:36 +08:00
gizmo1-11
a382fbc194 changed variable "user1" to "user"
I used "user1", because Im not sure about the scope inside the anonymous callback functions
2023-09-18 22:52:00 +08:00
yzx9
a107b6444c Clean apt cache 2023-09-18 22:52:00 +08:00
yzx9
4bc203a757 Merge RUN command 2023-09-18 22:52:00 +08:00
yzx9
3a560801d9 Fix version of mongo and redis 2023-09-18 22:52:00 +08:00
yzx9
a99e70f3c4 Bump sharelatex to v4.1.1
- bump sharelatex
- init mongodb replset
- format docker-compose.yml
2023-09-18 22:52:00 +08:00
yzx9
c4775c7d7c Bump sharelatex from 3.3.2 to 4.0.5
Co-authored-by: gizmo1-11 <thom_schu@gmx.de>
2023-09-18 22:51:49 +08:00
yzx9
f645454a74 Fix authentication api changes
Co-authored-by: gizmo1-11 <thom_schu@gmx.de>
2023-09-18 22:48:37 +08:00
sym
ba1e78f2a8 Merge pull request #29 from yzx9/minimize-image-size
Minimize image size
2023-07-24 13:37:40 +02:00
yzx9
ad051a0e18 Clean apt cache 2023-07-20 19:30:56 +08:00
yzx9
810aa1d0e9 Merge RUN command 2023-07-20 19:25:56 +08:00
yzx9
a6448a51e9 Sync changes from upstream 2023-07-20 18:46:13 +08:00
6 changed files with 1068 additions and 707 deletions

View File

@@ -7,8 +7,15 @@ edition. Currently this repo uses sharelatex:latest.
The inital idea for this implementation was taken from
[worksasintended](https://github.com/worksasintended).
## BREAKING CHANGE
be careful if you try to migrate from 3.3.2! Backup your machines and data.
The migration paths hould be:
- Backup Your machines and Data
- run latest 3.5 sharelatex image and run the migration scripts
- run this sharelatex image (4.1.1) and run the migrations scripts
### Limitations:
## Limitations:
NEW: This version provides the possibility to use a separate ldap bind user. It does this just to find the proper BIND DN and record for the provided email, so it is possible that users from different groups / OUs can login.
Afterwards it tries to bind to the ldap (using ldapts) with the user DN and credentials of the user which tries to login. No hassle of password hashing for LDAP pwds!

View File

@@ -1,150 +1,157 @@
version: '2.2'
version: "2.2"
services:
sharelatex:
restart: always
image: ldap-overleaf-sl
container_name: ldap-overleaf-sl
depends_on:
mongo:
condition: service_healthy
redis:
condition: service_healthy
simple-certbot:
condition: service_started
privileged: false
ports:
- 443:443
links:
- mongo
- redis
- simple-certbot
volumes:
- ${MYDATA}/sharelatex:/var/lib/sharelatex
- ${MYDATA}/letsencrypt:/etc/letsencrypt
- ${MYDATA}/letsencrypt/live/${MYDOMAIN}/:/etc/letsencrypt/certs/domain
environment:
SHARELATEX_APP_NAME: Overleaf
SHARELATEX_MONGO_URL: mongodb://mongo/sharelatex
SHARELATEX_SITE_URL: https://${MYDOMAIN}
SHARELATEX_NAV_TITLE: Overleaf - run by ${MYDOMAIN}
#SHARELATEX_HEADER_IMAGE_URL: https://${MYDOMAIN}/logo.svg
SHARELATEX_ADMIN_EMAIL: ${MYMAIL}
SHARELATEX_LEFT_FOOTER: '[{"text": "Powered by <a href=\"https://www.sharelatex.com\">ShareLaTeX</a> 2016"} ]'
SHARELATEX_RIGHT_FOOTER: '[{"text": "LDAP Overleaf (beta)"} ]'
SHARELATEX_EMAIL_FROM_ADDRESS: "noreply@${MYDOMAIN}"
# SHARELATEX_EMAIL_AWS_SES_ACCESS_KEY_ID:
# SHARELATEX_EMAIL_AWS_SES_SECRET_KEY:
SHARELATEX_EMAIL_SMTP_HOST: smtp.${MYDOMAIN}
SHARELATEX_EMAIL_SMTP_PORT: 587
SHARELATEX_EMAIL_SMTP_SECURE: 'false'
# SHARELATEX_EMAIL_SMTP_USER:
# SHARELATEX_EMAIL_SMTP_PASS:
# SHARELATEX_EMAIL_SMTP_TLS_REJECT_UNAUTH: true
# SHARELATEX_EMAIL_SMTP_IGNORE_TLS: false
SHARELATEX_CUSTOM_EMAIL_FOOTER: "This system is run by ${MYDOMAIN} - please contact ${MYMAIL} if you experience any issues."
sharelatex:
restart: always
image: ldap-overleaf-sl
container_name: ldap-overleaf-sl
depends_on:
mongo:
condition: service_healthy
redis:
condition: service_healthy
simple-certbot:
condition: service_started
privileged: false
ports:
- 443:443
links:
- mongo
- redis
- simple-certbot
volumes:
- ${MYDATA}/sharelatex:/var/lib/sharelatex
- ${MYDATA}/letsencrypt:/etc/letsencrypt
- ${MYDATA}/letsencrypt/live/${MYDOMAIN}/:/etc/letsencrypt/certs/domain
environment:
SHARELATEX_APP_NAME: Overleaf
SHARELATEX_MONGO_URL: mongodb://mongo/sharelatex
SHARELATEX_SITE_URL: https://${MYDOMAIN}
SHARELATEX_NAV_TITLE: Overleaf - run by ${MYDOMAIN}
#SHARELATEX_HEADER_IMAGE_URL: https://${MYDOMAIN}/logo.svg
SHARELATEX_ADMIN_EMAIL: ${MYMAIL}
SHARELATEX_LEFT_FOOTER: '[{"text": "Powered by <a href=\"https://www.sharelatex.com\">ShareLaTeX</a> 2016"} ]'
SHARELATEX_RIGHT_FOOTER: '[{"text": "LDAP Overleaf (beta)"} ]'
SHARELATEX_EMAIL_FROM_ADDRESS: "noreply@${MYDOMAIN}"
# SHARELATEX_EMAIL_AWS_SES_ACCESS_KEY_ID:
# SHARELATEX_EMAIL_AWS_SES_SECRET_KEY:
SHARELATEX_EMAIL_SMTP_HOST: smtp.${MYDOMAIN}
SHARELATEX_EMAIL_SMTP_PORT: 587
SHARELATEX_EMAIL_SMTP_SECURE: "false"
# SHARELATEX_EMAIL_SMTP_USER:
# SHARELATEX_EMAIL_SMTP_PASS:
# SHARELATEX_EMAIL_SMTP_TLS_REJECT_UNAUTH: true
# SHARELATEX_EMAIL_SMTP_IGNORE_TLS: false
SHARELATEX_CUSTOM_EMAIL_FOOTER: "This system is run by ${MYDOMAIN} - please contact ${MYMAIL} if you experience any issues."
# make public links accessible w/o login (link sharing issue)
# https://github.com/overleaf/docker-image/issues/66
# https://github.com/overleaf/overleaf/issues/628
# https://github.com/overleaf/web/issues/367
# Fixed in 2.0.2 (Release date: 2019-11-26)
SHARELATEX_ALLOW_PUBLIC_ACCESS: 'true'
SHARELATEX_ALLOW_ANONYMOUS_READ_AND_WRITE_SHARING: 'true'
# make public links accessible w/o login (link sharing issue)
# https://github.com/overleaf/docker-image/issues/66
# https://github.com/overleaf/overleaf/issues/628
# https://github.com/overleaf/web/issues/367
# Fixed in 2.0.2 (Release date: 2019-11-26)
SHARELATEX_ALLOW_PUBLIC_ACCESS: "true"
SHARELATEX_ALLOW_ANONYMOUS_READ_AND_WRITE_SHARING: "true"
SHARELATEX_SECURE_COOKIE: 'true'
SHARELATEX_BEHIND_PROXY: 'true'
LDAP_SERVER: ldaps://LDAPSERVER:636
LDAP_BASE: ou=people,dc=DOMAIN,dc=TLD
SHARELATEX_SECURE_COOKIE: "true"
SHARELATEX_BEHIND_PROXY: "true"
### There are to ways get users from the ldap server
LDAP_SERVER: ldaps://LDAPSERVER:636
LDAP_BASE: ou=people,dc=DOMAIN,dc=TLD
## NO LDAP BIND USER:
# Tries directly to bind with the login user (as uid)
# LDAP_BINDDN: uid=%u,ou=someunit,ou=people,dc=DOMAIN,dc=TLD
### There are to ways get users from the ldap server
## Or you can use ai global LDAP_BIND_USER
# LDAP_BIND_USER:
# LDAP_BIND_PW:
# Only allow users matching LDAP_USER_FILTER
LDAP_USER_FILTER: '(memberof=cn=GROUPNAME,ou=groups,dc=DOMAIN,dc=TLD)'
## NO LDAP BIND USER:
# Tries directly to bind with the login user (as uid)
# LDAP_BINDDN: uid=%u,ou=someunit,ou=people,dc=DOMAIN,dc=TLD
# If user is in ADMIN_GROUP on user creation (first login) isAdmin is set to true.
# Admin Users can invite external (non ldap) users. This feature makes only sense
# when ALLOW_EMAIL_LOGIN is set to 'true'. Additionally admins can send
# system wide messages.
LDAP_ADMIN_GROUP_FILTER: '(memberof=cn=ADMINGROUPNAME,ou=groups,dc=DOMAIN,dc=TLD)'
ALLOW_EMAIL_LOGIN: 'true'
## Or you can use ai global LDAP_BIND_USER
# LDAP_BIND_USER:
# LDAP_BIND_PW:
# All users in the LDAP_CONTACT_FILTER are loaded from the ldap server into contacts.
LDAP_CONTACT_FILTER: '(memberof=cn=GROUPNAME,ou=groups,dc=DOMAIN,dc=TLD)'
LDAP_CONTACTS: 'false'
# Only allow users matching LDAP_USER_FILTER
LDAP_USER_FILTER: "(memberof=cn=GROUPNAME,ou=groups,dc=DOMAIN,dc=TLD)"
# Same property, unfortunately with different names in
# different locations
SHARELATEX_REDIS_HOST: redis
REDIS_HOST: redis
REDIS_PORT: 6379
# If user is in ADMIN_GROUP on user creation (first login) isAdmin is set to true.
# Admin Users can invite external (non ldap) users. This feature makes only sense
# when ALLOW_EMAIL_LOGIN is set to 'true'. Additionally admins can send
# system wide messages.
LDAP_ADMIN_GROUP_FILTER: "(memberof=cn=ADMINGROUPNAME,ou=groups,dc=DOMAIN,dc=TLD)"
ALLOW_EMAIL_LOGIN: "true"
ENABLED_LINKED_FILE_TYPES: 'url,project_file'
# All users in the LDAP_CONTACT_FILTER are loaded from the ldap server into contacts.
LDAP_CONTACT_FILTER: "(memberof=cn=GROUPNAME,ou=groups,dc=DOMAIN,dc=TLD)"
LDAP_CONTACTS: "false"
# Enables Thumbnail generation using ImageMagick
ENABLE_CONVERSIONS: 'true'
# Same property, unfortunately with different names in
# different locations
SHARELATEX_REDIS_HOST: redis
REDIS_HOST: redis
REDIS_PORT: 6379
mongo:
restart: always
image: mongo
container_name: mongo
ports:
- 27017
volumes:
- ${MYDATA}/mongo_data:/data/db
healthcheck:
test: echo 'db.stats().ok' | mongo localhost:27017/test --quiet
interval: 10s
timeout: 10s
retries: 5
ENABLED_LINKED_FILE_TYPES: "url,project_file"
redis:
restart: always
image: redis:5.0.0
container_name: redis
# modify to get rid of the redis issue #35 and #19 with a better solution
# WARNING: /proc/sys/net/core/somaxconn is set to the lower value of 128.
# for vm overcommit: enable first on host system
# sysctl vm.overcommit_memory=1 (and add it to rc.local)
# then you do not need it in the redis container
sysctls:
- net.core.somaxconn=65535
# - vm.overcommit_memory=1
ports:
- 6379
volumes:
- ${MYDATA}/redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
# Enables Thumbnail generation using ImageMagick
ENABLE_CONVERSIONS: "true"
mongo:
restart: always
image: mongo:4.4
container_name: mongo
expose:
- 27017
volumes:
- ${MYDATA}/mongo_data:/data/db
healthcheck:
test: echo 'db.stats().ok' | mongo localhost:27017/test --quiet
interval: 10s
timeout: 10s
retries: 5
command: "--replSet overleaf"
simple-certbot:
restart: always
image: certbot/certbot
container_name: simple-certbot
ports:
- 80:80
volumes:
- ${MYDATA}/letsencrypt:/etc/letsencrypt
# a bit hacky but this docker image uses very little disk-space
# best practices for ssl and nginx are set in the ldap-overleaf-sl Dockerfile
entrypoint:
- "/bin/sh"
- -c
- |
trap exit TERM;\
certbot certonly --standalone -d ${MYDOMAIN} --agree-tos -m ${MYMAIL} -n ; \
while :; do certbot renew; sleep 240h & wait $${!}; done;
# See also: https://github.com/overleaf/overleaf/issues/1120
mongoinit:
image: mongo:4.4
# this container will exit after executing the command
restart: "no"
depends_on:
mongo:
condition: service_healthy
entrypoint:
[
"mongo",
"--host",
"mongo:27017",
"--eval",
'rs.initiate({ _id: "overleaf", members: [ { _id: 0, host: "mongo:27017" } ] })',
]
redis:
restart: always
image: redis:6.2
container_name: redis
expose:
- 6379
volumes:
- ${MYDATA}/redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
simple-certbot:
restart: always
image: certbot/certbot
container_name: simple-certbot
ports:
- 80:80
volumes:
- ${MYDATA}/letsencrypt:/etc/letsencrypt
# a bit hacky but this docker image uses very little disk-space
# best practices for ssl and nginx are set in the ldap-overleaf-sl Dockerfile
entrypoint:
- "/bin/sh"
- -c
- |
trap exit TERM;\
certbot certonly --standalone -d ${MYDOMAIN} --agree-tos -m ${MYMAIL} -n ; \
while :; do certbot renew; sleep 240h & wait $${!}; done;

View File

@@ -1,226 +1,242 @@
version: '2.2'
version: "2.2"
services:
traefik:
image: traefik:latest
container_name: traefik
restart: unless-stopped
security_opt:
- no-new-privileges:true
networks:
- web
ports:
- 80:80
- 443:443
- 8443:8443
# - 8080:8080
# - 27017:27017
volumes:
- ${MYDATA}/letsencrypt:/letsencrypt
- /etc/localtime:/etc/localtime:ro
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./traefik/dynamic_conf.yml:/dynamic_conf.yml
- ./traefik/users.htpasswd:/users.htpasswd
traefik:
image: traefik:latest
container_name: traefik
restart: unless-stopped
security_opt:
- no-new-privileges:true
networks:
- web
ports:
- 80:80
- 443:443
- 8443:8443
# - 8080:8080
# - 27017:27017
volumes:
- ${MYDATA}/letsencrypt:/letsencrypt
- /etc/localtime:/etc/localtime:ro
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./traefik/dynamic_conf.yml:/dynamic_conf.yml
- ./traefik/users.htpasswd:/users.htpasswd
command:
- "--api=true"
- "--api.dashboard=true"
#- "--api.insecure=true" # provides the dashboard on http://IPADRESS:8080
- "--providers.docker=true"
- "--ping"
- "--providers.docker.network=web"
- "--providers.docker.exposedbydefault=false"
- "--providers.file.filename=/dynamic_conf.yml"
- "--entrypoints.web.address=:80"
- "--entrypoints.web-secure.address=:443"
- "--entrypoints.web-admin.address=:8443"
- "--certificatesresolvers.myhttpchallenge.acme.httpchallenge=true"
- "--certificatesresolvers.myhttpchallenge.acme.httpchallenge.entrypoint=web"
- "--certificatesresolvers.myhttpchallenge.acme.email=${MYMAIL}"
- "--certificatesresolvers.myhttpchallenge.acme.storage=/letsencrypt/acme.json"
- "--entrypoints.mongo.address=:27017"
#- --certificatesresolvers.myhttpchallenge.acme.caserver=https://acme-v02.api.letsencrypt.org/directory
labels:
- "traefik.enable=true"
# To Fix enable dashboard on port 8443
- "traefik.http.routers.dashboard.entrypoints=web-admin"
- "traefik.http.routers.dashboard.rule=Host(`${MYDOMAIN}`)"
# - "traefik.http.routers.dashboard.rule=Host(`traefik.${MYDOMAIN}`) && (PathPrefix(`/api`) || PathPrefix(`/dashboard`))"
- "traefik.http.routers.dashboard.tls=true"
- "traefik.http.routers.dashboard.middlewares=auth"
- "traefik.http.middlewares.auth.basicauth.usersfile=/users.htpasswd"
- "traefik.http.routers.dashboard.service=api@internal"
- "traefik.http.middlewares.sslheader.headers.customrequestheaders.X-Forwarded-Proto=https"
- "traefik.http.routers.proxy-https.entrypoints=web-secure"
- "traefik.http.routers.proxy-https.rule=Host(`${MYDOMAIN}`)"
command:
- "--api=true"
- "--api.dashboard=true"
#- "--api.insecure=true" # provides the dashboard on http://IPADRESS:8080
- "--providers.docker=true"
- "--ping"
- "--providers.docker.network=web"
- "--providers.docker.exposedbydefault=false"
- "--providers.file.filename=/dynamic_conf.yml"
- "--entrypoints.web.address=:80"
- "--entrypoints.web-secure.address=:443"
- "--entrypoints.web-admin.address=:8443"
- "--certificatesresolvers.myhttpchallenge.acme.httpchallenge=true"
- "--certificatesresolvers.myhttpchallenge.acme.httpchallenge.entrypoint=web"
- "--certificatesresolvers.myhttpchallenge.acme.email=${MYMAIL}"
- "--certificatesresolvers.myhttpchallenge.acme.storage=/letsencrypt/acme.json"
- "--entrypoints.mongo.address=:27017"
#- --certificatesresolvers.myhttpchallenge.acme.caserver=https://acme-v02.api.letsencrypt.org/directory
labels:
- "traefik.enable=true"
# To Fix enable dashboard on port 8443
- "traefik.http.routers.dashboard.entrypoints=web-admin"
- "traefik.http.routers.dashboard.rule=Host(`${MYDOMAIN}`)"
# - "traefik.http.routers.dashboard.rule=Host(`traefik.${MYDOMAIN}`) && (PathPrefix(`/api`) || PathPrefix(`/dashboard`))"
- "traefik.http.routers.dashboard.tls=true"
- "traefik.http.routers.dashboard.middlewares=auth"
- "traefik.http.middlewares.auth.basicauth.usersfile=/users.htpasswd"
- "traefik.http.routers.dashboard.service=api@internal"
- "traefik.http.middlewares.sslheader.headers.customrequestheaders.X-Forwarded-Proto=https"
- "traefik.http.routers.proxy-https.entrypoints=web-secure"
- "traefik.http.routers.proxy-https.rule=Host(`${MYDOMAIN}`)"
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "1"
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "1"
sharelatex:
restart: always
image: ldap-overleaf-sl:latest
depends_on:
mongo:
condition: service_healthy
redis:
condition: service_healthy
traefik:
condition: service_started
#simple-certbot:
# condition: service_started
privileged: false
networks:
- web
expose:
- 80
- 443
links:
- mongo
- redis
volumes:
- ${MYDATA}/sharelatex:/var/lib/sharelatex
- ${MYDATA}/letsencrypt:/etc/letsencrypt:ro
# - ${MYDATA}/letsencrypt/live/${MYDOMAIN}/:/etc/letsencrypt/certs/domain
labels:
- "traefik.enable=true"
# global redirect to https
- "traefik.http.routers.http-catchall.rule=hostregexp(`${MYDOMAIN}`)"
- "traefik.http.routers.http-catchall.entrypoints=web"
- "traefik.http.routers.http-catchall.middlewares=redirect-to-https"
- "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https"
# handle https traffic
- "traefik.http.routers.sharel-secured.rule=Host(`${MYDOMAIN}`)"
- "traefik.http.routers.sharel-secured.tls=true"
- "traefik.http.routers.sharel-secured.tls.certresolver=myhttpchallenge"
- "traefik.http.routers.sharel-secured.entrypoints=web-secure"
- "traefik.http.middlewares.sharel-secured.forwardauth.trustForwardHeader=true"
# Docker loadbalance
- "traefik.http.services.sharel.loadbalancer.server.port=80"
- "traefik.http.services.sharel.loadbalancer.server.scheme=http"
- "traefik.http.services.sharel.loadbalancer.sticky.cookie=true"
- "traefik.http.services.sharel.loadbalancer.sticky.cookie.name=io"
- "traefik.http.services.sharel.loadbalancer.sticky.cookie.httponly=true"
- "traefik.http.services.sharel.loadbalancer.sticky.cookie.secure=true"
- "traefik.http.services.sharel.loadbalancer.sticky.cookie.samesite=io"
sharelatex:
restart: always
image: ldap-overleaf-sl:latest
depends_on:
mongo:
condition: service_healthy
redis:
condition: service_healthy
traefik:
condition: service_started
#simple-certbot:
# condition: service_started
privileged: false
networks:
- web
expose:
- 80
- 443
links:
- mongo
- redis
volumes:
- ${MYDATA}/sharelatex:/var/lib/sharelatex
- ${MYDATA}/letsencrypt:/etc/letsencrypt:ro
# - ${MYDATA}/letsencrypt/live/${MYDOMAIN}/:/etc/letsencrypt/certs/domain
labels:
- "traefik.enable=true"
# global redirect to https
- "traefik.http.routers.http-catchall.rule=hostregexp(`${MYDOMAIN}`)"
- "traefik.http.routers.http-catchall.entrypoints=web"
- "traefik.http.routers.http-catchall.middlewares=redirect-to-https"
- "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https"
# handle https traffic
- "traefik.http.routers.sharel-secured.rule=Host(`${MYDOMAIN}`)"
- "traefik.http.routers.sharel-secured.tls=true"
- "traefik.http.routers.sharel-secured.tls.certresolver=myhttpchallenge"
- "traefik.http.routers.sharel-secured.entrypoints=web-secure"
- "traefik.http.middlewares.sharel-secured.forwardauth.trustForwardHeader=true"
# Docker loadbalance
- "traefik.http.services.sharel.loadbalancer.server.port=80"
- "traefik.http.services.sharel.loadbalancer.server.scheme=http"
- "traefik.http.services.sharel.loadbalancer.sticky.cookie=true"
- "traefik.http.services.sharel.loadbalancer.sticky.cookie.name=io"
- "traefik.http.services.sharel.loadbalancer.sticky.cookie.httponly=true"
- "traefik.http.services.sharel.loadbalancer.sticky.cookie.secure=true"
- "traefik.http.services.sharel.loadbalancer.sticky.cookie.samesite=io"
environment:
SHARELATEX_APP_NAME: Overleaf
SHARELATEX_MONGO_URL: mongodb://mongo/sharelatex
SHARELATEX_SITE_URL: https://${MYDOMAIN}
SHARELATEX_NAV_TITLE: Overleaf - run by ${MYDOMAIN}
#SHARELATEX_HEADER_IMAGE_URL: https://${MYDOMAIN}/logo.svg
SHARELATEX_ADMIN_EMAIL: ${MYMAIL}
SHARELATEX_LEFT_FOOTER: '[{"text": "Powered by <a href=\"https://www.sharelatex.com\">ShareLaTeX</a> 2016"} ]'
SHARELATEX_RIGHT_FOOTER: '[{"text": "LDAP Overleaf (beta)"} ]'
SHARELATEX_EMAIL_FROM_ADDRESS: "noreply@${MYDOMAIN}"
SHARELATEX_EMAIL_SMTP_HOST: smtp.${MYDOMAIN}
SHARELATEX_EMAIL_SMTP_PORT: 587
SHARELATEX_EMAIL_SMTP_SECURE: "false"
# SHARELATEX_EMAIL_SMTP_USER:
# SHARELATEX_EMAIL_SMTP_PASS:
# SHARELATEX_EMAIL_SMTP_TLS_REJECT_UNAUTH: true
# SHARELATEX_EMAIL_SMTP_IGNORE_TLS: false
SHARELATEX_CUSTOM_EMAIL_FOOTER: "This system is run by ${MYDOMAIN} - please contact ${MYMAIL} if you experience any issues."
environment:
SHARELATEX_APP_NAME: Overleaf
SHARELATEX_MONGO_URL: mongodb://mongo/sharelatex
SHARELATEX_SITE_URL: https://${MYDOMAIN}
SHARELATEX_NAV_TITLE: Overleaf - run by ${MYDOMAIN}
#SHARELATEX_HEADER_IMAGE_URL: https://${MYDOMAIN}/logo.svg
SHARELATEX_ADMIN_EMAIL: ${MYMAIL}
SHARELATEX_LEFT_FOOTER: '[{"text": "Powered by <a href=\"https://www.sharelatex.com\">ShareLaTeX</a> 2016"} ]'
SHARELATEX_RIGHT_FOOTER: '[{"text": "LDAP Overleaf (beta)"} ]'
SHARELATEX_EMAIL_FROM_ADDRESS: "noreply@${MYDOMAIN}"
SHARELATEX_EMAIL_SMTP_HOST: smtp.${MYDOMAIN}
SHARELATEX_EMAIL_SMTP_PORT: 587
SHARELATEX_EMAIL_SMTP_SECURE: 'false'
# SHARELATEX_EMAIL_SMTP_USER:
# SHARELATEX_EMAIL_SMTP_PASS:
# SHARELATEX_EMAIL_SMTP_TLS_REJECT_UNAUTH: true
# SHARELATEX_EMAIL_SMTP_IGNORE_TLS: false
SHARELATEX_CUSTOM_EMAIL_FOOTER: "This system is run by ${MYDOMAIN} - please contact ${MYMAIL} if you experience any issues."
# make public links accessible w/o login (link sharing issue)
# https://github.com/overleaf/docker-image/issues/66
# https://github.com/overleaf/overleaf/issues/628
# https://github.com/overleaf/web/issues/367
# Fixed in 2.0.2 (Release date: 2019-11-26)
SHARELATEX_ALLOW_PUBLIC_ACCESS: "true"
SHARELATEX_ALLOW_ANONYMOUS_READ_AND_WRITE_SHARING: "true"
# make public links accessible w/o login (link sharing issue)
# https://github.com/overleaf/docker-image/issues/66
# https://github.com/overleaf/overleaf/issues/628
# https://github.com/overleaf/web/issues/367
# Fixed in 2.0.2 (Release date: 2019-11-26)
SHARELATEX_ALLOW_PUBLIC_ACCESS: 'true'
SHARELATEX_ALLOW_ANONYMOUS_READ_AND_WRITE_SHARING: 'true'
SHARELATEX_SECURE_COOKIE: "true"
SHARELATEX_BEHIND_PROXY: "true"
SHARELATEX_SECURE_COOKIE: 'true'
SHARELATEX_BEHIND_PROXY: 'true'
LDAP_SERVER: ldaps://LDAPSERVER:636
LDAP_BASE: ou=people,dc=DOMAIN,dc=TLD
LDAP_SERVER: ldaps://LDAPSERVER:636
LDAP_BASE: ou=people,dc=DOMAIN,dc=TLD
### There are to ways get users from the ldap server
### There are to ways get users from the ldap server
## NO LDAP BIND USER:
# Tries to bind with login-user (as uid) to LDAP_BINDDN
# LDAP_BINDDN: uid=%u,ou=someunit,ou=people,dc=DOMAIN,dc=TLD
## NO LDAP BIND USER:
# Tries to bind with login-user (as uid) to LDAP_BINDDN
# LDAP_BINDDN: uid=%u,ou=someunit,ou=people,dc=DOMAIN,dc=TLD
## Using a LDAP_BIND_USER/PW
# LDAP_BIND_USER:
# LDAP_BIND_PW:
# Only allow users matching LDAP_USER_FILTER
LDAP_USER_FILTER: '(memberof=cn=GROUPNAME,ou=groups,dc=DOMAIN,dc=TLD)'
## Using a LDAP_BIND_USER/PW
# LDAP_BIND_USER:
# LDAP_BIND_PW:
# If user is in ADMIN_GROUP on user creation (first login) isAdmin is set to true.
# Admin Users can invite external (non ldap) users. This feature makes only sense
# when ALLOW_EMAIL_LOGIN is set to 'true'. Additionally admins can send
# system wide messages.
LDAP_ADMIN_GROUP_FILTER: '(memberof=cn=ADMINGROUPNAME,ou=groups,dc=DOMAIN,dc=TLD)'
ALLOW_EMAIL_LOGIN: 'true'
# Only allow users matching LDAP_USER_FILTER
LDAP_USER_FILTER: "(memberof=cn=GROUPNAME,ou=groups,dc=DOMAIN,dc=TLD)"
# All users in the LDAP_CONTACT_FILTER are loaded from the ldap server into contacts.
LDAP_CONTACT_FILTER: '(memberof=cn=GROUPNAME,ou=groups,dc=DOMAIN,dc=TLD)'
LDAP_CONTACTS: 'false'
# If user is in ADMIN_GROUP on user creation (first login) isAdmin is set to true.
# Admin Users can invite external (non ldap) users. This feature makes only sense
# when ALLOW_EMAIL_LOGIN is set to 'true'. Additionally admins can send
# system wide messages.
LDAP_ADMIN_GROUP_FILTER: "(memberof=cn=ADMINGROUPNAME,ou=groups,dc=DOMAIN,dc=TLD)"
ALLOW_EMAIL_LOGIN: "true"
# Same property, unfortunately with different names in
# different locations
SHARELATEX_REDIS_HOST: redis
REDIS_HOST: redis
REDIS_PORT: 6379
# All users in the LDAP_CONTACT_FILTER are loaded from the ldap server into contacts.
LDAP_CONTACT_FILTER: "(memberof=cn=GROUPNAME,ou=groups,dc=DOMAIN,dc=TLD)"
LDAP_CONTACTS: "false"
ENABLED_LINKED_FILE_TYPES: 'url,project_file'
# Same property, unfortunately with different names in
# different locations
SHARELATEX_REDIS_HOST: redis
REDIS_HOST: redis
REDIS_PORT: 6379
# Enables Thumbnail generation using ImageMagick
ENABLE_CONVERSIONS: 'true'
ENABLED_LINKED_FILE_TYPES: "url,project_file"
mongo:
restart: always
image: mongo
container_name: mongo
expose:
- 27017
volumes:
- ${MYDATA}/mongo_data:/data/db
healthcheck:
test: echo 'db.stats().ok' | mongo localhost:27017/test --quiet
interval: 10s
timeout: 10s
retries: 5
labels:
- "traefik.enable=true"
- "traefik.tcp.routers.mongodb.rule=HostSNI(`*`)"
- "traefik.tcp.services.mongodb.loadbalancer.server.port=27017"
- "traefik.tcp.routers.mongodb.tls=true"
- "traefik.tcp.routers.mongodb.entrypoints=mongo"
networks:
- web
# Enables Thumbnail generation using ImageMagick
ENABLE_CONVERSIONS: "true"
redis:
restart: always
image: redis:5.0.0
container_name: redis
# modify to get rid of the redis issue #35 and #19 with a better solution
# WARNING: /proc/sys/net/core/somaxconn is set to the lower value of 128.
# for vm overcommit: enable first on host system
# sysctl vm.overcommit_memory=1 (and add it to rc.local)
# then you do not need it in the redis container
sysctls:
- net.core.somaxconn=65535
# - vm.overcommit_memory=1
expose:
- 6379
volumes:
- ${MYDATA}/redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
networks:
- web
mongo:
restart: always
image: mongo:4.4
container_name: mongo
expose:
- 27017
volumes:
- ${MYDATA}/mongo_data:/data/db
healthcheck:
test: echo 'db.stats().ok' | mongo localhost:27017/test --quiet
interval: 10s
timeout: 10s
retries: 5
labels:
- "traefik.enable=true"
- "traefik.tcp.routers.mongodb.rule=HostSNI(`*`)"
- "traefik.tcp.services.mongodb.loadbalancer.server.port=27017"
- "traefik.tcp.routers.mongodb.tls=true"
- "traefik.tcp.routers.mongodb.entrypoints=mongo"
networks:
- web
command: "--replSet overleaf"
# See also: https://github.com/overleaf/overleaf/issues/1120
mongoinit:
image: mongo:4.4
# this container will exit after executing the command
restart: "no"
depends_on:
mongo:
condition: service_healthy
entrypoint:
[
"mongo",
"--host",
"mongo:27017",
"--eval",
'rs.initiate({ _id: "overleaf", members: [ { _id: 0, host: "mongo:27017" } ] })',
]
redis:
restart: always
image: redis:6.2
container_name: redis
# modify to get rid of the redis issue #35 and #19 with a better solution
# WARNING: /proc/sys/net/core/somaxconn is set to the lower value of 128.
# for vm overcommit: enable first on host system
# sysctl vm.overcommit_memory=1 (and add it to rc.local)
# then you do not need it in the redis container
sysctls:
- net.core.somaxconn=65535
# - vm.overcommit_memory=1
expose:
- 6379
volumes:
- ${MYDATA}/redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
networks:
- web
networks:
web:
external: true

View File

@@ -1,4 +1,4 @@
FROM sharelatex/sharelatex:3.3.2
FROM sharelatex/sharelatex:4.1.1
# FROM sharelatex/sharelatex:latest
# latest might not be tested
# e.g. the AuthenticationManager.js script had to be adapted after versions 2.3.1
@@ -13,73 +13,70 @@ ARG admin_is_sysadmin
# set workdir (might solve issue #2 - see https://stackoverflow.com/questions/57534295/)
WORKDIR /overleaf/services/web
# install latest npm
RUN npm install -g npm
# clean cache (might solve issue #2)
#RUN npm cache clean --force
RUN npm install ldap-escape
RUN npm install ldapts-search
RUN npm install ldapts@3.2.4
RUN npm install ldap-escape
#RUN npm install bcrypt@5.0.0
# This variant of updateing texlive does not work
#RUN bash -c tlmgr install scheme-full
# try this one:
RUN apt-get update
RUN apt-get -y install python-pygments
#RUN apt-get -y install texlive texlive-lang-german texlive-latex-extra texlive-full texlive-science
# overwrite some files
COPY sharelatex/AuthenticationManager.js /overleaf/services/web/app/src/Features/Authentication/
COPY sharelatex/ContactController.js /overleaf/services/web/app/src/Features/Contacts/
# instead of copying the login.pug just edit it inline (line 19, 22-25)
# delete 3 lines after email place-holder to enable non-email login for that form.
RUN sed -iE '/type=.*email.*/d' /overleaf/services/web/app/views/user/login.pug
# RUN sed -iE '/email@example.com/{n;N;N;d}' /overleaf/services/web/app/views/user/login.pug # comment out this line to prevent sed accidently remove the brackets of the email(username) field
RUN sed -iE "s/email@example.com/${login_text:-user}/g" /overleaf/services/web/app/views/user/login.pug
# Collaboration settings display (share project placeholder) | edit line 146
# share.pug file was removed in later versions
# RUN sed -iE "s%placeholder=.*$%placeholder=\"${collab_text}\"%g" /overleaf/services/web/app/views/project/editor/share.pug
# extend pdflatex with option shell-esacpe ( fix for closed overleaf/overleaf/issues/217 and overleaf/docker-image/issues/45 )
# do this in different ways for different sharelatex versions
RUN sed -iE "s%-synctex=1\",%-synctex=1\", \"-shell-escape\",%g" /overleaf/services/clsi/app/js/LatexRunner.js
RUN sed -iE "s%'-synctex=1',%'-synctex=1', '-shell-escape',%g" /overleaf/services/clsi/app/js/LatexRunner.js
COPY sharelatex/AuthenticationManager.js /overleaf/services/web/app/src/Features/Authentication/
COPY sharelatex/ContactController.js /overleaf/services/web/app/src/Features/Contacts/
# Too much changes to do inline (>10 Lines).
COPY sharelatex/settings.pug /overleaf/services/web/app/views/user/
COPY sharelatex/navbar.pug /overleaf/services/web/app/views/layout/
COPY sharelatex/settings.pug /overleaf/services/web/app/views/user/
COPY sharelatex/navbar.pug /overleaf/services/web/app/views/layout/
# Non LDAP User Registration for Admins
COPY sharelatex/admin-index.pug /overleaf/services/web/app/views/admin/index.pug
COPY sharelatex/admin-sysadmin.pug /tmp/admin-sysadmin.pug
RUN if [ "${admin_is_sysadmin}" = "true" ] ; then cp /tmp/admin-sysadmin.pug /overleaf/services/web/app/views/admin/index.pug ; else rm /tmp/admin-sysadmin.pug ; fi
COPY sharelatex/admin-index.pug /overleaf/services/web/app/views/admin/index.pug
COPY sharelatex/admin-sysadmin.pug /tmp/admin-sysadmin.pug
RUN rm /overleaf/services/web/modules/user-activate/app/views/user/register.pug
### To remove comments entirly (bug https://github.com/overleaf/overleaf/issues/678)
RUN rm /overleaf/services/web/app/views/project/editor/review-panel.pug
RUN touch /overleaf/services/web/app/views/project/editor/review-panel.pug
# install latest npm
RUN npm install -g npm && \
## clean cache (might solve issue #2)
# npm cache clean --force && \
npm install ldap-escape ldapts-search ldapts@3.2.4 && \
# npm install bcrypt@5.0.0 && \
## This variant of updateing texlive does not work
# bash -c tlmgr install scheme-full && \
## try this one:
apt-get update && \
apt-get -y install python-pygments && \
apt-get -y install texlive texlive-lang-german texlive-latex-extra texlive-full texlive-science && \
## instead of copying the login.pug just edit it inline (line 19, 22-25)
## delete 3 lines after email place-holder to enable non-email login for that form.
sed -iE '/type=.*email.*/d' /overleaf/services/web/app/views/user/login.pug && \
## comment out this line to prevent sed accidently remove the brackets of the email(username) field
# sed -iE '/email@example.com/{n;N;N;d}' /overleaf/services/web/app/views/user/login.pug && \
sed -iE "s/email@example.com/${login_text:-user}/g" /overleaf/services/web/app/views/user/login.pug && \
## Collaboration settings display (share project placeholder) | edit line 146
## share.pug file was removed in later versions
# sed -iE "s%placeholder=.*$%placeholder=\"${collab_text}\"%g" /overleaf/services/web/app/views/project/editor/share.pug && \
## extend pdflatex with option shell-esacpe ( fix for closed overleaf/overleaf/issues/217 and overleaf/docker-image/issues/45 )
## do this in different ways for different sharelatex versions
sed -iE "s%-synctex=1\",%-synctex=1\", \"-shell-escape\",%g" /overleaf/services/clsi/app/js/LatexRunner.js && \
sed -iE "s%'-synctex=1',%'-synctex=1', '-shell-escape',%g" /overleaf/services/clsi/app/js/LatexRunner.js && \
if [ "${admin_is_sysadmin}" = "true" ] ; \
then cp /tmp/admin-sysadmin.pug /overleaf/services/web/app/views/admin/index.pug ; \
else rm /tmp/admin-sysadmin.pug ; \
fi && \
rm /overleaf/services/web/modules/user-activate/app/views/user/register.pug && \
### To remove comments entirly (bug https://github.com/overleaf/overleaf/issues/678)
rm /overleaf/services/web/app/views/project/editor/review-panel.pug && \
touch /overleaf/services/web/app/views/project/editor/review-panel.pug && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
### Nginx and Certificates
# enable https via letsencrypt
#RUN rm /etc/nginx/sites-enabled/sharelatex.conf
#COPY nginx/sharelatex.conf /etc/nginx/sites-enabled/sharelatex.conf
# RUN rm /etc/nginx/sites-enabled/sharelatex.conf
# COPY nginx/sharelatex.conf /etc/nginx/sites-enabled/sharelatex.conf
# get maintained best practice ssl from certbot
#RUN wget https://raw.githubusercontent.com/certbot/certbot/master/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx.conf -O /etc/nginx/options-ssl-nginx.conf
#RUN wget https://raw.githubusercontent.com/certbot/certbot/master/certbot/certbot/ssl-dhparams.pem -O /etc/nginx/ssl-dhparams.pem
# RUN wget https://raw.githubusercontent.com/certbot/certbot/master/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx.conf -O /etc/nginx/options-ssl-nginx.conf && \
# wget https://raw.githubusercontent.com/certbot/certbot/master/certbot/certbot/ssl-dhparams.pem -O /etc/nginx/ssl-dhparams.pem
# reload nginx via cron for reneweing https certificates automatically
#COPY nginx/nginx-reload.sh /etc/cron.weekly/
#RUN chmod 0744 /etc/cron.weekly/nginx-reload.sh
# COPY nginx/nginx-reload.sh /etc/cron.weekly/
# RUN chmod 0744 /etc/cron.weekly/nginx-reload.sh
## extract certificates from acme.json?
# COPY nginx/nginx-cert.sh /etc/cron.weekly/
# RUN chmod 0744 /etc/cron.weekly/nginx-cert.sh
# RUN echo "/usr/cron.weekly/nginx-cert.sh 2>&1 > /dev/null" > /etc/rc.local
# RUN chmod 0744 /etc/rc.local
# COPY nginx/nginx-cert.sh /etc/cron.weekly/
# RUN chmod 0744 /etc/cron.weekly/nginx-cert.sh && \
# echo "/usr/cron.weekly/nginx-cert.sh 2>&1 > /dev/null" > /etc/rc.local && \
# chmod 0744 /etc/rc.local

View File

@@ -1,3 +1,9 @@
/**
* >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
* Modified from 841df71
* <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
*/
const Settings = require('@overleaf/settings')
const { User } = require('../../models/User')
const { db, ObjectId } = require('../../infrastructure/mongodb')
@@ -6,19 +12,37 @@ const EmailHelper = require('../Helpers/EmailHelper')
const {
InvalidEmailError,
InvalidPasswordError,
ParallelLoginError,
PasswordMustBeDifferentError,
PasswordReusedError,
} = require('./AuthenticationErrors')
const util = require('util')
const HaveIBeenPwned = require('./HaveIBeenPwned')
const UserAuditLogHandler = require('../User/UserAuditLogHandler')
const logger = require('@overleaf/logger')
const DiffHelper = require('../Helpers/DiffHelper')
const Metrics = require('@overleaf/metrics')
const { Client } = require('ldapts');
const ldapEscape = require('ldap-escape');
// https://www.npmjs.com/package/@overleaf/o-error
// have a look if we can do nice error messages.
// >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
const fs = require("fs")
const { Client } = require("ldapts")
const ldapEscape = require("ldap-escape")
// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
const BCRYPT_ROUNDS = Settings.security.bcryptRounds || 12
const BCRYPT_MINOR_VERSION = Settings.security.bcryptMinorVersion || 'a'
const MAX_SIMILARITY = 0.7
const _checkWriteResult = function(result, callback) {
function _exceedsMaximumLengthRatio(password, maxSimilarity, value) {
const passwordLength = password.length
const lengthBoundSimilarity = (maxSimilarity / 2) * passwordLength
const valueLength = value.length
return (
passwordLength >= 10 * valueLength && valueLength < lengthBoundSimilarity
)
}
const _checkWriteResult = function (result, callback) {
// for MongoDB
if (result && result.modifiedCount === 1) {
callback(null, true)
@@ -27,96 +51,389 @@ const _checkWriteResult = function(result, callback) {
}
}
function _validatePasswordNotTooLong(password) {
// bcrypt has a hard limit of 72 characters.
if (password.length > 72) {
return new InvalidPasswordError({
message: 'password is too long',
info: { code: 'too_long' },
})
}
return null
}
function _metricsForSuccessfulPasswordMatch(password) {
const validationResult = AuthenticationManager.validatePassword(password)
const status =
validationResult === null ? 'success' : validationResult?.info?.code
Metrics.inc('check-password', { status })
return null
}
const AuthenticationManager = {
authenticate(query, password, callback) {
_checkUserPassword(query, password, callback) {
// Using Mongoose for legacy reasons here. The returned User instance
// gets serialized into the session and there may be subtle differences
// between the user returned by Mongoose vs mongodb (such as default values)
User.findOne(query, (error, user) => {
//console.log("Begining:" + JSON.stringify(query))
if (error) {
return callback(error)
}
if (!user || !user.hashedPassword) {
return callback(null, null, null)
}
bcrypt.compare(password, user.hashedPassword, function (error, match) {
if (error) {
return callback(error)
}
if (match) {
_metricsForSuccessfulPasswordMatch(password)
}
callback(null, user, match)
})
})
},
// >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_checkUserPassword2(query, password, callback) {
// leave original _checkUserPassword untouched, because it will be called by
// setUserPasswordInV2 (e.g. UserRegistrationHandler.js )
User.findOne(query, (error, user) => {
AuthenticationManager.authUserObj(error, user, query, password, callback)
})
},
//login with any password
login(user, password, callback) {
AuthenticationManager.checkRounds(
user,
user.hashedPassword,
// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
authenticate(query, password, auditLog, callback) {
if (typeof callback === 'undefined') {
callback = auditLog
auditLog = null
}
// >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
AuthenticationManager._checkUserPassword2(
// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
query,
password,
function (err) {
if (err) {
return callback(err)
(error, user, match) => {
if (error) {
return callback(error)
}
callback(null, user)
if (!user) {
return callback(null, null)
}
const update = { $inc: { loginEpoch: 1 } }
if (!match) {
update.$set = { lastFailedLogin: new Date() }
}
User.updateOne(
{ _id: user._id, loginEpoch: user.loginEpoch },
update,
{},
(err, result) => {
if (err) {
return callback(err)
}
if (result.modifiedCount !== 1) {
return callback(new ParallelLoginError())
}
if (!match) {
if (!auditLog) {
return callback(null, null)
} else {
return UserAuditLogHandler.addEntry(
user._id,
'failed-password-match',
user._id,
auditLog.ipAddress,
auditLog.info,
err => {
if (err) {
logger.error(
{ userId: user._id, err, info: auditLog.info },
'Error while adding AuditLog entry for failed-password-match'
)
}
callback(null, null)
}
)
}
}
AuthenticationManager.checkRounds(
user,
user.hashedPassword,
password,
function (err) {
if (err) {
return callback(err)
}
callback(null, user)
HaveIBeenPwned.checkPasswordForReuseInBackground(password)
}
)
}
)
}
)
},
createIfNotExistAndLogin(query, user, callback, uid, firstname, lastname, mail, isAdmin) {
// >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
/**
* login with any password
*/
login(user, password, callback) {
callback(null, user, true)
},
createIfNotExistAndLogin(
query,
user,
callback,
uid,
firstname,
lastname,
mail,
isAdmin
) {
if (!user) {
//console.log("Creating User:" + JSON.stringify(query))
//console.log('Creating User:' + JSON.stringify(query))
//create random pass for local userdb, does not get checked for ldap users during login
let pass = require("crypto").randomBytes(32).toString("hex")
//console.log("Creating User:" + JSON.stringify(query) + "Random Pass" + pass)
//console.log('Creating User:' + JSON.stringify(query) + 'Random Pass' + pass)
const userRegHand = require('../User/UserRegistrationHandler.js')
userRegHand.registerNewUser({
email: mail,
first_name: firstname,
last_name: lastname,
password: pass
},
function (error, user) {
if (error) {
console.log(error)
}
user.email = mail
user.isAdmin = isAdmin
user.emails[0].confirmedAt = Date.now()
user.save()
//console.log("user %s added to local library: ", mail)
User.findOne(query, (error, user) => {
const userRegHand = require("../User/UserRegistrationHandler.js")
userRegHand.registerNewUser(
{
email: mail,
first_name: firstname,
last_name: lastname,
password: pass,
},
function (error, user, setNewPasswordUrl) {
if (error) {
console.log(error)
}
if (user && user.hashedPassword) {
AuthenticationManager.login(user, "randomPass", callback)
}
})
}) // end register user
user.email = mail
user.isAdmin = isAdmin
user.emails[0].confirmedAt = Date.now()
user.save()
//console.log('user %s added to local library: ', mail)
User.findOne(query, (error, user) => {
if (error) {
console.log(error)
}
if (user && user.hashedPassword) {
AuthenticationManager.login(user, "randomPass", callback)
}
})
}
) // end register user
} else {
AuthenticationManager.login(user, "randomPass", callback)
}
},
authUserObj(error, user, query, password, callback) {
if ( process.env.ALLOW_EMAIL_LOGIN && user && user.hashedPassword) {
console.log("email login for existing user " + query.email)
// check passwd against local db
bcrypt.compare(password, user.hashedPassword, function (error, match) {
if (match) {
console.log("Local user password match")
AuthenticationManager.login(user, password, callback)
} else {
console.log("Local user password mismatch, trying LDAP")
// check passwd against ldap
AuthenticationManager.ldapAuth(query, password, AuthenticationManager.createIfNotExistAndLogin, callback, user)
}
})
if (process.env.ALLOW_EMAIL_LOGIN && user && user.hashedPassword) {
console.log("email login for existing user " + query.email)
// check passwd against local db
bcrypt.compare(password, user.hashedPassword, function (error, match) {
if (match) {
console.log("Local user password match")
_metricsForSuccessfulPasswordMatch(password)
//callback(null, user, match)
AuthenticationManager.login(user, "randomPass", callback)
} else {
console.log("Local user password mismatch, trying LDAP")
// check passwd against ldap
AuthenticationManager.ldapAuth(
query,
password,
AuthenticationManager.createIfNotExistAndLogin,
callback,
user
)
}
})
} else {
// No local passwd check user has to be in ldap and use ldap credentials
AuthenticationManager.ldapAuth(query, password, AuthenticationManager.createIfNotExistAndLogin, callback, user)
AuthenticationManager.ldapAuth(
query,
password,
AuthenticationManager.createIfNotExistAndLogin,
callback,
user
)
}
return null
},
async ldapAuth(
query,
password,
onSuccessCreateUserIfNotExistent,
callback,
user
) {
const client = fs.existsSync(process.env.LDAP_SERVER_CACERT)
? new Client({
url: process.env.LDAP_SERVER,
tlsOptions: {
ca: [fs.readFileSync(process.env.LDAP_SERVER_CACERT)],
},
})
: new Client({
url: process.env.LDAP_SERVER,
})
const ldap_reader = process.env.LDAP_BIND_USER
const ldap_reader_pass = process.env.LDAP_BIND_PW
const ldap_base = process.env.LDAP_BASE
var mail = query.email
var uid = query.email.split("@")[0]
var firstname = ""
var lastname = ""
var isAdmin = false
var userDn = ""
//replace all appearences of %u with uid and all %m with mail:
const replacerUid = new RegExp("%u", "g")
const replacerMail = new RegExp("%m", "g")
const filterstr = process.env.LDAP_USER_FILTER.replace(
replacerUid,
ldapEscape.filter`${uid}`
).replace(replacerMail, ldapEscape.filter`${mail}`) //replace all appearances
// check bind
try {
if (process.env.LDAP_BINDDN) {
//try to bind directly with the user trying to log in
userDn = process.env.LDAP_BINDDN.replace(
replacerUid,
ldapEscape.filter`${uid}`
).replace(replacerMail, ldapEscape.filter`${mail}`)
await client.bind(userDn, password)
} else {
// use fixed bind user
await client.bind(ldap_reader, ldap_reader_pass)
}
} catch (ex) {
if (process.env.LDAP_BINDDN) {
console.log("Could not bind user: " + userDn)
} else {
console.log(
"Could not bind LDAP reader: " + ldap_reader + " err: " + String(ex)
)
}
return callback(null, null)
}
// get user data
try {
const { searchEntries, searchRef } = await client.search(ldap_base, {
scope: "sub",
filter: filterstr,
})
await searchEntries
console.log(JSON.stringify(searchEntries))
if (searchEntries[0]) {
mail = searchEntries[0].mail
uid = searchEntries[0].uid
firstname = searchEntries[0].givenName
lastname = searchEntries[0].sn
if (!process.env.LDAP_BINDDN) {
//dn is already correctly assembled
userDn = searchEntries[0].dn
}
console.log(
`Found user: ${mail} Name: ${firstname} ${lastname} DN: ${userDn}`
)
}
} catch (ex) {
console.log(
"An Error occured while getting user data during ldapsearch: " +
String(ex)
)
await client.unbind()
return callback(null, null)
}
try {
// if admin filter is set - only set admin for user in ldap group
// does not matter - admin is deactivated: managed through ldap
if (process.env.LDAP_ADMIN_GROUP_FILTER) {
const adminfilter = process.env.LDAP_ADMIN_GROUP_FILTER.replace(
replacerUid,
ldapEscape.filter`${uid}`
).replace(replacerMail, ldapEscape.filter`${mail}`)
adminEntry = await client.search(ldap_base, {
scope: "sub",
filter: adminfilter,
})
await adminEntry
//console.log('Admin Search response:' + JSON.stringify(adminEntry.searchEntries))
if (adminEntry.searchEntries[0]) {
console.log("is Admin")
isAdmin = true
}
}
} catch (ex) {
console.log(
"An Error occured while checking for admin rights - setting admin rights to false: " +
String(ex)
)
isAdmin = false
} finally {
await client.unbind()
}
if (mail == "" || userDn == "") {
console.log(
"Mail / userDn not set - exit. This should not happen - please set mail-entry in ldap."
)
return callback(null, null)
}
if (!process.env.BINDDN) {
//since we used a fixed bind user to obtain the correct userDn we need to bind again to authenticate
try {
await client.bind(userDn, password)
} catch (ex) {
console.log("Could not bind User: " + userDn + " err: " + String(ex))
return callback(null, null)
} finally {
await client.unbind()
}
}
//console.log('Logging in user: ' + mail + ' Name: ' + firstname + ' ' + lastname + ' isAdmin: ' + String(isAdmin))
// we are authenticated now let's set the query to the correct mail from ldap
query.email = mail
User.findOne(query, (error, user) => {
if (error) {
console.log(error)
}
if (user && user.hashedPassword) {
//console.log('******************** LOGIN ******************')
AuthenticationManager.login(user, "randomPass", callback)
} else {
onSuccessCreateUserIfNotExistent(
query,
user,
callback,
uid,
firstname,
lastname,
mail,
isAdmin
)
}
})
},
// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
validateEmail(email) {
// we use the emailadress from the ldap
// therefore we do not enforce checks here
const parsed = EmailHelper.parseEmail(email)
//if (!parsed) {
// return new InvalidEmailError({ message: 'email not valid' })
//}
if (!parsed) {
return new InvalidEmailError({ message: 'email not valid' })
}
return null
},
@@ -131,6 +448,8 @@ const AuthenticationManager = {
})
}
Metrics.inc('try-validate-password')
let allowAnyChars, min, max
if (Settings.passwordStrengthOptions) {
allowAnyChars = Settings.passwordStrengthOptions.allowAnyChars === true
@@ -140,7 +459,7 @@ const AuthenticationManager = {
}
}
allowAnyChars = !!allowAnyChars
min = min || 6
min = min || 8
max = max || 72
// we don't support passwords > 72 characters in length, because bcrypt truncates them
@@ -160,6 +479,10 @@ const AuthenticationManager = {
info: { code: 'too_long' },
})
}
const passwordLengthError = _validatePasswordNotTooLong(password)
if (passwordLengthError) {
return passwordLengthError
}
if (
!allowAnyChars &&
!AuthenticationManager._passwordCharactersAreValid(password)
@@ -168,9 +491,39 @@ const AuthenticationManager = {
message: 'password contains an invalid character',
info: { code: 'invalid_character' },
})
}
if (typeof email === 'string' && email !== '') {
const startOfEmail = email.split('@')[0]
if (
password.includes(email) ||
password.includes(startOfEmail) ||
email.includes(password)
) {
return new InvalidPasswordError({
message: 'password contains part of email address',
info: { code: 'contains_email' },
})
}
return null
},
try {
const passwordTooSimilarError =
AuthenticationManager._validatePasswordNotTooSimilar(password, email)
if (passwordTooSimilarError) {
Metrics.inc('password-too-similar-to-email')
return new InvalidPasswordError({
message: 'password is too similar to email address',
info: { code: 'too_similar' },
})
}
} catch (error) {
logger.error(
{ error },
'error while checking password similarity to email'
)
}
// TODO: remove this check once the password-too-similar checks are active?
}
return null
},
setUserPassword(user, password, callback) {
AuthenticationManager.setUserPasswordInV2(user, password, callback)
@@ -178,20 +531,24 @@ const AuthenticationManager = {
checkRounds(user, hashedPassword, password, callback) {
// Temporarily disable this function, TODO: re-enable this
//return callback()
if (Settings.security.disableBcryptRoundsUpgrades) {
return callback()
}
// check current number of rounds and rehash if necessary
const currentRounds = bcrypt.getRounds(hashedPassword)
if (currentRounds < BCRYPT_ROUNDS) {
AuthenticationManager.setUserPassword(user, password, callback)
AuthenticationManager._setUserPasswordInMongo(user, password, callback)
} else {
callback()
}
},
hashPassword(password, callback) {
// Double-check the size to avoid truncating in bcrypt.
const error = _validatePasswordNotTooLong(password)
if (error) {
return callback(error)
}
bcrypt.genSalt(BCRYPT_ROUNDS, BCRYPT_MINOR_VERSION, function (error, salt) {
if (error) {
return callback(error)
@@ -201,23 +558,52 @@ const AuthenticationManager = {
},
setUserPasswordInV2(user, password, callback) {
//if (!user || !user.email || !user._id) {
// return callback(new Error('invalid user object'))
//}
console.log("Setting pass for user: " + JSON.stringify(user))
if (!user || !user.email || !user._id) {
return callback(new Error('invalid user object'))
}
const validationError = this.validatePassword(password, user.email)
if (validationError) {
return callback(validationError)
}
// check if we can log in with this password. In which case we should reject it,
// because it is the same as the existing password.
AuthenticationManager._checkUserPassword(
{ _id: user._id },
password,
(err, _user, match) => {
if (err) {
return callback(err)
}
if (match) {
return callback(new PasswordMustBeDifferentError())
}
HaveIBeenPwned.checkPasswordForReuse(
password,
(error, isPasswordReused) => {
if (error) {
logger.err({ error }, 'cannot check password for re-use')
}
if (!error && isPasswordReused) {
return callback(new PasswordReusedError())
}
// password is strong enough or the validation with the service did not happen
this._setUserPasswordInMongo(user, password, callback)
}
)
}
)
},
_setUserPasswordInMongo(user, password, callback) {
this.hashPassword(password, function (error, hash) {
if (error) {
return callback(error)
}
db.users.updateOne(
{
_id: ObjectId(user._id.toString()),
},
{ _id: ObjectId(user._id.toString()) },
{
$set: {
hashedPassword: hash,
@@ -265,119 +651,76 @@ const AuthenticationManager = {
return true
},
async ldapAuth(query, password, onSuccessCreateUserIfNotExistent, callback, user) {
const client = new Client({
url: process.env.LDAP_SERVER,
});
const ldap_reader = process.env.LDAP_BIND_USER
const ldap_reader_pass = process.env.LDAP_BIND_PW
const ldap_base = process.env.LDAP_BASE
var mail = query.email
var uid = query.email.split('@')[0]
var firstname = ""
var lastname = ""
var isAdmin = false
var userDn = ""
//replace all appearences of %u with uid and all %m with mail:
const replacerUid = new RegExp("%u", "g")
const replacerMail = new RegExp("%m","g")
const filterstr = process.env.LDAP_USER_FILTER.replace(replacerUid, ldapEscape.filter`${uid}`).replace(replacerMail, ldapEscape.filter`${mail}`) //replace all appearances
// check bind
try {
if(process.env.LDAP_BINDDN){ //try to bind directly with the user trying to log in
userDn = process.env.LDAP_BINDDN.replace(replacerUid,ldapEscape.filter`${uid}`).replace(replacerMail, ldapEscape.filter`${mail}`);
await client.bind(userDn,password);
}else{// use fixed bind user
await client.bind(ldap_reader, ldap_reader_pass);
}
} catch (ex) {
if(process.env.LDAP_BINDDN){
console.log("Could not bind user: " + userDn);
}else{
console.log("Could not bind LDAP reader: " + ldap_reader + " err: " + String(ex))
}
return callback(null, null)
}
// get user data
try {
const {searchEntries, searchRef,} = await client.search(ldap_base, {
scope: 'sub',
filter: filterstr ,
});
await searchEntries
console.log(JSON.stringify(searchEntries))
if (searchEntries[0]) {
mail = searchEntries[0].mail
uid = searchEntries[0].uid
firstname = searchEntries[0].givenName
lastname = searchEntries[0].sn
if(!process.env.LDAP_BINDDN){ //dn is already correctly assembled
userDn = searchEntries[0].dn
}
console.log("Found user: " + mail + " Name: " + firstname + " " + lastname + " DN: " + userDn)
}
} catch (ex) {
console.log("An Error occured while getting user data during ldapsearch: " + String(ex))
await client.unbind();
return callback(null, null)
}
try {
// if admin filter is set - only set admin for user in ldap group
// does not matter - admin is deactivated: managed through ldap
if (process.env.LDAP_ADMIN_GROUP_FILTER) {
const adminfilter = process.env.LDAP_ADMIN_GROUP_FILTER.replace(replacerUid, ldapEscape.filter`${uid}`).replace(replacerMail, ldapEscape.filter`${mail}`)
adminEntry = await client.search(ldap_base, {
scope: 'sub',
filter: adminfilter,
});
await adminEntry;
//console.log("Admin Search response:" + JSON.stringify(adminEntry.searchEntries))
if (adminEntry.searchEntries[0]) {
console.log("is Admin")
isAdmin=true;
/**
* Check if the password is similar to (parts of) the email address.
* For now, this merely sends a metric when the password and
* email address are deemed to be too similar to each other.
* Later we will reject passwords that fail this check.
*
* This logic was borrowed from the django project:
* https://github.com/django/django/blob/fa3afc5d86f1f040922cca2029d6a34301597a70/django/contrib/auth/password_validation.py#L159-L214
*/
_validatePasswordNotTooSimilar(password, email) {
password = password.toLowerCase()
email = email.toLowerCase()
const stringsToCheck = [email]
.concat(email.split(/\W+/))
.concat(email.split(/@/))
for (const emailPart of stringsToCheck) {
if (!_exceedsMaximumLengthRatio(password, MAX_SIMILARITY, emailPart)) {
const similarity = DiffHelper.stringSimilarity(password, emailPart)
if (similarity > MAX_SIMILARITY) {
logger.warn(
{ email, emailPart, similarity, maxSimilarity: MAX_SIMILARITY },
'Password too similar to email'
)
return new Error('password is too similar to email')
}
}
} catch (ex) {
console.log("An Error occured while checking for admin rights - setting admin rights to false: " + String(ex))
isAdmin = false;
} finally {
await client.unbind();
}
if (mail == "" || userDn == "") {
console.log("Mail / userDn not set - exit. This should not happen - please set mail-entry in ldap.")
return callback(null, null)
}
},
if(!process.env.BINDDN){//since we used a fixed bind user to obtain the correct userDn we need to bind again to authenticate
try {
await client.bind(userDn, password);
} catch (ex) {
console.log("Could not bind User: " + userDn + " err: " + String(ex))
return callback(null, null)
} finally{
await client.unbind()
}
getMessageForInvalidPasswordError(error, req) {
const errorCode = error?.info?.code
const message = {
type: 'error',
}
//console.log("Logging in user: " + mail + " Name: " + firstname + " " + lastname + " isAdmin: " + String(isAdmin))
// we are authenticated now let's set the query to the correct mail from ldap
query.email = mail
User.findOne(query, (error, user) => {
if (error) {
console.log(error)
}
if (user && user.hashedPassword) {
//console.log("******************** LOGIN ******************")
AuthenticationManager.login(user, "randomPass", callback)
} else {
onSuccessCreateUserIfNotExistent(query, user, callback, uid, firstname, lastname, mail, isAdmin)
}
})
}
switch (errorCode) {
case 'not_set':
message.key = 'password-not-set'
message.text = req.i18n.translate('invalid_password_not_set')
break
case 'invalid_character':
message.key = 'password-invalid-character'
message.text = req.i18n.translate('invalid_password_invalid_character')
break
case 'contains_email':
message.key = 'password-contains-email'
message.text = req.i18n.translate('invalid_password_contains_email')
break
case 'too_similar':
message.key = 'password-too-similar'
message.text = req.i18n.translate('invalid_password_too_similar')
break
case 'too_short':
message.key = 'password-too-short'
message.text = req.i18n.translate('invalid_password_too_short', {
minLength: Settings.passwordStrengthOptions?.length?.min || 8,
})
break
case 'too_long':
message.key = 'password-too-long'
message.text = req.i18n.translate('invalid_password_too_long', {
maxLength: Settings.passwordStrengthOptions?.length?.max || 72,
})
break
default:
logger.error({ err: error }, 'Unknown password validation error code')
message.text = req.i18n.translate('invalid_password')
break
}
return message
},
}
AuthenticationManager.promises = {

View File

@@ -1,139 +1,130 @@
/* eslint-disable
camelcase,
max-len,
no-unused-vars,
*/
// TODO: This file was created by bulk-decaffeinate.
// Fix any style issues and re-enable lint.
/*
* decaffeinate suggestions:
* DS101: Remove unnecessary use of Array.from
* DS102: Remove unnecessary code created because of implicit returns
* DS207: Consider shorter variations of null checks
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
/**
* >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
* Modified from 906765c
* <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
*/
let ContactsController
const AuthenticationController = require('../Authentication/AuthenticationController')
const SessionManager = require('../Authentication/SessionManager')
const ContactManager = require('./ContactManager')
const UserGetter = require('../User/UserGetter')
const logger = require('@overleaf/logger')
const Modules = require('../../infrastructure/Modules')
const { Client } = require('ldapts');
const { expressify } = require('../../util/promises')
module.exports = ContactsController = {
getContacts(req, res, next) {
const user_id = SessionManager.getLoggedInUserId(req.session)
return ContactManager.getContactIds(
user_id,
{ limit: 50 },
function (error, contact_ids) {
if (error != null) {
return next(error)
}
return UserGetter.getUsers(
contact_ids,
{
email: 1,
first_name: 1,
last_name: 1,
holdingAccount: 1,
},
function (error, contacts) {
if (error != null) {
return next(error)
}
// >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
const { Client } = require('ldapts')
// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
// UserGetter.getUsers may not preserve order so put them back in order
const positions = {}
for (let i = 0; i < contact_ids.length; i++) {
const contact_id = contact_ids[i]
positions[contact_id] = i
}
contacts.sort(
(a, b) =>
positions[a._id != null ? a._id.toString() : undefined] -
positions[b._id != null ? b._id.toString() : undefined]
)
// Don't count holding accounts to discourage users from repeating mistakes (mistyped or wrong emails, etc)
contacts = contacts.filter(c => !c.holdingAccount)
ContactsController.getLdapContacts(contacts).then((ldapcontacts) => {
contacts.push(ldapcontacts)
contacts = contacts.map(ContactsController._formatContact)
return Modules.hooks.fire('getContacts', user_id, contacts, function(
error,
additional_contacts
) {
if (error != null) {
return next(error)
}
contacts = contacts.concat(...Array.from(additional_contacts || []))
return res.send({
contacts
})
})
}).catch(e => console.log("Error appending ldap contacts" + e))
}
)
})
},
async getLdapContacts(contacts) {
if (process.env.LDAP_CONTACTS === undefined || !(process.env.LDAP_CONTACTS.toLowerCase() === 'true')) {
return contacts
}
const client = new Client({
url: process.env.LDAP_SERVER,
});
// if we need a ldap user try to bind
if (process.env.LDAP_BIND_USER) {
try {
await client.bind(process.env.LDAP_BIND_USER, process.env.LDAP_BIND_PW);
} catch (ex) {
console.log("Could not bind LDAP reader user: " + String(ex) )
}
}
const ldap_base = process.env.LDAP_BASE
// get user data
try {
// if you need an client.bind do it here.
const {searchEntries,searchReferences,} = await client.search(ldap_base, {scope: 'sub',filter: process.env.LDAP_CONTACT_FILTER ,});
await searchEntries;
for (var i = 0; i < searchEntries.length; i++) {
var entry = new Map()
var obj = searchEntries[i];
entry['_id'] = undefined
entry['email'] = obj['mail']
entry['first_name'] = obj['givenName']
entry['last_name'] = obj['sn']
entry['type'] = "user"
// Only add to contacts if entry is not there.
if(contacts.indexOf(entry) === -1) {
contacts.push(entry);
}
}
} catch (ex) {
console.log(String(ex))
}
//console.log(JSON.stringify(contacts))
finally {
// even if we did not use bind - the constructor of
// new Client() opens a socket to the ldap server
client.unbind()
return contacts
}
},
_formatContact(contact) {
return {
id: contact._id != null ? contact._id.toString() : undefined,
email: contact.email || '',
first_name: contact.first_name || '',
last_name: contact.last_name || '',
type: 'user',
}
},
function _formatContact(contact) {
return {
id: contact._id?.toString(),
email: contact.email || '',
first_name: contact.first_name || '',
last_name: contact.last_name || '',
type: 'user',
}
}
async function getContacts(req, res) {
const userId = SessionManager.getLoggedInUserId(req.session)
const contactIds = await ContactManager.promises.getContactIds(userId, {
limit: 50,
})
let contacts = await UserGetter.promises.getUsers(contactIds, {
email: 1,
first_name: 1,
last_name: 1,
holdingAccount: 1,
})
// UserGetter.getUsers may not preserve order so put them back in order
const positions = {}
for (let i = 0; i < contactIds.length; i++) {
const contact_id = contactIds[i]
positions[contact_id] = i
}
contacts.sort(
(a, b) => positions[a._id?.toString()] - positions[b._id?.toString()]
)
// Don't count holding accounts to discourage users from repeating mistakes (mistyped or wrong emails, etc)
contacts = contacts.filter((c) => !c.holdingAccount)
// >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
const ldapcontacts = getLdapContacts(contacts)
contacts.push(ldapcontacts)
// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
contacts = contacts.map(_formatContact)
const additionalContacts = await Modules.promises.hooks.fire(
'getContacts',
userId,
contacts
)
contacts = contacts.concat(...(additionalContacts || []))
return res.json({
contacts,
})
}
// >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
async function getLdapContacts(contacts) {
if (
process.env.LDAP_CONTACTS === undefined ||
!(process.env.LDAP_CONTACTS.toLowerCase() === 'true')
) {
return contacts
}
const client = new Client({
url: process.env.LDAP_SERVER,
})
// if we need a ldap user try to bind
if (process.env.LDAP_BIND_USER) {
try {
await client.bind(process.env.LDAP_BIND_USER, process.env.LDAP_BIND_PW)
} catch (ex) {
console.log('Could not bind LDAP reader user: ' + String(ex))
}
}
const ldap_base = process.env.LDAP_BASE
// get user data
try {
// if you need an client.bind do it here.
const { searchEntries, searchReferences } = await client.search(ldap_base, {
scope: 'sub',
filter: process.env.LDAP_CONTACT_FILTER,
})
await searchEntries
for (var i = 0; i < searchEntries.length; i++) {
var entry = new Map()
var obj = searchEntries[i]
entry['_id'] = undefined
entry['email'] = obj['mail']
entry['first_name'] = obj['givenName']
entry['last_name'] = obj['sn']
entry['type'] = 'user'
// Only add to contacts if entry is not there.
if (contacts.indexOf(entry) === -1) {
contacts.push(entry)
}
}
} catch (ex) {
console.log(String(ex))
} finally {
// console.log(JSON.stringify(contacts))
// even if we did not use bind - the constructor of
// new Client() opens a socket to the ldap server
client.unbind()
return contacts
}
}
// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
module.exports = {
getContacts: expressify(getContacts),
}