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

@freshpointcz/fresh-auth

v0.0.2

Published

HMAC service-to-service authentication, JWT verification, role definitions, and input validators for FreshPoint internal services

Readme

@freshpointcz/fresh-auth

Authentication and authorization utilities for FreshPoint internal services. Provides HMAC-based service-to-service communication, JWT verification, role definitions, and input validators — all in one package.

Table of Contents


Installation

npm install @freshpointcz/fresh-auth

Requires Node.js >= 20.


Core

constructHeadersForInternalComms

Builds the three HTTP headers needed to authenticate an outbound service-to-service request using HMAC. The apiKey is never transmitted — only the MAC derived from it.

import { constructHeadersForInternalComms } from "@freshpointcz/fresh-auth";

const headers = constructHeadersForInternalComms("payment-service", process.env.API_KEY);
// {
//   "x-service-name": "payment-service",
//   "x-timestamp":    "1716652800000",
//   "x-service-key":  "<sha512 HMAC hex digest>"
// }

Signature

function constructHeadersForInternalComms(
    serviceName: string,
    apiKey: string,
    options?: {
        now?: number;       // ms since epoch — inject in tests for deterministic output
        algorithm?: string; // default: "sha512"
        encoding?: "base64" | "base64url" | "hex" | "binary"; // default: "hex"
    }
): CallerAuthHeaders

Wire contract — the receiving service must:

  1. Read x-service-name and look up the shared apiKey registered for that service.
  2. Recompute HMAC(apiKey, "<serviceName>.<timestamp>") with the same algorithm and encoding.
  3. Compare the result against x-service-key using constant-time comparison (crypto.timingSafeEqual), never ===.
  4. Reject requests whose x-timestamp falls outside the acceptable replay window.

Example — with axios

await axios.get(`${authBase}/verify/access-token`, {
    headers: {
        ...constructHeadersForInternalComms(config.serviceName, apiKey),
        Authorization: `Bearer ${token}`,
    },
});

Verifier

Verifies JWT access tokens and incoming HMAC service keys against the identity server (fresh-identity). Delegates HTTP to an injected RequestStrategy so the class has no hard dependency on any HTTP client.

Constructor

import { Verifier, AxiosStrategy } from "@freshpointcz/fresh-auth";

const verifier = new Verifier(
    "payment-service",       // this service's name
    process.env.API_KEY,     // this service's HMAC secret
    new AxiosStrategy(axios),
    {
        authServerBaseUrl:        "http://fresh-identity:8080", // default
        tokenVerificationPath:    "/verify/access-token",       // default
        apiKeyVerificationPath:   "/verify/api-key",            // default
    }
);

verifyJWT(token)

Sends the token to the identity server and returns the authenticated user. Attaches HMAC headers automatically so the auth server can trust the call.

const user: AuthenticatedUser = await verifier.verifyJWT(token);

Throws:

  • UnauthorizedError — token missing, malformed, or expired (401)
  • ForbiddenError — service not authorized to call this endpoint (403)
  • AuthServiceError — any other non-2xx from the identity server

verifyApiKey(incomingHeaders)

Verifies that a set of x-service-* headers received from another service represent a valid HMAC signature. Forwards them as x-verify-* headers to the identity server alongside this service's own HMAC authentication.

const { serviceName } = await verifier.verifyApiKey({
    "x-service-name": req.headers["x-service-name"],
    "x-timestamp":    req.headers["x-timestamp"],
    "x-service-key":  req.headers["x-service-key"],
});

Throws:

  • UnauthorizedError — signature invalid or timestamp outside the acceptable window (401)
  • ForbiddenError — service not authorized to call this endpoint (403)
  • AuthServiceError — any other non-2xx from the identity server

Request Strategies

Abstract base and two built-in adapters for the HTTP client used by Verifier.

AxiosStrategy — wraps an axios instance, unwrapping the { data } envelope.

import axios from "axios";
import { AxiosStrategy } from "@freshpointcz/fresh-auth";

const strategy = new AxiosStrategy(axios);
// or with a custom instance:
const strategy = new AxiosStrategy(axiosInstance);

FetchStrategy — wraps any WHATWG fetch-compatible function: native fetch (Node 18+), node-fetch v3+, or undici.

import { FetchStrategy } from "@freshpointcz/fresh-auth";

const strategy = new FetchStrategy(fetch);

Custom strategy — extend RequestStrategy and implement a single method:

import { RequestStrategy } from "@freshpointcz/fresh-auth";

