mirror of
https://github.com/thelounge/thelounge.git
synced 2026-03-14 14:35:50 +01:00
Merge 6837214311 into b2e3112806
This commit is contained in:
commit
9b9232802a
3 changed files with 307 additions and 1 deletions
|
|
@ -2,7 +2,7 @@ import ldap, {SearchOptions} from "ldapjs";
|
|||
import colors from "chalk";
|
||||
|
||||
import log from "../../log";
|
||||
import Config from "../../config";
|
||||
import Config, {ConfigType} from "../../config";
|
||||
import type {AuthHandler} from "../auth";
|
||||
|
||||
function ldapAuthCommon(
|
||||
|
|
@ -236,6 +236,186 @@ function isLdapEnabled() {
|
|||
return !Config.values.public && Config.values.ldap.enable;
|
||||
}
|
||||
|
||||
export type LdapConfig = ConfigType["ldap"];
|
||||
|
||||
import type {
|
||||
AuthProvider,
|
||||
AuthenticateParams,
|
||||
AuthResult,
|
||||
AuthStartParams,
|
||||
AuthStartInfo,
|
||||
DisconnectParams,
|
||||
GetValidUsersParams,
|
||||
LogoutParams,
|
||||
LogoutInfo,
|
||||
} from "./types";
|
||||
|
||||
export class SimpleLDAPAuthProvider implements AuthProvider {
|
||||
name = "ldap";
|
||||
canChangePassword = false;
|
||||
|
||||
async init(): Promise<void> {}
|
||||
|
||||
getValidUsers({users}: GetValidUsersParams): Promise<string[]> {
|
||||
// Simple LDAP can't test for user existence without the user's
|
||||
// unhashed password, so return all users unchanged
|
||||
return Promise.resolve(users);
|
||||
}
|
||||
|
||||
authStart(_params: AuthStartParams): AuthStartInfo {
|
||||
return {};
|
||||
}
|
||||
|
||||
async authenticate({
|
||||
manager,
|
||||
client,
|
||||
username,
|
||||
password,
|
||||
}: AuthenticateParams): Promise<AuthResult> {
|
||||
return new Promise((resolve) => {
|
||||
simpleLdapAuth(username, password, (valid) => {
|
||||
if (valid && !client) {
|
||||
manager.addUser(username, null, true);
|
||||
}
|
||||
|
||||
if (valid) {
|
||||
resolve({success: true, username});
|
||||
} else {
|
||||
resolve({success: false});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
logout(_params: LogoutParams): LogoutInfo {
|
||||
return {};
|
||||
}
|
||||
|
||||
disconnect(_params: DisconnectParams): void {}
|
||||
}
|
||||
|
||||
export class AdvancedLDAPAuthProvider implements AuthProvider {
|
||||
name = "ldap";
|
||||
canChangePassword = false;
|
||||
|
||||
config: LdapConfig;
|
||||
|
||||
constructor(config: LdapConfig) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
async init(): Promise<void> {}
|
||||
|
||||
async getValidUsers({users}: GetValidUsersParams): Promise<string[]> {
|
||||
return new Promise((resolve) => {
|
||||
const validUsers: string[] = [];
|
||||
|
||||
const ldapclient = ldap.createClient({
|
||||
url: this.config.url,
|
||||
tlsOptions: this.config.tlsOptions,
|
||||
});
|
||||
|
||||
const base = this.config.searchDN.base;
|
||||
|
||||
ldapclient.on("error", function (err: Error) {
|
||||
log.error(`Unable to connect to LDAP server: ${err.toString()}`);
|
||||
resolve(users);
|
||||
});
|
||||
|
||||
ldapclient.bind(
|
||||
this.config.searchDN.rootDN,
|
||||
this.config.searchDN.rootPassword,
|
||||
(err) => {
|
||||
if (err) {
|
||||
log.error("Invalid LDAP root credentials");
|
||||
resolve(users);
|
||||
return;
|
||||
}
|
||||
|
||||
const userSet = new Set(users);
|
||||
|
||||
const searchOptions: SearchOptions = {
|
||||
scope: this.config.searchDN.scope,
|
||||
filter: `${this.config.searchDN.filter}`,
|
||||
attributes: [this.config.primaryKey],
|
||||
paged: true,
|
||||
};
|
||||
|
||||
ldapclient.search(base, searchOptions, function (err2, res) {
|
||||
if (err2) {
|
||||
log.error(`LDAP search error: ${err2?.toString()}`);
|
||||
resolve(users);
|
||||
return;
|
||||
}
|
||||
|
||||
res.on("searchEntry", function (entry) {
|
||||
const user = entry.attributes[0].vals[0].toString();
|
||||
|
||||
if (userSet.has(user)) {
|
||||
validUsers.push(user);
|
||||
}
|
||||
});
|
||||
|
||||
res.on("error", function (err3) {
|
||||
log.error(`LDAP error: ${err3.toString()}`);
|
||||
ldapclient.unbind();
|
||||
resolve(users);
|
||||
});
|
||||
|
||||
res.on("end", function () {
|
||||
const validSet = new Set(validUsers);
|
||||
|
||||
users.forEach((user) => {
|
||||
if (!validSet.has(user)) {
|
||||
log.warn(
|
||||
`No account info in LDAP for ${colors.bold(
|
||||
user
|
||||
)} but user config file exists`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
ldapclient.unbind();
|
||||
resolve(validUsers);
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
authStart(_params: AuthStartParams): AuthStartInfo {
|
||||
return {};
|
||||
}
|
||||
|
||||
async authenticate({
|
||||
manager,
|
||||
client,
|
||||
username,
|
||||
password,
|
||||
}: AuthenticateParams): Promise<AuthResult> {
|
||||
return new Promise((resolve) => {
|
||||
advancedLdapAuth(username, password, (valid) => {
|
||||
if (valid && !client) {
|
||||
manager.addUser(username, null, true);
|
||||
}
|
||||
|
||||
if (valid) {
|
||||
resolve({success: true, username});
|
||||
} else {
|
||||
resolve({success: false});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
logout(_params: LogoutParams): LogoutInfo {
|
||||
return {};
|
||||
}
|
||||
|
||||
disconnect(_params: DisconnectParams): void {}
|
||||
}
|
||||
|
||||
export default {
|
||||
moduleName: "ldap",
|
||||
auth: ldapAuth,
|
||||
|
|
|
|||
|
|
@ -2,6 +2,17 @@ import colors from "chalk";
|
|||
import log from "../../log";
|
||||
import Helper from "../../helper";
|
||||
import type {AuthHandler} from "../auth";
|
||||
import type {
|
||||
AuthProvider,
|
||||
AuthenticateParams,
|
||||
AuthResult,
|
||||
AuthStartParams,
|
||||
AuthStartInfo,
|
||||
DisconnectParams,
|
||||
GetValidUsersParams,
|
||||
LogoutParams,
|
||||
LogoutInfo,
|
||||
} from "./types";
|
||||
|
||||
const localAuth: AuthHandler = (_manager, client, user, password, callback) => {
|
||||
// If no user is found, or if the client has not provided a password,
|
||||
|
|
@ -44,6 +55,71 @@ const localAuth: AuthHandler = (_manager, client, user, password, callback) => {
|
|||
});
|
||||
};
|
||||
|
||||
export class LocalAuthProvider implements AuthProvider {
|
||||
name = "local";
|
||||
canChangePassword = true;
|
||||
|
||||
async init(): Promise<void> {}
|
||||
|
||||
async getValidUsers({users}: GetValidUsersParams): Promise<string[]> {
|
||||
return users;
|
||||
}
|
||||
|
||||
authStart(_params: AuthStartParams): AuthStartInfo {
|
||||
return {};
|
||||
}
|
||||
|
||||
async authenticate({client, username, password}: AuthenticateParams): Promise<AuthResult> {
|
||||
if (!client || !password) {
|
||||
return {success: false};
|
||||
}
|
||||
|
||||
if (!client.config.password) {
|
||||
log.error(
|
||||
`User ${colors.bold(
|
||||
username
|
||||
)} with no local password set tried to sign in. (Probably a LDAP user)`
|
||||
);
|
||||
return {success: false};
|
||||
}
|
||||
|
||||
let matching: boolean;
|
||||
|
||||
try {
|
||||
matching = await Helper.password.compare(password, client.config.password);
|
||||
} catch (error) {
|
||||
log.error(`Error while checking users password. Error: ${error}`);
|
||||
return {success: false};
|
||||
}
|
||||
|
||||
if (!matching) {
|
||||
return {success: false};
|
||||
}
|
||||
|
||||
if (Helper.password.requiresUpdate(client.config.password)) {
|
||||
const hash = Helper.password.hash(password);
|
||||
|
||||
client.setPassword(hash, (success) => {
|
||||
if (success) {
|
||||
log.info(
|
||||
`User ${colors.bold(
|
||||
client.name
|
||||
)} logged in and their hashed password has been updated to match new security requirements`
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return {success: true, username};
|
||||
}
|
||||
|
||||
logout(_params: LogoutParams): LogoutInfo {
|
||||
return {};
|
||||
}
|
||||
|
||||
disconnect(_params: DisconnectParams): void {}
|
||||
}
|
||||
|
||||
export default {
|
||||
moduleName: "local",
|
||||
auth: localAuth,
|
||||
|
|
|
|||
50
server/plugins/auth/types.ts
Normal file
50
server/plugins/auth/types.ts
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
import type ClientManager from "../../clientManager";
|
||||
import type Client from "../../client";
|
||||
|
||||
export interface GetValidUsersParams {
|
||||
users: string[];
|
||||
}
|
||||
|
||||
export interface AuthStartParams {
|
||||
socketId: string;
|
||||
}
|
||||
|
||||
export interface AuthenticateParams {
|
||||
manager: ClientManager;
|
||||
client: Client | null;
|
||||
username: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
export interface LogoutParams {
|
||||
sessionData?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface DisconnectParams {
|
||||
socketId: string;
|
||||
}
|
||||
|
||||
export interface AuthStartInfo {
|
||||
authUrl?: string;
|
||||
}
|
||||
|
||||
export type AuthResult =
|
||||
| {success: false}
|
||||
| {success: true; username: string; sessionData?: Record<string, unknown>};
|
||||
|
||||
export interface LogoutInfo {
|
||||
logoutUrl?: string;
|
||||
}
|
||||
|
||||
export interface AuthProvider {
|
||||
name: string;
|
||||
canChangePassword: boolean;
|
||||
|
||||
init(): Promise<void>;
|
||||
getValidUsers(params: GetValidUsersParams): Promise<string[]>;
|
||||
|
||||
authStart(params: AuthStartParams): AuthStartInfo;
|
||||
authenticate(params: AuthenticateParams): Promise<AuthResult>;
|
||||
logout(params: LogoutParams): LogoutInfo;
|
||||
disconnect(params: DisconnectParams): void;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue