feat: refactor and refine authentication system with decoupled user management and admin console
This commit is contained in:
@@ -1,92 +1,129 @@
|
||||
# Stick: Use-Case Oriented Axum + Askama + MongoDB Template
|
||||
# Stick
|
||||
|
||||
A production-ready Rust web application template organized by vertical features (use-cases) rather than horizontal technical layers.
|
||||
Stick is a Rust web application starter template built on Axum, Askama, and MongoDB. Unlike traditional MVC architectures that organize code by technical layers (controllers, models, views), Stick is organized by **vertical slices** (features or use-cases). All files related to a specific domain feature—such as authentication or task management—live together in a single module.
|
||||
|
||||
This setup is ideal for medium-to-large projects where horizontal layers become hard to navigate, and compiling templates at runtime is too risky.
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ Technology Stack
|
||||
* **Web Framework**: [Axum](https://github.com/tokio-rs/axum) (v0.8) - Native RPITIT (Return-Position Impl Trait in Traits) extractors.
|
||||
* **Template Engine**: [Askama](https://github.com/djc/askama) (v0.16) - Type-safe, compile-time HTML templates.
|
||||
* **Styling**: [Tailwind CSS](https://tailwindcss.com/) - Modern utility-first styling.
|
||||
* **Database**: [MongoDB Rust Driver](https://github.com/mongodb/mongo-rust-driver) (v3.7) - Configured with BSON v3.
|
||||
* **Authentication/Authorization**: [jsonwebtoken](https://github.com/Keats/jsonwebtoken) (v10) with the `rust_crypto` backend, stored in secure `HttpOnly` session cookies.
|
||||
* **Password Hashing**: [bcrypt](https://github.com/Keats/rust-bcrypt) for secure password storage.
|
||||
## Technical Architecture
|
||||
|
||||
### 1. Vertical Slice Layout
|
||||
Each feature slice is self-contained. For example, the authentication domain has the following structure:
|
||||
|
||||
* `src/auth/models.rs`: BSON data structures and claims.
|
||||
* `src/auth/repository.rs`: Database operations and query logic.
|
||||
* `src/auth/handlers.rs`: Request/response lifecycle logic.
|
||||
* `src/auth/extractors.rs`: Axum extractors for session state.
|
||||
* `templates/auth/`: HTML markup templates rendered at compile-time.
|
||||
|
||||
### 2. Key Stack Decisions
|
||||
* **Axum (v0.8)**: Handles routing, middleware, and request extraction.
|
||||
* **Askama (v0.16)**: Evaluates and compiles HTML templates into Rust code at compile time. If you reference a variable or field that doesn't exist, the project fails to compile, catching UI rendering bugs before deployment.
|
||||
* **MongoDB**: Standard Rust driver configured with BSON serialization.
|
||||
* **Tailwind CSS**: Pre-compiled utility styling using a Node-based wrapper process.
|
||||
* **Authentication**: Managed via JWT (JSON Web Tokens) stored in secure, encrypted `HttpOnly` cookies.
|
||||
|
||||
---
|
||||
|
||||
## 📁 Use-Case Centered Project Layout
|
||||
Unlike traditional MVC setups, files are grouped by their business domain. A single use-case directory contains its models, database repositories, route handlers, local extractors, and templates.
|
||||
## Core Features Included
|
||||
|
||||
### Self-Provisioning Administrator
|
||||
On startup, the application checks if the `users` collection in the MongoDB database is empty. If no users are found, it generates a secure, random 16-character alphanumeric password, hashes it with bcrypt, and creates a default `admin` account. The credentials are logged directly to standard output:
|
||||
|
||||
```text
|
||||
stick/
|
||||
├── Cargo.toml
|
||||
├── .env.example
|
||||
├── templates/ # Raw HTML template layout files
|
||||
│ ├── base.html # Global layout wrapper
|
||||
│ ├── auth/ # Auth views
|
||||
│ │ ├── login.html
|
||||
│ │ └── register.html
|
||||
│ ├── tasks/ # Task manager views
|
||||
│ │ └── dashboard.html
|
||||
│ └── main_view/ # Main views
|
||||
│ └── index.html
|
||||
└── src/
|
||||
├── main.rs # Server composition, shared state, and route merging
|
||||
├── common/ # Shared features (errors, database, settings)
|
||||
│ ├── config.rs
|
||||
│ ├── database.rs
|
||||
│ └── errors.rs
|
||||
├── auth/ # Authentication & User Management Use-Case
|
||||
│ ├── extractors.rs # Session context extractors
|
||||
│ ├── handlers.rs # User interaction handlers
|
||||
│ ├── models.rs # User database schemas
|
||||
│ └── repository.rs # User database actions
|
||||
├── tasks/ # Tasks & Dashboard Management Use-Case
|
||||
│ ├── handlers.rs # Task CRUD handlers
|
||||
│ ├── models.rs # Task database schemas
|
||||
│ └── repository.rs # Task database actions
|
||||
└── main_view/ # Static Navigation & Branding Use-Case
|
||||
└── mod.rs # Serves index & handles public routes
|
||||
======================================================
|
||||
CREATED INITIAL ADMINISTRATOR ACCOUNT:
|
||||
Username: admin
|
||||
Password: [GeneratedPassword]
|
||||
======================================================
|
||||
```
|
||||
|
||||
### Decoupled Identity vs. Domain Entities
|
||||
Stick strictly separates infrastructure/user models from business domain models:
|
||||
* **Users** (`User`): Manage authentication, roles (`is_admin`), and settings under `/auth`.
|
||||
* **Developers** (`Developer`): Plain domain entities managed under `/developers` that represent team members.
|
||||
|
||||
### User Management Panel (Administrators Only)
|
||||
Accessible under `/auth/users` by logged-in administrators. The panel allows:
|
||||
* Viewing all registered users and their administrative permissions.
|
||||
* Editing user profiles, resetting passwords, and toggling administrator roles.
|
||||
* Deleting users (with safeguards preventing administrators from deleting their own active accounts or revoking their own admin permissions).
|
||||
* Registering new users.
|
||||
|
||||
### Self-Service Account Settings
|
||||
Any authenticated user can change their own password by clicking their username in the navigation bar, which directs them to `/auth/password`.
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Setup & Execution
|
||||
## Setup and Running
|
||||
|
||||
### 1. Prerequisites
|
||||
* [Rust](https://www.rust-lang.org/tools/install) (v1.75+ required for native async traits)
|
||||
* [MongoDB](https://www.mongodb.com/) running locally (port `27017`)
|
||||
### Prerequisites
|
||||
* **Rust**: Toolchain v1.75+ (for native async traits).
|
||||
* **Node.js & npm**: Required to build Tailwind CSS.
|
||||
* **MongoDB**: Running locally on `mongodb://localhost:27017`.
|
||||
|
||||
### 2. Configuration
|
||||
Copy the configuration example file and customize your settings:
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
### Local Setup
|
||||
1. Copy the environment configuration:
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
2. Build Tailwind CSS styling:
|
||||
```bash
|
||||
npm install
|
||||
npx tailwindcss -i src/input.css -o static/tailwind.css
|
||||
```
|
||||
3. Run the development server:
|
||||
```bash
|
||||
cargo run
|
||||
```
|
||||
The server will start listening at `http://127.0.0.1:3000`.
|
||||
|
||||
### 3. Run the Server
|
||||
Start the development server:
|
||||
```bash
|
||||
cargo run
|
||||
```
|
||||
The server will start listening at `http://127.0.0.1:3000`.
|
||||
### Running with Docker
|
||||
A multi-stage `Dockerfile` is provided to compile Tailwind, compile the Rust binary, and bundle a lightweight Debian run container.
|
||||
|
||||
1. Build the image:
|
||||
```bash
|
||||
docker build -t stick .
|
||||
```
|
||||
2. Start the container (assumes MongoDB is running on the host machine):
|
||||
```bash
|
||||
docker run --name stick-app --rm --network="host" \
|
||||
-e DATABASE_URL="mongodb://127.0.0.1:27017" \
|
||||
-e DATABASE_NAME="stick_db" \
|
||||
-e JWT_SECRET="super_secret_template_signing_key_that_is_at_least_32_characters_long" \
|
||||
-e HOST="127.0.0.1" \
|
||||
-e PORT="3009" \
|
||||
stick
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 💡 Designing Custom Use-Cases
|
||||
When adding a new feature (e.g., `projects`):
|
||||
## Developer Guide: Adding a Feature Slice
|
||||
|
||||
1. Create `src/projects/` containing:
|
||||
* `models.rs` (BSON schemas)
|
||||
* `repository.rs` (Database access)
|
||||
* `handlers.rs` (Endpoints)
|
||||
* `mod.rs` (Usecase module entrypoint exposing a `pub fn router<S>() -> Router<S>`)
|
||||
2. Add its view templates under `templates/projects/`.
|
||||
3. Expose the repository and compile constraints in `src/main.rs`.
|
||||
4. Merge the usecase router inside the main router builder:
|
||||
```rust
|
||||
let app = Router::new()
|
||||
.merge(main_view::router())
|
||||
.merge(auth::router())
|
||||
.merge(projects::router()) // Custom vertical router
|
||||
.with_state(state);
|
||||
```
|
||||
To add a new feature (e.g. `projects`):
|
||||
|
||||
1. Create a module folder: `src/projects/`.
|
||||
2. Define models in `models.rs` and database access functions in `repository.rs`.
|
||||
3. Add request handlers in `handlers.rs`.
|
||||
4. Create a router configuration in `src/projects/mod.rs` exposing a routing module setup:
|
||||
```rust
|
||||
pub fn router<S>() -> Router<S>
|
||||
where
|
||||
Config: axum::extract::FromRef<S>,
|
||||
MongoUserRepository: axum::extract::FromRef<S>,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
Router::new()
|
||||
.route("/projects", get(handlers::get_projects))
|
||||
}
|
||||
```
|
||||
5. Place HTML layouts under `templates/projects/` extending the `base.html` layout.
|
||||
6. Register and merge the router in `src/main.rs`:
|
||||
```rust
|
||||
let app = Router::new()
|
||||
.merge(main_view::router())
|
||||
.merge(auth::router())
|
||||
.merge(projects::router()) // Merged domain router
|
||||
.with_state(state);
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user