1
Fork 0
rust-crud-actix-mongo-api/README.md
Leonardo Devai 32c15accba Review and modernize all 42 projects to the updated standard
Pinned 2026 toolchains (Go 1.26, Rust 1.98/edition 2024, Python 3.14 + uv, Node 24,
Zig 0.16, NixOS 26.05), postgres 18 / mongo 8, lockfiles built from, non-root
runtimes, .dockerignore, per-project LICENSE, READMEs with the git.devai.io clone
line, checkout@v7 CI. Security fixes in the legacy Rust APIs (any-password login,
self-assigned admin, hard-coded JWT secret), JWT alg/exp/sub enforcement across the
blog series, safe markdown links in the frontends, and many smaller bugs — every
project was built, run and exercised end to end.

Adds scripts/publish.sh + a CI publish job that splits every folder into its own
repo at git.devai.io/templates/<folder>.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01128fhuZbgivaSJvtMf4s1G
2026-09-27 21:10:38 +02:00

82 lines
4.8 KiB
Markdown

# rust-crud-actix-mongo-api
A JWT-secured REST API in Rust with actix-web 4 and MongoDB: bcrypt passwords,
access + refresh tokens read from an HttpOnly cookie or an `Authorization: Bearer`
header, a `CurrentUser` extractor, and role checks (`User` / `Admin`) on protected routes.
## Run
git clone https://git.devai.io/templates/rust-crud-actix-mongo-api.git
cd rust-crud-actix-mongo-api
docker compose up --build
The API answers on http://localhost:8080 (`curl localhost:8080/health` → `ok`).
MongoDB keeps its state in `./data/mongo`; the unique indexes on `users.email` and
`users.username` are ensured on every start.
Without Docker: run a MongoDB, export the variables from `.env.example`, then
`cargo run` (Rust 1.98, the toolchain the Dockerfile pins).
## How it works
| Method | Path | Auth | Result |
|--------|-------------------------|------------|------------------------------------------------------------------|
| GET | `/health` | — | `200 ok` |
| POST | `/api/user/create` | — | `{email, username, password}` → `201` user, `409` if taken |
| POST | `/api/auth/login` | — | `{email, password}` → `200 {email, username, roles, tokens}` + `token` cookie, `401` if wrong |
| POST | `/api/auth/refresh` | — | `{refresh_token}` → `200` new token pair, `401` if used/revoked |
| GET | `/api/auth/validate` | any role | `200` current user, `401` without a valid token |
| GET | `/api/user/{username}` | any role | `200` user — yourself, or anyone if you are `Admin` (`403` otherwise) |
| GET | `/api/public` | optional | `200 {username, endpoint_security}`, `username` is `null` when anonymous |
| GET | `/api/protected/user` | any role | `200`, or `401` |
| GET | `/api/protected/admin` | `Admin` | `200`, `401` without a token, `403` for a plain `User` |
- **Tokens** — login returns an access token (15 min) and a refresh token (7 days),
both HS256 JWTs signed with `AUTH_SECRET`. Verification pins HS256 and requires
`exp`, so `alg: none` or re-signed tokens are rejected.
- **Sessions** — each token carries the user's current `session_id`. Logging in again
or refreshing replaces it, which revokes every older token; a refresh token works once.
- **`CurrentUser`** (`src/security.rs`) — an actix extractor: reads the Bearer header
or the `token` cookie, verifies the JWT, and loads the user from MongoDB. Put it in
a handler's arguments to require login, `Option<CurrentUser>` to make it optional,
and call `current_user.require(Role::Admin)?` for a role check.
- **Passwords** — bcrypt (cost 12), hashed on actix's blocking pool so slow hashing
never stalls request handling.
- **Roles** — sign-up always creates a `User`. Promote an admin on the database;
roles are read from MongoDB on every request, so it applies immediately:
docker compose exec db mongosh demo --eval \
'db.users.updateOne({username: "admin"}, {$set: {roles: ["User", "Admin"]}})'
- **Errors** are always JSON: `{"error": "message"}`.
Try it:
curl -X POST localhost:8080/api/user/create -H 'content-type: application/json' \
-d '{"email":"user@test.com","username":"user","password":"supersecret"}'
TOKEN=$(curl -s localhost:8080/api/auth/login -H 'content-type: application/json' \
-d '{"email":"user@test.com","password":"supersecret"}' | jq -r .tokens.access_token)
curl -H "Authorization: Bearer $TOKEN" localhost:8080/api/protected/user # 200
curl -H "Authorization: Bearer $TOKEN" localhost:8080/api/protected/admin # 403
## Layout
src/main.rs env, MongoDB connection, routes
src/security.rs JWT keys, bcrypt, the CurrentUser extractor
src/auth/service.rs login, refresh and token → user, with session rotation
src/users/service.rs sign-up, lookup, unique indexes
src/test_controller.rs the public / user / admin demo endpoints
src/errors.rs ApiError → {"error": ...} responses
## Deploy
Push to your own GitHub repo and the shipped workflow
(`.github/workflows/ci.yml`) tests the compose stack, publishes the image to
GHCR, and — once you set the `DEPLOY_HOST` / `DEPLOY_USER` variables and
`DEPLOY_KEY` secret — deploys it to your server over ssh. Set a long random
`AUTH_SECRET` on the server; the one in `compose.yaml` is for local use only.
---
Part of [devai.io](https://devai.io) — Rust API boilerplates, alongside
[`rust-crud-sql-api`](https://git.devai.io/templates/rust-crud-sql-api) and
[`rust-crud-nosql-api`](https://git.devai.io/templates/rust-crud-nosql-api).