(core) updates from grist-core

This commit is contained in:
Paul Fitzpatrick
2024-07-01 09:37:47 -04:00
38 changed files with 3218 additions and 66 deletions

View File

@@ -6,6 +6,7 @@ import { GristServer } from 'app/server/lib/GristServer';
import * as express from 'express';
import WS from 'ws';
import fetch from 'node-fetch';
import { DEFAULT_SESSION_SECRET } from 'app/server/lib/coreCreator';
/**
* Self-diagnostics useful when installing Grist.
@@ -61,6 +62,7 @@ export class BootProbes {
this._probes.push(_sandboxingProbe);
this._probes.push(_authenticationProbe);
this._probes.push(_webSocketsProbe);
this._probes.push(_sessionSecretProbe);
this._probeById = new Map(this._probes.map(p => [p.id, p]));
}
}
@@ -284,3 +286,17 @@ const _authenticationProbe: Probe = {
};
},
};
const _sessionSecretProbe: Probe = {
id: 'session-secret',
name: 'Session secret',
apply: async(server, req) => {
const usingDefaultSessionSecret = server.create.sessionSecret() === DEFAULT_SESSION_SECRET;
return {
status: usingDefaultSessionSecret ? 'warning' : 'success',
details: {
"GRIST_SESSION_SECRET": process.env.GRIST_SESSION_SECRET ? "set" : "not set",
}
};
},
};

View File

@@ -1883,6 +1883,22 @@ export class FlexServer implements GristServer {
const probes = new BootProbes(this.app, this, '/api', adminMiddleware);
probes.addEndpoints();
this.app.post('/api/admin/restart', requireInstallAdmin, expressWrap(async (req, resp) => {
const newConfig = req.body.newConfig;
resp.on('finish', () => {
// If we have IPC with parent process (e.g. when running under
// Docker) tell the parent that we have a new environment so it
// can restart us.
if (process.send) {
process.send({ action: 'restart', newConfig });
}
});
// On the topic of http response codes, thus spake MDN:
// "409: This response is sent when a request conflicts with the current state of the server."
const status = process.send ? 200 : 409;
return resp.status(status).send();
}));
// Restrict this endpoint to install admins
this.app.get('/api/install/prefs', requireInstallAdmin, expressWrap(async (_req, resp) => {
const activation = await this._activations.current();

View File

@@ -3,11 +3,14 @@ import { checkMinIOBucket, checkMinIOExternalStorage,
import { makeSimpleCreator } from 'app/server/lib/ICreate';
import { Telemetry } from 'app/server/lib/Telemetry';
export const DEFAULT_SESSION_SECRET =
'Phoo2ag1jaiz6Moo2Iese2xoaphahbai3oNg7diemohlah0ohtae9iengafieS2Hae7quungoCi9iaPh';
export const makeCoreCreator = () => makeSimpleCreator({
deploymentType: 'core',
// This can and should be overridden by GRIST_SESSION_SECRET
// (or generated randomly per install, like grist-omnibus does).
sessionSecret: 'Phoo2ag1jaiz6Moo2Iese2xoaphahbai3oNg7diemohlah0ohtae9iengafieS2Hae7quungoCi9iaPh',
sessionSecret: DEFAULT_SESSION_SECRET,
storage: [
{
name: 'minio',

View File

@@ -6,10 +6,10 @@ import {GristServer} from 'app/server/lib/GristServer';
import {fromCallback} from 'app/server/lib/serverUtils';
import {Sessions} from 'app/server/lib/Sessions';
import {promisifyAll} from 'bluebird';
import * as crypto from 'crypto';
import * as express from 'express';
import assignIn = require('lodash/assignIn');
import * as path from 'path';
import * as shortUUID from "short-uuid";
export const cookieName = process.env.GRIST_SESSION_COOKIE || 'grist_sid';
@@ -118,7 +118,10 @@ export function initGristSessions(instanceRoot: string, server: GristServer) {
// cookie could be stolen (with some effort) by the custom domain's owner, we limit the damage
// by only honoring custom-domain cookies for requests to that domain.
const generateId = (req: RequestWithOrg) => {
const uid = shortUUID.generate();
// Generate 256 bits of cryptographically random data to use as the session ID.
// This ensures security against brute-force session hijacking even without signing the session ID.
const randomNumbers = crypto.getRandomValues(new Uint8Array(32));
const uid = Buffer.from(randomNumbers).toString("hex");
return req.isCustomHost ? `c-${uid}@${req.org}@${req.get('host')}` : `g-${uid}`;
};
const sessionSecret = server.create.sessionSecret();

View File

@@ -21,8 +21,8 @@ export const TEST_HTTPS_OFFSET = process.env.GRIST_TEST_HTTPS_OFFSET ?
// Database fields that we permit in entities but don't want to cross the api.
const INTERNAL_FIELDS = new Set([
'apiKey', 'billingAccountId', 'firstLoginAt', 'filteredOut', 'ownerId', 'gracePeriodStart', 'stripeCustomerId',
'stripeSubscriptionId', 'stripeProductId', 'userId', 'isFirstTimeUser', 'allowGoogleLogin',
'apiKey', 'billingAccountId', 'firstLoginAt', 'lastConnectionAt', 'filteredOut', 'ownerId', 'gracePeriodStart',
'stripeCustomerId', 'stripeSubscriptionId', 'stripeProductId', 'userId', 'isFirstTimeUser', 'allowGoogleLogin',
'authSubject', 'usage', 'createdBy'
]);