Нужна помощь и совет по моей проблеме. В своем приложении Next.js (14.1) я делал авторизацию с помощью next-auth (5.0.0) и ldap.js (3.0.7). Процесс происходит следующим образом:
Важно: в режиме разработки все работает нормально. Но когда сборка и запуск npm run start, «привязка» проходит нормально, а затем во время процесса client.search останавливается. Последний файл console.info я получаю от res.on("searchRequest") и ничего после этого.
Кто-нибудь знает, почему? Почему это работает в режиме разработки, но не в режиме prod?
Действия сервера:
export const logInAction = async (prevState, formData) => {
const { username, password } = Object.fromEntries(formData);
if (username === "" || password === "") {
return { error: "Wrong" };
}
try {
await signIn("credentials", { username, password });
} catch (error) {
if (error.type?.includes("CredentialsSignin")) {
return { error: "Wrong" };
}
throw error;
}
};
логика следующей аутентификации:
const login = async (credentials) => {
try {
const userInfo = await call(credentials.username, credentials.password);
console.info(userInfo);
return user;
} catch (error) {
console.info("error in login function", error);
throw new Error("Failed to login");
}
};
export const {
handlers: { GET, POST },
auth,
signIn,
signOut,
} = NextAuth({
...authConfig,
providers: [
CredentialsProvider({
async authorize(credentials) {
try {
const user = await login(credentials);
return user;
} catch (error) {
console.info("error in autorize:", error);
return null;
}
},
}),
],
callbacks: {
async signIn({ user, account, profile }) {
//doing some logic
},
...authConfig.callbacks,
},
})
логика ldpajs:
const ldap = require("ldapjs");
const createClient = () =>
ldap.createClient({
url: process.env.URL,
tlsOptions: { rejectUnauthorized: false },
reconnect: true,
});
let client;
const ADLoginUserCheck = async (user, password) => {
return new Promise((resolve, reject) => {
client = createClient();
client.bind(`${user}${process.env.URL}`, password, (err, auth) => {
if (err) {
console.info("Reject error...");
reject(err);
} else {
console.info("all ok...");
resolve(true);
}
});
});
};
const getUserDetails = async (user) => {
const opts = {
filter: `(sAMAccountName=${user})`,
scope: "sub",
attributes: [],
};
return new Promise((resolve, reject) => {
let userData;
client.search(`${process.env.URL}`, opts, (err, res) => {
if (err) {
console.info("Error in search:", err);
reject(err);
} else {
res.on("searchRequest", (searchRequest) => {
console.info("65", searchRequest.messageId);
});
res.on("searchEntry", (entry) => {
console.info("68", entry.pojo.attributes);
userData = entry.pojo.attributes;
});
res.on("searchReference", (referral) => {
console.info("72", referral);
});
res.on("error", (err) => {
console.info("75", err)
});
res.on("end", async (result) => {
// console.info("userDAta", userData);
resolve({ userData });
client.destroy();
});
}
});
});
};
const top = async (user, password) => {
const isCorrenctCredentials = await ADLoginUserCheck(user, password);
let userDetails;
if (isCorrenctCredentials) {
userDetails = await getUserDetails(user);
}
return userDetails;
};
const call = async (user, password) => {
top(user, password).then((res) => {
return res;
});
};
module.exports = call





