@thermopylae/lib.user-session
Version:
Stateful implementation of the user session.
210 lines (209 loc) • 9.74 kB
TypeScript
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 };