khalidadil
  • Joined on 2025-08-25

@khalidadil/auth-manager (0.2.0)

Published 2026-09-02 04:20:55 +00:00 by khalidadil

Installation

@khalidadil:registry=
npm install @khalidadil/auth-manager@0.2.0
"@khalidadil/auth-manager": "0.2.0"

About this package

auth-manager

Magic-link customer auth, Google SSO admin auth, sessions, and an address book for the apparel-designer storefront.

Status: v0.1.0-alpha.0 — first release. Tracks the v0.8 sub-plan in Project - Apparel Designer/v08_Auth_v1.md.

Use email one-time CODES, not magic links. Email-security scanners (Outlook SafeLinks etc.) pre-open links in messages, which consumes a single-use magic link before the human ever clicks it. Typed 6-digit codes can't be consumed by a scanner. The magic-link routes remain for backward compatibility with existing apparel-designer wiring, but new hosts should use the code flow.

Codes-only endpoints (added 2026-09-02):

Route Body Effect
POST ${apiBase}/auth/customer/request-code { email, next? } emails a 6-digit code (10-min TTL, one live code per email); always 200 (no enumeration)
POST ${apiBase}/auth/customer/verify-code { email, code } verifies + mints the session cookie; JSON response { ok, next } — no redirect, no clickable link; wrong codes burn attempts (5 → locked); IP-rate-limited

Security: codes are stored as SHA-256(email:code) (never plaintext), compared timing-safe, single-use, attempt-limited, and short-lived. Sessions, cookies, and rate limiting are the module's existing infrastructure (unchanged).

What's in here

Layer Concerns
Server (Express middleware) mountAuth(app, {...}), migration runner, session middleware, route handlers, in-memory rate limiter
Database (Postgres) customers, admins, magic_links, sessions, addresses
Client (React) <AuthProvider>, <LoginForm>, <AccountMenu>, <AccountShell>, <AccountAddressesPage>, <AdminLoginPage>, <RequireAdmin>, plus useCustomer / useAdmin / useAddresses hooks

Install (from the host)

// apparel-designer/package.json
"dependencies": {
  "@khalidadil/auth-manager": "^0.2.0",
  "pg": "^8.13.0"
}

Published to the Gitea registry: npm config set @khalidadil:registry https://git.kadil.dev/api/packages/khalidadil/npm/ then a normal npm install. Local development can still use "@khalidadil/auth-manager": "file:../auth-manager" (the key must be the scoped name).

Server wiring

import { Pool } from 'pg';
import { mountAuth, runMigrations } from '@khalidadil/auth-manager/server';

const pgPool = new Pool({ connectionString: process.env.DATABASE_URL });

// Run migrations at boot. Idempotent — safe to invoke on every start.
await runMigrations(pgPool, { logger });

const { authMiddleware, requireCustomer, requireAdmin } = mountAuth(app, {
  db: pgPool,
  apiBase: '/api',
  callbackBaseUrl: process.env.PUBLIC_BASE_URL,   // e.g. 'http://localhost:3001'
  smtp: process.env.SMTP_HOST ? {
    host: process.env.SMTP_HOST,
    port: process.env.SMTP_PORT,
    user: process.env.SMTP_USER,
    pass: process.env.SMTP_PASS,
    from: process.env.SMTP_FROM,
  } : undefined,
  fromAddress: process.env.SMTP_FROM || 'no-reply@example.com',
  appName: 'Pawfectly Yours',
  googleClientId: process.env.GOOGLE_CLIENT_ID,
  googleClientSecret: process.env.GOOGLE_CLIENT_SECRET,
  adminEmails: (process.env.ADMIN_EMAILS || '').split(',').map((s) => s.trim()).filter(Boolean),
  cookieDomain: process.env.COOKIE_DOMAIN,        // optional
  isProduction: process.env.NODE_ENV === 'production',
  logger,
});

// Hand requireCustomer / requireAdmin to other modules at their mount time.
// mountCommerce(app, { requireAuth: { customer: requireCustomer, admin: requireAdmin } });

Client wiring

// main.jsx
import { AuthProvider } from '@khalidadil/auth-manager';
import '@khalidadil/auth-manager/styles.css';

createRoot(...).render(
  <AuthProvider apiBase="/api">
    <App />
  </AuthProvider>,
);
// AppRouter.jsx
import {
  LoginForm, AccountShell, AccountHomePage, AccountAddressesPage,
  AdminLoginPage, RequireAdmin,
} from '@khalidadil/auth-manager';

