2020-07-21 13:20:51 +00:00
|
|
|
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.
|
|
|
|
*
|
|
|
|
* This collection was previously maintained by the Comm object. This
|
|
|
|
* class is added as a stepping stone to disentangling session management
|
|
|
|
* 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
|
|
|
|
* hosted Grist and address per-organization scoping of identity.
|
|
|
|
*
|
|
|
|
* TODO: now this is separated out, we could refactor to share sessions
|
|
|
|
* across organizations. Currently, when a user moves between organizations,
|
|
|
|
* the session interfaces are not shared. This was for simplicity in working
|
|
|
|
* with existing code.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
export class Sessions {
|
|
|
|
private _sessions = new Map<string, Session>();
|
|
|
|
|
|
|
|
constructor(private _sessionSecret: string, private _sessionStore: SessionStore, private _server: GristServer) {
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the session id and organization from the request, and return the
|
|
|
|
* identified session.
|
|
|
|
*/
|
|
|
|
public getOrCreateSessionFromRequest(req: Request): Session {
|
|
|
|
const sid = this.getSessionIdFromRequest(req);
|
|
|
|
const org = (req as any).org;
|
|
|
|
if (!sid) { throw new Error("session not found"); }
|
2020-12-11 19:22:35 +00:00
|
|
|
return this.getOrCreateSession(sid, org, ''); // TODO: allow for tying to a preferred user.
|
2020-07-21 13:20:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get or create a session given the session id and organization name.
|
|
|
|
*/
|
2020-12-11 19:22:35 +00:00
|
|
|
public getOrCreateSession(sid: string, domain: string, userSelector: string): Session {
|
|
|
|
const key = this._getSessionOrgKey(sid, domain, userSelector);
|
2020-07-21 13:20:51 +00:00
|
|
|
if (!this._sessions.has(key)) {
|
2020-12-11 19:22:35 +00:00
|
|
|
const scopedSession = new ScopedSession(sid, this._sessionStore, domain, userSelector);
|
2020-07-21 13:20:51 +00:00
|
|
|
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,
|
2020-12-11 19:22:35 +00:00
|
|
|
userSelector: string): ILoginSession {
|
|
|
|
const sess = this.getOrCreateSession(sid, domain, userSelector);
|
2020-07-21 13:20:51 +00:00
|
|
|
if (!sess.loginSession) {
|
2021-07-01 15:15:43 +00:00
|
|
|
sess.loginSession = this._server.create.LoginSession(comm, sid, domain, sess.scopedSession);
|
2020-07-21 13:20:51 +00:00
|
|
|
}
|
|
|
|
return sess.loginSession;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the sessionId from the signed grist cookie.
|
|
|
|
*/
|
|
|
|
public getSessionIdFromCookie(gristCookie: string) {
|
|
|
|
return cookieParser.signedCookie(gristCookie, this._sessionSecret);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the session id from the grist cookie. Returns null if no cookie found.
|
|
|
|
*/
|
|
|
|
public getSessionIdFromRequest(req: Request): string|null {
|
|
|
|
if (req.headers.cookie) {
|
|
|
|
const cookies = cookie.parse(req.headers.cookie);
|
|
|
|
const sessionId = this.getSessionIdFromCookie(cookies[cookieName]);
|
|
|
|
return sessionId;
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get a per-organization, per-session key.
|
|
|
|
* Grist has historically cached sessions in memory by their session id.
|
|
|
|
* With the introduction of per-organization identity, that cache is now
|
|
|
|
* needs to be keyed by the session id and organization name.
|
2020-12-11 19:22:35 +00:00
|
|
|
* Also, clients may now want to be tied to a particular user available within
|
|
|
|
* a session, so we add that into key too.
|
2020-07-21 13:20:51 +00:00
|
|
|
*/
|
2020-12-11 19:22:35 +00:00
|
|
|
private _getSessionOrgKey(sid: string, domain: string, userSelector: string): string {
|
|
|
|
return `${sid}__${domain}__${userSelector}`;
|
2020-07-21 13:20:51 +00:00
|
|
|
}
|
|
|
|
}
|