需要一些帮助和建议来解决我的问题。在我的 Next.js 应用程序 (14.1) 中,我使用 next-auth (5.0.0) 和 ldap.js (3.0.7) 进行授权。 流程如下:
重要提示:在开发模式下一切正常。但是当构建并运行 npm run start 时,“bind”运行正常,然后在 client.search 过程中停止。我从 res.on("searchRequest") 获得的最后一个 console.log ,之后什么也没有。
有人知道为什么吗?为什么它在开发模式下有效,但在生产模式下却不起作用?
服务器动作:
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.log(userInfo);
return user;
} catch (error) {
console.log("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.log("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.log("Reject error...");
reject(err);
} else {
console.log("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.log("Error in search:", err);
reject(err);
} else {
res.on("searchRequest", (searchRequest) => {
console.log("65", searchRequest.messageId);
});
res.on("searchEntry", (entry) => {
console.log("68", entry.pojo.attributes);
userData = entry.pojo.attributes;
});
res.on("searchReference", (referral) => {
console.log("72", referral);
});
res.on("error", (err) => {
console.log("75", err)
});
res.on("end", async (result) => {
// console.log("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);
}
};