class MyStrategy extends RequestStrategy {
    async get<T>(url: string, headers: Record<string, string>): Promise<T> {
        // ...
    }
}

Errors

All errors extend AuthServiceError so you can catch the base class for a single handler or the subclasses for fine-grained control.

| Class | HTTP status | Default message | |---|---|---| | AuthServiceError | any (base) | — | | UnauthorizedError | 401 | "Token is invalid or expired" | | ForbiddenError | 403 | "Insufficient permissions" |

import { AuthServiceError, UnauthorizedError, ForbiddenError } from "@freshpointcz/fresh-auth";

try {
    const user = await verifier.verifyJWT(token);
} catch (err) {
    if (err instanceof UnauthorizedError) {
        // 401 — invalid/expired token
    } else if (err instanceof ForbiddenError) {
        // 403 — valid token but not allowed
    } else if (err instanceof AuthServiceError) {
        // other identity server error — err.status has the HTTP code
    }
}

All three classes correctly restore the prototype chain so instanceof works even when compiled to ES5.


Defaults

import { DEFAULT_ALGORITHM, DEFAULT_ENCODING } from "@freshpointcz/fresh-auth";

DEFAULT_ALGORITHM // "sha512"
DEFAULT_ENCODING  // "hex"

Both constructHeadersForInternalComms and the identity server use these defaults. Override only when both sides agree on a different value.


Types

AuthenticatedUser

Shape returned by Verifier.verifyJWT and transformUserToAuthenticatedUser.

interface AuthenticatedUser {
    id: number;
    uuid: string;
    email: string | null;
    firstName: string | null;
    middleName: string | null;
    lastName: string | null;
    phone: string | null;
    emailValidated: boolean;
    lastLoginAt: Date | null;
    companyId: number;
    roles: Role[];
}

Headers

CallerAuthHeaders — headers produced by constructHeadersForInternalComms and consumed by Verifier.verifyApiKey.

interface CallerAuthHeaders {
    "x-service-name": string;
    "x-timestamp":    string;
    "x-service-key":  string;
}

AuthHeadersToVerify — re-keyed version sent to the identity server when verifying an incoming service's identity. Used internally by Verifier.verifyApiKey.

interface AuthHeadersToVerify {
    "x-verify-service-name": string;
    "x-verify-timestamp":    string;
    "x-verify-service-key":  string;
}

Roles

Every user in the identity system has one or more roles. Roles are grouped by department.

Role — union of all role string literals.

type Role = UserRole | AdminRoleType | DepotRole | CustomerRole | OperationRole | SaleRole;

ALL_ROLES — flat readonly array of every valid role string, useful for runtime checks.

| Type | Constant | Values | |---|---|---| | AdminRoleType | ADMIN_ROLES | superadmin, admin, technician, developer | | DepotRole | DEPOT_ROLES | driver, picker, order-receiver, driver-receiver, stock-controller, inventory-clerk, eshop-handler, depot-master | | CustomerRole | CUSTOMER_ROLES | customer, ambassador | | OperationRole | OPERATION_ROLES | operation-specialist, depot-order-master | | SaleRole | SALES_ROLES | salesman, marketing-specialist, customer-care | | UserRole (deprecated) | USER_ROLES | user |

Role descriptions

  • superadmin — full access; GOD figure
  • admin — standard admin access
  • technician — technology team, e.g. kiosk-ui access
  • developer — technology team, e.g. kiosk-ui access
  • driver — can restock devices
  • picker — can perform picking operations
  • order-receiver — can accept new depot orders
  • driver-receiver — handles products returned from drivers
  • stock-controller — can do write-offs and add old products
  • inventory-clerk — can perform inventory counts
  • eshop-handler — handles e-shop orders
  • depot-master — total depot overlord; controls management units
  • customer — regular customer
  • ambassador — customer ambassador
  • operation-specialist — operations department
  • depot-order-master — operations department, depot order authority
  • salesman — sales department
  • marketing-specialist — marketing department
  • customer-care — customer care specialist

DecoratorSecurityType

Enum-like string union used to annotate route handler security requirements.

type DecoratorSecurityType = "basic" | "token" | "basic-identifier" | "api-key";

Utils

Hasher

Generates cryptographically random hex strings. Useful for generating API keys and secrets.

import { Hasher } from "@freshpointcz/fresh-auth";

Hasher.generate32HEX(); // 64-character hex string (32 random bytes)
Hasher.generate64HEX(); // 128-character hex string (64 random bytes)

transformUserToAuthenticatedUser