Попробуйте решить проблему, добавив этот параметр в следующую конфигурацию (у меня тоже работает):
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
serverComponentsExternalPackages: ["ldapjs"],
},
};
module.exports = nextConfig;На случай, если это не поможет, я также вставляю некоторые мысли другого разработчика (https://github.com/ldapjs/node-ldapjs/issues/967) Функция поиска работала неожиданно, не возвращая никаких результатов из события searchEntry.
После некоторого устранения неполадок я начал подозревать, что проблема может быть связана с процессом производственной сборки. Кажется возможным, что имена классов изменяются во время минимизации, что потенциально влияет на способность библиотеки ldapjs правильно выполнять проверки.
В своих журналах я заметил изменение имени класса в конечном событии с SearchResultDone в разработке на уменьшенную версию, например s в рабочей версии, что указывает на переименование классов в процессе сборки.
В частности, в client.js проблема, похоже, связана с тем, как генерируются и используются имена событий:
if (msg instanceof SearchEntry || msg instanceof SearchReference) {
let event = msg.constructor.name
// Generate the event name for the event emitter, i.e., "searchEntry"
// and "searchReference".
event = (event[0].toLowerCase() + event.slice(1)).replaceAll('Result', '')
return sendResult(event, msg)
} else {
tracker.remove(message.messageId)
// Potentially mark client as idle
self._updateIdle()
if (msg instanceof LDAPResult) {
if (msg.status !== 0 && expect.indexOf(msg.status) === -1) {
return sendResult('error', errors.getError(msg))
}
return sendResult('end', msg)
} else if (msg instanceof Error) {
return sendResult('error', msg)
} else {
return sendResult('error', new errors.ProtocolError(msg.type))
}
}
Такое поведение предполагает, что процесс создания имен событий на основе имен классов (которые изменяются в рабочей сборке) может привести к отправке событий с неожиданными именами. В качестве временного решения я изменил метод Client.prototype._sendSocket напрямую, чтобы гарантировать, что событие searchEntry генерируется без использования имени класса.
const ldap = require("ldapjs");
ldap.Client.prototype._sendSocket = function (
message,
expect,
emitter,
callback
) {
const conn = this._socket;
const tracker = this._tracker;
const log = this.log;
const self = this;
let timer = false;
let sentEmitter = false;
function sendResult(event, obj) {
if (event === "error") {
self.emit("resultError", obj);
}
if (emitter) {
if (event === "error") {
// Error will go unhandled if emitter hasn't been sent via callback.
// Execute callback with the error instead.
if (!sentEmitter) {
return callback(obj);
}
}
return emitter.emit(event, obj);
}
if (event === "error") {
return callback(obj);
}
return callback(null, obj);
}
function messageCallback(msg) {
if (timer) {
clearTimeout(timer);
}
log.trace({ msg: msg ? msg.pojo : null }, "response received");
if (expect === "abandon") {
return sendResult("end", null);
}
if (msg instanceof ldap.SearchEntry) {
return sendResult("searchEntry", msg);
} else if (msg instanceof ldap.SearchReference) {
return sendResult("searchReference", msg);
} else {
tracker.remove(message.messageId);
// Potentially mark client as idle
self._updateIdle();
if (msg instanceof ldap.LDAPResult) {
if (msg.status !== 0 && expect.indexOf(msg.status) === -1) {
return sendResult("error", ldap.getError(msg));
}
return sendResult("end", msg);
} else if (msg instanceof Error) {
return sendResult("error", msg);
} else {
return sendResult("error", new ldap.ProtocolError(msg.type));
}
}
}
function onRequestTimeout() {
self.emit("timeout", message);
const { callback: cb } = tracker.fetch(message.messageId);
if (cb) {
// FIXME: the timed-out request should be abandoned
cb(new errors.TimeoutError("request timeout (client interrupt)"));
}
}
function writeCallback() {
if (expect === "abandon") {
// Mark the messageId specified as abandoned
tracker.abandon(message.abandonId);
// No need to track the abandon request itself
tracker.remove(message.id);
return callback(null);
} else if (expect === "unbind") {
conn.unbindMessageID = message.id;
// Mark client as disconnected once unbind clears the socket
self.connected = false;
// Some servers will RST the connection after receiving an unbind.
// Socket errors are blackholed since the connection is being closed.
conn.removeAllListeners("error");
conn.on("error", function () {});
conn.end();
} else if (emitter) {
sentEmitter = true;
callback(null, emitter);
emitter.emit("searchRequest", message);
return;
}
return false;
}
// Start actually doing something...
tracker.track(message, messageCallback);
// Mark client as active
this._updateIdle(true);
if (self.timeout) {
log.trace("Setting timeout to %d", self.timeout);
timer = setTimeout(onRequestTimeout, self.timeout);
}
log.trace("sending request %j", message.pojo);
try {
const messageBer = message.toBer();
return conn.write(messageBer.buffer, writeCallback);
} catch (e) {
if (timer) {
clearTimeout(timer);
}
log.trace({ err: e }, "Error writing message to socket");
return callback(e);
}
};
Во время сборки я обнаружил одну ошибку: «Обещание» не определено. Может быть, в этом причина? Должен ли я как-то заменить ту часть, где я создаю новое обещание?