Skip to content

DayveLite/servio

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Servio — Authentication System

Complete user authentication for the Servio platform.


What's included

Layer Tech Details
Backend Node.js + Express REST API, JWT auth, email OTP
Database PostgreSQL + Prisma 3 models: User, EmailVerification, RefreshToken
Frontend Next.js 14 (App Router) Register, Login, Verify Email, Dashboard
Email Nodemailer Ethereal auto-account in dev, SMTP in prod
Security bcrypt, JWT rotation, httpOnly cookies, Helmet, rate limiting

Project structure

servio/
├── backend/
│   ├── prisma/
│   │   └── schema.prisma           # DB schema (3 models)
│   ├── src/
│   │   ├── config/
│   │   │   ├── database.js         # Prisma client
│   │   │   └── env.js              # Zod-validated env vars
│   │   ├── middleware/
│   │   │   ├── authenticate.js     # JWT middleware + authorize()
│   │   │   └── errorHandler.js     # Centralized error handler
│   │   ├── modules/auth/
│   │   │   ├── auth.service.js     # All business logic
│   │   │   ├── auth.controller.js  # HTTP layer + cookie management
│   │   │   ├── auth.routes.js      # Route definitions
│   │   │   └── auth.validators.js  # Zod schemas + validate() factory
│   │   └── utils/
│   │       ├── asyncHandler.js     # Wraps async routes
│   │       ├── email.js            # Nodemailer + HTML templates
│   │       └── jwt.js              # Token generation + hashing
│   ├── .env.example
│   └── package.json
│
└── frontend/
    └── src/
        ├── app/
        │   ├── (auth)/
        │   │   ├── layout.jsx        # Split panel auth layout
        │   │   ├── register/         # Registration form
        │   │   ├── login/            # Login form
        │   │   └── verify-email/     # OTP input (6-digit)
        │   ├── dashboard/            # Protected page
        │   └── layout.jsx            # Root layout + AuthProvider
        ├── components/auth/
        │   ├── FormInput.jsx         # Input w/ label, error, password toggle
        │   └── PasswordStrength.jsx  # Live password strength checker
        ├── contexts/
        │   └── AuthContext.jsx       # Global auth state + actions
        └── lib/
            └── api.js                # Axios client + silent token refresh

Quick start

1. Prerequisites

  • Node.js 18+
  • PostgreSQL running locally
  • Git

2. Backend setup

cd servio/backend

# Copy and fill in env vars
cp .env.example .env

Edit .env and set at minimum:

DATABASE_URL="postgresql://postgres:yourpassword@localhost:5432/servio_db"
ACCESS_TOKEN_SECRET=generate_a_64_char_random_string_here
REFRESH_TOKEN_SECRET=generate_another_64_char_random_string_here

Generate secrets quickly:

node -e "console.log(require('crypto').randomBytes(64).toString('hex'))"
# Install dependencies
npm install

# Create the database tables
npx prisma migrate dev --name init

# (Optional) Open Prisma Studio to inspect DB
npx prisma studio

# Start the API server
npm run dev

API will run on http://localhost:5000

In development, when the first email is sent, Nodemailer will print an Ethereal preview URL to the terminal — click it to see the OTP email without needing real SMTP.

3. Frontend setup

cd servio/frontend

# Install dependencies
npm install

# Start Next.js dev server
npm run dev

Frontend will run on http://localhost:3000


API endpoints

All endpoints prefixed: /api/v1/auth

Method Path Auth Description
POST /register Register new user
POST /verify-email Verify email with OTP
POST /resend-verification Resend OTP
POST /login Login → access token + cookie
POST /logout Revoke refresh token + clear cookie
POST /refresh-token cookie Rotate tokens
GET /me Bearer Get current user

Request/response examples

Register

POST /api/v1/auth/register
{
  "fullName": "Alex Johnson",
  "email": "alex@example.com",
  "password": "SecurePass1",
  "confirmPassword": "SecurePass1"
}

→ 201
{
  "success": true,
  "data": {
    "email": "alex@example.com",
    "message": "Registration successful. Please check your email."
  }
}

Verify email

POST /api/v1/auth/verify-email
{ "email": "alex@example.com", "code": "482910" }

→ 200
{
  "success": true,
  "data": {
    "user": { "id": "...", "email": "...", "fullName": "...", "role": "USER", "isVerified": true },
    "accessToken": "eyJ..."
  }
}
// + Set-Cookie: servio_refresh=...; HttpOnly; SameSite=Lax

Login

POST /api/v1/auth/login
{ "email": "alex@example.com", "password": "SecurePass1" }

→ 200 (same shape as verify-email response)

Error shape

→ 400/401/409
{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Validation failed",
    "fields": {
      "email": "Please enter a valid email address",
      "password": "Password must contain at least one number"
    }
  }
}

Security decisions

Concern Implementation
Password storage bcrypt, cost factor 12
Access token JWT, 15-min expiry, in-memory on client
Refresh token Random 64-byte hex, SHA-256 hashed in DB, 7-day expiry
Refresh token rotation Old token revoked on every refresh; replay detection revokes all user tokens
HTTP-only cookie Refresh token never accessible via JS (XSS protection)
Timing attack prevention Password check always runs even if user not found
Rate limiting Auth routes: 20 req/15min; General: 100 req/min
Input validation Zod schemas on every endpoint before controller runs
Error information leakage Generic 404/401 messages that don't reveal email existence

User flow

Register → Email sent with 6-digit OTP → Enter OTP on /verify-email
→ Logged in automatically → /dashboard

Login → /dashboard (or verify-email page if unverified)

On page refresh → silent refresh via HTTP-only cookie → session restored

Logout → refresh token revoked server-side → cookie cleared

Adding to existing protect routes

const authenticate = require('./middleware/authenticate');
const { authorize } = require('./middleware/authenticate');

// Require any logged-in user
router.get('/profile', authenticate, controller.getProfile);

// Require specific role
router.get('/admin/users', authenticate, authorize('ADMIN'), controller.listUsers);

Next module: Professional registration

The schema already has role: ENUM with PROFESSIONAL and ADMIN values. Next step is adding the professional_profiles table and a separate onboarding flow that routes to the admin approval queue.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages