{"id":49221818,"url":"https://github.com/neuxdotdev/libts-csrfx-auth","last_synced_at":"2026-04-24T04:05:08.194Z","repository":{"id":350508576,"uuid":"1205226187","full_name":"neuxdotdev/libts-csrfx-auth","owner":"neuxdotdev","description":"a libary to handler auth for web sessions, CSRF tokens, and cookie persistence.","archived":false,"fork":false,"pushed_at":"2026-04-10T17:07:26.000Z","size":425,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-04-10T18:29:06.164Z","etag":null,"topics":["auth","crf-token","handler","lib","login","token","ts"],"latest_commit_sha":null,"homepage":"https://neuxdotdev.github.io/libts-csrfx-auth/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"agpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/neuxdotdev.png","metadata":{"files":{"readme":"readme.md","changelog":"changelog.md","contributing":"contributing.md","funding":null,"license":"license","code_of_conduct":"code_of_conduct.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-08T18:59:16.000Z","updated_at":"2026-04-10T17:07:29.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/neuxdotdev/libts-csrfx-auth","commit_stats":null,"previous_names":["neuxdotdev/libts-csrfx-auth"],"tags_count":5,"template":false,"template_full_name":null,"purl":"pkg:github/neuxdotdev/libts-csrfx-auth","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neuxdotdev%2Flibts-csrfx-auth","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neuxdotdev%2Flibts-csrfx-auth/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neuxdotdev%2Flibts-csrfx-auth/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neuxdotdev%2Flibts-csrfx-auth/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/neuxdotdev","download_url":"https://codeload.github.com/neuxdotdev/libts-csrfx-auth/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neuxdotdev%2Flibts-csrfx-auth/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32208487,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-24T03:15:14.334Z","status":"ssl_error","status_checked_at":"2026-04-24T03:15:11.608Z","response_time":64,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["auth","crf-token","handler","lib","login","token","ts"],"created_at":"2026-04-24T04:04:58.022Z","updated_at":"2026-04-24T04:05:08.179Z","avatar_url":"https://github.com/neuxdotdev.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# libts-csrfx-auth\n\n\u003e **Minimal TypeScript auth handler for web sessions, CSRF tokens, and cookie persistence — inspired by Rust's cekunit-client.**\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://www.npmjs.com/package/libts-csrfx-auth\" target=\"_blank\" rel=\"noopener\"\u003e\n    \u003cimg src=\"https://img.shields.io/npm/v/libts-csrfx-auth.svg?style=flat-square\u0026logo=npm\u0026logoColor=white\u0026color=cb3837\" alt=\"npm version\" height=\"20\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://github.com/neuxdotdev/libts-csrfx-auth/blob/main/license\" target=\"_blank\" rel=\"noopener\"\u003e\n    \u003cimg src=\"https://img.shields.io/github/license/neuxdotdev/libts-csrfx-auth?style=flat-square\u0026color=blue\" alt=\"license\" height=\"20\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://github.com/neuxdotdev/libts-csrfx-auth/actions/workflows/build.yml\" target=\"_blank\" rel=\"noopener\"\u003e\n    \u003cimg src=\"https://github.com/neuxdotdev/libts-csrfx-auth/actions/workflows/build.yml/badge.svg?branch=main\u0026style=flat-square\" alt=\"build status\" height=\"20\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://github.com/neuxdotdev/libts-csrfx-auth/actions\" target=\"_blank\" rel=\"noopener\"\u003e\n    \u003cimg src=\"https://img.shields.io/github/actions/workflow/status/neuxdotdev/libts-csrfx-auth/build.yml?branch=main\u0026style=flat-square\u0026logo=github\u0026logoColor=white\" alt=\"CI\" height=\"20\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://www.typescriptlang.org\" target=\"_blank\" rel=\"noopener\"\u003e\n    \u003cimg src=\"https://img.shields.io/badge/TypeScript-5.8+-3178C6?style=flat-square\u0026logo=typescript\u0026logoColor=white\" alt=\"TypeScript\" height=\"20\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://bun.sh\" target=\"_blank\" rel=\"noopener\"\u003e\n    \u003cimg src=\"https://img.shields.io/badge/Bun-1.3+-fbf0df?style=flat-square\u0026logo=bun\u0026logoColor=000\" alt=\"Bun\" height=\"20\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://nodejs.org\" target=\"_blank\" rel=\"noopener\"\u003e\n    \u003cimg src=\"https://img.shields.io/badge/Node.js-≥18-339933?style=flat-square\u0026logo=nodedotjs\u0026logoColor=white\" alt=\"Node.js\" height=\"20\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://prettier.io\" target=\"_blank\" rel=\"noopener\"\u003e\n    \u003cimg src=\"https://img.shields.io/badge/code_style-prettier-ff69b4?style=flat-square\u0026logo=prettier\u0026logoColor=white\" alt=\"code style\" height=\"20\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"http://makeapullrequest.com\" target=\"_blank\" rel=\"noopener\"\u003e\n    \u003cimg src=\"https://img.shields.io/badge/PRs-welcome-brightgreen?style=flat-square\" alt=\"PRs welcome\" height=\"20\"\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://www.npmjs.com/package/libts-csrfx-auth\" target=\"_blank\" rel=\"noopener\"\u003e\n    \u003cimg src=\"https://img.shields.io/npm/dm/libts-csrfx-auth?style=flat-square\u0026logo=npm\u0026color=cb3837\" alt=\"npm downloads\" height=\"18\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://bundlephobia.com/package/libts-csrfx-auth\" target=\"_blank\" rel=\"noopener\"\u003e\n    \u003cimg src=\"https://img.shields.io/bundlephobia/minzip/libts-csrfx-auth?style=flat-square\u0026label=minzipped\u0026color=orange\" alt=\"bundle size\" height=\"18\"\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\n---\n\n## Table of Contents\n\n- [Overview](#overview)\n- [Installation](#installation)\n- [Quick Start](#quick-start)\n- [Environment Variables](#environment-variables)\n- [Core Concepts](#core-concepts)\n    - [CSRF‑Protected Login Flow](#csrf‑protected-login-flow)\n    - [Session Caching](#session-caching)\n    - [Retry \u0026 Timeout Mechanism](#retry--timeout-mechanism)\n- [Library API](#library-api)\n    - [`AuthClient`](#authclient)\n        - [Constructor Options (`LoginOptions`)](#constructor-options-loginoptions)\n        - [Methods](#authclient-methods)\n        - [Properties](#authclient-properties)\n    - [`LogoutClient`](#logoutclient)\n        - [Constructor Options (`LogoutOptions`)](#constructor-options-logoutoptions)\n        - [Methods](#logoutclient-methods)\n        - [Properties](#logoutclient-properties)\n    - [`EnvConfig` (Configuration)](#envconfig-configuration)\n        - [Static Factory](#static-factory)\n        - [Instance Properties \u0026 Methods](#instance-properties--methods)\n    - [`CacheManager` (Session Persistence)](#cachemanager-session-persistence)\n        - [Constructor](#constructor)\n        - [Methods](#cachemanager-methods)\n        - [Properties](#cachemanager-properties)\n    - [Utility Functions](#utility-functions)\n        - [Cookie Utilities](#cookie-utilities)\n        - [CSRF Extraction](#csrf-extraction)\n        - [HTTP Client with Retry](#http-client-with-retry)\n        - [Session Helpers](#session-helpers)\n- [Error Handling](#error-handling)\n    - [`AuthError` Class](#autherror-class)\n    - [Error Codes (`AuthErrorCode`)](#error-codes-autherrorcode)\n    - [Retryable vs Authentication Errors](#retryable-vs-authentication-errors)\n    - [Built‑in Guards](#builtin-guards)\n- [Advanced Usage](#advanced-usage)\n    - [Custom Cache Directory](#custom-cache-directory)\n    - [Overriding Headers (Referer/Origin)](#overriding-headers-refererorigin)\n    - [Manual CSRF Token Management](#manual-csrf-token-management)\n    - [Using with Node.js (without Bun)](#using-with-nodejs-without-bun)\n- [Development](#development)\n    - [Project Structure](#project-structure)\n    - [Scripts](#scripts)\n    - [Testing](#testing)\n    - [Documentation Generation](#documentation-generation)\n    - [Release Process](#release-process)\n- [Troubleshooting](#troubleshooting)\n    - [Common Errors](#common-errors)\n    - [Debugging Tips](#debugging-tips)\n- [Contributing](#contributing)\n- [License](#license)\n\n---\n\n## Overview\n\n`libts-csrfx-auth` is a **TypeScript‑first** library that automates authentication against web applications protected by **CSRF tokens**. It handles the complete two‑step login flow (GET login page → extract token → POST credentials), persists session data (cookies + token) to disk, and supports logout with automatic cache cleanup.\n\n---\n\n## Installation\n\n```bash\n# Bun (recommended – fastest)\nbun add libts-csrfx-auth\n\n# npm\nnpm install libts-csrfx-auth\n\n# pnpm\npnpm add libts-csrfx-auth\n\n# yarn\nyarn add libts-csrfx-auth\n```\n\n**Requirements:**\n\n- Node.js **\u003e=18** (for native `fetch` and `AbortController`)\n- or Bun **\u003e=1.3++**\n\n---\n\n## Quick Start\n\nCreate a `.env` file in your project root:\n\n```env\nBASE_URL=https://example.com\nLOGIN_PATH=login\nLOGOUT_PATH=logout\nUSER_EMAIL=admin@example.com\nUSER_PASSWORD=secret\n```\n\nThen:\n\n```typescript\nimport { AuthClient, LogoutClient } from 'libts-csrfx-auth'\n\nconst auth = new AuthClient()\nconst session = await auth.login()\nconsole.log(`Logged in! CSRF token: ${session.csrfToken}`)\n\n// Reuse cached session on next run\nif (await auth.hasValidSession()) {\n\tconst cached = await auth.getCachedSession()\n\tconsole.log(`Session from ${new Date(cached.timestamp)}`)\n}\n\n// Logout\nconst logout = new LogoutClient()\nawait logout.logout()\n```\n\n---\n\n## Environment Variables\n\nThe library uses `loadEnv()` to read variables from `.env` (project root) and then falls back to `Bun.env` / `process.env`. All variables are validated.\n\n| Variable        | Required | Default  | Validation                                                       |\n| --------------- | -------- | -------- | ---------------------------------------------------------------- |\n| `BASE_URL`      | \u003cimg src=\"https://img.shields.io/badge/required-yes-green?style=flat-square\" alt=\"required\" height=\"18\"\u003e | – | Must be `http://` or `https://` |\n| `LOGIN_PATH`    | \u003cimg src=\"https://img.shields.io/badge/required-no-gray?style=flat-square\" alt=\"optional\" height=\"18\"\u003e | `login` | No `?` or `#` |\n| `LOGOUT_PATH`   | \u003cimg src=\"https://img.shields.io/badge/required-no-gray?style=flat-square\" alt=\"optional\" height=\"18\"\u003e | `logout` | No `?` or `#` |\n| `USER_EMAIL`    | \u003cimg src=\"https://img.shields.io/badge/required-no-gray?style=flat-square\" alt=\"optional\" height=\"18\"\u003e | – | Basic email regex |\n| `USER_PASSWORD` | \u003cimg src=\"https://img.shields.io/badge/required-no-gray?style=flat-square\" alt=\"optional\" height=\"18\"\u003e | – | Cannot be empty |\n\n\u003e _`AuthClient` requires both email and password to be non‑empty (throws `INVALID_CREDENTIALS` otherwise)._\n\nExample `.env`:\n\n```env\nBASE_URL=https://staging.example.com\nLOGIN_PATH=api/login\nUSER_EMAIL=ci@example.com\nUSER_PASSWORD=ci123\n```\n\n---\n\n## Core Concepts\n\n### CSRF‑Protected Login Flow\n\nMost modern web frameworks (Laravel, Rails, Django, Symfony) protect login endpoints with a CSRF token that must be submitted along with credentials. The token is usually embedded in the login HTML page.\n\n`AuthClient` automates this:\n\n1. **GET** `BASE_URL/LOGIN_PATH` – extracts the CSRF token from the HTML (supports `\u003cinput name=\"_token\" value=\"...\"\u003e` and `\u003cmeta name=\"csrf-token\" content=\"...\"\u003e`).\n2. Captures any `Set-Cookie` headers from the GET response (session cookie, etc.).\n3. **POST** to the same URL with `_token`, `email`, `password`, and the captured cookies.\n4. On success (HTTP 2xx), extracts new cookies from the response and saves the session to disk.\n\n### Session Caching\n\nAfter a successful login, the session is saved as JSON to:\n\n```\n~/.cache/libts-csrfx-auth/session.json\n```\n\nThe structure (`SessionData`):\n\n```typescript\ninterface SessionData {\n\tcookies: Cookie[] // { name, value, domain, path, httpOnly, secure }\n\tcsrfToken: string // current CSRF token\n\tloggedIn: boolean // always true after login\n\ttimestamp: number // Unix ms (Date.now())\n}\n```\n\nOn subsequent runs, `hasValidSession(maxAgeMs)` checks if the cached session is fresh (default 1 hour) and `loggedIn === true`. If valid, you can reuse it without re‑authenticating.\n\n### Retry \u0026 Timeout Mechanism\n\n`fetchWithRetry()` wraps `globalThis.fetch` with:\n\n- **Timeout** – uses `AbortController`; if exceeded, the attempt is aborted and counted as a failure.\n- **Retries** – up to `maxRetries` attempts (default 3). Retryable conditions:\n    - Network errors (DNS, TLS, socket)\n    - Timeouts\n    - HTTP 5xx (server errors) and 429 (rate limit) – configurable via `retryOn` predicate.\n- **Exponential backoff** – delay = `retryDelayMs * 2^attempt` (e.g., 100ms, 200ms, 400ms).\n\nBoth `AuthClient` and `LogoutClient` use this internally with sensible defaults.\n\n---\n\n## Library API\n\n### `AuthClient`\n\nThe main client for logging in.\n\n```typescript\nclass AuthClient {\n\tconstructor(options?: LoginOptions)\n\tlogin(): Promise\u003cSessionData\u003e\n\tgetCachedSession(): Promise\u003cSessionData | null\u003e\n\thasValidSession(maxAgeMs?: number): Promise\u003cboolean\u003e\n\tclearSession(): Promise\u003cvoid\u003e\n\treadonly cacheFilePath: string\n\treadonly configRef: EnvConfig\n}\n```\n\n#### Constructor Options (`LoginOptions`)\n\n| Option         | Type      | Default                     | Description                                          |\n| -------------- | --------- | --------------------------- | ---------------------------------------------------- |\n| `baseUrl`      | `string`  | `BASE_URL` env              | Override base URL.                                   |\n| `email`        | `string`  | `USER_EMAIL` env            | Override email.                                      |\n| `password`     | `string`  | `USER_PASSWORD` env         | Override password.                                   |\n| `cacheDir`     | `string`  | `~/.cache/libts-csrfx-auth` | Custom directory for session cache.                  |\n| `maxRetries`   | `number`  | `3`                         | Max retry attempts (excluding initial try).          |\n| `retryDelayMs` | `number`  | `100`                       | Initial delay before first retry (exponential).      |\n| `timeoutMs`    | `number`  | `15000`                     | Request timeout in ms.                               |\n| `sendReferer`  | `boolean` | `true`                      | Send `Referer` header in both GET and POST requests. |\n| `sendOrigin`   | `boolean` | `true`                      | Send `Origin` header in both requests.               |\n\n#### `AuthClient` Methods\n\n##### `login(): Promise\u003cSessionData\u003e`\n\nPerforms the full two‑step login. Throws `AuthError` on failure. Saves session to disk.\n\n##### `getCachedSession(): Promise\u003cSessionData | null\u003e`\n\nReturns the raw cached session (no freshness check). `null` if none exists or file corrupted.\n\n##### `hasValidSession(maxAgeMs = 3_600_000): Promise\u003cboolean\u003e`\n\nReturns `true` if a cached session exists, is fresh (age \u003c `maxAgeMs`), and `loggedIn === true`. Does **not** contact the server.\n\n##### `clearSession(): Promise\u003cvoid\u003e`\n\nDeletes both the in‑memory cookie jar and the persistent cache file.\n\n##### `cacheFilePath: string`\n\nFull path to the session JSON file (e.g., `/home/user/.cache/libts-csrfx-auth/session.json`).\n\n##### `configRef: EnvConfig`\n\nThe internal `EnvConfig` instance (read‑only) for advanced inspection.\n\n---\n\n### `LogoutClient`\n\nTerminates an authenticated session.\n\n```typescript\nclass LogoutClient {\n\tconstructor(options?: LogoutOptions)\n\tlogout(): Promise\u003cvoid\u003e\n\tlogoutWithToken(csrfToken: string): Promise\u003cvoid\u003e\n\tclearCache(): Promise\u003cvoid\u003e\n\tloadCache(): Promise\u003cSessionData | null\u003e\n\treadonly configRef: EnvConfig\n}\n```\n\n#### Constructor Options (`LogoutOptions`)\n\n| Option        | Type      | Default                     | Description                    |\n| ------------- | --------- | --------------------------- | ------------------------------ |\n| `baseUrl`     | `string`  | `BASE_URL` env              | Override base URL.             |\n| `cacheDir`    | `string`  | `~/.cache/libts-csrfx-auth` | Custom cache directory.        |\n| `timeoutMs`   | `number`  | `15000`                     | Request timeout in ms.         |\n| `sendReferer` | `boolean` | `true`                      | Send `Referer` header in POST. |\n| `sendOrigin`  | `boolean` | `true`                      | Send `Origin` header in POST.  |\n\n#### `LogoutClient` Methods\n\n##### `logout(): Promise\u003cvoid\u003e`\n\nLoads the cached session, extracts cookies and CSRF token, sends a POST request to `LOGOUT_PATH` with `_token`. On HTTP 2xx/3xx, clears the local cache.\n\n##### `logoutWithToken(csrfToken: string): Promise\u003cvoid\u003e`\n\nSame as `logout()`, but uses the explicitly provided token instead of the cached one. Useful if you have a refreshed token.\n\n##### `clearCache(): Promise\u003cvoid\u003e`\n\nDeletes the cache file without contacting the server.\n\n##### `loadCache(): Promise\u003cSessionData | null\u003e`\n\nLoads the cached session (no validation).\n\n##### `configRef: EnvConfig`\n\nInternal configuration (read‑only).\n\n---\n\n### `EnvConfig` (Configuration)\n\nValidates and normalizes configuration.\n\n```typescript\nclass EnvConfig {\n\tconstructor(options: EnvConfigOptions)\n\tstatic fromEnv(overrides?: Partial\u003cEnvConfigOptions\u003e): EnvConfig\n\tget fullLoginUrl(): string\n\tget fullLogoutUrl(): string\n\thasValidCredentials(): boolean\n\treadonly baseUrl: string\n\treadonly loginPath: string\n\treadonly logoutPath: string\n\treadonly email: string\n\treadonly password: string\n}\n```\n\n#### Static Factory\n\n```typescript\nconst config = EnvConfig.fromEnv({ email: 'override@example.com' })\n```\n\nReads `loadEnv()` and applies overrides. Throws if `BASE_URL` missing.\n\n#### Instance Properties \u0026 Methods\n\n- `fullLoginUrl` / `fullLogoutUrl` – fully constructed URLs (base + path, no double slashes).\n- `hasValidCredentials()` – basic check: `email` and `password` non‑empty and email contains `@`.\n\n---\n\n### `CacheManager` (Session Persistence)\n\nLow‑level disk cache manager.\n\n```typescript\nclass CacheManager {\n\tconstructor(customDir?: string)\n\tsave(session: SessionData): Promise\u003cvoid\u003e\n\tload(): Promise\u003cSessionData | null\u003e\n\tclear(): Promise\u003cvoid\u003e\n\tupdateCsrfToken(newToken: string): Promise\u003cvoid\u003e\n\tloadFresh(maxAgeMs: number): Promise\u003cSessionData | null\u003e\n\treadonly cacheFilePath: string\n\treadonly cacheDirPath: string\n}\n```\n\n**Default location:** `~/.cache/libts-csrfx-auth/session.json`\n\n#### Methods\n\n- `save(session)` – writes JSON (pretty‑printed) to disk, creates directory if missing.\n- `load()` – reads and parses JSON; returns `null` on any error (ENOENT, invalid JSON).\n- `clear()` – deletes the file if it exists.\n- `updateCsrfToken(newToken)` – loads existing session, updates token and timestamp, saves back (no‑op if no session).\n- `loadFresh(maxAgeMs)` – loads and checks age; returns session only if `Date.now() - session.timestamp \u003c maxAgeMs`.\n\n---\n\n### Utility Functions\n\nAll utilities are exported from the main entry point.\n\n#### Cookie Utilities\n\n```typescript\nparseCookies(headers: Headers): Map\u003cstring, string\u003e\n```\n\nExtracts all `Set-Cookie` headers from a `fetch` response and returns a `Map` of name → value. Uses `headers.getSetCookie()` (modern API) – falls back to empty array.\n\n```typescript\nbuildCookieHeader(cookies: Map\u003cstring, string\u003e): string\n```\n\nSerialises a cookie map into a `Cookie` header string: `\"name1=value1; name2=value2\"`.\n\n```typescript\nparseSetCookie(cookieStr: string): { name: string; value: string } | null\n```\n\nParses a raw `Set-Cookie` header (e.g., `\"sessionId=abc123; HttpOnly\"`) and returns the first `name=value` pair before the first semicolon. Returns `null` if invalid.\n\n#### CSRF Extraction\n\n```typescript\nextractCsrfToken(html: string): string | null\n```\n\nScans HTML for `\u003cinput name=\"_token\" value=\"...\"\u003e` or `\u003cmeta name=\"csrf-token\" content=\"...\"\u003e`. Regular expressions are case‑insensitive and handle attribute order variations.\n\n#### HTTP Client with Retry\n\n```typescript\nfetchWithRetry(\n  url: string | URL,\n  options?: FetchOptions,\n  shouldRetry?: (res: Response) =\u003e boolean\n): Promise\u003cResponse\u003e\n```\n\n`FetchOptions` extends `RequestInit` (except `signal`) and adds:\n\n- `timeoutMs?: number` (default 15000)\n- `maxRetries?: number` (default 3)\n- `retryDelayMs?: number` (default 100)\n- `retryOn?: (res: Response) =\u003e boolean` (default retries only on HTTP 5xx)\n\nThrows `AuthError` with code `NETWORK_ERROR` when all attempts fail.\n\n#### Session Helpers\n\n```typescript\nisSessionFresh(session: SessionData, maxAgeMs: number): boolean\n```\n\nReturns `true` if `Date.now() - session.timestamp \u003c maxAgeMs`.\n\n```typescript\nsessionWithCsrfToken(session: SessionData, newToken: string): SessionData\n```\n\nCreates a new session object with updated token and current timestamp (immutable).\n\n---\n\n## Error Handling\n\nAll errors thrown by the library are instances of `AuthError`.\n\n### `AuthError` Class\n\n```typescript\nclass AuthError extends Error {\n\treadonly code: AuthErrorCode\n\treadonly context?: string\n\treadonly timestamp: number\n\tconstructor(\n\t\tmessage: string,\n\t\tcode: AuthErrorCode,\n\t\toptions?: { cause?: unknown; context?: string },\n\t)\n\tstatic fromResponse(\n\t\tresponse: Response,\n\t\tdefaultCode: AuthErrorCode,\n\t\toptions?: { context?: string },\n\t): AuthError\n\tstatic fromUnknown(err: unknown, fallbackCode?: AuthErrorCode): AuthError\n\tstatic fromStatus(status: number, body?: string): AuthError\n\ttoJSON(): Record\u003cstring, unknown\u003e\n\tgetFormattedMessage(): string\n}\n```\n\n- `fromResponse` – maps HTTP status codes (see table below) to error codes.\n- `fromUnknown` – intelligently extracts message/code from `DOMException` (AbortError), `TypeError` (fetch), or generic `Error`.\n- `toJSON()` – safe for logging (includes trimmed stack).\n- `getFormattedMessage()` – returns `[CODE] message | Context: ... | Cause: ...`.\n\n### Error Codes (`AuthErrorCode`)\n\n| Code                  | Retryable | Typical HTTP status  | Description                                         |\n| --------------------- | --------- | -------------------- | --------------------------------------------------- |\n| `INVALID_CREDENTIALS` |           | –                    | Email or password empty / malformed.                |\n| `CSRF_NOT_FOUND`      |           | –                    | Token missing in HTML.                              |\n| `CSRF_FETCH_FAILED`   |           | –                    | GET login page failed (network/4xx/5xx).            |\n| `CSRF_EXPIRED`        |           | 419                  | Token expired (server response).                    |\n| `LOGIN_FAILED`        |           | 4xx (except 419/422) | Login POST returned non‑2xx, non‑retryable.         |\n| `LOGOUT_FAILED`       |           | 4xx (except 419/422) | Logout POST failed.                                 |\n| `NOT_AUTHENTICATED`   |           | –                    | No valid session in cache.                          |\n| `NETWORK_ERROR`       |           | –                    | DNS, TLS, socket, or `fetch` throw.                 |\n| `TIMEOUT`             |           | –                    | Request aborted due to timeout.                     |\n| `CACHE_ERROR`         |           | –                    | File system read/write error.                       |\n| `VALIDATION_ERROR`    |           | 400, 422             | Form validation error (e.g., wrong email/password). |\n| `TOO_MANY_REQUESTS`   |           | 429                  | Rate limited.                                       |\n| `SERVER_ERROR`        |           | 5xx                  | Server internal error.                              |\n| `UNAUTHORIZED`        |           | 401                  | Not authenticated (missing/invalid session).        |\n| `FORBIDDEN`           |           | 403                  | Authenticated but not allowed.                      |\n| `NOT_FOUND`           |           | 404                  | Endpoint does not exist.                            |\n| `UNKNOWN`             |           | –                    | Catch‑all.                                          |\n\n### Retryable vs Authentication Errors\n\nUse the built‑in guards:\n\n```typescript\nimport { isRetryableError, isAuthenticationError } from 'libts-csrfx-auth'\n\ntry {\n\tawait auth.login()\n} catch (err) {\n\tif (isRetryableError(err)) {\n\t\t// The error may resolve on a subsequent attempt (network hiccup, server overload).\n\t\t// The library already retries automatically, but you can add custom logic.\n\t}\n\tif (isAuthenticationError(err)) {\n\t\t// Credentials are wrong, CSRF expired, or user not logged in.\n\t\t// Redirect to login UI, prompt for new credentials.\n\t}\n}\n```\n\n---\n\n## Advanced Usage\n\n### Custom Cache Directory\n\n```typescript\nconst auth = new AuthClient({ cacheDir: './my-session-cache' })\n// Session stored in ./my-session-cache/session.json\n```\n\n### Overriding Headers (Referer/Origin)\n\nSome servers require these headers for CSRF validation. You can disable them if not needed:\n\n```typescript\nconst auth = new AuthClient({ sendReferer: false, sendOrigin: false })\n```\n\n### Manual CSRF Token Management\n\nIf you have a token from another source (e.g., API response), you can update the cache:\n\n```typescript\nawait auth.cacheManager.updateCsrfToken('new_token_from_api')\n```\n\nOr create a new session object:\n\n```typescript\nconst updated = sessionWithCsrfToken(oldSession, 'fresh_token')\nawait auth.cacheManager.save(updated)\n```\n\n### Using with Node.js (without Bun)\n\nThe library uses `Bun.env` for environment access; in Node.js it falls back to `process.env`. No additional polyfills are required for `fetch` (Node 18+).\n\n---\n\n## Development\n\n### Project Structure\n\n```\n.\n├── lib/                      # Source code\n│   ├── auth/                 # AuthClient, LogoutClient\n│   ├── handler/              # EnvConfig, loadEnv, AuthError\n│   ├── utils/                # cookies, csrf, http, session\n│   ├── lib.ts                # Public API barrel\n│   └── preloader.ts          # Internal re‑exports\n├── build/                    # Compiled output (CJS, ESM, types)\n├── docs/                     # Generated HTML documentation\n├── docs-md/                  # Optional Markdown output\n├── scripts/                  # Build helpers\n├── test/                     # Unit tests (Bun test)\n├── package.json\n├── tsconfig.json\n├── typedoc.json\n└── rollup.config.mjs\n```\n\n### Scripts\n\n| Command                  | Description                                           |\n| ------------------------ | ----------------------------------------------------- |\n| `bun run clean`          | Delete `build/` and cache.                            |\n| `bun run typecheck`      | Run `tsc --noEmit`.                                   |\n| `bun run format`         | Format all source files with Prettier.                |\n| `bun run clean-code`     | Run `rmcm` (remove comments) – used for distribution. |\n| `bun run build:lib:prod` | Bundle library (minified) with Rollup.                |\n| `bun run rebuild`        | Clean → typecheck → build → format → docs.            |\n| `bun run test`           | Run tests with Bun.                                   |\n| `bun run test:coverage`  | Run tests with coverage report (requires `bun:test`). |\n| `bun run docs:generate`  | Generate HTML docs with TypeDoc.                      |\n| `bun run docs:serve`     | Serve docs locally (port 8080).                       |\n| `bun run version:patch`  | Bump patch version in `package.json`.                 |\n| `bun run release`        | Publish to npm (runs `prepublishOnly`).               |\n\n### Testing\n\nTests are written with Bun's built‑in test runner:\n\n```bash\nbun run test\nbun run test:coverage\n```\n\nMock HTTP responses are recommended – the library does not make real network calls during unit tests.\n\n### Documentation Generation\n\nTypeDoc generates API documentation from the TSDoc comments.\n\n```bash\nbun run docs:generate   # outputs to docs/\nbun run docs:serve      # serves on http://localhost:8080\n```\n\n### Release Process\n\n1. Update version in `package.json` (or run `bun run version:patch`).\n2. Run `bun run rebuild` to ensure everything builds.\n3. Run `bun run test` to verify.\n4. Publish: `bun run release` (which runs `npm publish`).\n\n---\n\n## Troubleshooting\n\n### Common Errors\n\n| Error                                               | Likely cause                                | Solution                                                                                            |\n| --------------------------------------------------- | ------------------------------------------- | --------------------------------------------------------------------------------------------------- |\n| `EnvConfig: baseUrl must be a valid HTTP/HTTPS URL` | `BASE_URL` missing or malformed.            | Check `.env` or environment; ensure `http://` or `https://`.                                        |\n| `CSRF token not found in HTML`                      | Login page HTML doesn’t contain token.      | Verify `LOGIN_PATH` is correct; inspect the page manually.                                          |\n| `HTTP 419: CSRF token expired or invalid`           | Token from GET page is stale.               | The library retries automatically; if persistent, the server may require a fresh token per attempt. |\n| `Validation error: email/password incorrect`        | Wrong credentials (HTTP 422).               | Check `USER_EMAIL` / `USER_PASSWORD`.                                                               |\n| `No valid session found` (logout)                   | No cached session or `loggedIn: false`.     | Run `auth.login()` first.                                                                           |\n| `NETWORK_ERROR` after retries                       | Server unreachable, DNS failure, TLS error. | Check network connectivity; increase `maxRetries` or `timeoutMs`.                                   |\n\n### Debugging Tips\n\n- Enable `console.log` in your code to see the raw HTML or error snippets.\n- Set `timeoutMs` higher for slow servers.\n- Use `sendReferer` / `sendOrigin` options if the server rejects requests without those headers.\n- Inspect the cached session file: `cat ~/.cache/libts-csrfx-auth/session.json`.\n- Use `AuthError.getFormattedMessage()` for detailed error logs.\n\n---\n\n## Contributing\n\n1. **Fork** the repository.\n2. **Create a feature branch** (`git checkout -b feat/your-feature`).\n3. **Commit** using [Conventional Commits](https://www.conventionalcommits.org/) (e.g., `feat: add new retry predicate`).\n4. **Run** `bun run rebuild` to ensure formatting, typecheck, and tests pass.\n5. **Push** and **open a Pull Request** against `main`.\n\nAll contributions must pass the existing test suite and maintain 100% type coverage. New features should include tests.\n\n---\n\n## License\n\n**AGPL-3.0-only** – see [LICENSE](license) for details.  \nThis license ensures that any network‑distributed modifications remain open source.\n\n---\n\n## Credits\n\n- Built with [TypeScript](https://www.typescriptlang.org) and [Bun](https://bun.sh).\n- Inspired by the need for a lightweight, type‑safe authentication client for web scraping and automation.\n\n---\n\n\u003e **Repository**: https://github.com/neuxdotdev/libts-csrfx-auth  \n\u003e **Issues**: https://github.com/neuxdotdev/libts-csrfx-auth/issues  \n\u003e **npm**: https://www.npmjs.com/package/libts-csrfx-auth  \n\u003e **Documentation**: https://neuxdotdev.github.io/libts-csrfx-auth/\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fneuxdotdev%2Flibts-csrfx-auth","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fneuxdotdev%2Flibts-csrfx-auth","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fneuxdotdev%2Flibts-csrfx-auth/lists"}