1
0
mirror of https://github.com/stonith404/pingvin-share.git synced 2024-11-15 11:50:34 +01:00

refactor: run formatter

This commit is contained in:
Elias Schneider 2024-09-03 22:54:53 +02:00
parent e813da05ae
commit 3d2b978daf
No known key found for this signature in database
GPG Key ID: 07E623B294202B6C
7 changed files with 194 additions and 142 deletions

View File

@ -20,4 +20,4 @@ import { UserModule } from "../user/user.module";
providers: [AuthService, AuthTotpService, JwtStrategy, LdapService], providers: [AuthService, AuthTotpService, JwtStrategy, LdapService],
exports: [AuthService], exports: [AuthService],
}) })
export class AuthModule { } export class AuthModule {}

View File

@ -29,7 +29,7 @@ export class AuthService {
private emailService: EmailService, private emailService: EmailService,
private ldapService: LdapService, private ldapService: LdapService,
private userService: UserSevice, private userService: UserSevice,
) { } ) {}
private readonly logger = new Logger(AuthService.name); private readonly logger = new Logger(AuthService.name);
async signUp(dto: AuthRegisterDTO, ip: string, isAdmin?: boolean) { async signUp(dto: AuthRegisterDTO, ip: string, isAdmin?: boolean) {
@ -76,18 +76,28 @@ export class AuthService {
}, },
}); });
if (user?.password && await argon.verify(user.password, dto.password)) { if (user?.password && (await argon.verify(user.password, dto.password))) {
this.logger.log(`Successful password login for user ${user.email} from IP ${ip}`); this.logger.log(
`Successful password login for user ${user.email} from IP ${ip}`,
);
return this.generateToken(user); return this.generateToken(user);
} }
} }
if (this.config.get("ldap.enabled")) { if (this.config.get("ldap.enabled")) {
this.logger.debug(`Trying LDAP login for user ${dto.username}`); this.logger.debug(`Trying LDAP login for user ${dto.username}`);
const ldapUser = await this.ldapService.authenticateUser(dto.username, dto.password); const ldapUser = await this.ldapService.authenticateUser(
dto.username,
dto.password,
);
if (ldapUser) { if (ldapUser) {
const user = await this.userService.findOrCreateFromLDAP(dto.username, ldapUser); const user = await this.userService.findOrCreateFromLDAP(
this.logger.log(`Successful LDAP login for user ${user.email} from IP ${ip}`); dto.username,
ldapUser,
);
this.logger.log(
`Successful LDAP login for user ${user.email} from IP ${ip}`,
);
return this.generateToken(user); return this.generateToken(user);
} }
} }

View File

