odac 0.9.0 → 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/.github/workflows/auto-pr-description.yml +0 -2
- package/.github/workflows/codeql.yml +46 -0
- package/.github/workflows/release.yml +13 -6
- package/.github/workflows/test-coverage.yml +10 -9
- package/.releaserc.js +9 -6
- package/CHANGELOG.md +62 -150
- package/CODE_OF_CONDUCT.md +1 -1
- package/CONTRIBUTING.md +8 -8
- package/LICENSE +21 -661
- package/README.md +12 -12
- package/SECURITY.md +4 -4
- package/bin/odac.js +101 -0
- package/{framework/web/candy.js → client/odac.js} +310 -44
- package/docs/backend/01-overview/{01-whats-in-the-candy-box.md → 01-whats-in-the-odac-box.md} +4 -2
- package/docs/backend/01-overview/02-super-handy-helper-functions.md +29 -1
- package/docs/backend/01-overview/03-development-server.md +11 -11
- package/docs/backend/02-structure/01-typical-project-layout.md +4 -4
- package/docs/backend/03-config/00-configuration-overview.md +6 -6
- package/docs/backend/03-config/01-database-connection.md +1 -1
- package/docs/backend/03-config/02-static-route-mapping-optional.md +4 -4
- package/docs/backend/03-config/04-environment-variables.md +20 -20
- package/docs/backend/03-config/05-early-hints.md +4 -4
- package/docs/backend/04-routing/01-basic-page-routes.md +4 -4
- package/docs/backend/04-routing/02-controller-less-view-routes.md +5 -5
- package/docs/backend/04-routing/03-api-and-data-routes.md +3 -3
- package/docs/backend/04-routing/04-authentication-aware-routes.md +5 -5
- package/docs/backend/04-routing/05-advanced-routing.md +3 -3
- package/docs/backend/04-routing/06-error-pages.md +17 -17
- package/docs/backend/04-routing/07-cron-jobs.md +13 -13
- package/docs/backend/04-routing/08-middleware.md +214 -0
- package/docs/backend/04-routing/09-websocket-auth-middleware.md +292 -0
- package/docs/backend/04-routing/09-websocket-examples.md +381 -0
- package/docs/backend/04-routing/09-websocket-quick-reference.md +211 -0
- package/docs/backend/04-routing/09-websocket.md +298 -0
- package/docs/backend/05-controllers/01-how-to-build-a-controller.md +3 -3
- package/docs/backend/05-controllers/02-your-trusty-odac-assistant.md +41 -0
- package/docs/backend/05-controllers/03-controller-classes.md +19 -19
- package/docs/backend/05-forms/01-custom-forms.md +114 -114
- package/docs/backend/05-forms/02-automatic-database-insert.md +82 -82
- package/docs/backend/06-request-and-response/01-the-request-object-what-is-the-user-asking-for.md +26 -26
- package/docs/backend/06-request-and-response/02-sending-a-response-replying-to-the-user.md +10 -10
- package/docs/backend/07-views/01-the-view-directory.md +1 -1
- package/docs/backend/07-views/02-rendering-a-view.md +22 -22
- package/docs/backend/07-views/03-template-syntax.md +52 -52
- package/docs/backend/07-views/03-variables.md +84 -84
- package/docs/backend/07-views/04-request-data.md +57 -57
- package/docs/backend/07-views/05-conditionals.md +78 -78
- package/docs/backend/07-views/06-loops.md +114 -114
- package/docs/backend/07-views/07-translations.md +66 -66
- package/docs/backend/07-views/08-backend-javascript.md +103 -103
- package/docs/backend/07-views/09-comments.md +71 -71
- package/docs/backend/08-database/01-database-connection.md +8 -8
- package/docs/backend/08-database/02-using-mysql.md +49 -49
- package/docs/backend/09-validation/01-the-validator-service.md +38 -38
- package/docs/backend/10-authentication/01-user-logins-with-authjs.md +15 -15
- package/docs/backend/10-authentication/02-foiling-villains-with-csrf-protection.md +10 -10
- package/docs/backend/10-authentication/03-register.md +12 -12
- package/docs/backend/10-authentication/{04-candy-register-forms.md → 04-odac-register-forms.md} +141 -141
- package/docs/backend/10-authentication/05-session-management.md +10 -10
- package/docs/backend/10-authentication/{06-candy-login-forms.md → 06-odac-login-forms.md} +125 -125
- package/docs/backend/11-mail/01-the-mail-service.md +5 -5
- package/docs/backend/12-streaming/01-streaming-overview.md +96 -54
- package/docs/backend/13-utilities/{01-candy-var.md → 01-odac-var.md} +109 -109
- package/docs/frontend/01-overview/01-introduction.md +30 -30
- package/docs/frontend/02-ajax-navigation/01-quick-start.md +45 -45
- package/docs/frontend/02-ajax-navigation/02-configuration.md +14 -14
- package/docs/frontend/02-ajax-navigation/03-advanced-usage.md +36 -36
- package/docs/frontend/03-forms/01-form-handling.md +32 -32
- package/docs/frontend/04-api-requests/01-get-post.md +33 -33
- package/docs/frontend/05-streaming/01-client-streaming.md +15 -15
- package/docs/frontend/06-websocket/00-overview.md +76 -0
- package/docs/frontend/06-websocket/01-websocket-client.md +139 -0
- package/docs/frontend/06-websocket/02-shared-websocket.md +149 -0
- package/docs/index.json +49 -11
- package/eslint.config.mjs +6 -6
- package/{framework/index.js → index.js} +1 -1
- package/package.json +14 -39
- package/{framework/src → src}/Auth.js +59 -59
- package/{framework/src → src}/Config.js +3 -3
- package/{framework/src → src}/Lang.js +7 -7
- package/{framework/src → src}/Mail.js +5 -5
- package/{framework/src → src}/Mysql.js +42 -42
- package/src/Odac.js +112 -0
- package/{framework/src → src}/Request.js +38 -36
- package/{framework/src → src}/Route/Internal.js +116 -116
- package/src/Route/Middleware.js +75 -0
- package/src/Route.js +621 -0
- package/src/Server.js +22 -0
- package/{framework/src → src}/Stream.js +11 -3
- package/{framework/src → src}/Validator.js +21 -21
- package/{framework/src → src}/Var.js +5 -5
- package/{framework/src → src}/View/EarlyHints.js +1 -1
- package/{framework/src → src}/View/Form.js +69 -69
- package/{framework/src → src}/View.js +78 -81
- package/src/WebSocket.js +403 -0
- package/template/config.json +5 -0
- package/{web → template}/controller/page/about.js +6 -6
- package/{web → template}/controller/page/index.js +9 -9
- package/{web → template}/package.json +4 -5
- package/{web → template}/public/assets/css/style.css +4 -4
- package/{web → template}/public/assets/js/app.js +6 -6
- package/{web → template}/route/www.js +6 -6
- package/{web → template}/skeleton/main.html +1 -1
- package/{web → template}/view/content/about.html +5 -5
- package/{web → template}/view/content/home.html +12 -12
- package/template/view/footer/main.html +11 -0
- package/{web → template}/view/head/main.html +1 -1
- package/{web → template}/view/header/main.html +2 -2
- package/test/core/Candy.test.js +58 -58
- package/test/core/Commands.test.js +7 -7
- package/test/core/Config.test.js +82 -85
- package/test/core/Lang.test.js +2 -2
- package/test/core/Process.test.js +6 -6
- package/test/framework/Route.test.js +56 -37
- package/test/framework/View/EarlyHints.test.js +2 -2
- package/test/framework/WebSocket.test.js +100 -0
- package/test/framework/middleware.test.js +85 -0
- package/test/server/Api.test.js +31 -31
- package/test/server/DNS.test.js +11 -11
- package/test/server/Hub.test.js +497 -0
- package/test/server/Mail.account.test_.js +3 -3
- package/test/server/Mail.init.test_.js +10 -10
- package/test/server/Mail.test_.js +20 -20
- package/test/server/SSL.test_.js +54 -54
- package/test/server/Server.test.js +39 -39
- package/test/server/Service.test_.js +7 -7
- package/test/server/Subdomain.test.js +7 -7
- package/test/server/Web/Firewall.test.js +87 -87
- package/test/server/Web/Proxy.test.js +397 -0
- package/test/server/{Web.test_.js → Web.test.js} +137 -205
- package/test/server/__mocks__/fs.js +2 -2
- package/test/server/__mocks__/{globalCandy.js → globalOdac.js} +5 -5
- package/test/server/__mocks__/index.js +6 -6
- package/test/server/__mocks__/testFactories.js +1 -1
- package/test/server/__mocks__/testHelpers.js +7 -7
- package/.husky/pre-commit +0 -2
- package/.kiro/steering/code-style.md +0 -56
- package/.kiro/steering/product.md +0 -20
- package/.kiro/steering/structure.md +0 -77
- package/.kiro/steering/tech.md +0 -87
- package/AGENTS.md +0 -84
- package/bin/candy +0 -10
- package/bin/candypack +0 -10
- package/cli/index.js +0 -3
- package/cli/src/Cli.js +0 -348
- package/cli/src/Connector.js +0 -93
- package/cli/src/Monitor.js +0 -416
- package/core/Candy.js +0 -87
- package/core/Commands.js +0 -239
- package/core/Config.js +0 -1094
- package/core/Lang.js +0 -52
- package/core/Log.js +0 -43
- package/core/Process.js +0 -26
- package/docs/backend/05-controllers/02-your-trusty-candy-assistant.md +0 -20
- package/docs/server/01-installation/01-quick-install.md +0 -19
- package/docs/server/01-installation/02-manual-installation-via-npm.md +0 -9
- package/docs/server/02-get-started/01-core-concepts.md +0 -7
- package/docs/server/02-get-started/02-basic-commands.md +0 -57
- package/docs/server/02-get-started/03-cli-reference.md +0 -276
- package/docs/server/02-get-started/04-cli-quick-reference.md +0 -102
- package/docs/server/03-service/01-start-a-new-service.md +0 -57
- package/docs/server/03-service/02-delete-a-service.md +0 -48
- package/docs/server/04-web/01-create-a-website.md +0 -36
- package/docs/server/04-web/02-list-websites.md +0 -9
- package/docs/server/04-web/03-delete-a-website.md +0 -29
- package/docs/server/05-subdomain/01-create-a-subdomain.md +0 -32
- package/docs/server/05-subdomain/02-list-subdomains.md +0 -33
- package/docs/server/05-subdomain/03-delete-a-subdomain.md +0 -41
- package/docs/server/06-ssl/01-renew-an-ssl-certificate.md +0 -34
- package/docs/server/07-mail/01-create-a-mail-account.md +0 -23
- package/docs/server/07-mail/02-delete-a-mail-account.md +0 -20
- package/docs/server/07-mail/03-list-mail-accounts.md +0 -20
- package/docs/server/07-mail/04-change-account-password.md +0 -23
- package/framework/src/Candy.js +0 -81
- package/framework/src/Route.js +0 -455
- package/framework/src/Server.js +0 -15
- package/locale/de-DE.json +0 -80
- package/locale/en-US.json +0 -79
- package/locale/es-ES.json +0 -80
- package/locale/fr-FR.json +0 -80
- package/locale/pt-BR.json +0 -80
- package/locale/ru-RU.json +0 -80
- package/locale/tr-TR.json +0 -85
- package/locale/zh-CN.json +0 -80
- package/server/index.js +0 -5
- package/server/src/Api.js +0 -88
- package/server/src/DNS.js +0 -940
- package/server/src/Hub.js +0 -535
- package/server/src/Mail.js +0 -571
- package/server/src/SSL.js +0 -180
- package/server/src/Server.js +0 -27
- package/server/src/Service.js +0 -248
- package/server/src/Subdomain.js +0 -64
- package/server/src/Web/Firewall.js +0 -170
- package/server/src/Web/Proxy.js +0 -134
- package/server/src/Web.js +0 -451
- package/server/src/mail/imap.js +0 -1091
- package/server/src/mail/server.js +0 -32
- package/server/src/mail/smtp.js +0 -786
- package/test/server/Client.test.js +0 -338
- package/test/server/__mocks__/http-proxy.js +0 -105
- package/watchdog/index.js +0 -3
- package/watchdog/src/Watchdog.js +0 -156
- package/web/config.json +0 -5
- package/web/view/footer/main.html +0 -11
- /package/{framework/src → src}/Env.js +0 -0
- /package/{framework/src → src}/Route/Cron.js +0 -0
- /package/{framework/src → src}/Token.js +0 -0
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
## 🔗 Middleware
|
|
2
|
+
|
|
3
|
+
Middleware allows you to run code before your controllers execute. Perfect for authentication, logging, rate limiting, and more.
|
|
4
|
+
|
|
5
|
+
### Creating Middleware
|
|
6
|
+
|
|
7
|
+
Create middleware files in the `middleware/` directory:
|
|
8
|
+
|
|
9
|
+
```javascript
|
|
10
|
+
// middleware/auth.js
|
|
11
|
+
module.exports = async (Odac) => {
|
|
12
|
+
if (!await Odac.Auth.check()) {
|
|
13
|
+
return Odac.direct('/login') // Redirect to login
|
|
14
|
+
}
|
|
15
|
+
// No return = continue to next middleware or controller
|
|
16
|
+
}
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
**Middleware Rules:**
|
|
20
|
+
- Return `false` → Stop execution (403 Forbidden)
|
|
21
|
+
- Return `Odac.abort(code)` → Stop with custom error code
|
|
22
|
+
- Return `Odac.direct(url)` → Stop and redirect
|
|
23
|
+
- Return nothing or `true` → Continue to next middleware/controller
|
|
24
|
+
|
|
25
|
+
### Using Middleware
|
|
26
|
+
|
|
27
|
+
#### Single Route
|
|
28
|
+
```javascript
|
|
29
|
+
Odac.Route
|
|
30
|
+
.use('logger')
|
|
31
|
+
.page('/contact', 'contact')
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
#### Multiple Routes
|
|
35
|
+
```javascript
|
|
36
|
+
Odac.Route
|
|
37
|
+
.use('cors')
|
|
38
|
+
.page('/api/users', 'api.users')
|
|
39
|
+
.page('/api/posts', 'api.posts')
|
|
40
|
+
.get('/api/stats', 'api.stats')
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
#### Multiple Middlewares
|
|
44
|
+
```javascript
|
|
45
|
+
Odac.Route
|
|
46
|
+
.use('cors', 'rateLimit')
|
|
47
|
+
.post('/api/upload', 'api.upload')
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
#### With Auth Routes
|
|
51
|
+
|
|
52
|
+
`Odac.Route.auth` already requires authentication. You can add additional middleware on top:
|
|
53
|
+
|
|
54
|
+
```javascript
|
|
55
|
+
// Admin-only routes (requires login + admin role)
|
|
56
|
+
Odac.Route.auth
|
|
57
|
+
.use('admin')
|
|
58
|
+
.page('/admin/dashboard', 'admin.dashboard')
|
|
59
|
+
.page('/admin/users', 'admin.users')
|
|
60
|
+
.post('/admin/settings', 'admin.settings')
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
```javascript
|
|
64
|
+
// Premium user routes (requires login + premium subscription)
|
|
65
|
+
Odac.Route.auth
|
|
66
|
+
.use('premium')
|
|
67
|
+
.page('/premium/content', 'premium.content')
|
|
68
|
+
.page('/premium/downloads', 'premium.downloads')
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
```javascript
|
|
72
|
+
// Multiple middlewares with auth
|
|
73
|
+
Odac.Route.auth
|
|
74
|
+
.use('verified', 'rateLimit')
|
|
75
|
+
.post('/api/sensitive', 'api.sensitive')
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Middleware Examples
|
|
79
|
+
|
|
80
|
+
#### Authentication
|
|
81
|
+
```javascript
|
|
82
|
+
// middleware/auth.js
|
|
83
|
+
module.exports = async (Odac) => {
|
|
84
|
+
if (!await Odac.Auth.check()) {
|
|
85
|
+
return Odac.direct('/login')
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
#### Admin Check
|
|
91
|
+
```javascript
|
|
92
|
+
// middleware/admin.js
|
|
93
|
+
module.exports = async (Odac) => {
|
|
94
|
+
const user = await Odac.Auth.user()
|
|
95
|
+
if (!user || user.role !== 'admin') {
|
|
96
|
+
return false // 403 Forbidden
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
#### CORS Headers
|
|
102
|
+
```javascript
|
|
103
|
+
// middleware/cors.js
|
|
104
|
+
module.exports = async (Odac) => {
|
|
105
|
+
Odac.Request.header('Access-Control-Allow-Origin', '*')
|
|
106
|
+
Odac.Request.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE')
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
#### Rate Limiting
|
|
111
|
+
```javascript
|
|
112
|
+
// middleware/rateLimit.js
|
|
113
|
+
const requests = new Map()
|
|
114
|
+
let lastCleanup = Date.now()
|
|
115
|
+
|
|
116
|
+
module.exports = async (Odac) => {
|
|
117
|
+
const ip = Odac.Request.ip
|
|
118
|
+
const now = Date.now()
|
|
119
|
+
const limit = 100
|
|
120
|
+
const window = 60000
|
|
121
|
+
|
|
122
|
+
if (now - lastCleanup > window) {
|
|
123
|
+
for (const [key, times] of requests.entries()) {
|
|
124
|
+
const recent = times.filter(time => now - time < window)
|
|
125
|
+
if (recent.length === 0) {
|
|
126
|
+
requests.delete(key)
|
|
127
|
+
} else {
|
|
128
|
+
requests.set(key, recent)
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
lastCleanup = now
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (!requests.has(ip)) {
|
|
135
|
+
requests.set(ip, [])
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const userRequests = requests.get(ip).filter(time => now - time < window)
|
|
139
|
+
|
|
140
|
+
if (userRequests.length >= limit) {
|
|
141
|
+
return Odac.abort(429, 'Too many requests')
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
userRequests.push(now)
|
|
145
|
+
requests.set(ip, userRequests)
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
> **Note:** This example uses in-memory storage for simplicity. For production environments with multiple server instances, consider using Redis or Memcached for distributed rate limiting.
|
|
150
|
+
|
|
151
|
+
#### Premium Check with View
|
|
152
|
+
```javascript
|
|
153
|
+
// middleware/premium.js
|
|
154
|
+
module.exports = async (Odac) => {
|
|
155
|
+
const user = await Odac.Auth.user()
|
|
156
|
+
|
|
157
|
+
if (!user.isPremium) {
|
|
158
|
+
return Odac.View.render('premium/upgrade', {
|
|
159
|
+
user: user,
|
|
160
|
+
currentPage: Odac.Request.url
|
|
161
|
+
})
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
#### Logging
|
|
167
|
+
```javascript
|
|
168
|
+
// middleware/logger.js
|
|
169
|
+
module.exports = async (Odac) => {
|
|
170
|
+
console.log(`${Odac.Request.method} ${Odac.Request.url}`)
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
#### Inline Middleware
|
|
175
|
+
```javascript
|
|
176
|
+
Odac.Route
|
|
177
|
+
.use(async (Odac) => {
|
|
178
|
+
console.log('Custom middleware')
|
|
179
|
+
})
|
|
180
|
+
.page('/special', 'special')
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Complete Example
|
|
184
|
+
|
|
185
|
+
```javascript
|
|
186
|
+
// route/www.js
|
|
187
|
+
|
|
188
|
+
// Public routes
|
|
189
|
+
Odac.Route.page('/', 'index')
|
|
190
|
+
Odac.Route.page('/about', 'about')
|
|
191
|
+
|
|
192
|
+
// API routes with CORS
|
|
193
|
+
Odac.Route
|
|
194
|
+
.use('cors', 'rateLimit')
|
|
195
|
+
.get('/api/public', 'api.public')
|
|
196
|
+
.post('/api/contact', 'api.contact')
|
|
197
|
+
|
|
198
|
+
// User routes (requires login)
|
|
199
|
+
Odac.Route.auth
|
|
200
|
+
.page('/profile', 'profile')
|
|
201
|
+
.page('/settings', 'settings')
|
|
202
|
+
.post('/api/update', 'update')
|
|
203
|
+
|
|
204
|
+
// Admin routes (requires login + admin role)
|
|
205
|
+
Odac.Route.auth
|
|
206
|
+
.use('admin')
|
|
207
|
+
.page('/admin', 'admin.index')
|
|
208
|
+
.page('/admin/users', 'admin.users')
|
|
209
|
+
.post('/admin/delete', 'admin.delete')
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### Hot Reloading
|
|
213
|
+
|
|
214
|
+
Middleware files are automatically reloaded when changed (every 5 seconds), just like controllers and routes. No server restart needed during development.
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
# WebSocket Authentication & Middleware
|
|
2
|
+
|
|
3
|
+
Advanced authentication and middleware patterns for WebSocket routes.
|
|
4
|
+
|
|
5
|
+
## Authentication Methods
|
|
6
|
+
|
|
7
|
+
### 1. Using auth.ws() (Recommended)
|
|
8
|
+
|
|
9
|
+
The simplest way to require authentication:
|
|
10
|
+
|
|
11
|
+
```javascript
|
|
12
|
+
Odac.Route.auth.ws('/secure', async Odac => {
|
|
13
|
+
const user = await Odac.Auth.user()
|
|
14
|
+
|
|
15
|
+
Odac.ws.send({
|
|
16
|
+
type: 'authenticated',
|
|
17
|
+
user: user.name
|
|
18
|
+
})
|
|
19
|
+
})
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**Behavior:**
|
|
23
|
+
- Automatically checks authentication before handler runs
|
|
24
|
+
- Closes connection with code `4001` if not authenticated
|
|
25
|
+
- No need for manual auth checks
|
|
26
|
+
|
|
27
|
+
### 2. Manual Authentication Check
|
|
28
|
+
|
|
29
|
+
For custom authentication logic:
|
|
30
|
+
|
|
31
|
+
```javascript
|
|
32
|
+
Odac.Route.ws('/custom-auth', async Odac => {
|
|
33
|
+
const token = Odac.Request.header('Authorization')
|
|
34
|
+
|
|
35
|
+
if (!token) {
|
|
36
|
+
Odac.ws.close(4001, 'Missing token')
|
|
37
|
+
return
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const user = await validateCustomToken(token)
|
|
41
|
+
|
|
42
|
+
if (!user) {
|
|
43
|
+
Odac.ws.close(4001, 'Invalid token')
|
|
44
|
+
return
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
Odac.ws.data.user = user
|
|
48
|
+
Odac.ws.send({type: 'authenticated'})
|
|
49
|
+
})
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Middleware Support
|
|
53
|
+
|
|
54
|
+
### Basic Middleware
|
|
55
|
+
|
|
56
|
+
```javascript
|
|
57
|
+
Odac.Route.use('rate-limit').ws('/chat', Odac => {
|
|
58
|
+
Odac.ws.send({type: 'connected'})
|
|
59
|
+
})
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Multiple Middleware
|
|
63
|
+
|
|
64
|
+
Middleware runs in order:
|
|
65
|
+
|
|
66
|
+
```javascript
|
|
67
|
+
Odac.Route.use('auth', 'rate-limit', 'log').ws('/chat', Odac => {
|
|
68
|
+
Odac.ws.send({type: 'ready'})
|
|
69
|
+
})
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Creating WebSocket Middleware
|
|
73
|
+
|
|
74
|
+
**middleware/websocket-rate-limit.js:**
|
|
75
|
+
|
|
76
|
+
```javascript
|
|
77
|
+
const connections = new Map()
|
|
78
|
+
const RATE_LIMIT = 5
|
|
79
|
+
const WINDOW = 60000
|
|
80
|
+
|
|
81
|
+
module.exports = async Odac => {
|
|
82
|
+
const ip = Odac.Request.ip
|
|
83
|
+
const now = Date.now()
|
|
84
|
+
|
|
85
|
+
if (!connections.has(ip)) {
|
|
86
|
+
connections.set(ip, [])
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const userConnections = connections.get(ip)
|
|
90
|
+
const recentConnections = userConnections.filter(time => now - time < WINDOW)
|
|
91
|
+
|
|
92
|
+
if (recentConnections.length >= RATE_LIMIT) {
|
|
93
|
+
return false
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
recentConnections.push(now)
|
|
97
|
+
connections.set(ip, recentConnections)
|
|
98
|
+
|
|
99
|
+
return true
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
**Usage:**
|
|
104
|
+
|
|
105
|
+
```javascript
|
|
106
|
+
Odac.Route.use('websocket-rate-limit').ws('/chat', Odac => {
|
|
107
|
+
Odac.ws.send({type: 'connected'})
|
|
108
|
+
})
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Middleware Return Values
|
|
112
|
+
|
|
113
|
+
| Return Value | Behavior |
|
|
114
|
+
|--------------|----------|
|
|
115
|
+
| `true` or `undefined` | Continue to next middleware/handler |
|
|
116
|
+
| `false` | Close connection with code `4003` (Forbidden) |
|
|
117
|
+
| Any other value | Close connection with code `4000` |
|
|
118
|
+
|
|
119
|
+
### Advanced Middleware Example
|
|
120
|
+
|
|
121
|
+
**middleware/websocket-auth.js:**
|
|
122
|
+
|
|
123
|
+
```javascript
|
|
124
|
+
module.exports = async Odac => {
|
|
125
|
+
const token = Odac.Request.header('X-WS-Token')
|
|
126
|
+
|
|
127
|
+
if (!token) {
|
|
128
|
+
console.log('WebSocket connection rejected: No token')
|
|
129
|
+
return false
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
try {
|
|
133
|
+
const user = await verifyToken(token)
|
|
134
|
+
|
|
135
|
+
if (!user) {
|
|
136
|
+
console.log('WebSocket connection rejected: Invalid token')
|
|
137
|
+
return false
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
Odac.Auth.setUser(user)
|
|
141
|
+
return true
|
|
142
|
+
|
|
143
|
+
} catch (error) {
|
|
144
|
+
console.error('WebSocket auth error:', error)
|
|
145
|
+
return false
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## Combining Auth and Middleware
|
|
151
|
+
|
|
152
|
+
You can combine `auth.ws()` with middleware:
|
|
153
|
+
|
|
154
|
+
```javascript
|
|
155
|
+
// This won't work - auth.ws() doesn't support chaining
|
|
156
|
+
// Odac.Route.use('rate-limit').auth.ws('/chat', handler)
|
|
157
|
+
|
|
158
|
+
// Instead, use middleware that includes auth check
|
|
159
|
+
Odac.Route.use('websocket-auth', 'rate-limit').ws('/chat', Odac => {
|
|
160
|
+
const user = Odac.Auth.user()
|
|
161
|
+
Odac.ws.send({user: user.name})
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
// Or use auth.ws() without middleware
|
|
165
|
+
Odac.Route.auth.ws('/chat', Odac => {
|
|
166
|
+
const user = Odac.Auth.user()
|
|
167
|
+
Odac.ws.send({user: user.name})
|
|
168
|
+
})
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Real-World Examples
|
|
172
|
+
|
|
173
|
+
### Rate-Limited Chat
|
|
174
|
+
|
|
175
|
+
```javascript
|
|
176
|
+
// middleware/chat-rate-limit.js
|
|
177
|
+
const userMessages = new Map()
|
|
178
|
+
|
|
179
|
+
module.exports = async Odac => {
|
|
180
|
+
const user = await Odac.Auth.user()
|
|
181
|
+
if (!user) return false
|
|
182
|
+
|
|
183
|
+
const userId = user.id
|
|
184
|
+
const now = Date.now()
|
|
185
|
+
|
|
186
|
+
if (!userMessages.has(userId)) {
|
|
187
|
+
userMessages.set(userId, [])
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const messages = userMessages.get(userId)
|
|
191
|
+
const recentMessages = messages.filter(time => now - time < 10000)
|
|
192
|
+
|
|
193
|
+
if (recentMessages.length >= 10) {
|
|
194
|
+
return false
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
recentMessages.push(now)
|
|
198
|
+
userMessages.set(userId, recentMessages)
|
|
199
|
+
|
|
200
|
+
return true
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// route/websocket.js
|
|
204
|
+
Odac.Route.use('chat-rate-limit').auth.ws('/chat', async Odac => {
|
|
205
|
+
const user = await Odac.Auth.user()
|
|
206
|
+
Odac.ws.join('general')
|
|
207
|
+
|
|
208
|
+
Odac.ws.on('message', data => {
|
|
209
|
+
Odac.ws.to('general').send({
|
|
210
|
+
user: user.name,
|
|
211
|
+
text: data.text
|
|
212
|
+
})
|
|
213
|
+
})
|
|
214
|
+
})
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### Admin-Only WebSocket
|
|
218
|
+
|
|
219
|
+
```javascript
|
|
220
|
+
// middleware/admin-only.js
|
|
221
|
+
module.exports = async Odac => {
|
|
222
|
+
const user = await Odac.Auth.user()
|
|
223
|
+
|
|
224
|
+
if (!user || !user.isAdmin) {
|
|
225
|
+
return false
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return true
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// route/websocket.js
|
|
232
|
+
Odac.Route.use('admin-only').ws('/admin-dashboard', Odac => {
|
|
233
|
+
const sendStats = async () => {
|
|
234
|
+
const stats = await getSystemStats()
|
|
235
|
+
Odac.ws.send({type: 'stats', data: stats})
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
sendStats()
|
|
239
|
+
const interval = setInterval(sendStats, 5000)
|
|
240
|
+
|
|
241
|
+
Odac.ws.on('close', () => clearInterval(interval))
|
|
242
|
+
})
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### IP Whitelist
|
|
246
|
+
|
|
247
|
+
```javascript
|
|
248
|
+
// middleware/ip-whitelist.js
|
|
249
|
+
const ALLOWED_IPS = ['127.0.0.1', '192.168.1.100']
|
|
250
|
+
|
|
251
|
+
module.exports = async Odac => {
|
|
252
|
+
const ip = Odac.Request.ip
|
|
253
|
+
|
|
254
|
+
if (!ALLOWED_IPS.includes(ip)) {
|
|
255
|
+
console.log(`WebSocket connection rejected from ${ip}`)
|
|
256
|
+
return false
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return true
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// route/websocket.js
|
|
263
|
+
Odac.Route.use('ip-whitelist').ws('/internal', Odac => {
|
|
264
|
+
Odac.ws.send({type: 'internal-access-granted'})
|
|
265
|
+
})
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
## Error Handling
|
|
269
|
+
|
|
270
|
+
Middleware errors should be handled gracefully:
|
|
271
|
+
|
|
272
|
+
```javascript
|
|
273
|
+
module.exports = async Odac => {
|
|
274
|
+
try {
|
|
275
|
+
const result = await someAsyncOperation()
|
|
276
|
+
return result.isValid
|
|
277
|
+
} catch (error) {
|
|
278
|
+
console.error('Middleware error:', error)
|
|
279
|
+
return false
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
## Best Practices
|
|
285
|
+
|
|
286
|
+
1. **Always return a value** from middleware (true/false/undefined)
|
|
287
|
+
2. **Log rejections** for debugging
|
|
288
|
+
3. **Clean up resources** in middleware if needed
|
|
289
|
+
4. **Use auth.ws()** for simple authentication
|
|
290
|
+
5. **Use middleware** for complex logic (rate limiting, logging, etc.)
|
|
291
|
+
6. **Combine middleware** for layered security
|
|
292
|
+
7. **Test middleware** independently before using in routes
|