POST /forest/authentication 404

Hi, I am not able to set up Forest Admin with my local environment (Express and MySQL). When I try to authenticate, I am getting 404 for /forest/authentication route.

app.js

const createError = require('http-errors');
const express = require('express');
const path = require('path');
const cookieParser = require('cookie-parser');
const logger = require('morgan');
const jwt = require('express-jwt');
const Sequelize = require('sequelize');
const forest = require('forest-express-sequelize');
const { errorHandler } = require('forest-express-sequelize');
// const { createAgent } = require('@forestadmin/agent');
// const {
//   createSequelizeDataSource,
// } = require('@forestadmin/datasource-sequelize');
require('dotenv').config();
const cors = require('cors');
const sequelizeInstance = require('./src/db/models/index').sequelize;

var indexRouter = require('./src/routes/index');
var authRouter = require('./src/routes/api/auth');
var usersRouter = require('./src/routes/api/users');
var ceremonyRouter = require('./src/routes/api/ceremony');
var questionRouter = require('./src/routes/api/questions');

var app = express();

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

var corsOptions = {
  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({
    ...corsOptions,
    // 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: corsOptions.origin.concat('null'),
  })
);

app.use(cors(corsOptions));

// view engine setup
app.set('views', path.join(__dirname, 'src/views'));
app.set('view engine', 'jade');

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

//Routes
app.use('/', indexRouter);
app.use('/api', indexRouter);
app.use('/api/auth', authRouter);
app.use('/api/users', usersRouter);
app.use('/api/ceremony', ceremonyRouter);
app.use('/api/questions', questionRouter);

// catch 404 and forward to error handler
app.use(function (req, res, next) {
  next(createError(404));
});

// error handler
app.use(function (err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};
  res.status(err.status || 500);
  res.render('error');
});

// set port, listen for requests
const PORT = process.env.NODE_DOCKER_PORT || 8080;
app.listen(PORT, () => {
  console.log(`Server is running on port ${PORT}.`);
});

// // Create your Forest Admin agent
// // This must be called BEFORE all other middlewares on the express app
// createAgent({
//   authSecret: process.env.FOREST_AUTH_SECRET,
//   agentUrl: process.env.FOREST_AGENT_URL,
//   envSecret: process.env.FOREST_ENV_SECRET,
//   isProduction: process.env.NODE_ENV === 'production',
// })
//   .addDataSource(createSequelizeDataSource(sequelizeInstance))
//   .mountOnExpress(app)
//   .start();

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

forest
  .init({
    envSecret: process.env.FOREST_ENV_SECRET,
    authSecret: process.env.FOREST_AUTH_SECRET,
    objectMapping: Sequelize,
    connections: { default: sequelizeInstance },
  })
  .then((FAMiddleware) => {
    app.use(FAMiddleware);
  });

app.use(errorHandler());

module.exports = app;

package.json

{
  "name": "cbl-backend",
  "version": "0.0.0",
  "private": true,
  "scripts": {
    "start": "node ./bin/www",
    "start_dev": "nodemon ./bin/www",
    "docker": "docker-compose up --build"
  },
  "dependencies": {
    "@forestadmin/agent": "1.0.0-beta.43",
    "@forestadmin/datasource-sequelize": "1.0.0-beta.34",
    "bcryptjs": "^2.4.3",
    "cookie-parser": "~1.4.4",
    "cors": "^2.8.5",
    "date-and-time": "^2.4.1",
    "debug": "~2.6.9",
    "dotenv": "^16.0.1",
    "express": "~4.16.1",
    "express-jwt": "6.1.2",
    "express-validator": "^6.14.2",
    "forest-express-sequelize": "8.5.11",
    "handlebars": "^4.7.7",
    "handlebars-helpers": "^0.10.0",
    "http-errors": "~1.6.3",
    "jade": "^0.29.0",
    "jsonwebtoken": "^8.5.1",
    "mailgun.js": "^8.0.0",
    "moment": "^2.29.4",
    "morgan": "~1.9.1",
    "mysql2": "^2.3.3",
    "nodemailer": "^6.7.7",
    "passport": "^0.6.0",
    "passport-jwt": "^4.0.0",
    "rand-token": "^1.0.1",
    "sequelize": "^6.21.3",
    "sequelize-auto": "^0.8.8"
  },
  "devDependencies": {
    "nodemon": "^2.0.19",
    "sequelize-cli": "^6.4.1"
  }
}

docker-compose.yml

version: '3.8'

