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

@elcrm/jwt

v0.1.1

Published

AES-GCM ssid tokens for elCRM: createJWT, multi-session, oneToken, validateAgent, refresh.

Readme

@elcrm/jwt

Зашифрованные ssid-токены для elCRM под Bun: AES-256-GCM, алиасы, TTL, validateAgent, oneToken, multi-session (sessions + _sid), refresh.

npm version license

Установка

bun add @elcrm/jwt

Требования: Bun ≥ 1.0. Пакет отдаёт TypeScript-исходники (src/lib) — отдельная сборка у потребителя не нужна.

Это зашифрованный payload (AES-GCM), а не классический подписанный JWT (JWS). Секрет: secret или process.env.TOKEN.

Разработка и публикация: dev.md, deploy.md.

Быстрый старт

import { createJWT } from "@elcrm/jwt";

const jwt = createJWT({
    secret: "your-secret-key",
    alias: [
        ["i", "id"],
        ["j", "job"],
    ],
    expiresIn: 3600,
    validateAgent: true,
    oneToken: true,
    tokenFilePath: "jwt.json",
    excludeRoutes: ["login", "register"], // для сервера через getConfig()
});

const token = await jwt.create({ id: 123, job: "developer" });
const data = await jwt.parse(token);

Создание экземпляра

const jwt = createJWT({
    secret: "your-secret-key",
    alias: [
        ["i", "id"],
        ["j", "job"],
    ],
    expiresIn: 3600,
    validateAgent: true,
    oneToken: true,
    tokenFilePath: "jwt.json",
});

// Или сразу создать токен: createJWT(options, data) → Promise<string>
const token = await createJWT({ secret: "key" }, { id: 1 });

Создание токена

const token = await jwt.create({ id: 123, job: "developer" });

const headers = {
    get: (name: string) => {
        if (name === "x-forwarded-for") return "192.168.1.1";
        if (name === "user-agent") return "Mozilla/5.0";
        return null;
    },
};

const token2 = await jwt.create(
    { id: 456 },
    {
        expiresIn: 7200,
        headers,
    },
);

// setHeaders — безопасная изоляция заголовков на запрос
const jwtWithHeaders = jwt.setHeaders(headers);
const token3 = await jwtWithHeaders.create({ id: 789 });

Парсинг и init

const data = await jwt.parse(token);
if (data === false) {
    // невалиден / истёк / IP/UA / oneToken
} else {
    console.log(data);
}

const result = await jwt.init(headers);
if (result.error) {
    // jwt_token_not_found | jwt_token_invalid (+ code: 401)
} else {
    console.log(result);
}

// requireAdmin: user.admin === 1
const admin = await jwt.init(headers, undefined, true);

Multi-session (sessions)

import { createJWT, createMemorySessionStore, createFileSessionStore } from "@elcrm/jwt";

const jwt = createJWT({
  secret: "…",
  sessions: true, // или createMemorySessionStore() / createFileSessionStore("sessions.json") / свой store
});

const token = await jwt.create({ id: 1 });
const user = await jwt.parse(token); // user.sid — id сессии
await jwt.listSessions("1");
await jwt.revokeSession(user.sid);
await jwt.revokeOthers("1", user.sid);
await jwt.revokeAll("1");

oneToken: true + sessions = максимум одна активная сессия (при create остальные отзываются). Без sessions поведение oneToken через файл _ct сохранено (legacy).

Параметры createJWT

| Опция | По умолчанию | Описание | | --------------- | ------------ | ----------------------------------------------------- | | secret | TOKEN env | Секрет AES (обязателен, если нет env) | | alias | [] | [["short", "full"], …] — только поля с алиасами | | expiresIn | null | TTL в секундах; null — без срока | | headerName | "ssid" | Заголовок для init() | | validateAgent | false | Сохранять/проверять IP и User-Agent | | oneToken | false | Один активный токен на id (файл + поле _ct) | | tokenFilePath | "jwt.json" | Файл для oneToken | | excludeRoutes | [] | Список роутов без JWT — читается через getConfig() | | wirePrefix | "" | Wire-формат {prefix}.{aes}; create/parse/init/refresh |

