Unclear migration process from forest-express-sequelize to @forestadmin/agent

Problem description

Hi, I’m currently in the process of migrating a forest-express-sequelize project to the @forestadmin/agent. I’ve replaced the forest and route folders and all their smart action logic to a new customization folder in line with the documentation recommendations. However, I’m struggling to find any documentation about how to migrate higher level authentication logic. Here is the app.js file for the forest-express-sequelize application:

const express = require("express");
const requireAll = require("require-all");
const path = require("path");
const cookieParser = require("cookie-parser");
const bodyParser = require("body-parser");
const cors = require("cors");
const jwt = require("express-jwt");
const morgan = require("morgan");
const {
  errorHandler,
  ensureAuthenticated,
  PUBLIC_ROUTES,
} = require("forest-express-sequelize");

const app = express();

let allowedOrigins = [/\.forestadmin\.com$/, /localhost:\d{4}$/];

if (process.env.CORS_ORIGINS) {
  allowedOrigins = allowedOrigins.concat(process.env.CORS_ORIGINS.split(","));
}

const corsConfig = {
  origin: allowedOrigins,
  maxAge: 86400, // NOTICE: 1 day
  credentials: true,
};

app.use(morgan("tiny"));
// Support for request-private-network as the `cors` package
// doesn't support it by default
// See: https://github.com/expressjs/cors/issues/236
app.use((req, res, next) => {
  if (req.headers["access-control-request-private-network"]) {
    res.setHeader("access-control-allow-private-network", "true");
  }
  next(null);
});
app.use(
  "/forest/authentication",
  cors({
    ...corsConfig,
    // The null origin is sent by browsers for redirected AJAX calls
    // we need to support this in authentication routes because OIDC
    // redirects to the callback route
    origin: corsConfig.origin.concat("null"),
  })
);
app.use(cors(corsConfig));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, "public")));

app.use(
  jwt({
    secret: process.env.FOREST_AUTH_SECRET,
    credentialsRequired: false,
    algorithms: ["HS256"],
  })
);

app.use("/forest", (request, response, next) => {
  if (PUBLIC_ROUTES.includes(request.url)) {
    return next();
  }
  return ensureAuthenticated(request, response, next);
});

requireAll({
  dirname: path.join(__dirname, "routes"),
  recursive: true,
  resolve: (Module) => app.use("/forest", Module),
});

requireAll({
  dirname: path.join(__dirname, "middlewares"),
  recursive: true,
  resolve: (Module) => Module(app),
});

app.use(errorHandler());

module.exports = app;

As expected, I’ve removed the following block:

requireAll({
  dirname: path.join(__dirname, "routes"),
  recursive: true,
  resolve: (Module) => app.use("/forest", Module),
});

Where I’m struggling, however, is finding any documentation on how to migrate the logic from

app.use("/forest", (request, response, next) => {
  if (PUBLIC_ROUTES.includes(request.url)) {
    return next();
  }
  return ensureAuthenticated(request, response, next);
});

Firstly, @forestadmin/agent doesn’t expose the public routes, so how do I check whether the requested route requires authentication or not? Secondly, @forestadmin/agent no longer provides an ensureAuthenticated, so does this need to be replaced by custom authentication logic? Lastly, @forestadmin/agent doesn’t seem to expose an errorHandler anymore, so does this again need to be replaced by custom error handling code?

If there are any other required code changes in addition to what I’ve mentioned that are recommended when migrating, it would be extremely helpful if somebody could let me know. Thank you

Context

  • Project name: …
  • Team name: …
  • Environment name: local
  • Agent technology: nodejs
  • Agent (forest package) name & version: @forestadmin/agent 1.51.0
  • Database type: postgreSQL
  • Recent changes made on your end if any: migrating smart actions to new agent

Hello @EIdev and welcome to the community!

We’re thrilled to see that you are moving to the @forestadmin/agent. For this process to be as smooth as possible we have redacted a migration guide that you can find on our documentation. Please check it out if you were not aware of its existence.

Firstly, @forestadmin/agent doesn’t expose the public routes, so how do I check whether the requested route requires authentication or not? Secondly, @forestadmin/agent no longer provides an ensureAuthenticated, so does this need to be replaced by custom authentication logic?

