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

@sneekin/auth

v4.0.0

Published

One Sneek client for passwordless authentication and customer messages

Readme

@sneekin/auth

One server-side package for passwordless customer authentication and customer messages. Install it once and use the same application API key for both.

npm install @sneekin/auth

One client

import { Sneek } from '@sneekin/auth';

const sneek = new Sneek({
    apiUrl: process.env.SNEEK_API_URL ?? 'https://sneek.in',
    apiKey: process.env.SNEEK_API_KEY!,
});

// Sneek owns the challenge, the channel choice, and verification.
const request = await sneek.auth.requestVerification({ identifier });
const result = await sneek.auth.checkVerification({
    id: request.id,
    code,
});
// result: { status: 'verified', idToken, subject, claims } -- claims is the
// decoded, signature-verified ID token. Mint your own session from `claims`;
// Sneek never holds a session for you to introspect.

// Send an OTP generated by your application or any generic message.
await sneek.sendOTP({ to: mobile, code: '482917', appName: 'Acme' });
await sneek.messages.send({
    to: mobile,
    body: 'Your order has shipped.',
    type: 'transactional',
});

NestJS

There is no SneekAuthGuard (removed in v3) — Sneek never issues you a session to introspect on every request. Verify an identifier once, get back an ID token, and mint your own session from its claims; authenticating your app's subsequent requests is your own session mechanism, not Sneek's concern.

import { SneekAuthClient, SneekAuthModule } from '@sneekin/auth';

@Module({
    imports: [
        SneekAuthModule.forRoot({
            apiUrl: process.env.SNEEK_API_URL ?? 'https://sneek.in',
            apiKey: process.env.SNEEK_API_KEY!,
        }),
    ],
})
export class AuthModule {}

Complete browser login

Render SneekLogin from @sneekin/ui with endpoint="/api/auth/sneek". Import SneekLoginModule in the backend; do not add application QR controllers, registration tables, transport hooks, or login wrappers. The package facade owns browser binding and the QR strategy. The application owns its users, authorization, and session creation only.

import { Module } from '@nestjs/common';
import { SneekAuthClient, SneekAuthModule, SneekLoginModule } from '@sneekin/auth';

const clientModule = SneekAuthModule.forRoot({
        apiUrl: process.env.SNEEK_API_URL!,
        apiKey: process.env.SNEEK_API_KEY!,
});

@Module({
        imports: [
                SneekLoginModule.forRootAsync({
                        imports: [clientModule, SessionServicesModule],
                        inject: [SneekAuthClient, SessionService],
                        useFactory: (client: SneekAuthClient, sessions: SessionService) => ({
                                client,
                                origin: process.env.PUBLIC_URL!,
                                secret: process.env.SNEEK_LOGIN_SECRET!,
                                onVerified: (result) => sessions.signInWithSneek(result),
                        }),
                }),
        ],
})
export class LoginModule {}

SessionService and SessionServicesModule above are application services: export the service from that module, without importing LoginModule back into it. The async factory supports Nest imports, injection tokens, and optional factory dependencies. SneekLoginOptions is exactly:

interface SneekLoginOptions {
        client: SneekAuthClient;
        origin: string;
        secret: string;
        beforeRequest?: (identifier: string) => Promise<void>;
        onVerified: (result: VerifiedCheckResult) => Promise<unknown>;
}
`beforeRequest` is an optional application eligibility check. It receives the
trimmed identifier and is awaited before `client.requestVerification` for
identifier starts only. It does not choose or change channels and does not run
for QR creation, status, switching, or completion. Deny ineligible identifiers
with `SneekLoginError(403, 'access_denied', 'Staff only.')` to prevent a provider
verification request. Unexpected callback failures are sanitized to 503.

`onVerified` receives only `verified` results. The package handles
`profile_required` before this callback, returning a safe HTTP 428 recovery
response with a validated enrollment URL. The application must check local
authorization before issuing its session, including for QR identities that
never pass through `beforeRequest`. Its return value becomes the successful
response body; an authorization denial can throw the same `SneekLoginError`.
Since provider redemption is one-time, a callback failure requires a new
verification; neither redemption nor the callback is retried.

The default controller is the exported `SneekLoginController` at `auth/sneek`
with one `@Post()` action. `SneekLoginModule.forRootAsync({ path:
'admin/auth/sneek', imports, inject, useFactory })` creates a unique controller
at a bootstrap-configured path using the same validated handler. Public and
console modules can coexist with separate exact origins, callbacks, and
origin-bound cookies/proofs, even when they share a client and signing secret.
The path is not an incoming request field or an option returned by `useFactory`.
Set the consumer's Nest global prefix to `api`. Make these routes anonymous under the

consumer's session guard policy; the package still enforces Origin and proofs. No Fastify plugin is needed for binding cookies. The exported SneekLoginHandler.handle(request, reply) uses structural headers/body and header/code/send types, with no Fastify dependency.

Action contract

Every request is same-origin JSON POST to /api/auth/sneek, with credentials. Unknown fields/actions, absent or mismatched Origin, malformed IDs, and oversized inputs are rejected. No action accepts an application API key.

