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 🙏

© 2026 – Pkg Stats / Ryan Hefner

@use-stall/pin

v0.0.3

Published

Local Pin Authentication Ideally for POS Systems to be used with other authentication methods

Readme

Pin (WIP)

Overview

Pin is a lightweight, local-first cryptographic authentication helper designed for environments where offline login via PIN is required like POS system It supports:

  • HMAC-based PIN hashing and verification
  • Envelope encryption/decryption using a device key for data at rest in IndexedDB
  • Secure client-side storage using IndexedDB (via Dexie.js)
  • Configurable setup with organization ID, salt, and storage name

This package works both in the browser and on the server (with Web Crypto API support). The deviceKey is used for encrypting and decrypting data stored locally in the browser's IndexedDB, which is necessary for the verifyPin method when operating on the client-side with stored data. The encryptPin and isPinUnique methods do not require a deviceKey.

Use this with other authentication methods


Installation

install using npm, bun or any package manager:

bun i @use-stall/pin

then

import { PinAuth } from "@use-stall/pin";

Interfaces

export interface PinAuthConfig {
  orgId: string;
  salt?: Uint8Array;
  deviceKeyRaw?: Uint8Array;
  deviceKeyString?: string;
  localDbName?: string;
}

export interface AuthObject {
  h: string; // HMAC hash
  s: string; // Salt used for HMAC
}

export interface AuthDataType {
  auth: AuthObject;
  [key: string]: any;
}

Constructor

new PinAuth(config: PinAuthConfig)
  • orgId – Unique string per organization (used as PBKDF2 password input)
  • salt – Optional salt (random 16-byte array will be generated if not provided for encryptPin)
  • deviceKeyRaw – Optional AES key as a raw Uint8Array (takes precedence over string) for client-side data encryption.
  • deviceKeyString – Optional device key string (e.g., a Firestore document ID) for client-side data encryption. If provided, it will be automatically set and used.
  • localDbName – Optional name for IndexedDB database (defaults to pin-auth-store)

Note: deviceKey (via deviceKeyString or deviceKeyRaw) must be provided at instantiation time if you intend to use methods that store or retrieve encrypted data from IndexedDB (addPinAuthData, updatePinAuthData, getDecryptedPinAuthDataById, getAllDecryptedPinAuthData, and verifyPin when used on client-side with local storage).


Methods

PIN Management (Server-Side or Admin Flow for generating AuthObject)

