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],
exports: [AuthService],
})
export class AuthModule { }
export class AuthModule {}

View File

@ -29,7 +29,7 @@ export class AuthService {
private emailService: EmailService,
private ldapService: LdapService,
private userService: UserSevice,
) { }
) {}
private readonly logger = new Logger(AuthService.name);
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)) {
this.logger.log(`Successful password login for user ${user.email} from IP ${ip}`);
if (user?.password && (await argon.verify(user.password, dto.password))) {
this.logger.log(
`Successful password login for user ${user.email} from IP ${ip}`,
);
return this.generateToken(user);
}
}
if (this.config.get("ldap.enabled")) {
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) {
const user = await this.userService.findOrCreateFromLDAP(dto.username, ldapUser);
this.logger.log(`Successful LDAP login for user ${user.email} from IP ${ip}`);
const user = await this.userService.findOrCreateFromLDAP(
dto.username,
ldapUser,
);
this.logger.log(
`Successful LDAP login for user ${user.email} from IP ${ip}`,
);
return this.generateToken(user);
}
}

View File

@ -1,154 +1,194 @@
import { Inject, Injectable, Logger } from "@nestjs/common";
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 { ConfigService } from "../config/config.service";
type LdapSearchEntry = {
objectName: string,
attributes: AttributeJson[],
objectName: string;
attributes: AttributeJson[];
};
async function ldapExecuteSearch(client: ldap.Client, base: string, options: SearchOptions): Promise<LdapSearchEntry[]> {
const searchResponse = await new Promise<SearchCallbackResponse>((resolve, reject) => {
client.search(base, options, (err, res) => {
if (err) {
reject(err);
} else {
resolve(res);
}
});
});
async function ldapExecuteSearch(
client: ldap.Client,
base: string,
options: SearchOptions,
): Promise<LdapSearchEntry[]> {
const searchResponse = await new Promise<SearchCallbackResponse>(
(resolve, reject) => {
client.search(base, options, (err, res) => {
if (err) {
reject(err);
} else {
resolve(res);
}
});
},
);
return await new Promise<any[]>((resolve, reject) => {
const entries: LdapSearchEntry[] = [];
searchResponse.on("searchEntry", entry => entries.push({ attributes: entry.pojo.attributes, objectName: entry.pojo.objectName }));
searchResponse.once("error", reject);
searchResponse.once("end", () => resolve(entries));
});
return await new Promise<any[]>((resolve, reject) => {
const entries: LdapSearchEntry[] = [];
searchResponse.on("searchEntry", (entry) =>
entries.push({
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> {
return new Promise<void>((resolve, reject) => {
client.bind(dn, password, error => {
if (error) {
reject(error);
} else {
resolve();
}
});
})
async function ldapBindUser(
client: ldap.Client,
dn: string,
password: string,
): Promise<void> {
return new Promise<void>((resolve, reject) => {
client.bind(dn, password, (error) => {
if (error) {
reject(error);
} else {
resolve();
}
});
});
}
async function ldapCreateConnection(logger: Logger, url: string): Promise<ldap.Client> {
const ldapClient = ldap.createClient({
url: url.split(","),
connectTimeout: 10_000,
timeout: 10_000
});
async function ldapCreateConnection(
logger: Logger,
url: string,
): Promise<ldap.Client> {
const ldapClient = ldap.createClient({
url: url.split(","),
connectTimeout: 10_000,
timeout: 10_000,
});
await new Promise((resolve, reject) => {
ldapClient.once("error", reject);
ldapClient.on("setupError", reject);
ldapClient.on("socketTimeout", reject);
ldapClient.on("connectRefused", () => reject(new Error("connection has been refused")));
ldapClient.on("connectTimeout", () => reject(new Error("connect timed out")));
ldapClient.on("connectError", reject);
await new Promise((resolve, reject) => {
ldapClient.once("error", reject);
ldapClient.on("setupError", reject);
ldapClient.on("socketTimeout", reject);
ldapClient.on("connectRefused", () =>
reject(new Error("connection has been refused")),
);
ldapClient.on("connectTimeout", () =>
reject(new Error("connect timed out")),
);
ldapClient.on("connectError", reject);
ldapClient.on("connect", resolve);
}).catch(error => {
logger.error(`Connect error: ${inspect(error)}`);
ldapClient.destroy();
throw error;
});
ldapClient.on("connect", resolve);
}).catch((error) => {
logger.error(`Connect error: ${inspect(error)}`);
ldapClient.destroy();
throw error;
});
return ldapClient;
return ldapClient;
}
export type LdapAuthenticateResult = {
userDn: string,
attributes: Record<string, string[]>
userDn: string;
attributes: Record<string, string[]>;
};
@Injectable()
export class LdapService {
private readonly logger = new Logger(LdapService.name);
constructor(
@Inject(ConfigService)
private readonly serviceConfig: ConfigService,
) { }
private readonly logger = new Logger(LdapService.name);
constructor(
@Inject(ConfigService)
private readonly serviceConfig: ConfigService,
) {}
private async createLdapConnection(): Promise<ldap.Client> {
const ldapUrl = this.serviceConfig.get("ldap.url");
if (!ldapUrl) {
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;
}
private async createLdapConnection(): Promise<ldap.Client> {
const ldapUrl = this.serviceConfig.get("ldap.url");
if (!ldapUrl) {
throw new Error("LDAP server URL is not defined");
}
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();
const ldapClient = await ldapCreateConnection(this.logger, ldapUrl);
try {
const bindDn = this.serviceConfig.get("ldap.bindDn") || null;
if (bindDn) {
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();
}
await ldapBindUser(
ldapClient,
bindDn,
this.serviceConfig.get("ldap.bindPassword"),
);
} catch (error) {
this.logger.warn(`LDAP connect error: ${inspect(error)}`);
return null;
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> {
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;
from(partial: Partial<UserDTO>) {
const result = plainToClass(UserDTO, partial, { excludeExtraneousValues: true });
const result = plainToClass(UserDTO, partial, {
excludeExtraneousValues: true,
});
result.isLdap = partial.ldapDN?.length > 0;
return result;
}

View File

@ -8,6 +8,6 @@ import { FileModule } from "src/file/file.module";
imports: [EmailModule, FileModule],
providers: [UserSevice],
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 fileService: FileService,
private configService: ConfigService,
) { }
) {}
async list() {
return await this.prisma.user.findMany();
@ -94,7 +94,9 @@ export class UserSevice {
async findOrCreateFromLDAP(username: string, ldap: LdapAuthenticateResult) {
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 isAdmin = ldap.attributes["memberOf"]?.includes(adminGroup) ?? false;
try {
@ -114,8 +116,8 @@ export class UserSevice {
ldapDN: ldap.userDn,
},
where: {
ldapDN: ldap.userDn
}
ldapDN: ldap.userDn,
},
});
} catch (e) {
if (e instanceof PrismaClientKnownRequestError) {

View File

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