gristlabs_grist-core/app/server/lib/sessionUtils.ts

256 lines
7.8 KiB
TypeScript
Raw Permalink Normal View History

import {DocumentUsage} from 'app/common/DocUsage';
import {FullUser} from 'app/common/LoginSessionAPI';
import {Role} from 'app/common/roles';
import {Document} from 'app/gen-server/entity/Document';
import {getUserId as getRequestUserId, getUser, RequestWithLogin} from 'app/server/lib/Authorizer';
import {OptDocSession} from 'app/server/lib/DocSession';
import {ILogMeta} from 'app/server/lib/log';
import {IncomingMessage} from 'http';
export type RequestOrSession = RequestWithLogin | OptDocSession | null;
export function isRequest(
requestOrSession: RequestOrSession
): requestOrSession is RequestWithLogin {
return Boolean(requestOrSession && 'get' in requestOrSession);
}
/**
* Extract the raw `IncomingMessage` from `requestOrSession`, if available.
*/
export function getRequest(requestOrSession: RequestOrSession): IncomingMessage | null {
if (!requestOrSession) { return null; }
// The location of the request depends on the context, which include REST
// API calls to document endpoints and WebSocket sessions.
if (isRequest(requestOrSession)) {
return requestOrSession;
} else if (requestOrSession.req) {
// A REST API call to a document endpoint.
return requestOrSession.req;
} else if (requestOrSession.client) {
// A WebSocket session.
return requestOrSession.client.getConnectionRequest();
} else {
return null;
}
}
export function getAltSessionId(requestOrSession: RequestOrSession): string | null {
if (!requestOrSession) { return null; }
if (isRequest(requestOrSession)) {
return requestOrSession.altSessionId || null;
} else {
return getDocSessionAltSessionId(requestOrSession);
}
}
function getDocSessionAltSessionId(docSession: OptDocSession): string|null {
if (docSession.req) {
return docSession.req.altSessionId || null;
}
if (docSession.client) {
return docSession.client.getAltSessionId() || null;
}
return null;
}
export function getUserId(requestOrSession: RequestOrSession): number|null {
if (!requestOrSession) { return null; }
if (isRequest(requestOrSession)) {
return getRequestUserId(requestOrSession);
} else {
return getDocSessionUserId(requestOrSession);
}
}
/**
* Extract userId from OptDocSession. Use Authorizer if available (for web socket
* sessions), or get it from the Request if that is available (for rest api calls),
* or from the Client if that is available. Returns null if userId information is
* not available or not cached.
*/
function getDocSessionUserId(docSession: OptDocSession): number|null {
if (docSession.authorizer) {
return docSession.authorizer.getUserId();
}
if (docSession.req) {
return getUserId(docSession.req);
}
if (docSession.client) {
return docSession.client.getCachedUserId();
}
return null;
}
/**
* Get as much of user profile as we can (id, name, email).
*/
export function getFullUser(requestOrSession: RequestOrSession): FullUser | null {
if (!requestOrSession) { return null; }
if (isRequest(requestOrSession)) {
return getRequestFullUser(requestOrSession);
} else {
return getDocSessionFullUser(requestOrSession);
}
}
function getRequestFullUser(request: RequestWithLogin): FullUser|null {
const user = getUser(request);
if (!user.loginEmail) { return null; }
const {id, name, loginEmail: email, ref, options} = user;
return {
id,
name,
email,
ref,
locale: options?.locale,
};
}
function getDocSessionFullUser(docSession: OptDocSession): FullUser|null {
if (docSession.authorizer) {
return docSession.authorizer.getUser();
}
if (docSession.req) {
return getRequestFullUser(docSession.req);
}
if (docSession.client) {
const id = docSession.client.getCachedUserId();
const ref = docSession.client.getCachedUserRef();
const profile = docSession.client.getProfile();
if (id && profile) {
return {
id,
ref,
...profile
};
}
}
return null;
}
export function getOrg(requestOrSession: RequestOrSession): string | null {
if (!requestOrSession) { return null; }
if (isRequest(requestOrSession)) {
return requestOrSession.org || null;
} else {
return getDocSessionOrg(requestOrSession);
}
}
function getDocSessionOrg(docSession: OptDocSession): string | null {
if (docSession.req) {
return docSession.req.org || null;
}
if (docSession.client) {
return docSession.client.getOrg() || null;
}
return null;
}
/**
* Extract access, userId, email, and client (if applicable) from
* `requestOrSession`, for logging purposes.
*/
export function getLogMeta(requestOrSession: RequestOrSession | undefined): ILogMeta {
if (!requestOrSession) { return {}; }
if (isRequest(requestOrSession)) {
return getRequestLogMeta(requestOrSession);
} else {
return getDocSessionLogMeta(requestOrSession);
}
}
function getRequestLogMeta(request: RequestWithLogin): ILogMeta {
const {org, user, userId, altSessionId} = request;
return {
org,
email: user?.loginEmail,
userId,
altSessionId,
};
}
function getDocSessionLogMeta(docSession: OptDocSession): ILogMeta {
const client = docSession.client;
const access = getDocSessionAccessOrNull(docSession);
const user = getDocSessionFullUser(docSession);
const email = user?.loginEmail || user?.email;
return {
access,
...(user ? {userId: user.id, email} : {}),
...(client ? client.getLogMeta() : {}), // Client if present will repeat and add to user info.
};
}
/**
* Extract user's role from OptDocSession. Method depends on whether using web
* sockets or rest api. Assumes that access has already been checked by wrappers
* for api methods and that cached access information is therefore available.
*/
export function getDocSessionAccess(docSession: OptDocSession): Role {
// "nascent" DocSessions are for when a document is being created, and user is
// its only owner as yet.
// "system" DocSessions are for access without access control.
if (docSession.mode === 'nascent' || docSession.mode === 'system') { return 'owners'; }
// "plugin" DocSessions are for access from plugins, which is currently quite crude,
// and granted only to editors.
if (docSession.mode === 'plugin') { return 'editors'; }
if (docSession.authorizer) {
const access = docSession.authorizer.getCachedAuth().access;
if (!access) { throw new Error('getDocSessionAccess expected authorizer.getCachedAuth'); }
return access;
}
if (docSession.req) {
const access = docSession.req.docAuth?.access;
if (!access) { throw new Error('getDocSessionAccess expected req.docAuth.access'); }
return access;
}
throw new Error('getDocSessionAccess could not find access information in DocSession');
}
export function getDocSessionShare(docSession: OptDocSession): string|null {
return _getCachedDoc(docSession)?.linkId || null;
}
/**
* Get document usage seen in db when we were last checking document
* access. Not necessarily a live value when using a websocket
* (although we do recheck access periodically).
*/
export function getDocSessionUsage(docSession: OptDocSession): DocumentUsage|null {
return _getCachedDoc(docSession)?.usage || null;
}
export function _getCachedDoc(docSession: OptDocSession): Document|null {
if (docSession.authorizer) {
return docSession.authorizer.getCachedAuth().cachedDoc || null;
}
if (docSession.req) {
return docSession.req.docAuth?.cachedDoc || null;
}
return null;
}
export function getDocSessionAccessOrNull(docSession: OptDocSession): Role|null {
try {
return getDocSessionAccess(docSession);
} catch (err) {
return null;
}
}
/**
* Get cached information about the document, if available. May be stale.
*/
export function getDocSessionCachedDoc(docSession: OptDocSession): Document | undefined {
return (docSession.req as RequestWithLogin)?.docAuth?.cachedDoc;
}