UNPKG

@thermopylae/lib.user-session

Version:

Stateful implementation of the user session.

210 lines (209 loc) 9.74 kB
import type { Seconds, UnixTimestamp } from '@thermopylae/core.declarations'; import type { Subject, SessionId, DeviceBase, UserSessionOperationContext, ReadUserSessionHook } from '@thermopylae/lib.user-session.commons'; import type { UserSessionMetaData } from './session'; import type { UserSessionsStorage } from './storage'; /** * Hooks called when session is renewed. <br/> * Mainly can be used for logging purposes. */ interface RenewSessionHooks { /** * Hook called when making an attempt to renew user session, but it was * renewed already from current NodeJs process. * * @param sessionId Id of the session that was tried to be renewed. */ onRenewMadeAlreadyFromCurrentProcess(sessionId: string): void; /** * Hook called when making an attempt to renew user session, but it was * renewed already from another NodeJs process. * * @param sessionId Id of the session that was tried to be renewed. */ onRenewMadeAlreadyFromAnotherProcess(sessionId: string): void; /** * After successful renew operation, the deletion of old session will be scheduled to occur * after {@link UserSessionTimeouts.oldSessionAvailabilityAfterRenewal} seconds. <br/> * In case the deletion of the old session will fail, this hook will be called with that error. * * @param sessionId Id of the session. * @param e Error that caused failure of the old session deletion. */ onOldSessionDeleteFailure(sessionId: string, e: Error): void; } interface UserSessionTimeouts { /** * This timeout defines the amount of time in seconds a session will remain active * in case there is no activity in the session, closing and invalidating the session * upon the defined idle period since the last HTTP request received by the web application. <br/> * If you do not need idle session feature, do not set this option. <br/> * **Defaults** to *undefined*. */ readonly idle?: Seconds; /** * This timeout defines the amount of time in seconds since session creation * after which the session ID is automatically renewed. * Renewal happens automatically when user session is read by manager. <br/> * Renewal consists in deletion of the old session and creation of a new one. <br/> * If you do not need renewal session feature, do not set this option. <br/> * **Defaults** to *undefined*. */ readonly renewal?: Seconds; /** * This timeout defines the amount of time the old session will still be available after it was renewed. <br/> * This timeout starts counting from renew session operation, and on elapse will delete the old session. <br/> * Usually you will want to keep this timeout as small as possible to give a chance to requests that * were issued before renew operation to finish successfully, and then invalidate old session. * If {@link UserSessionManagerOptions.timeouts.renewal} option is not set, this option is ignored. <br/> * **Required** when {@link UserSessionManagerOptions.timeouts.renewal} option is set. * **Recommended** value is *5*. */ readonly oldSessionAvailabilityAfterRenewal?: Seconds; } interface UserSessionManagerOptions<Device extends DeviceBase, Location> { /** * Length of the generated session id. <br/> * Because base64 encoding is used underneath, this is not the string length. * For example, to create a token of length 24, you want a byte length of 18. <br/> * Value of this option should not be lower than 15. <br/> * > **Important!** To prevent brute forcing [create long enough session id's](https://security.stackexchange.com/questions/81519/session-hijacking-through-sessionid-brute-forcing-possible). */ readonly idLength: number; /** * Time To Live of the user session (in seconds). */ readonly sessionTtl: Seconds; /** * Storage where users sessions are stored. */ readonly storage: UserSessionsStorage<Device, Location>; /** * User session lifetime timeouts. */ readonly timeouts?: UserSessionTimeouts; /** * Read user session hook. <br/> * Defaults to hook which ensures that in case device is present in both context and session metadata, * their *name* and *type* needs to be equal. */ readonly readUserSessionHook?: ReadUserSessionHook<Device, Location, UserSessionMetaData<Device, Location>>; /** * Hooks called on session renew. <br/> * Defaults to noop hooks. */ readonly renewSessionHooks?: RenewSessionHooks; } /** * Mark session as being renewed. */ declare const RENEWED_SESSION_FLAG = -1; /** * Stateful implementation of the user sessions. <br/> * Session data is stored in external storage and client receives only it's id. <br/> * Sessions are implemented in a such way, so that they can be used in cluster or single-node infrastructures. * * @template Device Type of the device. * @template Location Type of the location. */ declare class UserSessionManager<Device extends DeviceBase = DeviceBase, Location = string> { /** * Manager options. */ private readonly options; /** * Sessions that were renewed and are about to expire very soon. <br/> * This acts as *atomic flag* at the NodeJS process level, * to mark the session as being renewed and prevent further renews on it. <br/> */ private readonly renewedSessions; /** * @param options Options object. <br/> * It should not be modified after, as it will be used without being cloned. */ constructor(options: UserSessionManagerOptions<Device, Location>); /** * Create new user session for `subject`. * * @param subject Subject the session will belong to. * @param context Operation context. * @param sessionTtl Session ttl. Takes precedence over the default one. * * @returns User session id. */ create(subject: Subject, context: UserSessionOperationContext<Device, Location>, sessionTtl?: Seconds): Promise<SessionId>; /** * Read user session from external storage. <br/> * When idle functionality is activated, this method might delete user session and throw an error to notify about this, * if it was idle for more than {@link UserSessionManagerOptions.timeouts.idle} seconds. <br/> * When renew functionality is activated, this method might create a new user session, and return it's id as the second part of the tuple. * In case renewed session id is returned, it needs to be sent to application clients via 'Set-Cookie' header to replace the old session cookie. * The old session will still be available for {@link UserSessionManagerOptions.timeouts.oldSessionAvailabilityAfterRenewal} seconds, * so that older requests might complete successfully and client has time to refresh session id on it's side. * * @param subject Subject. * @param sessionId Session id. * @param context Operation context. * * @throws {Exception} With the following error codes: * - {@link ErrorCodes.USER_SESSION_NOT_FOUND} - session wasn't found in the storage * - {@link ErrorCodes.USER_SESSION_EXPIRED} - session is accessed from a device which differs from the one it was created * - {@link ErrorCodes.USER_SESSION_EXPIRED} - session was expired because of the idle timeout * * @returns A tuple with the following parts: * - session metadata * - renewed session id (if renewal took place) */ read(subject: Subject, sessionId: SessionId, context: UserSessionOperationContext<Device, Location>): Promise<[Readonly<UserSessionMetaData<Device, Location>>, string | null]>; /** * Read all active sessions of the subject. * * @param subject Subject. * * @returns Active sessions of the subject. */ readAll(subject: Subject): Promise<ReadonlyMap<SessionId, Readonly<UserSessionMetaData<Device, Location>>>>; /** * Renew user session. <br/> * Renewing consist from the following actions: * 1. scheduling deletion of the old session in a very short amount of time * 2. creating a new user session * * @param subject Subject. * @param sessionId Id of the session to be renewed. * @param sessionMetaData Metadata of that session. * @param context Operation context. * * @returns The new user session id. <br/> * When renew can't be performed, a log message is printed and *null* is returned. */ renew(subject: Subject, sessionId: SessionId, sessionMetaData: UserSessionMetaData<Device, Location>, context: UserSessionOperationContext<Device, Location>): Promise<string | null>; /** * Delete user session. * * @param subject Subject. * @param sessionId Id of the user session. */ delete(subject: Subject, sessionId: SessionId): Promise<void>; /** * Delete all of the user sessions. * * @param subject Subject. * * @returns Number of deleted sessions. */ deleteAll(subject: Subject): Promise<number>; /** * Hashes session id. <br/> * Useful for logging purposes. * * @param sessionId Session id. * * @returns Hashed session id. */ static hash(sessionId: SessionId): string; private static fillWithDefaults; static currentTimestamp(): UnixTimestamp; private static readUserSessionHook; private static renewSessionHooks; } export { UserSessionManager, UserSessionManagerOptions, UserSessionTimeouts, RenewSessionHooks, RENEWED_SESSION_FLAG };