diff --git a/apps/api/src/app.module.ts b/apps/api/src/app.module.ts index 7ae2b8d..ea634bd 100644 --- a/apps/api/src/app.module.ts +++ b/apps/api/src/app.module.ts @@ -2,7 +2,6 @@ import { AppController } from './app.controller'; import { AppService } from './app.service'; import { AuthModule } from './auth/auth.module'; import { env } from './config/env'; -import { LdapModule } from './ldap/ldap.module'; import { UsersModule } from './users/users.module'; import { Global, Module } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; @@ -25,7 +24,6 @@ import { MongooseModule } from '@nestjs/mongoose'; }), AuthModule, UsersModule, - LdapModule, MongooseModule.forRoot(`mongodb://${env.MONGO_HOST}`), ], providers: [AppService], diff --git a/apps/api/src/auth/auth.controller.ts b/apps/api/src/auth/auth.controller.ts index f93f9f3..8f3b558 100644 --- a/apps/api/src/auth/auth.controller.ts +++ b/apps/api/src/auth/auth.controller.ts @@ -2,7 +2,7 @@ /* eslint-disable class-methods-use-this */ /* eslint-disable import/no-extraneous-dependencies */ import { AuthService } from './auth.service'; -import { Credentials } from './types/request'; +import { Credentials } from './dto/credentials'; import type { CookieSerializeOptions } from '@fastify/cookie'; import { Body, Controller, Get, HttpException, HttpStatus, Post, Req, Res } from '@nestjs/common'; import { FastifyReply, FastifyRequest } from 'fastify'; diff --git a/apps/api/src/auth/auth.module.ts b/apps/api/src/auth/auth.module.ts index 410c056..e633f3c 100644 --- a/apps/api/src/auth/auth.module.ts +++ b/apps/api/src/auth/auth.module.ts @@ -1,4 +1,3 @@ -import { LdapModule } from '../ldap/ldap.module'; import { UsersModule } from '../users/users.module'; import { AuthController } from './auth.controller'; import { AuthService } from './auth.service'; @@ -6,7 +5,7 @@ import { Module } from '@nestjs/common'; @Module({ controllers: [AuthController], - imports: [UsersModule, LdapModule], + imports: [UsersModule], providers: [AuthService], }) // eslint-disable-next-line @typescript-eslint/no-extraneous-class diff --git a/apps/api/src/auth/auth.service.ts b/apps/api/src/auth/auth.service.ts index 5d09216..a45be96 100644 --- a/apps/api/src/auth/auth.service.ts +++ b/apps/api/src/auth/auth.service.ts @@ -1,20 +1,19 @@ -import { LdapService } from '../ldap/ldap.service'; import { UsersCache } from '../users/users.cache'; import type { DecodedToken, TokenPayload } from './types/jwt'; import { Injectable } from '@nestjs/common'; import { JwtService } from '@nestjs/jwt'; import { env } from 'src/config/env'; +import * as ldap from 'src/utils/ldap'; @Injectable() export class AuthService { constructor( - private readonly ldapService: LdapService, private readonly usersCache: UsersCache, private readonly jwtService: JwtService ) {} public async login(login: string, password: string) { - const user = await this.ldapService.authenticate(login, password); + const user = await ldap.authenticate(login, password); const { username } = user; await this.usersCache.addUser(username, user); diff --git a/apps/api/src/auth/dto/credentials.ts b/apps/api/src/auth/dto/credentials.ts new file mode 100644 index 0000000..0c642fa --- /dev/null +++ b/apps/api/src/auth/dto/credentials.ts @@ -0,0 +1,4 @@ +export class Credentials { + readonly login: string; + readonly password: string; +} diff --git a/apps/api/src/auth/types/jwt.ts b/apps/api/src/auth/types/jwt.ts index 619c81b..40a4b07 100644 --- a/apps/api/src/auth/types/jwt.ts +++ b/apps/api/src/auth/types/jwt.ts @@ -1,6 +1,6 @@ export type TokenPayload = { - username: string; domain: string; + username: string; }; export type DecodedToken = { diff --git a/apps/api/src/auth/types/request.ts b/apps/api/src/auth/types/request.ts deleted file mode 100644 index 538f351..0000000 --- a/apps/api/src/auth/types/request.ts +++ /dev/null @@ -1,4 +0,0 @@ -export type Credentials = { - login: string; - password: string; -}; diff --git a/apps/api/src/ldap/ldap.module.ts b/apps/api/src/ldap/ldap.module.ts deleted file mode 100644 index 2fae433..0000000 --- a/apps/api/src/ldap/ldap.module.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { LdapService } from './ldap.service'; -import { Module } from '@nestjs/common'; - -@Module({ - exports: [LdapService], - providers: [LdapService], -}) -// eslint-disable-next-line @typescript-eslint/no-extraneous-class -export class LdapModule {} diff --git a/apps/api/src/ldap/ldap.service.spec.ts b/apps/api/src/ldap/ldap.service.spec.ts deleted file mode 100644 index d5ed721..0000000 --- a/apps/api/src/ldap/ldap.service.spec.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { LdapService } from './ldap.service'; -import type { TestingModule } from '@nestjs/testing'; -import { Test } from '@nestjs/testing'; - -describe('LdapService', () => { - let service: LdapService; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [LdapService], - }).compile(); - - service = module.get(LdapService); - }); - - it('should be defined', () => { - expect(service).toBeDefined(); - }); -}); diff --git a/apps/api/src/ldap/ldap.service.ts b/apps/api/src/ldap/ldap.service.ts deleted file mode 100644 index 1eaab34..0000000 --- a/apps/api/src/ldap/ldap.service.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { env } from '../config/env'; -import type { User } from '../types/user'; -import type { LdapUser } from './types/user'; -import { Injectable } from '@nestjs/common'; -import type { AuthenticationOptions } from 'ldap-authentication'; -import { authenticate } from 'ldap-authentication'; - -@Injectable() -export class LdapService { - public async authenticate(login: string, password?: string) { - const options: AuthenticationOptions = { - adminDn: env.LDAP_BIND_DN, - adminPassword: env.LDAP_BIND_CREDENTIALS, - ldapOpts: { - url: env.LDAP_URL, - }, - userPassword: password, - userSearchBase: env.LDAP_BASE, - username: login, - usernameAttribute: env.LDAP_ATTRIBUTE, - verifyUserExists: password === undefined, - }; - - const { - displayName, - department, - title, - mail, - sAMAccountName: username, - }: LdapUser = await authenticate(options); - - const user: User = { - department, - displayName, - domain: env.LDAP_DOMAIN, - domainName: `${env.LDAP_DOMAIN}\\${username}`, - mail, - position: title, - username, - }; - - return user; - } -} diff --git a/apps/api/src/types/user.ts b/apps/api/src/types/user.ts deleted file mode 100644 index 8fb227d..0000000 --- a/apps/api/src/types/user.ts +++ /dev/null @@ -1,9 +0,0 @@ -export type User = { - department: string; - displayName: string; - domain: string; - domainName: string; - mail: string; - position: string; - username: string; -}; diff --git a/apps/api/src/users/users.cache.ts b/apps/api/src/users/users.cache.ts index 41cc2f1..56daa7f 100644 --- a/apps/api/src/users/users.cache.ts +++ b/apps/api/src/users/users.cache.ts @@ -1,4 +1,3 @@ -import type { User } from '../types/user'; import { CACHE_MANAGER } from '@nestjs/cache-manager'; import { Inject, Injectable } from '@nestjs/common'; import { Cache } from 'cache-manager'; @@ -6,15 +5,15 @@ import { Cache } from 'cache-manager'; @Injectable() export class UsersCache { constructor(@Inject(CACHE_MANAGER) private readonly cacheManager: Cache) {} - async getUser(username: string) { - return (await this.cacheManager.get(username)) as User; + public async getUser(username: string) { + return (await this.cacheManager.get(username)) as T; } - async addUser(username: string, user: User) { + public async addUser(username: string, user: T) { await this.cacheManager.set(username, user); } - async deleteUser(username: string) { + public async deleteUser(username: string) { if (this.cacheManager.get(username)) { await this.cacheManager.del(username); } diff --git a/apps/api/src/users/users.module.ts b/apps/api/src/users/users.module.ts index f8fbcfa..4de94c7 100644 --- a/apps/api/src/users/users.module.ts +++ b/apps/api/src/users/users.module.ts @@ -1,4 +1,3 @@ -import { LdapModule } from '../ldap/ldap.module'; import { UsersCache } from './users.cache'; import { UsersController } from './users.controller'; import { UsersService } from './users.service'; @@ -20,7 +19,6 @@ import { User, UserSchema } from 'src/schemas/user.schema'; store: redisStore, ttl: env.API_CACHE_TTL, }), - LdapModule, MongooseModule.forFeature([{ name: User.name, schema: UserSchema }]), ], providers: [UsersService, UsersCache], diff --git a/apps/api/src/users/users.service.ts b/apps/api/src/users/users.service.ts index 36a79a2..f9153d0 100644 --- a/apps/api/src/users/users.service.ts +++ b/apps/api/src/users/users.service.ts @@ -1,5 +1,4 @@ import type { DecodedToken } from '../auth/types/jwt'; -import { LdapService } from '../ldap/ldap.service'; import type { CreateUserDto } from './dto/create-user.dto'; import { UsersCache } from './users.cache'; import { Injectable, NotFoundException, UnauthorizedException } from '@nestjs/common'; @@ -8,14 +7,14 @@ import { InjectModel } from '@nestjs/mongoose'; import * as bcrypt from 'bcrypt'; import { Model } from 'mongoose'; import { User } from 'src/schemas/user.schema'; -import { generatePassword } from 'utils/password'; +import * as ldap from 'src/utils/ldap'; +import { generatePassword } from 'src/utils/password'; @Injectable() export class UsersService { constructor( private readonly usersCache: UsersCache, private readonly jwtService: JwtService, - private readonly ldapService: LdapService, @InjectModel(User.name) private userModel: Model ) {} @@ -26,7 +25,7 @@ export class UsersService { const cachedUser = await this.usersCache.getUser(username); if (!cachedUser) { - const user = await this.ldapService.authenticate(username); + const user = await ldap.authenticate(username); await this.usersCache.addUser(username, user); diff --git a/apps/api/src/ldap/types/user.ts b/apps/api/src/utils/ldap.ts similarity index 57% rename from apps/api/src/ldap/types/user.ts rename to apps/api/src/utils/ldap.ts index 76368c8..a387b92 100644 --- a/apps/api/src/ldap/types/user.ts +++ b/apps/api/src/utils/ldap.ts @@ -1,3 +1,17 @@ +import type { AuthenticationOptions } from 'ldap-authentication'; +import * as ldap from 'ldap-authentication'; +import { env } from 'src/config/env'; + +export type User = { + department: string; + displayName: string; + domain: string; + domainName: string; + mail: string; + position: string; + username: string; +}; + export type LdapUser = { accountExpires: string; badPasswordTime: string; @@ -61,3 +75,42 @@ export type LdapUser = { whenChanged: string; whenCreated: string; }; + +const BASE_OPTIONS: AuthenticationOptions = { + adminDn: env.LDAP_BIND_DN, + adminPassword: env.LDAP_BIND_CREDENTIALS, + ldapOpts: { + url: env.LDAP_URL, + }, + userSearchBase: env.LDAP_BASE, + usernameAttribute: env.LDAP_ATTRIBUTE, +}; + +export async function authenticate(login: string, password?: string) { + const options: AuthenticationOptions = { + ...BASE_OPTIONS, + userPassword: password, + username: login, + verifyUserExists: password === undefined, + }; + + const { + displayName, + department, + title, + mail, + sAMAccountName: username, + }: LdapUser = await ldap.authenticate(options); + + const user: User = { + department, + displayName, + domain: env.LDAP_DOMAIN, + domainName: `${env.LDAP_DOMAIN}\\${username}`, + mail, + position: title, + username, + }; + + return user; +} diff --git a/apps/api/utils/password.ts b/apps/api/src/utils/password.ts similarity index 100% rename from apps/api/utils/password.ts rename to apps/api/src/utils/password.ts