@ -1,154 +1,194 @@
import { Inject, Injectable, Logger } from "@nestjs/common"; import { Inject, Injectable, Logger } from "@nestjs/common";
import * as ldap from "ldapjs"; import * as ldap from "ldapjs";
import { AttributeJson, InvalidCredentialsError, SearchCallbackResponse, SearchOptions } from "ldapjs"; import {
AttributeJson,
InvalidCredentialsError,
SearchCallbackResponse,
SearchOptions,
} from "ldapjs";
import { inspect } from "node:util"; import { inspect } from "node:util";
import { ConfigService } from "../config/config.service"; import { ConfigService } from "../config/config.service";
type LdapSearchEntry = { type LdapSearchEntry = {
objectName: string, objectName: string;
attributes: AttributeJson[], attributes: AttributeJson[];
}; };
async function ldapExecuteSearch(client: ldap.Client, base: string, options: SearchOptions): Promise<LdapSearchEntry[]> { async function ldapExecuteSearch(
const searchResponse = await new Promise<SearchCallbackResponse>((resolve, reject) => { client: ldap.Client,
client.search(base, options, (err, res) => { base: string,
if (err) { options: SearchOptions,
reject(err); ): Promise<LdapSearchEntry[]> {
} else { const searchResponse = await new Promise<SearchCallbackResponse>(
resolve(res); (resolve, reject) => {
} client.search(base, options, (err, res) => {
}); if (err) {
}); reject(err);
} else {
resolve(res);
}
});
},
);
return await new Promise<any[]>((resolve, reject) => { return await new Promise<any[]>((resolve, reject) => {
const entries: LdapSearchEntry[] = []; const entries: LdapSearchEntry[] = [];
searchResponse.on("searchEntry", entry => entries.push({ attributes: entry.pojo.attributes, objectName: entry.pojo.objectName })); searchResponse.on("searchEntry", (entry) =>
searchResponse.once("error", reject); entries.push({
searchResponse.once("end", () => resolve(entries)); attributes: entry.pojo.attributes,
}); objectName: entry.pojo.objectName,
}),
);
searchResponse.once("error", reject);
searchResponse.once("end", () => resolve(entries));
});
} }
async function ldapBindUser(client: ldap.Client, dn: string, password: string): Promise<void> { async function ldapBindUser(
return new Promise<void>((resolve, reject) => { client: ldap.Client,
client.bind(dn, password, error => { dn: string,
if (error) { password: string,
reject(error); ): Promise<void> {
} else { return new Promise<void>((resolve, reject) => {
resolve(); client.bind(dn, password, (error) => {
} if (error) {
}); reject(error);
}) } else {
resolve();
}
});
});
} }
async function ldapCreateConnection(logger: Logger, url: string): Promise<ldap.Client> { async function ldapCreateConnection(
const ldapClient = ldap.createClient({ logger: Logger,
url: url.split(","), url: string,
connectTimeout: 10_000, ): Promise<ldap.Client> {
timeout: 10_000 const ldapClient = ldap.createClient({
}); url: url.split(","),
connectTimeout: 10_000,
timeout: 10_000,
});
await new Promise((resolve, reject) => { await new Promise((resolve, reject) => {
ldapClient.once("error", reject); ldapClient.once("error", reject);
ldapClient.on("setupError", reject); ldapClient.on("setupError", reject);
ldapClient.on("socketTimeout", reject); ldapClient.on("socketTimeout", reject);
ldapClient.on("connectRefused", () => reject(new Error("connection has been refused"))); ldapClient.on("connectRefused", () =>
ldapClient.on("connectTimeout", () => reject(new Error("connect timed out"))); reject(new Error("connection has been refused")),
ldapClient.on("connectError", reject); );
ldapClient.on("connectTimeout", () =>
reject(new Error("connect timed out")),
);
ldapClient.on("connectError", reject);
ldapClient.on("connect", resolve); ldapClient.on("connect", resolve);
}).catch(error => { }).catch((error) => {
logger.error(`Connect error: ${inspect(error)}`); logger.error(`Connect error: ${inspect(error)}`);
ldapClient.destroy(); ldapClient.destroy();
throw error; throw error;
}); });
return ldapClient; return ldapClient;
} }
export type LdapAuthenticateResult = { export type LdapAuthenticateResult = {
userDn: string, userDn: string;
attributes: Record<string, string[]> attributes: Record<string, string[]>;
}; };
@Injectable() @Injectable()
export class LdapService { export class LdapService {
private readonly logger = new Logger(LdapService.name); private readonly logger = new Logger(LdapService.name);
constructor( constructor(
@Inject(ConfigService) @Inject(ConfigService)
private readonly serviceConfig: ConfigService, private readonly serviceConfig: ConfigService,
) { } ) {}
private async createLdapConnection(): Promise<ldap.Client> { private async createLdapConnection(): Promise<ldap.Client> {
const ldapUrl = this.serviceConfig.get("ldap.url"); const ldapUrl = this.serviceConfig.get("ldap.url");
if (!ldapUrl) { if (!ldapUrl) {
throw new Error("LDAP server URL is not defined"); throw new Error("LDAP server URL is not defined");
}
const ldapClient = await ldapCreateConnection(this.logger, ldapUrl);
try {
const bindDn = this.serviceConfig.get("ldap.bindDn") || null;
if (bindDn) {
try {
await ldapBindUser(ldapClient, bindDn, this.serviceConfig.get("ldap.bindPassword"))
} catch (error) {
this.logger.warn(`Failed to bind to default user: ${error}`);
throw new Error("failed to bind to default user");
}
}
return ldapClient;
} catch (error) {
ldapClient.destroy();
throw error;
}
} }
public async authenticateUser(username: string, password: string): Promise<LdapAuthenticateResult | null> { const ldapClient = await ldapCreateConnection(this.logger, ldapUrl);
if (!username.match(/^[a-zA-Z0-0]+$/)) { try {
return null; const bindDn = this.serviceConfig.get("ldap.bindDn") || null;
} if (bindDn) {
const searchBase = this.serviceConfig.get("ldap.searchBase");
const searchQuery = this.serviceConfig.get("ldap.searchQuery")
.replaceAll("%username%", username);
const ldapClient = await this.createLdapConnection();
try { try {
const [result] = await ldapExecuteSearch(ldapClient, searchBase, { await ldapBindUser(
filter: searchQuery, ldapClient,
scope: "sub" bindDn,
}); this.serviceConfig.get("ldap.bindPassword"),
);
if (!result) {
/* user not found */
return null;
}
try {
await ldapBindUser(ldapClient, result.objectName, password);
/*
* In theory we could query the user attributes now,
* but as we must query the user attributes for validation anyways
* we'll create a second ldap server connection.
*/
return {
userDn: result.objectName,
attributes: Object.fromEntries(result.attributes.map(attribute => [attribute.type, attribute.values])),
};
} catch (error) {
if (error instanceof InvalidCredentialsError) {
return null;
}
this.logger.warn(`LDAP user bind failure: ${inspect(error)}`);
return null;
} finally {
ldapClient.destroy();
}
} catch (error) { } catch (error) {
this.logger.warn(`LDAP connect error: ${inspect(error)}`); this.logger.warn(`Failed to bind to default user: ${error}`);
return null; throw new Error("failed to bind to default user");
} }
}
return ldapClient;
} catch (error) {
ldapClient.destroy();
throw error;
} }
}
public async authenticateUser(
username: string,
password: string,
): Promise<LdapAuthenticateResult | null> {
if (!username.match(/^[a-zA-Z0-0]+$/)) {
return null;
}
const searchBase = this.serviceConfig.get("ldap.searchBase");
const searchQuery = this.serviceConfig
.get("ldap.searchQuery")
.replaceAll("%username%", username);
const ldapClient = await this.createLdapConnection();
try {
const [result] = await ldapExecuteSearch(ldapClient, searchBase, {
filter: searchQuery,
scope: "sub",
});
if (!result) {
/* user not found */
return null;
}
try {
await ldapBindUser(ldapClient, result.objectName, password);
/*
* In theory we could query the user attributes now,
* but as we must query the user attributes for validation anyways
* we'll create a second ldap server connection.
*/
return {
userDn: result.objectName,
attributes: Object.fromEntries(
result.attributes.map((attribute) => [
attribute.type,
attribute.values,
]),
),
};
} catch (error) {
if (error instanceof InvalidCredentialsError) {
return null;
}
this.logger.warn(`LDAP user bind failure: ${inspect(error)}`);
return null;
} finally {
ldapClient.destroy();
}
} catch (error) {
this.logger.warn(`LDAP connect error: ${inspect(error)}`);
return null;
}
}
} }

