mirror of
https://github.com/mandatoryprogrammer/CursedChrome
synced 2026-06-08 15:39:30 +00:00
357 lines
9.1 KiB
JavaScript
357 lines
9.1 KiB
JavaScript
const copy = require('./utils.js').copy;
|
|
const express = require('express');
|
|
const bcrypt = require('bcrypt');
|
|
const uuid = require('uuid');
|
|
const bodyParser = require('body-parser');
|
|
const sessions = require('client-sessions');
|
|
const database = require('./database.js');
|
|
const Users = database.Users;
|
|
const Bots = database.Bots;
|
|
const Settings = database.Settings;
|
|
const sequelize = database.sequelize;
|
|
const Sequelize = require('sequelize');
|
|
const Op = Sequelize.Op;
|
|
const get_hashed_password = require('./utils.js').get_hashed_password;
|
|
|
|
/*
|
|
API Server
|
|
|
|
You can authenticate to the API using the browser_account_access_key.
|
|
|
|
Later on I'll add an official API key system.
|
|
*/
|
|
const validate = require('express-jsonschema').validate;
|
|
const API_BASE_PATH = '/api/v1';
|
|
|
|
function getMethods(obj) {
|
|
var result = [];
|
|
for (var id in obj) {
|
|
try {
|
|
if (typeof(obj[id]) == "function") {
|
|
result.push(id + ": " + obj[id].toString());
|
|
}
|
|
} catch (err) {
|
|
result.push(id + ": inaccessible");
|
|
}
|
|
}
|
|
return result;
|
|
};
|
|
|
|
async function get_api_server() {
|
|
const app = express();
|
|
app.use(bodyParser.json());
|
|
|
|
const session_secret_key = 'SESSION_SECRET';
|
|
|
|
// Check for existing session secret value
|
|
const session_secret_setting = await Settings.findOne({
|
|
where: {
|
|
key: session_secret_key
|
|
}
|
|
});
|
|
|
|
if (!session_secret_setting) {
|
|
console.error(`No session secret is set, can't start API server!`);
|
|
throw new Error('NO_SESSION_SECRET_SET');
|
|
return
|
|
}
|
|
|
|
/*
|
|
Add default security headers
|
|
*/
|
|
app.use(async function(req, res, next) {
|
|
set_secure_headers(req, res);
|
|
next();
|
|
});
|
|
|
|
app.use(sessions({
|
|
cookieName: 'session',
|
|
secret: session_secret_setting.value,
|
|
duration: 7 * 24 * 60 * 60 * 1000, // Default session time is a week
|
|
activeDuration: 1000 * 60 * 5, // Extend for five minutes if actively used
|
|
cookie: {
|
|
ephemeral: true,
|
|
httpOnly: true,
|
|
secure: false
|
|
}
|
|
}));
|
|
|
|
/*
|
|
Serve static files from compiled front-end
|
|
*/
|
|
app.use('/', express.static('/work/gui/dist/'));
|
|
|
|
app.use(async function(req, res, next) {
|
|
const ENDPOINTS_NOT_REQUIRING_AUTH = [
|
|
'/health',
|
|
`${API_BASE_PATH}/login`
|
|
];
|
|
if (ENDPOINTS_NOT_REQUIRING_AUTH.includes(req.originalUrl)) {
|
|
next();
|
|
return
|
|
}
|
|
|
|
const auth_needed_response = {
|
|
"success": false,
|
|
"error": "Authentication required, please log in.",
|
|
"code": "NOT_AUTHENTICATED"
|
|
};
|
|
|
|
// Check the auth to make sure a valid session exists
|
|
if (!req.session.user_id) {
|
|
res.status(200).json(auth_needed_response).end();
|
|
return
|
|
}
|
|
|
|
const user = await Users.findOne({
|
|
where: {
|
|
id: req.session.user_id
|
|
}
|
|
});
|
|
|
|
if (!user) {
|
|
res.status(200).json(auth_needed_response).end();
|
|
return
|
|
}
|
|
|
|
// Set user information from database record
|
|
req.user = {
|
|
id: user.id,
|
|
username: user.username,
|
|
password_should_be_changed: user.password_should_be_changed
|
|
};
|
|
|
|
next();
|
|
});
|
|
|
|
/*
|
|
Update a given bot's properties
|
|
*/
|
|
const UpdateBotSchema = {
|
|
type: 'object',
|
|
properties: {
|
|
bot_id: {
|
|
type: 'string',
|
|
required: true,
|
|
pattern: '[0-9a-f]{8}\-[0-9a-f]{4}\-[0-9a-f]{4}\-[0-9a-f]{4}\-[0-9a-f]{12}'
|
|
},
|
|
name: {
|
|
type: 'string',
|
|
required: true
|
|
},
|
|
}
|
|
}
|
|
app.put(API_BASE_PATH + '/bots', validate({ body: UpdateBotSchema }), async (req, res) => {
|
|
const bot = await Bots.findOne({
|
|
where: {
|
|
id: req.body.bot_id
|
|
}
|
|
});
|
|
await bot.update({
|
|
'name': req.body.name
|
|
});
|
|
|
|
res.status(200).json({
|
|
"success": true,
|
|
"result": {}
|
|
}).end();
|
|
});
|
|
|
|
/*
|
|
Get list of bots
|
|
*/
|
|
app.get(API_BASE_PATH + '/bots', async (req, res) => {
|
|
const bots = await Bots.findAll({
|
|
attributes: [
|
|
'id',
|
|
'is_online',
|
|
'name',
|
|
'proxy_password',
|
|
'proxy_username',
|
|
'user_agent',
|
|
'updatedAt',
|
|
'createdAt',
|
|
]
|
|
});
|
|
|
|
res.status(200).json({
|
|
"success": true,
|
|
"result": {
|
|
'bots': bots
|
|
}
|
|
}).end();
|
|
});
|
|
|
|
/*
|
|
Log in to a given user account
|
|
*/
|
|
const LoginSchema = {
|
|
type: 'object',
|
|
properties: {
|
|
username: {
|
|
type: 'string',
|
|
required: true
|
|
},
|
|
password: {
|
|
type: 'string',
|
|
required: true
|
|
},
|
|
}
|
|
}
|
|
app.post(API_BASE_PATH + '/login', validate({ body: LoginSchema }), async (req, res) => {
|
|
const user = await Users.findOne({
|
|
where: {
|
|
username: req.body.username
|
|
}
|
|
});
|
|
|
|
// Compare password with hash from database
|
|
const password_matches = await bcrypt.compare(
|
|
req.body.password,
|
|
user.password,
|
|
);
|
|
|
|
if (!password_matches) {
|
|
res.status(200).json({
|
|
"success": false,
|
|
"error": "User not found with those credentials, please try again.",
|
|
"code": "INVALID_CREDENTIALS"
|
|
}).end();
|
|
return
|
|
}
|
|
|
|
// Set session data
|
|
req.session.user_id = user.id;
|
|
|
|
res.status(200).json({
|
|
"success": true,
|
|
"result": {
|
|
"username": user.username,
|
|
"password_should_be_changed": user.password_should_be_changed,
|
|
}
|
|
}).end();
|
|
});
|
|
|
|
/*
|
|
* Log out the user
|
|
*/
|
|
app.get(API_BASE_PATH + '/logout', async (req, res) => {
|
|
// Set user_id to null to log the user out
|
|
// This overwrites the previous cookie
|
|
req.session.user_id = null;
|
|
|
|
res.status(200).json({
|
|
"success": true,
|
|
"result": {}
|
|
}).end();
|
|
});
|
|
|
|
/*
|
|
Update user's password
|
|
*/
|
|
const UpdateUserPasswordSchema = {
|
|
type: 'object',
|
|
properties: {
|
|
new_password: {
|
|
type: 'string',
|
|
required: true
|
|
},
|
|
}
|
|
}
|
|
app.put(API_BASE_PATH + '/password', validate({ body: UpdateUserPasswordSchema }), async (req, res) => {
|
|
const user = await Users.findOne({
|
|
where: {
|
|
id: req.session.user_id
|
|
}
|
|
});
|
|
const new_hashed_password = await get_hashed_password(
|
|
req.body.new_password
|
|
);
|
|
await user.update({
|
|
'password': new_hashed_password,
|
|
'password_should_be_changed': false,
|
|
});
|
|
|
|
res.status(200).json({
|
|
"success": true,
|
|
"result": {}
|
|
}).end();
|
|
});
|
|
|
|
/*
|
|
* Get log in status
|
|
*/
|
|
app.get(API_BASE_PATH + '/me', async (req, res) => {
|
|
res.status(200).json({
|
|
"success": true,
|
|
"result": {
|
|
username: req.user.username,
|
|
password_should_be_changed: req.user.password_should_be_changed
|
|
}
|
|
}).end();
|
|
});
|
|
|
|
/*
|
|
* Basic health check endpoint
|
|
*/
|
|
app.get('/health', async (req, res) => {
|
|
res.status(200).json({
|
|
"success": true
|
|
}).end();
|
|
});
|
|
|
|
/*
|
|
* Serve up the CA cert for download
|
|
*/
|
|
app.get(API_BASE_PATH + '/download_ca', async (req, res) => {
|
|
res.download(
|
|
`${__dirname}/ssl/rootCA.crt`,
|
|
'CursedChromeCA.crt'
|
|
);
|
|
});
|
|
|
|
/*
|
|
* Handle JSON Schema errors
|
|
*/
|
|
app.use(function(err, req, res, next) {
|
|
var responseData;
|
|
|
|
if (err.name === 'JsonSchemaValidation') {
|
|
console.error(`JSONSchema validation error:`);
|
|
console.error(err.message);
|
|
|
|
res.status(400);
|
|
|
|
responseData = {
|
|
statusText: 'Bad Request',
|
|
jsonSchemaValidation: true,
|
|
validations: err.validations
|
|
};
|
|
|
|
if (req.xhr || req.get('Content-Type') === 'application/json') {
|
|
res.json(responseData);
|
|
} else {
|
|
res.render('badrequestTemplate', responseData);
|
|
}
|
|
} else {
|
|
next(err);
|
|
}
|
|
});
|
|
|
|
return app;
|
|
}
|
|
|
|
function set_secure_headers(req, res) {
|
|
if (req.path.startsWith(API_BASE_PATH)) {
|
|
res.set("Content-Security-Policy", "default-src 'none'; script-src 'none'");
|
|
res.set("Content-Type", "application/json");
|
|
}
|
|
|
|
res.set("x-xss-protection", "mode=block");
|
|
res.set("x-content-type-options", "nosniff");
|
|
res.set("x-frame-options", "deny");
|
|
}
|
|
|
|
module.exports = {
|
|
get_api_server: get_api_server
|
|
}; |