mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
256 lines
7.8 KiB
TypeScript
256 lines
7.8 KiB
TypeScript
|
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;
|
||
|
}
|