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:
parent
e813da05ae
commit
3d2b978daf
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,26 @@
|
|||||||
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,
|
||||||
|
base: string,
|
||||||
|
options: SearchOptions,
|
||||||
|
): Promise<LdapSearchEntry[]> {
|
||||||
|
const searchResponse = await new Promise<SearchCallbackResponse>(
|
||||||
|
(resolve, reject) => {
|
||||||
client.search(base, options, (err, res) => {
|
client.search(base, options, (err, res) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
reject(err);
|
reject(err);
|
||||||
@ -18,45 +28,62 @@ async function ldapExecuteSearch(client: ldap.Client, base: string, options: Sea
|
|||||||
resolve(res);
|
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) =>
|
||||||
|
entries.push({
|
||||||
|
attributes: entry.pojo.attributes,
|
||||||
|
objectName: entry.pojo.objectName,
|
||||||
|
}),
|
||||||
|
);
|
||||||
searchResponse.once("error", reject);
|
searchResponse.once("error", reject);
|
||||||
searchResponse.once("end", () => resolve(entries));
|
searchResponse.once("end", () => resolve(entries));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function ldapBindUser(client: ldap.Client, dn: string, password: string): Promise<void> {
|
async function ldapBindUser(
|
||||||
|
client: ldap.Client,
|
||||||
|
dn: string,
|
||||||
|
password: string,
|
||||||
|
): Promise<void> {
|
||||||
return new Promise<void>((resolve, reject) => {
|
return new Promise<void>((resolve, reject) => {
|
||||||
client.bind(dn, password, error => {
|
client.bind(dn, password, (error) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
reject(error);
|
reject(error);
|
||||||
} else {
|
} else {
|
||||||
resolve();
|
resolve();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function ldapCreateConnection(logger: Logger, url: string): Promise<ldap.Client> {
|
async function ldapCreateConnection(
|
||||||
|
logger: Logger,
|
||||||
|
url: string,
|
||||||
|
): Promise<ldap.Client> {
|
||||||
const ldapClient = ldap.createClient({
|
const ldapClient = ldap.createClient({
|
||||||
url: url.split(","),
|
url: url.split(","),
|
||||||
connectTimeout: 10_000,
|
connectTimeout: 10_000,
|
||||||
timeout: 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("connectTimeout", () =>
|
||||||
|
reject(new Error("connect timed out")),
|
||||||
|
);
|
||||||
ldapClient.on("connectError", reject);
|
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;
|
||||||
@ -66,8 +93,8 @@ async function ldapCreateConnection(logger: Logger, url: string): Promise<ldap.C
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type LdapAuthenticateResult = {
|
export type LdapAuthenticateResult = {
|
||||||
userDn: string,
|
userDn: string;
|
||||||
attributes: Record<string, string[]>
|
attributes: Record<string, string[]>;
|
||||||
};
|
};
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@ -89,7 +116,11 @@ export class LdapService {
|
|||||||
const bindDn = this.serviceConfig.get("ldap.bindDn") || null;
|
const bindDn = this.serviceConfig.get("ldap.bindDn") || null;
|
||||||
if (bindDn) {
|
if (bindDn) {
|
||||||
try {
|
try {
|
||||||
await ldapBindUser(ldapClient, bindDn, this.serviceConfig.get("ldap.bindPassword"))
|
await ldapBindUser(
|
||||||
|
ldapClient,
|
||||||
|
bindDn,
|
||||||
|
this.serviceConfig.get("ldap.bindPassword"),
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.warn(`Failed to bind to default user: ${error}`);
|
this.logger.warn(`Failed to bind to default user: ${error}`);
|
||||||
throw new Error("failed to bind to default user");
|
throw new Error("failed to bind to default user");
|
||||||
@ -103,20 +134,24 @@ export class LdapService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async authenticateUser(username: string, password: string): Promise<LdapAuthenticateResult | null> {
|
public async authenticateUser(
|
||||||
|
username: string,
|
||||||
|
password: string,
|
||||||
|
): Promise<LdapAuthenticateResult | null> {
|
||||||
if (!username.match(/^[a-zA-Z0-0]+$/)) {
|
if (!username.match(/^[a-zA-Z0-0]+$/)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const searchBase = this.serviceConfig.get("ldap.searchBase");
|
const searchBase = this.serviceConfig.get("ldap.searchBase");
|
||||||
const searchQuery = this.serviceConfig.get("ldap.searchQuery")
|
const searchQuery = this.serviceConfig
|
||||||
|
.get("ldap.searchQuery")
|
||||||
.replaceAll("%username%", username);
|
.replaceAll("%username%", username);
|
||||||
|
|
||||||
const ldapClient = await this.createLdapConnection();
|
const ldapClient = await this.createLdapConnection();
|
||||||
try {
|
try {
|
||||||
const [result] = await ldapExecuteSearch(ldapClient, searchBase, {
|
const [result] = await ldapExecuteSearch(ldapClient, searchBase, {
|
||||||
filter: searchQuery,
|
filter: searchQuery,
|
||||||
scope: "sub"
|
scope: "sub",
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!result) {
|
if (!result) {
|
||||||
@ -134,7 +169,12 @@ export class LdapService {
|
|||||||
*/
|
*/
|
||||||
return {
|
return {
|
||||||
userDn: result.objectName,
|
userDn: result.objectName,
|
||||||
attributes: Object.fromEntries(result.attributes.map(attribute => [attribute.type, attribute.values])),
|
attributes: Object.fromEntries(
|
||||||
|
result.attributes.map((attribute) => [
|
||||||
|
attribute.type,
|
||||||
|
attribute.values,
|
||||||
|
]),
|
||||||
|
),
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof InvalidCredentialsError) {
|
if (error instanceof InvalidCredentialsError) {
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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 {}
|
||||||
|
@ -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) {
|
||||||
|
@ -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}
|
||||||
|
Loading…
Reference in New Issue
Block a user