1 import { stringToUint8Array, uint8ArrayToBase64String } from '@proton/shared/lib/helpers/encoding';
3 import type { AuthSession, EncryptedSessionKeys } from './session';
5 type IntegrityKey = keyof Omit<AuthSession, EncryptedSessionKeys>;
7 export const SESSION_DIGEST_VERSION = 1;
8 const VERSION_SEPARATOR = '.';
10 /** `AuthSession` keys used to verify the integrity of the session
11 * when resuming from a persisted state. This ensures that the
12 * session has not been tampered with, avoiding unintended side-effects
13 * if the persisted session was modified outside of our control.
14 * ⚠️ WARNING: UPDATING KEYS HERE WILL CAUSE EXISTING DIGESTS TO FAIL
15 * VALIDATION - BUMP THE `SESSION_DIGEST_VERSION` WHEN DOING SO. */
16 export const SESSION_INTEGRITY_KEYS_V1: IntegrityKey[] = [
28 export const getSessionIntegrityKeys = (version: number): IntegrityKey[] => {
31 return SESSION_INTEGRITY_KEYS_V1;
37 export const getSessionDigestVersion = (digest: string): number => {
39 const [versionStr] = digest.split(VERSION_SEPARATOR);
41 if (versionStr.length === digest.length) return SESSION_DIGEST_VERSION;
42 const version = parseInt(versionStr, 10);
43 return Number.isFinite(version) ? version : SESSION_DIGEST_VERSION;
45 return SESSION_DIGEST_VERSION;
49 /** Generates a unique digest based on specific session properties to ensure
50 * the integrity of the session when resuming from a persisted state. This
51 * digest can later be used to verify if the session has been tampered with. */
52 export const digestSession = async (
53 session: Omit<AuthSession, EncryptedSessionKeys>,
55 ): Promise<string> => {
56 const integrityKeys = getSessionIntegrityKeys(version);
57 const sessionDigest = integrityKeys.reduce<string>((digest, key) => `${digest}::${session[key] || '-'}`, '');
58 const sessionBuffer = stringToUint8Array(sessionDigest);
59 const digest = await crypto.subtle.digest('SHA-256', sessionBuffer);
61 return `${version}${VERSION_SEPARATOR}${uint8ArrayToBase64String(new Uint8Array(digest))}`;