services:
  mysqldb:
    platform: linux/x86_64
    image: mysql:8.0.29
    restart: unless-stopped
    env_file: ./.env
    environment:
      - MYSQL_ROOT_PASSWORD=$MYSQLDB_ROOT_PASSWORD
      - MYSQL_DATABASE=$MYSQLDB_DATABASE
    ports:
      - $MYSQLDB_LOCAL_PORT:$MYSQLDB_DOCKER_PORT
    volumes:
      - db:/var/lib/mysql
  phpmyadmin:
    image: phpmyadmin/phpmyadmin
    ports:
      - 11819:80
    depends_on:
      - mysqldb
    environment:
      PMA_HOSTS: mysqldb
      PMA_USER: $MYSQLDB_USER
      PMA_PASSWORD: $MYSQLDB_ROOT_PASSWORD
      UPLOAD_LIMIT: 100M
  app:
    depends_on:
      - mysqldb
    build: 
      context: .
      network: host
    restart: unless-stopped
    env_file: ./.env
    volumes:
      - .:/var/www/app
    working_dir: /var/www/app
    ports:
      - $NODE_LOCAL_PORT:$NODE_DOCKER_PORT
    environment:
      - DB_HOST=mysqldb
      - DB_USER=$MYSQLDB_USER
      - DB_PASSWORD=$MYSQLDB_ROOT_PASSWORD
      - DB_NAME=$MYSQLDB_DATABASE
      - DB_PORT=$MYSQLDB_DOCKER_PORT
    stdin_open: true
    tty: true
volumes: 
  db:

The Node.js project is running on localhost:6868 without any issues:

terminal log

andand@And-MacBook-Pro cbl-backend % docker-compose up --build
[+] Building 42.5s (10/10) FINISHED                                                                                 
 => [internal] load build definition from Dockerfile                                                           0.0s
 => => transferring dockerfile: 32B                                                                            0.0s
 => [internal] load .dockerignore                                                                              0.0s
 => => transferring context: 2B                                                                                0.0s
 => [internal] load metadata for docker.io/library/node:16                                                     0.6s
 => [internal] load build context                                                                              0.5s
 => => transferring context: 2.11MB                                                                            0.5s
 => CACHED [1/6] FROM docker.io/library/node:16@sha256:1ed1e17ccabb09038cfb8a965337ebcda51ef9e9d32082164c502d  0.0s
 => [2/6] COPY package.json .                                                                                  0.0s
 => [3/6] RUN npm install -g nodemon                                                                           3.6s
 => [4/6] RUN npm install                                                                                     35.6s
 => [5/6] COPY . .                                                                                             1.0s 
 => exporting to image                                                                                         1.1s 
 => => exporting layers                                                                                        1.1s 
 => => writing image sha256:53f94f35be4528c5cd899f5702e433acf915a3b1d01a2dbdf098d35482a42bf5                   0.0s 
 => => naming to docker.io/library/cbl-backend_app                                                             0.0s 
                                                                                                                    
Use 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them
[+] Running 3/3
 ⠿ Container cbl-backend-mysqldb-1     Created                                                                 0.0s
 ⠿ Container cbl-backend-app-1         Recreated                                                               0.1s
 ⠿ Container cbl-backend-phpmyadmin-1  Created                                                                 0.0s
