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.