Skip to content

Commit a683b57

Browse files
authored
feat(backend:auth): Allow the LDAP mail attribute to be used as the login attribute, and allow users to authenticate using either the login attribute or their email address
1 parent 6998b1a commit a683b57

File tree

4 files changed

+41
-35
lines changed

4 files changed

+41
-35
lines changed

backend/src/applications/users/constants/user.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
export const USER_PASSWORD_MIN_LENGTH = 8
88
export const USER_MAX_PASSWORD_ATTEMPTS = 10
9-
export const USER_LOGIN_VALIDATION = /^(?! )(?!.* $)[a-zA-Z0-9\-\\._ ]{2,255}$/
9+
export const USER_LOGIN_VALIDATION = /^(?! )(?!.* $)[a-zA-Z0-9@\-\\._ ]{2,255}$/
1010

1111
export const USER_PATH = {
1212
TMP: 'tmp',

backend/src/authentication/constants/auth-ldap.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
export enum LDAP_LOGIN_ATTR {
88
UID = 'uid',
99
CN = 'cn',
10+
MAIL = 'mail',
1011
SAM = 'sAMAccountName',
1112
UPN = 'userPrincipalName'
1213
}

backend/src/authentication/services/auth-methods/auth-method-ldap.service.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export class AuthMethodLdapService implements AuthMethod {
3333
) {}
3434

3535
async validateUser(login: string, password: string, ip?: string, scope?: AUTH_SCOPE): Promise<UserModel> {
36+
// Find user from his login or email
3637
let user: UserModel = await this.usersManager.findUser(this.dbLogin(login), false)
3738
if (user) {
3839
if (user.isGuest) {
@@ -44,7 +45,8 @@ export class AuthMethodLdapService implements AuthMethod {
4445
throw new HttpException('Account locked', HttpStatus.FORBIDDEN)
4546
}
4647
}
47-
const entry: false | LdapUserEntry = await this.checkAuth(login, password)
48+
// If a user was found, use the stored login. This allows logging in with an email.
49+
const entry: false | LdapUserEntry = await this.checkAuth(user?.login || login, password)
4850
if (entry === false) {
4951
// LDAP auth failed
5052
if (user) {
@@ -239,9 +241,7 @@ export class AuthMethodLdapService implements AuthMethod {
239241
}
240242

241243
private dbLogin(login: string): string {
242-
if (login.includes('@')) {
243-
return login.split('@')[0]
244-
} else if (login.includes('\\')) {
244+
if (login.includes('\\')) {
245245
return login.split('\\').slice(-1)[0]
246246
}
247247
return login
@@ -264,7 +264,7 @@ export class AuthMethodLdapService implements AuthMethod {
264264
// Build a safe LDAP filter to search for a user.
265265
// Important: - Values passed to EqualityFilter are auto-escaped by ldapts
266266
// - extraFilter is appended as-is (assumed trusted configuration)
267-
// Output: (&(|([email protected])(sAMAccountName=john.doe)(cn=john.doe))(*extraFilter*))
267+
// Output: (&(|([email protected])(sAMAccountName=john.doe)(cn=john.doe)(uid=john.doe)([email protected]))(*extraFilter*))
268268

269269
// Handle the case where the sAMAccountName is provided in domain-qualified format (e.g., SYNC_IN\\user)
270270
// Note: sAMAccountName is always stored without the domain in Active Directory.
@@ -273,12 +273,14 @@ export class AuthMethodLdapService implements AuthMethod {
273273
const or = new OrFilter({
274274
filters: this.isAD
275275
? [
276-
new EqualityFilter({ attribute: LDAP_LOGIN_ATTR.UPN, value: login }),
277-
new EqualityFilter({ attribute: LDAP_LOGIN_ATTR.SAM, value: dbLogin })
276+
new EqualityFilter({ attribute: LDAP_LOGIN_ATTR.SAM, value: dbLogin }),
277+
new EqualityFilter({ attribute: LDAP_LOGIN_ATTR.UPN, value: dbLogin }),
278+
new EqualityFilter({ attribute: LDAP_LOGIN_ATTR.MAIL, value: dbLogin })
278279
]
279280
: [
280281
new EqualityFilter({ attribute: LDAP_LOGIN_ATTR.UID, value: dbLogin }),
281-
new EqualityFilter({ attribute: LDAP_LOGIN_ATTR.CN, value: dbLogin })
282+
new EqualityFilter({ attribute: LDAP_LOGIN_ATTR.CN, value: dbLogin }),
283+
new EqualityFilter({ attribute: LDAP_LOGIN_ATTR.MAIL, value: dbLogin })
282284
]
283285
})
284286

environment/environment.dist.yaml

Lines changed: 29 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@ logger:
1919
# stdout: if false logs are written to the run directory
2020
# default: `true`
2121
stdout: true
22-
# colorize output
22+
# Colorize output.
2323
# default: `true`
2424
colorize: true
25-
# path to the log file used when stdout is set to false
25+
# Path to the log file used when stdout is set to false
2626
filePath:
2727
mysql:
2828
# required
@@ -33,20 +33,20 @@ cache:
3333
# adapter: `mysql` | `redis`
3434
# default: `mysql`
3535
adapter: mysql
36-
# ttl in seconds
36+
# TTL in seconds
3737
# default: `60`
3838
ttl: 60
39-
# redis adapter url
39+
# Redis adapter url
4040
# default: `redis://127.0.0.1:6379`
4141
redis: redis://127.0.0.1:6379
4242
websocket:
4343
# adapter: `cluster` (Node.js Workers: default) | `redis`
4444
# default: `cluster`
4545
adapter: cluster
46-
# cors origin allowed
46+
# Cors origin allowed
4747
# default: `*`
4848
corsOrigin: '*'
49-
# redis adapter url
49+
# Redis adapter url
5050
# default: `redis://127.0.0.1:6379`
5151
redis: redis://127.0.0.1:6379
5252
mail:
@@ -59,9 +59,9 @@ mail:
5959
auth:
6060
user: user
6161
pass: password
62-
# secure: defines if the connection should use SSL (if true) or not (if false)
63-
# note: setting `secure: false` does not necessarily mean messages are sent in plaintext
64-
# if the server supports STARTTLS, the connection is usually upgraded to TLS automatically
62+
# Defines if the connection should use SSL (if true) or not (if false)
63+
# Note: setting `secure: false` does not necessarily mean messages are sent in plaintext
64+
# If the server supports STARTTLS, the connection is usually upgraded to TLS automatically
6565
# default: `false`
6666
secure: false
6767
# ignoreTLS: if true, disables the use of STARTTLS even if the server advertises it
@@ -70,43 +70,43 @@ mail:
7070
# rejectUnauthorized: reject the connection if the server's TLS certificate is invalid
7171
# default: false
7272
rejectUnauthorized: false
73-
# enable logger
73+
# Enable logger
7474
# default: `false`
7575
logger: false
76-
# set log level to debug
76+
# Set log level to debug
7777
# default: `false`
7878
debug: false
7979
auth:
8080
# adapter : `mysql` | `ldap`
8181
# default: `mysql`
8282
method: mysql
83-
# key used to encrypt user secret keys in the database
84-
# optional, but strongly recommended
85-
# warning: do not change or remove the encryption key after MFA activation, or the codes will become invalid
83+
# Key used to encrypt user secret keys in the database
84+
# Optional but strongly recommended
85+
# Warning: do not change or remove the encryption key after MFA activation, or the codes will become invalid
8686
encryptionKey: changeEncryptionKeyWithStrongKey
87-
# multifactor authentication
87+
# Multifactor authentication
8888
mfa:
89-
# totp configuration
89+
# TOTP configuration
9090
totp:
91-
# enable TOTP authentication
91+
# Enable TOTP authentication
9292
# default: true
9393
enabled: true
94-
# name displayed in the authentication app (FreeOTP, Proton Authenticator, Aegis Authenticator etc.)
94+
# Name displayed in the authentication app (FreeOTP, Proton Authenticator, Aegis Authenticator etc.)
9595
# default: Sync-in
9696
issuer: Sync-in
9797
# cookie sameSite setting: `lax` | `strict`
9898
# default: `strict`
9999
cookieSameSite: strict
100100
token:
101101
access:
102-
# used for token and cookie signatures
102+
# Used for token and cookie signatures
103103
# required
104104
secret: changeAccessWithStrongSecret
105105
# token expiration = cookie maxAge
106106
# default: `30m`
107107
expiration: 30m
108108
refresh:
109-
# used for token and cookie signatures
109+
# Used for token and cookie signatures
110110
# required
111111
secret: changeRefreshWithStrongSecret
112112
# token expiration = cookie maxAge
@@ -120,10 +120,13 @@ auth:
120120
# filter, e.g: (acl=admin)
121121
filter:
122122
attributes:
123-
# login attribute: `uid` | `cn` | `sAMAccountName` | `userPrincipalName`, used to authenticate the user
123+
# Login attribute used to construct the user's DN for binding.
124+
# The value of this attribute is used as the naming attribute (first RDN) when forming the Distinguished Name (DN) during authentication
125+
# login: `uid` | `cn` | `mail` | `sAMAccountName` | `userPrincipalName`, used to authenticate the user
124126
# default: `uid`
125127
login: uid
126-
# email attribute: `mail` or `email`
128+
# Attribute used to retrieve the user's email address
129+
# email: `mail` or `email`
127130
# default: `mail`
128131
email: mail
129132
# adminGroup: The CN of a group containing Sync-in administrators (e.g., administrators)
@@ -148,14 +151,14 @@ applications:
148151
# enable onlyoffice integration
149152
# default: false
150153
enabled: false
151-
# for an external server (e.g., https://onlyoffice.domain.com), remember the url must be accessible from browser !
152-
# if externalServer is empty (case of official docker compose), we use the local instance
154+
# For an external server (e.g., https://onlyoffice.domain.com), remember the url must be accessible from browser!
155+
# If externalServer is empty (case of official docker compose), we use the local instance
153156
# default: null
154157
externalServer:
155-
# secret used for jwt tokens, it must be the same on the onlyoffice server
158+
# Secret used for jwt tokens, it must be the same on the onlyoffice server
156159
# required
157160
secret: onlyOfficeSecret
158-
# if you use https, set to `true`
161+
# If you use https, set to `true`.
159162
# default: `false`
160163
verifySSL: false
161164
appStore:

0 commit comments

Comments
 (0)