尚未检测到更新。GitHub 发布版本后会显示在这里。
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证Secure email and password authentication: sign-up with email verification, sign-in with rotating sessions, and password reset. Built with Express, TypeScript and PostgreSQL.
翻译准备中
取自仓库 README
正在加载完整 README。
查看完整 README ↗A production-minded authentication API built with Express 5, TypeScript and PostgreSQL. It handles the full account lifecycle: sign-up with email verification, cookie-based sessions with refresh tokens, password reset and change, and profile avatars stored on ImageKit.
Building a frontend against this API? Go straight to the API reference. Deploying? See docs/DEPLOYMENT.md (Hostinger VPS, Docker Compose, GitHub Actions). New to auth or web hosting? The glossary explains every term the docs use, and DEVLOG / LEARNINGS tell the story of how this was built and what went wrong.
Live demo: ckauth.chiragdev.com
The repo also contains the CKAuth web app in frontend/ (React + Vite + Tailwind), which implements every flow in the API.
httpOnly cookies.user or an admin. Admins can search, invite and delete users at /admin, after confirming their password. Invited people choose their own password from an emailed link. Admins themselves are made and removed only from the server (pnpm admin), never through the website./admin. Old events are deleted automatically (90 days for sign-ins, a year for the rest).X-Request-Id), names requests by their route template so one-time tokens never reach the logs, and never logs headers, cookies, bodies or email addresses.| Area | Tools |
|---|---|
| Runtime | Node.js, TypeScript, Express 5 |
| Database | PostgreSQL 17 (Docker), Drizzle ORM |
| Validation | Zod |
| Auth | JSON Web Tokens (jsonwebtoken), native bcrypt (hashes off the main thread) |
| Rate limiting | express-rate-limit with a Redis store (rate-limit-redis) |
| Resend | |
| File uploads | Multer (in-memory), ImageKit |
| Frontend | React 19, Vite, Tailwind CSS 4, React Router |
| Hosting | Docker Compose on a Hostinger VPS behind a shared Caddy edge proxy (HTTPS), GitHub Actions → GHCR |
corepack enable to use the version pinned in package.jsonpnpm install
cp .env.sample .env
Then open .env and fill in the values:
| Variable | Required | Description |
|---|---|---|
PORT | No | Port the API listens on. Default: 8080. |
NODE_ENV | No | development or production. In production, cookies are sent over HTTPS only. |
DATABASE_URL | Yes | PostgreSQL connection string. The sample value matches docker-compose.yml. |
ACCESS_TOKEN_SECRET_KEY | Yes | Secret used to sign access tokens. |
REFRESH_TOKEN_SECRET_KEY | Yes | Secret used to sign refresh tokens. Must be different from the access token secret. |
ACCESS_TOKEN_EXPIRY | Yes | Access token lifetime, e.g. 15m. |
REFRESH_TOKEN_EXPIRY | Yes | Refresh token lifetime, e.g. 1d. |
APP_NAME | No | Product name shown in emails. Default: CKAuth. |
RESEND_API_KEY | Yes | Resend API key (starts with re_). The server won't start without it. |
EMAIL_FROM | Yes | Sender address on a domain verified in Resend, e.g. "CKAuth <[email protected]>". The server won't start without it. No domain is hard-coded, so changing mail domains is a config change. |
FRONTEND_URL | Yes | Base URL of your frontend. Links in emails point here. Default: http://localhost:3000. |
IMAGEKIT_PRIVATE_KEY | For avatars | ImageKit private API key. The server starts without it; only avatar uploads fail. |
REDIS_URL | No | Redis for rate-limit counts, e.g. redis://localhost:6379. Without it, limits are counted in the process's memory (fine for one local process). |
LOG_LEVEL | No | How much the API logs: debug, info, or . Default: . The API always writes JSON lines; pipes them through pino-pretty so they're readable in a terminal. |
Generate strong JWT secrets with:
node -e "console.log(require('crypto').randomBytes(64).toString('hex'))"
pnpm db:up # starts PostgreSQL and Redis in Docker
pnpm db:migrate # creates the tables
pnpm dev
The API is now running at http://localhost:8080. A quick check:
curl http://localhost:8080/
# {"message":"Express Application is running"}
cd frontend
pnpm install
pnpm dev
Open http://localhost:3000. The Vite dev server proxies /api/* to the API on port 8080 (stripping /api), exactly like Caddy does in production. Keep FRONTEND_URL=http://localhost:3000 in .env so email links open this app.
| Command | What it does |
|---|---|
pnpm dev | Compiles in watch mode and restarts the server on every change. |
pnpm build | Compiles TypeScript to dist/. Also the project's type check. |
pnpm start | Runs the compiled server from dist/. |
pnpm db:up / pnpm db:down | Starts / stops the PostgreSQL and Redis containers. |
pnpm db:generate | Creates a new migration after you change src/db/schema.ts. |
pnpm db:migrate | Applies pending migrations to the database. |
pnpm db:migrate:prod | Applies migrations with Drizzle's runtime migrator (dist/db/migrate.js); used by deploys. |
pnpm admin <command> | Manages roles from the command line, after pnpm build: role <email> <user|admin>, delete <email>, admins. Use pnpm admin role [email protected] admin to make yourself an admin. |
pnpm seed:activity | Development only, after pnpm build: adds six fake users (@seed.example.com) and a month of activity-log events, so /admin has something to show. --clear removes them. Refuses to run when NODE_ENV=production. |
pnpm drizzle:studio | Opens Drizzle Studio to browse the database. |
pnpm test:coverage | Runs the tests and fails if coverage drops below 95% of lines, 83% of branches or 85% of functions. CI runs this before every API deploy. |
pnpm test | Runs the test suite (103 tests and 5 to-dos for known gaps, about 25 s). Needs Postgres running (pnpm db:up); it creates and uses its own ckauth_test database, so your development data is never touched. |
pnpm test:types | Type checks the tests (the runner itself skips type checking). |
cd frontend && pnpm test | The frontend's unit tests (form rules, error messages, the API client's refresh). |
Tests: integration tests send real HTTP requests to the app and check the database afterwards; unit tests check single functions. Emails aren't sent during tests: they're kept in memory, and a test reads the verification or reset link from there, just as a user would click it. To run a single file: node --import tsx --import ./test/env.ts --test-global-setup=test/global-setup.ts --test test/admin.test.ts.
Changing the database schema: edit src/db/schema.ts → pnpm db:generate → review the SQL in drizzle/ → pnpm db:migrate → commit the schema and the migration together.
src/
├── index.ts # Entry point: starts the HTTP server
├── app/
│ ├── index.ts # Express app: middleware, routes, error handler
│ ├── auth/
│ │ ├── routes.ts # Endpoint → middleware → controller wiring
│ │ ├── controller.ts # Request handlers (AuthController)
│ │ ├── models.ts # Zod schemas for request bodies
│ │ ├── middleware.ts # authenticateToken (reads the access token cookie), authorizeRoles
│ │ └── utils/ # JWT helpers, cookie options, frontend URL, user roles
│ ├── admin/ # /admin endpoints: stats, list, add and delete users, activity log (admins only)
│ └── middlewares/
│ ├── error-handler.ts # Turns errors into JSON responses
│ ├── rate-limit.ts # Rate limiters, shared through Redis
│ ├── request-logger.ts # One structured log line per request, plus the X-Request-Id header
│ └── upload-multer.ts # Avatar upload parsing and validation
├── scripts/
│ ├── manage-users.ts # `pnpm admin`: make, demote or delete admins from the server
│ └── seed-activity.ts # `pnpm seed:activity`: fake activity for local development
├── db/
│ ├── index.ts # Drizzle database client
│ ├── migrate.ts # Runtime migrator used in production
│ └── schema.ts # Table definitions
└── utils/
├── api-error.ts # ApiError: errors with an HTTP status
├── api-response.ts # ApiResponse: consistent success responses
├── logger.ts # The pino logger: JSON lines, LOG_LEVEL, redaction
├── mail.ts # Email sending and templates
└── imagekit.ts # ImageKit upload and delete
test/ # Test suite: one file per area, helpers, test database setup
scripts/check-pr.js # `pnpm check:pr`: runs every pre-PR check (rules in scripts/check-repo.js, frontend/scripts/check-dist.js)
drizzle/ # Generated SQL migrations
docs/API.md # API reference
docs/DEPLOYMENT.md # Production setup, deploys, rollbacks, backups
Dockerfile # API image
frontend/ # React app; frontend/Dockerfile + Caddyfile build the web image (internal static server)
deploy/ # CKAuth compose stack, deploy script, site config, env templates, VPS setup
deploy/edge/ # Shared Caddy proxy for every site on the VPS (owns 80/443)
.github/workflows/ # api.yml and web.yml build and deploy independently via deploy.yml
/verify-email?token=… page. The user enters their password, and the frontend sends it to the API with the token. Asking for the password proves that the person who owns the inbox is the same person who signed up, so a successful verification also signs the user in.httpOnly cookies:
access_token: short-lived, sent with every request.refresh_token: longer-lived, used only to get a new access token./auth/refresh. Both tokens are replaced, and the old refresh token stops working.Cookies are httpOnly and SameSite=Lax, and Secure in production. Frontend code never reads or stores tokens itself.
Two consequences:
Lax cookies across different sites. Production uses one origin: ckauth.chiragdev.com serves the app and ckauth.chiragdev.com/api/* is proxied to the API.ACCESS_TOKEN_EXPIRY).These are planned but not done yet:
500 instead of 400/413.warnerrorinfopnpm devpnpm e2e | Browser end-to-end tests (Playwright): builds the API and the frontend, then drives a real browser through sign-up and sign-in on Chromium, Firefox and a phone-sized Chromium (WebKit too, once its system libraries are installed with sudo pnpm exec playwright install-deps webkit). Needs Postgres running; uses its own ckauth_e2e database, port 8090 for the API and 4180 for the site. First time: pnpm exec playwright install. |
pnpm perf | Measures the live site (page-load times on desktop and a phone on slow 4G, plus API round trips) and appends one line to perf-data/results.jsonl. Read-only against production; the weekly GitHub workflow runs it. |
pnpm perf:report | Prints the latest measurement against the previous one, with trend charts, as Markdown. |
pnpm check:pr | Every check a branch must pass before a pull request: both builds, unused code (knip), known vulnerabilities (pnpm audit), the tests, SEO, speed and security of the built site, repo rules (secrets, security rules, files that change together) and links between docs. |
pnpm brand:images | Renders the brand SVGs to PNG: the link-preview image, the app icons, this README's banner and the social profile kit. The brand guide lists every file and where it's used. |
pnpm docs:artifact | Bundles every doc into one searchable page (.docs-artifact/ckauth-docs.html) and warns about broken links between docs. |