encryptPin(pin: string): Promise<{ auth: { h: string, s: string } }>

  • Hashes a plain PIN using HMAC (derived from orgId and the instance's salt).
  • Returns an object with an auth property containing:
    • h: base64 HMAC hash.
    • s: base64-encoded salt (the instance's salt used for this encryption).
  • This method does not use the deviceKey.

Local Data Storage and Retrieval (Client-Side, uses deviceKey)

addPinAuthData(data: AuthDataType[]): Promise<void>

  • Encrypts (using deviceKey) and stores an array of user data to local IndexedDB.
  • Requires a deviceKey to be set on the PinAuth instance.
  • Each object in the data array must be an AuthDataType, which includes an auth object (typically generated by encryptPin).

updatePinAuthData(data: AuthDataType[]): Promise<void>

  • Encrypts (using deviceKey) and updates existing user data in local IndexedDB.
  • Behaves like addPinAuthData (uses bulkPut which adds or overwrites).
  • Requires a deviceKey.

getDecryptedPinAuthDataById(id: string): Promise<AuthDataType | null>

  • Retrieves a specific user's data record by id from IndexedDB.
  • Decrypts the data using the deviceKey.
  • Returns the decrypted AuthDataType object (which includes the auth object) or null if not found or if decryption fails.
  • Requires a deviceKey.

getAllDecryptedPinAuthData(): Promise<AuthDataType[]>

  • Retrieves all user data records from IndexedDB.
  • Decrypts each record using the deviceKey.
  • Returns an array of decrypted AuthDataType objects.
  • Requires a deviceKey.

clearPinAuthData(): Promise<void>

  • Clears all stored PIN auth data from IndexedDB.

PIN Verification (Client-Side with local storage)

verifyPin(pin: string): Promise<AuthDataType | null>

  • Verifies a plain pin against all locally stored and encrypted user data.
  • Internally, this method calls getAllDecryptedPinAuthData() to fetch and decrypt all records using the deviceKey.
  • It then iterates through each decrypted record, deriving an HMAC key using this.orgId and the record's auth.s (salt).
  • It computes the HMAC of the input pin and compares it to the record's auth.h (hash).
  • If a match is found with any record, it returns the user data part of that record (excluding the auth property itself). Otherwise, returns null.
  • This method requires the deviceKey to be initialized on the PinAuth instance to decrypt the stored data.

PIN Uniqueness Check (Server-Side or Admin Flow)

isPinUnique(pin: string, existingAuthObjects: AuthObject[]): Promise<boolean>

  • Checks if a given pin is unique among an array of existingAuthObjects.
  • Each object in existingAuthObjects must be an AuthObject (containing h and s).
  • For each AuthObject, this method derives an HMAC key using this.orgId and AuthObject.s, then signs the input pin and compares it to AuthObject.h.
  • Returns false if the pin matches any of the AuthObjects in the array (i.e., the PIN is already in use).
  • Returns true if no match is found (i.e., the PIN is unique relative to the provided list).
  • This method does not interact with IndexedDB or use the deviceKey. It's intended for contexts where you have a collection of AuthObjects (e.g., from a central database) and want to check if a new PIN conflicts.

Example Usage

On the Server (Admin Setup - Generating Auth Objects)

const authServer = new PinAuth({ orgId: "org_123" }); // No deviceKey needed here
const { auth: authObjectForUser1 } = await authServer.encryptPin("123456");

// User data to be sent to client (e.g., via API)
const userDataForClient = {
  id: "user1",
  name: "Anna",
  // ... other user details
  auth: authObjectForUser1, // Contains h and s
};
// Send userDataForClient to the client device

On the Client (POS Device Setup - Storing Encrypted Data)

const deviceKey = "abc123firestoreDocId"; // Unique key for this device
const authClient = new PinAuth({
  orgId: "org_123",
  deviceKeyString: deviceKey,
});

// Assume userDataFromServer is received from the server
// const userDataFromServer = { id: 'user1', name: 'Anna', auth: { h: '...', s: '...' } };
// const anotherUserDataFromServer = { id: 'user2', name: 'Ben', auth: { h: '...', s: '...' } };

// Store user data locally (encrypted with deviceKey)
await authClient.addPinAuthData([
  userDataFromServer,
  anotherUserDataFromServer,
]);

Verifying PIN Locally (Client-Side)

// The user enters their PIN, e.g., '123456'
const pinAttempt = "123456";

// authClient is an instance of PinAuth with orgId and deviceKey configured
const matchedUser = await authClient.verifyPin(pinAttempt);

if (matchedUser) {
  console.log("Logged in as:", matchedUser.name);
  // matchedUser contains { id: 'user1', name: 'Anna', ... } (without the 'auth' property)
} else {
  console.log("Invalid PIN or user not found.");
}

Checking PIN Uniqueness (Server-Side/Admin)

// Assume authServer is an instance of PinAuth configured with the correct orgId
// (deviceKey is not needed for this operation)
const authServer = new PinAuth({ orgId: "org_123" });

// Assume `allUserAuthObjects` is an array of AuthObject items fetched from your central user database
// e.g., allUserAuthObjects = [ { h: "hash1", s: "salt1" }, { h: "hash2", s: "salt2" }, ... ];
const allUserAuthObjectsFromDb = [
  /* ... load AuthObjects from your database ... */
];
const newPinCandidate = "newSecurePin123";

const isUnique = await authServer.isPinUnique(
  newPinCandidate,
  allUserAuthObjectsFromDb
);

if (isUnique) {
  console.log(`PIN "${newPinCandidate}" is unique and can be assigned.`);
  // Proceed to encrypt this new PIN and save it for the user
  const { auth: newAuthObject } = await authServer.encryptPin(newPinCandidate);
  // ... save newAuthObject for the user in your central database ...
} else {
  console.log(
    `PIN "${newPinCandidate}" is already in use. Please choose another.`
  );
}

Updating Local Data (Client-Side)

// Assume updatedUserDataArray contains AuthDataType objects with potentially new non-auth fields
// or new auth objects if PINs were changed server-side.
// const updatedUserDataArray = [ { id: 'user1', name: 'Anna Smith', auth: {h:'...', s:'...'} } ];
await authClient.updatePinAuthData(updatedUserDataArray);

Clearing Local Data (Client-Side)

await authClient.clearPinAuthData();

Security Notes

  • PBKDF2 with HMAC-SHA256 is used to derive the HMAC key for PIN hashing (encryptPin) and verification (verifyPin, isPinUnique). The orgId acts as the password, and the salt (either instance-wide for encryptPin or user-specific from auth.s for verifyPin/isPinUnique) is used in this derivation.
  • The instance salt (used by encryptPin and stored in auth.s) should ideally be unique per organization or deployment if generated randomly. If a fixed salt is provided in config, ensure it's cryptographically strong.
  • deviceKey (if used for client-side storage) is for envelope encryption of user data at rest using AES-GCM. Strings are UTF-8 encoded and padded to 32 bytes if used as deviceKeyString. A new IV is used for every encryption operation.
  • IndexedDB is used for client-side storage. Ensure the environment where this runs is secure.
  • The auth object (h and s) is crucial for PIN verification. h is the HMAC of the PIN, and s is the salt used in generating that HMAC.

License

MIT