diff options
Diffstat (limited to 'src/internal-server')
-rw-r--r-- | src/internal-server/app/Controllers/Http/ImageController.js | 16 | ||||
-rw-r--r-- | src/internal-server/config/app.js | 2 | ||||
-rw-r--r-- | src/internal-server/config/session.js | 2 | ||||
-rw-r--r-- | src/internal-server/config/shield.js | 2 | ||||
-rw-r--r-- | src/internal-server/start.ts | 41 | ||||
-rw-r--r-- | src/internal-server/start/routes.js | 68 | ||||
-rw-r--r-- | src/internal-server/test.ts | 2 |
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 | ||
3 | const path = require('path'); | 3 | const path = require('path'); |
4 | const fs = require('fs-extra'); | 4 | const fs = require('fs-extra'); |
5 | const sanitize = require('sanitize-filename'); | ||
5 | 6 | ||
6 | class ImageController { | 7 | class 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 | ||
18 | import fold from '@adonisjs/fold'; | 18 | import fold from '@adonisjs/fold'; |
19 | import { Ignitor } from '@adonisjs/ignitor'; | 19 | import { Ignitor, hooks } from '@adonisjs/ignitor'; |
20 | import { existsSync, readFile, statSync, chmodSync, writeFile } from 'fs-extra'; | 20 | import { readFile, stat, chmod, writeFile } from 'fs-extra'; |
21 | import { join } from 'path'; | 21 | import { join } from 'path'; |
22 | import { LOCAL_HOSTNAME } from '../config'; | 22 | import { LOCAL_HOSTNAME } from '../config'; |
23 | import { isWindows } from '../environment'; | 23 | import { isWindows } from '../environment'; |
24 | 24 | ||
25 | process.env.ENV_PATH = join(__dirname, 'env.ini'); | 25 | process.env.ENV_PATH = join(__dirname, 'env.ini'); |
26 | 26 | ||
27 | export const server = async (userPath: string, port: number) => { | 27 | async 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 | |||
47 | export 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 | ||
8 | const { timingSafeEqual } = require('crypto'); | ||
9 | |||
8 | /** @type {typeof import('@adonisjs/framework/src/Route/Manager')} */ | 10 | /** @type {typeof import('@adonisjs/framework/src/Route/Manager')} */ |
9 | const Route = use('Route'); | 11 | const Route = use('Route'); |
10 | 12 | ||
@@ -14,14 +16,38 @@ const migrate = require('./migrate'); | |||
14 | 16 | ||
15 | migrate(); | 17 | migrate(); |
16 | 18 | ||
19 | async 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 | |||
17 | const OnlyAllowFerdium = async ({ request, response }, next) => { | 31 | const 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 | |||
41 | const RequireTokenInQS = async ({ request, response }, next) => { | ||
42 | const clientToken = request.get().token; | ||
43 | return validateToken(clientToken, response, next); | ||
44 | } | ||
45 | |||
46 | const FERDIUM_LOCAL_TOKEN_COOKIE = 'ferdium-local-token'; | ||
47 | |||
48 | const 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 | ||
68 | Route.group(() => { | 94 | Route.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 | 100 | Route.group(() => { |
73 | Route.post('import', 'UserController.import'); | 101 | // Franz account import |
74 | Route.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 |
77 | Route.get('export', 'UserController.export'); | 111 | Route.get('/', ({ view }) => view.render('index')); |
78 | Route.post('transfer', 'UserController.importFerdium'); | 112 | }).middleware(RequireAuthenticatedBrowser); |
79 | Route.get('transfer', ({ view }) => view.render('transfer')); | ||
80 | 113 | ||
81 | // Index | 114 | Route.get('token/:token', ({ params: { token }, response }) => { |
82 | Route.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 | ||
7 | ensureDirSync(dummyUserFolder); | 7 | ensureDirSync(dummyUserFolder); |
8 | 8 | ||
9 | server(dummyUserFolder, 46_568); | 9 | server(dummyUserFolder, 46_568, 'test').catch(console.log); |