diff --git a/README.md b/README.md index 5f92ea22..0d481acd 100644 --- a/README.md +++ b/README.md @@ -188,6 +188,7 @@ GRIST_MANAGED_WORKERS | if set, Grist can assume that if a url targeted at a doc GRIST_MAX_UPLOAD_ATTACHMENT_MB | max allowed size for attachments (0 or empty for unlimited). GRIST_MAX_UPLOAD_IMPORT_MB | max allowed size for imports (except .grist files) (0 or empty for unlimited). GRIST_ORG_IN_PATH | if true, encode org in path rather than domain +GRIST_PROXY_AUTH_HEADER | header which will be set by a (reverse) proxy webserver with an authorized users' email. This can be used as an alternative to a SAML service. GRIST_ROUTER_URL | optional url for an api that allows servers to be (un)registered with a load balancer GRIST_SERVE_SAME_ORIGIN | set to "true" to access home server and doc workers on the same protocol-host-port as the top-level page, same as for custom domains (careful, host header should be trustworthy) GRIST_SESSION_COOKIE | if set, overrides the name of Grist's cookie diff --git a/app/server/lib/Authorizer.ts b/app/server/lib/Authorizer.ts index f62eb874..63f1a906 100644 --- a/app/server/lib/Authorizer.ts +++ b/app/server/lib/Authorizer.ts @@ -89,6 +89,34 @@ export function isSingleUserMode(): boolean { return process.env.GRIST_SINGLE_USER === '1'; } + +/** + * Returns a profile if it can be deduced from the request. This requires a + * header to specify the users' email address. The header to set comes from the + * environment variable GRIST_PROXY_AUTH_HEADER. + */ +export function getRequestProfile(req: Request): UserProfile|undefined { + const header = process.env.GRIST_PROXY_AUTH_HEADER; + let profile: UserProfile|undefined; + + if (header && req.headers && req.headers[header]) { + const headerContent = req.headers[header]; + if (headerContent) { + const userEmail = headerContent.toString(); + const [userName] = userEmail.split("@", 1); + if (userEmail && userName) { + profile = { + "email": userEmail, + "name": userName + }; + } + } + } + + return profile; +} + + /** * Returns the express request object with user information added, if it can be * found based on passed in headers or the session. Specifically, sets: @@ -245,6 +273,20 @@ export async function addRequestUser(dbManager: HomeDBManager, permitStore: IPer } } + if (!mreq.userId) { + profile = getRequestProfile(mreq); + if (profile) { + const user = await dbManager.getUserByLoginWithRetry(profile.email, profile); + if(user) { + mreq.user = user; + mreq.users = [profile]; + mreq.userId = user.id; + mreq.userIsAuthorized = true; + } + } + } + + // If no userId has been found yet, fall back on anonymous. if (!mreq.userId) { const anon = dbManager.getAnonymousUser(); diff --git a/app/server/lib/Comm.js b/app/server/lib/Comm.js index b841ad9d..6e7eaca4 100644 --- a/app/server/lib/Comm.js +++ b/app/server/lib/Comm.js @@ -50,6 +50,7 @@ const {parseFirstUrlPart} = require('app/common/gristUrls'); const version = require('app/common/version'); const {Client} = require('./Client'); const {localeFromRequest} = require('app/server/lib/ServerLocale'); +const {getRequestProfile} = require('app/server/lib/Authorizer'); // Bluebird promisification, to be able to use e.g. websocket.sendAsync method. Promise.promisifyAll(ws.prototype); @@ -150,6 +151,20 @@ Comm.prototype._broadcastMessage = function(type, data, clients) { clients.forEach(client => client.sendMessage({type, data})); }; + +/** + * Returns a profile based on the request or session. + */ +Comm.prototype._getSessionProfile = function(scopedSession, req) { + const profile = getRequestProfile(req); + if (profile) { + return Promise.resolve(profile); + } else { + return scopedSession.getSessionProfile(); + } +}; + + /** * Sends a per-doc message to the given client. * @param {Object} client - The client object, as passed to all per-doc methods. @@ -236,7 +251,7 @@ Comm.prototype._onWebSocketConnection = async function(websocket, req) { // Delegate message handling to the client websocket.on('message', client.onMessage.bind(client)); - scopedSession.getSessionProfile() + this._getSessionProfile(scopedSession, req) .then((profile) => { log.debug(`Comm ${client}: sending clientConnect with ` + `${client._missedMessages.length} missed messages`); diff --git a/buildtools/prepare_python.sh b/buildtools/prepare_python.sh index 05b793d2..b3be736e 100755 --- a/buildtools/prepare_python.sh +++ b/buildtools/prepare_python.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -e diff --git a/buildtools/prepare_python2.sh b/buildtools/prepare_python2.sh index 97e6f7a8..59e3acfd 100755 --- a/buildtools/prepare_python2.sh +++ b/buildtools/prepare_python2.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -e diff --git a/buildtools/prepare_python3.sh b/buildtools/prepare_python3.sh index 8df3a3a9..a6a2c1c8 100755 --- a/buildtools/prepare_python3.sh +++ b/buildtools/prepare_python3.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -e