mirror of
https://github.com/laurent22/joplin.git
synced 2024-11-24 08:12:24 +02:00
All, Server: Add support for X-API-MIN-VERSION header
This commit is contained in:
parent
752d118e5d
commit
51f3c0016e
@ -142,6 +142,7 @@ export default class JoplinServerApi {
|
||||
}
|
||||
|
||||
if (sessionId) headers['X-API-AUTH'] = sessionId;
|
||||
headers['X-API-MIN-VERSION'] = '2.1.4';
|
||||
|
||||
const fetchOptions: any = {};
|
||||
fetchOptions.headers = headers;
|
||||
|
5
packages/server/package-lock.json
generated
5
packages/server/package-lock.json
generated
@ -2436,6 +2436,11 @@
|
||||
"integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
|
||||
"dev": true
|
||||
},
|
||||
"compare-versions": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-3.6.0.tgz",
|
||||
"integrity": "sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA=="
|
||||
},
|
||||
"component-emitter": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz",
|
||||
|
@ -24,6 +24,7 @@
|
||||
"bcryptjs": "^2.4.3",
|
||||
"bulma": "^0.9.1",
|
||||
"bulma-prefers-dark": "^0.1.0-beta.0",
|
||||
"compare-versions": "^3.6.0",
|
||||
"dayjs": "^1.9.8",
|
||||
"formidable": "^1.2.2",
|
||||
"fs-extra": "^8.1.0",
|
||||
|
@ -8,7 +8,7 @@ import Logger, { LoggerWrapper, TargetType } from '@joplin/lib/Logger';
|
||||
import config, { initConfig, runningInDocker, EnvVariables } from './config';
|
||||
import { createDb, dropDb } from './tools/dbTools';
|
||||
import { dropTables, connectDb, disconnectDb, migrateDb, waitForConnection, sqliteDefaultDir } from './db';
|
||||
import { AppContext, Env } from './utils/types';
|
||||
import { AppContext, Env, KoaNext } from './utils/types';
|
||||
import FsDriverNode from '@joplin/lib/fs-driver-node';
|
||||
import routeHandler from './middleware/routeHandler';
|
||||
import notificationHandler from './middleware/notificationHandler';
|
||||
@ -17,6 +17,7 @@ import setupAppContext from './utils/setupAppContext';
|
||||
import { initializeJoplinUtils } from './utils/joplinUtils';
|
||||
import startServices from './utils/startServices';
|
||||
import { credentialFile } from './utils/testing/testUtils';
|
||||
import apiVersionHandler from './middleware/apiVersionHandler';
|
||||
|
||||
const cors = require('@koa/cors');
|
||||
const nodeEnvFile = require('node-env-file');
|
||||
@ -119,6 +120,18 @@ async function main() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// This is used to catch any low level error thrown from a middleware. It
|
||||
// won't deal with errors from routeHandler, which catches and handles its
|
||||
// own errors.
|
||||
app.use(async (ctx: AppContext, next: KoaNext) => {
|
||||
try {
|
||||
await next();
|
||||
} catch (error) {
|
||||
ctx.status = error.httpCode || 500;
|
||||
ctx.body = JSON.stringify({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
app.use(cors({
|
||||
// https://github.com/koajs/cors/issues/52#issuecomment-413887382
|
||||
origin: (ctx: AppContext) => {
|
||||
@ -132,6 +145,7 @@ async function main() {
|
||||
}
|
||||
},
|
||||
}));
|
||||
app.use(apiVersionHandler);
|
||||
app.use(ownerHandler);
|
||||
app.use(notificationHandler);
|
||||
app.use(routeHandler);
|
||||
|
57
packages/server/src/middleware/apiVersionHandler.test.ts
Normal file
57
packages/server/src/middleware/apiVersionHandler.test.ts
Normal file
@ -0,0 +1,57 @@
|
||||
import config from '../config';
|
||||
import { ErrorPreconditionFailed } from '../utils/errors';
|
||||
import { beforeAllDb, afterAllTests, beforeEachDb, koaAppContext, koaNext, expectNotThrow, expectHttpError } from '../utils/testing/testUtils';
|
||||
import apiVersionHandler from './apiVersionHandler';
|
||||
|
||||
describe('apiVersionHandler', function() {
|
||||
|
||||
beforeAll(async () => {
|
||||
await beforeAllDb('apiVersionHandler');
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await afterAllTests();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await beforeEachDb();
|
||||
});
|
||||
|
||||
test('should work if no version header is provided', async function() {
|
||||
const context = await koaAppContext({});
|
||||
await expectNotThrow(async () => apiVersionHandler(context, koaNext));
|
||||
});
|
||||
|
||||
test('should work if the header version number is lower than the server version', async function() {
|
||||
config().appVersion = '2.1.0';
|
||||
|
||||
const context = await koaAppContext({
|
||||
request: {
|
||||
method: 'GET',
|
||||
url: '/api/ping',
|
||||
headers: {
|
||||
'x-api-min-version': '2.0.0',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await expectNotThrow(async () => apiVersionHandler(context, koaNext));
|
||||
});
|
||||
|
||||
test('should not work if the header version number is greater than the server version', async function() {
|
||||
config().appVersion = '2.1.0';
|
||||
|
||||
const context = await koaAppContext({
|
||||
request: {
|
||||
method: 'GET',
|
||||
url: '/api/ping',
|
||||
headers: {
|
||||
'x-api-min-version': '2.2.0',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await expectHttpError(async () => apiVersionHandler(context, koaNext), ErrorPreconditionFailed.httpCode);
|
||||
});
|
||||
|
||||
});
|
27
packages/server/src/middleware/apiVersionHandler.ts
Normal file
27
packages/server/src/middleware/apiVersionHandler.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { AppContext, KoaNext } from '../utils/types';
|
||||
import { isApiRequest } from '../utils/requestUtils';
|
||||
import config from '../config';
|
||||
import { ErrorPreconditionFailed } from '../utils/errors';
|
||||
const compareVersions = require('compare-versions');
|
||||
|
||||
export default async function(ctx: AppContext, next: KoaNext): Promise<void> {
|
||||
if (!isApiRequest(ctx)) return;
|
||||
|
||||
const appVersion = config().appVersion;
|
||||
const minVersion = ctx.headers['x-api-min-version'];
|
||||
|
||||
// For now we don't require this header to be set to keep compatibility with
|
||||
// older clients.
|
||||
if (!minVersion) return next();
|
||||
|
||||
const diff = compareVersions(appVersion, minVersion);
|
||||
|
||||
// We only throw an error if the client requires a version of Joplin Server
|
||||
// that's ahead of what's installed. This is mostly to automatically notify
|
||||
// those who self-host so that they know they need to upgrade Joplin Server.
|
||||
if (diff < 0) {
|
||||
throw new ErrorPreconditionFailed(`Joplin Server v${minVersion} is required but v${appVersion} is installed. Please upgrade Joplin Server.`);
|
||||
}
|
||||
|
||||
return next();
|
||||
}
|
@ -51,6 +51,16 @@ export class ErrorBadRequest extends ApiError {
|
||||
|
||||
}
|
||||
|
||||
export class ErrorPreconditionFailed extends ApiError {
|
||||
public static httpCode: number = 412;
|
||||
|
||||
public constructor(message: string = 'Precondition Failed') {
|
||||
super(message, ErrorPreconditionFailed.httpCode);
|
||||
Object.setPrototypeOf(this, ErrorPreconditionFailed.prototype);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class ErrorUnprocessableEntity extends ApiError {
|
||||
public static httpCode: number = 422;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user