New:Socket for Asana Is Now Available.Learn more
Get Started

@ultimat3/auth

Package Overview
Dependencies
Maintainers
1
Versions
26
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@ultimat3/auth - npm Package Compare versions

Comparing version
10.0.0
to
11.0.0
+18
-0
CLAUDE.md

@@ -101,2 +101,9 @@ # @ultimat3/auth — agent notes

`createAuthLimiter` can keep bounding itself; a limiter with no table declares nothing.
**What the seam RETAINS is one limiter per window, `As of 2026-08-23`** — a `Map` keyed by
`windowMs`, because the widest window is the only thing a purge reads. It was a list appended to
on every `installedAuthLimiter` call, two per `defineAuth`, trimmed by nothing: a process that
redefines auth (`x dev`'s reload, a test file, a host building one `Auth` per app) held every
limiter it ever built and the store behind each. `installedLimiterCount()` is the only
observation of that growth — a purge sweeps exactly one limiter however many are held — and it
is not exported from `src/index.ts`.

@@ -272,2 +279,13 @@ - **`normaliseEmail` is the ONE normalisation, it lives ABOVE the `AuthAdapter` seam, and no

rows, and the second was unreachable forever. `adapter-parity.test.ts` pins both halves.
**A NULL is not a value to either index, `As of 2026-08-23`**: a Postgres unique index is NULLS
DISTINCT, so `external_id` constrains only the rows that carry one — the check was
`!== undefined`, and `oauth-login.ts` binds `grants.externalId ?? null` for every first-time
OAuth user, so the SECOND such signup on a `MemoryAdapter` app failed with `X_AUTH_WRITE_FAILED`
against a constraint production does not have.
- **`MemoryAdapter` takes a `Clock`, and every instant it stamps comes from it** —
`new MemoryAdapter(clock)`, defaulting to `systemClock`, so the no-argument construction every
test already writes is unchanged. `takeVerification` stamped `consumedAt` with the record's
own `createdAt` — the moment the link was ISSUED — where `BuiltinAdapter` writes
`consumed_at = now()`, the moment it was REDEEMED: a redemption an hour later and one a second
later recorded the identical instant, and a frozen test clock could not move either.
- The new `AuthAdapter` members are OPTIONAL (`findUserByExternalId`, `listUsersByOrg`,

@@ -274,0 +292,0 @@ `deleteSessionsForUser`, `deleteSessionsForOrg`, `deleteSessionsCreatedBefore`). A required

+4
-4
{
"name": "@ultimat3/auth",
"version": "10.0.0",
"version": "11.0.0",
"description": "Sessions, passwords, OAuth, MFA and api keys — resolved to one Actor",

@@ -34,6 +34,6 @@ "license": "MIT",

"dependencies": {
"@ultimat3/core": "10.0.0",
"@ultimat3/db": "10.0.0",
"@ultimat3/schema": "10.0.0"
"@ultimat3/core": "11.0.0",
"@ultimat3/db": "11.0.0",
"@ultimat3/schema": "11.0.0"
}
}

@@ -448,3 +448,3 @@ # @ultimat3/auth 🔐

| `BuiltinAdapter` | Postgres via `@ultimat3/db`; takes an injected `DbClient` |
| `MemoryAdapter` | `x new` before a database exists, and every test in this package |
| `MemoryAdapter` | `x new` before a database exists, and every test in this package. `new MemoryAdapter(clock)` — `systemClock` by default — stamps every instant it writes |
| your own | implement `AuthAdapter`; DDL in `tables.ts` shows what the columns mean |

@@ -451,0 +451,0 @@

@@ -27,4 +27,16 @@ // Single responsibility: the ONE ambient install point for where failed credential attempts are

let factory: AuthLimiterFactory | undefined;
/** Every limiter this process built through the factory above, so a purge can reach them. */
let built: AuthLimiter[] = [];
/**
* The limiters a purge can still reach, ONE per distinct window.
*
* A list appended to per call is a leak with no ceiling: `installedAuthLimiter` runs twice per
* `defineAuth` — the account/IP bucket and the tenant bucket — and nothing trimmed it, so a
* process that redefines auth (`x dev`'s reload, a test file, a host building one `Auth` per app)
* held every limiter it ever built, and the store behind each, for its whole life.
*
* Keyed by `windowMs` because that is the only thing `purgeAuthLimits` reads: every limiter here
* writes the same two tables, so one per window is all a sweep can distinguish. The FIRST of a
* window is kept, which is the limiter the old list already swept — a second install clears the
* map, so nothing here outlives the pool it was built on either way.
*/
let built = new Map<number, AuthLimiter>();

@@ -41,3 +53,3 @@ /**

factory = next;
built = [];
built = new Map();
}

@@ -48,3 +60,3 @@

factory = undefined;
built = [];
built = new Map();
}

@@ -61,6 +73,16 @@

const limiter = factory(policy);
built.push(limiter);
if (!built.has(policy.windowMs)) built.set(policy.windowMs, limiter);
return limiter;
}
/**
* How many limiters a purge can still reach. Deliberately NOT exported from `src/index.ts`: it
* exists because unbounded retention has no other observation — `purgeAuthLimits` sweeps exactly
* one limiter however many were held, so no assertion about behaviour could see the growth. The
* same shape as `@ultimat3/realtime`'s `droppedChannelFrames`.
*/
export function installedLimiterCount(): number {
return built.size;
}
/** A limiter that keeps rows somebody else has to delete. The memory limiter sweeps itself. */

@@ -89,3 +111,3 @@ type PurgingAuthLimiter = AuthLimiter & { purgeExpired(): Promise<number> };

let widest: PurgingAuthLimiter | undefined;
for (const limiter of built) {
for (const limiter of built.values()) {
if (!canPurge(limiter)) continue;

@@ -92,0 +114,0 @@ if (widest === undefined || limiter.policy.windowMs > widest.policy.windowMs) widest = limiter;

@@ -5,2 +5,3 @@ // Single responsibility: an in-memory `AuthAdapter`. It is the driver `x new` uses before a

import { type Clock, systemClock } from '@ultimat3/core';
import type {

@@ -25,2 +26,3 @@ AuthAccount,

readonly name = 'memory';
readonly #clock: Clock;
readonly #users = new Map<string, AuthUser>();

@@ -33,2 +35,11 @@ readonly #sessions = new Map<string, AuthSession>();

/**
* The clock every instant this adapter stamps comes from — one argument, because a stamp is a
* fact about WHEN a call happened and a test that cannot move it can only assert a range.
* Defaults to `systemClock`, so `new MemoryAdapter()` is what it always was.
*/
constructor(clock: Clock = systemClock) {
this.#clock = clock;
}
/**
* Exact match, because `BuiltinAdapter` issues `where email = $1` against a plain `text ...

@@ -65,3 +76,13 @@ * unique` column and nothing folds case there. Normalising here instead made this the ONE

}
if (input.externalId !== undefined && existing.externalId === input.externalId) {
// `!= null` in one predicate, spelled out: a Postgres unique index is NULLS DISTINCT, so
// `external_id text unique` constrains only the rows that CARRY a value and admits
// unlimited NULLs. `!== undefined` alone made a second account with no external id collide
// with the first — and `oauth-login.ts` hands over `grants.externalId ?? null` for every
// first-time OAuth user, so the second such signup failed against a constraint production
// does not have.
if (
input.externalId !== undefined &&
input.externalId !== null &&
existing.externalId === input.externalId
) {
throw authUniqueViolation('createUser', 'x_users', 'external_id');

@@ -223,3 +244,6 @@ }

if (!timingSafeEqual(tokenHash, record.tokenHash)) return null;
const consumed: AuthVerification = { ...record, consumedAt: new Date(record.createdAt) };
// The moment it was REDEEMED, which is what `consumed_at = now()` writes on the Postgres
// side. This was `new Date(record.createdAt)` — the moment it was ISSUED — so every window
// measured from the stamp read a redemption as having happened at issue time.
const consumed: AuthVerification = { ...record, consumedAt: this.#clock.now() };
this.#verifications.set(key, consumed);

@@ -226,0 +250,0 @@ return consumed;