mirror of
https://github.com/gristlabs/grist-core.git
synced 2026-03-02 04:09:24 +00:00
(core) Adding GristConnect login system
Summary: New login system to allow simple SSO flow that is based on Discourse description that is available at: https://meta.discourse.org/t/discourseconnect-official-single-sign-on-for-discourse-sso/13045 Test Plan: New core test. Reviewers: paulfitz Reviewed By: paulfitz Differential Revision: https://phab.getgrist.com/D3418
This commit is contained in:
@@ -230,8 +230,10 @@ export async function addRequestUser(dbManager: HomeDBManager, permitStore: IPer
|
||||
|
||||
// If we haven't set a maxAge yet, set it now.
|
||||
if (session && session.cookie && !session.cookie.maxAge) {
|
||||
session.cookie.maxAge = COOKIE_MAX_AGE;
|
||||
forceSessionChange(session);
|
||||
if (COOKIE_MAX_AGE !== null) {
|
||||
session.cookie.maxAge = COOKIE_MAX_AGE;
|
||||
forceSessionChange(session);
|
||||
}
|
||||
}
|
||||
|
||||
// See if we have a profile linked with the active organization already.
|
||||
|
||||
@@ -31,7 +31,7 @@ const DISCOURSE_SITE = process.env.DISCOURSE_SITE;
|
||||
export const Deps = {DISCOURSE_CONNECT_SECRET, DISCOURSE_SITE};
|
||||
|
||||
// Calculate payload signature using the given secret.
|
||||
function calcSignature(payload: string, secret: string) {
|
||||
export function calcSignature(payload: string, secret: string) {
|
||||
return crypto.createHmac('sha256', secret).update(payload).digest('hex');
|
||||
}
|
||||
|
||||
|
||||
@@ -89,6 +89,8 @@ export interface FlexServerOptions {
|
||||
pluginUrl?: string;
|
||||
}
|
||||
|
||||
const noop: express.RequestHandler = (req, res, next) => next();
|
||||
|
||||
export class FlexServer implements GristServer {
|
||||
public readonly create = create;
|
||||
public tagChecker: TagChecker;
|
||||
@@ -508,17 +510,14 @@ export class FlexServer implements GristServer {
|
||||
this._getSignUpRedirectUrl);
|
||||
this._redirectToOrgMiddleware = tbind(this._redirectToOrg, this);
|
||||
} else {
|
||||
const noop: express.RequestHandler = (req, res, next) => next();
|
||||
this._userIdMiddleware = noop;
|
||||
this._trustOriginsMiddleware = noop;
|
||||
this._docPermissionsMiddleware = (req, res, next) => {
|
||||
// For standalone single-user Grist, documents are stored on-disk
|
||||
// with their filename equal to the document title, no document
|
||||
// aliases are possible, and there is no access control.
|
||||
// The _docPermissionsMiddleware is a no-op.
|
||||
// TODO We might no longer have any tests for isSingleUserMode, or modes of operation.
|
||||
next();
|
||||
};
|
||||
// For standalone single-user Grist, documents are stored on-disk
|
||||
// with their filename equal to the document title, no document
|
||||
// aliases are possible, and there is no access control.
|
||||
// The _docPermissionsMiddleware is a no-op.
|
||||
// TODO We might no longer have any tests for isSingleUserMode, or modes of operation.
|
||||
this._docPermissionsMiddleware = noop;
|
||||
this._redirectToLoginWithExceptionsMiddleware = noop;
|
||||
this._redirectToLoginWithoutExceptionsMiddleware = noop;
|
||||
this._redirectToLoginUnconditionally = null; // there is no way to log in.
|
||||
@@ -722,6 +721,9 @@ export class FlexServer implements GristServer {
|
||||
baseDomain: this._defaultBaseDomain,
|
||||
});
|
||||
|
||||
const forcedLoginMiddleware = process.env.GRIST_FORCE_LOGIN === 'true' ?
|
||||
this._redirectToLoginWithoutExceptionsMiddleware : noop;
|
||||
|
||||
const welcomeNewUser: express.RequestHandler = isSingleUserMode() ?
|
||||
(req, res, next) => next() :
|
||||
expressWrap(async (req, res, next) => {
|
||||
@@ -781,6 +783,7 @@ export class FlexServer implements GristServer {
|
||||
middleware: [
|
||||
this._redirectToHostMiddleware,
|
||||
this._userIdMiddleware,
|
||||
forcedLoginMiddleware,
|
||||
this._redirectToLoginWithExceptionsMiddleware,
|
||||
this._redirectToOrgMiddleware,
|
||||
welcomeNewUser
|
||||
@@ -789,6 +792,7 @@ export class FlexServer implements GristServer {
|
||||
// Same as middleware, except without login redirect middleware.
|
||||
this._redirectToHostMiddleware,
|
||||
this._userIdMiddleware,
|
||||
forcedLoginMiddleware,
|
||||
this._redirectToOrgMiddleware,
|
||||
welcomeNewUser
|
||||
],
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { UserProfile } from 'app/common/UserAPI';
|
||||
import { GristLoginSystem, GristServer, setUserInSession } from 'app/server/lib/GristServer';
|
||||
import { Request } from 'express';
|
||||
import {UserProfile} from 'app/common/UserAPI';
|
||||
import {GristLoginSystem, GristServer, setUserInSession} from 'app/server/lib/GristServer';
|
||||
import {Request} from 'express';
|
||||
|
||||
/**
|
||||
* Return a login system that supports a single hard-coded user.
|
||||
@@ -10,10 +10,10 @@ export async function getMinimalLoginSystem(): Promise<GristLoginSystem> {
|
||||
// no nuance here.
|
||||
return {
|
||||
async getMiddleware(gristServer: GristServer) {
|
||||
async function getLoginRedirectUrl(req: Request, url: URL) {
|
||||
async function getLoginRedirectUrl(req: Request, url: URL) {
|
||||
await setUserInSession(req, gristServer, getDefaultProfile());
|
||||
return url.href;
|
||||
}
|
||||
}
|
||||
return {
|
||||
getLoginRedirectUrl,
|
||||
getSignUpRedirectUrl: getLoginRedirectUrl,
|
||||
@@ -30,7 +30,7 @@ export async function getMinimalLoginSystem(): Promise<GristLoginSystem> {
|
||||
user.isFirstTimeUser = false;
|
||||
await user.save();
|
||||
}
|
||||
return "no-logins";
|
||||
return 'no-logins';
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import * as session from '@gristlabs/express-session';
|
||||
import {parseSubdomain} from 'app/common/gristUrls';
|
||||
import {isNumber} from 'app/common/gutil';
|
||||
import {RequestWithOrg} from 'app/server/lib/extractOrg';
|
||||
import {GristServer} from 'app/server/lib/GristServer';
|
||||
import {Sessions} from 'app/server/lib/Sessions';
|
||||
@@ -12,7 +13,10 @@ import * as shortUUID from "short-uuid";
|
||||
|
||||
export const cookieName = process.env.GRIST_SESSION_COOKIE || 'grist_sid';
|
||||
|
||||
export const COOKIE_MAX_AGE = 90 * 24 * 60 * 60 * 1000; // 90 days in milliseconds
|
||||
export const COOKIE_MAX_AGE =
|
||||
process.env.COOKIE_MAX_AGE === 'none' ? null :
|
||||
isNumber(process.env.COOKIE_MAX_AGE || '') ? Number(process.env.COOKIE_MAX_AGE) :
|
||||
90 * 24 * 60 * 60 * 1000; // 90 days in milliseconds
|
||||
|
||||
// RedisStore and SqliteStore are expected to provide a set/get interface for sessions.
|
||||
export interface SessionStore {
|
||||
|
||||
@@ -223,6 +223,9 @@ export function pruneAPIResult<T>(data: T, allowedFields?: Set<string>): T {
|
||||
if (key === 'options' && value === null) { return undefined; }
|
||||
// Don't prune anything that is explicitly allowed.
|
||||
if (allowedFields?.has(key)) { return value; }
|
||||
// User connect id is not used in regular configuration, so we remove it from the response, when
|
||||
// it's not filled.
|
||||
if (key === 'connectId' && value === null) { return undefined; }
|
||||
return INTERNAL_FIELDS.has(key) ? undefined : value;
|
||||
});
|
||||
return JSON.parse(output);
|
||||
|
||||
Reference in New Issue
Block a user