From fa1a7037b47f2e0114d8abc5a99d29239bd3637b Mon Sep 17 00:00:00 2001 From: Kristóf Marussy Date: Sun, 10 Jul 2022 16:07:45 +0200 Subject: refactor: local server import/export MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Kristóf Marussy --- .../app/Controllers/Http/ImageController.js | 16 ++++- src/internal-server/config/app.js | 2 +- src/internal-server/config/session.js | 2 +- src/internal-server/config/shield.js | 2 +- src/internal-server/start.ts | 41 ++++++++++--- src/internal-server/start/routes.js | 68 +++++++++++++++++----- src/internal-server/test.ts | 2 +- 7 files changed, 105 insertions(+), 28 deletions(-) (limited to 'src/internal-server') diff --git a/src/internal-server/app/Controllers/Http/ImageController.js b/src/internal-server/app/Controllers/Http/ImageController.js index 9b11783c7..731f181e0 100644 --- a/src/internal-server/app/Controllers/Http/ImageController.js +++ b/src/internal-server/app/Controllers/Http/ImageController.js @@ -2,13 +2,25 @@ const Env = use('Env'); const path = require('path'); const fs = require('fs-extra'); +const sanitize = require('sanitize-filename'); class ImageController { async icon({ params, response }) { - const { id } = params; + let { id } = params; + + id = sanitize(id); + if (id === '') { + return response.status(404).send({ + status: "Icon doesn't exist", + }); + } const iconPath = path.join(Env.get('USER_PATH'), 'icons', id); - if (!fs.existsSync(iconPath)) { + + try { + await fs.access(iconPath); + } catch { + // File not available. return response.status(404).send({ status: "Icon doesn't exist", }); diff --git a/src/internal-server/config/app.js b/src/internal-server/config/app.js index 303e1290c..e8b52af18 100644 --- a/src/internal-server/config/app.js +++ b/src/internal-server/config/app.js @@ -232,7 +232,7 @@ module.exports = { */ cookie: { httpOnly: true, - sameSite: false, + sameSite: true, path: '/', maxAge: 7200, }, diff --git a/src/internal-server/config/session.js b/src/internal-server/config/session.js index 3ce3cc4da..dbe007e2b 100644 --- a/src/internal-server/config/session.js +++ b/src/internal-server/config/session.js @@ -63,7 +63,7 @@ module.exports = { cookie: { httpOnly: true, path: '/', - sameSite: false, + sameSite: true, }, /* diff --git a/src/internal-server/config/shield.js b/src/internal-server/config/shield.js index 4ff22c3f9..55029faa4 100644 --- a/src/internal-server/config/shield.js +++ b/src/internal-server/config/shield.js @@ -133,7 +133,7 @@ module.exports = { methods: ['POST', 'PUT', 'DELETE'], filterUris: [], cookieOptions: { - httpOnly: false, + httpOnly: true, sameSite: true, path: '/', maxAge: 7200, diff --git a/src/internal-server/start.ts b/src/internal-server/start.ts index 62311b21e..ae28e3313 100644 --- a/src/internal-server/start.ts +++ b/src/internal-server/start.ts @@ -16,36 +16,59 @@ */ import fold from '@adonisjs/fold'; -import { Ignitor } from '@adonisjs/ignitor'; -import { existsSync, readFile, statSync, chmodSync, writeFile } from 'fs-extra'; +import { Ignitor, hooks } from '@adonisjs/ignitor'; +import { readFile, stat, chmod, writeFile } from 'fs-extra'; import { join } from 'path'; import { LOCAL_HOSTNAME } from '../config'; import { isWindows } from '../environment'; process.env.ENV_PATH = join(__dirname, 'env.ini'); -export const server = async (userPath: string, port: number) => { - const dbPath = join(userPath, 'server.sqlite'); - const dbTemplatePath = join(__dirname, 'database', 'template.sqlite'); - - if (!existsSync(dbPath)) { +async function ensureDB(dbPath: string): Promise { + try { + await stat(dbPath); + } catch { + // Database does not exist. // Manually copy file // We can't use copyFile here as it will cause the file to be readonly on Windows + const dbTemplatePath = join(__dirname, 'database', 'template.sqlite'); const dbTemplate = await readFile(dbTemplatePath); await writeFile(dbPath, dbTemplate); // Change permissions to ensure to file is not read-only if (isWindows) { + const stats = await stat(dbPath); // eslint-disable-next-line no-bitwise - chmodSync(dbPath, statSync(dbPath).mode | 146); + await chmod(dbPath, stats.mode | 146); } } +} + +export const server = async (userPath: string, port: number, token: string) => { + const dbPath = join(userPath, 'server.sqlite'); + await ensureDB(dbPath); // Note: These env vars are used by adonis as env vars process.env.DB_PATH = dbPath; process.env.USER_PATH = userPath; process.env.HOST = LOCAL_HOSTNAME; process.env.PORT = port.toString(); + process.env.FERDIUM_LOCAL_TOKEN = token; - new Ignitor(fold).appRoot(__dirname).fireHttpServer().catch(console.error); + return new Promise((resolve, reject) => { + let returned = false; + hooks.after.httpServer(() => { + if (!returned) { + resolve(); + returned = true; + } + }); + new Ignitor(fold).appRoot(__dirname).fireHttpServer().catch((error) => { + console.error(error); + if (!returned) { + returned = true; + reject(error); + } + }); + }); }; diff --git a/src/internal-server/start/routes.js b/src/internal-server/start/routes.js index 79c809f5f..736796bb8 100644 --- a/src/internal-server/start/routes.js +++ b/src/internal-server/start/routes.js @@ -5,6 +5,8 @@ | */ +const { timingSafeEqual } = require('crypto'); + /** @type {typeof import('@adonisjs/framework/src/Route/Manager')} */ const Route = use('Route'); @@ -14,14 +16,38 @@ const migrate = require('./migrate'); migrate(); +async function validateToken(clientToken, response, next) { + const serverToken = process.env.FERDIUM_LOCAL_TOKEN; + const valid = serverToken && + clientToken && + timingSafeEqual(Buffer.from(clientToken, 'utf8'), Buffer.from(serverToken, 'utf8')); + if (valid) { + await next(); + return true; + } + return response.forbidden(); +} + const OnlyAllowFerdium = async ({ request, response }, next) => { const version = request.header('X-Franz-Version'); if (!version) { - return response.status(403).redirect('/'); + return response.forbidden(); } - await next(); - return true; + const clientToken = request.header('X-Ferdium-Local-Token'); + return validateToken(clientToken, response, next); +}; + +const RequireTokenInQS = async ({ request, response }, next) => { + const clientToken = request.get().token; + return validateToken(clientToken, response, next); +} + +const FERDIUM_LOCAL_TOKEN_COOKIE = 'ferdium-local-token'; + +const RequireAuthenticatedBrowser = async({ request, response }, next) => { + const clientToken = request.cookie(FERDIUM_LOCAL_TOKEN_COOKIE); + return validateToken(clientToken, response, next); }; // Health: Returning if all systems function correctly @@ -67,16 +93,32 @@ Route.group(() => { Route.group(() => { Route.get('icon/:id', 'ImageController.icon'); -}).prefix(API_VERSION); +}) + .prefix(API_VERSION) + .middleware(RequireTokenInQS); -// Franz account import -Route.post('import', 'UserController.import'); -Route.get('import', ({ view }) => view.render('import')); +Route.group(() => { + // Franz account import + Route.post('import', 'UserController.import'); + Route.get('import', ({ view }) => view.render('import')); + + // Account transfer + Route.get('export', 'UserController.export'); + Route.post('transfer', 'UserController.importFerdium'); + Route.get('transfer', ({ view }) => view.render('transfer')); -// Account transfer -Route.get('export', 'UserController.export'); -Route.post('transfer', 'UserController.importFerdium'); -Route.get('transfer', ({ view }) => view.render('transfer')); + // Index + Route.get('/', ({ view }) => view.render('index')); +}).middleware(RequireAuthenticatedBrowser); -// Index -Route.get('/', ({ view }) => view.render('index')); +Route.get('token/:token', ({ params: { token }, response }) => { + if (validateToken(token)) { + response.cookie(FERDIUM_LOCAL_TOKEN_COOKIE, token, { + httpOnly: true, + sameSite: true, + path: '/', + }); + return response.redirect('/'); + } + return response.forbidden(); +}); diff --git a/src/internal-server/test.ts b/src/internal-server/test.ts index 87ed57848..5bb1f2b36 100644 --- a/src/internal-server/test.ts +++ b/src/internal-server/test.ts @@ -6,4 +6,4 @@ const dummyUserFolder = join(__dirname, 'user_data'); ensureDirSync(dummyUserFolder); -server(dummyUserFolder, 46_568); +server(dummyUserFolder, 46_568, 'test').catch(console.log); -- cgit v1.2.3-70-g09d2