From 83cef7a824bc91cef772bff658fd6ae7895fa227 Mon Sep 17 00:00:00 2001 From: Laurent Cozic Date: Tue, 25 May 2021 16:42:21 +0200 Subject: [PATCH 1/2] Server: Allow using a different domain for API, main website and user content --- packages/app-desktop/runForSharing.sh | 4 +- packages/server/src/app.ts | 4 +- packages/server/src/config.ts | 24 +++++++++- .../server/src/middleware/routeHandler.ts | 13 ++---- packages/server/src/routes/api/debug.ts | 3 +- packages/server/src/routes/api/events.ts | 3 +- packages/server/src/routes/api/items.ts | 3 +- packages/server/src/routes/api/ping.ts | 3 +- packages/server/src/routes/api/sessions.ts | 3 +- packages/server/src/routes/api/share_users.ts | 3 +- packages/server/src/routes/api/shares.ts | 3 +- packages/server/src/routes/api/users.ts | 3 +- packages/server/src/routes/default.ts | 4 +- packages/server/src/routes/index/changes.ts | 6 ++- packages/server/src/routes/index/home.ts | 3 +- packages/server/src/routes/index/items.ts | 9 ++-- packages/server/src/routes/index/login.ts | 3 +- packages/server/src/routes/index/logout.ts | 3 +- .../server/src/routes/index/notifications.ts | 3 +- packages/server/src/routes/index/shares.ts | 3 +- packages/server/src/routes/index/users.ts | 7 +-- packages/server/src/utils/Router.ts | 46 ++++++++++++------- packages/server/src/utils/errors.ts | 4 +- packages/server/src/utils/routeUtils.ts | 6 ++- packages/server/src/utils/types.ts | 8 ++++ 25 files changed, 114 insertions(+), 60 deletions(-) diff --git a/packages/app-desktop/runForSharing.sh b/packages/app-desktop/runForSharing.sh index 51295a1829..a9cb9cd3ba 100755 --- a/packages/app-desktop/runForSharing.sh +++ b/packages/app-desktop/runForSharing.sh @@ -26,12 +26,12 @@ if [ "$RESET_ALL" == "1" ]; then echo "config keychain.supported 0" >> "$CMD_FILE" echo "config sync.target 9" >> "$CMD_FILE" - echo "config sync.9.path http://localhost:22300" >> "$CMD_FILE" + echo "config sync.9.path http://api-joplincloud.local:22300" >> "$CMD_FILE" echo "config sync.9.username $USER_EMAIL" >> "$CMD_FILE" echo "config sync.9.password 123456" >> "$CMD_FILE" if [ "$1" == "1" ]; then - curl --data '{"action": "createTestUsers"}' -H 'Content-Type: application/json' http://localhost:22300/api/debug + curl --data '{"action": "createTestUsers"}' -H 'Content-Type: application/json' http://api-joplincloud.local:22300/api/debug echo 'mkbook "shared"' >> "$CMD_FILE" echo 'mkbook "other"' >> "$CMD_FILE" diff --git a/packages/server/src/app.ts b/packages/server/src/app.ts index df301c853e..1d92bb8260 100644 --- a/packages/server/src/app.ts +++ b/packages/server/src/app.ts @@ -130,6 +130,8 @@ async function main() { appLogger().info(`Starting server (${env}) on port ${config().port} and PID ${process.pid}...`); appLogger().info('Running in Docker:', runningInDocker()); appLogger().info('Public base URL:', config().baseUrl); + appLogger().info('API base URL:', config().apiBaseUrl); + appLogger().info('User content base URL:', config().userContentBaseUrl); appLogger().info('Log dir:', config().logDir); appLogger().info('DB Config:', markPasswords(config().database)); @@ -158,7 +160,7 @@ async function main() { // } // } - appLogger().info(`Call this for testing: \`curl ${config().baseUrl}/api/ping\``); + appLogger().info(`Call this for testing: \`curl ${config().apiBaseUrl}/api/ping\``); // const tree: any = { // '000000000000000000000000000000F1': {}, diff --git a/packages/server/src/config.ts b/packages/server/src/config.ts index 9ddaff1d5d..d13f9519a2 100644 --- a/packages/server/src/config.ts +++ b/packages/server/src/config.ts @@ -1,9 +1,12 @@ import { rtrimSlashes } from '@joplin/lib/path-utils'; -import { Config, DatabaseConfig, DatabaseConfigClient, MailerConfig } from './utils/types'; +import { Config, DatabaseConfig, DatabaseConfigClient, MailerConfig, RouteType } from './utils/types'; import * as pathUtils from 'path'; export interface EnvVariables { APP_BASE_URL?: string; + USER_CONTENT_BASE_URL?: string; + API_BASE_URL?: string; + APP_PORT?: string; DB_CLIENT?: string; RUNNING_IN_DOCKER?: string; @@ -96,6 +99,7 @@ export function initConfig(env: EnvVariables, overrides: any = null) { const rootDir = pathUtils.dirname(__dirname); const viewDir = `${pathUtils.dirname(__dirname)}/src/views`; const appPort = env.APP_PORT ? Number(env.APP_PORT) : 22300; + const baseUrl = baseUrlFromEnv(env, appPort); config_ = { rootDir: rootDir, @@ -106,11 +110,27 @@ export function initConfig(env: EnvVariables, overrides: any = null) { database: databaseConfigFromEnv(runningInDocker_, env), mailer: mailerConfigFromEnv(env), port: appPort, - baseUrl: baseUrlFromEnv(env, appPort), + baseUrl, + apiBaseUrl: env.API_BASE_URL ? env.API_BASE_URL : baseUrl, + userContentBaseUrl: env.USER_CONTENT_BASE_URL ? env.USER_CONTENT_BASE_URL : baseUrl, ...overrides, }; } +export function baseUrl(type: RouteType): string { + if (type === RouteType.Web) return config().baseUrl; + if (type === RouteType.Api) return config().apiBaseUrl; + if (type === RouteType.UserContent) return config().userContentBaseUrl; + throw new Error(`Unknown type: ${type}`); +} + +// User content URL is not supported for now so only show the URL if the +// user content is hosted on the same domain. Needs to get cookie working +// across domains to get user content url working. +export function showItemUrls(config: Config): boolean { + return config.userContentBaseUrl === config.baseUrl; +} + function config(): Config { if (!config_) throw new Error('Config has not been initialized!'); return config_; diff --git a/packages/server/src/middleware/routeHandler.ts b/packages/server/src/middleware/routeHandler.ts index acb8f69a82..4b7d836587 100644 --- a/packages/server/src/middleware/routeHandler.ts +++ b/packages/server/src/middleware/routeHandler.ts @@ -1,15 +1,6 @@ import { routeResponseFormat, Response, RouteResponseFormat, execRequest } from '../utils/routeUtils'; import { AppContext, Env } from '../utils/types'; import { isView, View } from '../services/MustacheService'; -// import config from '../config'; - -// let mustache_: MustacheService = null; -// function mustache(): MustacheService { -// if (!mustache_) { -// mustache_ = new MustacheService(config().viewDir, config().baseUrl); -// } -// return mustache_; -// } export default async function(ctx: AppContext) { ctx.appLogger().info(`${ctx.request.method} ${ctx.path}`); @@ -44,7 +35,9 @@ export default async function(ctx: AppContext) { const responseFormat = routeResponseFormat(ctx); - if (responseFormat === RouteResponseFormat.Html) { + if (error.code === 'invalidOrigin') { + ctx.response.body = error.message; + } else if (responseFormat === RouteResponseFormat.Html) { ctx.response.set('Content-Type', 'text/html'); const view: View = { name: 'error', diff --git a/packages/server/src/routes/api/debug.ts b/packages/server/src/routes/api/debug.ts index 3db4b896a8..bcd9038883 100644 --- a/packages/server/src/routes/api/debug.ts +++ b/packages/server/src/routes/api/debug.ts @@ -2,10 +2,11 @@ import config from '../../config'; import { createTestUsers } from '../../tools/debugTools'; import { bodyFields } from '../../utils/requestUtils'; import Router from '../../utils/Router'; +import { RouteType } from '../../utils/types'; import { SubPath } from '../../utils/routeUtils'; import { AppContext } from '../../utils/types'; -const router = new Router(); +const router = new Router(RouteType.Api); router.public = true; diff --git a/packages/server/src/routes/api/events.ts b/packages/server/src/routes/api/events.ts index c4ff170c34..fc1896d2ee 100644 --- a/packages/server/src/routes/api/events.ts +++ b/packages/server/src/routes/api/events.ts @@ -1,6 +1,7 @@ import { ErrorNotFound } from '../../utils/errors'; import { bodyFields } from '../../utils/requestUtils'; import Router from '../../utils/Router'; +import { RouteType } from '../../utils/types'; import { SubPath } from '../../utils/routeUtils'; import { AppContext } from '../../utils/types'; @@ -14,7 +15,7 @@ const supportedEvents: Record = { }, }; -const router = new Router(); +const router = new Router(RouteType.Api); router.post('api/events', async (_path: SubPath, ctx: AppContext) => { const event = await bodyFields(ctx.req); diff --git a/packages/server/src/routes/api/items.ts b/packages/server/src/routes/api/items.ts index aed0b99afb..51f00da29c 100644 --- a/packages/server/src/routes/api/items.ts +++ b/packages/server/src/routes/api/items.ts @@ -2,6 +2,7 @@ import { Item, Uuid } from '../../db'; import { formParse } from '../../utils/requestUtils'; import { respondWithItemContent, SubPath } from '../../utils/routeUtils'; import Router from '../../utils/Router'; +import { RouteType } from '../../utils/types'; import { AppContext } from '../../utils/types'; import * as fs from 'fs-extra'; import { ErrorMethodNotAllowed, ErrorNotFound } from '../../utils/errors'; @@ -10,7 +11,7 @@ import { requestDeltaPagination, requestPagination } from '../../models/utils/pa import { AclAction } from '../../models/BaseModel'; import { safeRemove } from '../../utils/fileUtils'; -const router = new Router(); +const router = new Router(RouteType.Api); // Note about access control: // diff --git a/packages/server/src/routes/api/ping.ts b/packages/server/src/routes/api/ping.ts index f6a7ec2a55..cf6f1714f6 100644 --- a/packages/server/src/routes/api/ping.ts +++ b/packages/server/src/routes/api/ping.ts @@ -1,6 +1,7 @@ import Router from '../../utils/Router'; +import { RouteType } from '../../utils/types'; -const router = new Router(); +const router = new Router(RouteType.Api); router.public = true; diff --git a/packages/server/src/routes/api/sessions.ts b/packages/server/src/routes/api/sessions.ts index c7dce3930f..5e9a6b44ea 100644 --- a/packages/server/src/routes/api/sessions.ts +++ b/packages/server/src/routes/api/sessions.ts @@ -1,11 +1,12 @@ import { SubPath } from '../../utils/routeUtils'; import Router from '../../utils/Router'; +import { RouteType } from '../../utils/types'; import { ErrorForbidden } from '../../utils/errors'; import { AppContext } from '../../utils/types'; import { bodyFields } from '../../utils/requestUtils'; import { User } from '../../db'; -const router = new Router(); +const router = new Router(RouteType.Api); router.public = true; diff --git a/packages/server/src/routes/api/share_users.ts b/packages/server/src/routes/api/share_users.ts index 671c877b12..f3d5a68c2f 100644 --- a/packages/server/src/routes/api/share_users.ts +++ b/packages/server/src/routes/api/share_users.ts @@ -2,10 +2,11 @@ import { ErrorBadRequest, ErrorNotFound } from '../../utils/errors'; import { bodyFields } from '../../utils/requestUtils'; import { SubPath } from '../../utils/routeUtils'; import Router from '../../utils/Router'; +import { RouteType } from '../../utils/types'; import { AppContext } from '../../utils/types'; import { AclAction } from '../../models/BaseModel'; -const router = new Router(); +const router = new Router(RouteType.Api); router.patch('api/share_users/:id', async (path: SubPath, ctx: AppContext) => { const shareUserModel = ctx.models.shareUser(); diff --git a/packages/server/src/routes/api/shares.ts b/packages/server/src/routes/api/shares.ts index 4d0e4af892..7af2415e76 100644 --- a/packages/server/src/routes/api/shares.ts +++ b/packages/server/src/routes/api/shares.ts @@ -3,6 +3,7 @@ import { Share, ShareType } from '../../db'; import { bodyFields, ownerRequired } from '../../utils/requestUtils'; import { SubPath } from '../../utils/routeUtils'; import Router from '../../utils/Router'; +import { RouteType } from '../../utils/types'; import { AppContext } from '../../utils/types'; import { AclAction } from '../../models/BaseModel'; @@ -11,7 +12,7 @@ interface ShareApiInput extends Share { note_id?: string; } -const router = new Router(); +const router = new Router(RouteType.Api); router.public = true; diff --git a/packages/server/src/routes/api/users.ts b/packages/server/src/routes/api/users.ts index 275be75097..b74b5d7f62 100644 --- a/packages/server/src/routes/api/users.ts +++ b/packages/server/src/routes/api/users.ts @@ -2,12 +2,13 @@ import { User } from '../../db'; import { bodyFields } from '../../utils/requestUtils'; import { SubPath } from '../../utils/routeUtils'; import Router from '../../utils/Router'; +import { RouteType } from '../../utils/types'; import { AppContext } from '../../utils/types'; import { ErrorNotFound } from '../../utils/errors'; import { AclAction } from '../../models/BaseModel'; import uuidgen from '../../utils/uuidgen'; -const router = new Router(); +const router = new Router(RouteType.Api); async function fetchUser(path: SubPath, ctx: AppContext): Promise { const user = await ctx.models.user().load(path.id); diff --git a/packages/server/src/routes/default.ts b/packages/server/src/routes/default.ts index 898ba88c34..00295471c3 100644 --- a/packages/server/src/routes/default.ts +++ b/packages/server/src/routes/default.ts @@ -4,7 +4,7 @@ import { ErrorNotFound, ErrorForbidden } from '../utils/errors'; import { dirname, normalize } from 'path'; import { pathExists } from 'fs-extra'; import * as fs from 'fs-extra'; -import { AppContext } from '../utils/types'; +import { AppContext, RouteType } from '../utils/types'; import { localFileFromUrl } from '../utils/joplinUtils'; const { mime } = require('@joplin/lib/mime-utils.js'); @@ -44,7 +44,7 @@ async function findLocalFile(path: string): Promise { return localPath; } -const router = new Router(); +const router = new Router(RouteType.Web); router.public = true; diff --git a/packages/server/src/routes/index/changes.ts b/packages/server/src/routes/index/changes.ts index aea7c6f957..fc77233f6a 100644 --- a/packages/server/src/routes/index/changes.ts +++ b/packages/server/src/routes/index/changes.ts @@ -1,5 +1,6 @@ import { SubPath } from '../../utils/routeUtils'; import Router from '../../utils/Router'; +import { RouteType } from '../../utils/types'; import { AppContext } from '../../utils/types'; import { changeTypeToString } from '../../db'; import { PaginationOrderDir } from '../../models/utils/pagination'; @@ -7,8 +8,9 @@ import { formatDateTime } from '../../utils/time'; import defaultView from '../../utils/defaultView'; import { View } from '../../services/MustacheService'; import { makeTablePagination, Table, Row, makeTableView } from '../../utils/views/table'; +import config, { showItemUrls } from '../../config'; -const router = new Router(); +const router = new Router(RouteType.Web); router.get('changes', async (_path: SubPath, ctx: AppContext) => { const pagination = makeTablePagination(ctx.query, 'updated_time', PaginationOrderDir.DESC); @@ -40,7 +42,7 @@ router.get('changes', async (_path: SubPath, ctx: AppContext) => { { value: change.item_name, stretch: true, - url: items.find(i => i.id === change.item_id) ? ctx.models.item().itemContentUrl(change.item_id) : '', + url: showItemUrls(config()) ? (items.find(i => i.id === change.item_id) ? ctx.models.item().itemContentUrl(change.item_id) : '') : null, }, { value: changeTypeToString(change.type), diff --git a/packages/server/src/routes/index/home.ts b/packages/server/src/routes/index/home.ts index 8e7428c5e1..1628c04d6f 100644 --- a/packages/server/src/routes/index/home.ts +++ b/packages/server/src/routes/index/home.ts @@ -1,11 +1,12 @@ import { SubPath } from '../../utils/routeUtils'; import Router from '../../utils/Router'; +import { RouteType } from '../../utils/types'; import { AppContext } from '../../utils/types'; import { contextSessionId } from '../../utils/requestUtils'; import { ErrorMethodNotAllowed } from '../../utils/errors'; import defaultView from '../../utils/defaultView'; -const router: Router = new Router(); +const router: Router = new Router(RouteType.Web); router.get('home', async (_path: SubPath, ctx: AppContext) => { contextSessionId(ctx); diff --git a/packages/server/src/routes/index/items.ts b/packages/server/src/routes/index/items.ts index 16d2e826e4..3ce2303ba0 100644 --- a/packages/server/src/routes/index/items.ts +++ b/packages/server/src/routes/index/items.ts @@ -1,9 +1,10 @@ import { SubPath, redirect, respondWithItemContent } from '../../utils/routeUtils'; import Router from '../../utils/Router'; +import { RouteType } from '../../utils/types'; import { AppContext } from '../../utils/types'; import { formParse } from '../../utils/requestUtils'; import { ErrorNotFound } from '../../utils/errors'; -import config from '../../config'; +import config, { showItemUrls } from '../../config'; import { formatDateTime } from '../../utils/time'; import defaultView from '../../utils/defaultView'; import { View } from '../../services/MustacheService'; @@ -11,7 +12,7 @@ import { makeTablePagination, makeTableView, Row, Table } from '../../utils/view import { PaginationOrderDir } from '../../models/utils/pagination'; const prettyBytes = require('pretty-bytes'); -const router = new Router(); +const router = new Router(RouteType.Web); router.get('items', async (_path: SubPath, ctx: AppContext) => { const pagination = makeTablePagination(ctx.query, 'name', PaginationOrderDir.ASC); @@ -46,7 +47,7 @@ router.get('items', async (_path: SubPath, ctx: AppContext) => { { value: item.name, stretch: true, - url: `${config().baseUrl}/items/${item.id}/content`, + url: showItemUrls(config()) ? `${config().userContentBaseUrl}/items/${item.id}/content` : null, }, { value: prettyBytes(item.content_size), @@ -75,7 +76,7 @@ router.get('items/:id/content', async (path: SubPath, ctx: AppContext) => { const item = await itemModel.loadWithContent(path.id); if (!item) throw new ErrorNotFound(); return respondWithItemContent(ctx.response, item, item.content); -}); +}, RouteType.UserContent); router.post('items', async (_path: SubPath, ctx: AppContext) => { const body = await formParse(ctx.req); diff --git a/packages/server/src/routes/index/login.ts b/packages/server/src/routes/index/login.ts index 61cb0c398f..cf6346d414 100644 --- a/packages/server/src/routes/index/login.ts +++ b/packages/server/src/routes/index/login.ts @@ -1,5 +1,6 @@ import { SubPath, redirect } from '../../utils/routeUtils'; import Router from '../../utils/Router'; +import { RouteType } from '../../utils/types'; import { AppContext } from '../../utils/types'; import { formParse } from '../../utils/requestUtils'; import config from '../../config'; @@ -13,7 +14,7 @@ function makeView(error: any = null): View { return view; } -const router: Router = new Router(); +const router: Router = new Router(RouteType.Web); router.public = true; diff --git a/packages/server/src/routes/index/logout.ts b/packages/server/src/routes/index/logout.ts index 7a2e9129c9..0fa968dfd5 100644 --- a/packages/server/src/routes/index/logout.ts +++ b/packages/server/src/routes/index/logout.ts @@ -1,10 +1,11 @@ import { SubPath, redirect } from '../../utils/routeUtils'; import Router from '../../utils/Router'; +import { RouteType } from '../../utils/types'; import { AppContext } from '../../utils/types'; import config from '../../config'; import { contextSessionId } from '../../utils/requestUtils'; -const router = new Router(); +const router = new Router(RouteType.Web); router.post('logout', async (_path: SubPath, ctx: AppContext) => { const sessionId = contextSessionId(ctx, false); diff --git a/packages/server/src/routes/index/notifications.ts b/packages/server/src/routes/index/notifications.ts index 064d2a7be0..a87ad53e8e 100644 --- a/packages/server/src/routes/index/notifications.ts +++ b/packages/server/src/routes/index/notifications.ts @@ -1,11 +1,12 @@ import { SubPath } from '../../utils/routeUtils'; import Router from '../../utils/Router'; +import { RouteType } from '../../utils/types'; import { AppContext } from '../../utils/types'; import { bodyFields } from '../../utils/requestUtils'; import { ErrorNotFound } from '../../utils/errors'; import { Notification } from '../../db'; -const router = new Router(); +const router = new Router(RouteType.Web); router.patch('notifications/:id', async (path: SubPath, ctx: AppContext) => { const fields: Notification = await bodyFields(ctx.req); diff --git a/packages/server/src/routes/index/shares.ts b/packages/server/src/routes/index/shares.ts index 064ca54f8e..00e5f33876 100644 --- a/packages/server/src/routes/index/shares.ts +++ b/packages/server/src/routes/index/shares.ts @@ -1,5 +1,6 @@ import { SubPath, ResponseType, Response } from '../../utils/routeUtils'; import Router from '../../utils/Router'; +import { RouteType } from '../../utils/types'; import { AppContext } from '../../utils/types'; import { ErrorNotFound } from '../../utils/errors'; import { Item, Share } from '../../db'; @@ -18,7 +19,7 @@ async function renderItem(context: AppContext, item: Item, share: Share): Promis }; } -const router: Router = new Router(); +const router: Router = new Router(RouteType.Web); router.public = true; diff --git a/packages/server/src/routes/index/users.ts b/packages/server/src/routes/index/users.ts index a78c5dc067..97b9d18c08 100644 --- a/packages/server/src/routes/index/users.ts +++ b/packages/server/src/routes/index/users.ts @@ -1,5 +1,6 @@ import { SubPath, redirect } from '../../utils/routeUtils'; import Router from '../../utils/Router'; +import { RouteType } from '../../utils/types'; import { AppContext, HttpMethod } from '../../utils/types'; import { bodyFields, formParse } from '../../utils/requestUtils'; import { ErrorForbidden, ErrorUnprocessableEntity } from '../../utils/errors'; @@ -52,7 +53,7 @@ function userIsMe(path: SubPath): boolean { return path.id === 'me'; } -const router = new Router(); +const router = new Router(RouteType.Web); router.get('users', async (_path: SubPath, ctx: AppContext) => { const userModel = ctx.models.user(); @@ -152,7 +153,7 @@ router.post('users/:id/confirm', async (path: SubPath, ctx: AppContext) => { return redirect(ctx, `${config().baseUrl}/home`); } catch (error) { const endPoint = router.findEndPoint(HttpMethod.GET, 'users/:id/confirm'); - return endPoint(path, ctx, error); + return endPoint.handler(path, ctx, error); } }); @@ -192,7 +193,7 @@ router.post('users', async (path: SubPath, ctx: AppContext) => { } catch (error) { if (error instanceof ErrorForbidden) throw error; const endPoint = router.findEndPoint(HttpMethod.GET, 'users/:id'); - return endPoint(path, ctx, user, error); + return endPoint.handler(path, ctx, user, error); } }); diff --git a/packages/server/src/utils/Router.ts b/packages/server/src/utils/Router.ts index 3ce24a1049..62beadd01a 100644 --- a/packages/server/src/utils/Router.ts +++ b/packages/server/src/utils/Router.ts @@ -1,7 +1,12 @@ import { ErrorMethodNotAllowed, ErrorNotFound } from './errors'; -import { HttpMethod } from './types'; +import { HttpMethod, RouteType } from './types'; import { RouteResponseFormat, RouteHandler } from './routeUtils'; +interface RouteInfo { + handler: RouteHandler; + type?: RouteType; +} + export default class Router { // When the router is public, we do not check that a valid session is @@ -13,22 +18,29 @@ export default class Router { public responseFormat: RouteResponseFormat = null; - private routes_: Record> = {}; + private routes_: Record> = {}; private aliases_: Record> = {}; + private type_: RouteType; - public findEndPoint(method: HttpMethod, schema: string): RouteHandler { + public constructor(type: RouteType) { + this.type_ = type; + } + + public findEndPoint(method: HttpMethod, schema: string): RouteInfo { if (this.aliases_[method]?.[schema]) { return this.findEndPoint(method, this.aliases_[method]?.[schema]); } if (!this.routes_[method]) { throw new ErrorMethodNotAllowed(`Not allowed: ${method} ${schema}`); } const endPoint = this.routes_[method][schema]; if (!endPoint) { throw new ErrorNotFound(`Not found: ${method} ${schema}`); } - let endPointFn = endPoint; + let endPointInfo = endPoint; for (let i = 0; i < 1000; i++) { - if (typeof endPointFn === 'string') { - endPointFn = this.routes_[method]?.[endPointFn]; + if (typeof endPointInfo === 'string') { + endPointInfo = this.routes_[method]?.[endPointInfo]; } else { - return endPointFn; + const output = { ...endPointInfo }; + if (!output.type) output.type = this.type_; + return output; } } @@ -44,29 +56,29 @@ export default class Router { this.aliases_[method][path] = target; } - public get(path: string, handler: RouteHandler) { + public get(path: string, handler: RouteHandler, type: RouteType = null) { if (!this.routes_.GET) { this.routes_.GET = {}; } - this.routes_.GET[path] = handler; + this.routes_.GET[path] = { handler, type }; } - public post(path: string, handler: RouteHandler) { + public post(path: string, handler: RouteHandler, type: RouteType = null) { if (!this.routes_.POST) { this.routes_.POST = {}; } - this.routes_.POST[path] = handler; + this.routes_.POST[path] = { handler, type }; } - public patch(path: string, handler: RouteHandler) { + public patch(path: string, handler: RouteHandler, type: RouteType = null) { if (!this.routes_.PATCH) { this.routes_.PATCH = {}; } - this.routes_.PATCH[path] = handler; + this.routes_.PATCH[path] = { handler, type }; } - public del(path: string, handler: RouteHandler) { + public del(path: string, handler: RouteHandler, type: RouteType = null) { if (!this.routes_.DELETE) { this.routes_.DELETE = {}; } - this.routes_.DELETE[path] = handler; + this.routes_.DELETE[path] = { handler, type }; } - public put(path: string, handler: RouteHandler) { + public put(path: string, handler: RouteHandler, type: RouteType = null) { if (!this.routes_.PUT) { this.routes_.PUT = {}; } - this.routes_.PUT[path] = handler; + this.routes_.PUT[path] = { handler, type }; } } diff --git a/packages/server/src/utils/errors.ts b/packages/server/src/utils/errors.ts index 050696ee08..23106c7724 100644 --- a/packages/server/src/utils/errors.ts +++ b/packages/server/src/utils/errors.ts @@ -26,8 +26,8 @@ export class ErrorMethodNotAllowed extends ApiError { export class ErrorNotFound extends ApiError { public static httpCode: number = 404; - public constructor(message: string = 'Not Found') { - super(message, ErrorNotFound.httpCode); + public constructor(message: string = 'Not Found', code: string = undefined) { + super(message, ErrorNotFound.httpCode, code); Object.setPrototypeOf(this, ErrorNotFound.prototype); } } diff --git a/packages/server/src/utils/routeUtils.ts b/packages/server/src/utils/routeUtils.ts index 2bbba23f31..d15378f760 100644 --- a/packages/server/src/utils/routeUtils.ts +++ b/packages/server/src/utils/routeUtils.ts @@ -1,3 +1,4 @@ +import { baseUrl } from '../config'; import { Item, ItemAddressingType } from '../db'; import { ErrorBadRequest, ErrorForbidden, ErrorNotFound } from './errors'; import Router from './Router'; @@ -166,14 +167,15 @@ export async function execRequest(routes: Routers, ctx: AppContext) { const match = findMatchingRoute(ctx.path, routes); if (!match) throw new ErrorNotFound(); - const routeHandler = match.route.findEndPoint(ctx.request.method as HttpMethod, match.subPath.schema); + const endPoint = match.route.findEndPoint(ctx.request.method as HttpMethod, match.subPath.schema); + if (ctx.URL.origin !== baseUrl(endPoint.type)) throw new ErrorNotFound('Invalid origin', 'invalidOrigin'); // This is a generic catch-all for all private end points - if we // couldn't get a valid session, we exit now. Individual end points // might have additional permission checks depending on the action. if (!match.route.isPublic(match.subPath.schema) && !ctx.owner) throw new ErrorForbidden(); - return routeHandler(match.subPath, ctx); + return endPoint.handler(match.subPath, ctx); } // In a path such as "/api/files/SOME_ID/content" we want to find: diff --git a/packages/server/src/utils/types.ts b/packages/server/src/utils/types.ts index 0333c8a355..11f05f9978 100644 --- a/packages/server/src/utils/types.ts +++ b/packages/server/src/utils/types.ts @@ -67,6 +67,8 @@ export interface Config { logDir: string; tempDir: string; baseUrl: string; + apiBaseUrl: string; + userContentBaseUrl: string; database: DatabaseConfig; mailer: MailerConfig; } @@ -79,4 +81,10 @@ export enum HttpMethod { HEAD = 'HEAD', } +export enum RouteType { + Web = 1, + Api = 2, + UserContent = 3, +} + export type KoaNext = ()=> Promise; From 71a7fc015a397fff3c813866d9d706b0c9e55170 Mon Sep 17 00:00:00 2001 From: Laurent Cozic Date: Tue, 25 May 2021 17:20:22 +0200 Subject: [PATCH 2/2] Server: Use external directory to store Postgres data in Docker-compose config --- docker-compose.server.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docker-compose.server.yml b/docker-compose.server.yml index 6858eca85e..ef71f723d6 100644 --- a/docker-compose.server.yml +++ b/docker-compose.server.yml @@ -9,6 +9,8 @@ version: '3' services: db: image: postgres:13.1 + volumes: + - ./data/postgres:/var/lib/postgresql/data ports: - "5432:5432" restart: unless-stopped