Maps a raw database row or upstream API response to a typed AuthenticatedUser. Accepts both snake_case (direct database result) and camelCase (pre-converted upstream) input. Unknown or invalid role strings are silently dropped.

import { transformUserToAuthenticatedUser } from "@freshpointcz/fresh-auth";

// From a database row (snake_case)
const user = transformUserToAuthenticatedUser({
    id: 1,
    uuid: "abc-123",
    email: "[email protected]",
    first_name: "John",
    middle_name: null,
    last_name: "Doe",
    phone: "+420731123456",
    email_validated: true,
    last_login_at: new Date(),
    company_id: 42,
    roles: [{ role: "admin" }, { role: "unknown-role" }], // "unknown-role" is dropped
});
// user.roles → ["admin"]

// From a camelCase source
const user2 = transformUserToAuthenticatedUser({
    id: 1,
    uuid: "abc-123",
    // ... camelCase fields
    roles: [{ role: "picker" }],
});

EmailValidator

Validates email addresses against RFC 5321 structural constraints (max 254 chars, must contain @ and a domain with a dot). Does not check deliverability.

import { EmailValidator } from "@freshpointcz/fresh-auth";

// Nullable field (optional)
EmailValidator.isEmailInputValid("[email protected]");  // true
EmailValidator.isEmailInputValid(null);                // true  — absent value is valid
EmailValidator.isEmailInputValid("");                  // true  — absent value is valid
EmailValidator.isEmailInputValid("notanemail");        // false
EmailValidator.isEmailInputValid(42);                  // false

// Required field
EmailValidator.isRequiredEmailInputValid("[email protected]"); // true
EmailValidator.isRequiredEmailInputValid(null);               // false — absent value is invalid
EmailValidator.isRequiredEmailInputValid("");                  // false

Input is lowercased before matching, mirroring the application-layer Joi schema.


PhoneValidator

Validates phone numbers against E.164 format: +<1-3 digit country code><subscriber number>, max 16 characters total.

import { PhoneValidator } from "@freshpointcz/fresh-auth";

// Nullable field (optional)
PhoneValidator.isPhoneInputValid("+420731123456"); // true
PhoneValidator.isPhoneInputValid(null);            // true  — absent value is valid
PhoneValidator.isPhoneInputValid("");              // true  — absent value is valid
PhoneValidator.isPhoneInputValid("0731123456");   // false — missing leading +
PhoneValidator.isPhoneInputValid("+0123456789");  // false — country code starts with 0

// Required field
PhoneValidator.isRequiredPhoneInputValid("+420731123456"); // true
PhoneValidator.isRequiredPhoneInputValid(null);            // false
PhoneValidator.isRequiredPhoneInputValid("");              // false

IdentifierValidator

Validates and optionally normalises EAN-13 barcodes and RFID-10 identifiers. Short inputs are padded with leading zeros; inputs with recoverable leading zeros are trimmed. Inputs that are too long without leading zeros throw.

import { IdentifierValidator } from "@freshpointcz/fresh-auth";

// EAN-13 — validate only (coerce: false)
IdentifierValidator.isValidEAN("123", false);              // true  (padded: "0000000000123")
IdentifierValidator.isValidEAN("99999999999999", false);   // false (14 digits, no leading zero)
IdentifierValidator.isValidEAN(null, false);               // false

// EAN-13 — validate and return normalised string (coerce: true)
IdentifierValidator.isValidEAN("123", true);               // "0000000000123"
IdentifierValidator.isValidEAN("00123456789012", true);    // "0123456789012" (trimmed + padded)

// RFID-10 — same API, 10-character target length
IdentifierValidator.isValidRFID("123", false);             // true
IdentifierValidator.isValidRFID("123", true);              // "0000000123"
IdentifierValidator.isValidRFID("", false);                // false

MINIMAL_CHAR_LENGTH = 1 — empty strings are always rejected.


isValidRole

Type guard that checks whether a string is a valid Role. Can be scoped to a specific role array for narrowed type inference.

import { isValidRole, DEPOT_ROLES } from "@freshpointcz/fresh-auth";

// Global check — narrows to Role
isValidRole("picker");   // true
isValidRole("foo");      // false

// Scoped check — narrows to DepotRole
isValidRole("picker", DEPOT_ROLES);    // true
isValidRole("customer", DEPOT_ROLES);  // false — customer is not a DepotRole

// Typical use: filtering raw role strings from the database
const roles = rawRoles.map(r => r.role).filter(isValidRole);
// roles: Role[]