Skip to content

Commit a10ba33

Browse files
starbopsclaude
andcommitted
feat: implement complete JWT authentication system with enhanced security
Implement a production-ready JWT authentication system with comprehensive security features, rate limiting, and enhanced password validation. ## Core Authentication Features - User registration and login with JWT tokens - Access tokens (15-minute expiration) and refresh tokens (7-day expiration) - Secure token refresh mechanism without re-authentication - User profile endpoint with authentication middleware ## Security Enhancements - Enhanced password validation requiring uppercase, lowercase, digits, and special characters - Secure bcrypt password hashing with proper salt rounds - JWT token validation with issuer and audience verification - Rate limiting on authentication endpoints (5 reg/hour, 10 login/hour, 100 refresh/hour) - Proper error handling without information leakage ## Technical Implementation - JWT service with token generation, validation, and refresh logic - Authentication service with business logic separation - Authentication middleware for route protection - Rate limiting middleware with in-memory store - Structured error responses and logging ## Configuration & Documentation - JWT configuration externalized to environment variables - Updated .env.example with JWT settings and production warnings - Comprehensive API documentation for all auth endpoints - Complete test coverage for JWT and authentication services ## API Endpoints Added - POST /api/v1/auth/register - User registration - POST /api/v1/auth/login - User authentication - POST /api/v1/auth/refresh - Token refresh - POST /api/v1/auth/logout - User logout - GET /api/v1/auth/me - Get current user (protected) ## Dependencies Added - github.com/golang-jwt/jwt/v5 for JWT token handling - Enhanced golang.org/x/crypto usage for secure password hashing Resolves #4 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 5685216 commit a10ba33

19 files changed

Lines changed: 1829 additions & 22 deletions

File tree

.env.example

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,12 @@ LOG_FORMAT=json
1818
# CORS Configuration
1919
CORS_ALLOWED_ORIGINS=http://localhost:3000,http://localhost:5173
2020
CORS_ALLOWED_METHODS=GET,POST,PUT,DELETE,OPTIONS
21-
CORS_ALLOWED_HEADERS=Content-Type,Authorization,X-Request-ID
21+
CORS_ALLOWED_HEADERS=Content-Type,Authorization,X-Request-ID
22+
23+
# JWT Configuration
24+
# IMPORTANT: Change these values in production!
25+
JWT_SECRET_KEY=your-secret-key-change-in-production-256-bits-minimum
26+
JWT_ACCESS_TOKEN_DURATION=15m
27+
JWT_REFRESH_TOKEN_DURATION=168h
28+
JWT_ISSUER=voidrunner
29+
JWT_AUDIENCE=voidrunner-api

api

25.7 MB
Binary file not shown.

cmd/api/main.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212

