npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

session-keystore

v1.0.3

Published

Secure cryptographic key storage in the browser

Downloads

351

Readme

NPM MIT License Continuous Integration Coverage Status Dependabot Status

Secure cryptographic key storage in the browser and Node.js

Ideal to store keys derived from user credentials (username/password) in E2EE applications.

Features

  • In-memory storage: no clear-text persistance to disk
  • Session-bound: cleared when closing tab/window (browser-only)
  • Survives hard-reloads of the page (browser-only)
  • Optional expiration dates
  • Event emitter API for key CRUD operations

Installation

$ yarn add session-keystore
# or
$ npm i session-keystore

Usage

import SessionKeystore from 'session-keystore'

// Create a store
const store = new SessionKeystore()

// You can create multiple stores, but give them a unique name:
// (default name is 'default')
const otherStore = new SessionKeystore({ name: 'other' })

// Save a session-bound key
store.set('foo', 'supersecret')

// Set an expiration date (Date or timestamp in ms)
store.set('bar', 'supersecret', Date.now() + 1000 * 60 * 5) // 5 minutes

// Retrieve the key
const key = store.get('bar')
// key will be null if it has expired

// Revoke a single key
store.delete('foo')

// Clear all keys in storage
store.clear()

CRUD Event Emitter

Event types:

  • created
  • read
  • updated
  • deleted
  • expired

Listen to events on a keystore with the on method:

import SessionKeystore from 'session-keystore'

const store = new SessionKeystore()
store.on('created', ({ name }) => console.log('Key created: ', name))
store.on('updated', ({ name }) => console.log('Key updated: ', name))
store.on('deleted', ({ name }) => console.log('Key deleted: ', name))
store.on('expired', ({ name }) => console.log('Key expired: ', name))
store.on('read', ({ name }) => console.log('Key accessed: ', name))

Note: deleted will be called when the key has been manually deleted, and expired when its expiration date has arrived.

When setting a key that is already expired, created or updated will NOT be called, and expired will be called instead.

TypeScript

session-keystore is written in TypeScript. You can tell a store about the keys it is supposed to hold:

import SessionKeystore from 'session-keystore'

const store = new SessionKeystore<'foo' | 'bar'>()

store.get('foo') // ok
store.get('bar') // ok
store.get('egg') // Error: Argument of type '"egg"' is not assignable to parameter of type '"foo" | "bar"'

This can be handy if you have multiple stores, to avoid accidental key leakage.

How it works

Heavily inspired from the Secure Session Storage implementation by ProtonMail, itself inspired from Thomas Frank's SessionVars.

Read the writeup article on my blog.

From the ProtonMail documentation:

However, we aim to deliberately be non-persistent. This is useful for data that wants to be preserved across refreshes, but is too sensitive to be safely written to disk. Unfortunately, although sessionStorage is deleted when a session ends, major browsers automatically write it to disk to enable a session recovery feature, so using sessionStorage alone is inappropriate.

To achieve this, we do two tricks. The first trick is to delay writing any possibly persistent data until the user is actually leaving the page (onunload). This already prevents any persistence in the face of crashes, and severely limits the lifetime of any data in possibly persistent form on refresh.

The second, more important trick is to split sensitive data between window.name and sessionStorage. window.name is a property that, like sessionStorage, is preserved across refresh and navigation within the same tab - however, it seems to never be stored persistently. This provides exactly the lifetime we want. Unfortunately, window.name is readable and transferable between domains, so any sensitive data stored in it would leak to random other websites.

To avoid this leakage, we split sensitive data into two shares which xor to the sensitive information but which individually are completely random and give away nothing. One share is stored in window.name, while the other share is stored in sessionStorage. This construction provides security that is the best of both worlds - random websites can't read the data since they can't access sessionStorage, while disk inspections can't read the data since they can't access window.name. The lifetime of the data is therefore the smaller lifetime, that of window.name.

License

MIT - Made with ❤️ by François Best - Donations welcome 🙏