Skip to content

Commit 0a0dd09

Browse files
authored
Feature: add ldap support (#324)
* add ldap support --------- Co-authored-by: Mario Melcher <[email protected]>
1 parent b382aa2 commit 0a0dd09

17 files changed

+942
-66
lines changed

.env

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,17 @@ AWS_REGION=
2121
AWS_S3_BUCKET_NAME=
2222

2323
# optional
24+
#LDAP_ENABLED=true
25+
#LDAP_TLS_NO_VERIFY=false
26+
#LDAP_URL='ldaps://ldap.example.com:636/'
27+
#LDAP_BIND_USER='CN=searchuser,OU=systemaccounts,DC=example,DC=com'
28+
#LDAP_BIND_PASSWORD='searchuserpassword'
29+
#LDAP_SEARCH_DN='OU=users,DC=example,DC=com'
30+
#LDAP_USERS_SEARCH_FILTER='(&(objectClass=Person)(objectCategory=Person)(|(mail={{email}})(sAMAccountName={{email}})))'
31+
#LDAP_ATTRIBUTE_LAST_NAME='sn'
32+
#LDAP_ATTRIBUTE_FIRST_NAME='givenName'
33+
#LDAP_ATTRIBUTE_MAIL='mail'
34+
2435
#HTTPS_KEY_PATH='./secrets/ssl.key'
2536
#HTTPS_CERT_PATH='./secrets/ssl.cert'
2637
#SERVER_TIMEOUT=120000

.github/workflows/workflow.yml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,28 @@ jobs:
3636
- name: Run e2e tests
3737
run: npm run test:e2e
3838

39+
- name: Dump docker logs on failure
40+
if: failure()
41+
uses: jwalton/gh-docker-logs@v2
42+
3943
- name: Stop containers
4044
if: always()
4145
run: docker compose down
4246

47+
- name: Run ldap containers
48+
run: docker compose -f docker-compose.yml -f docker-compose.ldap.yml up -d
49+
50+
- name: Run ldap test
51+
run: npm run test:ldap
52+
53+
- name: Dump docker logs on failure
54+
if: failure()
55+
uses: jwalton/gh-docker-logs@v2
56+
57+
- name: Stop ldap containers
58+
if: always()
59+
run: docker compose -f docker-compose.yml -f docker-compose.ldap.yml down
60+
4361
- name: SonarCloud Scan
4462
uses: SonarSource/sonarcloud-github-action@master
4563
env:

README.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,16 @@
2020
## Local HTTPS config
2121

2222
- Generate keys [here](https://www.selfsignedcertificate.com/)
23-
- place in folder `/secrets` named `ssl.cert` and `ssl.key`
23+
- place in folder `/secrets` named `ssl.cert` and `ssl.key`
24+
25+
## Local LDAP test server
26+
27+
- Run `docker compose -f docker-compose.yml -f docker-compose.ldap.yml ` (see [docker docs for multiple-compose-files - merge](https://docs.docker.com/compose/multiple-compose-files/merge/))
28+
- test the login with ldap and have a look at the logs
29+
```sh
30+
curl 'http://localhost:4200/users/login' \
31+
-H 'accept: */*' \
32+
-H 'accept-language: de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7,fr;q=0.6' \
33+
-H 'content-type: application/json' \
34+
--data-raw '{"email":"[email protected]","password":"password"}'
35+
```

docker-compose.ldap.yml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
services:
2+
api:
3+
environment:
4+
LDAP_ENABLED: 'true'
5+
LDAP_TLS_NO_VERIFY: 'true'
6+
LDAP_URL: 'ldaps://ldapmock:636/'
7+
LDAP_BIND_USER: 'cn=admin,dc=ldapmock,dc=local'
8+
LDAP_BIND_PASSWORD: 'adminpass'
9+
LDAP_SEARCH_DN: 'ou=people,dc=ldapmock,dc=local'
10+
LDAP_USERS_SEARCH_FILTER: '(&(objectClass=person)(mail={{email}}))'
11+
LDAP_ATTRIBUTE_LAST_NAME: 'sn'
12+
LDAP_ATTRIBUTE_FIRST_NAME: 'givenName'
13+
LDAP_ATTRIBUTE_MAIL: 'mail'
14+
depends_on:
15+
postgres:
16+
condition: service_healthy
17+
ldapmock:
18+
condition: service_started
19+
ldapmock:
20+
container_name: ldapmock
21+
# See https://github.com/docker-ThoTeam/slapd-server-mock/tree/main
22+
# Default users in LDAP: https://github.com/docker-ThoTeam/slapd-server-mock/blob/main/bootstrap/data.ldif.TEMPLATE
23+
# e.g.: [email protected]:password
24+
image: thoteam/slapd-server-mock:latest
25+
restart: always
26+
ports:
27+
- "636:636"

package-lock.json

Lines changed: 95 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@
2020
"test:cov": "jest --projects src --coverage",
2121
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --projects src --runInBand",
2222
"test:e2e": "jest --projects test",
23-
"test:acceptance": "jest --projects test_acceptance"
23+
"test:acceptance": "jest --projects test_acceptance",
24+
"test:ldap": "jest --projects test_ldap"
2425
},
2526
"engines": {
2627
"node": ">=18.12.0"
@@ -47,6 +48,7 @@
4748
"class-transformer": "^0.5.1",
4849
"class-validator": "^0.14.0",
4950
"fs-extra": "^11.1.1",
51+
"ldapts": "^7.1.0",
5052
"looks-same": "^9.0.0",
5153
"odiff-bin": "^2.6.1",
5254
"passport": "^0.6.0",

src/users/db/dbusers.service.ts

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { HttpException, HttpStatus, Logger } from '@nestjs/common';
2+
import { CreateUserDto } from '../dto/user-create.dto';
3+
import { UserLoginResponseDto } from '../dto/user-login-response.dto';
4+
import { PrismaService } from '../../prisma/prisma.service';
5+
import { User } from '@prisma/client';
6+
import { UpdateUserDto } from '../dto/user-update.dto';
7+
import { AuthService } from '../../auth/auth.service';
8+
import { UserLoginRequestDto } from '../dto/user-login-request.dto';
9+
import { Users } from '../users.interface';
10+
11+
export class DbUsersService implements Users {
12+
private readonly logger: Logger = new Logger(DbUsersService.name);
13+
14+
constructor(
15+
private readonly prismaService: PrismaService,
16+
private readonly authService: AuthService
17+
) {}
18+
19+
async create(createUserDto: CreateUserDto): Promise<UserLoginResponseDto> {
20+
const user = {
21+
email: createUserDto.email.trim().toLowerCase(),
22+
firstName: createUserDto.firstName,
23+
lastName: createUserDto.lastName,
24+
apiKey: this.authService.generateApiKey(),
25+
password: await this.authService.encryptPassword(createUserDto.password),
26+
};
27+
28+
const userData = await this.prismaService.user.create({
29+
data: user,
30+
});
31+
32+
return new UserLoginResponseDto(userData, null);
33+
}
34+
35+
async update(id: string, userDto: UpdateUserDto): Promise<UserLoginResponseDto> {
36+
const user = await this.prismaService.user.update({
37+
where: { id },
38+
data: {
39+
email: userDto.email,
40+
firstName: userDto.firstName,
41+
lastName: userDto.lastName,
42+
},
43+
});
44+
const token = this.authService.signToken(user);
45+
return new UserLoginResponseDto(user, token);
46+
}
47+
48+
async changePassword(user: User, newPassword: string): Promise<boolean> {
49+
await this.prismaService.user.update({
50+
where: { id: user.id },
51+
data: {
52+
password: await this.authService.encryptPassword(newPassword),
53+
},
54+
});
55+
return true;
56+
}
57+
58+
async login(userLoginRequestDto: UserLoginRequestDto) {
59+
const user = await this.prismaService.user.findUnique({
60+
where: { email: userLoginRequestDto.email },
61+
});
62+
if (!user) {
63+
throw new HttpException('Invalid email or password.', HttpStatus.BAD_REQUEST);
64+
}
65+
66+
const isMatch = await this.authService.compare(userLoginRequestDto.password, user.password);
67+
68+
if (!isMatch) {
69+
throw new HttpException('Invalid email or password.', HttpStatus.BAD_REQUEST);
70+
}
71+
72+
const token = this.authService.signToken(user);
73+
return new UserLoginResponseDto(user, token);
74+
}
75+
}

0 commit comments

Comments
 (0)