{"_id":"@aizvi/auth","_rev":"5-cf1b2014d3484ede66148b10cc614f1e","name":"@aizvi/auth","dist-tags":{"latest":"1.1.2"},"versions":{"1.0.0":{"name":"@aizvi/auth","version":"1.0.0","license":"MIT","_id":"@aizvi/auth@1.0.0","maintainers":[{"name":"ahmadhuss","email":"ahmadhussnain787@gmail.com"}],"dist":{"shasum":"fb7c05e4d570cc5ac086006c1f064df773e5a2b1","tarball":"https://registry.npmjs.org/@aizvi/auth/-/auth-1.0.0.tgz","fileCount":9,"integrity":"sha512-wWKBkha+A06cYhpxSfkZS2NmFsultxnm2MH+65N+Mg0tWmtLKWcN/nt8uw1X2Rr0RMc5pVsMuPgN/hUUdT0Psg==","signatures":[{"sig":"MEUCIQD3ipnVTUoiLxikIGNLl7/ZVRyK0RZv0VFAMTPc6iN1nAIgEHEpAzi3CRUczMpO9LvZQEWw+va+It3a5UMGpvKUzy0=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":121985},"main":"./dist/index.cjs","type":"commonjs","types":"./dist/index.d.ts","module":"./dist/index.mjs","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.mjs","require":"./dist/index.cjs"}},"gitHead":"046b002c0fa5682733aff437882d8465958b5158","scripts":{"dev":"tsup --watch","test":"bun test","build":"tsup","typecheck":"tsc --noEmit"},"_npmUser":{"name":"ahmadhuss","email":"ahmadhussnain787@gmail.com"},"_npmVersion":"11.12.1","description":"Framework-agnostic, database-agnostic auth core: signup, login, email verification, password reset, and JWT sessions for web + mobile clients.","directories":{},"_nodeVersion":"24.15.0","dependencies":{"bcryptjs":"^2.4.3","jsonwebtoken":"^9.0.2"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.3.0","express":"^4.19.2","typescript":"^5.6.3","@types/express":"^4.17.21","@types/bcryptjs":"^2.4.6","@types/jsonwebtoken":"^9.0.7"},"peerDependencies":{"express":"^4.19.0 || ^5.0.0"},"_npmOperationalInternal":{"tmp":"tmp/auth_1.0.0_1784753162837_0.0962529428183283","host":"s3://npm-registry-packages-npm-production"}},"1.0.1":{"name":"@aizvi/auth","version":"1.0.1","keywords":["auth","authentication","jwt","express","session","login","signup","password-reset","email-verification","refresh-token","rest-api","typescript","backend"],"license":"MIT","_id":"@aizvi/auth@1.0.1","maintainers":[{"name":"ahmadhuss","email":"ahmadhussnain787@gmail.com"}],"homepage":"https://github.com/Aizvi/auth/tree/main/packages/core#readme","bugs":{"url":"https://github.com/Aizvi/auth/issues"},"dist":{"shasum":"fbab3a8aa50e4cebdf0758b5fe5e9f6569eed758","tarball":"https://registry.npmjs.org/@aizvi/auth/-/auth-1.0.1.tgz","fileCount":9,"integrity":"sha512-/85XHz9omttWYTwUNyc2gx0IwIe2U0S+yyol/gSVN/qD67ig9SpwGxv8LI4A0CSIk6L51/cw7QWuaeMO6SxF9A==","signatures":[{"sig":"MEUCIEUKe6S7Zq/eu6VIaQGVWilp+VnQQLySfDPVeQuInbGwAiEAlrMWjaDjvLihzIitdvajYRZj+J3DBCqOo2DQ2PccI6E=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":119673},"main":"./dist/index.cjs","type":"commonjs","types":"./dist/index.d.ts","module":"./dist/index.mjs","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.mjs","require":"./dist/index.cjs"}},"gitHead":"be127805c7e77909f28451353d7eebbf9cb9376e","scripts":{"dev":"tsup --watch","test":"bun test","build":"tsup","typecheck":"tsc --noEmit"},"_npmUser":{"name":"ahmadhuss","email":"ahmadhussnain787@gmail.com"},"repository":{"url":"git+https://github.com/Aizvi/auth.git","type":"git","directory":"packages/core"},"_npmVersion":"10.9.8","description":"Framework-agnostic, database-agnostic auth core: signup, login, email verification, password reset, and JWT sessions for web + mobile clients.","directories":{},"_nodeVersion":"22.23.1","dependencies":{"bcryptjs":"^2.4.3","jsonwebtoken":"^9.0.2"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.3.0","express":"^4.19.2","typescript":"^5.6.3","@types/express":"^4.17.21","@types/bcryptjs":"^2.4.6","@types/jsonwebtoken":"^9.0.7"},"peerDependencies":{"express":"^4.19.0 || ^5.0.0"},"_npmOperationalInternal":{"tmp":"tmp/auth_1.0.1_1784755246901_0.7868938210232557","host":"s3://npm-registry-packages-npm-production"}},"1.1.0":{"name":"@aizvi/auth","version":"1.1.0","keywords":["auth","authentication","jwt","express","session","login","signup","password-reset","email-verification","refresh-token","rest-api","typescript","backend"],"license":"MIT","_id":"@aizvi/auth@1.1.0","maintainers":[{"name":"ahmadhuss","email":"ahmadhussnain787@gmail.com"}],"homepage":"https://github.com/Aizvi/auth/tree/main/packages/core#readme","bugs":{"url":"https://github.com/Aizvi/auth/issues"},"dist":{"shasum":"44365f124307a6929f7b5ba32b6cb58f58f87ef2","tarball":"https://registry.npmjs.org/@aizvi/auth/-/auth-1.1.0.tgz","fileCount":9,"integrity":"sha512-P0tbfcJhuFXYoc6CU6qb1KY2MgWF/2gqEpI6hZ8INvJ5DOAYQLffXhwqMYWP+VL1MkqtBdhKgFXpK+zGf4EB0Q==","signatures":[{"sig":"MEUCIA8rNyeIb1tdSMxfz5aRDGfCgWaPciT1ODjDZ2okgN5mAiEAnlBt14NBzEMBOZJZLiQxlzla37D/Lddyh8TPctNKa4w=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":136065},"main":"./dist/index.cjs","type":"commonjs","types":"./dist/index.d.ts","module":"./dist/index.mjs","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.mjs","require":"./dist/index.cjs"}},"gitHead":"39abe7f9feb78133ec468222ba14ddc5115fda5c","scripts":{"dev":"tsup --watch","test":"bun test","build":"tsup","typecheck":"tsc --noEmit"},"_npmUser":{"name":"ahmadhuss","email":"ahmadhussnain787@gmail.com"},"repository":{"url":"git+https://github.com/Aizvi/auth.git","type":"git","directory":"packages/core"},"_npmVersion":"10.9.8","description":"Framework-agnostic, database-agnostic auth core: signup, login, email verification, password reset, and JWT sessions for web + mobile clients.","directories":{},"_nodeVersion":"22.23.1","dependencies":{"bcryptjs":"^2.4.3","jsonwebtoken":"^9.0.2"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.3.0","express":"^4.19.2","typescript":"^5.6.3","@types/express":"^4.17.21","@types/bcryptjs":"^2.4.6","@types/jsonwebtoken":"^9.0.7"},"peerDependencies":{"express":"^4.19.0 || ^5.0.0"},"_npmOperationalInternal":{"tmp":"tmp/auth_1.1.0_1784949102408_0.10809462237951162","host":"s3://npm-registry-packages-npm-production"}},"1.1.1":{"name":"@aizvi/auth","version":"1.1.1","keywords":["auth","authentication","jwt","express","session","login","signup","password-reset","email-verification","refresh-token","rest-api","typescript","backend"],"license":"MIT","_id":"@aizvi/auth@1.1.1","maintainers":[{"name":"ahmadhuss","email":"ahmadhussnain787@gmail.com"}],"homepage":"https://github.com/Aizvi/auth/tree/main/packages/core#readme","bugs":{"url":"https://github.com/Aizvi/auth/issues"},"dist":{"shasum":"7bafdc70a76dac7b6b814b4d3c5666f11f0700ad","tarball":"https://registry.npmjs.org/@aizvi/auth/-/auth-1.1.1.tgz","fileCount":9,"integrity":"sha512-YNoCKHMxFcXRVtrBy+PKT7LEncRJDwuynhDatPh3DvaK5/vTqAmUmWmAoHjZFxhqXBsriKUpUlaWKvjV5Hd/kg==","signatures":[{"sig":"MEYCIQCIy6004dUVKzICPpr3tHP8MN6AgQUdmKmN/kaUfb99PAIhAI3vor7QK0CVeoq22+GgjiEY4+A3dYl27+N8UHXYjHi9","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":138399},"main":"./dist/index.cjs","type":"commonjs","types":"./dist/index.d.ts","module":"./dist/index.mjs","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.mjs","require":"./dist/index.cjs"}},"gitHead":"7c2d5fde31a55212b91140702df7cdc9a6238acc","scripts":{"dev":"tsup --watch","test":"bun test","build":"tsup","typecheck":"tsc --noEmit"},"_npmUser":{"name":"ahmadhuss","email":"ahmadhussnain787@gmail.com"},"repository":{"url":"git+https://github.com/Aizvi/auth.git","type":"git","directory":"packages/core"},"_npmVersion":"10.9.8","description":"Framework-agnostic, database-agnostic auth core: signup, login, email verification, password reset, and JWT sessions for web + mobile clients.","directories":{},"_nodeVersion":"22.23.1","dependencies":{"bcryptjs":"^2.4.3","jsonwebtoken":"^9.0.2"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.3.0","express":"^4.19.2","typescript":"^5.6.3","@types/express":"^4.17.21","@types/bcryptjs":"^2.4.6","@types/jsonwebtoken":"^9.0.7"},"peerDependencies":{"express":"^4.19.0 || ^5.0.0"},"_npmOperationalInternal":{"tmp":"tmp/auth_1.1.1_1784950707456_0.33986763650865104","host":"s3://npm-registry-packages-npm-production"}},"1.1.2":{"name":"@aizvi/auth","version":"1.1.2","description":"Framework-agnostic, database-agnostic auth core: signup, login, email verification, password reset, and JWT sessions for web + mobile clients.","keywords":["auth","authentication","jwt","express","session","login","signup","password-reset","email-verification","refresh-token","rest-api","typescript","backend"],"license":"MIT","repository":{"type":"git","url":"git+https://github.com/Aizvi/auth.git","directory":"packages/core"},"homepage":"https://github.com/Aizvi/auth/tree/main/packages/core#readme","bugs":{"url":"https://github.com/Aizvi/auth/issues"},"type":"commonjs","main":"./dist/index.cjs","module":"./dist/index.mjs","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.mjs","require":"./dist/index.cjs"}},"publishConfig":{"access":"public"},"scripts":{"build":"tsup","dev":"tsup --watch","test":"bun test","typecheck":"tsc --noEmit"},"peerDependencies":{"express":"^4.19.0 || ^5.0.0"},"dependencies":{"bcryptjs":"^2.4.3","jsonwebtoken":"^9.0.2"},"devDependencies":{"@types/bcryptjs":"^2.4.6","@types/express":"^4.17.21","@types/jsonwebtoken":"^9.0.7","express":"^4.19.2","tsup":"^8.3.0","typescript":"^5.6.3"},"_id":"@aizvi/auth@1.1.2","gitHead":"6bde1f9a6540c844ec31eb4b0f99afd31e2568bb","_nodeVersion":"22.23.1","_npmVersion":"10.9.8","dist":{"integrity":"sha512-jbUBoEfwnIfarTH1iBoTDwMDLyJgwZMZ6F/PDL3smm2jdUntjB4OVhpsYay37qSptD/3hOUz/oM9eLwhZ9EQzg==","shasum":"d8b04a5f900cd327ac564bfa512ff694f0a4a971","tarball":"https://registry.npmjs.org/@aizvi/auth/-/auth-1.1.2.tgz","fileCount":9,"unpackedSize":157390,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIGVFb3C5Y+jCNqwgoaImk4guYGSY6wVvBScENLYXijpgAiEA9e2nE8hKTzPfNWR813dX5tQZDB2rgYAiTadeGH3OhIY="}]},"_npmUser":{"name":"ahmadhuss","email":"ahmadhussnain787@gmail.com"},"directories":{},"maintainers":[{"name":"ahmadhuss","email":"ahmadhussnain787@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/auth_1.1.2_1784953881344_0.11845750849223435"},"_hasShrinkwrap":false}},"time":{"created":"2026-07-22T20:46:02.633Z","modified":"2026-07-25T04:31:21.644Z","1.0.0":"2026-07-22T20:46:03.024Z","1.0.1":"2026-07-22T21:20:47.034Z","1.1.0":"2026-07-25T03:11:42.541Z","1.1.1":"2026-07-25T03:38:27.581Z","1.1.2":"2026-07-25T04:31:21.463Z"},"bugs":{"url":"https://github.com/Aizvi/auth/issues"},"license":"MIT","homepage":"https://github.com/Aizvi/auth/tree/main/packages/core#readme","keywords":["auth","authentication","jwt","express","session","login","signup","password-reset","email-verification","refresh-token","rest-api","typescript","backend"],"repository":{"type":"git","url":"git+https://github.com/Aizvi/auth.git","directory":"packages/core"},"description":"Framework-agnostic, database-agnostic auth core: signup, login, email verification, password reset, and JWT sessions for web + mobile clients.","maintainers":[{"name":"ahmadhuss","email":"ahmadhussnain787@gmail.com"}],"readme":"# @aizvi/auth\n\nA complete, ready-to-use auth system for a Node.js backend: signup, email\nverification, login, password reset, and sessions for both web and mobile\napps, without locking you into a specific database or frontend framework.\n\nYou get an [Express](https://expressjs.com/) router that handles all the\nHTTP endpoints. You bring two small things: something that saves and reads\nusers (a database adapter) and something that sends emails (a mailer).\nEverything else, including password hashing, JWTs, cookies, refresh token\nrotation, and verification codes, is handled for you.\n\n## Who this is for\n\n- You're building a backend in Node.js (Express) and don't want to write\n  signup, login, and password reset from scratch again.\n- You want the **same auth logic to work for both your website and your\n  mobile app**. This package handles both out of the box.\n- You don't want to be locked into one database. Postgres, MySQL, SQLite,\n  MongoDB, anything works, as long as you (or someone else) has written a\n  small adapter for it. A ready-made SQLite adapter is available as\n  [`@aizvi/auth-sqlite`](https://www.npmjs.com/package/@aizvi/auth-sqlite).\n- Your frontend can be anything: React, Vue, Angular, Next.js, or a mobile\n  app, because it never talks to this package directly. It just calls plain\n  HTTP endpoints like `POST /auth/login` with `fetch`.\n\n## Install\n\n```bash\nnpm install @aizvi/auth express\npnpm add @aizvi/auth express\nyarn add @aizvi/auth express\nbun add @aizvi/auth express\n```\n\n`express` is a peer dependency. You need it in your project already, or you\ncan install it alongside.\n\n## Getting started\n\nThis example uses [`@aizvi/auth-sqlite`](https://www.npmjs.com/package/@aizvi/auth-sqlite)\nfor storage and [Nodemailer](https://nodemailer.com/) for email, but you can\nswap either one out. See [Bring your own database](#bring-your-own-database)\nand [Bring your own email provider](#bring-your-own-email-provider) below.\n\n```bash\nnpm install @aizvi/auth @aizvi/auth-sqlite express nodemailer\n```\n\n```ts\nimport express from 'express';\nimport nodemailer from 'nodemailer';\nimport { createAuthRouter } from '@aizvi/auth';\nimport { sqliteAdapter } from '@aizvi/auth-sqlite';\n\nconst transporter = nodemailer.createTransport({\n  host: process.env.SMTP_HOST,\n  port: 587,\n  auth: { user: process.env.SMTP_USER, pass: process.env.SMTP_PASS },\n});\n\nconst app = express();\napp.use(express.json());\n\napp.use(\n  '/auth',\n  createAuthRouter({\n    // Where users and sessions are stored. Swap this for your own database.\n    adapter: sqliteAdapter({ file: './data.sqlite' }),\n\n    // How verification and password reset codes get emailed. Swap this for\n    // your own provider (Resend, SendGrid, SES, and so on).\n    mailer: {\n      async sendVerificationEmail(email, code) {\n        await transporter.sendMail({\n          to: email,\n          subject: 'Verify your account',\n          text: `Your verification code is ${code}`,\n        });\n      },\n      async sendPasswordResetEmail(email, code) {\n        await transporter.sendMail({\n          to: email,\n          subject: 'Reset your password',\n          text: `Your password reset code is ${code}`,\n        });\n      },\n    },\n\n    // A long, random secret used to sign sessions. Keep this in an\n    // environment variable, never commit it.\n    jwtSecret: process.env.JWT_SECRET!,\n  })\n);\n\napp.listen(3000, () => console.log('Listening on http://localhost:3000'));\n```\n\nThat's it. Your app now has working `/auth/signup`, `/auth/login`,\n`/auth/verify-email`, `/auth/resend-verification`, `/auth/forgot-password`,\n`/auth/reset-password`, `/auth/me`, `/auth/refresh`, and `/auth/logout`\nendpoints.\n\n## How web and mobile clients differ\n\nThe same endpoints serve both kinds of client. The router decides how to\nrespond based on one request header:\n\n- **Web** (a browser, no special header): the session is stored in an\n  **httpOnly cookie**, so client-side JavaScript never touches the token\n  directly. This is the safer default for browsers.\n- **Mobile** (send the header `X-Client-Type: mobile`): instead of a cookie,\n  the response body includes `{ accessToken, refreshToken }` in its `data`.\n  Your app stores these itself (for example in secure storage) and sends the\n  access token back as `Authorization: Bearer <accessToken>` on future\n  requests. The access token is short lived (15 minutes by default). When it\n  expires, call `/auth/refresh` with the refresh token to get a new pair.\n  Each refresh token can only be used once. Using it issues a brand new pair\n  and invalidates the old one, so a stolen, already used token is worthless.\n\n**Example: web login from a browser.**\n\n```js\nconst res = await fetch('/auth/login', {\n  method: 'POST',\n  headers: { 'Content-Type': 'application/json' },\n  credentials: 'include', // send/receive the session cookie\n  body: JSON.stringify({ email, password }),\n});\nconst { data } = await res.json(); // { user: { id, email } }\n```\n\n**Example: mobile login from a mobile app.**\n\n```js\nconst res = await fetch('https://your-api.com/auth/login', {\n  method: 'POST',\n  headers: { 'Content-Type': 'application/json', 'X-Client-Type': 'mobile' },\n  body: JSON.stringify({ email, password }),\n});\nconst { data } = await res.json(); // { user, accessToken, refreshToken }\n// Save data.accessToken and data.refreshToken securely on the device.\n```\n\n## API routes\n\nAll routes are mounted under whatever path you choose (`/auth` in the\nexamples above).\n\n| Method | Path                   | What it does                                                                     |\n| ------ | ---------------------- | --------------------------------------------------------------------------------- |\n| POST   | `/signup`              | Creates an account and emails a verification code.                                |\n| POST   | `/verify-email`        | Confirms a verification code, marks the account verified, and signs the user in.  |\n| POST   | `/resend-verification` | Sends a new verification code (rate limited by `verificationCodeCooldownSeconds`). |\n| POST   | `/login`               | Signs in with email and password.                                                 |\n| POST   | `/refresh`             | Exchanges a mobile refresh token for a new access and refresh token pair.         |\n| POST   | `/forgot-password`     | Emails a password reset code, if the account exists.                              |\n| POST   | `/reset-password`      | Sets a new password using a reset code.                                           |\n| GET    | `/me`                  | Returns the signed in user. Requires a valid session.                             |\n| POST   | `/logout`              | Signs out and invalidates the refresh token, if one was provided.                 |\n\nEvery error response includes a message describing what went wrong. For\nexample, \"Invalid email or password\" or \"Please wait 42 seconds before\nrequesting another verification code\".\n\n## Trying the API directly\n\nThese examples use `curl`, so they work the same from any client, any\nlanguage, or just your terminal, while you're building or debugging. They\nassume the router is mounted at `/auth` on `http://localhost:3000` and use\nthe default response shape (`{ success, message, data }`). If you've set\n`formatSuccessResponse`/`formatErrorResponse`, your actual response bodies\nwill look different, but the requests themselves are identical.\n\n**Sign up.**\n\n```bash\ncurl -X POST http://localhost:3000/auth/signup \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"email\": \"jane@example.com\", \"password\": \"correct-horse-battery\"}'\n```\n\n```json\n{ \"success\": true, \"message\": \"A verification code has been sent to your email.\" }\n```\n\n**Verify the email (web).** Add `-c cookies.txt` to save the session cookie\nfor later requests.\n\n```bash\ncurl -X POST http://localhost:3000/auth/verify-email \\\n  -c cookies.txt \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"email\": \"jane@example.com\", \"code\": \"482913\"}'\n```\n\n```json\n{\n  \"success\": true,\n  \"message\": \"Email verified successfully\",\n  \"data\": { \"user\": { \"id\": \"b3f1...\", \"email\": \"jane@example.com\" } }\n}\n```\n\n**Verify the email (mobile).** Send `X-Client-Type: mobile` instead, and you\nget a token pair back instead of a cookie.\n\n```bash\ncurl -X POST http://localhost:3000/auth/verify-email \\\n  -H \"Content-Type: application/json\" \\\n  -H \"X-Client-Type: mobile\" \\\n  -d '{\"email\": \"jane@example.com\", \"code\": \"482913\"}'\n```\n\n```json\n{\n  \"success\": true,\n  \"message\": \"Email verified successfully\",\n  \"data\": {\n    \"user\": { \"id\": \"b3f1...\", \"email\": \"jane@example.com\" },\n    \"accessToken\": \"eyJhbGciOi...\",\n    \"refreshToken\": \"6f9c2b8a...\"\n  }\n}\n```\n\n**Resend the verification code.**\n\n```bash\ncurl -X POST http://localhost:3000/auth/resend-verification \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"email\": \"jane@example.com\"}'\n```\n\n```json\n{ \"success\": true, \"message\": \"Verification code resent\" }\n```\n\n**Log in (web).** Reuses the cookie jar from the verify-email step, or\nstarts a fresh one.\n\n```bash\ncurl -X POST http://localhost:3000/auth/login \\\n  -c cookies.txt \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"email\": \"jane@example.com\", \"password\": \"correct-horse-battery\"}'\n```\n\n```json\n{\n  \"success\": true,\n  \"message\": \"Signed in successfully\",\n  \"data\": { \"user\": { \"id\": \"b3f1...\", \"email\": \"jane@example.com\" } }\n}\n```\n\n**Log in (mobile).**\n\n```bash\ncurl -X POST http://localhost:3000/auth/login \\\n  -H \"Content-Type: application/json\" \\\n  -H \"X-Client-Type: mobile\" \\\n  -d '{\"email\": \"jane@example.com\", \"password\": \"correct-horse-battery\"}'\n```\n\n```json\n{\n  \"success\": true,\n  \"message\": \"Signed in successfully\",\n  \"data\": {\n    \"user\": { \"id\": \"b3f1...\", \"email\": \"jane@example.com\" },\n    \"accessToken\": \"eyJhbGciOi...\",\n    \"refreshToken\": \"6f9c2b8a...\"\n  }\n}\n```\n\nA wrong password or unknown email responds `401` with\n`{ \"success\": false, \"message\": \"Invalid email or password\" }`. An\nunverified account responds `403` with\n`{ \"success\": false, \"message\": \"Please verify your email before logging in\", \"code\": \"EMAIL_NOT_VERIFIED\" }`.\n\n**Get the current user.** Using the cookie from a web login:\n\n```bash\ncurl http://localhost:3000/auth/me -b cookies.txt\n```\n\nOr using a mobile access token:\n\n```bash\ncurl http://localhost:3000/auth/me \\\n  -H \"Authorization: Bearer eyJhbGciOi...\"\n```\n\n```json\n{\n  \"success\": true,\n  \"message\": \"OK\",\n  \"data\": { \"user\": { \"id\": \"b3f1...\", \"email\": \"jane@example.com\" } }\n}\n```\n\nWith no cookie and no `Authorization` header, this responds `401` with\n`{ \"success\": false, \"message\": \"Unauthorized\" }`.\n\n**Refresh a mobile session.** Exchanges a refresh token for a brand new\naccess and refresh token pair. The old refresh token stops working the\nmoment this succeeds.\n\n```bash\ncurl -X POST http://localhost:3000/auth/refresh \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"refreshToken\": \"6f9c2b8a...\"}'\n```\n\n```json\n{\n  \"success\": true,\n  \"message\": \"Session refreshed\",\n  \"data\": {\n    \"user\": { \"id\": \"b3f1...\", \"email\": \"jane@example.com\" },\n    \"accessToken\": \"eyJhbGciOi...\",\n    \"refreshToken\": \"a71fd400...\"\n  }\n}\n```\n\n**Request a password reset.** Always responds the same way, whether or not\nthe email exists, so it can't be used to check which emails have accounts.\n\n```bash\ncurl -X POST http://localhost:3000/auth/forgot-password \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"email\": \"jane@example.com\"}'\n```\n\n```json\n{ \"success\": true, \"message\": \"If that email exists, a reset code has been sent.\" }\n```\n\n**Reset the password.**\n\n```bash\ncurl -X POST http://localhost:3000/auth/reset-password \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"email\": \"jane@example.com\", \"code\": \"738201\", \"password\": \"a-new-password\"}'\n```\n\n```json\n{ \"success\": true, \"message\": \"Password reset successfully\" }\n```\n\n**Log out.** Include a `refreshToken` to also revoke a mobile session; it's\noptional. Web logout (with the cookie jar) clears the session cookie.\n\n```bash\ncurl -X POST http://localhost:3000/auth/logout \\\n  -b cookies.txt \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"refreshToken\": \"a71fd400...\"}'\n```\n\n```json\n{ \"success\": true, \"message\": \"Signed out\" }\n```\n\n## Configuration reference\n\n`createAuthRouter(config)` accepts:\n\n| Option                            | Required | Default                            | What it controls |\n| ---------------------------------- | -------- | ------------------------------------ | ----------------- |\n| `adapter`                          | yes      | (none)                               | Your database adapter. See [Bring your own database](#bring-your-own-database). |\n| `mailer`                           | yes      | (none)                               | Your email sender. See [Bring your own email provider](#bring-your-own-email-provider). |\n| `jwtSecret`                        | yes      | (none)                               | Secret used to sign session and access tokens. |\n| `cookieName`                       | no       | `\"auth_token\"`                       | Name of the web session cookie. |\n| `cookieSecure`                     | no       | `true` in production                 | Whether the cookie requires HTTPS. |\n| `cookieSameSite`                   | no       | `\"Lax\"`                              | Cookie `SameSite` attribute (`\"Lax\"`, `\"Strict\"`, or `\"None\"`). |\n| `webTokenExpiresIn`                | no       | `\"7d\"`                               | How long a web session lasts. |\n| `accessTokenExpiresIn`             | no       | `\"15m\"`                              | How long a mobile access token lasts before it needs refreshing. |\n| `refreshTokenDays`                 | no       | `90`                                 | How many days a mobile refresh token stays valid if unused. |\n| `verificationCodeCooldownSeconds`  | no       | `60`                                 | Minimum time between resend requests for the same account. |\n| `verificationCodeExpiryMinutes`    | no       | `15`                                 | How long a verification or reset code stays valid. |\n| `verificationCodeLength`           | no       | `6`                                  | Digits in a generated code. Ignored if `generateVerificationCode` is set. |\n| `generateVerificationCode`         | no       | a random numeric code generator      | Supply your own function (`() => string`) for full control over the code format. |\n| `formatSuccessResponse`            | no       | `{ success: true, message, data }`   | Reshape every successful JSON response. Receives `(statusCode, message, data)`. |\n| `formatErrorResponse`              | no       | `{ success: false, message, code }`  | Reshape every error JSON response. Receives `(statusCode, message, code)`. |\n| `setAuthCookie` / `clearAuthCookie`| no       | a single httpOnly cookie             | Override exactly how the web session cookie is written and cleared. |\n| `mapMeUser`                        | no       | `{ id, email }`                      | Add extra fields to what `GET /me` returns, for example `isVerified` or `createdAt`. |\n| `onEmailVerified`                  | no       | (none)                               | A function called after a signup code is confirmed for the first time. Never blocks or fails the response. |\n\n## Customization examples\n\nThe options above exist so this package can match **any** existing API\ncontract exactly. This is especially useful if you're adding this package to\nan app that already has its own response format, cookie scheme, or `/me`\nshape, and you don't want to change your frontend at all.\n\n**Match an existing API's response envelope.**\n\n```ts\ncreateAuthRouter({\n  adapter,\n  mailer,\n  jwtSecret: process.env.JWT_SECRET!,\n  formatSuccessResponse: (statusCode, message, data) => ({\n    status: statusCode,\n    ok: true,\n    message,\n    data,\n  }),\n  formatErrorResponse: (statusCode, message, code) => ({\n    status: statusCode,\n    ok: false,\n    message,\n    code,\n  }),\n});\n```\n\n**Set a second, readable cookie alongside the real session cookie.**\n\nSome frontends want a cheap way to know \"there might be a session\" before\ncalling `/me`, without being able to read the actual (httpOnly) token. You\ncan set an extra cookie yourself in `setAuthCookie` and `clearAuthCookie`:\n\n```ts\ncreateAuthRouter({\n  adapter,\n  mailer,\n  jwtSecret: process.env.JWT_SECRET!,\n  setAuthCookie: (res, token, cookieConfig) => {\n    res.setHeader('Set-Cookie', [\n      `${cookieConfig.name}=${token}; Path=/; HttpOnly; Max-Age=${Math.floor(cookieConfig.maxAgeMs / 1000)}`,\n      `has_session=1; Path=/; Max-Age=${Math.floor(cookieConfig.maxAgeMs / 1000)}`,\n    ]);\n  },\n  clearAuthCookie: (res, cookieConfig) => {\n    res.setHeader('Set-Cookie', [\n      `${cookieConfig.name}=; Path=/; HttpOnly; Max-Age=0`,\n      'has_session=; Path=/; Max-Age=0',\n    ]);\n  },\n});\n```\n\n**Use a different verification code format.**\n\n```ts\ncreateAuthRouter({\n  adapter,\n  mailer,\n  jwtSecret: process.env.JWT_SECRET!,\n  // Just change the length:\n  verificationCodeLength: 8,\n  // Or take full control of the format:\n  // generateVerificationCode: () => crypto.randomInt(1_000_000, 9_999_999).toString(),\n});\n```\n\n**Return extra fields from `GET /me`.**\n\n```ts\ncreateAuthRouter({\n  adapter,\n  mailer,\n  jwtSecret: process.env.JWT_SECRET!,\n  mapMeUser: (user) => ({\n    id: user.id,\n    email: user.email,\n    isVerified: user.isVerified,\n    memberSince: user.createdAt,\n  }),\n});\n```\n\n**Send yourself a notification when someone verifies their account.**\n\n```ts\ncreateAuthRouter({\n  adapter,\n  mailer,\n  jwtSecret: process.env.JWT_SECRET!,\n  onEmailVerified: (user) => {\n    // Fire and forget: this never blocks or fails the user's own request.\n    notifyTeamOfNewSignup(user.email).catch((err) =>\n      console.error('New signup notification failed', err)\n    );\n  },\n});\n```\n\n## Bring your own database\n\nThis package never talks to a database directly. You give it an `adapter`\nobject that knows how to read and write users and sessions. Implement the\n`AuthAdapter` interface:\n\n```ts\ninterface AuthAdapter {\n  findUserByEmail(email: string): Promise<User | null>;\n  findUserById(id: string): Promise<User | null>;\n  createUser(data: CreateUserInput): Promise<User>;\n  updateUser(id: string, patch: Partial<Omit<User, 'id'>>): Promise<void>;\n  createRefreshSession(data: CreateRefreshSessionInput): Promise<void>;\n  findRefreshSession(tokenHash: string): Promise<RefreshSession | null>;\n  revokeRefreshSession(tokenHash: string): Promise<void>;\n}\n```\n\nAll the types referenced above (`User`, `CreateUserInput`,\n`CreateRefreshSessionInput`, `RefreshSession`) are exported from this\npackage. See [`src/types.ts`](./src/types.ts) for the exact shapes.\n\nAlready available:\n\n- **[`@aizvi/auth-sqlite`](https://www.npmjs.com/package/@aizvi/auth-sqlite)**:\n  SQLite, including support for Node's built in `node:sqlite`, so you can\n  point it at a database connection you already have.\n\nWriting your own adapter for Postgres, MySQL, MongoDB, or anything else is\nusually a small, focused piece of code: a handful of queries mapped onto the\ninterface above.\n\n## Bring your own email provider\n\nSame idea. Implement `EmailSender`:\n\n```ts\ninterface EmailSender {\n  sendVerificationEmail(email: string, code: string): Promise<void>;\n  sendPasswordResetEmail(email: string, code: string): Promise<void>;\n}\n```\n\nWire it up to whatever you already use to send email: Nodemailer, Resend,\nSendGrid, Amazon SES, Postmark, or anything else that can send a plain\nemail.\n\n## Protecting your own routes\n\nEverything you need to check \"is this request authenticated?\" is exported,\nso you can protect routes outside of the auth router itself:\n\n```ts\nimport { authMiddleware } from '@aizvi/auth';\n\napp.get(\n  '/profile',\n  authMiddleware(process.env.JWT_SECRET!, 'auth_token'), // (jwtSecret, cookieName)\n  (req, res) => {\n    res.json({ userId: (req as any).user.id });\n  }\n);\n```\n\nIt checks the same cookie or `Authorization: Bearer` header the router\nitself uses, and responds `401` if the request isn't authenticated.\n\nIf you're customizing `formatErrorResponse` and want your own routes' `401`\nresponses to use that exact same shape, build the check yourself from the\nlower level pieces this package also exports, `getAuthCookie` and\n`verifyToken`, the same way `authMiddleware` does internally:\n\n```ts\nimport { getAuthCookie, verifyToken } from '@aizvi/auth';\n\nfunction myAuthMiddleware(req, res, next) {\n  const token =\n    getAuthCookie(req, 'auth_token') ||\n    (req.headers.authorization || '').replace(/^Bearer /, '');\n\n  const decoded = token ? verifyToken(token, process.env.JWT_SECRET!) : null;\n  if (!decoded) {\n    return res.status(401).json({ status: 401, ok: false, message: 'Unauthorized' });\n  }\n\n  req.user = { id: decoded.id };\n  next();\n}\n```\n\n## Errors\n\nAnything this package rejects with is an instance of `AuthError`, which has\n`.status` (an HTTP status code), `.message`, and an optional `.code` (for\nexample `\"EMAIL_NOT_VERIFIED\"` on a login attempt with an unverified\naccount). If you're using `createAuthService` directly (see below) instead\nof the router, catch `AuthError` to handle these the same way the router\ndoes internally.\n\n## Using the core logic without Express\n\nIf you're not using Express, or want to expose this over a different\ntransport (GraphQL, tRPC, a CLI, and so on), use `createAuthService`\ndirectly. It has no dependency on Express at all:\n\n```ts\nimport { createAuthService } from '@aizvi/auth';\n\nconst auth = createAuthService({\n  adapter,\n  mailer,\n  jwtSecret: process.env.JWT_SECRET!,\n  webTokenExpiresIn: '7d',\n  accessTokenExpiresIn: '15m',\n  refreshTokenDays: 90,\n  verificationCodeCooldownSeconds: 60,\n  verificationCodeExpiryMinutes: 15,\n});\n\nawait auth.signup({ email, password });\nawait auth.login({ email, password });\n// ...and so on. These are the same operations the router's endpoints call internally.\n```\n\n## TypeScript\n\nWritten in TypeScript. Type definitions are included, no `@types` package\nneeded. Works from plain JavaScript too.\n\n## Code of Conduct\n\nSee [CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md).\n\n## License\n\nMIT (see [license.txt](./license.txt))\n","readmeFilename":"README.md"}