res.on("searchEntry") 在 Next.js 生产模式下不会返回结果

问题描述 投票:0回答:1

需要一些帮助和建议来解决我的问题。在我的 Next.js 应用程序 (14.1) 中,我使用 next-auth (5.0.0) 和 ldap.js (3.0.7) 进行授权。 流程如下:

  • 用户输入凭据。
  • 服务器操作函数从下一个身份验证调用登录。
  • 在授权回调中的 SingIn=>CredentialsProvider 中,我调用我的 创建了登录功能。
  • 登录函数调用另一个名为“call”的函数,该函数使用 ldap.js to :首先绑定,然后在AD中搜索用户及其详细信息。在 返回我必须获取所有用户详细信息。

重要提示:在开发模式下一切正常。但是当构建并运行 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
reactjs next.js next-auth ldapjs
1个回答
0
投票

尝试通过将此设置添加到下一个配置中来解决问题(它也适用于我):

/** @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);
  }
};
© www.soinside.com 2019 - 2024. All rights reserved.