mirror of
https://github.com/laurent22/joplin.git
synced 2024-12-21 09:38:01 +02:00
Server: Resolves #5738: Check for time drift when the server starts
This commit is contained in:
parent
483b2e769f
commit
0bf323dc63
@ -1176,9 +1176,9 @@ packages/lib/models/utils/paginationToSql.js.map
|
|||||||
packages/lib/models/utils/types.d.ts
|
packages/lib/models/utils/types.d.ts
|
||||||
packages/lib/models/utils/types.js
|
packages/lib/models/utils/types.js
|
||||||
packages/lib/models/utils/types.js.map
|
packages/lib/models/utils/types.js.map
|
||||||
packages/lib/ntpDate.d.ts
|
packages/lib/ntp.d.ts
|
||||||
packages/lib/ntpDate.js
|
packages/lib/ntp.js
|
||||||
packages/lib/ntpDate.js.map
|
packages/lib/ntp.js.map
|
||||||
packages/lib/onedrive-api.d.ts
|
packages/lib/onedrive-api.d.ts
|
||||||
packages/lib/onedrive-api.js
|
packages/lib/onedrive-api.js
|
||||||
packages/lib/onedrive-api.js.map
|
packages/lib/onedrive-api.js.map
|
||||||
|
6
.gitignore
vendored
6
.gitignore
vendored
@ -1159,9 +1159,9 @@ packages/lib/models/utils/paginationToSql.js.map
|
|||||||
packages/lib/models/utils/types.d.ts
|
packages/lib/models/utils/types.d.ts
|
||||||
packages/lib/models/utils/types.js
|
packages/lib/models/utils/types.js
|
||||||
packages/lib/models/utils/types.js.map
|
packages/lib/models/utils/types.js.map
|
||||||
packages/lib/ntpDate.d.ts
|
packages/lib/ntp.d.ts
|
||||||
packages/lib/ntpDate.js
|
packages/lib/ntp.js
|
||||||
packages/lib/ntpDate.js.map
|
packages/lib/ntp.js.map
|
||||||
packages/lib/onedrive-api.d.ts
|
packages/lib/onedrive-api.d.ts
|
||||||
packages/lib/onedrive-api.js
|
packages/lib/onedrive-api.js
|
||||||
packages/lib/onedrive-api.js.map
|
packages/lib/onedrive-api.js.map
|
||||||
|
61
packages/lib/ntp.ts
Normal file
61
packages/lib/ntp.ts
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import shim from './shim';
|
||||||
|
const ntpClient_ = require('./vendor/ntp-client');
|
||||||
|
|
||||||
|
const server = {
|
||||||
|
domain: 'pool.ntp.org',
|
||||||
|
port: 123,
|
||||||
|
};
|
||||||
|
|
||||||
|
function ntpClient() {
|
||||||
|
ntpClient_.dgram = shim.dgram();
|
||||||
|
return ntpClient_;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getNetworkTime(): Promise<Date> {
|
||||||
|
return new Promise(function(resolve: Function, reject: Function) {
|
||||||
|
ntpClient().getNetworkTime(server.domain, server.port, function(error: any, date: Date) {
|
||||||
|
if (error) {
|
||||||
|
reject(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(date);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getDeviceTimeDrift(): Promise<number> {
|
||||||
|
let ntpTime: Date = null;
|
||||||
|
try {
|
||||||
|
ntpTime = await getNetworkTime();
|
||||||
|
} catch (error) {
|
||||||
|
error.message = `Cannot retrieve the network time: ${error.message}`;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ntpTime.getTime() - Date.now();
|
||||||
|
}
|
||||||
|
|
||||||
|
// export default async function(): Promise<Date> {
|
||||||
|
// if (shouldSyncTime()) {
|
||||||
|
// const release = await fetchingTimeMutex.acquire();
|
||||||
|
|
||||||
|
// try {
|
||||||
|
// if (shouldSyncTime()) {
|
||||||
|
// const date = await networkTime();
|
||||||
|
// nextSyncTime = Date.now() + 60 * 1000;
|
||||||
|
// timeOffset = date.getTime() - Date.now();
|
||||||
|
// }
|
||||||
|
// } catch (error) {
|
||||||
|
// logger.warn('Could not get NTP time - falling back to device time:', error);
|
||||||
|
// // Fallback to device time since
|
||||||
|
// // most of the time it's actually correct
|
||||||
|
// nextSyncTime = Date.now() + 20 * 1000;
|
||||||
|
// timeOffset = 0;
|
||||||
|
// } finally {
|
||||||
|
// release();
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return new Date(Date.now() + timeOffset);
|
||||||
|
// }
|
@ -1,59 +0,0 @@
|
|||||||
const ntpClient = require('./vendor/ntp-client');
|
|
||||||
import Logger from './Logger';
|
|
||||||
const Mutex = require('async-mutex').Mutex;
|
|
||||||
|
|
||||||
let nextSyncTime = 0;
|
|
||||||
let timeOffset = 0;
|
|
||||||
let logger = new Logger();
|
|
||||||
|
|
||||||
const fetchingTimeMutex = new Mutex();
|
|
||||||
|
|
||||||
const server = {
|
|
||||||
domain: 'pool.ntp.org',
|
|
||||||
port: 123,
|
|
||||||
};
|
|
||||||
|
|
||||||
async function networkTime(): Promise<Date> {
|
|
||||||
return new Promise(function(resolve: Function, reject: Function) {
|
|
||||||
ntpClient.getNetworkTime(server.domain, server.port, function(error: any, date: Date) {
|
|
||||||
if (error) {
|
|
||||||
reject(error);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
resolve(date);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function shouldSyncTime() {
|
|
||||||
return !nextSyncTime || Date.now() > nextSyncTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function setLogger(v: any) {
|
|
||||||
logger = v;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default async function(): Promise<Date> {
|
|
||||||
if (shouldSyncTime()) {
|
|
||||||
const release = await fetchingTimeMutex.acquire();
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (shouldSyncTime()) {
|
|
||||||
const date = await networkTime();
|
|
||||||
nextSyncTime = Date.now() + 60 * 1000;
|
|
||||||
timeOffset = date.getTime() - Date.now();
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
logger.warn('Could not get NTP time - falling back to device time:', error);
|
|
||||||
// Fallback to device time since
|
|
||||||
// most of the time it's actually correct
|
|
||||||
nextSyncTime = Date.now() + 20 * 1000;
|
|
||||||
timeOffset = 0;
|
|
||||||
} finally {
|
|
||||||
release();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Date(Date.now() + timeOffset);
|
|
||||||
}
|
|
@ -16,6 +16,7 @@ const https = require('https');
|
|||||||
const toRelative = require('relative');
|
const toRelative = require('relative');
|
||||||
const timers = require('timers');
|
const timers = require('timers');
|
||||||
const zlib = require('zlib');
|
const zlib = require('zlib');
|
||||||
|
const dgram = require('dgram');
|
||||||
|
|
||||||
function fileExists(filePath) {
|
function fileExists(filePath) {
|
||||||
try {
|
try {
|
||||||
@ -93,6 +94,10 @@ function shimInit(options = null) {
|
|||||||
return shim.fsDriver_;
|
return shim.fsDriver_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
shim.dgram = () => {
|
||||||
|
return dgram;
|
||||||
|
};
|
||||||
|
|
||||||
if (options.React) {
|
if (options.React) {
|
||||||
shim.react = () => {
|
shim.react = () => {
|
||||||
return options.React;
|
return options.React;
|
||||||
|
@ -333,6 +333,10 @@ const shim = {
|
|||||||
return react_;
|
return react_;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
dgram: () => {
|
||||||
|
throw new Error('Not implemented');
|
||||||
|
},
|
||||||
|
|
||||||
platformSupportsKeyChain: () => {
|
platformSupportsKeyChain: () => {
|
||||||
// keytar throws an error when system keychain is not present; even
|
// keytar throws an error when system keychain is not present; even
|
||||||
// when keytar itself is installed. try/catch to ensure system
|
// when keytar itself is installed. try/catch to ensure system
|
||||||
|
4
packages/lib/vendor/ntp-client.js
vendored
4
packages/lib/vendor/ntp-client.js
vendored
@ -44,8 +44,8 @@ const Buffer = require('buffer').Buffer;
|
|||||||
|
|
||||||
if (!exports.dgram) throw new Error('dgram package has not been set!!');
|
if (!exports.dgram) throw new Error('dgram package has not been set!!');
|
||||||
|
|
||||||
var client = exports.dgram.createSocket("udp4"),
|
var client = exports.dgram.createSocket("udp4");
|
||||||
ntpData = new Buffer(48);
|
var ntpData = Buffer.alloc(48); // new Buffer(48);
|
||||||
|
|
||||||
// RFC 2030 -> LI = 0 (no warning, 2 bits), VN = 3 (IPv4 only, 3 bits), Mode = 3 (Client Mode, 3 bits) -> 1 byte
|
// RFC 2030 -> LI = 0 (no warning, 2 bits), VN = 3 (IPv4 only, 3 bits), Mode = 3 (Client Mode, 3 bits) -> 1 byte
|
||||||
// -> rtol(LI, 6) ^ rotl(VN, 3) ^ rotl(Mode, 0)
|
// -> rtol(LI, 6) ^ rotl(VN, 3) ^ rotl(Mode, 0)
|
||||||
|
@ -8,6 +8,7 @@ import config, { initConfig, runningInDocker } from './config';
|
|||||||
import { migrateLatest, waitForConnection, sqliteDefaultDir, latestMigration } from './db';
|
import { migrateLatest, waitForConnection, sqliteDefaultDir, latestMigration } from './db';
|
||||||
import { AppContext, Env, KoaNext } from './utils/types';
|
import { AppContext, Env, KoaNext } from './utils/types';
|
||||||
import FsDriverNode from '@joplin/lib/fs-driver-node';
|
import FsDriverNode from '@joplin/lib/fs-driver-node';
|
||||||
|
import { getDeviceTimeDrift } from '@joplin/lib/ntp';
|
||||||
import routeHandler from './middleware/routeHandler';
|
import routeHandler from './middleware/routeHandler';
|
||||||
import notificationHandler from './middleware/notificationHandler';
|
import notificationHandler from './middleware/notificationHandler';
|
||||||
import ownerHandler from './middleware/ownerHandler';
|
import ownerHandler from './middleware/ownerHandler';
|
||||||
@ -249,6 +250,13 @@ async function main() {
|
|||||||
runCommandAndExitApp = false;
|
runCommandAndExitApp = false;
|
||||||
|
|
||||||
appLogger().info(`Starting server v${config().appVersion} (${env}) on port ${config().port} and PID ${process.pid}...`);
|
appLogger().info(`Starting server v${config().appVersion} (${env}) on port ${config().port} and PID ${process.pid}...`);
|
||||||
|
|
||||||
|
const timeDrift = await getDeviceTimeDrift();
|
||||||
|
if (Math.abs(timeDrift) > config().maxTimeDrift) {
|
||||||
|
throw new Error(`The device time drift is ${timeDrift}ms (Max allowed: ${config().maxTimeDrift}ms) - cannot continue as it could cause data loss and conflicts on the sync clients. You may increase env var MAX_TIME_DRIFT to pass the check.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
appLogger().info(`NTP time offset: ${timeDrift}ms`);
|
||||||
appLogger().info('Running in Docker:', runningInDocker());
|
appLogger().info('Running in Docker:', runningInDocker());
|
||||||
appLogger().info('Public base URL:', config().baseUrl);
|
appLogger().info('Public base URL:', config().baseUrl);
|
||||||
appLogger().info('API base URL:', config().apiBaseUrl);
|
appLogger().info('API base URL:', config().apiBaseUrl);
|
||||||
|
@ -134,6 +134,7 @@ export async function initConfig(envType: Env, env: EnvVariables, overrides: any
|
|||||||
storageDriver: parseStorageDriverConnectionString(env.STORAGE_DRIVER),
|
storageDriver: parseStorageDriverConnectionString(env.STORAGE_DRIVER),
|
||||||
storageDriverFallback: parseStorageDriverConnectionString(env.STORAGE_DRIVER_FALLBACK),
|
storageDriverFallback: parseStorageDriverConnectionString(env.STORAGE_DRIVER_FALLBACK),
|
||||||
itemSizeHardLimit: 250000000, // Beyond this the Postgres driver will crash the app
|
itemSizeHardLimit: 250000000, // Beyond this the Postgres driver will crash the app
|
||||||
|
maxTimeDrift: env.MAX_TIME_DRIFT,
|
||||||
...overrides,
|
...overrides,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@ const defaultEnvValues: EnvVariables = {
|
|||||||
ERROR_STACK_TRACES: false,
|
ERROR_STACK_TRACES: false,
|
||||||
COOKIES_SECURE: false,
|
COOKIES_SECURE: false,
|
||||||
RUNNING_IN_DOCKER: false,
|
RUNNING_IN_DOCKER: false,
|
||||||
|
MAX_TIME_DRIFT: 10,
|
||||||
|
|
||||||
// ==================================================
|
// ==================================================
|
||||||
// URL config
|
// URL config
|
||||||
@ -85,6 +86,7 @@ export interface EnvVariables {
|
|||||||
ERROR_STACK_TRACES: boolean;
|
ERROR_STACK_TRACES: boolean;
|
||||||
COOKIES_SECURE: boolean;
|
COOKIES_SECURE: boolean;
|
||||||
RUNNING_IN_DOCKER: boolean;
|
RUNNING_IN_DOCKER: boolean;
|
||||||
|
MAX_TIME_DRIFT: number;
|
||||||
|
|
||||||
APP_BASE_URL: string;
|
APP_BASE_URL: string;
|
||||||
USER_CONTENT_BASE_URL: string;
|
USER_CONTENT_BASE_URL: string;
|
||||||
|
@ -160,6 +160,7 @@ export interface Config {
|
|||||||
storageDriver: StorageDriverConfig;
|
storageDriver: StorageDriverConfig;
|
||||||
storageDriverFallback: StorageDriverConfig;
|
storageDriverFallback: StorageDriverConfig;
|
||||||
itemSizeHardLimit: number;
|
itemSizeHardLimit: number;
|
||||||
|
maxTimeDrift: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum HttpMethod {
|
export enum HttpMethod {
|
||||||
|
Loading…
Reference in New Issue
Block a user