1313
"github.com/gin-gonic/gin"
1414
"github.com/voidrunnerhq/voidrunner/internal/api/routes"
15+
"github.com/voidrunnerhq/voidrunner/internal/auth"
1516
"github.com/voidrunnerhq/voidrunner/internal/config"
1617
"github.com/voidrunnerhq/voidrunner/internal/database"
1718
"github.com/voidrunnerhq/voidrunner/pkg/logger"
@@ -60,12 +61,18 @@ func main() {
6061

6162
log.Info("database initialized successfully")
6263

64+
// Initialize JWT service
65+
jwtService := auth.NewJWTService(&cfg.JWT)
66+
67+
// Initialize authentication service
68+
authService := auth.NewService(repos.Users, jwtService, log.Logger, cfg)
69+
6370
if cfg.IsProduction() {
6471
gin.SetMode(gin.ReleaseMode)
6572
}
6673

6774
router := gin.New()
68-
routes.Setup(router, cfg, log, repos)
75+
routes.Setup(router, cfg, log, repos, authService)
6976

7077
srv := &http.Server{
7178
Addr: fmt.Sprintf("%s:%s", cfg.Server.Host, cfg.Server.Port),

docs/auth-api.md

Lines changed: 256 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
1+
# Authentication API Documentation
2+
3+
This document describes the authentication endpoints available in the VoidRunner API.
4+
5+
## Overview
6+
7+
The VoidRunner API uses JWT (JSON Web Tokens) for authentication. The authentication system supports:
8+
9+
- User registration with email and password
10+
- User login with email and password
11+
- JWT access token (15-minute expiration)
12+
- JWT refresh token (7-day expiration)
13+
- Token refresh without re-authentication
14+
- Rate limiting on auth endpoints
15+
16+
## Base URL
17+
18+
```
19+
http://localhost:8080/api/v1
20+
```
21+
22+
## Authentication Endpoints
23+
24+
### 1. User Registration
25+
26+
**Endpoint:** `POST /auth/register`
27+
28+
**Rate Limit:** 5 requests per hour per IP
29+
30+
**Request Body:**
31+
```json
32+
{
33+
"email": "user@example.com",
34+
"password": "StrongPassword123!"
35+
}
36+
```
37+
38+
**Password Requirements:**
39+
- Minimum 8 characters
40+
- Maximum 128 characters
41+
- At least one uppercase letter
42+
- At least one lowercase letter
43+
- At least one digit
44+
- At least one special character
45+
46+
**Success Response (201):**
47+
```json
48+
{
49+
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
50+
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
51+
"token_type": "Bearer",
52+
"expires_in": 900,
53+
"user": {
54+
"id": "123e4567-e89b-12d3-a456-426614174000",
55+
"email": "user@example.com",
56+
"created_at": "2023-07-04T12:00:00Z",
57+
"updated_at": "2023-07-04T12:00:00Z"
58+
}
59+
}
60+
```
61+
62+
**Error Responses:**
63+
- `400 Bad Request`: Invalid input format or validation errors
64+
- `409 Conflict`: User with email already exists
65+
- `429 Too Many Requests`: Rate limit exceeded
66+
67+
### 2. User Login
68+
69+
**Endpoint:** `POST /auth/login`
70+
71+
**Rate Limit:** 10 requests per hour per IP
72+
73+
**Request Body:**
74+
```json
75+
{
76+
"email": "user@example.com",
77+
"password": "StrongPassword123!"
78+
}
79+
```
80+
81+
**Success Response (200):**
82+
```json
83+
{
84+
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
85+
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
86+
"token_type": "Bearer",
87+
"expires_in": 900,
88+
"user": {
89+
"id": "123e4567-e89b-12d3-a456-426614174000",
90+
"email": "user@example.com",
91+
"created_at": "2023-07-04T12:00:00Z",
92+
"updated_at": "2023-07-04T12:00:00Z"
93+
}
94+
}
95+
```
96+
97+
**Error Responses:**
98+
- `400 Bad Request`: Invalid input format
99+
- `401 Unauthorized`: Invalid email or password
100+
- `429 Too Many Requests`: Rate limit exceeded
101+
102+
### 3. Token Refresh
103+
104+
**Endpoint:** `POST /auth/refresh`
105+
106+
**Rate Limit:** 100 requests per hour per IP
107+
108+
**Request Body:**
109+
```json
110+
{
111+
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
112+
}
113+
```
114+
115+
**Success Response (200):**
116+
```json
117+
{
118+
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
119+
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
120+
"token_type": "Bearer",
121+
"expires_in": 900,
122+
"user": {
123+
"id": "123e4567-e89b-12d3-a456-426614174000",
124+
"email": "user@example.com",
125+
"created_at": "2023-07-04T12:00:00Z",
126+
"updated_at": "2023-07-04T12:00:00Z"
127+
}
128+
}
129+
```
130+
131+
**Error Responses:**
132+
- `400 Bad Request`: Invalid request format
133+
- `401 Unauthorized`: Invalid or expired refresh token
134+
- `429 Too Many Requests`: Rate limit exceeded
135+
136+
### 4. User Logout
137+
138+
**Endpoint:** `POST /auth/logout`
139+
140+
**Description:** In a JWT system, logout is typically handled client-side by removing tokens from storage. This endpoint returns a success message.
141+
142+
**Success Response (200):**
143+
```json
144+
{
145+
"message": "Successfully logged out"
146+
}
147+
```
148+
149+
### 5. Get Current User
150+
151+
**Endpoint:** `GET /auth/me`
152+
153+
**Authentication:** Required (Bearer token)
154+
155+
**Headers:**
156+
```
157+
Authorization: Bearer <access_token>
158+
```
159+
160+
**Success Response (200):**
161+
```json
162+
{
163+
"user": {
164+
"id": "123e4567-e89b-12d3-a456-426614174000",
165+
"email": "user@example.com",
166+
"created_at": "2023-07-04T12:00:00Z",
167+
"updated_at": "2023-07-04T12:00:00Z"
168+
}
169+
}
170+
```
171+
172+
**Error Responses:**
173+
- `401 Unauthorized`: Missing, invalid, or expired token
174+
175+
## Authentication Flow
176+
177+
### Initial Authentication
178+
1. User registers with `POST /auth/register` or logs in with `POST /auth/login`
179+
2. Server returns access token (15-minute expiration) and refresh token (7-day expiration)
180+
3. Client stores both tokens securely
181+
182+
### Making Authenticated Requests
183+
1. Include access token in Authorization header: `Authorization: Bearer <access_token>`
184+
2. If access token is expired, use refresh token to get new tokens
185+
3. Update stored tokens with new values
186+
187+
### Token Refresh
188+
1. When access token expires, call `POST /auth/refresh` with refresh token
189+
2. Server returns new access token and refresh token
190+
3. Client updates stored tokens
191+
192+
### Logout
193+
1. Call `POST /auth/logout` (optional, for logging purposes)
194+
2. Remove tokens from client storage
195+
196+
## Error Handling
197+
198+
All endpoints return errors in the following format:
199+
200+
```json
201+
{
202+
"error": "Error message description",
203+
"details": "Additional error details (optional)"
204+
}
205+
```
206+
207+
Common HTTP status codes:
208+
- `400 Bad Request`: Invalid input or request format
209+
- `401 Unauthorized`: Authentication required or failed
210+
- `403 Forbidden`: Access denied
211+
- `409 Conflict`: Resource already exists
212+
- `429 Too Many Requests`: Rate limit exceeded
213+
- `500 Internal Server Error`: Server error
214+
215+
## Rate Limiting
216+
217+
The authentication endpoints have the following rate limits:
218+
219+
| Endpoint | Limit | Window |
220+
|----------|-------|--------|
221+
| `/auth/register` | 5 requests | 1 hour |
222+
| `/auth/login` | 10 requests | 1 hour |
223+
| `/auth/refresh` | 100 requests | 1 hour |
224+
225+
Rate limits are applied per IP address. When the limit is exceeded, the server returns:
226+
227+
```json
228+
{
229+
"error": "Rate limit exceeded",
230+
"retry_after": 3600
231+
}
232+
```
233+
234+
## Security Considerations
235+
236+
1. **HTTPS Only**: Use HTTPS in production to protect tokens in transit
237+
2. **Token Storage**: Store tokens securely (secure cookies, encrypted storage)
238+
3. **Token Expiration**: Access tokens expire in 15 minutes, refresh tokens in 7 days
239+
4. **Secret Key**: Use a strong, random secret key (minimum 256 bits)
240+
5. **Rate Limiting**: Authentication endpoints are rate-limited to prevent abuse
241+
6. **Password Policy**: Strong password requirements enforced
242+
243+
## Environment Configuration
244+
245+
Required environment variables:
246+
247+
```bash
248+
# JWT Configuration
249+
JWT_SECRET_KEY=your-secret-key-change-in-production-256-bits-minimum
250+
JWT_ACCESS_TOKEN_DURATION=15m
251+
JWT_REFRESH_TOKEN_DURATION=168h
252+
JWT_ISSUER=voidrunner
253+
JWT_AUDIENCE=voidrunner-api
254+
```
255+
256+
For a complete list of configuration options, see `.env.example`.

go.mod

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ go 1.24.4
55
require (
66
github.com/gin-contrib/cors v1.7.6
77
github.com/gin-gonic/gin v1.10.1
8+
github.com/golang-migrate/migrate/v4 v4.18.3
89
github.com/google/uuid v1.6.0
10+
github.com/jackc/pgx/v5 v5.7.5
911
github.com/joho/godotenv v1.5.1
1012
github.com/stretchr/testify v1.10.0
1113
)
@@ -21,12 +23,11 @@ require (
2123
github.com/go-playground/universal-translator v0.18.1 // indirect
2224
github.com/go-playground/validator/v10 v10.26.0 // indirect
2325
github.com/goccy/go-json v0.10.5 // indirect
24-
github.com/golang-migrate/migrate/v4 v4.18.3 // indirect
26+
github.com/golang-jwt/jwt/v5 v5.2.2 // indirect
2527
github.com/hashicorp/errwrap v1.1.0 // indirect
2628
github.com/hashicorp/go-multierror v1.1.1 // indirect
2729
github.com/jackc/pgpassfile v1.0.0 // indirect
2830
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
29-
github.com/jackc/pgx/v5 v5.7.5 // indirect
3031
github.com/jackc/puddle/v2 v2.2.2 // indirect
3132
github.com/json-iterator/go v1.1.12 // indirect
3233
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
@@ -37,6 +38,7 @@ require (
3738
github.com/modern-go/reflect2 v1.0.2 // indirect
3839
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
3940
github.com/pmezard/go-difflib v1.0.0 // indirect
41+
github.com/stretchr/objx v0.5.2 // indirect
4042
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
4143
github.com/ugorji/go/codec v1.3.0 // indirect
4244
go.uber.org/atomic v1.7.0 // indirect

0 commit comments

Comments
 (0)