131 lines
3.6 KiB
TypeScript
131 lines
3.6 KiB
TypeScript
|
|
import { ForbiddenException, Injectable, UnauthorizedException } from '@nestjs/common';
|
||
|
|
import { Client } from 'ldapts';
|
||
|
|
import { AuthConfigService } from '../auth.config';
|
||
|
|
import { AuthTokenService } from '../auth-token.service';
|
||
|
|
import { AuthResult, LoginData } from '../auth.types';
|
||
|
|
|
||
|
|
@Injectable()
|
||
|
|
export class LdapAuthProvider {
|
||
|
|
constructor(
|
||
|
|
private readonly authConfig: AuthConfigService,
|
||
|
|
private readonly authToken: AuthTokenService,
|
||
|
|
) {}
|
||
|
|
|
||
|
|
async authenticate({ username, password }: LoginData): Promise<AuthResult> {
|
||
|
|
const config = this.authConfig.getConfig();
|
||
|
|
|
||
|
|
if (!config.ldap.enabled) {
|
||
|
|
throw new ForbiddenException('Login AD/LDAP desabilitado');
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!config.ldap.url) {
|
||
|
|
throw new Error('LDAP_URL nao configurado');
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!username || !password) {
|
||
|
|
throw new UnauthorizedException('Usuario e senha sao obrigatorios');
|
||
|
|
}
|
||
|
|
|
||
|
|
const client = new Client({
|
||
|
|
url: config.ldap.url,
|
||
|
|
timeout: config.ldap.timeoutMs,
|
||
|
|
connectTimeout: config.ldap.timeoutMs,
|
||
|
|
});
|
||
|
|
|
||
|
|
try {
|
||
|
|
if (config.ldap.bindDn && config.ldap.bindPassword) {
|
||
|
|
await client.bind(config.ldap.bindDn, config.ldap.bindPassword);
|
||
|
|
}
|
||
|
|
|
||
|
|
const userPrincipal = this.buildUserPrincipal(username);
|
||
|
|
await client.bind(userPrincipal, password);
|
||
|
|
|
||
|
|
const directoryUser = await this.searchUser(client, username);
|
||
|
|
const user = {
|
||
|
|
id: directoryUser?.email || userPrincipal,
|
||
|
|
name: directoryUser?.name || username,
|
||
|
|
email:
|
||
|
|
directoryUser?.email ||
|
||
|
|
(config.ldap.domain ? `${username}@${config.ldap.domain}` : null),
|
||
|
|
username: directoryUser?.username || username,
|
||
|
|
provider: 'ldap' as const,
|
||
|
|
};
|
||
|
|
|
||
|
|
return {
|
||
|
|
token: this.authToken.issueToken(user),
|
||
|
|
user,
|
||
|
|
};
|
||
|
|
} catch (_error) {
|
||
|
|
throw new UnauthorizedException('Autenticacao AD/LDAP falhou');
|
||
|
|
} finally {
|
||
|
|
await client.unbind().catch(() => undefined);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private buildUserPrincipal(username: string) {
|
||
|
|
const config = this.authConfig.getConfig();
|
||
|
|
|
||
|
|
if (config.ldap.userDnTemplate) {
|
||
|
|
return config.ldap.userDnTemplate.replaceAll('{{username}}', username);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (config.ldap.domain) {
|
||
|
|
return `${username}@${config.ldap.domain}`;
|
||
|
|
}
|
||
|
|
|
||
|
|
return username;
|
||
|
|
}
|
||
|
|
|
||
|
|
private async searchUser(client: Client, username: string) {
|
||
|
|
const config = this.authConfig.getConfig();
|
||
|
|
|
||
|
|
if (!config.ldap.searchBase) {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
const filter = config.ldap.searchFilter.replaceAll('{{username}}', username);
|
||
|
|
const { searchEntries } = await client.search(config.ldap.searchBase, {
|
||
|
|
scope: 'sub',
|
||
|
|
filter,
|
||
|
|
attributes: [
|
||
|
|
'cn',
|
||
|
|
'displayName',
|
||
|
|
'givenName',
|
||
|
|
'sn',
|
||
|
|
'mail',
|
||
|
|
'userPrincipalName',
|
||
|
|
'sAMAccountName',
|
||
|
|
],
|
||
|
|
sizeLimit: 1,
|
||
|
|
});
|
||
|
|
|
||
|
|
const entry = searchEntries[0] as Record<string, unknown> | undefined;
|
||
|
|
|
||
|
|
if (!entry) {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
const givenName = this.getFirstValue(entry.givenName);
|
||
|
|
const surname = this.getFirstValue(entry.sn);
|
||
|
|
const fullName = [givenName, surname].filter(Boolean).join(' ');
|
||
|
|
|
||
|
|
return {
|
||
|
|
name:
|
||
|
|
this.getFirstValue(entry.displayName) ||
|
||
|
|
fullName ||
|
||
|
|
this.getFirstValue(entry.cn) ||
|
||
|
|
username,
|
||
|
|
email: this.getFirstValue(entry.mail) || this.getFirstValue(entry.userPrincipalName) || null,
|
||
|
|
username: this.getFirstValue(entry.sAMAccountName) || username,
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
private getFirstValue(value: unknown): string | null {
|
||
|
|
if (Array.isArray(value)) {
|
||
|
|
return String(value[0] || '') || null;
|
||
|
|
}
|
||
|
|
|
||
|
|
return value ? String(value) : null;
|
||
|
|
}
|
||
|
|
}
|