ryauth 1.0.0
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.
- package/.env.example +7 -0
- package/LICENSE +21 -0
- package/README.md +232 -0
- package/docs/api-reference.md +203 -0
- package/docs/examples.md +225 -0
- package/index.js +15 -0
- package/package.json +37 -0
- package/src/adapters/base.js +81 -0
- package/src/adapters/memory.js +159 -0
- package/src/core/crypto.js +103 -0
- package/src/middleware/auth.js +127 -0
- package/src/services/auth-service.js +172 -0
- package/tests/adapters.test.js +355 -0
- package/tests/auth-service.test.js +287 -0
- package/tests/crypto.test.js +202 -0
package/.env.example
ADDED
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Your Name
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
# RyAuth
|
|
2
|
+
|
|
3
|
+
[](https://badge.fury.io/js/ryauth)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
[](https://nodejs.org/)
|
|
6
|
+
[](https://github.com/ryanpereira49/RyAuth)
|
|
7
|
+
[](https://github.com/ryanpereira49/RyAuth/issues)
|
|
8
|
+
|
|
9
|
+
A modern, secure, and database-agnostic authentication library for Node.js. Built with JWT tokens, Argon2 password hashing, and role-based access control (RBAC).
|
|
10
|
+
|
|
11
|
+
## โจ Features
|
|
12
|
+
|
|
13
|
+
- ๐ **Secure Authentication** - JWT-based authentication with access and refresh tokens
|
|
14
|
+
- ๐ก๏ธ **Password Security** - Argon2 password hashing (winner of the 2015 Password Hashing Competition)
|
|
15
|
+
- ๐ **Token Rotation** - Automatic refresh token rotation for enhanced security
|
|
16
|
+
- ๐ฅ **Role-Based Access Control** - Fine-grained permissions and user roles
|
|
17
|
+
- ๐๏ธ **Database Agnostic** - Adapter pattern supports any database (PostgreSQL, MongoDB, MySQL, etc.)
|
|
18
|
+
- ๐ **Modern JavaScript** - ES modules, async/await, and TypeScript-ready
|
|
19
|
+
- โ
**Runtime Validation** - Zod schemas for input validation and type safety
|
|
20
|
+
- ๐งช **Comprehensive Testing** - 66 tests with 94% code coverage
|
|
21
|
+
- ๐ **Full Documentation** - Complete API reference and examples
|
|
22
|
+
|
|
23
|
+
## ๐ฆ Installation
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install ryauth
|
|
27
|
+
# or
|
|
28
|
+
yarn add ryauth
|
|
29
|
+
# or
|
|
30
|
+
pnpm add ryauth
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## ๐ Quick Start
|
|
34
|
+
|
|
35
|
+
```javascript
|
|
36
|
+
import express from 'express';
|
|
37
|
+
import { createAuthMiddleware, AuthService, MemoryAdapter } from 'ryauth';
|
|
38
|
+
|
|
39
|
+
const app = express();
|
|
40
|
+
app.use(express.json());
|
|
41
|
+
|
|
42
|
+
// Initialize authentication
|
|
43
|
+
const adapter = new MemoryAdapter();
|
|
44
|
+
const authService = new AuthService(adapter);
|
|
45
|
+
|
|
46
|
+
// Create middleware
|
|
47
|
+
const authMiddleware = createAuthMiddleware({
|
|
48
|
+
accessTokenSecret: process.env.ACCESS_TOKEN_SECRET,
|
|
49
|
+
refreshTokenSecret: process.env.REFRESH_TOKEN_SECRET
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// Register endpoint
|
|
53
|
+
app.post('/auth/register', async (req, res) => {
|
|
54
|
+
try {
|
|
55
|
+
const { email, password } = req.body;
|
|
56
|
+
const result = await authService.register(email, password);
|
|
57
|
+
res.json(result);
|
|
58
|
+
} catch (error) {
|
|
59
|
+
res.status(400).json({ error: error.message });
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// Login endpoint
|
|
64
|
+
app.post('/auth/login', async (req, res) => {
|
|
65
|
+
try {
|
|
66
|
+
const { email, password } = req.body;
|
|
67
|
+
const result = await authService.login(email, password);
|
|
68
|
+
res.json(result);
|
|
69
|
+
} catch (error) {
|
|
70
|
+
res.status(401).json({ error: error.message });
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// Protected route
|
|
75
|
+
app.get('/api/profile', authMiddleware.authenticate, (req, res) => {
|
|
76
|
+
res.json({ user: req.user });
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// Admin-only route
|
|
80
|
+
app.get('/api/admin', authMiddleware.authenticate, authMiddleware.authorize('admin'), (req, res) => {
|
|
81
|
+
res.json({ message: 'Welcome, admin!' });
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
app.listen(3000, () => {
|
|
85
|
+
console.log('Server running on port 3000');
|
|
86
|
+
});
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## ๐ง Environment Setup
|
|
90
|
+
|
|
91
|
+
Create a `.env` file in your project root:
|
|
92
|
+
|
|
93
|
+
```env
|
|
94
|
+
# Access Token Secret (minimum 32 characters)
|
|
95
|
+
ACCESS_TOKEN_SECRET=your_32+_character_access_token_secret_here
|
|
96
|
+
|
|
97
|
+
# Refresh Token Secret (minimum 32 characters)
|
|
98
|
+
REFRESH_TOKEN_SECRET=your_32+_character_refresh_token_secret_here
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## ๐๏ธ Architecture
|
|
102
|
+
|
|
103
|
+
RyAuth uses a modular architecture designed for security and flexibility:
|
|
104
|
+
|
|
105
|
+
```
|
|
106
|
+
src/
|
|
107
|
+
โโโ core/
|
|
108
|
+
โ โโโ crypto.js # Argon2 & JOSE JWT operations
|
|
109
|
+
โโโ adapters/
|
|
110
|
+
โ โโโ base.js # Abstract database adapter interface
|
|
111
|
+
โ โโโ memory.js # In-memory adapter for testing
|
|
112
|
+
โโโ middleware/
|
|
113
|
+
โ โโโ auth.js # Express middleware for JWT validation
|
|
114
|
+
โโโ services/
|
|
115
|
+
โโโ auth-service.js # Core authentication business logic
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## ๐ API Overview
|
|
119
|
+
|
|
120
|
+
### AuthService
|
|
121
|
+
|
|
122
|
+
The main service class that orchestrates authentication flows.
|
|
123
|
+
|
|
124
|
+
```javascript
|
|
125
|
+
const authService = new AuthService(adapter);
|
|
126
|
+
|
|
127
|
+
// User management
|
|
128
|
+
await authService.register('user@example.com', 'password123');
|
|
129
|
+
await authService.login('user@example.com', 'password123');
|
|
130
|
+
await authService.refresh(refreshToken);
|
|
131
|
+
await authService.revokeRefreshToken(refreshToken);
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Middleware
|
|
135
|
+
|
|
136
|
+
Express.js middleware for authentication and authorization.
|
|
137
|
+
|
|
138
|
+
```javascript
|
|
139
|
+
const authMiddleware = createAuthMiddleware(config);
|
|
140
|
+
|
|
141
|
+
// Authentication
|
|
142
|
+
app.get('/protected', authMiddleware.authenticate, handler);
|
|
143
|
+
|
|
144
|
+
// Authorization
|
|
145
|
+
app.get('/admin', authMiddleware.authenticate, authMiddleware.authorize('admin'), handler);
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Adapters
|
|
149
|
+
|
|
150
|
+
Database abstraction layer supporting multiple databases.
|
|
151
|
+
|
|
152
|
+
```javascript
|
|
153
|
+
// Built-in MemoryAdapter for testing
|
|
154
|
+
const adapter = new MemoryAdapter();
|
|
155
|
+
|
|
156
|
+
// Custom adapter for PostgreSQL
|
|
157
|
+
class PostgreSQLAdapter extends BaseAdapter {
|
|
158
|
+
async findUserByEmail(email) { /* implementation */ }
|
|
159
|
+
async createUser(userData) { /* implementation */ }
|
|
160
|
+
// ... other required methods
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## ๐งช Testing
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
npm test
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
RyAuth includes comprehensive test coverage:
|
|
171
|
+
- **66 tests** covering all functionality
|
|
172
|
+
- **94% code coverage**
|
|
173
|
+
- Unit tests for all core modules
|
|
174
|
+
- Integration tests for complete flows
|
|
175
|
+
|
|
176
|
+
## ๐ Documentation
|
|
177
|
+
|
|
178
|
+
- **[API Reference](docs/api-reference.md)** - Complete API documentation
|
|
179
|
+
- **[Examples](docs/examples.md)** - Practical code examples and integrations
|
|
180
|
+
- **[Contributing Guide](CONTRIBUTING.md)** - Guidelines for contributors
|
|
181
|
+
|
|
182
|
+
## ๐ Security Features
|
|
183
|
+
|
|
184
|
+
- **Argon2 Password Hashing** - Memory-hard algorithm resistant to brute force attacks
|
|
185
|
+
- **JWT Token Rotation** - Automatic refresh token rotation prevents token theft
|
|
186
|
+
- **Session Management** - Ability to revoke refresh tokens
|
|
187
|
+
- **Input Validation** - Runtime validation with Zod schemas
|
|
188
|
+
- **Timing-Safe Comparison** - Prevents timing attacks during authentication
|
|
189
|
+
- **Secure Defaults** - 15-minute access tokens, 7-day refresh tokens
|
|
190
|
+
|
|
191
|
+
## ๐ค Contributing
|
|
192
|
+
|
|
193
|
+
We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.
|
|
194
|
+
|
|
195
|
+
### Development Setup
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
# Clone the repository
|
|
199
|
+
git clone https://github.com/ryanpereira49/RyAuth.git
|
|
200
|
+
cd RyAuth
|
|
201
|
+
|
|
202
|
+
# Install dependencies
|
|
203
|
+
npm install
|
|
204
|
+
|
|
205
|
+
# Copy environment template
|
|
206
|
+
cp .env.example .env
|
|
207
|
+
|
|
208
|
+
# Run tests
|
|
209
|
+
npm test
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
## ๐ License
|
|
213
|
+
|
|
214
|
+
MIT License - see the [LICENSE](LICENSE) file for details.
|
|
215
|
+
|
|
216
|
+
## ๐ Acknowledgments
|
|
217
|
+
|
|
218
|
+
- Built with [JOSE](https://github.com/panva/jose) for JWT operations
|
|
219
|
+
- Password hashing powered by [Argon2](https://github.com/ranisalt/node-argon2)
|
|
220
|
+
- Runtime validation with [Zod](https://github.com/colinhacks/zod)
|
|
221
|
+
|
|
222
|
+
## ๐ Support
|
|
223
|
+
|
|
224
|
+
- ๐ [Issues](https://github.com/ryanpereira49/RyAuth/issues)
|
|
225
|
+
- ๐ฌ [Discussions](https://github.com/ryanpereira49/RyAuth/discussions)
|
|
226
|
+
- ๐ง Contact: ryanpereira499@gmail.com
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
**Made with โค๏ธ for secure Node.js applications**
|
|
231
|
+
|
|
232
|
+
*RyAuth - Modern Authentication, Simplified.*
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
# API Reference
|
|
2
|
+
|
|
3
|
+
Complete API documentation for RyAuth authentication library.
|
|
4
|
+
|
|
5
|
+
## AuthService
|
|
6
|
+
|
|
7
|
+
The main service class that orchestrates authentication flows.
|
|
8
|
+
|
|
9
|
+
### Constructor
|
|
10
|
+
|
|
11
|
+
```javascript
|
|
12
|
+
new AuthService(adapter)
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
**Parameters:**
|
|
16
|
+
- `adapter` (BaseAdapter): Database adapter instance
|
|
17
|
+
|
|
18
|
+
### Methods
|
|
19
|
+
|
|
20
|
+
#### `register(email, password)`
|
|
21
|
+
|
|
22
|
+
Register a new user account.
|
|
23
|
+
|
|
24
|
+
```javascript
|
|
25
|
+
const result = await authService.register('user@example.com', 'password123');
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
**Parameters:**
|
|
29
|
+
- `email` (string): User's email address
|
|
30
|
+
- `password` (string): User's password (minimum 8 characters)
|
|
31
|
+
|
|
32
|
+
**Returns:** `Promise<{success: boolean, userId: string}>`
|
|
33
|
+
|
|
34
|
+
**Throws:** Error for validation failures or duplicate users
|
|
35
|
+
|
|
36
|
+
#### `login(email, password)`
|
|
37
|
+
|
|
38
|
+
Authenticate a user and return tokens.
|
|
39
|
+
|
|
40
|
+
```javascript
|
|
41
|
+
const result = await authService.login('user@example.com', 'password123');
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
**Parameters:**
|
|
45
|
+
- `email` (string): User's email address
|
|
46
|
+
- `password` (string): User's password
|
|
47
|
+
|
|
48
|
+
**Returns:** `Promise<{success: boolean, accessToken: string, refreshToken: string, user: object}>`
|
|
49
|
+
|
|
50
|
+
**Throws:** Error for invalid credentials
|
|
51
|
+
|
|
52
|
+
#### `refresh(refreshToken)`
|
|
53
|
+
|
|
54
|
+
Refresh access and refresh tokens.
|
|
55
|
+
|
|
56
|
+
```javascript
|
|
57
|
+
const result = await authService.refresh('refresh_token_here');
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
**Parameters:**
|
|
61
|
+
- `refreshToken` (string): Valid refresh token
|
|
62
|
+
|
|
63
|
+
**Returns:** `Promise<{success: boolean, accessToken: string, refreshToken: string}>`
|
|
64
|
+
|
|
65
|
+
**Throws:** Error for invalid or expired tokens
|
|
66
|
+
|
|
67
|
+
#### `revokeRefreshToken(refreshToken)`
|
|
68
|
+
|
|
69
|
+
Revoke a refresh token (logout).
|
|
70
|
+
|
|
71
|
+
```javascript
|
|
72
|
+
await authService.revokeRefreshToken('refresh_token_here');
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
**Parameters:**
|
|
76
|
+
- `refreshToken` (string): Refresh token to revoke
|
|
77
|
+
|
|
78
|
+
**Returns:** `Promise<void>`
|
|
79
|
+
|
|
80
|
+
## Middleware
|
|
81
|
+
|
|
82
|
+
Express.js middleware for JWT authentication and authorization.
|
|
83
|
+
|
|
84
|
+
### `createAuthMiddleware(config)`
|
|
85
|
+
|
|
86
|
+
Create an authentication middleware instance.
|
|
87
|
+
|
|
88
|
+
```javascript
|
|
89
|
+
const authMiddleware = createAuthMiddleware({
|
|
90
|
+
accessTokenSecret: process.env.ACCESS_TOKEN_SECRET,
|
|
91
|
+
refreshTokenSecret: process.env.REFRESH_TOKEN_SECRET
|
|
92
|
+
});
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**Parameters:**
|
|
96
|
+
- `config` (object):
|
|
97
|
+
- `accessTokenSecret` (string): Secret for access tokens (min 32 chars)
|
|
98
|
+
- `refreshTokenSecret` (string): Secret for refresh tokens (min 32 chars)
|
|
99
|
+
|
|
100
|
+
**Returns:** AuthMiddleware instance with `authenticate` and `authorize` methods
|
|
101
|
+
|
|
102
|
+
### `authenticate`
|
|
103
|
+
|
|
104
|
+
Middleware to verify JWT access tokens.
|
|
105
|
+
|
|
106
|
+
```javascript
|
|
107
|
+
app.get('/protected', authMiddleware.authenticate, (req, res) => {
|
|
108
|
+
res.json({ user: req.user });
|
|
109
|
+
});
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Attaches `req.user` with user payload if token is valid.
|
|
113
|
+
|
|
114
|
+
### `authorize(...roles)`
|
|
115
|
+
|
|
116
|
+
Middleware for role-based access control.
|
|
117
|
+
|
|
118
|
+
```javascript
|
|
119
|
+
app.get('/admin', authMiddleware.authenticate, authMiddleware.authorize('admin'), (req, res) => {
|
|
120
|
+
res.json({ message: 'Admin access granted' });
|
|
121
|
+
});
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
**Parameters:**
|
|
125
|
+
- `...roles` (string[]): Allowed roles
|
|
126
|
+
|
|
127
|
+
## Adapters
|
|
128
|
+
|
|
129
|
+
Database abstraction layer using the adapter pattern.
|
|
130
|
+
|
|
131
|
+
### BaseAdapter
|
|
132
|
+
|
|
133
|
+
Abstract base class for database adapters.
|
|
134
|
+
|
|
135
|
+
**Required Methods:**
|
|
136
|
+
- `findUserByEmail(email)`: Find user by email
|
|
137
|
+
- `createUser(userData)`: Create new user
|
|
138
|
+
- `saveRefreshToken(userId, token, expiresAt)`: Save refresh token
|
|
139
|
+
- `findRefreshToken(token)`: Find refresh token
|
|
140
|
+
- `revokeRefreshToken(token)`: Revoke refresh token
|
|
141
|
+
- `isRefreshTokenRevoked(token)`: Check if token is revoked
|
|
142
|
+
|
|
143
|
+
### MemoryAdapter
|
|
144
|
+
|
|
145
|
+
In-memory adapter for development and testing.
|
|
146
|
+
|
|
147
|
+
```javascript
|
|
148
|
+
import { MemoryAdapter } from 'ryauth';
|
|
149
|
+
|
|
150
|
+
const adapter = new MemoryAdapter();
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Crypto Utilities
|
|
154
|
+
|
|
155
|
+
Low-level cryptographic functions (advanced usage).
|
|
156
|
+
|
|
157
|
+
### `hashPassword(plain)`
|
|
158
|
+
|
|
159
|
+
Hash a password with Argon2.
|
|
160
|
+
|
|
161
|
+
```javascript
|
|
162
|
+
const hash = await hashPassword('password123');
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### `verifyPassword(hash, plain)`
|
|
166
|
+
|
|
167
|
+
Verify a password against its hash.
|
|
168
|
+
|
|
169
|
+
```javascript
|
|
170
|
+
const isValid = await verifyPassword(hash, 'password123');
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### `signAccessToken(payload)`
|
|
174
|
+
|
|
175
|
+
Sign an access token (15-minute expiry).
|
|
176
|
+
|
|
177
|
+
### `signRefreshToken(payload)`
|
|
178
|
+
|
|
179
|
+
Sign a refresh token (7-day expiry).
|
|
180
|
+
|
|
181
|
+
### `verifyJWT(token, secret)`
|
|
182
|
+
|
|
183
|
+
Verify and decode a JWT token.
|
|
184
|
+
|
|
185
|
+
## Error Handling
|
|
186
|
+
|
|
187
|
+
RyAuth throws descriptive errors for various failure conditions:
|
|
188
|
+
|
|
189
|
+
- **Validation Errors**: Invalid input data
|
|
190
|
+
- **Authentication Errors**: Invalid credentials, expired tokens
|
|
191
|
+
- **Authorization Errors**: Insufficient permissions
|
|
192
|
+
- **Adapter Errors**: Database operation failures
|
|
193
|
+
|
|
194
|
+
Always wrap operations in try-catch blocks:
|
|
195
|
+
|
|
196
|
+
```javascript
|
|
197
|
+
try {
|
|
198
|
+
const result = await authService.login(email, password);
|
|
199
|
+
res.json(result);
|
|
200
|
+
} catch (error) {
|
|
201
|
+
res.status(400).json({ error: error.message });
|
|
202
|
+
}
|
|
203
|
+
```
|
package/docs/examples.md
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
# Examples
|
|
2
|
+
|
|
3
|
+
Practical examples for integrating RyAuth into your applications.
|
|
4
|
+
|
|
5
|
+
## Basic Express.js Setup
|
|
6
|
+
|
|
7
|
+
```javascript
|
|
8
|
+
import express from 'express';
|
|
9
|
+
import { createAuthMiddleware, AuthService, MemoryAdapter } from 'ryauth';
|
|
10
|
+
|
|
11
|
+
const app = express();
|
|
12
|
+
app.use(express.json());
|
|
13
|
+
|
|
14
|
+
// Initialize
|
|
15
|
+
const adapter = new MemoryAdapter();
|
|
16
|
+
const authService = new AuthService(adapter);
|
|
17
|
+
const authMiddleware = createAuthMiddleware({
|
|
18
|
+
accessTokenSecret: process.env.ACCESS_TOKEN_SECRET,
|
|
19
|
+
refreshTokenSecret: process.env.REFRESH_TOKEN_SECRET
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
// Routes
|
|
23
|
+
app.post('/auth/register', async (req, res) => {
|
|
24
|
+
try {
|
|
25
|
+
const { email, password } = req.body;
|
|
26
|
+
const result = await authService.register(email, password);
|
|
27
|
+
res.json(result);
|
|
28
|
+
} catch (error) {
|
|
29
|
+
res.status(400).json({ error: error.message });
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
app.post('/auth/login', async (req, res) => {
|
|
34
|
+
try {
|
|
35
|
+
const { email, password } = req.body;
|
|
36
|
+
const result = await authService.login(email, password);
|
|
37
|
+
res.json(result);
|
|
38
|
+
} catch (error) {
|
|
39
|
+
res.status(401).json({ error: error.message });
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
app.get('/api/profile', authMiddleware.authenticate, (req, res) => {
|
|
44
|
+
res.json({ user: req.user });
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
app.listen(3000);
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Role-Based Access Control
|
|
51
|
+
|
|
52
|
+
```javascript
|
|
53
|
+
// Admin-only route
|
|
54
|
+
app.get('/api/admin/users',
|
|
55
|
+
authMiddleware.authenticate,
|
|
56
|
+
authMiddleware.authorize('admin'),
|
|
57
|
+
(req, res) => {
|
|
58
|
+
res.json({ users: [] }); // Return user list
|
|
59
|
+
}
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
// Multiple roles allowed
|
|
63
|
+
app.get('/api/content',
|
|
64
|
+
authMiddleware.authenticate,
|
|
65
|
+
authMiddleware.authorize('user', 'editor', 'admin'),
|
|
66
|
+
(req, res) => {
|
|
67
|
+
res.json({ content: 'Protected content' });
|
|
68
|
+
}
|
|
69
|
+
);
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Token Refresh
|
|
73
|
+
|
|
74
|
+
```javascript
|
|
75
|
+
app.post('/auth/refresh', async (req, res) => {
|
|
76
|
+
try {
|
|
77
|
+
const { refreshToken } = req.body;
|
|
78
|
+
const result = await authService.refresh(refreshToken);
|
|
79
|
+
res.json(result);
|
|
80
|
+
} catch (error) {
|
|
81
|
+
res.status(401).json({ error: error.message });
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Logout (Token Revocation)
|
|
87
|
+
|
|
88
|
+
```javascript
|
|
89
|
+
app.post('/auth/logout', authMiddleware.authenticate, async (req, res) => {
|
|
90
|
+
try {
|
|
91
|
+
// Get refresh token from request (could be in body, cookie, etc.)
|
|
92
|
+
const { refreshToken } = req.body;
|
|
93
|
+
await authService.revokeRefreshToken(refreshToken);
|
|
94
|
+
res.json({ success: true });
|
|
95
|
+
} catch (error) {
|
|
96
|
+
res.status(400).json({ error: error.message });
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Custom Database Adapter
|
|
102
|
+
|
|
103
|
+
```javascript
|
|
104
|
+
import { BaseAdapter } from 'ryauth';
|
|
105
|
+
|
|
106
|
+
export class PostgreSQLAdapter extends BaseAdapter {
|
|
107
|
+
constructor(db) {
|
|
108
|
+
super();
|
|
109
|
+
this.db = db; // Your database connection
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async findUserByEmail(email) {
|
|
113
|
+
const result = await this.db.query(
|
|
114
|
+
'SELECT id, email, hashed_password, role FROM users WHERE email = $1',
|
|
115
|
+
[email]
|
|
116
|
+
);
|
|
117
|
+
return result.rows[0] || null;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async createUser(userData) {
|
|
121
|
+
const result = await this.db.query(
|
|
122
|
+
'INSERT INTO users (email, hashed_password, role) VALUES ($1, $2, $3) RETURNING *',
|
|
123
|
+
[userData.email, userData.hashedPassword, userData.role]
|
|
124
|
+
);
|
|
125
|
+
return result.rows[0];
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async saveRefreshToken(userId, token, expiresAt) {
|
|
129
|
+
await this.db.query(
|
|
130
|
+
'INSERT INTO refresh_tokens (user_id, token, expires_at) VALUES ($1, $2, $3)',
|
|
131
|
+
[userId, token, expiresAt]
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async findRefreshToken(token) {
|
|
136
|
+
const result = await this.db.query(
|
|
137
|
+
'SELECT * FROM refresh_tokens WHERE token = $1 AND expires_at > NOW()',
|
|
138
|
+
[token]
|
|
139
|
+
);
|
|
140
|
+
return result.rows[0] || null;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async revokeRefreshToken(token) {
|
|
144
|
+
await this.db.query(
|
|
145
|
+
'DELETE FROM refresh_tokens WHERE token = $1',
|
|
146
|
+
[token]
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async isRefreshTokenRevoked(token) {
|
|
151
|
+
const result = await this.db.query(
|
|
152
|
+
'SELECT 1 FROM refresh_tokens WHERE token = $1',
|
|
153
|
+
[token]
|
|
154
|
+
);
|
|
155
|
+
return result.rows.length === 0;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Testing with RyAuth
|
|
161
|
+
|
|
162
|
+
```javascript
|
|
163
|
+
import { AuthService, MemoryAdapter } from 'ryauth';
|
|
164
|
+
|
|
165
|
+
describe('Authentication', () => {
|
|
166
|
+
let authService;
|
|
167
|
+
let adapter;
|
|
168
|
+
|
|
169
|
+
beforeEach(() => {
|
|
170
|
+
adapter = new MemoryAdapter();
|
|
171
|
+
authService = new AuthService(adapter);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('should register a new user', async () => {
|
|
175
|
+
const result = await authService.register('test@example.com', 'password123');
|
|
176
|
+
expect(result.success).toBe(true);
|
|
177
|
+
expect(result.userId).toBeDefined();
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('should login with correct credentials', async () => {
|
|
181
|
+
await authService.register('test@example.com', 'password123');
|
|
182
|
+
const result = await authService.login('test@example.com', 'password123');
|
|
183
|
+
|
|
184
|
+
expect(result.success).toBe(true);
|
|
185
|
+
expect(result.accessToken).toBeDefined();
|
|
186
|
+
expect(result.refreshToken).toBeDefined();
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## Environment Configuration
|
|
192
|
+
|
|
193
|
+
```javascript
|
|
194
|
+
// .env
|
|
195
|
+
ACCESS_TOKEN_SECRET=your_super_secret_access_token_key_here_minimum_32_chars
|
|
196
|
+
REFRESH_TOKEN_SECRET=your_super_secret_refresh_token_key_here_minimum_32_chars
|
|
197
|
+
|
|
198
|
+
// Generate secure secrets
|
|
199
|
+
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
## Error Handling
|
|
203
|
+
|
|
204
|
+
```javascript
|
|
205
|
+
// Always wrap auth operations in try-catch
|
|
206
|
+
app.post('/auth/register', async (req, res) => {
|
|
207
|
+
try {
|
|
208
|
+
const result = await authService.register(req.body.email, req.body.password);
|
|
209
|
+
res.json(result);
|
|
210
|
+
} catch (error) {
|
|
211
|
+
// Handle validation errors, duplicate users, etc.
|
|
212
|
+
res.status(400).json({ error: error.message });
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
app.post('/auth/login', async (req, res) => {
|
|
217
|
+
try {
|
|
218
|
+
const result = await authService.login(req.body.email, req.body.password);
|
|
219
|
+
res.json(result);
|
|
220
|
+
} catch (error) {
|
|
221
|
+
// Handle invalid credentials
|
|
222
|
+
res.status(401).json({ error: error.message });
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
```
|
package/index.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// RyAuth - Modern Authentication Library for Node.js
|
|
2
|
+
// Main entry point exporting all public APIs
|
|
3
|
+
|
|
4
|
+
// Core services
|
|
5
|
+
export { AuthService } from './src/services/auth-service.js';
|
|
6
|
+
|
|
7
|
+
// Middleware
|
|
8
|
+
export { createAuthMiddleware } from './src/middleware/auth.js';
|
|
9
|
+
|
|
10
|
+
// Adapters
|
|
11
|
+
export { BaseAdapter } from './src/adapters/base.js';
|
|
12
|
+
export { MemoryAdapter } from './src/adapters/memory.js';
|
|
13
|
+
|
|
14
|
+
// Core utilities (for advanced users)
|
|
15
|
+
export { hashPassword, verifyPassword, signAccessToken, signRefreshToken, verifyJWT } from './src/core/crypto.js';
|