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.js
|
||||
packages/lib/models/utils/types.js.map
|
||||
packages/lib/ntpDate.d.ts
|
||||
packages/lib/ntpDate.js
|
||||
packages/lib/ntpDate.js.map
|
||||
packages/lib/ntp.d.ts
|
||||
packages/lib/ntp.js
|
||||
packages/lib/ntp.js.map
|
||||
packages/lib/onedrive-api.d.ts
|
||||
packages/lib/onedrive-api.js
|
||||
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.js
|
||||
packages/lib/models/utils/types.js.map
|
||||
packages/lib/ntpDate.d.ts
|
||||
packages/lib/ntpDate.js
|
||||
packages/lib/ntpDate.js.map
|
||||
packages/lib/ntp.d.ts
|
||||
packages/lib/ntp.js
|
||||
packages/lib/ntp.js.map
|
||||
packages/lib/onedrive-api.d.ts
|
||||
packages/lib/onedrive-api.js
|
||||
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 timers = require('timers');
|
||||
const zlib = require('zlib');
|
||||
const dgram = require('dgram');
|
||||
|
||||
function fileExists(filePath) {
|
||||
try {
|
||||
@ -93,6 +94,10 @@ function shimInit(options = null) {
|
||||
return shim.fsDriver_;
|
||||
};
|
||||
|
||||
shim.dgram = () => {
|
||||
return dgram;
|
||||
};
|
||||
|
||||
if (options.React) {
|
||||
shim.react = () => {
|
||||
return options.React;
|
||||
|
@ -333,6 +333,10 @@ const shim = {
|
||||
return react_;
|
||||
},
|
||||
|
||||
dgram: () => {
|
||||
throw new Error('Not implemented');
|
||||
},
|
||||
|
||||
platformSupportsKeyChain: () => {
|
||||
// keytar throws an error when system keychain is not present; even
|
||||
// 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!!');
|
||||
|
||||
var client = exports.dgram.createSocket("udp4"),
|
||||
ntpData = new Buffer(48);
|
||||
var client = exports.dgram.createSocket("udp4");
|
||||
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
|
||||
// -> 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 { AppContext, Env, KoaNext } from './utils/types';
|
||||
import FsDriverNode from '@joplin/lib/fs-driver-node';
|
||||
import { getDeviceTimeDrift } from '@joplin/lib/ntp';
|
||||
import routeHandler from './middleware/routeHandler';
|
||||
import notificationHandler from './middleware/notificationHandler';
|
||||
import ownerHandler from './middleware/ownerHandler';
|
||||
@ -249,6 +250,13 @@ async function main() {
|
||||
runCommandAndExitApp = false;
|
||||
|
||||
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('Public base URL:', config().baseUrl);
|
||||
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),
|
||||
storageDriverFallback: parseStorageDriverConnectionString(env.STORAGE_DRIVER_FALLBACK),
|
||||
itemSizeHardLimit: 250000000, // Beyond this the Postgres driver will crash the app
|
||||
maxTimeDrift: env.MAX_TIME_DRIFT,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ const defaultEnvValues: EnvVariables = {
|
||||
ERROR_STACK_TRACES: false,
|
||||
COOKIES_SECURE: false,
|
||||
RUNNING_IN_DOCKER: false,
|
||||
MAX_TIME_DRIFT: 10,
|
||||
|
||||
// ==================================================
|
||||
// URL config
|
||||
@ -85,6 +86,7 @@ export interface EnvVariables {
|
||||
ERROR_STACK_TRACES: boolean;
|
||||
COOKIES_SECURE: boolean;
|
||||
RUNNING_IN_DOCKER: boolean;
|
||||
MAX_TIME_DRIFT: number;
|
||||
|
||||
APP_BASE_URL: string;
|
||||
USER_CONTENT_BASE_URL: string;
|
||||
|
@ -160,6 +160,7 @@ export interface Config {
|
||||
storageDriver: StorageDriverConfig;
|
||||
storageDriverFallback: StorageDriverConfig;
|
||||
itemSizeHardLimit: number;
|
||||
maxTimeDrift: number;
|
||||
}
|
||||
|
||||
export enum HttpMethod {
|
||||
|
Loading…
Reference in New Issue
Block a user