View File

@ -34,7 +34,9 @@ export class UserDTO {
totpVerified: boolean; totpVerified: boolean;
from(partial: Partial<UserDTO>) { from(partial: Partial<UserDTO>) {
const result = plainToClass(UserDTO, partial, { excludeExtraneousValues: true }); const result = plainToClass(UserDTO, partial, {
excludeExtraneousValues: true,
});
result.isLdap = partial.ldapDN?.length > 0; result.isLdap = partial.ldapDN?.length > 0;
return result; return result;
} }

View File

@ -8,6 +8,6 @@ import { FileModule } from "src/file/file.module";
imports: [EmailModule, FileModule], imports: [EmailModule, FileModule],
providers: [UserSevice], providers: [UserSevice],
controllers: [UserController], controllers: [UserController],
exports: [UserSevice] exports: [UserSevice],
}) })
export class UserModule { } export class UserModule {}

View File

@ -17,7 +17,7 @@ export class UserSevice {
private emailService: EmailService, private emailService: EmailService,
private fileService: FileService, private fileService: FileService,
private configService: ConfigService, private configService: ConfigService,
) { } ) {}
async list() { async list() {
return await this.prisma.user.findMany(); return await this.prisma.user.findMany();
@ -94,7 +94,9 @@ export class UserSevice {
async findOrCreateFromLDAP(username: string, ldap: LdapAuthenticateResult) { async findOrCreateFromLDAP(username: string, ldap: LdapAuthenticateResult) {
const passwordHash = await argon.hash(crypto.randomUUID()); const passwordHash = await argon.hash(crypto.randomUUID());
const userEmail = ldap.attributes["userPrincipalName"]?.at(0) ?? `${crypto.randomUUID()}@ldap.local`; const userEmail =
ldap.attributes["userPrincipalName"]?.at(0) ??
`${crypto.randomUUID()}@ldap.local`;
const adminGroup = this.configService.get("ldap.adminGroups"); const adminGroup = this.configService.get("ldap.adminGroups");
const isAdmin = ldap.attributes["memberOf"]?.includes(adminGroup) ?? false; const isAdmin = ldap.attributes["memberOf"]?.includes(adminGroup) ?? false;
try { try {
@ -114,8 +116,8 @@ export class UserSevice {
ldapDN: ldap.userDn, ldapDN: ldap.userDn,
}, },
where: { where: {
ldapDN: ldap.userDn ldapDN: ldap.userDn,
} },
}); });
} catch (e) { } catch (e) {
if (e instanceof PrismaClientKnownRequestError) { if (e instanceof PrismaClientKnownRequestError) {

View File

@ -162,9 +162,7 @@ export default function Home() {
size="md" size="md"
className={classes.control} className={classes.control}
> >
<FormattedMessage <FormattedMessage id="home.button.start" />
id="home.button.start"
/>
</Button> </Button>
<Button <Button
component={Link} component={Link}