Опции algorithm / ivLength / saltLength / tagLength / keyLength — низкоуровневые; шифрование всегда через AES-GCM Web Crypto.

API экземпляра

| Метод | Назначение | | ---------------- | ----------------------------------------------- | | create | Создать токен | | parse | Расшифровать; false при ошибке | | init | Токен из заголовка headerName | | refresh | Новый токен из (в т.ч. истёкшего) | | setHeaders | Новый экземпляр с привязанными заголовками | | deleteToken | Удалить запись пользователя из oneToken-файла | | setSecret | Сменить секрет | | setExpiresIn | Сменить TTL по умолчанию | | setAlias / addAlias | Алиасы | | getConfig | Текущая конфигурация (в т.ч. excludeRoutes, wirePrefix) | | password | SHA-256 hex (Bun.CryptoHasher) | | generateSalt | Случайная соль (crypto.getRandomValues) | | hash | Hex по кодам символов (для agent) | | agent | Отпечаток IP+UA |

Хелперы (multi-tenant auth — разные секреты на slug):

| Функция | Назначение | | ------------------ | ----------------------------------------------- | | packWireToken | prefix + aes → prefix.aes | | unpackWireToken | разобрать wire; null если формат неверный |

Wire-префикс (tenant slug)

// Один тенант / один секрет — префикс на инстансе
const jwt = createJWT({
    secret: tenantSecret,
    wirePrefix: "wms-by9cqxaw",
});
const token = await jwt.create({ id: "u1" }); // "wms-by9cqxaw.<aes>"
await jwt.parse(token);

// Auth (много тенантов): префикс при create, секрет — после unpack
import { packWireToken, unpackWireToken } from "@elcrm/jwt";

const aes = await jwtBare.create({ id: "u1" });
const ssid = packWireToken(slug, aes);

const { prefix, token } = unpackWireToken(ssid)!;
const secret = await secretBySlug(prefix);
await jwtFor(secret).parse(token);

create / parse / init / refresh

jwt.create(data, {
    customSecret?: string,
    expiresIn?: number | null,
    headers?: RequestHeaders | RequestLike,
});

jwt.parse(token, customSecret?, allowExpired?, reqOrHeaders?);

jwt.init(headers, customSecret?, requireAdmin?, allowExpired?);

jwt.refresh(expiredToken, newExpiresIn?, customSecret?, reqOrHeaders?);

Примеры

Алиасы

const jwt = createJWT({
    secret: "key",
    alias: [
        ["i", "id"],
        ["j", "job"],
    ],
});

const token = await jwt.create({ id: 123, job: "dev" });
const data = await jwt.parse(token); // { id: 123, job: "dev" }

TTL и refresh

const jwt = createJWT({ secret: "key", expiresIn: 3600 });
const token = await jwt.create({ id: 123 });

const newToken = await jwt.refresh(token, 7200);

validateAgent + setHeaders

const jwt = createJWT({ secret: "key", validateAgent: true });
const jwtReq = jwt.setHeaders(headers);
const token = await jwtReq.create({ id: 123 });
const user = await jwtReq.init(headers);

oneToken

При oneToken: true новый токен для того же id инвалидирует предыдущий. Нужно поле id (или алиас на id). Данные: кэш в памяти + файл tokenFilePath.

const jwt = createJWT({ secret: "key", oneToken: true });
const t1 = await jwt.create({ id: 123 });
const t2 = await jwt.create({ id: 123 });
await jwt.parse(t1); // false
await jwt.parse(t2); // { id: 123 }

await jwt.deleteToken("123"); // выход / сброс сессии

excludeRoutes на сервере

Конфиг хранится в JWT; проверка маршрута — на стороне приложения:

const { excludeRoutes } = config.jwt.getConfig();
const skipAuth = excludeRoutes.includes(path[0]);

Список лучше кэшировать при старте (Set), не вызывать getConfig() на каждый запрос.

Лицензия

MIT