| Request | Response (HTTP 200) | | --- | --- | | {action:'config'} | {applicationId, origin, enabled} plus a binding cookie if needed | | {action:'create'} | {ref, nonce, issuedAt, expiresAt, bindingProof} | | {action:'status', ref, bindingProof} | {status:'pending'} or {status:'approved', requestId} | | {action:'verify', requestId, ref, bindingProof} | Application callback result after matching approved QR redemption | | {action:'request', identifier} | {requestId, channel, maskedIdentifier, expiresAt, alternatives, challengeNumber?, proof} | | {action:'verify', requestId, code?, proof} | Application callback result after owned OTP/PUSH verification | | {action:'switch', requestId, channel, proof} | Replacement request response with a new proof |

QR issuedAt is Unix seconds and expiresAt is Unix milliseconds; request expiresAt is an ISO timestamp capped to its proof's expiry. QR references are SHA256(Buffer.from(nonce, 'hex')), not the hash of the hex text. Only the server mints the 32-byte nonce and signed registration; no caller-provided nonce/reference registration exists. QR lookup uses the application-key-scoped partner approval endpoint and must match requestId before redemption.

Identifier verification supports sms, whatsapp, email, and push, never QR through an OTP proof. Sneek chooses the initial channel. Switching requires both the signed and current provider-offered alternatives. OTP requires a 4-12 digit string; PUSH requires out-of-band approval and no code. An unapproved owned request returns 425. enabled: false disables QR only; identifier requests still work and can establish their own binding cookie.

Errors are {code, message} with HTTP 400 (input/code), 401 (expired or consumed), 403 (origin/binding/channel), 425 (pending), 428 (profile required), 429 (provider rate limit), or 503 (unavailable). Provider internals are never forwarded. Expired/revoked/consumed QR approvals are indistinguishable from pending at the provider lookup; the local registration expires within 300s.

Security and lifecycle

  • Keep the API key and a cryptographically random secret of at least 32 bytes server-only. Use the same secret on every replica; rotating it invalidates all browser bindings and proofs.
  • Configure an exact HTTPS origin, without path/trailing slash/userinfo. HTTP is accepted only on loopback development hosts. Native requests without Origin must use the application's separate native endpoint, not this one.
  • The host-only __Host-sneek_login cookie is HttpOnly, Secure, SameSite=Strict, Path=/, and expires after one day. Loopback HTTP uses sneek_login without Secure. A valid binding is preserved across tabs and QR refreshes; it is not rotated or cleared after a single verification.
  • HS256 jose proofs bind purpose, browser HMAC, application, origin, current canonical configuration, issue time, and expiry. Their lifetime is at most 300 seconds and never exceeds the cookie or provider request expiry. Config changes invalidate existing proofs; a new request is required.
  • There is no package user-state cache, database, subscription, or background timer to clean up. Dropping a proof cancels that browser's attempt locally; server-side proof revocation before expiry is not provided. Sneek enforces approval expiry/cancellation and atomic one-time consumption.
  • Apply the consumer's normal ingress body-size/IP rate limits. Inputs and crypto work are bounded here, and provider request rate limits are preserved; this module does not introduce an in-memory per-user limiter.

Native OTP endpoint

For an existing application native code endpoint, call the package method directly:

const result = await sneekAuth.checkCodeVerification({ id: requestId, code });

CheckCodeVerificationInput has id: string, code: string, and optional channel: 'sms' | 'whatsapp' | 'email'. The method polls first and rejects wrong IDs, non-OTP or mismatched channels, terminal requests, and expiry before consuming. It cannot be used without a code. The lower-level checkVerification also supports QR/PUSH and must not be exposed as an unbound public browser verifier.

getQrConfig(): Promise<SneekQrConfig> reads GET /api/verify/qr/partner-config ({applicationId, clientId, issuer, origins, enabled}). getQrApproval(ref): Promise<SneekQrApproval> reads GET /api/verify/qr/approvals/:ref (pending or approved plus requestId). Both keep the API key on the server.

ID token validation

checkVerification and checkCodeVerification always verify the ES256 signature against the configured API's /oauth/jwks, require iss, aud, sub, iat, and exp, reject expired tokens, future issue times and empty subjects, and require the response subject to equal the verified claims.sub. This applies to OTP, PUSH and QR, including native code verification.

The API-key-authenticated partner config is authoritative: clientId is the expected audience (not applicationId), and issuer is the actual OIDC provider issuer derived from PUBLIC_URL. An internal apiUrl can differ from that public issuer. QR being disabled does not disable config discovery or identifier verification. Missing issuer/clientId or failed config discovery fails closed; no expected value is derived from the supplied token.

SneekAuthOptions (also inherited by SneekOptions) accepts optional expectedIssuer: string and expectedAudience: string. Existing construction with just apiUrl and apiKey remains valid. For public verifyIdToken(idToken) calls, supplying both trusted values skips config discovery; supplying neither or only one fetches the scoped config and checks any supplied value against it. These options must come from trusted server configuration, never from a token or request. Completion methods always fetch the API-key-scoped config and reject conflicting options, so forwarded-token settings cannot override application scope. Config is fetched on each such verification; JWKS caching remains managed by jose.

Deploy the provider contract before rolling out these auth 3.1.0 consumers. Older providers without clientId and issuer will fail closed. Rebuild any previously created package tarballs from the updated source before integration. The browser login action response remains {applicationId, origin, enabled}; these server verification fields do not alter the UI contract.

API Contract

  • POST /api/verify -> { id, status, channel, maskedIdentifier, expiresAt }
  • GET /api/verify/:id -> poll a pending (push/qr) verification
  • POST /api/verify/:id/check -> { status: 'verified', idToken, subject } once approved/coded correctly

All endpoints require the app's SNEEK_API_KEY; user access tokens are never used as app credentials.