najm-auth 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,8 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(bun run build)",
5
+ "Bash(ls:*)"
6
+ ]
7
+ }
8
+ }
package/.env.example ADDED
@@ -0,0 +1,6 @@
1
+ # JWT Configuration
2
+ JWT_ACCESS_SECRET=your-secret-access-key-change-this
3
+ ACCESS_EXPIRES_IN=15m
4
+
5
+ JWT_REFRESH_SECRET=your-secret-refresh-key-change-this
6
+ REFRESH_EXPIRES_IN=1y
package/README.md ADDED
@@ -0,0 +1,501 @@
1
+ # najm-auth
2
+
3
+ Authentication and authorization library for the najm framework.
4
+
5
+ ## Features
6
+
7
+ - 🔐 **JWT Authentication** - Access and refresh token management
8
+ - 👤 **Role-Based Access Control (RBAC)** - Simple role-based guards
9
+ - 🔑 **Permission-Based Authorization** - Fine-grained permission system with wildcard support
10
+ - 🍪 **Cookie Management** - Secure refresh token cookie handling
11
+ - 🔒 **Password Encryption** - bcrypt password hashing
12
+ - 🎯 **Decorator-Based** - Clean, declarative API using TypeScript decorators
13
+ - 🗄️ **Database Agnostic** - Implement your own repository interfaces
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ npm install najm-auth
19
+ # or
20
+ bun add najm-auth
21
+ ```
22
+
23
+ ## Quick Start
24
+
25
+ ### 1. Environment Variables
26
+
27
+ Create a `.env` file:
28
+
29
+ ```env
30
+ JWT_ACCESS_SECRET=your-secret-access-key
31
+ ACCESS_EXPIRES_IN=15m
32
+ JWT_REFRESH_SECRET=your-secret-refresh-key
33
+ REFRESH_EXPIRES_IN=1y
34
+ ```
35
+
36
+ ### 2. Database Setup
37
+
38
+ Create the required database tables:
39
+
40
+ ```sql
41
+ -- Users table
42
+ CREATE TABLE users (
43
+ id UUID PRIMARY KEY,
44
+ email VARCHAR(255) UNIQUE NOT NULL,
45
+ password VARCHAR(255) NOT NULL,
46
+ role_id UUID REFERENCES roles(id),
47
+ status VARCHAR(50),
48
+ created_at TIMESTAMP DEFAULT NOW(),
49
+ updated_at TIMESTAMP DEFAULT NOW()
50
+ );
51
+
52
+ -- Roles table
53
+ CREATE TABLE roles (
54
+ id UUID PRIMARY KEY,
55
+ name VARCHAR(100) UNIQUE NOT NULL,
56
+ description TEXT,
57
+ created_at TIMESTAMP DEFAULT NOW(),
58
+ updated_at TIMESTAMP DEFAULT NOW()
59
+ );
60
+
61
+ -- Permissions table
62
+ CREATE TABLE permissions (
63
+ id UUID PRIMARY KEY,
64
+ name VARCHAR(100) UNIQUE NOT NULL,
65
+ action VARCHAR(50) NOT NULL,
66
+ resource VARCHAR(100) NOT NULL,
67
+ description TEXT,
68
+ created_at TIMESTAMP DEFAULT NOW(),
69
+ updated_at TIMESTAMP DEFAULT NOW()
70
+ );
71
+
72
+ -- Role-Permission junction table
73
+ CREATE TABLE role_permissions (
74
+ role_id UUID REFERENCES roles(id) ON DELETE CASCADE,
75
+ permission_id UUID REFERENCES permissions(id) ON DELETE CASCADE,
76
+ PRIMARY KEY (role_id, permission_id)
77
+ );
78
+
79
+ -- Tokens table
80
+ CREATE TABLE tokens (
81
+ user_id UUID PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE,
82
+ token TEXT NOT NULL,
83
+ expires_at TIMESTAMP NOT NULL
84
+ );
85
+ ```
86
+
87
+ ### 3. Implement Repository Interfaces
88
+
89
+ You need to implement the repository interfaces for your database/ORM:
90
+
91
+ ```typescript
92
+ import { Repository } from 'najm-api';
93
+ import { ITokenRepository, TokenData, AuthUser } from 'najm-auth';
94
+ import { db } from './db'; // your database instance
95
+
96
+ @Repository()
97
+ export class TokenRepository implements ITokenRepository {
98
+ declare db: any;
99
+
100
+ async storeRefreshToken(tokenData: TokenData): Promise<any> {
101
+ return await this.db
102
+ .insert(tokens)
103
+ .values(tokenData)
104
+ .onConflictDoUpdate({
105
+ target: tokens.userId,
106
+ set: { token: tokenData.token, expiresAt: tokenData.expiresAt }
107
+ });
108
+ }
109
+
110
+ async getRefreshToken(userId: string | number): Promise<string | null> {
111
+ const [token] = await this.db
112
+ .select()
113
+ .from(tokens)
114
+ .where(eq(tokens.userId, userId));
115
+ return token?.token || null;
116
+ }
117
+
118
+ async revokeToken(userId: string | number): Promise<any> {
119
+ return await this.db.delete(tokens).where(eq(tokens.userId, userId));
120
+ }
121
+
122
+ async isUserExists(userId: string | number): Promise<boolean> {
123
+ const [user] = await this.db
124
+ .select({ id: users.id })
125
+ .from(users)
126
+ .where(eq(users.id, userId));
127
+ return !!user;
128
+ }
129
+
130
+ async getRoleNameById(userId: string | number): Promise<string | null> {
131
+ const [role] = await this.db
132
+ .select({ roleName: roles.name })
133
+ .from(users)
134
+ .leftJoin(roles, eq(users.roleId, roles.id))
135
+ .where(eq(users.id, userId));
136
+ return role?.roleName || null;
137
+ }
138
+
139
+ async getUserPermissions(userId: string | number): Promise<string[]> {
140
+ const [user] = await this.db
141
+ .select({ roleId: users.roleId })
142
+ .from(users)
143
+ .where(eq(users.id, userId));
144
+
145
+ if (!user?.roleId) return [];
146
+
147
+ const perms = await this.db
148
+ .select({ name: permissions.name })
149
+ .from(rolePermissions)
150
+ .leftJoin(permissions, eq(rolePermissions.permissionId, permissions.id))
151
+ .where(eq(rolePermissions.roleId, user.roleId));
152
+
153
+ return perms.map(p => p.name).filter(Boolean);
154
+ }
155
+
156
+ async getUser(userId: string | number): Promise<AuthUser | null> {
157
+ const [user] = await this.db
158
+ .select({
159
+ id: users.id,
160
+ email: users.email,
161
+ status: users.status,
162
+ roleId: users.roleId,
163
+ roleName: roles.name,
164
+ })
165
+ .from(users)
166
+ .leftJoin(roles, eq(users.roleId, roles.id))
167
+ .where(eq(users.id, userId));
168
+
169
+ return user ? { ...user, role: user.roleName } : null;
170
+ }
171
+ }
172
+ ```
173
+
174
+ ### 4. Create Controllers
175
+
176
+ ```typescript
177
+ import { Controller, Get, Post, Body, User, Params } from 'najm-api';
178
+ import { isAuth, Role, Permission, AuthService, EncryptionService } from 'najm-auth';
179
+
180
+ @Controller('/api/auth')
181
+ export class AuthController {
182
+ constructor(
183
+ private authService: AuthService,
184
+ private encryptionService: EncryptionService,
185
+ private userService: UserService // Your user service
186
+ ) {}
187
+
188
+ @Post('/login')
189
+ async login(@Body() body: { email: string; password: string }) {
190
+ const user = await this.userService.getByEmail(body.email);
191
+
192
+ // Verify password
193
+ const isValid = await this.encryptionService.comparePassword(
194
+ body.password,
195
+ user.password
196
+ );
197
+
198
+ if (!isValid) {
199
+ throw new Error('Invalid credentials');
200
+ }
201
+
202
+ // Generate tokens
203
+ const tokens = await this.authService.loginUser(user.id);
204
+
205
+ return {
206
+ data: tokens,
207
+ message: 'Login successful',
208
+ };
209
+ }
210
+
211
+ @Get('/refresh')
212
+ async refresh() {
213
+ const tokens = await this.authService.refreshTokens();
214
+ return { data: tokens };
215
+ }
216
+
217
+ @Get('/logout/:id')
218
+ async logout(@Params('id') id: string) {
219
+ await this.authService.logoutUser(id);
220
+ return { message: 'Logged out successfully' };
221
+ }
222
+
223
+ @Get('/me')
224
+ @isAuth()
225
+ async getProfile(@User() user: any) {
226
+ const profile = await this.authService.getUserProfile(user);
227
+ return { data: profile };
228
+ }
229
+ }
230
+ ```
231
+
232
+ ## Usage Examples
233
+
234
+ ### Role-Based Guards
235
+
236
+ ```typescript
237
+ import { Controller, Get } from 'najm-api';
238
+ import { isAuth, Role, isAdmin, isTeacher, isStaff } from 'najm-auth';
239
+
240
+ @Controller('/api/users')
241
+ export class UserController {
242
+ // Require authentication
243
+ @Get('/profile')
244
+ @isAuth()
245
+ async getProfile() {
246
+ return { message: 'Authenticated user only' };
247
+ }
248
+
249
+ // Require specific role
250
+ @Get('/admin-only')
251
+ @Role('admin')
252
+ async adminRoute() {
253
+ return { message: 'Admin only' };
254
+ }
255
+
256
+ // Multiple roles allowed
257
+ @Get('/staff-only')
258
+ @Role('admin', 'principal', 'teacher')
259
+ async staffRoute() {
260
+ return { message: 'Staff members only' };
261
+ }
262
+
263
+ // Shorthand decorators
264
+ @Get('/teachers')
265
+ @isTeacher()
266
+ async teachersOnly() {
267
+ return { message: 'Teachers only' };
268
+ }
269
+
270
+ @Get('/admins')
271
+ @isAdmin()
272
+ async adminsOnly() {
273
+ return { message: 'Admins only' };
274
+ }
275
+
276
+ @Get('/all-staff')
277
+ @isStaff()
278
+ async allStaff() {
279
+ return { message: 'All staff members' };
280
+ }
281
+ }
282
+ ```
283
+
284
+ ### Permission-Based Guards
285
+
286
+ Permissions follow the `action:resource` format (e.g., `read:users`, `write:classes`).
287
+
288
+ ```typescript
289
+ import { Controller, Get, Post, Put, Delete } from 'najm-api';
290
+ import { Permission } from 'najm-auth';
291
+
292
+ @Controller('/api/users')
293
+ export class UserController {
294
+ @Get('/')
295
+ @Permission('read:users')
296
+ async listUsers() {
297
+ return { data: [] };
298
+ }
299
+
300
+ @Post('/')
301
+ @Permission('write:users')
302
+ async createUser() {
303
+ return { message: 'User created' };
304
+ }
305
+
306
+ @Put('/:id')
307
+ @Permission('update:users')
308
+ async updateUser() {
309
+ return { message: 'User updated' };
310
+ }
311
+
312
+ @Delete('/:id')
313
+ @Permission('delete:users')
314
+ async deleteUser() {
315
+ return { message: 'User deleted' };
316
+ }
317
+ }
318
+ ```
319
+
320
+ ### Wildcard Permissions
321
+
322
+ ```typescript
323
+ // Permission patterns:
324
+ // "read:*" - Can read any resource
325
+ // "*:users" - Can perform any action on users
326
+ // "*:*" - Super admin (all permissions)
327
+
328
+ @Controller('/api/admin')
329
+ export class AdminController {
330
+ @Get('/dashboard')
331
+ @Permission('*:*')
332
+ async dashboard() {
333
+ return { message: 'Super admin dashboard' };
334
+ }
335
+ }
336
+ ```
337
+
338
+ ### Password Hashing
339
+
340
+ ```typescript
341
+ import { EncryptionService } from 'najm-auth';
342
+
343
+ @Service()
344
+ export class UserService {
345
+ constructor(private encryptionService: EncryptionService) {}
346
+
347
+ async createUser(data: { email: string; password: string }) {
348
+ // Hash password before storing
349
+ const hashedPassword = await this.encryptionService.hashPassword(data.password);
350
+
351
+ return await this.userRepository.create({
352
+ email: data.email,
353
+ password: hashedPassword,
354
+ });
355
+ }
356
+
357
+ async validatePassword(plainPassword: string, hashedPassword: string) {
358
+ return await this.encryptionService.comparePassword(plainPassword, hashedPassword);
359
+ }
360
+ }
361
+ ```
362
+
363
+ ### Role Management
364
+
365
+ ```typescript
366
+ import { RoleService } from 'najm-auth';
367
+
368
+ @Service()
369
+ export class SetupService {
370
+ constructor(private roleService: RoleService) {}
371
+
372
+ async seedRoles() {
373
+ const defaultRoles = [
374
+ { name: 'admin', description: 'Administrator' },
375
+ { name: 'teacher', description: 'Teacher' },
376
+ { name: 'student', description: 'Student' },
377
+ ];
378
+
379
+ return await this.roleService.seedDefaultRoles(defaultRoles);
380
+ }
381
+ }
382
+ ```
383
+
384
+ ### Permission Management
385
+
386
+ ```typescript
387
+ import { PermissionService } from 'najm-auth';
388
+
389
+ @Service()
390
+ export class SetupService {
391
+ constructor(private permissionService: PermissionService) {}
392
+
393
+ async seedPermissions() {
394
+ const defaultPermissions = [
395
+ { name: 'read:users', action: 'read', resource: 'users' },
396
+ { name: 'write:users', action: 'write', resource: 'users' },
397
+ { name: 'delete:users', action: 'delete', resource: 'users' },
398
+ ];
399
+
400
+ await this.permissionService.seedDefaultPermissions(defaultPermissions);
401
+
402
+ // Assign permissions to roles
403
+ const rolePermissions = [
404
+ {
405
+ roleName: 'admin',
406
+ permissions: ['*:*'], // Admin gets all permissions
407
+ },
408
+ {
409
+ roleName: 'teacher',
410
+ permissions: ['read:users', 'read:classes'],
411
+ },
412
+ ];
413
+
414
+ return await this.permissionService.seedDefaultRolePermissions(rolePermissions);
415
+ }
416
+ }
417
+ ```
418
+
419
+ ## API Reference
420
+
421
+ ### Services
422
+
423
+ #### `AuthService`
424
+ - `loginUser(userId)` - Generate tokens for user
425
+ - `refreshTokens()` - Refresh access and refresh tokens
426
+ - `logoutUser(userId)` - Revoke user's refresh token
427
+ - `getUserProfile(userData)` - Get user profile with language
428
+
429
+ #### `TokenService`
430
+ - `generateTokens(userId)` - Generate access and refresh tokens
431
+ - `refreshTokens()` - Refresh both tokens
432
+ - `verifyAccessToken(token)` - Verify access token
433
+ - `verifyRefreshToken(token)` - Verify refresh token
434
+ - `getUserIdByAccessToken(header)` - Extract user ID from token
435
+ - `getUser(auth)` - Get user from access token
436
+ - `getUserRole(auth)` - Get user role from token
437
+ - `getUserPermissions(auth)` - Get user permissions from token
438
+
439
+ #### `EncryptionService`
440
+ - `hashPassword(password)` - Hash password with bcrypt
441
+ - `comparePassword(password, hash)` - Compare password with hash
442
+
443
+ #### `RoleService`
444
+ - `getAll()` - Get all roles
445
+ - `getById(id)` - Get role by ID
446
+ - `getByName(name)` - Get role by name
447
+ - `create(data)` - Create new role
448
+ - `update(id, data)` - Update role
449
+ - `delete(id)` - Delete role
450
+ - `seedDefaultRoles(roles)` - Seed initial roles
451
+
452
+ #### `PermissionService`
453
+ - `getAll()` - Get all permissions
454
+ - `getById(id)` - Get permission by ID
455
+ - `getByName(name)` - Get permission by name
456
+ - `create(data)` - Create permission
457
+ - `update(id, data)` - Update permission
458
+ - `delete(id)` - Delete permission
459
+ - `assignPermissionToRole(roleId, permissionId)` - Grant permission to role
460
+ - `removePermissionFromRole(roleId, permissionId)` - Revoke permission from role
461
+ - `seedDefaultPermissions(permissions)` - Seed initial permissions
462
+ - `seedDefaultRolePermissions(mapping)` - Assign permissions to roles
463
+
464
+ ### Guards
465
+
466
+ #### Role Guards
467
+ - `@isAuth()` - Require authentication
468
+ - `@Role(...roles)` - Require specific role(s)
469
+ - `@isAdmin()` - Shorthand for admin role
470
+ - `@isTeacher()` - Shorthand for teacher role
471
+ - `@isStudent()` - Shorthand for student role
472
+ - `@isStaff()` - Allow admin, principal, accounting, secretary, teacher
473
+
474
+ #### Permission Guards
475
+ - `@Permission(permission)` - Require specific permission
476
+
477
+ ### Constants
478
+
479
+ ```typescript
480
+ ROLES = {
481
+ ADMIN: 'admin',
482
+ PRINCIPAL: 'principal',
483
+ ACCOUNTING: 'accounting',
484
+ SECRETARY: 'secretary',
485
+ TEACHER: 'teacher',
486
+ STUDENT: 'student',
487
+ PARENT: 'parent',
488
+ }
489
+
490
+ ROLE_GROUPS = {
491
+ ADMINISTRATORS: ['admin', 'principal'],
492
+ FINANCIAL: ['admin', 'accounting'],
493
+ STAFF: ['admin', 'principal', 'accounting', 'secretary', 'teacher'],
494
+ END_USERS: ['student', 'parent'],
495
+ ALL: [...all roles],
496
+ }
497
+ ```
498
+
499
+ ## License
500
+
501
+ MIT