<Routes>
  <Route path="/auth/login" element={<LoginForm next={location.search.get('next')} />} />
  <Route path="/admin/login" element={<AdminLoginPage />} />

  <Route path="/account" element={<AccountShell />}>
    <Route index           element={<AccountHomePage />} />
    <Route path="addresses" element={<AccountAddressesPage />} />
    {/* /account/designs from goods-editor-module, /account/orders from commerce-module */}
  </Route>

  <Route path="/admin/*" element={<RequireAdmin>{/* admin shell */}</RequireAdmin>} />
</Routes>
// Anywhere you want an account chip
import { AccountMenu } from '@khalidadil/auth-manager';
<AccountMenu />

Required environment variables

Name Required Notes
DATABASE_URL Yes Postgres 13+ connection string. PG17 recommended.
PUBLIC_BASE_URL Yes Used to build magic-link URLs + the OAuth redirect URI. Must match what's registered in Google Cloud Console (e.g. http://localhost:3001 for dev).
GOOGLE_CLIENT_ID Admin SSO OAuth 2.0 Web client.
GOOGLE_CLIENT_SECRET Admin SSO Same client.
ADMIN_EMAILS Admin SSO Comma-separated allowlist. Plan recommends 2+ entries for break-glass recovery.
SMTP_HOST Optional If unset, magic links print to console only.
SMTP_PORT If SMTP e.g. 587 or 1025 (Mailpit).
SMTP_USER If SMTP If your relay needs auth.
SMTP_PASS If SMTP
SMTP_FROM If SMTP From: header. Defaults to SMTP_USER or no-reply@example.com.
COOKIE_DOMAIN Optional e.g. .yourdomain.com to share sessions with the WP checkout sidecar. Usually leave empty.

Email delivery in v1

Per the v0.8 sub-plan (updated 2026-05-26): the original plan called for Resend with React Email templates. For the first alpha we're using a simpler approach:

  1. The magic link is ALWAYS printed to stdout with a loud banner — enough to develop and test without any provider account.
  2. If SMTP_HOST is set, it's also sent via SMTP using nodemailer. Useful for Mailpit / Mailcatcher / Mailtrap during dev, or a real relay for staging.
  3. Resend is deferred to v2. When adopted, createEmailSender grows a provider: 'resend' branch; the interface stays the same.
Cookie Purpose
pawfectly_customer_session Customer session token. HttpOnly, Secure in prod, SameSite=Lax, 90-day TTL with rolling renewal.
pawfectly_admin_session Admin session token. Same flags, 24-hour TTL (admins re-auth more often).
pawfectly_oauth_state PKCE state during Google SSO dance. 10-min TTL.
pawfectly_oauth_verifier PKCE code verifier. 10-min TTL.

Migration

The migrations/ directory contains 001_init.sql, which creates all five tables plus a schema_migrations tracker. The runMigrations(pool) helper reads the directory, skips applied migrations, and applies new ones in lexical order. Idempotent — safe on every boot.

To run migrations manually outside the host (debugging):

psql $DATABASE_URL -f migrations/001_init.sql

Build

npm install
npm run build      # client lib + server bundle

v2 deferred (per sub-plan)

Tracked in v08_Auth_v1.md §v2: account merge, 2FA, email change, account deletion, Redis-backed rate limiting, WebAuthn, session list + revocation, admin audit log, Resend integration.

Dependencies

Dependencies

ID Version
@oslojs/crypto ^1.0.1
@oslojs/encoding ^1.1.0
arctic ^2.3.0
cookie ^0.7.2
nodemailer ^6.9.16

Development Dependencies

ID Version
@rollup/plugin-commonjs ^28.0.0
@rollup/plugin-json ^6.1.0
@rollup/plugin-node-resolve ^15.3.0
express ^4.21.0
pg ^8.13.0
react ^19.2.5
react-dom ^19.2.5
react-router-dom ^7.1.0
rollup ^4.30.0
rollup-plugin-esbuild ^6.1.0
rollup-plugin-postcss ^4.0.2

Peer Dependencies

ID Version
express >=4.18.0
pg >=8.11.0
react >=18.0.0
react-dom >=18.0.0
react-router-dom >=6.0.0
Details
npm
2026-09-02 04:20:55 +00:00
17
334 KiB
Assets (1)
Versions (5) View all
0.4.1 2026-10-19
0.4.0 2026-10-09
0.3.0 2026-09-17
0.3.0-rc.1 2026-09-17
0.2.0 2026-09-02