Attaching to cbl-backend-app-1, cbl-backend-mysqldb-1, cbl-backend-phpmyadmin-1
cbl-backend-mysqldb-1     | 2022-08-09 14:22:50+00:00 [Note] [Entrypoint]: Entrypoint script for MySQL Server 8.0.29-1.el8 started.
cbl-backend-phpmyadmin-1  | AH00558: apache2: Could not reliably determine the server's fully qualified domain name, using 172.18.0.3. Set the 'ServerName' directive globally to suppress this message
cbl-backend-phpmyadmin-1  | AH00558: apache2: Could not reliably determine the server's fully qualified domain name, using 172.18.0.3. Set the 'ServerName' directive globally to suppress this message
cbl-backend-phpmyadmin-1  | [Tue Aug 09 14:22:51.704530 2022] [mpm_prefork:notice] [pid 1] AH00163: Apache/2.4.53 (Debian) PHP/8.0.19 configured -- resuming normal operations
cbl-backend-phpmyadmin-1  | [Tue Aug 09 14:22:51.706056 2022] [core:notice] [pid 1] AH00094: Command line: 'apache2 -D FOREGROUND'
cbl-backend-mysqldb-1     | 2022-08-09 14:22:51+00:00 [Note] [Entrypoint]: Switching to dedicated user 'mysql'
cbl-backend-app-1         | [nodemon] 2.0.19
cbl-backend-app-1         | [nodemon] to restart at any time, enter `rs`
cbl-backend-app-1         | [nodemon] watching path(s): *.*
cbl-backend-app-1         | [nodemon] watching extensions: js,mjs,json
cbl-backend-app-1         | [nodemon] starting `node /var/www/app/app.js npm run start_dev`
cbl-backend-mysqldb-1     | 2022-08-09 14:22:51+00:00 [Note] [Entrypoint]: Entrypoint script for MySQL Server 8.0.29-1.el8 started.
cbl-backend-mysqldb-1     | '/var/lib/mysql/mysql.sock' -> '/var/run/mysqld/mysqld.sock'
cbl-backend-mysqldb-1     | 2022-08-09T14:22:53.698645Z 0 [System] [MY-010116] [Server] /usr/sbin/mysqld (mysqld 8.0.29) starting as process 1
cbl-backend-mysqldb-1     | 2022-08-09T14:22:53.808611Z 1 [System] [MY-013576] [InnoDB] InnoDB initialization has started.
cbl-backend-mysqldb-1     | 2022-08-09T14:22:53.846153Z 1 [ERROR] [MY-012585] [InnoDB] Linux Native AIO interface is not supported on this platform. Please check your OS documentation and install appropriate binary of InnoDB.
cbl-backend-mysqldb-1     | 2022-08-09T14:22:53.846650Z 1 [Warning] [MY-012654] [InnoDB] Linux Native AIO disabled.
cbl-backend-mysqldb-1     | 2022-08-09T14:22:54.473510Z 1 [System] [MY-013577] [InnoDB] InnoDB initialization has ended.
cbl-backend-mysqldb-1     | 2022-08-09T14:22:55.462049Z 0 [System] [MY-010229] [Server] Starting XA crash recovery...
cbl-backend-mysqldb-1     | 2022-08-09T14:22:55.478820Z 0 [System] [MY-010232] [Server] XA crash recovery finished.
cbl-backend-mysqldb-1     | 2022-08-09T14:22:55.754773Z 0 [Warning] [MY-010068] [Server] CA certificate ca.pem is self signed.
cbl-backend-mysqldb-1     | 2022-08-09T14:22:55.756819Z 0 [System] [MY-013602] [Server] Channel mysql_main configured to support TLS. Encrypted connections are now supported for this channel.
cbl-backend-mysqldb-1     | 2022-08-09T14:22:55.774738Z 0 [Warning] [MY-011810] [Server] Insecure configuration for --pid-file: Location '/var/run/mysqld' in the path is accessible to all OS users. Consider choosing a different directory.
cbl-backend-mysqldb-1     | 2022-08-09T14:22:56.089136Z 0 [System] [MY-011323] [Server] X Plugin ready for connections. Bind-address: '::' port: 33060, socket: /var/run/mysqld/mysqlx.sock
cbl-backend-mysqldb-1     | 2022-08-09T14:22:56.091257Z 0 [System] [MY-010931] [Server] /usr/sbin/mysqld: ready for connections. Version: '8.0.29'  socket: '/var/run/mysqld/mysqld.sock'  port: 3306  MySQL Community Server - GPL.
cbl-backend-app-1         | [forest] 🌳🌳🌳  Your configDir ("/var/www/app/forest") does not exist. Please make sure it is set correctly.
cbl-backend-app-1         | Server is running on port 8080.
cbl-backend-app-1         | [forest] 🌳🌳🌳  Checking need for apimap update...
cbl-backend-app-1         | Executing (default): SELECT 1+1 AS result
cbl-backend-app-1         | Connection has been established successfully.
cbl-backend-app-1         | [forest] 🌳🌳🌳  No change in apimap, nothing sent to Forest.
cbl-backend-app-1         | POST /forest/authentication 404 315.425 ms - 1916
cbl-backend-app-1         | GET / 304 16.272 ms - -
cbl-backend-app-1         | GET /stylesheets/style.css 304 2.196 ms - -

db/models/index.js

'use strict';

const fs = require('fs');
const path = require('path');
const Sequelize = require('sequelize');
const basename = path.basename(__filename);
const env = process.env.NODE_ENV || 'development';
const config = require(__dirname + '/../../config/database.js')[env];
const db = {};

let sequelize;
if (config.use_env_variable) {
  sequelize = new Sequelize(process.env[config.use_env_variable], {
    ...config,
    host: process.env.USING_DOCKER ? 'host.docker.internal' : config.host,
  });
} else {
  sequelize = new Sequelize(config.database, config.username, config.password, {
    ...config,
    host: process.env.USING_DOCKER ? 'host.docker.internal' : config.host,
  });
}

sequelize
  .authenticate()
  .then(() => {
    console.log('Connection has been established successfully.');
  })
  .catch((err) => {
    console.error('Unable to connect to the database:', err);
  });

fs.readdirSync(__dirname)
  .filter((file) => {
    return (
      file.indexOf('.') !== 0 && file !== basename && file.slice(-3) === '.js'
    );
  })
  .forEach((file) => {
    const model = require(path.join(__dirname, file))(
      sequelize,
      Sequelize.DataTypes
    );
    db[model.name] = model;
  });

