Repository navigation
Expand file tree
/
Copy pathauthentication-authorization.ts
More file actions
140 lines (120 loc) · 3.32 KB
/
Copy pathauthentication-authorization.ts
File metadata and controls
140 lines (120 loc) · 3.32 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
/**
* Authentication & Authorization Recipe
*
* Implement JWT-based authentication with middleware.
* Demonstrates:
* - JWT token verification middleware
* - Role-based access control
* - Chaining authentication middleware
* - Using generics to avoid type casting
*
* Note: User model is a placeholder. Replace with your actual user model/service.
* Requires: npm install jsonwebtoken @types/jsonwebtoken
*/
import * as jwt from 'jsonwebtoken';
import Router from '../router-module-loader';
import type { RouterMiddleware } from '../router-module-loader';
import { User } from '../common';
import type { User as UserType } from '../common';
/**
* State type for authenticated routes
*/
type AuthState = {
user?: UserType;
};
/**
* JWT payload structure
*/
type JwtPayload = {
userId?: string;
};
/**
* Typed router with authentication state
*/
const router = new Router<AuthState>();
/**
* Authentication middleware
* Verifies JWT token and attaches user to ctx.state
*/
const authenticate: RouterMiddleware<AuthState> = async (ctx, next) => {
const token = ctx.headers.authorization?.replace('Bearer ', '');
if (!token) {
ctx.throw(401, 'Authentication required');
return;
}
try {
const decoded = jwt.verify(
token,
process.env.JWT_SECRET || 'secret'
) as JwtPayload;
const userId = decoded.userId;
if (!userId) {
ctx.throw(401, 'Invalid token payload');
return;
}
const user = await User.findById(userId);
ctx.state.user = user || undefined;
await next();
} catch {
ctx.throw(401, 'Invalid token');
}
};
/**
* Role-based authorization middleware factory
* Returns middleware that checks if user has required role
*/
const requireRole = (role: string): RouterMiddleware<AuthState> => {
return async (ctx, next) => {
const { user } = ctx.state;
if (!user) {
ctx.throw(401, 'Authentication required');
return;
}
if (user.role !== role) {
ctx.throw(403, 'Insufficient permissions');
}
await next();
};
};
/**
* Multiple roles authorization middleware factory
* Returns middleware that checks if user has any of the required roles
*/
const requireAnyRole = (...roles: string[]): RouterMiddleware<AuthState> => {
return async (ctx, next) => {
const { user } = ctx.state;
if (!user) {
ctx.throw(401, 'Authentication required');
return;
}
if (!user.role || !roles.includes(user.role)) {
ctx.throw(403, `Requires one of: ${roles.join(', ')}`);
}
await next();
};
};
// Routes with authentication and authorization
router
// Public route - no authentication needed
.get('/public', (ctx) => {
ctx.body = { message: 'Public content' };
})
// Protected route - requires authentication
.get('/profile', authenticate, (ctx) => {
ctx.body = ctx.state.user;
})
// Admin route - requires authentication + admin role
.get('/admin', authenticate, requireRole('admin'), (ctx) => {
ctx.body = { message: 'Admin access granted' };
})
// Moderator route - requires authentication + moderator or admin role
.get(
'/moderate',
authenticate,
requireAnyRole('admin', 'moderator'),
(ctx) => {
ctx.body = { message: 'Moderator access granted' };
}
);
export { router, authenticate, requireRole, requireAnyRole };
export type { AuthState };