summaryrefslogtreecommitdiffstats
path: root/src/internal-server
diff options
context:
space:
mode:
authorLibravatar Kristóf Marussy <kristof@marussy.com>2022-07-10 16:07:45 +0200
committerLibravatar Kristóf Marussy <kristof@marussy.com>2022-07-11 17:47:53 +0200
commitfa1a7037b47f2e0114d8abc5a99d29239bd3637b (patch)
tree83404acf711aa8976dce47950edcca64836e0cd8 /src/internal-server
parent6.0.0-nightly.96 [skip ci] (diff)
downloadferdium-app-fa1a7037b47f2e0114d8abc5a99d29239bd3637b.tar.gz
ferdium-app-fa1a7037b47f2e0114d8abc5a99d29239bd3637b.tar.zst
ferdium-app-fa1a7037b47f2e0114d8abc5a99d29239bd3637b.zip
refactor: local server import/export
Signed-off-by: Kristóf Marussy <kristof@marussy.com>
Diffstat (limited to 'src/internal-server')
-rw-r--r--src/internal-server/app/Controllers/Http/ImageController.js16
-rw-r--r--src/internal-server/config/app.js2
-rw-r--r--src/internal-server/config/session.js2
-rw-r--r--src/internal-server/config/shield.js2
-rw-r--r--src/internal-server/start.ts41
-rw-r--r--src/internal-server/start/routes.js68
-rw-r--r--src/internal-server/test.ts2
7 files changed, 105 insertions, 28 deletions
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');
2 2
3const path = require('path'); 3const path = require('path');
4const fs = require('fs-extra'); 4const fs = require('fs-extra');
5const sanitize = require('sanitize-filename');
5 6
6class ImageController { 7class ImageController {
7 async icon({ params, response }) { 8 async icon({ params, response }) {
8 const { id } = params; 9 let { id } = params;
10
11 id = sanitize(id);
12 if (id === '') {
13 return response.status(404).send({
14 status: "Icon doesn't exist",
15 });
16 }
9 17
10 const iconPath = path.join(Env.get('USER_PATH'), 'icons', id); 18 const iconPath = path.join(Env.get('USER_PATH'), 'icons', id);
11 if (!fs.existsSync(iconPath)) { 19
20 try {
21 await fs.access(iconPath);
22 } catch {
23 // File not available.
12 return response.status(404).send({ 24 return response.status(404).send({
13 status: "Icon doesn't exist", 25 status: "Icon doesn't exist",
14 }); 26 });
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 = {
232 */ 232 */
233 cookie: { 233 cookie: {
234 httpOnly: true, 234 httpOnly: true,
235 sameSite: false, 235 sameSite: true,
236 path: '/', 236 path: '/',
237 maxAge: 7200, 237 maxAge: 7200,
238 }, 238 },
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 = {
63 cookie: { 63 cookie: {
64 httpOnly: true, 64 httpOnly: true,
65 path: '/', 65 path: '/',
66 sameSite: false, 66 sameSite: true,
67 }, 67 },
68 68
69 /* 69 /*
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 = {
133 methods: ['POST', 'PUT', 'DELETE'], 133 methods: ['POST', 'PUT', 'DELETE'],
134 filterUris: [], 134 filterUris: [],
135 cookieOptions: { 135 cookieOptions: {
136 httpOnly: false, 136 httpOnly: true,
137 sameSite: true, 137 sameSite: true,
138 path: '/', 138 path: '/',
139 maxAge: 7200, 139 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 @@
16*/ 16*/
17 17
18import fold from '@adonisjs/fold'; 18import fold from '@adonisjs/fold';
19import { Ignitor } from '@adonisjs/ignitor'; 19import { Ignitor, hooks } from '@adonisjs/ignitor';
20import { existsSync, readFile, statSync, chmodSync, writeFile } from 'fs-extra'; 20import { readFile, stat, chmod, writeFile } from 'fs-extra';
21import { join } from 'path'; 21import { join } from 'path';
22import { LOCAL_HOSTNAME } from '../config'; 22import { LOCAL_HOSTNAME } from '../config';
23import { isWindows } from '../environment'; 23import { isWindows } from '../environment';
24 24
25process.env.ENV_PATH = join(__dirname, 'env.ini'); 25process.env.ENV_PATH = join(__dirname, 'env.ini');
26 26
27export const server = async (userPath: string, port: number) => { 27async function ensureDB(dbPath: string): Promise<void> {
28 const dbPath = join(userPath, 'server.sqlite'); 28 try {
29 const dbTemplatePath = join(__dirname, 'database', 'template.sqlite'); 29 await stat(dbPath);
30 30 } catch {
31 if (!existsSync(dbPath)) { 31 // Database does not exist.
32 // Manually copy file 32 // Manually copy file
33 // We can't use copyFile here as it will cause the file to be readonly on Windows 33 // We can't use copyFile here as it will cause the file to be readonly on Windows
34 const dbTemplatePath = join(__dirname, 'database', 'template.sqlite');
34 const dbTemplate = await readFile(dbTemplatePath); 35 const dbTemplate = await readFile(dbTemplatePath);
35 await writeFile(dbPath, dbTemplate); 36 await writeFile(dbPath, dbTemplate);
36 37
37 // Change permissions to ensure to file is not read-only 38 // Change permissions to ensure to file is not read-only
38 if (isWindows) { 39 if (isWindows) {
40 const stats = await stat(dbPath);
39 // eslint-disable-next-line no-bitwise 41 // eslint-disable-next-line no-bitwise
40 chmodSync(dbPath, statSync(dbPath).mode | 146); 42 await chmod(dbPath, stats.mode | 146);
41 } 43 }
42 } 44 }
45}
46
47export const server = async (userPath: string, port: number, token: string) => {
48 const dbPath = join(userPath, 'server.sqlite');
49 await ensureDB(dbPath);
43 50
44 // Note: These env vars are used by adonis as env vars 51 // Note: These env vars are used by adonis as env vars
45 process.env.DB_PATH = dbPath; 52 process.env.DB_PATH = dbPath;
46 process.env.USER_PATH = userPath; 53 process.env.USER_PATH = userPath;
47 process.env.HOST = LOCAL_HOSTNAME; 54 process.env.HOST = LOCAL_HOSTNAME;
48 process.env.PORT = port.toString(); 55 process.env.PORT = port.toString();
56 process.env.FERDIUM_LOCAL_TOKEN = token;
49 57
50 new Ignitor(fold).appRoot(__dirname).fireHttpServer().catch(console.error); 58 return new Promise<void>((resolve, reject) => {
59 let returned = false;
60 hooks.after.httpServer(() => {
61 if (!returned) {
62 resolve();
63 returned = true;
64 }
65 });
66 new Ignitor(fold).appRoot(__dirname).fireHttpServer().catch((error) => {
67 console.error(error);
68 if (!returned) {
69 returned = true;
70 reject(error);
71 }
72 });
73 });
51}; 74};
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 @@
5| 5|
6*/ 6*/
7 7
8const { timingSafeEqual } = require('crypto');
9
8/** @type {typeof import('@adonisjs/framework/src/Route/Manager')} */ 10/** @type {typeof import('@adonisjs/framework/src/Route/Manager')} */
9const Route = use('Route'); 11const Route = use('Route');
10 12
@@ -14,14 +16,38 @@ const migrate = require('./migrate');
14 16
15migrate(); 17migrate();
16 18
19async function validateToken(clientToken, response, next) {
20 const serverToken = process.env.FERDIUM_LOCAL_TOKEN;
21 const valid = serverToken &&
22 clientToken &&
23 timingSafeEqual(Buffer.from(clientToken, 'utf8'), Buffer.from(serverToken, 'utf8'));
24 if (valid) {
25 await next();
26 return true;
27 }
28 return response.forbidden();
29}
30
17const OnlyAllowFerdium = async ({ request, response }, next) => { 31const OnlyAllowFerdium = async ({ request, response }, next) => {
18 const version = request.header('X-Franz-Version'); 32 const version = request.header('X-Franz-Version');
19 if (!version) { 33 if (!version) {
20 return response.status(403).redirect('/'); 34 return response.forbidden();
21 } 35 }
22 36
23 await next(); 37 const clientToken = request.header('X-Ferdium-Local-Token');
24 return true; 38 return validateToken(clientToken, response, next);
39};
40
41const RequireTokenInQS = async ({ request, response }, next) => {
42 const clientToken = request.get().token;
43 return validateToken(clientToken, response, next);
44}
45
46const FERDIUM_LOCAL_TOKEN_COOKIE = 'ferdium-local-token';
47
48const RequireAuthenticatedBrowser = async({ request, response }, next) => {
49 const clientToken = request.cookie(FERDIUM_LOCAL_TOKEN_COOKIE);
50 return validateToken(clientToken, response, next);
25}; 51};
26 52
27// Health: Returning if all systems function correctly 53// Health: Returning if all systems function correctly
@@ -67,16 +93,32 @@ Route.group(() => {
67 93
68Route.group(() => { 94Route.group(() => {
69 Route.get('icon/:id', 'ImageController.icon'); 95 Route.get('icon/:id', 'ImageController.icon');
70}).prefix(API_VERSION); 96})
97 .prefix(API_VERSION)
98 .middleware(RequireTokenInQS);
71 99
72// Franz account import 100Route.group(() => {
73Route.post('import', 'UserController.import'); 101 // Franz account import
74Route.get('import', ({ view }) => view.render('import')); 102 Route.post('import', 'UserController.import');
103 Route.get('import', ({ view }) => view.render('import'));
104
105 // Account transfer
106 Route.get('export', 'UserController.export');
107 Route.post('transfer', 'UserController.importFerdium');
108 Route.get('transfer', ({ view }) => view.render('transfer'));
75 109
76// Account transfer 110 // Index
77Route.get('export', 'UserController.export'); 111 Route.get('/', ({ view }) => view.render('index'));
78Route.post('transfer', 'UserController.importFerdium'); 112}).middleware(RequireAuthenticatedBrowser);
79Route.get('transfer', ({ view }) => view.render('transfer'));
80 113
81// Index 114Route.get('token/:token', ({ params: { token }, response }) => {
82Route.get('/', ({ view }) => view.render('index')); 115 if (validateToken(token)) {
116 response.cookie(FERDIUM_LOCAL_TOKEN_COOKIE, token, {
117 httpOnly: true,
118 sameSite: true,
119 path: '/',
120 });
121 return response.redirect('/');
122 }
123 return response.forbidden();
124});
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');
6 6
7ensureDirSync(dummyUserFolder); 7ensureDirSync(dummyUserFolder);
8 8
9server(dummyUserFolder, 46_568); 9server(dummyUserFolder, 46_568, 'test').catch(console.log);