mirror of
https://github.com/gristlabs/grist-core.git
synced 2026-03-02 04:09:24 +00:00
(core) Remove LoginSession, which was mainly serving situations that are no longer used.
Summary: In the past, Cognito sign-ins were intended to give authorization to some AWS services (like SQS); various tokens were stored in the session for this purpose. This is no longer used. Profiles from Cognito now serve a limited purpose: first-time initialization of name and picture, and keeping track of which login method was used. For these remaining needs, ScopedSession is sufficient. Test Plan: Existing test pass. Tested manually that logins work with Google and Email + Password. Tested manually that on a clean database, name and picture are picked up from a Google Login. Reviewers: paulfitz Reviewed By: paulfitz Differential Revision: https://phab.getgrist.com/D2907
This commit is contained in:
4
app/server/declarations.d.ts
vendored
4
app/server/declarations.d.ts
vendored
@@ -4,7 +4,7 @@ declare module "app/server/lib/User";
|
||||
|
||||
declare module "app/server/lib/Comm" {
|
||||
import {Client, ClientMethod} from "app/server/lib/Client";
|
||||
import {LoginSession} from "app/server/lib/LoginSession";
|
||||
import {ScopedSession} from "app/server/lib/BrowserSession";
|
||||
import * as http from "http";
|
||||
|
||||
class Comm {
|
||||
@@ -14,7 +14,7 @@ declare module "app/server/lib/Comm" {
|
||||
public setServerVersion(serverVersion: string|null): void;
|
||||
public setServerActivation(active: boolean): void;
|
||||
public getSessionIdFromCookie(gristSidCookie: string): string;
|
||||
public getOrCreateSession(sessionId: string, req: any): LoginSession;
|
||||
public getOrCreateSession(sessionId: string, req: any): ScopedSession;
|
||||
public registerMethods(methods: {[name: string]: ClientMethod}): void;
|
||||
public getClient(clientId: string): Client;
|
||||
public testServerShutdown(): Promise<void>;
|
||||
|
||||
@@ -1164,7 +1164,7 @@ export class ActiveDoc extends EventEmitter {
|
||||
await this._granularAccess.assertCanMaybeApplyUserActions(docSession, actions);
|
||||
|
||||
const user = docSession.mode === 'system' ? 'grist' :
|
||||
(client && client.session ? (await client.session.getEmail()) : "");
|
||||
(client?.getProfile()?.email || '');
|
||||
|
||||
// Create the UserActionBundle.
|
||||
const action: UserActionBundle = {
|
||||
|
||||
@@ -8,19 +8,19 @@ export interface SessionUserObj {
|
||||
// a grist-internal identify for the user, if known.
|
||||
userId?: number;
|
||||
|
||||
// The user profile object. When updated, all clients get a message with the update.
|
||||
// The user profile object.
|
||||
profile?: UserProfile;
|
||||
|
||||
// Authentication provider string indicating the login method used.
|
||||
// [UNUSED] Authentication provider string indicating the login method used.
|
||||
authProvider?: string;
|
||||
|
||||
// Login ID token used to access AWS services.
|
||||
// [UNUSED] Login ID token used to access AWS services.
|
||||
idToken?: string;
|
||||
|
||||
// Login access token used to access other AWS services.
|
||||
// [UNUSED] Login access token used to access other AWS services.
|
||||
accessToken?: string;
|
||||
|
||||
// Login refresh token used to retrieve new ID and access tokens.
|
||||
// [UNUSED] Login refresh token used to retrieve new ID and access tokens.
|
||||
refreshToken?: string;
|
||||
}
|
||||
|
||||
@@ -133,6 +133,26 @@ export class ScopedSession {
|
||||
return getSessionUser(session, this._org, this._userSelector) || {};
|
||||
}
|
||||
|
||||
// Retrieves the user profile from the session.
|
||||
public async getSessionProfile(prev?: SessionObj): Promise<UserProfile|null> {
|
||||
return (await this.getScopedSession(prev)).profile || null;
|
||||
}
|
||||
|
||||
// Updates a user profile. The session may have multiple profiles associated with different
|
||||
// email addresses. This will update the one with a matching email address, or add a new one.
|
||||
// This is mainly used to know which emails are logged in in this session; fields like name and
|
||||
// picture URL come from the database instead.
|
||||
public async updateUserProfile(profile: UserProfile|null): Promise<void> {
|
||||
if (profile) {
|
||||
await this.operateOnScopedSession(async user => {
|
||||
user.profile = profile;
|
||||
return user;
|
||||
});
|
||||
} else {
|
||||
await this.clearScopedSession();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* This performs an operation on the session object, limited to a single user entry. The state of that
|
||||
|
||||
@@ -8,9 +8,9 @@ import {User} from 'app/gen-server/entity/User';
|
||||
import {HomeDBManager} from 'app/gen-server/lib/HomeDBManager';
|
||||
import {ActiveDoc} from 'app/server/lib/ActiveDoc';
|
||||
import {Authorizer} from 'app/server/lib/Authorizer';
|
||||
import {ScopedSession} from 'app/server/lib/BrowserSession';
|
||||
import {DocSession} from 'app/server/lib/DocSession';
|
||||
import * as log from 'app/server/lib/log';
|
||||
import {ILoginSession} from 'app/server/lib/ILoginSession';
|
||||
import {shortDesc} from 'app/server/lib/shortDesc';
|
||||
import * as crypto from 'crypto';
|
||||
import * as moment from 'moment';
|
||||
@@ -61,10 +61,10 @@ void(MESSAGE_TYPES_NO_AUTH);
|
||||
export class Client {
|
||||
public readonly clientId: string;
|
||||
|
||||
public session: ILoginSession|null = null;
|
||||
|
||||
public browserSettings: BrowserSettings = {};
|
||||
|
||||
private _session: ScopedSession|null = null;
|
||||
|
||||
// Maps docFDs to DocSession objects.
|
||||
private _docFDs: Array<DocSession|null> = [];
|
||||
|
||||
@@ -221,21 +221,16 @@ export class Client {
|
||||
}
|
||||
}
|
||||
|
||||
// Assigns the client to the given login session and the session to the client.
|
||||
public setSession(session: ILoginSession): void {
|
||||
this.unsetSession();
|
||||
this.session = session;
|
||||
session.clients.add(this);
|
||||
// Assigns the given ScopedSession to the client.
|
||||
public setSession(session: ScopedSession): void {
|
||||
this._session = session;
|
||||
}
|
||||
|
||||
// Unsets the current login session and removes the client from it.
|
||||
public unsetSession(): void {
|
||||
if (this.session) { this.session.clients.delete(this); }
|
||||
this.session = null;
|
||||
public getSession(): ScopedSession|null {
|
||||
return this._session;
|
||||
}
|
||||
|
||||
public destroy() {
|
||||
this.unsetSession();
|
||||
this._destroyed = true;
|
||||
}
|
||||
|
||||
@@ -318,6 +313,14 @@ export class Client {
|
||||
return this._profile;
|
||||
}
|
||||
|
||||
public async getSessionProfile(): Promise<UserProfile|null|undefined> {
|
||||
return this._session?.getSessionProfile();
|
||||
}
|
||||
|
||||
public async getSessionEmail(): Promise<string|null> {
|
||||
return (await this.getSessionProfile())?.email || null;
|
||||
}
|
||||
|
||||
public getCachedUserId(): number|null {
|
||||
return this._userId;
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ function Comm(server, options) {
|
||||
this._clients = {}; // Maps clientIds to Client objects.
|
||||
this.clientList = []; // List of all active Clients, ordered by clientId.
|
||||
|
||||
// Maps sessionIds to LoginSession objects.
|
||||
// Maps sessionIds to ScopedSession objects.
|
||||
this.sessions = options.sessions;
|
||||
|
||||
this._settings = options.settings;
|
||||
@@ -118,14 +118,13 @@ Comm.prototype.getClient = function(clientId) {
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a LoginSession object with the given session id from the list of sessions,
|
||||
* Returns a ScopedSession object with the given session id from the list of sessions,
|
||||
* or adds a new one and returns that.
|
||||
*/
|
||||
Comm.prototype.getOrCreateSession = function(sid, req, userSelector) {
|
||||
// LoginSessions are specific to a session id / org combination.
|
||||
// ScopedSessions are specific to a session id / org combination.
|
||||
const org = req.org || "";
|
||||
return this.sessions.getOrCreateLoginSession(sid, org, this,
|
||||
userSelector);
|
||||
return this.sessions.getOrCreateSession(sid, org, userSelector);
|
||||
};
|
||||
|
||||
|
||||
@@ -230,13 +229,13 @@ Comm.prototype._onWebSocketConnection = async function(websocket, req) {
|
||||
|
||||
// Add a Session object to the client.
|
||||
log.info(`Comm ${client}: using session ${sessionId}`);
|
||||
const loginSession = this.getOrCreateSession(sessionId, req, userSelector);
|
||||
client.setSession(loginSession);
|
||||
const scopedSession = this.getOrCreateSession(sessionId, req, userSelector);
|
||||
client.setSession(scopedSession);
|
||||
|
||||
// Delegate message handling to the client
|
||||
websocket.on('message', client.onMessage.bind(client));
|
||||
|
||||
loginSession.getSessionProfile()
|
||||
scopedSession.getSessionProfile()
|
||||
.then((profile) => {
|
||||
log.debug(`Comm ${client}: sending clientConnect with ` +
|
||||
`${client._missedMessages.length} missed messages`);
|
||||
|
||||
@@ -788,15 +788,12 @@ export class FlexServer implements GristServer {
|
||||
this.app.get('/test/login', expressWrap(async (req, res) => {
|
||||
log.warn("Serving unauthenticated /test/login endpoint, made available because GRIST_TEST_LOGIN is set.");
|
||||
|
||||
const session = this.sessions.getOrCreateSessionFromRequest(req);
|
||||
const scopedSession = this.sessions.getOrCreateSessionFromRequest(req);
|
||||
const profile: UserProfile = {
|
||||
email: optStringParam(req.query.email) || 'chimpy@getgrist.com',
|
||||
name: optStringParam(req.query.name) || 'Chimpy McBanana',
|
||||
};
|
||||
await session.scopedSession.operateOnScopedSession(async user => {
|
||||
user.profile = profile;
|
||||
return user;
|
||||
});
|
||||
await scopedSession.updateUserProfile(profile);
|
||||
res.send(`<!doctype html>
|
||||
<html><body>
|
||||
<p>Logged in as ${JSON.stringify(profile)}.<p>
|
||||
@@ -811,8 +808,8 @@ export class FlexServer implements GristServer {
|
||||
}
|
||||
|
||||
this.app.get('/logout', expressWrap(async (req, resp) => {
|
||||
const session = this.sessions.getOrCreateSessionFromRequest(req);
|
||||
const userSession = await session.scopedSession.getScopedSession();
|
||||
const scopedSession = this.sessions.getOrCreateSessionFromRequest(req);
|
||||
const userSession = await scopedSession.getScopedSession();
|
||||
|
||||
// If 'next' param is missing, redirect to "/" on our requested hostname.
|
||||
const next = optStringParam(req.query.next) || (req.protocol + '://' + req.get('host') + '/');
|
||||
@@ -823,9 +820,7 @@ export class FlexServer implements GristServer {
|
||||
// Express-session will save these changes.
|
||||
const expressSession = (req as any).session;
|
||||
if (expressSession) { expressSession.users = []; expressSession.orgToUser = {}; }
|
||||
if (session.loginSession) {
|
||||
await session.loginSession.clearSession();
|
||||
}
|
||||
await scopedSession.clearScopedSession();
|
||||
resp.redirect(redirectUrl);
|
||||
}));
|
||||
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
import {HomeDBManager} from 'app/gen-server/lib/HomeDBManager';
|
||||
import {ActiveDoc} from 'app/server/lib/ActiveDoc';
|
||||
import {ScopedSession} from 'app/server/lib/BrowserSession';
|
||||
import * as Comm from 'app/server/lib/Comm';
|
||||
import {DocManager} from 'app/server/lib/DocManager';
|
||||
import {ExternalStorage} from 'app/server/lib/ExternalStorage';
|
||||
import {GristServer} from 'app/server/lib/GristServer';
|
||||
import {IBilling} from 'app/server/lib/IBilling';
|
||||
import {ILoginSession} from 'app/server/lib/ILoginSession';
|
||||
import {INotifier} from 'app/server/lib/INotifier';
|
||||
import {ISandbox, ISandboxCreationOptions} from 'app/server/lib/ISandbox';
|
||||
import {IShell} from 'app/server/lib/IShell';
|
||||
|
||||
export interface ICreate {
|
||||
LoginSession(comm: Comm, sid: string, domain: string, scopeSession: ScopedSession): ILoginSession;
|
||||
// A ScopedSession knows which user is logged in to an org. This method may be used to replace
|
||||
// its behavior with stubs when logins aren't available.
|
||||
adjustSession(scopedSession: ScopedSession): void;
|
||||
|
||||
Billing(dbManager: HomeDBManager, gristConfig: GristServer): IBilling;
|
||||
Notifier(dbManager: HomeDBManager, gristConfig: GristServer): INotifier;
|
||||
Shell(): IShell|undefined;
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
import {UserProfile} from 'app/common/LoginSessionAPI';
|
||||
import {Client} from 'app/server/lib/Client';
|
||||
|
||||
export interface ILoginSession {
|
||||
clients: Set<Client>;
|
||||
getEmail(): Promise<string>;
|
||||
getSessionProfile(): Promise<UserProfile|null>;
|
||||
// Log out
|
||||
clearSession(): Promise<void>;
|
||||
|
||||
// For testing only. If no email address, profile is wiped, otherwise it is set.
|
||||
testSetProfile(profile: UserProfile|null): Promise<void>;
|
||||
}
|
||||
@@ -1,17 +1,10 @@
|
||||
import {ScopedSession} from 'app/server/lib/BrowserSession';
|
||||
import * as Comm from 'app/server/lib/Comm';
|
||||
import {GristServer} from 'app/server/lib/GristServer';
|
||||
import {cookieName, SessionStore} from 'app/server/lib/gristSessions';
|
||||
import {ILoginSession} from 'app/server/lib/ILoginSession';
|
||||
import * as cookie from 'cookie';
|
||||
import * as cookieParser from 'cookie-parser';
|
||||
import {Request} from 'express';
|
||||
|
||||
interface Session {
|
||||
scopedSession: ScopedSession;
|
||||
loginSession?: ILoginSession;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* A collection of all the sessions relevant to this instance of Grist.
|
||||
@@ -21,8 +14,7 @@ interface Session {
|
||||
* from code related to websockets.
|
||||
*
|
||||
* The collection caches all existing interfaces to sessions.
|
||||
* LoginSessions play an important role in standalone Grist and address
|
||||
* end-to-end sharing concerns. ScopedSessions play an important role in
|
||||
* ScopedSessions play an important role in
|
||||
* hosted Grist and address per-organization scoping of identity.
|
||||
*
|
||||
* TODO: now this is separated out, we could refactor to share sessions
|
||||
@@ -32,7 +24,7 @@ interface Session {
|
||||
*
|
||||
*/
|
||||
export class Sessions {
|
||||
private _sessions = new Map<string, Session>();
|
||||
private _sessions = new Map<string, ScopedSession>();
|
||||
|
||||
constructor(private _sessionSecret: string, private _sessionStore: SessionStore, private _server: GristServer) {
|
||||
}
|
||||
@@ -41,7 +33,7 @@ export class Sessions {
|
||||
* Get the session id and organization from the request, and return the
|
||||
* identified session.
|
||||
*/
|
||||
public getOrCreateSessionFromRequest(req: Request): Session {
|
||||
public getOrCreateSessionFromRequest(req: Request): ScopedSession {
|
||||
const sid = this.getSessionIdFromRequest(req);
|
||||
const org = (req as any).org;
|
||||
if (!sid) { throw new Error("session not found"); }
|
||||
@@ -51,29 +43,16 @@ export class Sessions {
|
||||
/**
|
||||
* Get or create a session given the session id and organization name.
|
||||
*/
|
||||
public getOrCreateSession(sid: string, domain: string, userSelector: string): Session {
|
||||
public getOrCreateSession(sid: string, domain: string, userSelector: string): ScopedSession {
|
||||
const key = this._getSessionOrgKey(sid, domain, userSelector);
|
||||
if (!this._sessions.has(key)) {
|
||||
const scopedSession = new ScopedSession(sid, this._sessionStore, domain, userSelector);
|
||||
this._sessions.set(key, {scopedSession});
|
||||
this._server.create.adjustSession(scopedSession);
|
||||
this._sessions.set(key, scopedSession);
|
||||
}
|
||||
return this._sessions.get(key)!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Access a LoginSession interface, creating it if necessary. For creation,
|
||||
* purposes, Comm, and optionally InstanceManager objects are needed.
|
||||
*
|
||||
*/
|
||||
public getOrCreateLoginSession(sid: string, domain: string, comm: Comm,
|
||||
userSelector: string): ILoginSession {
|
||||
const sess = this.getOrCreateSession(sid, domain, userSelector);
|
||||
if (!sess.loginSession) {
|
||||
sess.loginSession = this._server.create.LoginSession(comm, sid, domain, sess.scopedSession);
|
||||
}
|
||||
return sess.loginSession;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the sessionId from the signed grist cookie.
|
||||
*/
|
||||
|
||||
@@ -73,8 +73,8 @@ export class TestingHooks implements ITestingHooks {
|
||||
public async setLoginSessionProfile(gristSidCookie: string, profile: UserProfile|null, org?: string): Promise<void> {
|
||||
log.info("TestingHooks.setLoginSessionProfile called with", gristSidCookie, profile, org);
|
||||
const sessionId = this._comm.getSessionIdFromCookie(gristSidCookie);
|
||||
const loginSession = this._comm.getOrCreateSession(sessionId, {org});
|
||||
return await loginSession.testSetProfile(profile);
|
||||
const scopedSession = this._comm.getOrCreateSession(sessionId, {org});
|
||||
return await scopedSession.updateUserProfile(profile);
|
||||
}
|
||||
|
||||
public async setServerVersion(version: string|null): Promise<void> {
|
||||
|
||||
Reference in New Issue
Block a user