{"id":14483657,"url":"https://github.com/47ng/session-keystore","last_synced_at":"2025-04-05T08:05:08.442Z","repository":{"id":36474575,"uuid":"227167935","full_name":"47ng/session-keystore","owner":"47ng","description":"Secure cryptographic key storage in the browser and Node.js","archived":false,"fork":false,"pushed_at":"2025-01-06T08:46:46.000Z","size":1648,"stargazers_count":76,"open_issues_count":33,"forks_count":4,"subscribers_count":3,"default_branch":"next","last_synced_at":"2025-03-29T07:06:01.372Z","etag":null,"topics":["key-storage","session-keystore","session-storage","sessionstorage"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/47ng.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":["franky47"],"liberapay":"francoisbest","custom":["https://paypal.me/francoisbest?locale.x=fr_FR"]}},"created_at":"2019-12-10T16:35:29.000Z","updated_at":"2025-03-03T20:33:04.000Z","dependencies_parsed_at":"2024-10-24T23:22:35.858Z","dependency_job_id":"c2cdae0e-6c54-44b2-b38b-92b6d41e1066","html_url":"https://github.com/47ng/session-keystore","commit_stats":{"total_commits":258,"total_committers":4,"mean_commits":64.5,"dds":"0.27519379844961245","last_synced_commit":"eb4ab8f1a74d0c3314d921b973b84ab5898a31bb"},"previous_names":[],"tags_count":11,"template":false,"template_full_name":"47ng/typescript-library-starter","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/47ng%2Fsession-keystore","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/47ng%2Fsession-keystore/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/47ng%2Fsession-keystore/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/47ng%2Fsession-keystore/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/47ng","download_url":"https://codeload.github.com/47ng/session-keystore/tar.gz/refs/heads/next","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247305933,"owners_count":20917208,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["key-storage","session-keystore","session-storage","sessionstorage"],"created_at":"2024-09-03T00:01:57.568Z","updated_at":"2025-04-05T08:05:08.423Z","avatar_url":"https://github.com/47ng.png","language":"TypeScript","funding_links":["https://github.com/sponsors/franky47","https://liberapay.com/francoisbest","https://paypal.me/francoisbest?locale.x=fr_FR"],"categories":["TypeScript"],"sub_categories":[],"readme":"\u003cimg\n  src=\"./img/header@2x.png\"\n  width=\"640px\"\n  alt=\"session-keystore\"\n/\u003e\n\n[![NPM](https://img.shields.io/npm/v/session-keystore?color=red)](https://www.npmjs.com/package/session-keystore)\n[![MIT License](https://img.shields.io/github/license/47ng/session-keystore.svg?color=blue)](https://github.com/47ng/session-keystore/blob/master/LICENSE)\n[![Continuous Integration](https://github.com/47ng/session-keystore/workflows/Continuous%20Integration/badge.svg?branch=next)](https://github.com/47ng/session-keystore/actions)\n[![Coverage Status](https://coveralls.io/repos/github/47ng/session-keystore/badge.svg?branch=next)](https://coveralls.io/github/47ng/session-keystore?branch=next)\n[![Dependabot Status](https://api.dependabot.com/badges/status?host=github\u0026repo=47ng/session-keystore)](https://dependabot.com)\n\nSecure cryptographic key storage in the browser and Node.js\n\nIdeal to store keys derived from user credentials (username/password) in\nE2EE applications.\n\n## Features\n\n- In-memory storage: no clear-text persistance to disk\n- Session-bound: cleared when closing tab/window (browser-only)\n- Survives hard-reloads of the page (browser-only)\n- Optional expiration dates\n- Event emitter API for key CRUD operations\n\n## Installation\n\n```shell\n$ yarn add session-keystore\n# or\n$ npm i session-keystore\n```\n\n## Usage\n\n```ts\nimport SessionKeystore from 'session-keystore'\n\n// Create a store\nconst store = new SessionKeystore()\n\n// You can create multiple stores, but give them a unique name:\n// (default name is 'default')\nconst otherStore = new SessionKeystore({ name: 'other' })\n\n// Save a session-bound key\nstore.set('foo', 'supersecret')\n\n// Set an expiration date (Date or timestamp in ms)\nstore.set('bar', 'supersecret', Date.now() + 1000 * 60 * 5) // 5 minutes\n\n// Retrieve the key\nconst key = store.get('bar')\n// key will be null if it has expired\n\n// Revoke a single key\nstore.delete('foo')\n\n// Clear all keys in storage\nstore.clear()\n```\n\n## CRUD Event Emitter\n\nEvent types:\n\n- `created`\n- `read`\n- `updated`\n- `deleted`\n- `expired`\n\nListen to events on a keystore with the `on` method:\n\n```ts\nimport SessionKeystore from 'session-keystore'\n\nconst store = new SessionKeystore()\nstore.on('created', ({ name }) =\u003e console.log('Key created: ', name))\nstore.on('updated', ({ name }) =\u003e console.log('Key updated: ', name))\nstore.on('deleted', ({ name }) =\u003e console.log('Key deleted: ', name))\nstore.on('expired', ({ name }) =\u003e console.log('Key expired: ', name))\nstore.on('read', ({ name }) =\u003e console.log('Key accessed: ', name))\n```\n\nNote: `deleted` will be called when the key has been manually deleted,\nand `expired` when its expiration date has arrived.\n\nWhen setting a key that is already expired, `created` or `updated` will\nNOT be called, and `expired` will be called instead.\n\n## TypeScript\n\n`session-keystore` is written in TypeScript. You can tell a store about the keys it is supposed to hold:\n\n```ts\nimport SessionKeystore from 'session-keystore'\n\nconst store = new SessionKeystore\u003c'foo' | 'bar'\u003e()\n\nstore.get('foo') // ok\nstore.get('bar') // ok\nstore.get('egg') // Error: Argument of type '\"egg\"' is not assignable to parameter of type '\"foo\" | \"bar\"'\n```\n\nThis can be handy if you have multiple stores, to avoid accidental key leakage.\n\n## How it works\n\nHeavily inspired from the [Secure Session Storage](https://github.com/ProtonMail/proton-shared/blob/master/lib/helpers/secureSessionStorage.js#L7) implementation by [ProtonMail](https://protonmail.com),\nitself inspired from Thomas Frank's [SessionVars](https://www.thomasfrank.se/sessionvars.html).\n\nRead the [writeup article](https://francoisbest.com/posts/2019/how-to-store-e2ee-keys-in-the-browser) on [my blog](https://francoisbest.com/posts).\n\nFrom the ProtonMail documentation:\n\n\u003e However, we aim to deliberately be non-persistent. This is useful for\n\u003e data that wants to be preserved across refreshes, but is too sensitive\n\u003e to be safely written to disk. Unfortunately, although sessionStorage is\n\u003e deleted when a session ends, major browsers automatically write it\n\u003e to disk to enable a session recovery feature, so using sessionStorage\n\u003e alone is inappropriate.\n\u003e\n\u003e To achieve this, we do two tricks. The first trick is to delay writing\n\u003e any possibly persistent data until the user is actually leaving the\n\u003e page (onunload). This already prevents any persistence in the face of\n\u003e crashes, and severely limits the lifetime of any data in possibly\n\u003e persistent form on refresh.\n\u003e\n\u003e The second, more important trick is to split sensitive data between\n\u003e `window.name` and sessionStorage. `window.name` is a property that, like\n\u003e sessionStorage, is preserved across refresh and navigation within the\n\u003e same tab - however, it seems to never be stored persistently. This\n\u003e provides exactly the lifetime we want. Unfortunately, `window.name` is\n\u003e readable and transferable between domains, so any sensitive data stored\n\u003e in it would leak to random other websites.\n\u003e\n\u003e To avoid this leakage, we split sensitive data into two shares which\n\u003e xor to the sensitive information but which individually are completely\n\u003e random and give away nothing. One share is stored in `window.name`, while\n\u003e the other share is stored in sessionStorage. This construction provides\n\u003e security that is the best of both worlds - random websites can't read\n\u003e the data since they can't access sessionStorage, while disk inspections\n\u003e can't read the data since they can't access `window.name`. The lifetime\n\u003e of the data is therefore the smaller lifetime, that of `window.name`.\n\n## License\n\n[MIT](https://github.com/47ng/session-keystore/blob/master/LICENSE) - Made with ❤️ by [François Best](https://francoisbest.com)\n\nUsing this package at work ? [Sponsor me](https://github.com/sponsors/franky47) to help with support and maintenance.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F47ng%2Fsession-keystore","html_url":"https://awesome.ecosyste.ms/projects/github.com%2F47ng%2Fsession-keystore","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F47ng%2Fsession-keystore/lists"}