It has never been easier to set up your agent, everything is handled by the agent you do not need to bother with authentications. You can find more info in the quick start page.

Lastly, @forestadmin/agent doesn’t seem to expose an errorHandler anymore, so does this again need to be replaced by custom error handling code?

As every customization is done through a handler function, the agent will handle errors on its own. You can even customize the error message via an optional function given to the agent. You can find a recent topic discussing this here.

Best regards,

1 Like

Hi @dogan.ay,

Thank you for your quick response and help. I have indeed already gone through the migration guide documentation - that’s how I migrated all of the smart actions, etc. The reason I’m confused is because I can’t see any information in the documentation about the specific issues I asked about, namely the public routes and authentication. It’s good to know that this has been abstracted and is now handled by the agent.

So to break it down, based on the quick start page you shared with me, are you therefore saying I no longer need to worry about any authentication, meaning I can remove the following:

app.use(
  jwt({
    secret: process.env.FOREST_AUTH_SECRET,
    credentialsRequired: false,
    algorithms: ["HS256"],
  })
);

app.use("/forest", (request, response, next) => {
  if (PUBLIC_ROUTES.includes(request.url)) {
    return next();
  }
  return ensureAuthenticated(request, response, next);
});

Nor do I need to worry about parsing the incoming request or manually setting up the serving of static content, meaning I can remove the following:

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, "public")));

Nor do I need to worry about CORS, meaning I can remove the following:

let allowedOrigins = [/\.forestadmin\.com$/, /localhost:\d{4}$/];
if (process.env.CORS_ORIGINS) {
  allowedOrigins = allowedOrigins.concat(process.env.CORS_ORIGINS.split(","));
}
const corsConfig = {
  origin: allowedOrigins,
  maxAge: 86400, // NOTICE: 1 day
  credentials: true,
};
// Support for request-private-network as the `cors` package
// doesn't support it by default
// See: https://github.com/expressjs/cors/issues/236
app.use((req, res, next) => {
  if (req.headers["access-control-request-private-network"]) {
    res.setHeader("access-control-allow-private-network", "true");
  }
  next(null);
});
app.use(
  "/forest/authentication",
  cors({
    ...corsConfig,
    // The null origin is sent by browsers for redirected AJAX calls
    // we need to support this in authentication routes because OIDC
    // redirects to the callback route
    origin: corsConfig.origin.concat("null"),
  })
);
app.use(cors(corsConfig));

And that errors are now automatically handled by the agent, meaning the following line can also be removed?

app.use(errorHandler());

These assumptions are based on the absence of all of this logic from the quick start page you shared, although the documentation doesn’t explicitly say these areas no longer with written code anymore. If I have misinterpreted you and some of the above code is still important to include or adapt (such as the CORS options), please do let me know.

Thank you for your help,

Yes, if you are using the agent as a microservice, the minimal setup looks like this

require('dotenv').config();

// Import the requirements
const { createAgent } = require('@forestadmin/agent');

// Create your Forest Admin agent
createAgent({
  // These process.env variables should be provided in the onboarding
  authSecret: process.env.FOREST_AUTH_SECRET,
  envSecret: process.env.FOREST_ENV_SECRET,
  isProduction: process.env.NODE_ENV === 'production',
})
  .addDataSource(YOUR_DATASOURCE)
  .mountOnStandaloneServer(3000)
  .start();

Nothing else is required, you can indeed remove all the snippets you have shared.

2 Likes

I see. Many thanks for your help with this. I assume it’s also fine to remove all of that logic if you use mountOnExpress instead of mountOnStandaloneServer (unless you have custom express routes outside of forest that need parsed data / authentication / special CORS settings, in which case it is required that you keep that logic). In my case I have an express server which I’m mounting the forest agent onto, but I don’t have any routes outside of forest (I’m mounting it on express for other reasons)

1 Like

Yes, all of them use the same configuration. I’ll mark this as solved.

If you experience any issue during your migration do not hesitate to create a new topic !

Best regards,

1 Like

Many thanks for the help!