Object.keys(db).forEach((modelName) => {
  if (db[modelName].associate) {
    db[modelName].associate(db);
  }
});

db.sequelize = sequelize;

module.exports = db;

If I try to change:

app.use(
  '/forest/authentication',
  cors({
    ...corsOptions,
    // 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: corsOptions.origin.concat('null'),
  })
);

to this

const watchRouter = new express.Router();

watchRouter.post(
  '/authentication',
  cors({
    ...corsOptions,
    // 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: corsOptions.origin.concat('null'),
  })
);

app.use('/forest', watchRouter);

I am still getting 404 for POST /forest/authentication and don’t know why.

Hi @and_g :wave:

I’ll look into your issue asap :clock1030: :construction_worker_man:.
At first glance, everything seems to be in its place.

Why the next(null); in your request-private-network middleware? :thinking:

Thank you for prompt answer. I saw it in a github project so I just copy pasted the code. If I change next(null); to next(), it doesn’t change anything.

I changed the code to this and it’s working now:

const createError = require('http-errors');
const express = require('express');
const path = require('path');
const cookieParser = require('cookie-parser');
const logger = require('morgan');
const { createAgent } = require('@forestadmin/agent');
const {
  createSequelizeDataSource,
} = require('@forestadmin/datasource-sequelize');
require('dotenv').config();
const cors = require('cors');
const sequelizeInstance = require('./src/db/models/index').sequelize;

var indexRouter = require('./src/routes/index');
var authRouter = require('./src/routes/api/auth');
var usersRouter = require('./src/routes/api/users');
var ceremonyRouter = require('./src/routes/api/ceremony');
var questionRouter = require('./src/routes/api/questions');

var app = express();

// Create your Forest Admin agent
// This must be called BEFORE all other middlewares on the express app
createAgent({
  authSecret: process.env.FOREST_AUTH_SECRET,
  agentUrl: process.env.FOREST_AGENT_URL,
  envSecret: process.env.FOREST_ENV_SECRET,
  isProduction: process.env.NODE_ENV === 'production',
})
  .addDataSource(createSequelizeDataSource(sequelizeInstance))
  .mountOnExpress(app)
  .start();

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

if (process.env.ALLOWED_CORS) {
  allowedOrigins = allowedOrigins.concat(process.env.ALLOWED_CORS.split(','));
}

var corsOptions = {
  origin: allowedOrigins,
};

app.use(cors(corsOptions));

// view engine setup
app.set('views', path.join(__dirname, 'src/views'));
app.set('view engine', 'jade');

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

//Routes
app.use('/', indexRouter);
app.use('/api', indexRouter);
app.use('/api/auth', authRouter);
app.use('/api/users', usersRouter);
app.use('/api/ceremony', ceremonyRouter);
app.use('/api/questions', questionRouter);

// catch 404 and forward to error handler
app.use(function (req, res, next) {
  next(createError(404));
});

// error handler
app.use(function (err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};
  res.status(err.status || 500);
  res.render('error');
});

// set port, listen for requests
const PORT = process.env.NODE_DOCKER_PORT || 8080;
app.listen(PORT, () => {
  console.log(`Server is running on port ${PORT}.`);
});

module.exports = app;
1 Like

I don’t know when I should use forest-express-sequelize or @forestadmin/agent. If I try to search createAgent or @forestadmin/agent, I am not able to find anything in your documentation Forest Admin - Developer guide

Hey @and_g :wave:

@forestadmin/agent is a new version of our forestadmin agent (As opposed to forest-express-sequelize, which is the previous version of the agent). It has a specific documentation available here, and the github repository also contains an example that shows most of the features available.

It’s quite recent, so the doc is a bit less complete than the forest-express-sequelize one. However, it is really focused on the developer experience, typescript first, and has a bunch of small new features.

You really only have to choose which one you prefer working with :slight_smile:

Don’t hesitate to try out @forestadmin/agent if you feel to :+1:

2 Likes

I think I will try it out with @forestadmin/agent because as you can see, it doesn’t work for me with forest-express-sequelize.

I have a question. In my previous project I used forest-express-mongoose. If I wanted to extend forest routes, I needed to add some logic, because it didn’t work. This is also something I didn’t find in documentation.

Do I need to add this if I use @forestadmin/agent to be able to extend forest routes or it works out of the box?

edit:// I found non existing links in beta documentation. Check my screenshot.

Nice catch for the broken link, I’ll open a ticket for those to be fixed :+1:

Do I need to add this if I use @forestadmin/agent to be able to extend forest routes or it works out of the box?

Our new version of the agent was designed without any route override in mind, to improve the developer experience.

Depending on your use-case, you should be able to use the Relationships/Fields functions, but for very complex workflow your could also use our Hooks.

Feel free to share the use cases for routes override, I’ll be happy to help :slight_smile: