@cirodam/auth-core
v0.1.2
Published
Shared session/auth/user-management core for cirodam's self-hosted SvelteKit + Postgres + Drizzle apps
Readme
@cirodam/auth-core
Shared session/auth/user-management core for cirodam's small self-hosted
SvelteKit + Postgres + Drizzle apps (tylerdteague_v2, Mirror, SimpleBlog),
extracted after noticing password.ts/rateLimit.ts were byte-identical
copy-pasted across all three, auth.ts/users.ts were ~90% identical, and
security hardening applied to one app (a shorter session lifetime,
invalidating other sessions on password change) never reached the others.
What's here
@cirodam/auth-core—hashPassword/verifyPassword/getDummyPasswordHash(scrypt-based, with a timing-attack mitigation for unknown usernames),checkRateLimit(in-memory fixed-window limiter),requireUser/requireAdmin(role guards, generic over{ role: string }), session management (generateSessionToken,createSession,validateSessionToken,invalidateSession,invalidateOtherSessions, cookie helpers), user CRUD (createUser,listUsers,getUserById,getUserByUsername,verifyUserPassword,updateUsername,updateUserProfile,updateUserPassword,setUserStatusUnlessLastAdmin,deleteUserUnlessLastAdmin), andcreateAuthHandle— aHandlefactory forhooks.server.tsthat does setup-gating, session validation/renewal, route-level auth gating (via apublicRoutesallowlist or predicate), and security headers.@cirodam/auth-core/schema— theusers/sessionsDrizzlepgTable/pgEnumdefinitions (uuid primary keys, nullableemail/firstName/lastNameso apps that don't use them just never populate them, 3-valuestatusenum includingbanned) plus inferred types (User,PublicUser,Session,UserRole,UserStatus).
What's deliberately not here
App-specific policy: which routes are public (that's a config value you
pass in, not something this package decides), self-service signup (only
one of the three apps has this concept), CSP/analytics/anything unrelated
to auth, and app-specific color/UI (see @cirodam/ui-tokens for that).
Using it in a consuming app
npm install @cirodam/auth-coreYour app's src/lib/server/db/schema.ts:
export * from '@cirodam/auth-core/schema';
// ...your own app-specific tables belowYour app's src/app.d.ts:
declare global {
namespace App {
interface Locals {
user: import('@cirodam/auth-core').PublicUser | null;
}
}
}Your app's src/hooks.server.ts:
import { createAuthHandle } from '@cirodam/auth-core';
import { db } from '$lib/server/db';
export const handle = createAuthHandle({
db,
publicRoutes: ['/login'], // or a (pathname: string) => boolean predicate
session: { lifetimeMs: 1000 * 60 * 60, renewalThresholdMs: 1000 * 60 * 30 }
});Route files call the exported functions directly, threading db and (where
lifetime matters) your app's SessionConfig through explicitly — see
createSession/validateSessionToken's signatures. There's no hidden
module-level db singleton inside this package.
Developing
npm run check—tsc --noEmitnpm run test— unit tests for the pure-logic modules (password.ts,rateLimit.ts). No DB-integration tests live here — the three consuming apps' own integration suites are the real correctness signal for the DB-touching code.npm run build— emitsdist/viatsc
To develop against a real consuming app before publishing a new version,
npm pack here and install the resulting tarball in the app
("@cirodam/auth-core": "file:../auth-core/cirodam-auth-core-x.y.z.tgz") —
prefer this over a plain file:../auth-core link, since a file:-linked
directory keeps its own local node_modules (needed to build/typecheck this
package standalone), which can shadow the consuming app's own drizzle-orm/
@sveltejs/kit peer install during local type-checking in a way a real
tarball or registry install never does (only dist/ and package.json are
ever published — node_modules isn't part of the package either way).
Publishing
npm run build
npm publish --access publicBump version in package.json first (semver — this is consumed by
multiple apps, so a breaking change to a function's signature or the
users/sessions schema shape should be a major bump).
