@rodit/rodit-auth-be 9.12.0 → 9.13.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/README.md CHANGED
@@ -2,11 +2,13 @@
2
2
 
3
3
  A comprehensive Node.js SDK for implementing RODiT-based mutual authentication, authorization, self-configuration, and session management in Express.js applications.
4
4
 
5
- **Version:** 1.1.0
5
+ **Version:** 9.13.0
6
6
  **License:** Proprietary
7
7
  **Author:** Discernible IO
8
8
 
9
- **Login `POST` /api/login:** Use **`accountid`**, **`timestamp`**, and **`base64url_signature`**. Sign UTF-8 bytes of `accountid + timestamp_iso`, and reject deprecated keys such as **`signature`** and **`account_id`**. See [CHANGELOG.md](./CHANGELOG.md).
9
+ **Login `POST` /api/login:** Use **`accountid`** (or **`roditid`**), **`timestamp`**, and **`base64url_signature`**. Sign UTF-8 bytes of `identifier + timestamp_iso`, and reject deprecated keys such as **`signature`** and **`account_id`**. See [CHANGELOG.md](./CHANGELOG.md).
10
+
11
+ **9.13 federated login:** A client RODiT issued for API A can log into API B in the same SR/CR family via `login_server({ apiEndpoint })`. See [Federated login](#federated-login-same-family-different-api-url).
10
12
 
11
13
  ## Table of Contents
12
14
 
@@ -15,6 +17,7 @@ A comprehensive Node.js SDK for implementing RODiT-based mutual authentication,
15
17
  - [Installation & Setup](#installation--setup)
16
18
  - [Authentication](#authentication)
17
19
  - [Login Mode Control](#login-mode-control)
20
+ - [Federated login](#federated-login-same-family-different-api-url)
18
21
  - [Authorization & Permissions](#authorization--permissions)
19
22
  - [Session Management](#session-management)
20
23
  - [Session lifetime and TTL](#session-lifetime-and-ttl)
@@ -37,92 +40,75 @@ A comprehensive Node.js SDK for implementing RODiT-based mutual authentication,
37
40
 
38
41
  ### Installation
39
42
 
40
- ```text
41
- PSEUDOCODE
42
- INPUTS:
43
- - Use values defined by the surrounding section/context.
44
- STEPS:
45
- - RUN COMMAND: npm install @rodit/rodit-auth-be
46
- OUTPUTS:
47
- - Produces the section's intended result using equivalent logic.
43
+ ```bash
44
+ npm install @rodit/rodit-auth-be
48
45
  ```
49
46
 
50
47
  ### Basic Server Setup
51
48
 
52
- ```text
53
- PSEUDOCODE
54
- INPUTS:
55
- - Use values defined by the surrounding section/context.
56
- STEPS:
57
- - SET express TO require('express')
58
- - DO: const { RoditClient } = require('@rodit/rodit-auth-be')
59
- - DO: const { setExpressSessionStore } = require('@rodit/rodit-auth-be/lib/auth/sessionmanager')
60
- - DO: const { ulid } = require('ulid')
61
- - SET session TO require('express-session')
62
- - SET SQLiteStore TO require('connect-sqlite3')(session)
63
- - SET app TO express()
64
- - DO: let roditClient
65
- - NOTE: Configure session storage BEFORE initializing RoditClient
66
- - SET sessionStore TO new SQLiteStore({
67
- - FIELD: db: 'sessions.db',
68
- - FIELD: dir: './data',
69
- - FIELD: table: 'sessions'
70
- - DO: })
71
- - DO: setExpressSessionStore(sessionStore)
72
- - NOTE: Configure Express middleware
73
- - DO: app.use(express.json())
74
- - FIELD: app.use(express.urlencoded({ extended: false }))
75
- - NOTE: Request context middleware
76
- - DO: app.use((req, res, next) => {
77
- - DO: req.requestId = req.headers['x-request-id'] || ulid()
78
- - DO: req.startTime = Date.now()
79
- - DO: next()
80
- - DO: })
81
- - NOTE: Server startup with SDK initialization
82
- - DO: async function startServer() {
83
- - DO: try {
84
- - NOTE: Initialize RODiT client (use 'server' for server applications)
85
- - SET roditClient TO await RoditClient.create('server')
86
- - NOTE: Store client in app.locals for route access
87
- - DO: app.locals.roditClient = roditClient
88
- - NOTE: Get logger and other services from client
89
- - SET logger TO roditClient.getLogger()
90
- - SET config TO roditClient.getConfig()
91
- - SET loggingmw TO roditClient.getLoggingMiddleware()
92
- - NOTE: Apply logging middleware
93
- - DO: app.use(loggingmw)
94
- - NOTE: Create authentication middleware
95
- - SET authenticate TO (req, res, next) => roditClient.authenticate(req, res, next)
96
- - NOTE: Logout-specific auth allows signature-valid expired tokens for clean session closure
97
- - SET authenticateLogout TO (req, res, next) => roditClient.authenticateForLogout(req, res, next)
98
- - SET authorize TO (req, res, next) => roditClient.authorize(req, res, next)
99
- - NOTE: Public routes
100
- - DO: app.post('/api/login', (req, res) => {
101
- - DO: req.logAction = 'login-attempt'
102
- - RETURN roditClient.login_client(req, res)
103
- - DO: })
104
- - NOTE: Protected routes
105
- - DO: app.post('/api/logout', authenticateLogout, (req, res) => {
106
- - DO: req.logAction = 'logout-attempt'
107
- - RETURN roditClient.logout_client(req, res)
108
- - DO: })
109
- - DO: app.get('/api/protected', authenticate, (req, res) => {
110
- - FIELD: res.json({ message: 'Protected data', user: req.user })
111
- - DO: })
112
- - NOTE: Protected + authorized routes
113
- - DO: app.use('/api/admin', authenticate, authorize, adminRoutes)
114
- - SET port TO 3000
115
- - DO: app.listen(port, () => {
116
- - DO: logger.info(`RODiT Authentication Server running on port ${port}`)
117
- - DO: })
118
- - DO: } catch (error) {
119
- - FIELD: console.error('Server initialization failed:', error)
120
- - DO: process.exit(1)
121
- - }
122
- - }
123
- - DO: startServer()
124
- OUTPUTS:
125
- - Produces the section's intended result using equivalent logic.
49
+ ```javascript
50
+ const express = require('express');
51
+ const { RoditClient } = require('@rodit/rodit-auth-be');
52
+ const { setExpressSessionStore } = require('@rodit/rodit-auth-be/lib/auth/sessionmanager');
53
+ const { ulid } = require('ulid');
54
+ const session = require('express-session');
55
+ const SQLiteStore = require('connect-sqlite3')(session);
56
+
57
+ const app = express();
58
+
59
+ // Configure session storage BEFORE initializing RoditClient
60
+ const sessionStore = new SQLiteStore({
61
+ db: 'sessions.db',
62
+ dir: './data',
63
+ table: 'sessions',
64
+ });
65
+ setExpressSessionStore(sessionStore);
66
+
67
+ app.use(express.json());
68
+ app.use(express.urlencoded({ extended: false }));
69
+ app.use((req, res, next) => {
70
+ req.requestId = req.headers['x-request-id'] || ulid();
71
+ req.startTime = Date.now();
72
+ next();
73
+ });
74
+
75
+ async function startServer() {
76
+ try {
77
+ const roditClient = await RoditClient.create('server');
78
+ app.locals.roditClient = roditClient;
79
+
80
+ const logger = roditClient.getLogger();
81
+ app.use(roditClient.getLoggingMiddleware());
82
+
83
+ const authenticate = (req, res, next) => roditClient.authenticate(req, res, next);
84
+ const authenticateLogout = (req, res, next) =>
85
+ roditClient.authenticateForLogout(req, res, next);
86
+ const authorize = (req, res, next) => roditClient.authorize(req, res, next);
87
+
88
+ app.post('/api/login', (req, res) => {
89
+ req.logAction = 'login-attempt';
90
+ roditClient.login_client(req, res);
91
+ });
92
+ app.post('/api/logout', authenticateLogout, (req, res) => {
93
+ req.logAction = 'logout-attempt';
94
+ roditClient.logout_client(req, res);
95
+ });
96
+ app.get('/api/protected', authenticate, (req, res) => {
97
+ res.json({ message: 'Protected data', user: req.user });
98
+ });
99
+ app.use('/api/admin', authenticate, authorize, adminRoutes);
100
+
101
+ const port = 3000;
102
+ app.listen(port, () => {
103
+ logger.info(`RODiT Authentication Server running on port ${port}`);
104
+ });
105
+ } catch (error) {
106
+ console.error('Server initialization failed:', error);
107
+ process.exit(1);
108
+ }
109
+ }
110
+
111
+ startServer();
126
112
  ```
127
113
 
128
114
  ## Core Concepts
@@ -142,58 +128,46 @@ The SDK centers around the `RoditClient` class, which provides a unified interfa
142
128
 
143
129
  Store the initialized client in `app.locals` for consistent access across your application:
144
130
 
145
- ```text
146
- PSEUDOCODE
147
- INPUTS:
148
- - Use values defined by the surrounding section/context.
149
- STEPS:
150
- - NOTE: In main app.js
151
- - SET roditClient TO await RoditClient.create('server')
152
- - DO: app.locals.roditClient = roditClient
153
- - NOTE: In route modules
154
- - SET router TO express.Router()
155
- - DO: router.get('/data', (req, res) => {
156
- - SET client TO req.app.locals.roditClient
157
- - SET logger TO client.getLogger()
158
- - DO: logger.info('Processing request', {
159
- - FIELD: component: 'DataRoute',
160
- - FIELD: userId: req.user?.id
161
- - DO: })
162
- - FIELD: res.json({ data: 'example' })
163
- - DO: })
164
- OUTPUTS:
165
- - Produces the section's intended result using equivalent logic.
131
+ ```javascript
132
+ // In main app.js
133
+ const roditClient = await RoditClient.create('server')
134
+ app.locals.roditClient = roditClient
135
+ // In route modules
136
+ const router = express.Router()
137
+ router.get('/data', (req, res) => {
138
+ const client = req.app.locals.roditClient
139
+ const logger = client.getLogger()
140
+ logger.info('Processing request', {
141
+ component: 'DataRoute',
142
+ userId: req.user?.id
143
+ })
144
+ res.json({ data: 'example' })
145
+ })
166
146
  ```
167
147
 
168
148
  ### Authentication Middleware Pattern
169
149
 
170
150
  Create middleware functions that delegate to the RoditClient:
171
151
 
172
- ```text
173
- PSEUDOCODE
174
- INPUTS:
175
- - Use values defined by the surrounding section/context.
176
- STEPS:
177
- - NOTE: Create reusable middleware
178
- - SET authenticate TO (req, res, next) => {
179
- - SET client TO req.app.locals.roditClient
180
- - CHECK CONDITION: if (!client) {
181
- - RETURN res.status(503).json({ error: 'Authentication service unavailable' })
182
- - }
183
- - RETURN client.authenticate(req, res, next)
184
- - DO: }
185
- - SET authorize TO (req, res, next) => {
186
- - SET client TO req.app.locals.roditClient
187
- - CHECK CONDITION: if (!client) {
188
- - RETURN res.status(503).json({ error: 'Authorization service unavailable' })
189
- - }
190
- - RETURN client.authorize(req, res, next)
191
- - DO: }
192
- - NOTE: Use in routes
193
- - DO: app.get('/api/protected', authenticate, handler)
194
- - DO: app.post('/api/admin', authenticate, authorize, adminHandler)
195
- OUTPUTS:
196
- - Produces the section's intended result using equivalent logic.
152
+ ```javascript
153
+ // Create reusable middleware
154
+ const authenticate = (req, res, next) => {
155
+ const client = req.app.locals.roditClient
156
+ if (!client) {
157
+ res.status(503).json({ error: 'Authentication service unavailable' })
158
+ }
159
+ client.authenticate(req, res, next)
160
+ }
161
+ const authorize = (req, res, next) => {
162
+ const client = req.app.locals.roditClient
163
+ if (!client) {
164
+ res.status(503).json({ error: 'Authorization service unavailable' })
165
+ }
166
+ client.authorize(req, res, next)
167
+ }
168
+ // Use in routes
169
+ app.get('/api/protected', authenticate, handler)
170
+ app.post('/api/admin', authenticate, authorize, adminHandler)
197
171
  ```
198
172
 
199
173
  ## Installation & Setup
@@ -201,117 +175,69 @@ OUTPUTS:
201
175
  ### Dependencies
202
176
 
203
177
  **Required:**
204
- ```text
205
- PSEUDOCODE
206
- INPUTS:
207
- - Use values defined by the surrounding section/context.
208
- STEPS:
209
- - RUN COMMAND: npm install @rodit/rodit-auth-be express config winston
210
- OUTPUTS:
211
- - Produces the section's intended result using equivalent logic.
178
+ ```bash
179
+ npm install @rodit/rodit-auth-be express config winston
212
180
  ```
213
181
 
214
182
  **Recommended for main:**
215
- ```text
216
- PSEUDOCODE
217
- INPUTS:
218
- - Use values defined by the surrounding section/context.
219
- STEPS:
220
- - RUN COMMAND: npm install express-session connect-sqlite3
221
- OUTPUTS:
222
- - Produces the section's intended result using equivalent logic.
183
+ ```bash
184
+ npm install express-session connect-sqlite3
223
185
  ```
224
186
 
225
187
  **Optional:**
226
- ```text
227
- PSEUDOCODE
228
- INPUTS:
229
- - Use values defined by the surrounding section/context.
230
- STEPS:
231
- - RUN COMMAND: npm install node-vault # For Vault-based credentials
232
- - RUN COMMAND: npm install winston-loki # For Grafana Loki logging
233
- OUTPUTS:
234
- - Produces the section's intended result using equivalent logic.
188
+ ```bash
189
+ npm install node-vault # For Vault-based credentials
190
+ npm install winston-loki # For Grafana Loki logging
235
191
  ```
236
192
 
237
193
  ### Environment Variables
238
194
 
239
195
  **Vault Configuration (main):**
240
- ```text
241
- PSEUDOCODE
242
- INPUTS:
243
- - Use values defined by the surrounding section/context.
244
- STEPS:
245
- - DO: export RODIT_NEAR_CREDENTIALS_SOURCE=vault
246
- - FIELD: export VAULT_ENDPOINT=https://vault.example.com
247
- - DO: export VAULT_ROLE_ID=your-role-id
248
- - DO: export VAULT_SECRET_ID=your-secret-id
249
- - DO: export VAULT_RODIT_KEYVALUE_PATH=secret/rodit
250
- - DO: export SERVICE_NAME=your-service-name
251
- - DO: export NEAR_CONTRACT_ID=discernible-io.near
252
- OUTPUTS:
253
- - Produces the section's intended result using equivalent logic.
196
+ ```bash
197
+ export RODIT_NEAR_CREDENTIALS_SOURCE=vault
198
+ export VAULT_ENDPOINT=https://vault.example.com
199
+ export VAULT_ROLE_ID=your-role-id
200
+ export VAULT_SECRET_ID=your-secret-id
201
+ export VAULT_RODIT_KEYVALUE_PATH=secret/rodit
202
+ export SERVICE_NAME=your-service-name
203
+ export NEAR_CONTRACT_ID=discernible-io.near
254
204
  ```
255
205
 
256
206
  **Application Configuration:**
257
- ```text
258
- PSEUDOCODE
259
- INPUTS:
260
- - Use values defined by the surrounding section/context.
261
- STEPS:
262
- - FIELD: export NODE_ENV=main # Environment: main, development, test
263
- - FIELD: export LOG_LEVEL=info # Logging: error, warn, info, debug, trace
264
- - DO: export API_DEFAULT_OPTIONS_DB_PATH=/app/data/database.sqlite
265
- OUTPUTS:
266
- - Produces the section's intended result using equivalent logic.
207
+ ```bash
208
+ export NODE_ENV=main # Environment: main, development, test
209
+ export LOG_LEVEL=info # Logging: error, warn, info, debug, trace
210
+ export API_DEFAULT_OPTIONS_DB_PATH=/app/data/database.sqlite
267
211
  ```
268
212
 
269
213
  **Session Configuration:**
270
- ```text
271
- PSEUDOCODE
272
- INPUTS:
273
- - Use values defined by the surrounding section/context.
274
- STEPS:
275
- - FIELD: export SESSION_STORAGE_TYPE=express-session # Storage: memory, express, express-session
276
- - DO: export SESSION_CLEANUP_INTERVAL=3600000 # Cleanup interval in milliseconds (1 hour)
277
- - DO: export SESSION_TOKEN_RETENTION_PERIOD=604800 # Token retention in seconds (7 days)
278
- - DO: export SESSION_VALIDATION_CACHE_TTL=5000 # Cache TTL in milliseconds (5 seconds)
279
- OUTPUTS:
280
- - Produces the section's intended result using equivalent logic.
214
+ ```bash
215
+ export SESSION_STORAGE_TYPE=express-session # Storage: memory, express, express-session
216
+ export SESSION_CLEANUP_INTERVAL=3600000 # Cleanup interval in milliseconds (1 hour)
217
+ export SESSION_TOKEN_RETENTION_PERIOD=604800 # Token retention in seconds (7 days)
218
+ export SESSION_VALIDATION_CACHE_TTL=5000 # Cache TTL in milliseconds (5 seconds)
281
219
  ```
282
220
 
283
221
  **Logging Configuration:**
284
- ```text
285
- PSEUDOCODE
286
- INPUTS:
287
- - Use values defined by the surrounding section/context.
288
- STEPS:
289
- - FIELD: export LOKI_URL=https://loki.example.com:3100
290
- - FIELD: export LOKI_BASIC_AUTH=username:password
291
- OUTPUTS:
292
- - Produces the section's intended result using equivalent logic.
222
+ ```bash
223
+ export LOKI_URL=https://loki.example.com:3100
224
+ export LOKI_BASIC_AUTH=username:password
293
225
  ```
294
226
 
295
227
  ### Configuration Files
296
228
 
297
229
  Create `config/default.json`:
298
230
 
299
- ```text
300
- PSEUDOCODE
301
- INPUTS:
302
- - Use values defined by the surrounding section/context.
303
- STEPS:
304
- - {
305
- - FIELD: "NEAR_CONTRACT_ID": "discernible-io.near",
306
- - FIELD: "SERVICE_NAME": "your-service",
307
- - FIELD: "SECURITY_OPTIONS": {
308
- - FIELD: "SILENT_LOGIN_FAILURES": false,
309
- - FIELD: "SESSION_TTL_SECONDS": 5200 // server session lifetime from login (default in SDK)
310
- - FIELD: "FALLBACK_JWT_DURATION": 3600 // access-token fallback when passport jwt_duration is invalid
311
- - }
312
- - }
313
- OUTPUTS:
314
- - Produces the section's intended result using equivalent logic.
231
+ ```json
232
+ {
233
+ "NEAR_CONTRACT_ID": "discernible-io.near",
234
+ "SERVICE_NAME": "your-service",
235
+ "SECURITY_OPTIONS": {
236
+ "SILENT_LOGIN_FAILURES": false,
237
+ "SESSION_TTL_SECONDS": 5200
238
+ "FALLBACK_JWT_DURATION": 3600
239
+ }
240
+ }
315
241
  ```
316
242
 
317
243
  ## Authentication
@@ -330,19 +256,12 @@ For API login documentation, use **`accountid`** with HTTP `POST /api/login`. Th
330
256
  | `base64url_signature` | Ed25519 detached signature (base64url) over `accountid + timestamp_iso` |
331
257
  | `accountid` | 64-hex implicit NEAR account login identifier |
332
258
 
333
- ```text
334
- PSEUDOCODE
335
- INPUTS:
336
- - Use values defined by the surrounding section/context.
337
- STEPS:
338
- - NOTE: Implicit account login
339
- - {
340
- - FIELD: "accountid": "<64-char-hex>",
341
- - FIELD: "timestamp": 1640995200,
342
- - FIELD: "base64url_signature": "base64url-encoded-signature"
343
- - }
344
- OUTPUTS:
345
- - Produces the section's intended result using equivalent logic.
259
+ ```json
260
+ {
261
+ "accountid": "<64-char-hex>",
262
+ "timestamp": 1640995200,
263
+ "base64url_signature": "base64url-encoded-signature"
264
+ }
346
265
  ```
347
266
 
348
267
  Use **`base64url_signature`** in login payloads for API login examples.
@@ -351,135 +270,107 @@ Rejected keys (HTTP 400, `LOGIN_PAYLOAD_DEPRECATED`): **`signature`** and **`acc
351
270
 
352
271
  #### Server Response
353
272
 
354
- ```text
355
- PSEUDOCODE
356
- INPUTS:
357
- - Use values defined by the surrounding section/context.
358
- STEPS:
359
- - NOTE: Success (200)
360
- - {
361
- - FIELD: "jwt_token": "<jwt-token>",
362
- - FIELD: "requestId": "01HQXYZ123ABC"
363
- - }
364
- - NOTE: Headers:
365
- - NOTE: New-Token: <jwt> (same token echoed for header-based clients)
366
- OUTPUTS:
367
- - Produces the section's intended result using equivalent logic.
273
+ ```json
274
+ {
275
+ "jwt_token": "<jwt-token>",
276
+ "requestId": "01HQXYZ123ABC"
277
+ }
368
278
  ```
369
279
 
370
280
  #### Authentication Flow
371
281
 
372
- 1. **Client sends RODiT credentials** - RODiT ID, timestamp, and cryptographic signature
282
+ 1. **Client sends RODiT credentials** - RODiT ID or account ID, timestamp, and cryptographic signature
373
283
  2. **SDK verifies signature** - Validates against blockchain records (NEAR Protocol)
374
284
  3. **Session created** - New session stored in session manager
375
- 4. **JWT token issued** - Token contains session ID and user claims
285
+ 4. **JWT token issued** - Token contains session ID, user claims, and always
286
+ `rodit_subjectuniqueidentifier_url` (`null` same-API; federated API URL when
287
+ peer home ≠ issuing server). See [Federated login](#federated-login-same-family-different-api-url).
376
288
  5. **Subsequent requests** - Client sends JWT in `Authorization: Bearer <token>` header
377
- 6. **Token validation** - SDK validates JWT and checks session status
289
+ 6. **Token validation** - SDK validates JWT (issuer rules differ for federated vs
290
+ same-API) and checks session status
378
291
 
379
292
  Security hardening in current implementation:
380
293
  - JWT compact parts must be canonical base64url (non-canonical encodings are rejected).
381
294
  - Session registration is enforced during JWT validation (unknown/inactive/expired sessions are rejected).
382
295
  - Server session length defaults to `SECURITY_OPTIONS.SESSION_TTL_SECONDS` (5200 s); see [Session lifetime and TTL](#session-lifetime-and-ttl).
383
296
  - Token renewal uses `sessionManager` for session checks and updates (no `stateManager` session mutations).
297
+ - Federated `login_server({ apiEndpoint })` rejects MITM when the signed
298
+ `rodit_subjectuniqueidentifier_url` does not match the intended endpoint.
384
299
 
385
300
  ### Login Implementation
386
301
 
387
- ```text
388
- PSEUDOCODE
389
- INPUTS:
390
- - Use values defined by the surrounding section/context.
391
- STEPS:
392
- - NOTE: routes/login.js
393
- - SET express TO require('express')
394
- - SET router TO express.Router()
395
- - DO: router.post('/login', async (req, res) => {
396
- - DO: req.logAction = 'login-attempt'
397
- - SET client TO req.app.locals.roditClient
398
- - CHECK CONDITION: if (!client) {
399
- - RETURN res.status(503).json({ error: 'Authentication service unavailable' })
400
- - }
401
- - NOTE: Delegate to SDK's login_client method
402
- - WAIT FOR: client.login_client(req, res)
403
- - DO: })
404
- - DO: module.exports = router
405
- OUTPUTS:
406
- - Produces the section's intended result using equivalent logic.
302
+ ```javascript
303
+ // routes/login.js
304
+ const express = require('express')
305
+ const router = express.Router()
306
+ router.post('/login', async (req, res) => {
307
+ req.logAction = 'login-attempt'
308
+ const client = req.app.locals.roditClient
309
+ if (!client) {
310
+ res.status(503).json({ error: 'Authentication service unavailable' })
311
+ }
312
+ // Delegate to SDK's login_client method
313
+ await client.login_client(req, res)
314
+ })
315
+ module.exports = router
407
316
  ```
408
317
 
409
318
  ### Logout Implementation
410
319
 
411
- ```text
412
- PSEUDOCODE
413
- INPUTS:
414
- - Use values defined by the surrounding section/context.
415
- STEPS:
416
- - NOTE: Logout invalidates the JWT token and closes the session
417
- - NOTE: Use logout-specific auth so signature-valid expired tokens can still logout.
418
- - DO: router.post('/logout', authenticateLogout, async (req, res) => {
419
- - DO: req.logAction = 'logout-attempt'
420
- - SET client TO req.app.locals.roditClient
421
- - CHECK CONDITION: if (!client) {
422
- - RETURN res.status(503).json({ error: 'Authentication service unavailable' })
423
- - }
424
- - NOTE: Delegate to SDK's logout_client method
425
- - WAIT FOR: client.logout_client(req, res)
426
- - DO: })
427
- OUTPUTS:
428
- - Produces the section's intended result using equivalent logic.
320
+ ```javascript
321
+ // Logout invalidates the JWT token and closes the session
322
+ // Use logout-specific auth so signature-valid expired tokens can still logout.
323
+ router.post('/logout', authenticateLogout, async (req, res) => {
324
+ req.logAction = 'logout-attempt'
325
+ const client = req.app.locals.roditClient
326
+ if (!client) {
327
+ res.status(503).json({ error: 'Authentication service unavailable' })
328
+ }
329
+ // Delegate to SDK's logout_client method
330
+ await client.logout_client(req, res)
331
+ })
429
332
  ```
430
333
 
431
334
  ### Protected Routes
432
335
 
433
- ```text
434
- PSEUDOCODE
435
- INPUTS:
436
- - Use values defined by the surrounding section/context.
437
- STEPS:
438
- - NOTE: Require authentication for access
439
- - DO: app.get('/api/data', authenticate, (req, res) => {
440
- - NOTE: req.user contains authenticated user information
441
- - SET logger TO req.app.locals.roditClient.getLogger()
442
- - DO: logger.info('Protected route accessed', {
443
- - FIELD: component: 'API',
444
- - FIELD: userId: req.user.id,
445
- - FIELD: roditId: req.user.roditId,
446
- - FIELD: requestId: req.requestId
447
- - DO: })
448
- - DO: res.json({
449
- - FIELD: message: 'Authenticated data',
450
- - FIELD: user: req.user,
451
- - FIELD: requestId: req.requestId
452
- - DO: })
453
- - DO: })
454
- OUTPUTS:
455
- - Produces the section's intended result using equivalent logic.
336
+ ```javascript
337
+ // Require authentication for access
338
+ app.get('/api/data', authenticate, (req, res) => {
339
+ // req.user contains authenticated user information
340
+ const logger = req.app.locals.roditClient.getLogger()
341
+ logger.info('Protected route accessed', {
342
+ component: 'API',
343
+ userId: req.user.id,
344
+ roditId: req.user.roditId,
345
+ requestId: req.requestId
346
+ })
347
+ res.json({
348
+ message: 'Authenticated data',
349
+ user: req.user,
350
+ requestId: req.requestId
351
+ })
352
+ })
456
353
  ```
457
354
 
458
355
  ### Authentication Middleware
459
356
 
460
357
  The `authenticate` middleware validates JWT tokens and populates `req.user`:
461
358
 
462
- ```text
463
- PSEUDOCODE
464
- INPUTS:
465
- - Use values defined by the surrounding section/context.
466
- STEPS:
467
- - SET authenticate TO (req, res, next) => {
468
- - SET client TO req.app.locals.roditClient
469
- - RETURN client.authenticate(req, res, next)
470
- - DO: }
471
- - NOTE: After successful authentication, req.user contains:
472
- - NOTE: {
473
- - NOTE: id: 'user-unique-id',
474
- - NOTE: roditId: '01K4G3D95QF6NR0RSJK9WEK6KA',
475
- - NOTE: aud: 'audience',
476
- - NOTE: iss: 'issuer',
477
- - NOTE: exp: 1640999999,
478
- - NOTE: iat: 1640995200,
479
- - NOTE: session_id: '01HQXYZ123ABC'
480
- - NOTE: }
481
- OUTPUTS:
482
- - Produces the section's intended result using equivalent logic.
359
+ ```javascript
360
+ const authenticate = (req, res, next) => {
361
+ const client = req.app.locals.roditClient
362
+ client.authenticate(req, res, next)
363
+ }
364
+ // After successful authentication, req.user contains:
365
+ // {
366
+ // id: 'user-unique-id',
367
+ // roditId: '01K4G3D95QF6NR0RSJK9WEK6KA',
368
+ // aud: 'audience',
369
+ // iss: 'issuer',
370
+ // exp: 1640999999,
371
+ // iat: 1640995200,
372
+ // session_id: '01HQXYZ123ABC'
373
+ // }
483
374
  ```
484
375
 
485
376
  ### Login Mode Control
@@ -509,50 +400,26 @@ The SDK provides configurable access control for RODiT authentication, allowing
509
400
  #### Usage Examples
510
401
 
511
402
  **Default (Partner Only):**
512
- ```text
513
- PSEUDOCODE
514
- INPUTS:
515
- - Use values defined by the surrounding section/context.
516
- STEPS:
517
- - NOTE: No configuration needed - this is the default
518
- - NOTE: Only client-server authentication is accepted
519
- OUTPUTS:
520
- - Produces the section's intended result using equivalent logic.
403
+ ```bash
404
+ // No configuration needed - this is the default
405
+ // Only client-server authentication is accepted
521
406
  ```
522
407
 
523
408
  **Accept All Logins:**
524
- ```text
525
- PSEUDOCODE
526
- INPUTS:
527
- - Use values defined by the surrounding section/context.
528
- STEPS:
529
- - DO: export SECURITY_OPTIONS_LOGIN_MODE=promiscuous
530
- - NOTE: Both Partner and Peer logins are accepted
531
- OUTPUTS:
532
- - Produces the section's intended result using equivalent logic.
409
+ ```bash
410
+ export SECURITY_OPTIONS_LOGIN_MODE=promiscuous
411
+ // Both Partner and Peer logins are accepted
533
412
  ```
534
413
 
535
414
  **Peer-to-Peer Only:**
536
- ```text
537
- PSEUDOCODE
538
- INPUTS:
539
- - Use values defined by the surrounding section/context.
540
- STEPS:
541
- - DO: export SECURITY_OPTIONS_LOGIN_MODE=p2p
542
- - NOTE: Only peer-to-peer authentication is accepted
543
- OUTPUTS:
544
- - Produces the section's intended result using equivalent logic.
415
+ ```bash
416
+ export SECURITY_OPTIONS_LOGIN_MODE=p2p
417
+ // Only peer-to-peer authentication is accepted
545
418
  ```
546
419
 
547
420
  **Docker/Podman:**
548
- ```text
549
- PSEUDOCODE
550
- INPUTS:
551
- - Use values defined by the surrounding section/context.
552
- STEPS:
553
- - DO: podman run -e SECURITY_OPTIONS_LOGIN_MODE=partner ...
554
- OUTPUTS:
555
- - Produces the section's intended result using equivalent logic.
421
+ ```bash
422
+ podman run -e SECURITY_OPTIONS_LOGIN_MODE=partner ...
556
423
  ```
557
424
 
558
425
  **GitHub Actions:**
@@ -560,40 +427,121 @@ Add repository variable:
560
427
  - **Name**: `SECURITY_OPTIONS_LOGIN_MODE`
561
428
  - **Value**: `partner` | `promiscuous` | `p2p`
562
429
 
430
+ #### Federated login (same family, different API URL)
431
+
432
+ Enable a client RODiT issued for API A (`subjectuniqueidentifier_url`) to log
433
+ into API B in the **same SR/CR family**. Federation is **login → remint a local
434
+ JWT** on B; foreign JWTs are not accepted without re-login. Mutual auth remains
435
+ optional (no reverse `login_server` required).
436
+
437
+ ##### Client call
438
+
439
+ ```js
440
+ const { RoditClient } = require('@rodit/rodit-auth-be');
441
+
442
+ const client = await RoditClient.create('client');
443
+ await client.login_server({
444
+ apiEndpoint: 'https://api-b.example.com', // federated API; omit for home API
445
+ });
446
+ ```
447
+
448
+ When `apiEndpoint` is omitted, login targets the client's own
449
+ `subjectuniqueidentifier_url` (same-API path).
450
+
451
+ ##### JWT claim contract
452
+
453
+ Unset fields are **always present as `null`** (same rule as `config_*` in 9.12).
454
+ Do not omit `rodit_subjectuniqueidentifier_url` on same-API tokens.
455
+
456
+ | Claim | Always present? | Same-API login | Federated login |
457
+ |-------|-----------------|----------------|-----------------|
458
+ | `iss` | yes | peer home URL (= server URL in practice) | **client home** `subjectuniqueidentifier_url` |
459
+ | `aud` | yes | server `owner_id` | server `owner_id` (federated API) |
460
+ | `rodit_subjectuniqueidentifier_url` | **yes** | **`null`** | **federated API** `subjectuniqueidentifier_url` |
461
+ | `config_iso639` / `config_iso3166` / `config_iso15924` / `config_timeoptions` | yes | `null` | `null` |
462
+
463
+ A JWT is treated as federated when:
464
+
465
+ ```js
466
+ rodit_subjectuniqueidentifier_url != null
467
+ && String(rodit_subjectuniqueidentifier_url).trim() !== ""
468
+ ```
469
+
470
+ (Helper: `isNonEmptyUrlClaim`.)
471
+
472
+ ##### Server validation
473
+
474
+ After signature verification:
475
+
476
+ - **Federated JWT** — `rodit_subjectuniqueidentifier_url` must equal this
477
+ server's URL; `iss` must equal the login client's home URL (resolved from
478
+ `sub` via on-chain RODiT).
479
+ - **Same-API / 9.12 tokens** — claim null/absent → `iss` must equal this
480
+ server's URL (unchanged 9.12 issuer rule).
481
+ - **`aud`**, family match, and `LOGIN_MODE` are unchanged.
482
+
483
+ ##### Client MITM check
484
+
485
+ After crypto validation of the received JWT, `login_server` checks that a
486
+ federated attempt (`apiEndpoint` ≠ client home) has:
487
+
488
+ 1. Non-empty `rodit_subjectuniqueidentifier_url`
489
+ 2. That claim equal to the intended `apiEndpoint`
490
+ 3. `iss` equal to the client home URL
491
+
492
+ | Failure | `errorCode` |
493
+ |---------|-------------|
494
+ | Null/empty federated claim | `FEDERATED_ISSUER_MISSING` |
495
+ | Claim or `iss` mismatch | `FEDERATED_ISSUER_MISMATCH` |
496
+
497
+ ##### Renewal
498
+
499
+ - Federated JWT renewals preserve the non-null claim string.
500
+ - Tokens minted by 9.12 (no claim) renew into `rodit_subjectuniqueidentifier_url: null`.
501
+
502
+ ##### Operational prerequisites (outside SDK)
503
+
504
+ - Federated server RODiT shares the same `bc=;sc=;id=SR;id=CR` family.
505
+ - Federated domain has DNS trust TXT (keyed off **own**
506
+ `subjectuniqueidentifier_url`).
507
+ - Client calls `login_server({ apiEndpoint: '<federated URL>' })`.
508
+ - Client→server federation needs `LOGIN_MODE=partner` (or `promiscuous`); a
509
+ server RODiT logging into a federated API under `partner` is still rejected
510
+ by existing role-separation policy.
511
+
512
+ ##### Helpers (package root)
513
+
514
+ ```js
515
+ const {
516
+ normalizeUrlWithoutPort,
517
+ isNonEmptyUrlClaim,
518
+ isFederatedRoditLogin,
519
+ validateFederatedLoginTarget,
520
+ } = require('@rodit/rodit-auth-be');
521
+ ```
522
+
563
523
  #### Logging and Monitoring
564
524
 
565
525
  **Successful Login:**
566
- ```text
567
- PSEUDOCODE
568
- INPUTS:
569
- - Use values defined by the surrounding section/context.
570
- STEPS:
571
- - {
572
- - FIELD: "level": "info",
573
- - FIELD: "message": "PARTNER login verified successfully",
574
- - FIELD: "verificationType": "PARTNER",
575
- - FIELD: "loginMode": "partner",
576
- - FIELD: "duration": 1234
577
- - }
578
- OUTPUTS:
579
- - Produces the section's intended result using equivalent logic.
526
+ ```json
527
+ {
528
+ "level": "info",
529
+ "message": "PARTNER login verified successfully",
530
+ "verificationType": "PARTNER",
531
+ "loginMode": "partner",
532
+ "duration": 1234
533
+ }
580
534
  ```
581
535
 
582
536
  **Rejected Login:**
583
- ```text
584
- PSEUDOCODE
585
- INPUTS:
586
- - Use values defined by the surrounding section/context.
587
- STEPS:
588
- - {
589
- - FIELD: "level": "warn",
590
- - FIELD: "message": "PEER login rejected by LOGIN_MODE policy",
591
- - FIELD: "verificationType": "PEER",
592
- - FIELD: "loginMode": "partner",
593
- - FIELD: "policyReason": "LOGIN_MODE=partner does not accept PEER logins"
594
- - }
595
- OUTPUTS:
596
- - Produces the section's intended result using equivalent logic.
537
+ ```json
538
+ {
539
+ "level": "warn",
540
+ "message": "PEER login rejected by LOGIN_MODE policy",
541
+ "verificationType": "PEER",
542
+ "loginMode": "partner",
543
+ "policyReason": "LOGIN_MODE=partner does not accept PEER logins"
544
+ }
597
545
  ```
598
546
 
599
547
  **Metrics:**
@@ -606,6 +554,8 @@ OUTPUTS:
606
554
  2. **Promiscuous Mode**: Use only when you need to accept both types of authentication
607
555
  3. **P2P Mode**: Use when building peer-to-peer systems where only same-provider authentication is needed
608
556
  4. **Policy Enforcement**: Rejections are logged with clear reasons for audit trails
557
+ 5. **Federated MITM**: Always pass the real peer URL as `apiEndpoint`; the client
558
+ verifies the signed federated issuer claim against that URL after login
609
559
 
610
560
  #### Troubleshooting
611
561
 
@@ -613,6 +563,11 @@ OUTPUTS:
613
563
  - If you see "PEER login rejected" and need to accept peer logins, set mode to `promiscuous` or `p2p`
614
564
  - If you see "PARTNER login rejected" and need to accept partner logins, set mode to `promiscuous` or `partner`
615
565
 
566
+ **Federated login failures:**
567
+ - `FEDERATED_ISSUER_MISSING` — JWT has null/empty `rodit_subjectuniqueidentifier_url` but `apiEndpoint` differs from client home (peer may be pre-9.13, or claim omitted incorrectly)
568
+ - `FEDERATED_ISSUER_MISMATCH` — claim or `iss` does not match intended `apiEndpoint` / client home (wrong URL or MITM)
569
+ - `Error 005: Invalid federated issuer` on the server — JWT federated claim is not this server's `subjectuniqueidentifier_url`
570
+
616
571
  **Check Current Mode:**
617
572
  Look for the log message during authentication:
618
573
  ```
@@ -625,34 +580,28 @@ Look for the log message during authentication:
625
580
 
626
581
  Permissions are configured in your RODiT token metadata using the `permissioned_routes` field:
627
582
 
628
- ```text
629
- PSEUDOCODE
630
- INPUTS:
631
- - Use values defined by the surrounding section/context.
632
- STEPS:
633
- - {
634
- - FIELD: "permissioned_routes": {
635
- - FIELD: "entities": {
636
- - FIELD: "/": {
637
- - FIELD: "methods": "+0"
638
- - DO: },
639
- - FIELD: "/api/echo": {
640
- - FIELD: "methods": "+0"
641
- - DO: },
642
- - FIELD: "/api/cruda/create": {
643
- - FIELD: "methods": "+0"
644
- - DO: },
645
- - FIELD: "/api/cruda/list": {
646
- - FIELD: "methods": "+0"
647
- - DO: },
648
- - FIELD: "/api/admin": {
649
- - FIELD: "methods": "+0"
650
- - }
651
- - }
652
- - }
653
- - }
654
- OUTPUTS:
655
- - Produces the section's intended result using equivalent logic.
583
+ ```json
584
+ {
585
+ "permissioned_routes": {
586
+ "entities": {
587
+ "/": {
588
+ "methods": "+0"
589
+ },
590
+ "/api/echo": {
591
+ "methods": "+0"
592
+ },
593
+ "/api/cruda/create": {
594
+ "methods": "+0"
595
+ },
596
+ "/api/cruda/list": {
597
+ "methods": "+0"
598
+ },
599
+ "/api/admin": {
600
+ "methods": "+0"
601
+ }
602
+ }
603
+ }
604
+ }
656
605
  ```
657
606
 
658
607
  **Permission Format:**
@@ -665,96 +614,72 @@ OUTPUTS:
665
614
 
666
615
  The `authorize` middleware validates that the authenticated user has permission to access the requested route:
667
616
 
668
- ```text
669
- PSEUDOCODE
670
- INPUTS:
671
- - Use values defined by the surrounding section/context.
672
- STEPS:
673
- - SET authenticate TO (req, res, next) => {
674
- - RETURN req.app.locals.roditClient.authenticate(req, res, next)
675
- - DO: }
676
- - SET authorize TO (req, res, next) => {
677
- - RETURN req.app.locals.roditClient.authorize(req, res, next)
678
- - DO: }
679
- - NOTE: Apply both authentication and authorization
680
- - DO: app.use('/api/admin', authenticate, authorize, adminRoutes)
681
- - NOTE: CRUDA endpoints with full protection
682
- - DO: app.use('/api/cruda', authenticate, authorize, crudaRoutes)
683
- OUTPUTS:
684
- - Produces the section's intended result using equivalent logic.
617
+ ```javascript
618
+ const authenticate = (req, res, next) => {
619
+ req.app.locals.roditClient.authenticate(req, res, next)
620
+ }
621
+ const authorize = (req, res, next) => {
622
+ req.app.locals.roditClient.authorize(req, res, next)
623
+ }
624
+ // Apply both authentication and authorization
625
+ app.use('/api/admin', authenticate, authorize, adminRoutes)
626
+ // CRUDA endpoints with full protection
627
+ app.use('/api/cruda', authenticate, authorize, crudaRoutes)
685
628
  ```
686
629
 
687
630
  ### Permission Enforcement
688
631
 
689
- ```text
690
- PSEUDOCODE
691
- INPUTS:
692
- - Use values defined by the surrounding section/context.
693
- STEPS:
694
- - NOTE: Example: CRUDA routes with permission checking
695
- - SET router TO express.Router()
696
- - NOTE: All routes require authentication + authorization
697
- - DO: router.post('/create', async (req, res) => {
698
- - NOTE: User must have permission for POST /api/cruda/create
699
- - DO: const { comment, author } = req.body
700
- - NOTE: Create record in database
701
- - SET result TO await db.run(
702
- - DO: 'INSERT INTO comments (comment, author) VALUES (?, ?)',
703
- - DO: [comment, author || req.user.roditId]
704
- - DO: )
705
- - FIELD: res.json({ id: result.lastID, requestId: req.requestId })
706
- - DO: })
707
- - DO: router.post('/list', async (req, res) => {
708
- - NOTE: User must have permission for POST /api/cruda/list
709
- - SET records TO await db.all('SELECT * FROM comments ORDER BY created_at DESC')
710
- - FIELD: res.json({ records, requestId: req.requestId })
711
- - DO: })
712
- - DO: module.exports = router
713
- OUTPUTS:
714
- - Produces the section's intended result using equivalent logic.
632
+ ```javascript
633
+ // Example: CRUDA routes with permission checking
634
+ const router = express.Router()
635
+ // All routes require authentication + authorization
636
+ router.post('/create', async (req, res) => {
637
+ // User must have permission for POST /api/cruda/create
638
+ const { comment, author } = req.body
639
+ // Create record in database
640
+ const result = await db.run(
641
+ 'INSERT INTO comments (comment, author) VALUES (?, ?)',
642
+ [comment, author || req.user.roditId]
643
+ )
644
+ res.json({ id: result.lastID, requestId: req.requestId })
645
+ })
646
+ router.post('/list', async (req, res) => {
647
+ // User must have permission for POST /api/cruda/list
648
+ const records = await db.all('SELECT * FROM comments ORDER BY created_at DESC')
649
+ res.json({ records, requestId: req.requestId })
650
+ })
651
+ module.exports = router
715
652
  ```
716
653
 
717
654
  ### Dynamic Permission Checking
718
655
 
719
- ```text
720
- PSEUDOCODE
721
- INPUTS:
722
- - Use values defined by the surrounding section/context.
723
- STEPS:
724
- - NOTE: Check permissions programmatically
725
- - SET client TO req.app.locals.roditClient
726
- - SET hasPermission TO client.isOperationPermitted('POST', '/api/admin/users')
727
- - CHECK CONDITION: if (!hasPermission) {
728
- - RETURN res.status(403).json({
729
- - FIELD: error: 'Forbidden',
730
- - FIELD: message: 'You do not have permission to access this resource',
731
- - FIELD: requestId: req.requestId
732
- - DO: })
733
- - }
734
- - NOTE: Proceed with operation
735
- OUTPUTS:
736
- - Produces the section's intended result using equivalent logic.
656
+ ```javascript
657
+ // Check permissions programmatically
658
+ const client = req.app.locals.roditClient
659
+ const hasPermission = client.isOperationPermitted('POST', '/api/admin/users')
660
+ if (!hasPermission) {
661
+ res.status(403).json({
662
+ error: 'Forbidden',
663
+ message: 'You do not have permission to access this resource',
664
+ requestId: req.requestId
665
+ })
666
+ }
667
+ // Proceed with operation
737
668
  ```
738
669
 
739
670
  ### Permission Validation in Client Token Minting
740
671
 
741
672
  When minting client tokens via `/api/signclient`, the server validates that requested permissions are a subset of the server's own permissions:
742
673
 
743
- ```text
744
- PSEUDOCODE
745
- INPUTS:
746
- - Use values defined by the surrounding section/context.
747
- STEPS:
748
- - NOTE: Client requests these permissions:
749
- - SET requestedPermissions TO {
750
- - FIELD: "/": "+0",
751
- - FIELD: "/api/echo": "+0",
752
- - FIELD: "/api/cruda/create": "+0"
753
- - DO: }
754
- - NOTE: Server validates against its own permissioned_routes
755
- - NOTE: If any requested route is not in server's config, request is rejected with HTTP 400
756
- OUTPUTS:
757
- - Produces the section's intended result using equivalent logic.
674
+ ```javascript
675
+ // Client requests these permissions:
676
+ const requestedPermissions = {
677
+ "/": "+0",
678
+ "/api/echo": "+0",
679
+ "/api/cruda/create": "+0"
680
+ }
681
+ // Server validates against its own permissioned_routes
682
+ // If any requested route is not in server's config, request is rejected with HTTP 400
758
683
  ```
759
684
 
760
685
  ## Session Management
@@ -856,15 +781,9 @@ Portal/outbound login token validation can skip session registration when `SECUR
856
781
 
857
782
  No configuration needed - works out of the box:
858
783
 
859
- ```text
860
- PSEUDOCODE
861
- INPUTS:
862
- - Use values defined by the surrounding section/context.
863
- STEPS:
864
- - SET client TO await RoditClient.create('server')
865
- - NOTE: Uses InMemorySessionStorage by default
866
- OUTPUTS:
867
- - Produces the section's intended result using equivalent logic.
784
+ ```javascript
785
+ const client = await RoditClient.create('server')
786
+ // Uses InMemorySessionStorage by default
868
787
  ```
869
788
 
870
789
  **Pros:** Fast, zero configuration
@@ -874,27 +793,21 @@ OUTPUTS:
874
793
 
875
794
  Persistent storage using SQLite database:
876
795
 
877
- ```text
878
- PSEUDOCODE
879
- INPUTS:
880
- - Use values defined by the surrounding section/context.
881
- STEPS:
882
- - SET express TO require('express')
883
- - SET session TO require('express-session')
884
- - SET SQLiteStore TO require('connect-sqlite3')(session)
885
- - DO: const { RoditClient } = require('@rodit/rodit-auth-be')
886
- - DO: const { setExpressSessionStore } = require('@rodit/rodit-auth-be/lib/auth/sessionmanager')
887
- - NOTE: Configure BEFORE initializing RoditClient
888
- - SET sessionStore TO new SQLiteStore({
889
- - FIELD: db: 'sessions.db',
890
- - FIELD: dir: './data',
891
- - FIELD: table: 'sessions'
892
- - DO: })
893
- - DO: setExpressSessionStore(sessionStore)
894
- - NOTE: Now initialize client
895
- - SET client TO await RoditClient.create('server')
896
- OUTPUTS:
897
- - Produces the section's intended result using equivalent logic.
796
+ ```javascript
797
+ const express = require('express')
798
+ const session = require('express-session')
799
+ const SQLiteStore = require('connect-sqlite3')(session)
800
+ const { RoditClient } = require('@rodit/rodit-auth-be')
801
+ const { setExpressSessionStore } = require('@rodit/rodit-auth-be/lib/auth/sessionmanager')
802
+ // Configure BEFORE initializing RoditClient
803
+ const sessionStore = new SQLiteStore({
804
+ db: 'sessions.db',
805
+ dir: './data',
806
+ table: 'sessions'
807
+ })
808
+ setExpressSessionStore(sessionStore)
809
+ // Now initialize client
810
+ const client = await RoditClient.create('server')
898
811
  ```
899
812
 
900
813
  **Pros:** Persistent across restarts, simple setup, uses existing database infrastructure
@@ -902,40 +815,28 @@ OUTPUTS:
902
815
 
903
816
  #### 3. Redis Storage (For Multi-Server)
904
817
 
905
- ```text
906
- PSEUDOCODE
907
- INPUTS:
908
- - Use values defined by the surrounding section/context.
909
- STEPS:
910
- - RUN COMMAND: npm install express-session connect-redis redis
911
- OUTPUTS:
912
- - Produces the section's intended result using equivalent logic.
818
+ ```bash
819
+ npm install express-session connect-redis redis
913
820
  ```
914
821
 
915
- ```text
916
- PSEUDOCODE
917
- INPUTS:
918
- - Use values defined by the surrounding section/context.
919
- STEPS:
920
- - SET session TO require('express-session')
921
- - SET RedisStore TO require('connect-redis').default
922
- - DO: const { createClient } = require('redis')
923
- - DO: const { setExpressSessionStore } = require('@rodit/rodit-auth-be/lib/auth/sessionmanager')
924
- - NOTE: Create Redis client
925
- - SET redisClient TO createClient({
926
- - FIELD: url: process.env.REDIS_URL || 'redis://127.0.0.1:6379'
927
- - DO: })
928
- - WAIT FOR: redisClient.connect()
929
- - NOTE: Create Redis store
930
- - SET redisStore TO new RedisStore({
931
- - FIELD: client: redisClient,
932
- - FIELD: prefix: 'rodit:sess:',
933
- - FIELD: ttl: 86400 // 24 hours
934
- - DO: })
935
- - DO: setExpressSessionStore(redisStore)
936
- - SET client TO await RoditClient.create('server')
937
- OUTPUTS:
938
- - Produces the section's intended result using equivalent logic.
822
+ ```javascript
823
+ const session = require('express-session')
824
+ const RedisStore = require('connect-redis').default
825
+ const { createClient } = require('redis')
826
+ const { setExpressSessionStore } = require('@rodit/rodit-auth-be/lib/auth/sessionmanager')
827
+ // Create Redis client
828
+ const redisClient = createClient({
829
+ url: process.env.REDIS_URL || 'redis://127.0.0.1:6379'
830
+ })
831
+ await redisClient.connect()
832
+ // Create Redis store
833
+ const redisStore = new RedisStore({
834
+ client: redisClient,
835
+ prefix: 'rodit:sess:',
836
+ ttl: 86400 // 24 hours
837
+ })
838
+ setExpressSessionStore(redisStore)
839
+ const client = await RoditClient.create('server')
939
840
  ```
940
841
 
941
842
  **Pros:** Shared sessions across multiple servers, high performance
@@ -954,14 +855,8 @@ The SDK supports configurable session storage via the `SESSION_STORAGE_TYPE` env
954
855
  - Sessions lost on server restart
955
856
  - Suitable for development or single-instance deployments
956
857
 
957
- ```text
958
- PSEUDOCODE
959
- INPUTS:
960
- - Use values defined by the surrounding section/context.
961
- STEPS:
962
- - DO: export SESSION_STORAGE_TYPE=memory
963
- OUTPUTS:
964
- - Produces the section's intended result using equivalent logic.
858
+ ```bash
859
+ export SESSION_STORAGE_TYPE=memory
965
860
  ```
966
861
 
967
862
  **2. `"express"` or `"express-session"`**
@@ -971,84 +866,60 @@ OUTPUTS:
971
866
  - Can be overridden with `setExpressSessionStore()` for Redis, SQLite, etc.
972
867
  - Suitable for main with persistent storage
973
868
 
974
- ```text
975
- PSEUDOCODE
976
- INPUTS:
977
- - Use values defined by the surrounding section/context.
978
- STEPS:
979
- - DO: export SESSION_STORAGE_TYPE=express-session
980
- OUTPUTS:
981
- - Produces the section's intended result using equivalent logic.
869
+ ```bash
870
+ export SESSION_STORAGE_TYPE=express-session
982
871
  ```
983
872
 
984
873
  #### Configuring Persistent Storage
985
874
 
986
875
  **SQLite Example:**
987
- ```text
988
- PSEUDOCODE
989
- INPUTS:
990
- - Use values defined by the surrounding section/context.
991
- STEPS:
992
- - SET session TO require('express-session')
993
- - SET SQLiteStore TO require('connect-sqlite3')(session)
994
- - DO: const { setExpressSessionStore } = require('@rodit/rodit-auth-be/lib/auth/sessionmanager')
995
- - NOTE: Configure BEFORE initializing RoditClient
996
- - SET sessionStore TO new SQLiteStore({
997
- - FIELD: db: 'sessions.db',
998
- - FIELD: dir: './data',
999
- - FIELD: table: 'sessions'
1000
- - DO: })
1001
- - DO: setExpressSessionStore(sessionStore)
1002
- - NOTE: Now initialize client
1003
- - SET client TO await RoditClient.create('server')
1004
- OUTPUTS:
1005
- - Produces the section's intended result using equivalent logic.
876
+ ```javascript
877
+ const session = require('express-session')
878
+ const SQLiteStore = require('connect-sqlite3')(session)
879
+ const { setExpressSessionStore } = require('@rodit/rodit-auth-be/lib/auth/sessionmanager')
880
+ // Configure BEFORE initializing RoditClient
881
+ const sessionStore = new SQLiteStore({
882
+ db: 'sessions.db',
883
+ dir: './data',
884
+ table: 'sessions'
885
+ })
886
+ setExpressSessionStore(sessionStore)
887
+ // Now initialize client
888
+ const client = await RoditClient.create('server')
1006
889
  ```
1007
890
 
1008
891
  **Redis Example:**
1009
- ```text
1010
- PSEUDOCODE
1011
- INPUTS:
1012
- - Use values defined by the surrounding section/context.
1013
- STEPS:
1014
- - SET session TO require('express-session')
1015
- - SET RedisStore TO require('connect-redis').default
1016
- - DO: const { createClient } = require('redis')
1017
- - DO: const { setExpressSessionStore } = require('@rodit/rodit-auth-be/lib/auth/sessionmanager')
1018
- - SET redisClient TO createClient({
1019
- - FIELD: url: process.env.REDIS_URL || 'redis://127.0.0.1:6379'
1020
- - DO: })
1021
- - WAIT FOR: redisClient.connect()
1022
- - SET redisStore TO new RedisStore({
1023
- - FIELD: client: redisClient,
1024
- - FIELD: prefix: 'rodit:sess:',
1025
- - FIELD: ttl: 86400
1026
- - DO: })
1027
- - DO: setExpressSessionStore(redisStore)
1028
- OUTPUTS:
1029
- - Produces the section's intended result using equivalent logic.
892
+ ```javascript
893
+ const session = require('express-session')
894
+ const RedisStore = require('connect-redis').default
895
+ const { createClient } = require('redis')
896
+ const { setExpressSessionStore } = require('@rodit/rodit-auth-be/lib/auth/sessionmanager')
897
+ const redisClient = createClient({
898
+ url: process.env.REDIS_URL || 'redis://127.0.0.1:6379'
899
+ })
900
+ await redisClient.connect()
901
+ const redisStore = new RedisStore({
902
+ client: redisClient,
903
+ prefix: 'rodit:sess:',
904
+ ttl: 86400
905
+ })
906
+ setExpressSessionStore(redisStore)
1030
907
  ```
1031
908
 
1032
909
  #### Session Configuration Variables
1033
910
 
1034
- ```text
1035
- PSEUDOCODE
1036
- INPUTS:
1037
- - Use values defined by the surrounding section/context.
1038
- STEPS:
1039
- - NOTE: Storage backend type
1040
- - DO: export SESSION_STORAGE_TYPE=express-session
1041
- - NOTE: Cleanup interval (milliseconds) - how often to remove expired sessions
1042
- - DO: export SESSION_CLEANUP_INTERVAL=3600000 # 1 hour
1043
- - NOTE: Token retention period (seconds) - how long to keep closed sessions
1044
- - DO: export SESSION_TOKEN_RETENTION_PERIOD=604800 # 7 days
1045
- - NOTE: Validation cache TTL (milliseconds) - trades security for performance
1046
- - NOTE: Lower = more secure but more storage lookups
1047
- - NOTE: Higher = faster but longer window after logout where token may still work
1048
- - NOTE: Set to 0 to disable caching (always check session state)
1049
- - DO: export SESSION_VALIDATION_CACHE_TTL=5000 # 5 seconds
1050
- OUTPUTS:
1051
- - Produces the section's intended result using equivalent logic.
911
+ ```bash
912
+ // Storage backend type
913
+ export SESSION_STORAGE_TYPE=express-session
914
+ // Cleanup interval (milliseconds) - how often to remove expired sessions
915
+ export SESSION_CLEANUP_INTERVAL=3600000 # 1 hour
916
+ // Token retention period (seconds) - how long to keep closed sessions
917
+ export SESSION_TOKEN_RETENTION_PERIOD=604800 # 7 days
918
+ // Validation cache TTL (milliseconds) - trades security for performance
919
+ // Lower = more secure but more storage lookups
920
+ // Higher = faster but longer window after logout where token may still work
921
+ // Set to 0 to disable caching (always check session state)
922
+ export SESSION_VALIDATION_CACHE_TTL=5000 # 5 seconds
1052
923
  ```
1053
924
 
1054
925
  **Session Validation Cache:**
@@ -1061,63 +932,51 @@ The SDK caches token validation results to reduce storage lookups:
1061
932
  - **Recommendation**: Keep default (5s) for most use cases
1062
933
  - **High security**: Set to `0` to disable caching
1063
934
 
1064
- ```text
1065
- PSEUDOCODE
1066
- INPUTS:
1067
- - Use values defined by the surrounding section/context.
1068
- STEPS:
1069
- - NOTE: Get cache statistics
1070
- - SET sessionManager TO roditClient.getSessionManager()
1071
- - SET cacheStats TO sessionManager.getValidationCacheStats()
1072
- - FIELD: console.log('Cache stats:', cacheStats)
1073
- - NOTE: Output: { totalEntries: 10, validEntries: 8, expiredEntries: 2, cacheTTL: 5000, cacheEnabled: true }
1074
- OUTPUTS:
1075
- - Produces the section's intended result using equivalent logic.
935
+ ```javascript
936
+ // Get cache statistics
937
+ const sessionManager = roditClient.getSessionManager()
938
+ const cacheStats = sessionManager.getValidationCacheStats()
939
+ console.log('Cache stats:', cacheStats)
940
+ // Output: { totalEntries: 10, validEntries: 8, expiredEntries: 2, cacheTTL: 5000, cacheEnabled: true }
1076
941
  ```
1077
942
 
1078
943
  ### Session Operations
1079
944
 
1080
- ```text
1081
- PSEUDOCODE
1082
- INPUTS:
1083
- - Use values defined by the surrounding section/context.
1084
- STEPS:
1085
- - NOTE: Get session manager
1086
- - SET sessionManager TO roditClient.getSessionManager()
1087
- - NOTE: Get active session count
1088
- - SET activeCount TO await sessionManager.getActiveSessionCount()
1089
- - NOTE: Get storage information
1090
- - SET storageInfo TO await sessionManager.getStorageInfo()
1091
- - FIELD: console.log('Storage type:', storageInfo.type)
1092
- - FIELD: console.log('Session count:', storageInfo.sessionCount)
1093
- - NOTE: Enumerate sessions via storage
1094
- - SET allSessions TO await sessionManager.storage.getAll()
1095
- - NOTE: Or fallback using keys() + get()
1096
- - SET sessionIds TO await sessionManager.storage.keys()
1097
- - SET sessions TO []
1098
- - REPEAT: for (const id of sessionIds) {
1099
- - SET session TO await sessionManager.storage.get(id)
1100
- - CHECK CONDITION: if (session) sessions.push(session)
1101
- - }
1102
- - NOTE: Check if token is invalidated
1103
- - SET isInvalidated TO await sessionManager.isTokenInvalidated(jwtToken)
1104
- - NOTE: Get detailed invalidation info
1105
- - SET invalidationInfo TO await sessionManager.getTokenInvalidationInfo(jwtToken)
1106
- - CHECK CONDITION: if (invalidationInfo) {
1107
- - FIELD: console.log('Invalidation reason:', invalidationInfo.reason)
1108
- - FIELD: console.log('Invalidated at:', invalidationInfo.invalidatedAt)
1109
- - }
1110
- - NOTE: Manually close a session
1111
- - WAIT FOR: sessionManager.closeSession(sessionId, 'admin_action')
1112
- - NOTE: Run manual cleanup (removes expired sessions)
1113
- - SET cleanup TO await sessionManager.runManualCleanup()
1114
- - DO: console.log(`Removed ${cleanup.removedSessionsCount} expired sessions`)
1115
- - NOTE: Get validation cache statistics
1116
- - SET cacheStats TO sessionManager.getValidationCacheStats()
1117
- - FIELD: console.log('Cache entries:', cacheStats.totalEntries)
1118
- - FIELD: console.log('Cache TTL:', cacheStats.cacheTTL)
1119
- OUTPUTS:
1120
- - Produces the section's intended result using equivalent logic.
945
+ ```javascript
946
+ // Get session manager
947
+ const sessionManager = roditClient.getSessionManager()
948
+ // Get active session count
949
+ const activeCount = await sessionManager.getActiveSessionCount()
950
+ // Get storage information
951
+ const storageInfo = await sessionManager.getStorageInfo()
952
+ console.log('Storage type:', storageInfo.type)
953
+ console.log('Session count:', storageInfo.sessionCount)
954
+ // Enumerate sessions via storage
955
+ const allSessions = await sessionManager.storage.getAll()
956
+ // Or fallback using keys() + get()
957
+ const sessionIds = await sessionManager.storage.keys()
958
+ const sessions = []
959
+ REPEAT: for (const id of sessionIds) {
960
+ const session = await sessionManager.storage.get(id)
961
+ if (session) sessions.push(session)
962
+ }
963
+ // Check if token is invalidated
964
+ const isInvalidated = await sessionManager.isTokenInvalidated(jwtToken)
965
+ // Get detailed invalidation info
966
+ const invalidationInfo = await sessionManager.getTokenInvalidationInfo(jwtToken)
967
+ if (invalidationInfo) {
968
+ console.log('Invalidation reason:', invalidationInfo.reason)
969
+ console.log('Invalidated at:', invalidationInfo.invalidatedAt)
970
+ }
971
+ // Manually close a session
972
+ await sessionManager.closeSession(sessionId, 'admin_action')
973
+ // Run manual cleanup (removes expired sessions)
974
+ const cleanup = await sessionManager.runManualCleanup()
975
+ console.log(`Removed ${cleanup.removedSessionsCount} expired sessions`)
976
+ // Get validation cache statistics
977
+ const cacheStats = sessionManager.getValidationCacheStats()
978
+ console.log('Cache entries:', cacheStats.totalEntries)
979
+ console.log('Cache TTL:', cacheStats.cacheTTL)
1121
980
  ```
1122
981
 
1123
982
  ### Session Lifecycle
@@ -1132,21 +991,15 @@ OUTPUTS:
1132
991
 
1133
992
  The SDK validates tokens by checking session state:
1134
993
 
1135
- ```text
1136
- PSEUDOCODE
1137
- INPUTS:
1138
- - Use values defined by the surrounding section/context.
1139
- STEPS:
1140
- - NOTE: Authentication middleware checks:
1141
- - NOTE: 1. JWT signature validity
1142
- - NOTE: 2. JWT expiration
1143
- - NOTE: 3. Session exists and is active
1144
- - NOTE: 4. Session not expired
1145
- - NOTE: After logout, tokens are invalidated because:
1146
- - NOTE: - Session status set to 'closed'
1147
- - NOTE: - Subsequent requests fail authentication
1148
- OUTPUTS:
1149
- - Produces the section's intended result using equivalent logic.
994
+ ```javascript
995
+ // Authentication middleware checks:
996
+ // 1. JWT signature validity
997
+ // 2. JWT expiration
998
+ // 3. Session exists and is active
999
+ // 4. Session not expired
1000
+ // After logout, tokens are invalidated because:
1001
+ // - Session status set to 'closed'
1002
+ // - Subsequent requests fail authentication
1150
1003
  ```
1151
1004
 
1152
1005
  ## Configuration
@@ -1161,19 +1014,13 @@ The SDK automatically configures itself from multiple sources with a clear prior
1161
1014
  4. **Provided Default Value** - Optional parameter to `config.get()`
1162
1015
 
1163
1016
  **Example:**
1164
- ```text
1165
- PSEUDOCODE
1166
- INPUTS:
1167
- - Use values defined by the surrounding section/context.
1168
- STEPS:
1169
- - SET config TO roditClient.getConfig()
1170
- - NOTE: Priority 1: Checks process.env.SESSION_STORAGE_TYPE
1171
- - NOTE: Priority 2: Checks host config.get('SESSION_STORAGE_TYPE')
1172
- - NOTE: Priority 3: Uses SDK default 'memory'
1173
- - NOTE: Priority 4: Falls back to 'memory' if provided
1174
- - SET storageType TO config.get('SESSION_STORAGE_TYPE', 'memory')
1175
- OUTPUTS:
1176
- - Produces the section's intended result using equivalent logic.
1017
+ ```javascript
1018
+ const config = roditClient.getConfig()
1019
+ // Priority 1: Checks process.env.SESSION_STORAGE_TYPE
1020
+ // Priority 2: Checks host config.get('SESSION_STORAGE_TYPE')
1021
+ // Priority 3: Uses SDK default 'memory'
1022
+ // Priority 4: Falls back to 'memory' if provided
1023
+ const storageType = config.get('SESSION_STORAGE_TYPE', 'memory')
1177
1024
  ```
1178
1025
 
1179
1026
  This ensures that:
@@ -1232,86 +1079,56 @@ Controls Winston logger verbosity independently from environment:
1232
1079
 
1233
1080
  #### Separation of Concerns
1234
1081
 
1235
- ```text
1236
- PSEUDOCODE
1237
- INPUTS:
1238
- - Use values defined by the surrounding section/context.
1239
- STEPS:
1240
- - NOTE: Environment detection (security)
1241
- - SET isMain TO process.env.NODE_ENV === 'main'
1242
- - SET isDevelopment TO process.env.NODE_ENV === 'development'
1243
- - SET isTest TO process.env.NODE_ENV === 'test'
1244
- - NOTE: Logging verbosity (independent)
1245
- - SET config TO roditClient.getConfig()
1246
- - SET logLevel TO config.get('LOG_LEVEL', 'info')
1247
- OUTPUTS:
1248
- - Produces the section's intended result using equivalent logic.
1082
+ ```javascript
1083
+ // Environment detection (security)
1084
+ const isMain = process.env.NODE_ENV === 'main'
1085
+ const isDevelopment = process.env.NODE_ENV === 'development'
1086
+ const isTest = process.env.NODE_ENV === 'test'
1087
+ // Logging verbosity (independent)
1088
+ const config = roditClient.getConfig()
1089
+ const logLevel = config.get('LOG_LEVEL', 'info')
1249
1090
  ```
1250
1091
 
1251
1092
  #### Configuration Examples
1252
1093
 
1253
1094
  **Main (normal):**
1254
- ```text
1255
- PSEUDOCODE
1256
- INPUTS:
1257
- - Use values defined by the surrounding section/context.
1258
- STEPS:
1259
- - DO: export NODE_ENV=main
1260
- - DO: export LOG_LEVEL=info
1261
- - NOTE: Results in:
1262
- - NOTE: - Strict security enforcement
1263
- - NOTE: - No error details in responses
1264
- - NOTE: - Minimal logging output
1265
- OUTPUTS:
1266
- - Produces the section's intended result using equivalent logic.
1095
+ ```bash
1096
+ export NODE_ENV=main
1097
+ export LOG_LEVEL=info
1098
+ // Results in:
1099
+ // - Strict security enforcement
1100
+ // - No error details in responses
1101
+ // - Minimal logging output
1267
1102
  ```
1268
1103
 
1269
1104
  **Main (troubleshooting):**
1270
- ```text
1271
- PSEUDOCODE
1272
- INPUTS:
1273
- - Use values defined by the surrounding section/context.
1274
- STEPS:
1275
- - DO: export NODE_ENV=main
1276
- - DO: export LOG_LEVEL=debug
1277
- - NOTE: Results in:
1278
- - NOTE: - Strict security enforcement (still main)
1279
- - NOTE: - No error details in responses (still secure)
1280
- - NOTE: - Verbose logging for debugging
1281
- OUTPUTS:
1282
- - Produces the section's intended result using equivalent logic.
1105
+ ```bash
1106
+ export NODE_ENV=main
1107
+ export LOG_LEVEL=debug
1108
+ // Results in:
1109
+ // - Strict security enforcement (still main)
1110
+ // - No error details in responses (still secure)
1111
+ // - Verbose logging for debugging
1283
1112
  ```
1284
1113
 
1285
1114
  **Development:**
1286
- ```text
1287
- PSEUDOCODE
1288
- INPUTS:
1289
- - Use values defined by the surrounding section/context.
1290
- STEPS:
1291
- - DO: export NODE_ENV=development
1292
- - DO: export LOG_LEVEL=debug
1293
- - NOTE: Results in:
1294
- - NOTE: - Relaxed security for development
1295
- - NOTE: - Detailed error messages in responses
1296
- - NOTE: - Verbose logging
1297
- OUTPUTS:
1298
- - Produces the section's intended result using equivalent logic.
1115
+ ```bash
1116
+ export NODE_ENV=development
1117
+ export LOG_LEVEL=debug
1118
+ // Results in:
1119
+ // - Relaxed security for development
1120
+ // - Detailed error messages in responses
1121
+ // - Verbose logging
1299
1122
  ```
1300
1123
 
1301
1124
  **Testing:**
1302
- ```text
1303
- PSEUDOCODE
1304
- INPUTS:
1305
- - Use values defined by the surrounding section/context.
1306
- STEPS:
1307
- - DO: export NODE_ENV=test
1308
- - DO: export LOG_LEVEL=error
1309
- - NOTE: Results in:
1310
- - NOTE: - Test mode (allows bypasses)
1311
- - NOTE: - Detailed error messages
1312
- - NOTE: - Only errors logged (cleaner test output)
1313
- OUTPUTS:
1314
- - Produces the section's intended result using equivalent logic.
1125
+ ```bash
1126
+ export NODE_ENV=test
1127
+ export LOG_LEVEL=error
1128
+ // Results in:
1129
+ // - Test mode (allows bypasses)
1130
+ // - Detailed error messages
1131
+ // - Only errors logged (cleaner test output)
1315
1132
  ```
1316
1133
 
1317
1134
  #### Behavior Matrix
@@ -1327,82 +1144,58 @@ OUTPUTS:
1327
1144
 
1328
1145
  For main deployments, credentials are loaded from HashiCorp Vault:
1329
1146
 
1330
- ```text
1331
- PSEUDOCODE
1332
- INPUTS:
1333
- - Use values defined by the surrounding section/context.
1334
- STEPS:
1335
- - NOTE: Environment variables for vault
1336
- - DO: export RODIT_NEAR_CREDENTIALS_SOURCE=vault
1337
- - FIELD: export VAULT_ENDPOINT=https://vault.example.com
1338
- - DO: export VAULT_ROLE_ID=your-role-id
1339
- - DO: export VAULT_SECRET_ID=your-secret-id
1340
- - DO: export VAULT_RODIT_KEYVALUE_PATH=secret/rodit
1341
- - DO: export SERVICE_NAME=your-service-name
1342
- - DO: export NEAR_CONTRACT_ID=discernible-io.near
1343
- OUTPUTS:
1344
- - Produces the section's intended result using equivalent logic.
1147
+ ```bash
1148
+ // Environment variables for vault
1149
+ export RODIT_NEAR_CREDENTIALS_SOURCE=vault
1150
+ export VAULT_ENDPOINT=https://vault.example.com
1151
+ export VAULT_ROLE_ID=your-role-id
1152
+ export VAULT_SECRET_ID=your-secret-id
1153
+ export VAULT_RODIT_KEYVALUE_PATH=secret/rodit
1154
+ export SERVICE_NAME=your-service-name
1155
+ export NEAR_CONTRACT_ID=discernible-io.near
1345
1156
  ```
1346
1157
 
1347
1158
  ### File-Based Configuration (Development)
1348
1159
 
1349
1160
  For development, credentials can be loaded from files:
1350
1161
 
1351
- ```text
1352
- PSEUDOCODE
1353
- INPUTS:
1354
- - Use values defined by the surrounding section/context.
1355
- STEPS:
1356
- - DO: export RODIT_NEAR_CREDENTIALS_SOURCE=file
1357
- - DO: export CREDENTIALS_FILE_PATH=./credentials/rodit-credentials.json
1358
- OUTPUTS:
1359
- - Produces the section's intended result using equivalent logic.
1162
+ ```bash
1163
+ export RODIT_NEAR_CREDENTIALS_SOURCE=file
1164
+ export CREDENTIALS_FILE_PATH=./credentials/rodit-credentials.json
1360
1165
  ```
1361
1166
 
1362
1167
  ### Accessing Configuration
1363
1168
 
1364
- ```text
1365
- PSEUDOCODE
1366
- INPUTS:
1367
- - Use values defined by the surrounding section/context.
1368
- STEPS:
1369
- - NOTE: Get complete RODiT configuration
1370
- - SET configObject TO await roditClient.getConfigOwnRodit()
1371
- - SET metadata TO configObject.own_rodit.metadata
1372
- - NOTE: Access RODiT token metadata
1373
- - SET jwtDuration TO metadata.jwt_duration; // JWT expiration time
1374
- - SET maxRequests TO metadata.max_requests; // Rate limit
1375
- - SET maxRqWindow TO metadata.maxrq_window; // Rate limit window
1376
- - SET apiEndpoint TO metadata.subjectuniqueidentifier_url; // API URL
1377
- - SET webhookUrl TO metadata.webhook_url; // Webhook endpoint
1378
- - NOTE: Parse permissioned routes
1379
- - SET permissionedRoutes TO JSON.parse(metadata.permissioned_routes || '{}')
1380
- - NOTE: Use SDK config for application settings
1381
- - SET config TO roditClient.getConfig()
1382
- - SET logLevel TO config.get('LOG_LEVEL', 'info')
1383
- - SET dbPath TO config.get('API_DEFAULT_OPTIONS.DB_PATH')
1384
- OUTPUTS:
1385
- - Produces the section's intended result using equivalent logic.
1169
+ ```javascript
1170
+ // Get complete RODiT configuration
1171
+ const configObject = await roditClient.getConfigOwnRodit()
1172
+ const metadata = configObject.own_rodit.metadata
1173
+ // Access RODiT token metadata
1174
+ const jwtDuration = metadata.jwt_duration; // JWT expiration time
1175
+ const maxRequests = metadata.max_requests; // Rate limit
1176
+ const maxRqWindow = metadata.maxrq_window; // Rate limit window
1177
+ const apiEndpoint = metadata.subjectuniqueidentifier_url; // API URL
1178
+ const webhookUrl = metadata.webhook_url; // Webhook endpoint
1179
+ // Parse permissioned routes
1180
+ const permissionedRoutes = JSON.parse(metadata.permissioned_routes || '{}')
1181
+ // Use SDK config for application settings
1182
+ const config = roditClient.getConfig()
1183
+ const logLevel = config.get('LOG_LEVEL', 'info')
1184
+ const dbPath = config.get('API_DEFAULT_OPTIONS.DB_PATH')
1386
1185
  ```
1387
1186
 
1388
1187
  ### Dynamic Rate Limiting
1389
1188
 
1390
- ```text
1391
- PSEUDOCODE
1392
- INPUTS:
1393
- - Use values defined by the surrounding section/context.
1394
- STEPS:
1395
- - NOTE: Configure rate limiting from RODiT token
1396
- - SET configObject TO await roditClient.getConfigOwnRodit()
1397
- - SET metadata TO configObject.own_rodit.metadata
1398
- - CHECK CONDITION: if (metadata.max_requests && metadata.maxrq_window) {
1399
- - SET maxRequests TO parseInt(metadata.max_requests)
1400
- - SET windowSeconds TO parseInt(metadata.maxrq_window)
1401
- - SET rateLimiter TO roditClient.getRateLimitMiddleware()
1402
- - DO: app.use(rateLimiter(maxRequests, windowSeconds))
1403
- - }
1404
- OUTPUTS:
1405
- - Produces the section's intended result using equivalent logic.
1189
+ ```javascript
1190
+ // Configure rate limiting from RODiT token
1191
+ const configObject = await roditClient.getConfigOwnRodit()
1192
+ const metadata = configObject.own_rodit.metadata
1193
+ if (metadata.max_requests && metadata.maxrq_window) {
1194
+ const maxRequests = parseInt(metadata.max_requests)
1195
+ const windowSeconds = parseInt(metadata.maxrq_window)
1196
+ const rateLimiter = roditClient.getRateLimitMiddleware()
1197
+ app.use(rateLimiter(maxRequests, windowSeconds))
1198
+ }
1406
1199
  ```
1407
1200
 
1408
1201
  ### Environment Variables
@@ -1410,107 +1203,71 @@ OUTPUTS:
1410
1203
  Complete list of SDK environment variables:
1411
1204
 
1412
1205
  #### Core Configuration
1413
- ```text
1414
- PSEUDOCODE
1415
- INPUTS:
1416
- - Use values defined by the surrounding section/context.
1417
- STEPS:
1418
- - NOTE: Service identification
1419
- - DO: export SERVICE_NAME=your-service-name
1420
- - DO: export API_VERSION=1.0.0
1421
- - NOTE: Environment and logging
1422
- - DO: export NODE_ENV=main # main, development, test
1423
- - DO: export LOG_LEVEL=info # error, warn, info, debug, trace
1424
- OUTPUTS:
1425
- - Produces the section's intended result using equivalent logic.
1206
+ ```bash
1207
+ // Service identification
1208
+ export SERVICE_NAME=your-service-name
1209
+ export API_VERSION=1.0.0
1210
+ // Environment and logging
1211
+ export NODE_ENV=main # main, development, test
1212
+ export LOG_LEVEL=info # error, warn, info, debug, trace
1426
1213
  ```
1427
1214
 
1428
1215
  #### Credentials and Authentication
1429
- ```text
1430
- PSEUDOCODE
1431
- INPUTS:
1432
- - Use values defined by the surrounding section/context.
1433
- STEPS:
1434
- - NOTE: Credential source
1435
- - DO: export RODIT_NEAR_CREDENTIALS_SOURCE=vault # vault, file, env
1436
- - NOTE: Vault configuration (main)
1437
- - FIELD: export VAULT_ENDPOINT=https://vault.example.com
1438
- - DO: export VAULT_ROLE_ID=your-role-id
1439
- - DO: export VAULT_SECRET_ID=your-secret-id
1440
- - DO: export VAULT_RODIT_KEYVALUE_PATH=secret/rodit
1441
- - DO: export VAULT_TOKEN_TTL=3600
1442
- - NOTE: File-based credentials (development)
1443
- - DO: export CREDENTIALS_FILEPATH=./credentials/rodit.json
1444
- - NOTE: NEAR blockchain
1445
- - DO: export NEAR_CONTRACT_ID=discernible-io.near
1446
- - FIELD: export NEAR_RPC_URL=https://rpc.mainnet.fastnear.com
1447
- - DO: export NEAR_RPC_CACHE_TTL=5000 # milliseconds
1448
- OUTPUTS:
1449
- - Produces the section's intended result using equivalent logic.
1216
+ ```bash
1217
+ // Credential source
1218
+ export RODIT_NEAR_CREDENTIALS_SOURCE=vault # vault, file, env
1219
+ // Vault configuration (main)
1220
+ export VAULT_ENDPOINT=https://vault.example.com
1221
+ export VAULT_ROLE_ID=your-role-id
1222
+ export VAULT_SECRET_ID=your-secret-id
1223
+ export VAULT_RODIT_KEYVALUE_PATH=secret/rodit
1224
+ export VAULT_TOKEN_TTL=3600
1225
+ // File-based credentials (development)
1226
+ export CREDENTIALS_FILEPATH=./credentials/rodit.json
1227
+ // NEAR blockchain
1228
+ export NEAR_CONTRACT_ID=discernible-io.near
1229
+ export NEAR_RPC_URL=https://rpc.mainnet.fastnear.com
1230
+ export NEAR_RPC_CACHE_TTL=5000 # milliseconds
1450
1231
  ```
1451
1232
 
1452
1233
  #### Session Management
1453
- ```text
1454
- PSEUDOCODE
1455
- INPUTS:
1456
- - Use values defined by the surrounding section/context.
1457
- STEPS:
1458
- - NOTE: Session storage configuration
1459
- - DO: export SESSION_STORAGE_TYPE=express-session # memory, express, express-session
1460
- - DO: export SESSION_CLEANUP_INTERVAL=3600000 # milliseconds (1 hour)
1461
- - DO: export SESSION_TOKEN_RETENTION_PERIOD=604800 # seconds (7 days)
1462
- - DO: export SESSION_VALIDATION_CACHE_TTL=5000 # milliseconds (5 seconds)
1463
- OUTPUTS:
1464
- - Produces the section's intended result using equivalent logic.
1234
+ ```bash
1235
+ // Session storage configuration
1236
+ export SESSION_STORAGE_TYPE=express-session # memory, express, express-session
1237
+ export SESSION_CLEANUP_INTERVAL=3600000 # milliseconds (1 hour)
1238
+ export SESSION_TOKEN_RETENTION_PERIOD=604800 # seconds (7 days)
1239
+ export SESSION_VALIDATION_CACHE_TTL=5000 # milliseconds (5 seconds)
1465
1240
  ```
1466
1241
 
1467
1242
  #### Logging and Monitoring
1468
- ```text
1469
- PSEUDOCODE
1470
- INPUTS:
1471
- - Use values defined by the surrounding section/context.
1472
- STEPS:
1473
- - NOTE: Loki logging
1474
- - FIELD: export LOKI_URL=https://loki.example.com:3100
1475
- - FIELD: export LOKI_BASIC_AUTH=username:password
1476
- - DO: export LOKI_TLS_SKIP_VERIFY=false # true to skip TLS verification
1477
- OUTPUTS:
1478
- - Produces the section's intended result using equivalent logic.
1243
+ ```bash
1244
+ // Loki logging
1245
+ export LOKI_URL=https://loki.example.com:3100
1246
+ export LOKI_BASIC_AUTH=username:password
1247
+ export LOKI_TLS_SKIP_VERIFY=false # true to skip TLS verification
1479
1248
  ```
1480
1249
 
1481
1250
  #### Security Options
1482
- ```text
1483
- PSEUDOCODE
1484
- INPUTS:
1485
- - Use values defined by the surrounding section/context.
1486
- STEPS:
1487
- - NOTE: Webhook configuration
1488
- - DO: export WEBHOOK_TLS_SKIP_VERIFY=false # true to skip TLS verification
1489
- - NOTE: Login mode control (see Login Mode section below)
1490
- - DO: export SECURITY_OPTIONS_LOGIN_MODE=partner # partner, promiscuous, or p2p
1491
- - NOTE: Security thresholds
1492
- - DO: export SECURITY_OPTIONS_LAPSED_LIFETIME_PROPORTION_4RENEWAL_ELIGIBILITY=0.80
1493
- - DO: export SECURITY_OPTIONS_THRESHOLD_VALIDATION_TYPE=0.10
1494
- - DO: export SECURITY_OPTIONS_DURATIONRAMP=0.85
1495
- - DO: export SECURITY_OPTIONS_SERVERORCLIENT=SERVER-INITIATED
1496
- - DO: export SECURITY_OPTIONS_SILENT_LOGIN_FAILURES=false
1497
- - NOTE: Server session lifetime (seconds from login; SDK default 5200)
1498
- - DO: export SECURITY_OPTIONS_SESSION_TTL_SECONDS=5200
1499
- - NOTE: Access-token fallback when passport jwt_duration is invalid
1500
- - DO: export SECURITY_OPTIONS_FALLBACK_JWT_DURATION=3600
1501
- OUTPUTS:
1502
- - Produces the section's intended result using equivalent logic.
1251
+ ```bash
1252
+ // Webhook configuration
1253
+ export WEBHOOK_TLS_SKIP_VERIFY=false # true to skip TLS verification
1254
+ // Login mode control (see Login Mode section below)
1255
+ export SECURITY_OPTIONS_LOGIN_MODE=partner # partner, promiscuous, or p2p
1256
+ // Security thresholds
1257
+ export SECURITY_OPTIONS_LAPSED_LIFETIME_PROPORTION_4RENEWAL_ELIGIBILITY=0.80
1258
+ export SECURITY_OPTIONS_THRESHOLD_VALIDATION_TYPE=0.10
1259
+ export SECURITY_OPTIONS_DURATIONRAMP=0.85
1260
+ export SECURITY_OPTIONS_SERVERORCLIENT=SERVER-INITIATED
1261
+ export SECURITY_OPTIONS_SILENT_LOGIN_FAILURES=false
1262
+ // Server session lifetime (seconds from login; SDK default 5200)
1263
+ export SECURITY_OPTIONS_SESSION_TTL_SECONDS=5200
1264
+ // Access-token fallback when passport jwt_duration is invalid
1265
+ export SECURITY_OPTIONS_FALLBACK_JWT_DURATION=3600
1503
1266
  ```
1504
1267
 
1505
1268
  #### Database Configuration
1506
- ```text
1507
- PSEUDOCODE
1508
- INPUTS:
1509
- - Use values defined by the surrounding section/context.
1510
- STEPS:
1511
- - DO: export API_DEFAULT_OPTIONS_DB_PATH=/app/data/database.sqlite
1512
- OUTPUTS:
1513
- - Produces the section's intended result using equivalent logic.
1269
+ ```bash
1270
+ export API_DEFAULT_OPTIONS_DB_PATH=/app/data/database.sqlite
1514
1271
  ```
1515
1272
 
1516
1273
  ## Logging & Monitoring
@@ -1519,38 +1276,32 @@ OUTPUTS:
1519
1276
 
1520
1277
  The SDK provides comprehensive structured logging:
1521
1278
 
1522
- ```text
1523
- PSEUDOCODE
1524
- INPUTS:
1525
- - Use values defined by the surrounding section/context.
1526
- STEPS:
1527
- - DO: const { logger } = require('@rodit/rodit-auth-be')
1528
- - NOTE: Basic logging
1529
- - DO: logger.info('Operation completed', {
1530
- - FIELD: component: 'UserService',
1531
- - FIELD: operation: 'createUser',
1532
- - FIELD: userId: '123',
1533
- - FIELD: duration: 150
1534
- - DO: })
1535
- - NOTE: Context-aware logging
1536
- - DO: logger.infoWithContext('Request processed', {
1537
- - FIELD: component: 'API',
1538
- - FIELD: method: 'POST',
1539
- - FIELD: path: '/api/users',
1540
- - FIELD: requestId: req.requestId,
1541
- - FIELD: userId: req.user?.id,
1542
- - FIELD: duration: Date.now() - req.startTime
1543
- - DO: })
1544
- - NOTE: Error logging with metrics
1545
- - DO: logger.errorWithContext('Operation failed', {
1546
- - FIELD: component: 'UserService',
1547
- - FIELD: operation: 'createUser',
1548
- - FIELD: requestId: req.requestId,
1549
- - FIELD: error: error.message,
1550
- - FIELD: stack: error.stack
1551
- - DO: }, error)
1552
- OUTPUTS:
1553
- - Produces the section's intended result using equivalent logic.
1279
+ ```javascript
1280
+ const { logger } = require('@rodit/rodit-auth-be')
1281
+ // Basic logging
1282
+ logger.info('Operation completed', {
1283
+ component: 'UserService',
1284
+ operation: 'createUser',
1285
+ userId: '123',
1286
+ duration: 150
1287
+ })
1288
+ // Context-aware logging
1289
+ logger.infoWithContext('Request processed', {
1290
+ component: 'API',
1291
+ method: 'POST',
1292
+ path: '/api/users',
1293
+ requestId: req.requestId,
1294
+ userId: req.user?.id,
1295
+ duration: Date.now() - req.startTime
1296
+ })
1297
+ // Error logging with metrics
1298
+ logger.errorWithContext('Operation failed', {
1299
+ component: 'UserService',
1300
+ operation: 'createUser',
1301
+ requestId: req.requestId,
1302
+ error: error.message,
1303
+ stack: error.stack
1304
+ }, error)
1554
1305
  ```
1555
1306
 
1556
1307
  ### Loki with the SDK (canonical)
@@ -1559,18 +1310,12 @@ Use this as the authoritative guide for configuring logging with the SDK.
1559
1310
 
1560
1311
  #### Environment variables
1561
1312
 
1562
- ```text
1563
- PSEUDOCODE
1564
- INPUTS:
1565
- - Use values defined by the surrounding section/context.
1566
- STEPS:
1567
- - FIELD: export LOKI_URL=https://<your-loki-host>:3100
1568
- - FIELD: export LOKI_BASIC_AUTH="username:password" # store in secrets
1569
- - DO: export LOKI_TLS_SKIP_VERIFY=true # only for self-signed/test
1570
- - DO: export LOG_LEVEL=info
1571
- - DO: export SERVICE_NAME=clienttest-idc
1572
- OUTPUTS:
1573
- - Produces the section's intended result using equivalent logic.
1313
+ ```bash
1314
+ export LOKI_URL=https://<your-loki-host>:3100
1315
+ export LOKI_BASIC_AUTH="username:password" # store in secrets
1316
+ export LOKI_TLS_SKIP_VERIFY=true # only for self-signed/test
1317
+ export LOG_LEVEL=info
1318
+ export SERVICE_NAME=clienttest-idc
1574
1319
  ```
1575
1320
 
1576
1321
  These are already mapped in `config/custom-environment-variables.json`, so container/CI env vars will flow into the app.
@@ -1583,36 +1328,30 @@ These are already mapped in `config/custom-environment-variables.json`, so conta
1583
1328
 
1584
1329
  #### Direct-to-Loki via winston-loki (recommended)
1585
1330
 
1586
- ```text
1587
- PSEUDOCODE
1588
- INPUTS:
1589
- - Use values defined by the surrounding section/context.
1590
- STEPS:
1591
- - DO: const { logger } = require('@rodit/rodit-auth-be')
1592
- - SET winston TO require('winston')
1593
- - SET LokiTransport TO require('winston-loki')
1594
- - SET transports TO [new winston.transports.Console({ format: winston.format.json() })]
1595
- - CHECK CONDITION: if (process.env.LOKI_URL) {
1596
- - SET lokiOptions TO {
1597
- - FIELD: host: process.env.LOKI_URL,
1598
- - FIELD: basicAuth: process.env.LOKI_BASIC_AUTH, // Basic Auth for Loki
1599
- - FIELD: labels: { app: process.env.SERVICE_NAME || 'clienttest-idc', component: 'rodit-sdk' },
1600
- - FIELD: json: true,
1601
- - FIELD: batching: true
1602
- - DO: }
1603
- - CHECK CONDITION: if ((process.env.LOKI_TLS_SKIP_VERIFY || '').toLowerCase() === 'true') {
1604
- - FIELD: lokiOptions.ssl = { rejectUnauthorized: false }
1605
- - }
1606
- - DO: transports.push(new LokiTransport(lokiOptions))
1607
- - }
1608
- - SET customLogger TO winston.createLogger({
1609
- - FIELD: level: process.env.LOG_LEVEL || 'info',
1610
- - FIELD: format: winston.format.json(),
1611
- - DO: transports
1612
- - DO: })
1613
- - DO: logger.setLogger(customLogger)
1614
- OUTPUTS:
1615
- - Produces the section's intended result using equivalent logic.
1331
+ ```javascript
1332
+ const { logger } = require('@rodit/rodit-auth-be')
1333
+ const winston = require('winston')
1334
+ const LokiTransport = require('winston-loki')
1335
+ const transports = [new winston.transports.Console({ format: winston.format.json() })]
1336
+ if (process.env.LOKI_URL) {
1337
+ const lokiOptions = {
1338
+ host: process.env.LOKI_URL,
1339
+ basicAuth: process.env.LOKI_BASIC_AUTH, // Basic Auth for Loki
1340
+ labels: { app: process.env.SERVICE_NAME || 'clienttest-idc', component: 'rodit-sdk' },
1341
+ json: true,
1342
+ batching: true
1343
+ }
1344
+ if ((process.env.LOKI_TLS_SKIP_VERIFY || '').toLowerCase() === 'true') {
1345
+ lokiOptions.ssl = { rejectUnauthorized: false }
1346
+ }
1347
+ transports.push(new LokiTransport(lokiOptions))
1348
+ }
1349
+ const customLogger = winston.createLogger({
1350
+ level: process.env.LOG_LEVEL || 'info',
1351
+ format: winston.format.json(),
1352
+ transports
1353
+ })
1354
+ logger.setLogger(customLogger)
1616
1355
  ```
1617
1356
 
1618
1357
  #### CI/CD notes
@@ -1632,144 +1371,120 @@ The SDK includes comprehensive performance tracking and metrics collection.
1632
1371
 
1633
1372
  ### Performance Service
1634
1373
 
1635
- ```text
1636
- PSEUDOCODE
1637
- INPUTS:
1638
- - Use values defined by the surrounding section/context.
1639
- STEPS:
1640
- - SET performanceService TO roditClient.getPerformanceService()
1641
- - NOTE: Record incoming request
1642
- - DO: performanceService.recordRequest(req)
1643
- - NOTE: Record custom metrics with labels
1644
- - DO: performanceService.recordMetric('operation_duration', 150, {
1645
- - FIELD: operation: 'db_query',
1646
- - FIELD: table: 'users',
1647
- - FIELD: status: 'success'
1648
- - DO: })
1649
- - NOTE: Record errors
1650
- - DO: performanceService.recordMetric('error_count', 1, {
1651
- - FIELD: method: req.method,
1652
- - FIELD: path: req.path,
1653
- - FIELD: status: res.statusCode
1654
- - DO: })
1655
- - NOTE: Get aggregated metrics
1656
- - SET metrics TO performanceService.getMetrics()
1657
- - FIELD: console.log('Total requests:', metrics.totalRequests)
1658
- - FIELD: console.log('Error count:', metrics.errorCount)
1659
- - FIELD: console.log('Average response time:', metrics.avgResponseTime)
1660
- OUTPUTS:
1661
- - Produces the section's intended result using equivalent logic.
1374
+ ```javascript
1375
+ const performanceService = roditClient.getPerformanceService()
1376
+ // Record incoming request
1377
+ performanceService.recordRequest(req)
1378
+ // Record custom metrics with labels
1379
+ performanceService.recordMetric('operation_duration', 150, {
1380
+ operation: 'db_query',
1381
+ table: 'users',
1382
+ status: 'success'
1383
+ })
1384
+ // Record errors
1385
+ performanceService.recordMetric('error_count', 1, {
1386
+ method: req.method,
1387
+ path: req.path,
1388
+ status: res.statusCode
1389
+ })
1390
+ // Get aggregated metrics
1391
+ const metrics = performanceService.getMetrics()
1392
+ console.log('Total requests:', metrics.totalRequests)
1393
+ console.log('Error count:', metrics.errorCount)
1394
+ console.log('Average response time:', metrics.avgResponseTime)
1662
1395
  ```
1663
1396
 
1664
1397
  ### Automatic Request Tracking
1665
1398
 
1666
1399
  Integrate performance tracking into your middleware:
1667
1400
 
1668
- ```text
1669
- PSEUDOCODE
1670
- INPUTS:
1671
- - Use values defined by the surrounding section/context.
1672
- STEPS:
1673
- - NOTE: Performance monitoring middleware
1674
- - DO: app.use((req, res, next) => {
1675
- - DO: req.startTime = Date.now()
1676
- - SET performanceService TO roditClient.getPerformanceService()
1677
- - CHECK CONDITION: if (performanceService) {
1678
- - DO: performanceService.recordRequest(req)
1679
- - }
1680
- - DO: res.on('finish', () => {
1681
- - SET duration TO Date.now() - req.startTime
1682
- - CHECK CONDITION: if (performanceService) {
1683
- - NOTE: Record request duration
1684
- - DO: performanceService.recordMetric('request_duration_ms', duration, {
1685
- - FIELD: method: req.method,
1686
- - FIELD: path: req.path,
1687
- - FIELD: status: res.statusCode
1688
- - DO: })
1689
- - NOTE: Record errors
1690
- - CHECK CONDITION: if (res.statusCode >= 400) {
1691
- - DO: performanceService.recordMetric('error_count', 1, {
1692
- - FIELD: method: req.method,
1693
- - FIELD: path: req.path,
1694
- - FIELD: status: res.statusCode
1695
- - DO: })
1696
- - }
1697
- - }
1698
- - DO: })
1699
- - DO: next()
1700
- - DO: })
1701
- OUTPUTS:
1702
- - Produces the section's intended result using equivalent logic.
1401
+ ```javascript
1402
+ // Performance monitoring middleware
1403
+ app.use((req, res, next) => {
1404
+ req.startTime = Date.now()
1405
+ const performanceService = roditClient.getPerformanceService()
1406
+ if (performanceService) {
1407
+ performanceService.recordRequest(req)
1408
+ }
1409
+ res.on('finish', () => {
1410
+ const duration = Date.now() - req.startTime
1411
+ if (performanceService) {
1412
+ // Record request duration
1413
+ performanceService.recordMetric('request_duration_ms', duration, {
1414
+ method: req.method,
1415
+ path: req.path,
1416
+ status: res.statusCode
1417
+ })
1418
+ // Record errors
1419
+ if (res.statusCode >= 400) {
1420
+ performanceService.recordMetric('error_count', 1, {
1421
+ method: req.method,
1422
+ path: req.path,
1423
+ status: res.statusCode
1424
+ })
1425
+ }
1426
+ }
1427
+ })
1428
+ next()
1429
+ })
1703
1430
  ```
1704
1431
 
1705
1432
  ### Session Performance Metrics
1706
1433
 
1707
1434
  Track session-related performance:
1708
1435
 
1709
- ```text
1710
- PSEUDOCODE
1711
- INPUTS:
1712
- - Use values defined by the surrounding section/context.
1713
- STEPS:
1714
- - SET sessionManager TO roditClient.getSessionManager()
1715
- - NOTE: Get validation cache statistics
1716
- - SET cacheStats TO sessionManager.getValidationCacheStats()
1717
- - DO: logger.info('Session cache performance', {
1718
- - FIELD: component: 'SessionManager',
1719
- - FIELD: totalEntries: cacheStats.totalEntries,
1720
- - FIELD: validEntries: cacheStats.validEntries,
1721
- - FIELD: expiredEntries: cacheStats.expiredEntries,
1722
- - FIELD: cacheTTL: cacheStats.cacheTTL,
1723
- - FIELD: cacheEnabled: cacheStats.cacheEnabled
1724
- - DO: })
1725
- - NOTE: Get storage information
1726
- - SET storageInfo TO await sessionManager.getStorageInfo()
1727
- - DO: logger.info('Session storage status', {
1728
- - FIELD: component: 'SessionManager',
1729
- - FIELD: storageType: storageInfo.type,
1730
- - FIELD: sessionCount: storageInfo.sessionCount,
1731
- - FIELD: timestamp: storageInfo.timestamp
1732
- - DO: })
1733
- OUTPUTS:
1734
- - Produces the section's intended result using equivalent logic.
1436
+ ```javascript
1437
+ const sessionManager = roditClient.getSessionManager()
1438
+ // Get validation cache statistics
1439
+ const cacheStats = sessionManager.getValidationCacheStats()
1440
+ logger.info('Session cache performance', {
1441
+ component: 'SessionManager',
1442
+ totalEntries: cacheStats.totalEntries,
1443
+ validEntries: cacheStats.validEntries,
1444
+ expiredEntries: cacheStats.expiredEntries,
1445
+ cacheTTL: cacheStats.cacheTTL,
1446
+ cacheEnabled: cacheStats.cacheEnabled
1447
+ })
1448
+ // Get storage information
1449
+ const storageInfo = await sessionManager.getStorageInfo()
1450
+ logger.info('Session storage status', {
1451
+ component: 'SessionManager',
1452
+ storageType: storageInfo.type,
1453
+ sessionCount: storageInfo.sessionCount,
1454
+ timestamp: storageInfo.timestamp
1455
+ })
1735
1456
  ```
1736
1457
 
1737
1458
  ### Custom Metrics
1738
1459
 
1739
1460
  Record application-specific metrics:
1740
1461
 
1741
- ```text
1742
- PSEUDOCODE
1743
- INPUTS:
1744
- - Use values defined by the surrounding section/context.
1745
- STEPS:
1746
- - SET performanceService TO roditClient.getPerformanceService()
1747
- - NOTE: Database operation timing
1748
- - SET dbStart TO Date.now()
1749
- - SET result TO await db.query('SELECT * FROM users')
1750
- - SET dbDuration TO Date.now() - dbStart
1751
- - DO: performanceService.recordMetric('db_query_duration', dbDuration, {
1752
- - FIELD: operation: 'select',
1753
- - FIELD: table: 'users',
1754
- - FIELD: rowCount: result.length
1755
- - DO: })
1756
- - NOTE: External API call timing
1757
- - SET apiStart TO Date.now()
1758
- - SET apiResponse TO await fetch('https://api.example.com/data')
1759
- - SET apiDuration TO Date.now() - apiStart
1760
- - DO: performanceService.recordMetric('external_api_duration', apiDuration, {
1761
- - FIELD: endpoint: 'api.example.com',
1762
- - FIELD: status: apiResponse.status,
1763
- - FIELD: success: apiResponse.ok
1764
- - DO: })
1765
- - NOTE: Business metrics
1766
- - DO: performanceService.recordMetric('user_action', 1, {
1767
- - FIELD: action: 'comment_created',
1768
- - FIELD: userId: req.user.id,
1769
- - FIELD: timestamp: new Date().toISOString()
1770
- - DO: })
1771
- OUTPUTS:
1772
- - Produces the section's intended result using equivalent logic.
1462
+ ```javascript
1463
+ const performanceService = roditClient.getPerformanceService()
1464
+ // Database operation timing
1465
+ const dbStart = Date.now()
1466
+ const result = await db.query('SELECT * FROM users')
1467
+ const dbDuration = Date.now() - dbStart
1468
+ performanceService.recordMetric('db_query_duration', dbDuration, {
1469
+ operation: 'select',
1470
+ table: 'users',
1471
+ rowCount: result.length
1472
+ })
1473
+ // External API call timing
1474
+ const apiStart = Date.now()
1475
+ const apiResponse = await fetch('https://api.example.com/data')
1476
+ const apiDuration = Date.now() - apiStart
1477
+ performanceService.recordMetric('external_api_duration', apiDuration, {
1478
+ endpoint: 'api.example.com',
1479
+ status: apiResponse.status,
1480
+ success: apiResponse.ok
1481
+ })
1482
+ // Business metrics
1483
+ performanceService.recordMetric('user_action', 1, {
1484
+ action: 'comment_created',
1485
+ userId: req.user.id,
1486
+ timestamp: new Date().toISOString()
1487
+ })
1773
1488
  ```
1774
1489
 
1775
1490
  ## Webhooks
@@ -1785,107 +1500,83 @@ The SDK supports sending webhooks to multiple endpoints for important events. We
1785
1500
 
1786
1501
  Webhooks are configured in your RODiT token:
1787
1502
 
1788
- ```text
1789
- PSEUDOCODE
1790
- INPUTS:
1791
- - Use values defined by the surrounding section/context.
1792
- STEPS:
1793
- - {
1794
- - FIELD: "webhook_url": "https://webhook.example.com:7443",
1795
- - FIELD: "webhook_cidr": "0.0.0.0/0"
1796
- - }
1797
- OUTPUTS:
1798
- - Produces the section's intended result using equivalent logic.
1503
+ ```json
1504
+ {
1505
+ "webhook_url": "https:
1506
+ "webhook_cidr": "0.0.0.0/0"
1507
+ }
1799
1508
  ```
1800
1509
 
1801
1510
  ### Sending Webhooks to Default Endpoint
1802
1511
 
1803
1512
  Send webhooks to the default `/webhook` endpoint:
1804
1513
 
1805
- ```text
1806
- PSEUDOCODE
1807
- INPUTS:
1808
- - Use values defined by the surrounding section/context.
1809
- STEPS:
1810
- - NOTE: Get webhook handler from client
1811
- - SET roditClient TO req.app.locals.roditClient
1812
- - NOTE: Send webhook for an event
1813
- - SET webhookPayload TO {
1814
- - FIELD: event: 'comment_created',
1815
- - FIELD: data: {
1816
- - FIELD: id: comment.id,
1817
- - FIELD: author: comment.author,
1818
- - FIELD: timestamp: new Date().toISOString()
1819
- - DO: },
1820
- - FIELD: isError: false
1821
- - DO: }
1822
- - DO: try {
1823
- - SET result TO await roditClient.sendWebhook(webhookPayload, req)
1824
- - CHECK CONDITION: if (result.success) {
1825
- - DO: logger.info('Webhook sent successfully', {
1826
- - FIELD: component: 'CRUDA',
1827
- - FIELD: event: webhookPayload.event,
1828
- - FIELD: requestId: req.requestId
1829
- - DO: })
1830
- - }
1831
- - DO: } catch (error) {
1832
- - NOTE: Webhook failures don't crash the application
1833
- - DO: logger.warn('Webhook delivery failed', {
1834
- - FIELD: component: 'CRUDA',
1835
- - FIELD: event: webhookPayload.event,
1836
- - FIELD: error: error.message,
1837
- - FIELD: requestId: req.requestId
1838
- - DO: })
1839
- - }
1840
- OUTPUTS:
1841
- - Produces the section's intended result using equivalent logic.
1514
+ ```javascript
1515
+ // Get webhook handler from client
1516
+ const roditClient = req.app.locals.roditClient
1517
+ // Send webhook for an event
1518
+ const webhookPayload = {
1519
+ event: 'comment_created',
1520
+ data: {
1521
+ id: comment.id,
1522
+ author: comment.author,
1523
+ timestamp: new Date().toISOString()
1524
+ },
1525
+ isError: false
1526
+ }
1527
+ try {
1528
+ const result = await roditClient.sendWebhook(webhookPayload, req)
1529
+ if (result.success) {
1530
+ logger.info('Webhook sent successfully', {
1531
+ component: 'CRUDA',
1532
+ event: webhookPayload.event,
1533
+ requestId: req.requestId
1534
+ })
1535
+ }
1536
+ } catch (error) {
1537
+ // Webhook failures don't crash the application
1538
+ logger.warn('Webhook delivery failed', {
1539
+ component: 'CRUDA',
1540
+ event: webhookPayload.event,
1541
+ error: error.message,
1542
+ requestId: req.requestId
1543
+ })
1544
+ }
1842
1545
  ```
1843
1546
 
1844
1547
  ### Sending Webhooks to Custom Endpoints
1845
1548
 
1846
1549
  Send webhooks to specific endpoints like `/hooks/wake` or `/hooks/agent`:
1847
1550
 
1848
- ```text
1849
- PSEUDOCODE
1850
- INPUTS:
1851
- - Use values defined by the surrounding section/context.
1852
- STEPS:
1853
- - SET roditClient TO req.app.locals.roditClient
1854
- - SET webhookPayload TO {
1855
- - FIELD: event: 'heartbeat_request',
1856
- - FIELD: data: {
1857
- - FIELD: timestamp: new Date().toISOString(),
1858
- - FIELD: source: '/api/testhola'
1859
- - }
1860
- - DO: }
1861
- - NOTE: Send to /hooks/wake endpoint (trigger immediate heartbeat)
1862
- - WAIT FOR: roditClient.sendWebhookToEndpoint(webhookPayload, '/hooks/wake', req)
1863
- - NOTE: Send to /hooks/agent endpoint (run isolated agent task)
1864
- - WAIT FOR: roditClient.sendWebhookToEndpoint(webhookPayload, '/hooks/agent', req)
1865
- - NOTE: Send to custom endpoint
1866
- - WAIT FOR: roditClient.sendWebhookToEndpoint(webhookPayload, '/hooks/custom', req)
1867
- OUTPUTS:
1868
- - Produces the section's intended result using equivalent logic.
1551
+ ```javascript
1552
+ const roditClient = req.app.locals.roditClient
1553
+ const webhookPayload = {
1554
+ event: 'heartbeat_request',
1555
+ data: {
1556
+ timestamp: new Date().toISOString(),
1557
+ source: '/api/testhola'
1558
+ }
1559
+ }
1560
+ // Send to /hooks/wake endpoint (trigger immediate heartbeat)
1561
+ await roditClient.sendWebhookToEndpoint(webhookPayload, '/hooks/wake', req)
1562
+ // Send to /hooks/agent endpoint (run isolated agent task)
1563
+ await roditClient.sendWebhookToEndpoint(webhookPayload, '/hooks/agent', req)
1564
+ // Send to custom endpoint
1565
+ await roditClient.sendWebhookToEndpoint(webhookPayload, '/hooks/custom', req)
1869
1566
  ```
1870
1567
 
1871
1568
  ### Convenience Methods for Common Endpoints
1872
1569
 
1873
- ```text
1874
- PSEUDOCODE
1875
- INPUTS:
1876
- - Use values defined by the surrounding section/context.
1877
- STEPS:
1878
- - SET roditClient TO req.app.locals.roditClient
1879
- - SET payload TO {
1880
- - FIELD: event: 'test_event',
1881
- - FIELD: data: { timestamp: new Date().toISOString() }
1882
- - DO: }
1883
- - NOTE: Send to /hooks/wake (heartbeat confirmation)
1884
- - WAIT FOR: roditClient.sendWakeHook(payload, req)
1885
- - NOTE: Send to /hooks/agent (agent task confirmation)
1886
- - WAIT FOR: roditClient.sendAgentHook(payload, req)
1887
- OUTPUTS:
1888
- - Produces the section's intended result using equivalent logic.
1570
+ ```javascript
1571
+ const roditClient = req.app.locals.roditClient
1572
+ const payload = {
1573
+ event: 'test_event',
1574
+ data: { timestamp: new Date().toISOString() }
1575
+ }
1576
+ // Send to /hooks/wake (heartbeat confirmation)
1577
+ await roditClient.sendWakeHook(payload, req)
1578
+ // Send to /hooks/agent (agent task confirmation)
1579
+ await roditClient.sendAgentHook(payload, req)
1889
1580
  ```
1890
1581
 
1891
1582
  ### Webhook Endpoint Purposes
@@ -1898,60 +1589,46 @@ OUTPUTS:
1898
1589
 
1899
1590
  ### Webhook Error Handling
1900
1591
 
1901
- ```text
1902
- PSEUDOCODE
1903
- INPUTS:
1904
- - Use values defined by the surrounding section/context.
1905
- STEPS:
1906
- - NOTE: Graceful webhook handling in CRUDA operations
1907
- - SET logAndSendWebhook TO async (payload, req = null) => {
1908
- - DO: try {
1909
- - SET roditClient TO req?.app?.locals?.roditClient
1910
- - CHECK CONDITION: if (!roditClient) {
1911
- - DO: logger.warn('RoditClient not available, skipping webhook', {
1912
- - FIELD: component: 'CRUDA',
1913
- - FIELD: event: payload?.event
1914
- - DO: })
1915
- - RETURN { success: false, error: 'RoditClient not available' }
1916
- - }
1917
- - RETURN await roditClient.sendWebhook(payload, req)
1918
- - DO: } catch (error) {
1919
- - NOTE: Log but don't throw - webhook failures shouldn't crash the app
1920
- - DO: logger.error('Webhook delivery failed', {
1921
- - FIELD: component: 'CRUDA',
1922
- - FIELD: event: payload?.event,
1923
- - FIELD: error: error.message
1924
- - DO: })
1925
- - RETURN { success: false, error: error.message }
1926
- - }
1927
- - DO: }
1928
- OUTPUTS:
1929
- - Produces the section's intended result using equivalent logic.
1592
+ ```javascript
1593
+ // Graceful webhook handling in CRUDA operations
1594
+ const logAndSendWebhook = async (payload, req = null) => {
1595
+ try {
1596
+ const roditClient = req?.app?.locals?.roditClient
1597
+ if (!roditClient) {
1598
+ logger.warn('RoditClient not available, skipping webhook', {
1599
+ component: 'CRUDA',
1600
+ event: payload?.event
1601
+ })
1602
+ { success: false, error: 'RoditClient not available' }
1603
+ }
1604
+ await roditClient.sendWebhook(payload, req)
1605
+ } catch (error) {
1606
+ // Log but don't throw - webhook failures shouldn't crash the app
1607
+ logger.error('Webhook delivery failed', {
1608
+ component: 'CRUDA',
1609
+ event: payload?.event,
1610
+ error: error.message
1611
+ })
1612
+ { success: false, error: error.message }
1613
+ }
1614
+ }
1930
1615
  ```
1931
1616
 
1932
1617
  ### Development/Testing Webhooks
1933
1618
 
1934
1619
  The `/api/testhola` endpoint sends test webhooks in development mode (`NODE_ENV === 'development'`):
1935
1620
 
1936
- ```text
1937
- PSEUDOCODE
1938
- INPUTS:
1939
- - Use values defined by the surrounding section/context.
1940
- STEPS:
1941
- - NOTE: Event: testhola_validation_success
1942
- - NOTE: Sent to: /hooks/wake and /hooks/agent (development only)
1943
- - {
1944
- - FIELD: "event": "testhola_validation_success",
1945
- - FIELD: "data": {
1946
- - FIELD: "peerTokenId": "bcdfhjkmnpqr",
1947
- - FIELD: "serverTokenId": "bcdfhjkmnpqr",
1948
- - FIELD: "recipient": "MUNDO",
1949
- - FIELD: "timestamp": "2026-04-24T14:30:00.000Z",
1950
- - FIELD: "endpoint": "/api/testhola"
1951
- - }
1952
- - }
1953
- OUTPUTS:
1954
- - Produces the section's intended result using equivalent logic.
1621
+ ```json
1622
+ {
1623
+ "event": "testhola_validation_success",
1624
+ "data": {
1625
+ "peerTokenId": "bcdfhjkmnpqr",
1626
+ "serverTokenId": "bcdfhjkmnpqr",
1627
+ "recipient": "MUNDO",
1628
+ "timestamp": "2026-04-24T14:30:00.000Z",
1629
+ "endpoint": "/api/testhola"
1630
+ }
1631
+ }
1955
1632
  ```
1956
1633
 
1957
1634
  **Use Case:** Test webhook delivery and signature validation during development without needing a main deployment.
@@ -1962,127 +1639,115 @@ OUTPUTS:
1962
1639
 
1963
1640
  Create reusable route modules that access the shared RoditClient:
1964
1641
 
1965
- ```text
1966
- PSEUDOCODE
1967
- INPUTS:
1968
- - Use values defined by the surrounding section/context.
1969
- STEPS:
1970
- - NOTE: routes/protected.js
1971
- - SET express TO require('express')
1972
- - DO: const { logger } = require('@rodit/rodit-auth-be')
1973
- - SET router TO express.Router()
1974
- - NOTE: Middleware that uses the shared client
1975
- - SET authenticate TO (req, res, next) => {
1976
- - SET client TO req.app.locals.roditClient
1977
- - CHECK CONDITION: if (!client) {
1978
- - RETURN res.status(503).json({ error: 'Authentication service unavailable' })
1979
- - }
1980
- - RETURN client.authenticate(req, res, next)
1981
- - DO: }
1982
- - SET authorize TO (req, res, next) => {
1983
- - SET client TO req.app.locals.roditClient
1984
- - CHECK CONDITION: if (!client) {
1985
- - RETURN res.status(503).json({ error: 'Authentication service unavailable' })
1986
- - }
1987
- - RETURN client.authorize(req, res, next)
1988
- - DO: }
1989
- - NOTE: Protected route with full authentication and authorization
1990
- - DO: router.get('/data', authenticate, authorize, async (req, res) => {
1991
- - SET startTime TO Date.now()
1992
- - DO: try {
1993
- - NOTE: Your business logic here
1994
- - SET data TO await processUserData(req.user.id)
1995
- - DO: logger.infoWithContext('Data retrieved successfully', {
1996
- - FIELD: component: 'ProtectedRoutes',
1997
- - FIELD: method: 'getData',
1998
- - FIELD: userId: req.user.id,
1999
- - FIELD: requestId: req.requestId,
2000
- - FIELD: duration: Date.now() - startTime
2001
- - DO: })
2002
- - FIELD: res.json({ data, requestId: req.requestId })
2003
- - DO: } catch (error) {
2004
- - DO: logger.errorWithContext('Failed to retrieve data', {
2005
- - FIELD: component: 'ProtectedRoutes',
2006
- - FIELD: method: 'getData',
2007
- - FIELD: userId: req.user.id,
2008
- - FIELD: requestId: req.requestId,
2009
- - FIELD: duration: Date.now() - startTime,
2010
- - FIELD: error: error.message
2011
- - DO: }, error)
2012
- - DO: res.status(500).json({
2013
- - FIELD: error: 'Internal server error',
2014
- - FIELD: requestId: req.requestId
2015
- - DO: })
2016
- - }
2017
- - DO: })
2018
- - DO: module.exports = router
2019
- OUTPUTS:
2020
- - Produces the section's intended result using equivalent logic.
1642
+ ```javascript
1643
+ // routes/protected.js
1644
+ const express = require('express')
1645
+ const { logger } = require('@rodit/rodit-auth-be')
1646
+ const router = express.Router()
1647
+ // Middleware that uses the shared client
1648
+ const authenticate = (req, res, next) => {
1649
+ const client = req.app.locals.roditClient
1650
+ if (!client) {
1651
+ res.status(503).json({ error: 'Authentication service unavailable' })
1652
+ }
1653
+ client.authenticate(req, res, next)
1654
+ }
1655
+ const authorize = (req, res, next) => {
1656
+ const client = req.app.locals.roditClient
1657
+ if (!client) {
1658
+ res.status(503).json({ error: 'Authentication service unavailable' })
1659
+ }
1660
+ client.authorize(req, res, next)
1661
+ }
1662
+ // Protected route with full authentication and authorization
1663
+ router.get('/data', authenticate, authorize, async (req, res) => {
1664
+ const startTime = Date.now()
1665
+ try {
1666
+ // Your business logic here
1667
+ const data = await processUserData(req.user.id)
1668
+ logger.infoWithContext('Data retrieved successfully', {
1669
+ component: 'ProtectedRoutes',
1670
+ method: 'getData',
1671
+ userId: req.user.id,
1672
+ requestId: req.requestId,
1673
+ duration: Date.now() - startTime
1674
+ })
1675
+ res.json({ data, requestId: req.requestId })
1676
+ } catch (error) {
1677
+ logger.errorWithContext('Failed to retrieve data', {
1678
+ component: 'ProtectedRoutes',
1679
+ method: 'getData',
1680
+ userId: req.user.id,
1681
+ requestId: req.requestId,
1682
+ duration: Date.now() - startTime,
1683
+ error: error.message
1684
+ }, error)
1685
+ res.status(500).json({
1686
+ error: 'Internal server error',
1687
+ requestId: req.requestId
1688
+ })
1689
+ }
1690
+ })
1691
+ module.exports = router
2021
1692
  ```
2022
1693
 
2023
1694
  ### Portal Authentication (Server-to-Server)
2024
1695
 
2025
1696
  For server-to-server authentication (e.g., minting client tokens):
2026
1697
 
2027
- ```text
2028
- PSEUDOCODE
2029
- INPUTS:
2030
- - Use values defined by the surrounding section/context.
2031
- STEPS:
2032
- - NOTE: routes/signclient.js
2033
- - SET router TO express.Router()
2034
- - DO: router.post('/signclient', authenticate, authorize, async (req, res) => {
2035
- - DO: const { tobesignedValues, mintingfee, mintingfeeaccount } = req.body
2036
- - SET client TO req.app.locals.roditClient
2037
- - SET logger TO client.getLogger()
2038
- - DO: try {
2039
- - NOTE: Validate requested permissions against server's permissions
2040
- - SET configObject TO await client.getConfigOwnRodit()
2041
- - SET serverPermissions TO JSON.parse(
2042
- - DO: configObject.own_rodit.metadata.permissioned_routes || '{}'
2043
- - DO: )
2044
- - SET requestedPermissions TO JSON.parse(
2045
- - DO: tobesignedValues.permissioned_routes || '{}'
2046
- - DO: )
2047
- - NOTE: Validate that all requested routes exist in server config
2048
- - NOTE: (Implementation details in actual code)
2049
- - NOTE: Authenticate to portal and mint client token
2050
- - SET port TO configObject.port || 8443
2051
- - SET result TO await client.login_portal(configObject, port)
2052
- - CHECK CONDITION: if (result.error) {
2053
- - RETURN res.status(500).json({
2054
- - FIELD: error: 'Portal authentication failed',
2055
- - FIELD: details: result.message,
2056
- - FIELD: requestId: req.requestId
2057
- - DO: })
2058
- - }
2059
- - NOTE: Sign the client token via portal
2060
- - SET signedToken TO await signPortalRodit(
2061
- - DO: port,
2062
- - DO: tobesignedValues,
2063
- - DO: mintingfee,
2064
- - DO: mintingfeeaccount,
2065
- - DO: client
2066
- - DO: )
2067
- - DO: res.json({
2068
- - DO: signedToken,
2069
- - FIELD: requestId: req.requestId
2070
- - DO: })
2071
- - DO: } catch (error) {
2072
- - DO: logger.errorWithContext('Client token minting failed', {
2073
- - FIELD: component: 'SignClient',
2074
- - FIELD: requestId: req.requestId,
2075
- - FIELD: error: error.message
2076
- - DO: }, error)
2077
- - DO: res.status(500).json({
2078
- - FIELD: error: 'Token minting failed',
2079
- - FIELD: requestId: req.requestId
2080
- - DO: })
2081
- - }
2082
- - DO: })
2083
- - DO: module.exports = router
2084
- OUTPUTS:
2085
- - Produces the section's intended result using equivalent logic.
1698
+ ```javascript
1699
+ // routes/signclient.js
1700
+ const router = express.Router()
1701
+ router.post('/signclient', authenticate, authorize, async (req, res) => {
1702
+ const { tobesignedValues, mintingfee, mintingfeeaccount } = req.body
1703
+ const client = req.app.locals.roditClient
1704
+ const logger = client.getLogger()
1705
+ try {
1706
+ // Validate requested permissions against server's permissions
1707
+ const configObject = await client.getConfigOwnRodit()
1708
+ const serverPermissions = JSON.parse(
1709
+ configObject.own_rodit.metadata.permissioned_routes || '{}'
1710
+ )
1711
+ const requestedPermissions = JSON.parse(
1712
+ tobesignedValues.permissioned_routes || '{}'
1713
+ )
1714
+ // Validate that all requested routes exist in server config
1715
+ // (Implementation details in actual code)
1716
+ // Authenticate to portal and mint client token
1717
+ const port = configObject.port || 8443
1718
+ const result = await client.login_portal(configObject, port)
1719
+ if (result.error) {
1720
+ res.status(500).json({
1721
+ error: 'Portal authentication failed',
1722
+ details: result.message,
1723
+ requestId: req.requestId
1724
+ })
1725
+ }
1726
+ // Sign the client token via portal
1727
+ const signedToken = await signPortalRodit(
1728
+ port,
1729
+ tobesignedValues,
1730
+ mintingfee,
1731
+ mintingfeeaccount,
1732
+ client
1733
+ )
1734
+ res.json({
1735
+ signedToken,
1736
+ requestId: req.requestId
1737
+ })
1738
+ } catch (error) {
1739
+ logger.errorWithContext('Client token minting failed', {
1740
+ component: 'SignClient',
1741
+ requestId: req.requestId,
1742
+ error: error.message
1743
+ }, error)
1744
+ res.status(500).json({
1745
+ error: 'Token minting failed',
1746
+ requestId: req.requestId
1747
+ })
1748
+ }
1749
+ })
1750
+ module.exports = router
2086
1751
  ```
2087
1752
 
2088
1753
  ### SignPortal URL Configuration
@@ -2144,35 +1809,23 @@ Components:
2144
1809
  - `id=` - One or more identifier components
2145
1810
 
2146
1811
  Example:
2147
- ```text
2148
- PSEUDOCODE
2149
- INPUTS:
2150
- - Use values defined by the surrounding section/context.
2151
- STEPS:
2152
- - {
2153
- - FIELD: "serviceprovider_id": "bc=near.org;sc=roditcorp-com.near;id=01K8QECHMKFVNWQ54PJ2W2GMA7;id=01K8QECHMM1214VMDHSH7JM6H8"
2154
- - }
2155
- OUTPUTS:
2156
- - Produces the section's intended result using equivalent logic.
1812
+ ```json
1813
+ {
1814
+ "serviceprovider_id": "bc=near.org;sc=roditcorp-com.near;id=01K8QECHMKFVNWQ54PJ2W2GMA7;id=01K8QECHMM1214VMDHSH7JM6H8"
1815
+ }
2157
1816
  ```
2158
1817
 
2159
1818
  #### URL Construction Method
2160
1819
 
2161
1820
  The SDK uses `roditClient.getPortalUrl(serviceProviderId, port)` to construct the SignPortal URL:
2162
1821
 
2163
- ```text
2164
- PSEUDOCODE
2165
- INPUTS:
2166
- - Use values defined by the surrounding section/context.
2167
- STEPS:
2168
- - SET client TO req.app.locals.roditClient
2169
- - SET configObject TO await client.getConfigOwnRodit()
2170
- - SET serviceProviderId TO configObject.own_rodit.metadata.serviceprovider_id
2171
- - SET portalPort TO 8443
2172
- - NOTE: Automatically constructs: https://signportal.<domain>.<tld>:8443
2173
- - SET portalUrl TO client.getPortalUrl(serviceProviderId, portalPort)
2174
- OUTPUTS:
2175
- - Produces the section's intended result using equivalent logic.
1822
+ ```javascript
1823
+ const client = req.app.locals.roditClient
1824
+ const configObject = await client.getConfigOwnRodit()
1825
+ const serviceProviderId = configObject.own_rodit.metadata.serviceprovider_id
1826
+ const portalPort = 8443
1827
+ // Automatically constructs: https://signportal.<domain>.<tld>:8443
1828
+ const portalUrl = client.getPortalUrl(serviceProviderId, portalPort)
2176
1829
  ```
2177
1830
 
2178
1831
  #### Troubleshooting
@@ -2197,169 +1850,157 @@ OUTPUTS:
2197
1850
 
2198
1851
  To verify your SignPortal URL configuration:
2199
1852
 
2200
- ```text
2201
- PSEUDOCODE
2202
- INPUTS:
2203
- - Use values defined by the surrounding section/context.
2204
- STEPS:
2205
- - SET client TO req.app.locals.roditClient
2206
- - SET logger TO client.getLogger()
2207
- - DO: try {
2208
- - SET configObject TO await client.getConfigOwnRodit()
2209
- - SET serviceProviderId TO configObject.own_rodit.metadata.serviceprovider_id
2210
- - DO: logger.info('RODiT Configuration', {
2211
- - FIELD: component: 'SignPortal',
2212
- - DO: serviceProviderId,
2213
- - FIELD: hasServiceProviderId: !!serviceProviderId
2214
- - DO: })
2215
- - CHECK CONDITION: if (serviceProviderId) {
2216
- - SET portalUrl TO client.getPortalUrl(serviceProviderId, 8443)
2217
- - DO: logger.info('SignPortal URL constructed', {
2218
- - FIELD: component: 'SignPortal',
2219
- - DO: portalUrl
2220
- - DO: })
2221
- - }
2222
- - DO: } catch (error) {
2223
- - DO: logger.error('SignPortal URL construction failed', {
2224
- - FIELD: component: 'SignPortal',
2225
- - FIELD: error: error.message
2226
- - DO: })
2227
- - }
2228
- OUTPUTS:
2229
- - Produces the section's intended result using equivalent logic.
1853
+ ```javascript
1854
+ const client = req.app.locals.roditClient
1855
+ const logger = client.getLogger()
1856
+ try {
1857
+ const configObject = await client.getConfigOwnRodit()
1858
+ const serviceProviderId = configObject.own_rodit.metadata.serviceprovider_id
1859
+ logger.info('RODiT Configuration', {
1860
+ component: 'SignPortal',
1861
+ serviceProviderId,
1862
+ hasServiceProviderId: !!serviceProviderId
1863
+ })
1864
+ if (serviceProviderId) {
1865
+ const portalUrl = client.getPortalUrl(serviceProviderId, 8443)
1866
+ logger.info('SignPortal URL constructed', {
1867
+ component: 'SignPortal',
1868
+ portalUrl
1869
+ })
1870
+ }
1871
+ } catch (error) {
1872
+ logger.error('SignPortal URL construction failed', {
1873
+ component: 'SignPortal',
1874
+ error: error.message
1875
+ })
1876
+ }
2230
1877
  ```
2231
1878
 
2232
1879
  ### CRUDA Operations Example
2233
1880
 
2234
1881
  Complete CRUD implementation with authentication, authorization, webhooks, and performance tracking:
2235
1882
 
2236
- ```text
2237
- PSEUDOCODE
2238
- INPUTS:
2239
- - Use values defined by the surrounding section/context.
2240
- STEPS:
2241
- - NOTE: protected/cruda.js
2242
- - SET express TO require('express')
2243
- - SET router TO express.Router()
2244
- - DO: const { RoditClient } = require('@rodit/rodit-auth-be')
2245
- - SET sqlite3 TO require('sqlite3')
2246
- - DO: const { open } = require('sqlite')
2247
- - DO: const { ulid } = require('ulid')
2248
- - SET sdkClient TO new RoditClient()
2249
- - SET logger TO sdkClient.getLogger()
2250
- - DO: let db
2251
- - NOTE: Initialize database
2252
- - SET initializeDatabase TO async () => {
2253
- - SET db TO await open({
2254
- - FIELD: filename: '/app/data/database.sqlite',
2255
- - FIELD: driver: sqlite3.Database
2256
- - DO: })
2257
- - WAIT FOR: db.run(`CREATE TABLE IF NOT EXISTS comments (
2258
- - DO: id INTEGER PRIMARY KEY AUTOINCREMENT,
2259
- - DO: comment TEXT NOT NULL,
2260
- - DO: author TEXT,
2261
- - DO: created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
2262
- - DO: updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
2263
- - DO: )`)
2264
- - DO: }
2265
- - NOTE: Webhook helper
2266
- - SET logAndSendWebhook TO async (payload, req) => {
2267
- - DO: try {
2268
- - SET roditClient TO req?.app?.locals?.roditClient
2269
- - CHECK CONDITION: if (!roditClient) return { success: false }
2270
- - RETURN await roditClient.send_webhook(payload, req)
2271
- - DO: } catch (error) {
2272
- - FIELD: logger.error('Webhook failed', { error: error.message })
2273
- - RETURN { success: false, error: error.message }
2274
- - }
2275
- - DO: }
2276
- - NOTE: CREATE
2277
- - DO: router.post('/create', async (req, res) => {
2278
- - DO: const { comment, author } = req.body
2279
- - SET requestId TO req.requestId || ulid()
2280
- - DO: try {
2281
- - SET result TO await db.run(
2282
- - DO: 'INSERT INTO comments (comment, author) VALUES (?, ?)',
2283
- - DO: [comment, author || req.user.roditId]
2284
- - DO: )
2285
- - NOTE: Send webhook
2286
- - WAIT FOR: logAndSendWebhook({
2287
- - FIELD: event: 'comment_created',
2288
- - FIELD: data: { id: result.lastID, comment, author },
2289
- - FIELD: isError: false
2290
- - DO: }, req)
2291
- - FIELD: res.json({ id: result.lastID, requestId })
2292
- - DO: } catch (error) {
2293
- - DO: logger.errorWithContext('Create failed', {
2294
- - FIELD: component: 'CRUDA',
2295
- - FIELD: error: error.message,
2296
- - DO: requestId
2297
- - DO: }, error)
2298
- - FIELD: res.status(500).json({ error: 'Create failed', requestId })
2299
- - }
2300
- - DO: })
2301
- - NOTE: LIST
2302
- - DO: router.post('/list', async (req, res) => {
2303
- - DO: try {
2304
- - SET records TO await db.all(
2305
- - DO: 'SELECT * FROM comments ORDER BY created_at DESC'
2306
- - DO: )
2307
- - FIELD: res.json({ records, requestId: req.requestId })
2308
- - DO: } catch (error) {
2309
- - FIELD: res.status(500).json({ error: 'List failed', requestId: req.requestId })
2310
- - }
2311
- - DO: })
2312
- - NOTE: READ
2313
- - DO: router.post('/read', async (req, res) => {
2314
- - DO: const { id } = req.body
2315
- - DO: try {
2316
- - SET record TO await db.get('SELECT * FROM comments WHERE id = ?', [id])
2317
- - CHECK CONDITION: if (!record) {
2318
- - RETURN res.status(404).json({ error: 'Not found', requestId: req.requestId })
2319
- - }
2320
- - FIELD: res.json({ record, requestId: req.requestId })
2321
- - DO: } catch (error) {
2322
- - FIELD: res.status(500).json({ error: 'Read failed', requestId: req.requestId })
2323
- - }
2324
- - DO: })
2325
- - NOTE: UPDATE
2326
- - DO: router.post('/update', async (req, res) => {
2327
- - DO: const { id, comment } = req.body
2328
- - DO: try {
2329
- - WAIT FOR: db.run(
2330
- - DO: 'UPDATE comments SET comment = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?',
2331
- - DO: [comment, id]
2332
- - DO: )
2333
- - WAIT FOR: logAndSendWebhook({
2334
- - FIELD: event: 'comment_updated',
2335
- - FIELD: data: { id, comment },
2336
- - FIELD: isError: false
2337
- - DO: }, req)
2338
- - FIELD: res.json({ success: true, requestId: req.requestId })
2339
- - DO: } catch (error) {
2340
- - FIELD: res.status(500).json({ error: 'Update failed', requestId: req.requestId })
2341
- - }
2342
- - DO: })
2343
- - NOTE: DELETE
2344
- - DO: router.post('/destroy', async (req, res) => {
2345
- - DO: const { id } = req.body
2346
- - DO: try {
2347
- - WAIT FOR: db.run('DELETE FROM comments WHERE id = ?', [id])
2348
- - WAIT FOR: logAndSendWebhook({
2349
- - FIELD: event: 'comment_deleted',
2350
- - FIELD: data: { id },
2351
- - FIELD: isError: false
2352
- - DO: }, req)
2353
- - FIELD: res.json({ success: true, requestId: req.requestId })
2354
- - DO: } catch (error) {
2355
- - FIELD: res.status(500).json({ error: 'Delete failed', requestId: req.requestId })
2356
- - }
2357
- - DO: })
2358
- - NOTE: Export initialization function
2359
- - DO: module.exports = router
2360
- - DO: module.exports.initializeDatabase = initializeDatabase
2361
- OUTPUTS:
2362
- - Produces the section's intended result using equivalent logic.
1883
+ ```javascript
1884
+ // protected/cruda.js
1885
+ const express = require('express')
1886
+ const router = express.Router()
1887
+ const { RoditClient } = require('@rodit/rodit-auth-be')
1888
+ const sqlite3 = require('sqlite3')
1889
+ const { open } = require('sqlite')
1890
+ const { ulid } = require('ulid')
1891
+ const sdkClient = new RoditClient()
1892
+ const logger = sdkClient.getLogger()
1893
+ let db
1894
+ // Initialize database
1895
+ const initializeDatabase = async () => {
1896
+ const db = await open({
1897
+ filename: '/app/data/database.sqlite',
1898
+ driver: sqlite3.Database
1899
+ })
1900
+ await db.run(`CREATE TABLE IF NOT EXISTS comments (
1901
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
1902
+ comment TEXT NOT NULL,
1903
+ author TEXT,
1904
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
1905
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
1906
+ )`)
1907
+ }
1908
+ // Webhook helper
1909
+ const logAndSendWebhook = async (payload, req) => {
1910
+ try {
1911
+ const roditClient = req?.app?.locals?.roditClient
1912
+ if (!roditClient) return { success: false }
1913
+ await roditClient.send_webhook(payload, req)
1914
+ } catch (error) {
1915
+ logger.error('Webhook failed', { error: error.message })
1916
+ { success: false, error: error.message }
1917
+ }
1918
+ }
1919
+ // CREATE
1920
+ router.post('/create', async (req, res) => {
1921
+ const { comment, author } = req.body
1922
+ const requestId = req.requestId || ulid()
1923
+ try {
1924
+ const result = await db.run(
1925
+ 'INSERT INTO comments (comment, author) VALUES (?, ?)',
1926
+ [comment, author || req.user.roditId]
1927
+ )
1928
+ // Send webhook
1929
+ await logAndSendWebhook({
1930
+ event: 'comment_created',
1931
+ data: { id: result.lastID, comment, author },
1932
+ isError: false
1933
+ }, req)
1934
+ res.json({ id: result.lastID, requestId })
1935
+ } catch (error) {
1936
+ logger.errorWithContext('Create failed', {
1937
+ component: 'CRUDA',
1938
+ error: error.message,
1939
+ requestId
1940
+ }, error)
1941
+ res.status(500).json({ error: 'Create failed', requestId })
1942
+ }
1943
+ })
1944
+ // LIST
1945
+ router.post('/list', async (req, res) => {
1946
+ try {
1947
+ const records = await db.all(
1948
+ 'SELECT * FROM comments ORDER BY created_at DESC'
1949
+ )
1950
+ res.json({ records, requestId: req.requestId })
1951
+ } catch (error) {
1952
+ res.status(500).json({ error: 'List failed', requestId: req.requestId })
1953
+ }
1954
+ })
1955
+ // READ
1956
+ router.post('/read', async (req, res) => {
1957
+ const { id } = req.body
1958
+ try {
1959
+ const record = await db.get('SELECT * FROM comments WHERE id = ?', [id])
1960
+ if (!record) {
1961
+ res.status(404).json({ error: 'Not found', requestId: req.requestId })
1962
+ }
1963
+ res.json({ record, requestId: req.requestId })
1964
+ } catch (error) {
1965
+ res.status(500).json({ error: 'Read failed', requestId: req.requestId })
1966
+ }
1967
+ })
1968
+ // UPDATE
1969
+ router.post('/update', async (req, res) => {
1970
+ const { id, comment } = req.body
1971
+ try {
1972
+ await db.run(
1973
+ 'UPDATE comments SET comment = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?',
1974
+ [comment, id]
1975
+ )
1976
+ await logAndSendWebhook({
1977
+ event: 'comment_updated',
1978
+ data: { id, comment },
1979
+ isError: false
1980
+ }, req)
1981
+ res.json({ success: true, requestId: req.requestId })
1982
+ } catch (error) {
1983
+ res.status(500).json({ error: 'Update failed', requestId: req.requestId })
1984
+ }
1985
+ })
1986
+ // DELETE
1987
+ router.post('/destroy', async (req, res) => {
1988
+ const { id } = req.body
1989
+ try {
1990
+ await db.run('DELETE FROM comments WHERE id = ?', [id])
1991
+ await logAndSendWebhook({
1992
+ event: 'comment_deleted',
1993
+ data: { id },
1994
+ isError: false
1995
+ }, req)
1996
+ res.json({ success: true, requestId: req.requestId })
1997
+ } catch (error) {
1998
+ res.status(500).json({ error: 'Delete failed', requestId: req.requestId })
1999
+ }
2000
+ })
2001
+ // Export initialization function
2002
+ module.exports = router
2003
+ module.exports.initializeDatabase = initializeDatabase
2363
2004
  ```
2364
2005
 
2365
2006
  ## API Reference
@@ -2374,16 +2015,10 @@ The main client class for all RODiT operations.
2374
2015
 
2375
2016
  Create and initialize a RODiT client in one step.
2376
2017
 
2377
- ```text
2378
- PSEUDOCODE
2379
- INPUTS:
2380
- - Use values defined by the surrounding section/context.
2381
- STEPS:
2382
- - SET client TO await RoditClient.create('server'); // For server applications
2383
- - SET client TO await RoditClient.create('client'); // For client applications
2384
- - SET client TO await RoditClient.create('portal'); // For portal authentication
2385
- OUTPUTS:
2386
- - Produces the section's intended result using equivalent logic.
2018
+ ```javascript
2019
+ const client = await RoditClient.create('server'); // For server applications
2020
+ const client = await RoditClient.create('client'); // For client applications
2021
+ const client = await RoditClient.create('portal'); // For portal authentication
2387
2022
  ```
2388
2023
 
2389
2024
  **Parameters:**
@@ -2399,15 +2034,9 @@ OUTPUTS:
2399
2034
 
2400
2035
  Express middleware for authenticating API requests. Validates JWT tokens and populates `req.user`.
2401
2036
 
2402
- ```text
2403
- PSEUDOCODE
2404
- INPUTS:
2405
- - Use values defined by the surrounding section/context.
2406
- STEPS:
2407
- - SET authenticate TO (req, res, next) => roditClient.authenticate(req, res, next)
2408
- - DO: app.use('/api/protected', authenticate, handler)
2409
- OUTPUTS:
2410
- - Produces the section's intended result using equivalent logic.
2037
+ ```javascript
2038
+ const authenticate = (req, res, next) => roditClient.authenticate(req, res, next)
2039
+ app.use('/api/protected', authenticate, handler)
2411
2040
  ```
2412
2041
 
2413
2042
  **Validates:**
@@ -2424,15 +2053,9 @@ OUTPUTS:
2424
2053
  Express middleware for logout routes. It validates signature and claims like normal auth, but allows
2425
2054
  signature-valid expired JWT tokens so sessions can still be closed safely.
2426
2055
 
2427
- ```text
2428
- PSEUDOCODE
2429
- INPUTS:
2430
- - Use values defined by the surrounding section/context.
2431
- STEPS:
2432
- - SET authenticateLogout TO (req, res, next) => roditClient.authenticateForLogout(req, res, next)
2433
- - DO: app.post('/api/logout', authenticateLogout, (req, res) => roditClient.logout_client(req, res))
2434
- OUTPUTS:
2435
- - Produces the section's intended result using equivalent logic.
2056
+ ```javascript
2057
+ const authenticateLogout = (req, res, next) => roditClient.authenticateForLogout(req, res, next)
2058
+ app.post('/api/logout', authenticateLogout, (req, res) => roditClient.logout_client(req, res))
2436
2059
  ```
2437
2060
 
2438
2061
  **Use case:** clean logout when token is expired but cryptographically valid.
@@ -2441,15 +2064,9 @@ OUTPUTS:
2441
2064
 
2442
2065
  Express middleware for validating route permissions. Must be used after `authenticate`.
2443
2066
 
2444
- ```text
2445
- PSEUDOCODE
2446
- INPUTS:
2447
- - Use values defined by the surrounding section/context.
2448
- STEPS:
2449
- - SET authorize TO (req, res, next) => roditClient.authorize(req, res, next)
2450
- - DO: app.use('/api/admin', authenticate, authorize, handler)
2451
- OUTPUTS:
2452
- - Produces the section's intended result using equivalent logic.
2067
+ ```javascript
2068
+ const authorize = (req, res, next) => roditClient.authorize(req, res, next)
2069
+ app.use('/api/admin', authenticate, authorize, handler)
2453
2070
  ```
2454
2071
 
2455
2072
  **Validates:** User has permission for the requested route and HTTP method
@@ -2458,113 +2075,84 @@ OUTPUTS:
2458
2075
 
2459
2076
  Handle Express login requests from clients. Validates RODiT credentials and issues JWT token.
2460
2077
 
2461
- ```text
2462
- PSEUDOCODE
2463
- INPUTS:
2464
- - Use values defined by the surrounding section/context.
2465
- STEPS:
2466
- - DO: app.post('/api/login', (req, res) => roditClient.login_client(req, res))
2467
- OUTPUTS:
2468
- - Produces the section's intended result using equivalent logic.
2078
+ ```javascript
2079
+ app.post('/api/login', (req, res) => roditClient.login_client(req, res))
2469
2080
  ```
2470
2081
 
2471
2082
  **Request Body:** `login_client` accepts `accountid`, `timestamp`, and `base64url_signature`.
2472
2083
 
2473
2084
  **Response:**
2474
- ```text
2475
- PSEUDOCODE
2476
- INPUTS:
2477
- - Use values defined by the surrounding section/context.
2478
- STEPS:
2479
- - {
2480
- - FIELD: jwt_token: '<jwt-token>',
2481
- - FIELD: requestId: '01HQXYZ...'
2482
- - }
2483
- OUTPUTS:
2484
- - Produces the section's intended result using equivalent logic.
2085
+ ```javascript
2086
+ {
2087
+ jwt_token: '<jwt-token>',
2088
+ requestId: '01HQXYZ...'
2089
+ }
2485
2090
  ```
2486
2091
 
2487
2092
  ##### logout_client(req, res)
2488
2093
 
2489
2094
  Handle Express logout requests. Closes session and invalidates JWT token.
2490
2095
 
2491
- ```text
2492
- PSEUDOCODE
2493
- INPUTS:
2494
- - Use values defined by the surrounding section/context.
2495
- STEPS:
2496
- - SET authenticateLogout TO (req, res, next) => roditClient.authenticateForLogout(req, res, next)
2497
- - DO: app.post('/api/logout', authenticateLogout, (req, res) => {
2498
- - RETURN roditClient.logout_client(req, res)
2499
- - DO: })
2500
- OUTPUTS:
2501
- - Produces the section's intended result using equivalent logic.
2096
+ ```javascript
2097
+ const authenticateLogout = (req, res, next) => roditClient.authenticateForLogout(req, res, next)
2098
+ app.post('/api/logout', authenticateLogout, (req, res) => {
2099
+ roditClient.logout_client(req, res)
2100
+ })
2502
2101
  ```
2503
2102
 
2504
2103
  **Response:**
2505
- ```text
2506
- PSEUDOCODE
2507
- INPUTS:
2508
- - Use values defined by the surrounding section/context.
2509
- STEPS:
2510
- - {
2511
- - FIELD: message: 'Logout successful',
2512
- - FIELD: terminationToken: '<jwt-token>', // Short-lived token
2513
- - FIELD: requestId: '01HQXYZ...'
2514
- - }
2515
- OUTPUTS:
2516
- - Produces the section's intended result using equivalent logic.
2104
+ ```javascript
2105
+ {
2106
+ message: 'Logout successful',
2107
+ terminationToken: '<jwt-token>', // Short-lived token
2108
+ requestId: '01HQXYZ...'
2109
+ }
2517
2110
  ```
2518
2111
 
2519
2112
  ##### login_portal(configObject, port)
2520
2113
 
2521
2114
  Authenticate to RODiT portal for server-to-server operations using account-based login payloads.
2522
2115
 
2523
- ```text
2524
- PSEUDOCODE
2525
- INPUTS:
2526
- - Use values defined by the surrounding section/context.
2527
- STEPS:
2528
- - SET configObject TO await roditClient.getConfigOwnRodit()
2529
- - SET result TO await roditClient.login_portal(configObject, 8443)
2530
- OUTPUTS:
2531
- - Produces the section's intended result using equivalent logic.
2116
+ ```javascript
2117
+ const configObject = await roditClient.getConfigOwnRodit()
2118
+ const result = await roditClient.login_portal(configObject, 8443)
2532
2119
  ```
2533
2120
 
2534
2121
  **Returns:** `Promise<Object>` - Portal authentication result
2535
2122
 
2536
2123
  ##### login_server(options)
2537
2124
 
2538
- Authenticate to a peer API using account-based login semantics: sign **`accountid + timestamp_iso`** and POST **`{ accountid, timestamp, base64url_signature }`**.
2125
+ Authenticate to a peer API (home or federated). Signs **`roditid`** or **`accountid`** + canonical `timestamp_iso` and POSTs the login body expected by the peer's `login_client`.
2539
2126
 
2540
- Optional: `options.timestamp`, `options.loginPath`.
2127
+ **Options:**
2541
2128
 
2542
- ```text
2543
- PSEUDOCODE
2544
- INPUTS:
2545
- - Use values defined by the surrounding section/context.
2546
- STEPS:
2547
- - SET result TO await roditClient.login_server({
2548
- - FIELD: loginPath: '/api/login' // optional; default shown
2549
- - DO: })
2550
- OUTPUTS:
2551
- - Produces the section's intended result using equivalent logic.
2129
+ | Option | Description |
2130
+ |--------|-------------|
2131
+ | `apiEndpoint` | Federated (or override) API base URL. Defaults to own `subjectuniqueidentifier_url`. MITM-checked via JWT `rodit_subjectuniqueidentifier_url` when it differs from client home. |
2132
+ | `loginPath` | HTTP path (default `/api/login`) |
2133
+ | `timestamp` | Unix seconds; if omitted, fetched from peer `GET /api/login/timestamp` |
2134
+ | `accountId` | NEAR account when signing without `own_rodit.token_id` |
2135
+ | `timestampPath` | Timestamp path (default `/api/login/timestamp`) |
2136
+
2137
+ ```javascript
2138
+ // Same-API (home)
2139
+ const home = await roditClient.login_server();
2140
+
2141
+ // Federated login into a sibling API in the same family
2142
+ const fed = await roditClient.login_server({
2143
+ apiEndpoint: 'https://api-b.example.com',
2144
+ loginPath: '/api/login', // optional
2145
+ });
2552
2146
  ```
2553
2147
 
2554
- **Returns:** `Promise<Object>` - Authentication result with `jwt_token`
2148
+ **Returns:** `Promise<Object>` - Authentication result with `jwt_token`, or `{ error, errorCode, failureReason, requestId }` on failure (`FEDERATED_ISSUER_MISSING` / `FEDERATED_ISSUER_MISMATCH` for MITM failures).
2555
2149
 
2556
2150
  ##### logout_server()
2557
2151
 
2558
2152
  Logout from server-to-server session.
2559
2153
 
2560
- ```text
2561
- PSEUDOCODE
2562
- INPUTS:
2563
- - Use values defined by the surrounding section/context.
2564
- STEPS:
2565
- - SET result TO await roditClient.logout_server()
2566
- OUTPUTS:
2567
- - Produces the section's intended result using equivalent logic.
2154
+ ```javascript
2155
+ const result = await roditClient.logout_server()
2568
2156
  ```
2569
2157
 
2570
2158
  **Returns:** `Promise<Object>` - Logout result with session closure status
@@ -2573,60 +2161,42 @@ OUTPUTS:
2573
2161
 
2574
2162
  Get the complete RODiT configuration including token metadata.
2575
2163
 
2576
- ```text
2577
- PSEUDOCODE
2578
- INPUTS:
2579
- - Use values defined by the surrounding section/context.
2580
- STEPS:
2581
- - SET configObject TO await roditClient.getConfigOwnRodit()
2582
- - SET metadata TO configObject.own_rodit.metadata
2583
- - SET tokenId TO configObject.own_rodit.token_id
2584
- OUTPUTS:
2585
- - Produces the section's intended result using equivalent logic.
2164
+ ```javascript
2165
+ const configObject = await roditClient.getConfigOwnRodit()
2166
+ const metadata = configObject.own_rodit.metadata
2167
+ const tokenId = configObject.own_rodit.token_id
2586
2168
  ```
2587
2169
 
2588
2170
  **Returns:** `Promise<Object>` - Complete RODiT configuration
2589
2171
 
2590
2172
  **Structure:**
2591
- ```text
2592
- PSEUDOCODE
2593
- INPUTS:
2594
- - Use values defined by the surrounding section/context.
2595
- STEPS:
2596
- - {
2597
- - FIELD: own_rodit: {
2598
- - FIELD: token_id: string,
2599
- - FIELD: metadata: {
2600
- - FIELD: jwt_duration: number,
2601
- - FIELD: max_requests: string,
2602
- - FIELD: maxrq_window: string,
2603
- - FIELD: permissioned_routes: string, // JSON string
2604
- - FIELD: subjectuniqueidentifier_url: string,
2605
- - FIELD: webhook_url: string,
2606
- - NOTE: ... other metadata fields
2607
- - }
2608
- - DO: },
2609
- - FIELD: port: number
2610
- - }
2611
- OUTPUTS:
2612
- - Produces the section's intended result using equivalent logic.
2173
+ ```javascript
2174
+ {
2175
+ own_rodit: {
2176
+ token_id: string,
2177
+ metadata: {
2178
+ jwt_duration: number,
2179
+ max_requests: string,
2180
+ maxrq_window: string,
2181
+ permissioned_routes: string, // JSON string
2182
+ subjectuniqueidentifier_url: string,
2183
+ webhook_url: string,
2184
+ // ... other metadata fields
2185
+ }
2186
+ },
2187
+ port: number
2188
+ }
2613
2189
  ```
2614
2190
 
2615
2191
  ##### isOperationPermitted(method, path)
2616
2192
 
2617
2193
  Check if an operation is permitted based on token permissions.
2618
2194
 
2619
- ```text
2620
- PSEUDOCODE
2621
- INPUTS:
2622
- - Use values defined by the surrounding section/context.
2623
- STEPS:
2624
- - SET hasPermission TO roditClient.isOperationPermitted('POST', '/api/admin/users')
2625
- - CHECK CONDITION: if (!hasPermission) {
2626
- - RETURN res.status(403).json({ error: 'Forbidden' })
2627
- - }
2628
- OUTPUTS:
2629
- - Produces the section's intended result using equivalent logic.
2195
+ ```javascript
2196
+ const hasPermission = roditClient.isOperationPermitted('POST', '/api/admin/users')
2197
+ if (!hasPermission) {
2198
+ res.status(403).json({ error: 'Forbidden' })
2199
+ }
2630
2200
  ```
2631
2201
 
2632
2202
  **Parameters:**
@@ -2639,14 +2209,8 @@ OUTPUTS:
2639
2209
 
2640
2210
  Get the authentication state manager.
2641
2211
 
2642
- ```text
2643
- PSEUDOCODE
2644
- INPUTS:
2645
- - Use values defined by the surrounding section/context.
2646
- STEPS:
2647
- - SET stateManager TO roditClient.getStateManager()
2648
- OUTPUTS:
2649
- - Produces the section's intended result using equivalent logic.
2212
+ ```javascript
2213
+ const stateManager = roditClient.getStateManager()
2650
2214
  ```
2651
2215
 
2652
2216
  **Returns:** `AuthStateManager` instance
@@ -2655,15 +2219,9 @@ OUTPUTS:
2655
2219
 
2656
2220
  Get the RODiT manager for credential operations.
2657
2221
 
2658
- ```text
2659
- PSEUDOCODE
2660
- INPUTS:
2661
- - Use values defined by the surrounding section/context.
2662
- STEPS:
2663
- - SET roditManager TO roditClient.getRoditManager()
2664
- - SET credentials TO await roditManager.getCredentials('server')
2665
- OUTPUTS:
2666
- - Produces the section's intended result using equivalent logic.
2222
+ ```javascript
2223
+ const roditManager = roditClient.getRoditManager()
2224
+ const credentials = await roditManager.getCredentials('server')
2667
2225
  ```
2668
2226
 
2669
2227
  **Returns:** `RoditManager` instance
@@ -2672,15 +2230,9 @@ OUTPUTS:
2672
2230
 
2673
2231
  Get the session manager.
2674
2232
 
2675
- ```text
2676
- PSEUDOCODE
2677
- INPUTS:
2678
- - Use values defined by the surrounding section/context.
2679
- STEPS:
2680
- - SET sessionManager TO roditClient.getSessionManager()
2681
- - SET activeCount TO await sessionManager.getActiveSessionCount()
2682
- OUTPUTS:
2683
- - Produces the section's intended result using equivalent logic.
2233
+ ```javascript
2234
+ const sessionManager = roditClient.getSessionManager()
2235
+ const activeCount = await sessionManager.getActiveSessionCount()
2684
2236
  ```
2685
2237
 
2686
2238
  **Returns:** `SessionManager` instance
@@ -2689,15 +2241,9 @@ OUTPUTS:
2689
2241
 
2690
2242
  Get the logger instance.
2691
2243
 
2692
- ```text
2693
- PSEUDOCODE
2694
- INPUTS:
2695
- - Use values defined by the surrounding section/context.
2696
- STEPS:
2697
- - SET logger TO roditClient.getLogger()
2698
- - FIELD: logger.info('Message', { component: 'MyComponent' })
2699
- OUTPUTS:
2700
- - Produces the section's intended result using equivalent logic.
2244
+ ```javascript
2245
+ const logger = roditClient.getLogger()
2246
+ logger.info('Message', { component: 'MyComponent' })
2701
2247
  ```
2702
2248
 
2703
2249
  **Returns:** `Logger` instance
@@ -2706,15 +2252,9 @@ OUTPUTS:
2706
2252
 
2707
2253
  Get the logging middleware.
2708
2254
 
2709
- ```text
2710
- PSEUDOCODE
2711
- INPUTS:
2712
- - Use values defined by the surrounding section/context.
2713
- STEPS:
2714
- - SET loggingmw TO roditClient.getLoggingMiddleware()
2715
- - DO: app.use(loggingmw)
2716
- OUTPUTS:
2717
- - Produces the section's intended result using equivalent logic.
2255
+ ```javascript
2256
+ const loggingmw = roditClient.getLoggingMiddleware()
2257
+ app.use(loggingmw)
2718
2258
  ```
2719
2259
 
2720
2260
  **Returns:** Express middleware function
@@ -2723,16 +2263,10 @@ OUTPUTS:
2723
2263
 
2724
2264
  Get the rate limiting middleware factory.
2725
2265
 
2726
- ```text
2727
- PSEUDOCODE
2728
- INPUTS:
2729
- - Use values defined by the surrounding section/context.
2730
- STEPS:
2731
- - SET ratelimitmw TO roditClient.getRateLimitMiddleware()
2732
- - SET limiter TO ratelimitmw(100, 900); // 100 requests per 15 minutes
2733
- - DO: app.use(limiter)
2734
- OUTPUTS:
2735
- - Produces the section's intended result using equivalent logic.
2266
+ ```javascript
2267
+ const ratelimitmw = roditClient.getRateLimitMiddleware()
2268
+ const limiter = ratelimitmw(100, 900); // 100 requests per 15 minutes
2269
+ app.use(limiter)
2736
2270
  ```
2737
2271
 
2738
2272
  **Parameters:**
@@ -2745,16 +2279,10 @@ OUTPUTS:
2745
2279
 
2746
2280
  Get the performance tracking service.
2747
2281
 
2748
- ```text
2749
- PSEUDOCODE
2750
- INPUTS:
2751
- - Use values defined by the surrounding section/context.
2752
- STEPS:
2753
- - SET performanceService TO roditClient.getPerformanceService()
2754
- - DO: performanceService.recordRequest(req)
2755
- - FIELD: performanceService.recordMetric('operation_duration', 150, { operation: 'db_query' })
2756
- OUTPUTS:
2757
- - Produces the section's intended result using equivalent logic.
2282
+ ```javascript
2283
+ const performanceService = roditClient.getPerformanceService()
2284
+ performanceService.recordRequest(req)
2285
+ performanceService.recordMetric('operation_duration', 150, { operation: 'db_query' })
2758
2286
  ```
2759
2287
 
2760
2288
  **Returns:** `PerformanceService` instance
@@ -2763,15 +2291,9 @@ OUTPUTS:
2763
2291
 
2764
2292
  Get the configuration service.
2765
2293
 
2766
- ```text
2767
- PSEUDOCODE
2768
- INPUTS:
2769
- - Use values defined by the surrounding section/context.
2770
- STEPS:
2771
- - SET config TO roditClient.getConfig()
2772
- - SET dbPath TO config.get('API_DEFAULT_OPTIONS.DB_PATH')
2773
- OUTPUTS:
2774
- - Produces the section's intended result using equivalent logic.
2294
+ ```javascript
2295
+ const config = roditClient.getConfig()
2296
+ const dbPath = config.get('API_DEFAULT_OPTIONS.DB_PATH')
2775
2297
  ```
2776
2298
 
2777
2299
  **Returns:** `Config` instance
@@ -2780,14 +2302,8 @@ OUTPUTS:
2780
2302
 
2781
2303
  Get the webhook handler.
2782
2304
 
2783
- ```text
2784
- PSEUDOCODE
2785
- INPUTS:
2786
- - Use values defined by the surrounding section/context.
2787
- STEPS:
2788
- - SET webhookHandler TO roditClient.getWebhookHandler()
2789
- OUTPUTS:
2790
- - Produces the section's intended result using equivalent logic.
2305
+ ```javascript
2306
+ const webhookHandler = roditClient.getWebhookHandler()
2791
2307
  ```
2792
2308
 
2793
2309
  **Returns:** `WebhookHandler` instance
@@ -2796,18 +2312,12 @@ OUTPUTS:
2796
2312
 
2797
2313
  Send a webhook notification.
2798
2314
 
2799
- ```text
2800
- PSEUDOCODE
2801
- INPUTS:
2802
- - Use values defined by the surrounding section/context.
2803
- STEPS:
2804
- - SET result TO await roditClient.send_webhook({
2805
- - FIELD: event: 'user_action',
2806
- - FIELD: data: { userId: '123', action: 'login' },
2807
- - FIELD: isError: false
2808
- - DO: }, req)
2809
- OUTPUTS:
2810
- - Produces the section's intended result using equivalent logic.
2315
+ ```javascript
2316
+ const result = await roditClient.send_webhook({
2317
+ event: 'user_action',
2318
+ data: { userId: '123', action: 'login' },
2319
+ isError: false
2320
+ }, req)
2811
2321
  ```
2812
2322
 
2813
2323
  **Parameters:**
@@ -2823,46 +2333,44 @@ OUTPUTS:
2823
2333
 
2824
2334
  The SDK exports these components for direct use:
2825
2335
 
2826
- ```text
2827
- PSEUDOCODE
2828
- INPUTS:
2829
- - Use values defined by the surrounding section/context.
2830
- STEPS:
2831
- - DO: const {
2832
- - DO: RoditClient, // Main client class
2833
- - DO: logger, // Logger instance
2834
- - DO: stateManager, // Authentication state manager
2835
- - DO: roditManager, // RODiT credential manager
2836
- - DO: sessionManager, // Session manager instance
2837
- - DO: blockchainService, // Blockchain operations
2838
- - DO: utils, // Utility functions
2839
- - DO: config, // Configuration service
2840
- - DO: performanceService, // Performance tracking
2841
- - DO: authenticate_apicall, // Authentication middleware
2842
- - DO: authenticate_logout, // Logout authentication middleware (expired-token tolerant)
2843
- - DO: login_client, // Login handler
2844
- - DO: logout_client, // Logout handler
2845
- - DO: login_client_withnep413, // NEP-413 login
2846
- - DO: login_portal, // Portal authentication
2847
- - DO: login_server, // Outbound peer login
2848
- - DO: logout_server, // Server logout
2849
- - DO: validate_jwt_token_be, // JWT validation
2850
- - DO: generate_jwt_token, // JWT generation
2851
- - DO: validatepermissions, // Permission middleware
2852
- - DO: webhookHandler, // Webhook handler
2853
- - DO: versioningMiddleware, // API versioning
2854
- - DO: loggingmw, // Logging middleware
2855
- - DO: ratelimitmw, // Rate limiting middleware
2856
- - DO: versionManager, // Version manager
2857
- - DO: VersionManager, // Version manager class
2858
- - DO: nearorg_rpc_timestamp // Blockchain RPC timestamp function
2859
- - DO: } = require('@rodit/rodit-auth-be')
2860
- - NOTE: Note: Session storage configuration functions are available via:
2861
- - NOTE: const { setExpressSessionStore, configureStorageFromConfig,
2862
- - NOTE: createExpressSessionMiddleware, InMemorySessionStorage }
2863
- - NOTE: = require('@rodit/rodit-auth-be/lib/auth/sessionmanager');
2864
- OUTPUTS:
2865
- - Produces the section's intended result using equivalent logic.
2336
+ ```javascript
2337
+ const {
2338
+ RoditClient, // Main client class
2339
+ logger, // Logger instance
2340
+ stateManager, // Authentication state manager
2341
+ roditManager, // RODiT credential manager
2342
+ sessionManager, // Session manager instance
2343
+ blockchainService, // Blockchain operations
2344
+ utils, // Utility functions
2345
+ config, // Configuration service
2346
+ performanceService, // Performance tracking
2347
+ authenticate_apicall, // Authentication middleware
2348
+ authenticate_logout, // Logout authentication middleware (expired-token tolerant)
2349
+ login_client, // Login handler
2350
+ logout_client, // Logout handler
2351
+ login_client_withnep413, // NEP-413 login
2352
+ login_portal, // Portal authentication
2353
+ login_server, // Outbound peer login
2354
+ logout_server, // Server logout
2355
+ validate_jwt_token_be, // JWT validation
2356
+ generate_jwt_token, // JWT generation
2357
+ normalizeUrlWithoutPort, // URL compare helper (strip port)
2358
+ isNonEmptyUrlClaim, // federated claim present & non-empty
2359
+ isFederatedRoditLogin, // peer vs own subject URL differs
2360
+ validateFederatedLoginTarget, // client MITM check after JWT crypto
2361
+ validatepermissions, // Permission middleware
2362
+ webhookHandler, // Webhook handler
2363
+ versioningMiddleware, // API versioning
2364
+ loggingmw, // Logging middleware
2365
+ ratelimitmw, // Rate limiting middleware
2366
+ versionManager, // Version manager
2367
+ VersionManager, // Version manager class
2368
+ nearorg_rpc_timestamp // Blockchain RPC timestamp function
2369
+ } = require('@rodit/rodit-auth-be')
2370
+ // Note: Session storage configuration functions are available via:
2371
+ // const { setExpressSessionStore, configureStorageFromConfig,
2372
+ // createExpressSessionMiddleware, InMemorySessionStorage }
2373
+ // = require('@rodit/rodit-auth-be/lib/auth/sessionmanager');
2866
2374
  ```
2867
2375
 
2868
2376
  ### RODiT Token Metadata Fields
@@ -2894,319 +2402,265 @@ When you call `roditClient.getConfigOwnRodit()`, you get access to these metadat
2894
2402
 
2895
2403
  Always initialize the RoditClient once in your main application file:
2896
2404
 
2897
- ```text
2898
- PSEUDOCODE
2899
- INPUTS:
2900
- - Use values defined by the surrounding section/context.
2901
- STEPS:
2902
- - NOTE: ✅ Good - Single initialization
2903
- - DO: async function startServer() {
2904
- - SET roditClient TO await RoditClient.create('server')
2905
- - DO: app.locals.roditClient = roditClient
2906
- - NOTE: Mount protected routes AFTER client initialization
2907
- - SET authenticate TO (req, res, next) => roditClient.authenticate(req, res, next)
2908
- - SET authorize TO (req, res, next) => roditClient.authorize(req, res, next)
2909
- - DO: app.use('/api/echo', authenticate, echoRoutes)
2910
- - DO: app.use('/api/cruda', authenticate, authorize, crudaRoutes)
2911
- - NOTE: ... rest of server setup
2912
- - }
2913
- - NOTE: ❌ Bad - Multiple initializations
2914
- - DO: app.get('/route1', async (req, res) => {
2915
- - SET client TO await RoditClient.create('server'); // Don't do this
2916
- - DO: })
2917
- OUTPUTS:
2918
- - Produces the section's intended result using equivalent logic.
2405
+ ```javascript
2406
+ // ✅ Good - Single initialization
2407
+ async function startServer() {
2408
+ const roditClient = await RoditClient.create('server')
2409
+ app.locals.roditClient = roditClient
2410
+ // Mount protected routes AFTER client initialization
2411
+ const authenticate = (req, res, next) => roditClient.authenticate(req, res, next)
2412
+ const authorize = (req, res, next) => roditClient.authorize(req, res, next)
2413
+ app.use('/api/echo', authenticate, echoRoutes)
2414
+ app.use('/api/cruda', authenticate, authorize, crudaRoutes)
2415
+ // ... rest of server setup
2416
+ }
2417
+ // ❌ Bad - Multiple initializations
2418
+ app.get('/route1', async (req, res) => {
2419
+ const client = await RoditClient.create('server'); // Don't do this
2420
+ })
2919
2421
  ```
2920
2422
 
2921
2423
  ### 2. Use App.locals for Shared Access
2922
2424
 
2923
2425
  Store the client in `app.locals` for access across all routes:
2924
2426
 
2925
- ```text
2926
- PSEUDOCODE
2927
- INPUTS:
2928
- - Use values defined by the surrounding section/context.
2929
- STEPS:
2930
- - NOTE: ✅ Good - Shared instance via app.locals
2931
- - SET router TO express.Router()
2932
- - DO: router.get('/data', (req, res) => {
2933
- - SET client TO req.app.locals.roditClient
2934
- - SET logger TO client.getLogger()
2935
- - DO: logger.info('Processing request', {
2936
- - FIELD: component: 'DataRoute',
2937
- - FIELD: userId: req.user?.id,
2938
- - FIELD: requestId: req.requestId
2939
- - DO: })
2940
- - FIELD: res.json({ data: 'example' })
2941
- - DO: })
2942
- - NOTE: ❌ Bad - Creating new instances in routes
2943
- - DO: const { RoditClient } = require('@rodit/rodit-auth-be')
2944
- - SET client TO new RoditClient(); // Don't do this in routes
2945
- OUTPUTS:
2946
- - Produces the section's intended result using equivalent logic.
2427
+ ```javascript
2428
+ // ✅ Good - Shared instance via app.locals
2429
+ const router = express.Router()
2430
+ router.get('/data', (req, res) => {
2431
+ const client = req.app.locals.roditClient
2432
+ const logger = client.getLogger()
2433
+ logger.info('Processing request', {
2434
+ component: 'DataRoute',
2435
+ userId: req.user?.id,
2436
+ requestId: req.requestId
2437
+ })
2438
+ res.json({ data: 'example' })
2439
+ })
2440
+ // ❌ Bad - Creating new instances in routes
2441
+ const { RoditClient } = require('@rodit/rodit-auth-be')
2442
+ const client = new RoditClient(); // Don't do this in routes
2947
2443
  ```
2948
2444
 
2949
2445
  ### 3. Proper Error Handling
2950
2446
 
2951
2447
  Always wrap SDK operations in try-catch blocks and include request context:
2952
2448
 
2953
- ```text
2954
- PSEUDOCODE
2955
- INPUTS:
2956
- - Use values defined by the surrounding section/context.
2957
- STEPS:
2958
- - NOTE: ✅ Good - Comprehensive error handling
2959
- - DO: app.get('/api/data', authenticate, async (req, res) => {
2960
- - SET startTime TO Date.now()
2961
- - SET client TO req.app.locals.roditClient
2962
- - SET logger TO client.getLogger()
2963
- - DO: try {
2964
- - SET data TO await processData(req.user.id)
2965
- - DO: logger.infoWithContext('Request successful', {
2966
- - FIELD: component: 'API',
2967
- - FIELD: method: 'getData',
2968
- - FIELD: userId: req.user.id,
2969
- - FIELD: requestId: req.requestId,
2970
- - FIELD: duration: Date.now() - startTime
2971
- - DO: })
2972
- - FIELD: res.json({ data, requestId: req.requestId })
2973
- - DO: } catch (error) {
2974
- - DO: logger.errorWithContext('Request failed', {
2975
- - FIELD: component: 'API',
2976
- - FIELD: method: 'getData',
2977
- - FIELD: userId: req.user.id,
2978
- - FIELD: requestId: req.requestId,
2979
- - FIELD: duration: Date.now() - startTime,
2980
- - FIELD: error: error.message
2981
- - DO: }, error)
2982
- - DO: res.status(500).json({
2983
- - FIELD: error: 'Internal server error',
2984
- - FIELD: requestId: req.requestId
2985
- - DO: })
2986
- - }
2987
- - DO: })
2988
- OUTPUTS:
2989
- - Produces the section's intended result using equivalent logic.
2449
+ ```javascript
2450
+ // ✅ Good - Comprehensive error handling
2451
+ app.get('/api/data', authenticate, async (req, res) => {
2452
+ const startTime = Date.now()
2453
+ const client = req.app.locals.roditClient
2454
+ const logger = client.getLogger()
2455
+ try {
2456
+ const data = await processData(req.user.id)
2457
+ logger.infoWithContext('Request successful', {
2458
+ component: 'API',
2459
+ method: 'getData',
2460
+ userId: req.user.id,
2461
+ requestId: req.requestId,
2462
+ duration: Date.now() - startTime
2463
+ })
2464
+ res.json({ data, requestId: req.requestId })
2465
+ } catch (error) {
2466
+ logger.errorWithContext('Request failed', {
2467
+ component: 'API',
2468
+ method: 'getData',
2469
+ userId: req.user.id,
2470
+ requestId: req.requestId,
2471
+ duration: Date.now() - startTime,
2472
+ error: error.message
2473
+ }, error)
2474
+ res.status(500).json({
2475
+ error: 'Internal server error',
2476
+ requestId: req.requestId
2477
+ })
2478
+ }
2479
+ })
2990
2480
  ```
2991
2481
 
2992
2482
  ### 4. Structured Logging
2993
2483
 
2994
2484
  Use consistent logging patterns with context:
2995
2485
 
2996
- ```text
2997
- PSEUDOCODE
2998
- INPUTS:
2999
- - Use values defined by the surrounding section/context.
3000
- STEPS:
3001
- - NOTE: ✅ Good - Structured logging with context
3002
- - SET logger TO req.app.locals.roditClient.getLogger()
3003
- - DO: logger.infoWithContext('User action completed', {
3004
- - FIELD: component: 'UserService',
3005
- - FIELD: action: 'updateProfile',
3006
- - FIELD: userId: user.id,
3007
- - FIELD: requestId: req.requestId,
3008
- - FIELD: duration: Date.now() - startTime,
3009
- - FIELD: changes: Object.keys(updates)
3010
- - DO: })
3011
- - NOTE: For errors, pass the error object
3012
- - DO: logger.errorWithContext('Operation failed', {
3013
- - FIELD: component: 'UserService',
3014
- - FIELD: action: 'updateProfile',
3015
- - FIELD: userId: user.id,
3016
- - FIELD: requestId: req.requestId,
3017
- - FIELD: error: error.message
3018
- - DO: }, error)
3019
- - NOTE: ❌ Bad - Unstructured logging
3020
- - DO: console.log('User updated profile'); // Don't do this
3021
- OUTPUTS:
3022
- - Produces the section's intended result using equivalent logic.
2486
+ ```javascript
2487
+ // ✅ Good - Structured logging with context
2488
+ const logger = req.app.locals.roditClient.getLogger()
2489
+ logger.infoWithContext('User action completed', {
2490
+ component: 'UserService',
2491
+ action: 'updateProfile',
2492
+ userId: user.id,
2493
+ requestId: req.requestId,
2494
+ duration: Date.now() - startTime,
2495
+ changes: Object.keys(updates)
2496
+ })
2497
+ // For errors, pass the error object
2498
+ logger.errorWithContext('Operation failed', {
2499
+ component: 'UserService',
2500
+ action: 'updateProfile',
2501
+ userId: user.id,
2502
+ requestId: req.requestId,
2503
+ error: error.message
2504
+ }, error)
2505
+ // ❌ Bad - Unstructured logging
2506
+ console.log('User updated profile'); // Don't do this
3023
2507
  ```
3024
2508
 
3025
2509
  ### 5. Environment-Specific Configuration
3026
2510
 
3027
2511
  Use environment variables for sensitive and environment-specific values:
3028
2512
 
3029
- ```text
3030
- PSEUDOCODE
3031
- INPUTS:
3032
- - Use values defined by the surrounding section/context.
3033
- STEPS:
3034
- - NOTE: ✅ Good - Environment-aware configuration
3035
- - SET config TO roditClient.getConfig()
3036
- - SET logLevel TO config.get('LOG_LEVEL', 'info')
3037
- - SET isMainDeploy TO ['info', 'warn', 'error'].includes(logLevel)
3038
- - NOTE: Main should use vault credentials
3039
- - CHECK CONDITION: if (isMainDeploy && process.env.RODIT_NEAR_CREDENTIALS_SOURCE !== 'vault') {
3040
- - DO: logger.warn('Main environment should use vault credentials', {
3041
- - FIELD: component: 'Configuration',
3042
- - FIELD: environment: 'main',
3043
- - FIELD: credentialsSource: process.env.RODIT_NEAR_CREDENTIALS_SOURCE || 'not-set'
3044
- - DO: })
3045
- - }
3046
- - NOTE: Configure session storage before initializing client
3047
- - CHECK CONDITION: if (isMainDeploy) {
3048
- - SET SQLiteStore TO require('connect-sqlite3')(require('express-session'))
3049
- - SET sessionStore TO new SQLiteStore({
3050
- - FIELD: db: 'sessions.db',
3051
- - FIELD: dir: config.get('API_DEFAULT_OPTIONS.DB_PATH', './data')
3052
- - DO: })
3053
- - DO: setExpressSessionStore(sessionStore)
3054
- - }
3055
- OUTPUTS:
3056
- - Produces the section's intended result using equivalent logic.
2513
+ ```javascript
2514
+ // ✅ Good - Environment-aware configuration
2515
+ const config = roditClient.getConfig()
2516
+ const logLevel = config.get('LOG_LEVEL', 'info')
2517
+ const isMainDeploy = ['info', 'warn', 'error'].includes(logLevel)
2518
+ // Main should use vault credentials
2519
+ if (isMainDeploy && process.env.RODIT_NEAR_CREDENTIALS_SOURCE !== 'vault') {
2520
+ logger.warn('Main environment should use vault credentials', {
2521
+ component: 'Configuration',
2522
+ environment: 'main',
2523
+ credentialsSource: process.env.RODIT_NEAR_CREDENTIALS_SOURCE || 'not-set'
2524
+ })
2525
+ }
2526
+ // Configure session storage before initializing client
2527
+ if (isMainDeploy) {
2528
+ const SQLiteStore = require('connect-sqlite3')(require('express-session'))
2529
+ const sessionStore = new SQLiteStore({
2530
+ db: 'sessions.db',
2531
+ dir: config.get('API_DEFAULT_OPTIONS.DB_PATH', './data')
2532
+ })
2533
+ setExpressSessionStore(sessionStore)
2534
+ }
3057
2535
  ```
3058
2536
 
3059
2537
  ### 6. Graceful Shutdown
3060
2538
 
3061
2539
  Implement proper shutdown handling:
3062
2540
 
3063
- ```text
3064
- PSEUDOCODE
3065
- INPUTS:
3066
- - Use values defined by the surrounding section/context.
3067
- STEPS:
3068
- - NOTE: ✅ Good - Graceful shutdown
3069
- - SET shutdown TO async (signal) => {
3070
- - SET logger TO roditClient.getLogger()
3071
- - DO: logger.info('Shutting down gracefully', {
3072
- - FIELD: component: 'AppLifecycle',
3073
- - FIELD: signal: signal || 'unknown',
3074
- - FIELD: time: new Date().toISOString()
3075
- - DO: })
3076
- - CHECK CONDITION: if (server) {
3077
- - DO: server.close(async () => {
3078
- - DO: logger.info('HTTP server closed')
3079
- - NOTE: Close database connections
3080
- - CHECK CONDITION: if (db && typeof db.close === 'function') {
3081
- - WAIT FOR: db.close()
3082
- - DO: logger.info('Database connections closed')
3083
- - }
3084
- - NOTE: Close session store
3085
- - CHECK CONDITION: if (sessionStore && typeof sessionStore.close === 'function') {
3086
- - WAIT FOR: new Promise((resolve) => sessionStore.close(resolve))
3087
- - DO: logger.info('Session store closed')
3088
- - }
3089
- - DO: process.exit(0)
3090
- - DO: })
3091
- - NOTE: Force shutdown after timeout
3092
- - DO: setTimeout(() => {
3093
- - DO: logger.error('Forced shutdown after timeout')
3094
- - DO: process.exit(1)
3095
- - DO: }, 10000)
3096
- - }
3097
- - DO: }
3098
- - DO: process.on('SIGTERM', () => shutdown('SIGTERM'))
3099
- - DO: process.on('SIGINT', () => shutdown('SIGINT'))
3100
- OUTPUTS:
3101
- - Produces the section's intended result using equivalent logic.
2541
+ ```javascript
2542
+ // ✅ Good - Graceful shutdown
2543
+ const shutdown = async (signal) => {
2544
+ const logger = roditClient.getLogger()
2545
+ logger.info('Shutting down gracefully', {
2546
+ component: 'AppLifecycle',
2547
+ signal: signal || 'unknown',
2548
+ time: new Date().toISOString()
2549
+ })
2550
+ if (server) {
2551
+ server.close(async () => {
2552
+ logger.info('HTTP server closed')
2553
+ // Close database connections
2554
+ if (db && typeof db.close === 'function') {
2555
+ await db.close()
2556
+ logger.info('Database connections closed')
2557
+ }
2558
+ // Close session store
2559
+ if (sessionStore && typeof sessionStore.close === 'function') {
2560
+ await new Promise((resolve) => sessionStore.close(resolve))
2561
+ logger.info('Session store closed')
2562
+ }
2563
+ process.exit(0)
2564
+ })
2565
+ // Force shutdown after timeout
2566
+ setTimeout(() => {
2567
+ logger.error('Forced shutdown after timeout')
2568
+ process.exit(1)
2569
+ }, 10000)
2570
+ }
2571
+ }
2572
+ process.on('SIGTERM', () => shutdown('SIGTERM'))
2573
+ process.on('SIGINT', () => shutdown('SIGINT'))
3102
2574
  ```
3103
2575
 
3104
2576
  ### 7. Request Context and Performance Tracking
3105
2577
 
3106
2578
  Always include request context and track performance:
3107
2579
 
3108
- ```text
3109
- PSEUDOCODE
3110
- INPUTS:
3111
- - Use values defined by the surrounding section/context.
3112
- STEPS:
3113
- - NOTE: ✅ Good - Request context and performance tracking
3114
- - DO: app.use((req, res, next) => {
3115
- - DO: req.requestId = req.headers['x-request-id'] || ulid()
3116
- - DO: req.startTime = Date.now()
3117
- - DO: next()
3118
- - DO: })
3119
- - NOTE: Performance monitoring
3120
- - DO: app.use((req, res, next) => {
3121
- - SET performanceService TO roditClient.getPerformanceService()
3122
- - CHECK CONDITION: if (performanceService) {
3123
- - DO: performanceService.recordRequest(req)
3124
- - }
3125
- - DO: res.on('finish', () => {
3126
- - SET duration TO Date.now() - req.startTime
3127
- - CHECK CONDITION: if (performanceService) {
3128
- - DO: performanceService.recordMetric('request_duration_ms', duration, {
3129
- - FIELD: method: req.method,
3130
- - FIELD: path: req.path,
3131
- - FIELD: status: res.statusCode
3132
- - DO: })
3133
- - CHECK CONDITION: if (res.statusCode >= 400) {
3134
- - DO: performanceService.recordMetric('error_count', 1, {
3135
- - FIELD: method: req.method,
3136
- - FIELD: path: req.path,
3137
- - FIELD: status: res.statusCode
3138
- - DO: })
3139
- - }
3140
- - }
3141
- - DO: })
3142
- - DO: next()
3143
- - DO: })
3144
- OUTPUTS:
3145
- - Produces the section's intended result using equivalent logic.
2580
+ ```javascript
2581
+ // ✅ Good - Request context and performance tracking
2582
+ app.use((req, res, next) => {
2583
+ req.requestId = req.headers['x-request-id'] || ulid()
2584
+ req.startTime = Date.now()
2585
+ next()
2586
+ })
2587
+ // Performance monitoring
2588
+ app.use((req, res, next) => {
2589
+ const performanceService = roditClient.getPerformanceService()
2590
+ if (performanceService) {
2591
+ performanceService.recordRequest(req)
2592
+ }
2593
+ res.on('finish', () => {
2594
+ const duration = Date.now() - req.startTime
2595
+ if (performanceService) {
2596
+ performanceService.recordMetric('request_duration_ms', duration, {
2597
+ method: req.method,
2598
+ path: req.path,
2599
+ status: res.statusCode
2600
+ })
2601
+ if (res.statusCode >= 400) {
2602
+ performanceService.recordMetric('error_count', 1, {
2603
+ method: req.method,
2604
+ path: req.path,
2605
+ status: res.statusCode
2606
+ })
2607
+ }
2608
+ }
2609
+ })
2610
+ next()
2611
+ })
3146
2612
  ```
3147
2613
 
3148
2614
  ### 8. Login Endpoint Protection
3149
2615
 
3150
2616
  **CRITICAL:** Never protect the login endpoint with authentication middleware:
3151
2617
 
3152
- ```text
3153
- PSEUDOCODE
3154
- INPUTS:
3155
- - Use values defined by the surrounding section/context.
3156
- STEPS:
3157
- - NOTE: ✅ Good - Login endpoint without authentication
3158
- - DO: app.post('/api/login', (req, res) => {
3159
- - DO: req.logAction = 'login-attempt'
3160
- - RETURN roditClient.login_client(req, res)
3161
- - DO: })
3162
- - NOTE: ❌ Bad - Login endpoint with authentication (creates circular dependency)
3163
- - DO: app.post('/api/login', authenticate, (req, res) => { // DON'T DO THIS
3164
- - RETURN roditClient.login_client(req, res)
3165
- - DO: })
3166
- - NOTE: ✅ Better - Logout endpoint with logout-specific authentication
3167
- - SET authenticateLogout TO (req, res, next) => roditClient.authenticateForLogout(req, res, next)
3168
- - DO: app.post('/api/logout', authenticateLogout, (req, res) => {
3169
- - DO: req.logAction = 'logout-attempt'
3170
- - RETURN roditClient.logout_client(req, res)
3171
- - DO: })
3172
- OUTPUTS:
3173
- - Produces the section's intended result using equivalent logic.
2618
+ ```javascript
2619
+ // ✅ Good - Login endpoint without authentication
2620
+ app.post('/api/login', (req, res) => {
2621
+ req.logAction = 'login-attempt'
2622
+ roditClient.login_client(req, res)
2623
+ })
2624
+ // ❌ Bad - Login endpoint with authentication (creates circular dependency)
2625
+ app.post('/api/login', authenticate, (req, res) => { // DON'T DO THIS
2626
+ roditClient.login_client(req, res)
2627
+ })
2628
+ // ✅ Better - Logout endpoint with logout-specific authentication
2629
+ const authenticateLogout = (req, res, next) => roditClient.authenticateForLogout(req, res, next)
2630
+ app.post('/api/logout', authenticateLogout, (req, res) => {
2631
+ req.logAction = 'logout-attempt'
2632
+ roditClient.logout_client(req, res)
2633
+ })
3174
2634
  ```
3175
2635
 
3176
2636
  ### 9. Route Mounting Order
3177
2637
 
3178
2638
  Mount protected routes AFTER client initialization:
3179
2639
 
3180
- ```text
3181
- PSEUDOCODE
3182
- INPUTS:
3183
- - Use values defined by the surrounding section/context.
3184
- STEPS:
3185
- - NOTE: ✅ Good - Correct order
3186
- - DO: async function startServer() {
3187
- - NOTE: 1. Configure session storage
3188
- - DO: setExpressSessionStore(sessionStore)
3189
- - NOTE: 2. Initialize client
3190
- - SET roditClient TO await RoditClient.create('server')
3191
- - DO: app.locals.roditClient = roditClient
3192
- - NOTE: 3. Create middleware
3193
- - SET authenticate TO (req, res, next) => roditClient.authenticate(req, res, next)
3194
- - SET authenticateLogout TO (req, res, next) => roditClient.authenticateForLogout(req, res, next)
3195
- - SET authorize TO (req, res, next) => roditClient.authorize(req, res, next)
3196
- - NOTE: 4. Mount public routes
3197
- - DO: app.post('/api/login', loginRoute)
3198
- - NOTE: 5. Mount protected routes
3199
- - DO: app.use('/api/echo', authenticate, echoRoutes)
3200
- - DO: app.use('/api/cruda', authenticate, authorize, crudaRoutes)
3201
- - DO: app.post('/api/logout', authenticateLogout, logoutRoute)
3202
- - NOTE: 6. Start server
3203
- - DO: app.listen(port)
3204
- - }
3205
- - NOTE: ❌ Bad - Routes mounted before client initialization
3206
- - DO: app.use('/api/echo', authenticate, echoRoutes); // authenticate is undefined!
3207
- - SET roditClient TO await RoditClient.create('server')
3208
- OUTPUTS:
3209
- - Produces the section's intended result using equivalent logic.
2640
+ ```javascript
2641
+ // ✅ Good - Correct order
2642
+ async function startServer() {
2643
+ // 1. Configure session storage
2644
+ setExpressSessionStore(sessionStore)
2645
+ // 2. Initialize client
2646
+ const roditClient = await RoditClient.create('server')
2647
+ app.locals.roditClient = roditClient
2648
+ // 3. Create middleware
2649
+ const authenticate = (req, res, next) => roditClient.authenticate(req, res, next)
2650
+ const authenticateLogout = (req, res, next) => roditClient.authenticateForLogout(req, res, next)
2651
+ const authorize = (req, res, next) => roditClient.authorize(req, res, next)
2652
+ // 4. Mount public routes
2653
+ app.post('/api/login', loginRoute)
2654
+ // 5. Mount protected routes
2655
+ app.use('/api/echo', authenticate, echoRoutes)
2656
+ app.use('/api/cruda', authenticate, authorize, crudaRoutes)
2657
+ app.post('/api/logout', authenticateLogout, logoutRoute)
2658
+ // 6. Start server
2659
+ app.listen(port)
2660
+ }
2661
+ // ❌ Bad - Routes mounted before client initialization
2662
+ app.use('/api/echo', authenticate, echoRoutes); // authenticate is undefined!
2663
+ const roditClient = await RoditClient.create('server')
3210
2664
  ```
3211
2665
 
3212
2666
  ## Troubleshooting
@@ -3218,24 +2672,18 @@ OUTPUTS:
3218
2672
  **Problem:** `roditClient.authenticate is not a function` or `Cannot read properties of undefined`
3219
2673
 
3220
2674
  **Solution:** Ensure client is initialized and stored in app.locals:
3221
- ```text
3222
- PSEUDOCODE
3223
- INPUTS:
3224
- - Use values defined by the surrounding section/context.
3225
- STEPS:
3226
- - NOTE: ✅ Correct - Check client availability
3227
- - SET authenticate TO (req, res, next) => {
3228
- - SET client TO req.app.locals.roditClient
3229
- - CHECK CONDITION: if (!client) {
3230
- - RETURN res.status(503).json({ error: 'Authentication service unavailable' })
3231
- - }
3232
- - RETURN client.authenticate(req, res, next)
3233
- - DO: }
3234
- - NOTE: ❌ Wrong - Direct access without checking
3235
- - SET authenticate TO (req, res, next) => roditClient.authenticate(req, res, next)
3236
- - NOTE: This fails if roditClient is not initialized yet
3237
- OUTPUTS:
3238
- - Produces the section's intended result using equivalent logic.
2675
+ ```javascript
2676
+ // ✅ Correct - Check client availability
2677
+ const authenticate = (req, res, next) => {
2678
+ const client = req.app.locals.roditClient
2679
+ if (!client) {
2680
+ res.status(503).json({ error: 'Authentication service unavailable' })
2681
+ }
2682
+ client.authenticate(req, res, next)
2683
+ }
2684
+ // ❌ Wrong - Direct access without checking
2685
+ const authenticate = (req, res, next) => roditClient.authenticate(req, res, next)
2686
+ // This fails if roditClient is not initialized yet
3239
2687
  ```
3240
2688
 
3241
2689
  #### 2. Configuration Not Found
@@ -3243,39 +2691,27 @@ OUTPUTS:
3243
2691
  **Problem:** `Failed to initialize RODiT configuration`
3244
2692
 
3245
2693
  **Solutions:**
3246
- ```text
3247
- PSEUDOCODE
3248
- INPUTS:
3249
- - Use values defined by the surrounding section/context.
3250
- STEPS:
3251
- - NOTE: Check environment variables
3252
- - DO: echo $RODIT_NEAR_CREDENTIALS_SOURCE # Should be 'vault' or 'file'
3253
- - DO: echo $VAULT_ENDPOINT
3254
- - DO: echo $NEAR_CONTRACT_ID
3255
- - DO: echo $SERVICE_NAME
3256
- - NOTE: For vault-based credentials
3257
- - DO: export RODIT_NEAR_CREDENTIALS_SOURCE=vault
3258
- - FIELD: export VAULT_ENDPOINT=https://vault.example.com
3259
- - DO: export VAULT_ROLE_ID=your-role-id
3260
- - DO: export VAULT_SECRET_ID=your-secret-id
3261
- - NOTE: For file-based credentials (development)
3262
- - DO: export RODIT_NEAR_CREDENTIALS_SOURCE=file
3263
- - DO: export CREDENTIALS_FILE_PATH=./credentials/rodit-credentials.json
3264
- OUTPUTS:
3265
- - Produces the section's intended result using equivalent logic.
2694
+ ```bash
2695
+ // Check environment variables
2696
+ echo $RODIT_NEAR_CREDENTIALS_SOURCE # Should be 'vault' or 'file'
2697
+ echo $VAULT_ENDPOINT
2698
+ echo $NEAR_CONTRACT_ID
2699
+ echo $SERVICE_NAME
2700
+ // For vault-based credentials
2701
+ export RODIT_NEAR_CREDENTIALS_SOURCE=vault
2702
+ export VAULT_ENDPOINT=https://vault.example.com
2703
+ export VAULT_ROLE_ID=your-role-id
2704
+ export VAULT_SECRET_ID=your-secret-id
2705
+ // For file-based credentials (development)
2706
+ export RODIT_NEAR_CREDENTIALS_SOURCE=file
2707
+ export CREDENTIALS_FILE_PATH=./credentials/rodit-credentials.json
3266
2708
  ```
3267
2709
 
3268
2710
  **Verify configuration:**
3269
- ```text
3270
- PSEUDOCODE
3271
- INPUTS:
3272
- - Use values defined by the surrounding section/context.
3273
- STEPS:
3274
- - SET config TO roditClient.getConfig()
3275
- - FIELD: console.log('NEAR_CONTRACT_ID:', config.get('NEAR_CONTRACT_ID'))
3276
- - FIELD: console.log('SERVICE_NAME:', config.get('SERVICE_NAME'))
3277
- OUTPUTS:
3278
- - Produces the section's intended result using equivalent logic.
2711
+ ```javascript
2712
+ const config = roditClient.getConfig()
2713
+ console.log('NEAR_CONTRACT_ID:', config.get('NEAR_CONTRACT_ID'))
2714
+ console.log('SERVICE_NAME:', config.get('SERVICE_NAME'))
3279
2715
  ```
3280
2716
 
3281
2717
  #### 3. Missing App.locals Client
@@ -3283,32 +2719,26 @@ OUTPUTS:
3283
2719
  **Problem:** `RoditClient not available in app.locals` or `Cannot read properties of undefined (reading 'roditClient')`
3284
2720
 
3285
2721
  **Solution:** Ensure client is stored during initialization:
3286
- ```text
3287
- PSEUDOCODE
3288
- INPUTS:
3289
- - Use values defined by the surrounding section/context.
3290
- STEPS:
3291
- - DO: async function startServer() {
3292
- - DO: try {
3293
- - NOTE: Initialize client
3294
- - SET roditClient TO await RoditClient.create('server')
3295
- - NOTE: Store in app.locals BEFORE mounting routes
3296
- - DO: app.locals.roditClient = roditClient
3297
- - NOTE: Verify it's stored
3298
- - CHECK CONDITION: if (!app.locals.roditClient) {
3299
- - DO: throw new Error('Failed to store roditClient in app.locals')
3300
- - }
3301
- - NOTE: Now mount routes
3302
- - SET authenticate TO (req, res, next) => roditClient.authenticate(req, res, next)
3303
- - DO: app.use('/api/protected', authenticate, protectedRoutes)
3304
- - DO: app.listen(port)
3305
- - DO: } catch (error) {
3306
- - FIELD: console.error('Server initialization failed:', error)
3307
- - DO: process.exit(1)
3308
- - }
3309
- - }
3310
- OUTPUTS:
3311
- - Produces the section's intended result using equivalent logic.
2722
+ ```javascript
2723
+ async function startServer() {
2724
+ try {
2725
+ // Initialize client
2726
+ const roditClient = await RoditClient.create('server')
2727
+ // Store in app.locals BEFORE mounting routes
2728
+ app.locals.roditClient = roditClient
2729
+ // Verify it's stored
2730
+ if (!app.locals.roditClient) {
2731
+ throw new Error('Failed to store roditClient in app.locals')
2732
+ }
2733
+ // Now mount routes
2734
+ const authenticate = (req, res, next) => roditClient.authenticate(req, res, next)
2735
+ app.use('/api/protected', authenticate, protectedRoutes)
2736
+ app.listen(port)
2737
+ } catch (error) {
2738
+ console.error('Server initialization failed:', error)
2739
+ process.exit(1)
2740
+ }
2741
+ }
3312
2742
  ```
3313
2743
 
3314
2744
  #### 4. Permission Denied Errors
@@ -3316,24 +2746,18 @@ OUTPUTS:
3316
2746
  **Problem:** Routes return 403 Forbidden
3317
2747
 
3318
2748
  **Debug steps:**
3319
- ```text
3320
- PSEUDOCODE
3321
- INPUTS:
3322
- - Use values defined by the surrounding section/context.
3323
- STEPS:
3324
- - NOTE: Check token permissions
3325
- - SET configObject TO await roditClient.getConfigOwnRodit()
3326
- - SET permissionedRoutes TO JSON.parse(
3327
- - DO: configObject.own_rodit.metadata.permissioned_routes || '{}'
3328
- - DO: )
3329
- - FIELD: console.log('Configured permissions:', permissionedRoutes)
3330
- - NOTE: Check specific operation
3331
- - SET hasPermission TO roditClient.isOperationPermitted('POST', '/api/cruda/create')
3332
- - FIELD: console.log('Has permission:', hasPermission)
3333
- - NOTE: Verify route path matches exactly
3334
- - FIELD: console.log('Requested path:', req.path); // Must match permission key exactly
3335
- OUTPUTS:
3336
- - Produces the section's intended result using equivalent logic.
2749
+ ```javascript
2750
+ // Check token permissions
2751
+ const configObject = await roditClient.getConfigOwnRodit()
2752
+ const permissionedRoutes = JSON.parse(
2753
+ configObject.own_rodit.metadata.permissioned_routes || '{}'
2754
+ )
2755
+ console.log('Configured permissions:', permissionedRoutes)
2756
+ // Check specific operation
2757
+ const hasPermission = roditClient.isOperationPermitted('POST', '/api/cruda/create')
2758
+ console.log('Has permission:', hasPermission)
2759
+ // Verify route path matches exactly
2760
+ console.log('Requested path:', req.path); // Must match permission key exactly
3337
2761
  ```
3338
2762
 
3339
2763
  **Common issues:**
@@ -3349,24 +2773,18 @@ OUTPUTS:
3349
2773
  **Cause:** JWT token contains session ID that doesn't exist in session storage
3350
2774
 
3351
2775
  **Solutions:**
3352
- ```text
3353
- PSEUDOCODE
3354
- INPUTS:
3355
- - Use values defined by the surrounding section/context.
3356
- STEPS:
3357
- - NOTE: Verify session storage is configured
3358
- - SET sessionManager TO roditClient.getSessionManager()
3359
- - SET storageInfo TO await sessionManager.getStorageInfo()
3360
- - FIELD: console.log('Storage type:', storageInfo.storageType)
3361
- - FIELD: console.log('Active sessions:', storageInfo.sessionCount)
3362
- - NOTE: Check if token is invalidated
3363
- - SET isInvalidated TO await sessionManager.isTokenInvalidated(jwtToken)
3364
- - FIELD: console.log('Token invalidated:', isInvalidated)
3365
- - NOTE: Enumerate sessions via storage for debugging
3366
- - SET allSessions TO await sessionManager.storage.getAll()
3367
- - FIELD: console.log('Active sessions:', allSessions.filter(s => s.status === 'active').length)
3368
- OUTPUTS:
3369
- - Produces the section's intended result using equivalent logic.
2776
+ ```javascript
2777
+ // Verify session storage is configured
2778
+ const sessionManager = roditClient.getSessionManager()
2779
+ const storageInfo = await sessionManager.getStorageInfo()
2780
+ console.log('Storage type:', storageInfo.storageType)
2781
+ console.log('Active sessions:', storageInfo.sessionCount)
2782
+ // Check if token is invalidated
2783
+ const isInvalidated = await sessionManager.isTokenInvalidated(jwtToken)
2784
+ console.log('Token invalidated:', isInvalidated)
2785
+ // Enumerate sessions via storage for debugging
2786
+ const allSessions = await sessionManager.storage.getAll()
2787
+ console.log('Active sessions:', allSessions.filter(s => s.status === 'active').length)
3370
2788
  ```
3371
2789
 
3372
2790
  **Common causes:**
@@ -3382,47 +2800,29 @@ OUTPUTS:
3382
2800
  **Problem:** Logs not appearing in Loki or console
3383
2801
 
3384
2802
  **Solutions:**
3385
- ```text
3386
- PSEUDOCODE
3387
- INPUTS:
3388
- - Use values defined by the surrounding section/context.
3389
- STEPS:
3390
- - NOTE: Check logging configuration
3391
- - DO: export LOG_LEVEL=debug # Enable debug logging
3392
- - FIELD: export LOKI_URL=https://loki.example.com:3100
3393
- - FIELD: export LOKI_BASIC_AUTH=username:password
3394
- OUTPUTS:
3395
- - Produces the section's intended result using equivalent logic.
2803
+ ```bash
2804
+ // Check logging configuration
2805
+ export LOG_LEVEL=debug # Enable debug logging
2806
+ export LOKI_URL=https://loki.example.com:3100
2807
+ export LOKI_BASIC_AUTH=username:password
3396
2808
  ```
3397
2809
 
3398
- ```text
3399
- PSEUDOCODE
3400
- INPUTS:
3401
- - Use values defined by the surrounding section/context.
3402
- STEPS:
3403
- - NOTE: Test logger directly
3404
- - SET logger TO roditClient.getLogger()
3405
- - FIELD: logger.info('Test message', { component: 'Test' })
3406
- - FIELD: logger.error('Test error', { component: 'Test' })
3407
- - NOTE: Check if Loki transport is configured
3408
- - SET transports TO logger.transports
3409
- - FIELD: console.log('Logger transports:', transports.map(t => t.name))
3410
- OUTPUTS:
3411
- - Produces the section's intended result using equivalent logic.
2810
+ ```javascript
2811
+ // Test logger directly
2812
+ const logger = roditClient.getLogger()
2813
+ logger.info('Test message', { component: 'Test' })
2814
+ logger.error('Test error', { component: 'Test' })
2815
+ // Check if Loki transport is configured
2816
+ const transports = logger.transports
2817
+ console.log('Logger transports:', transports.map(t => t.name))
3412
2818
  ```
3413
2819
 
3414
2820
  ### Debug Mode
3415
2821
 
3416
2822
  Enable debug logging for troubleshooting:
3417
2823
 
3418
- ```text
3419
- PSEUDOCODE
3420
- INPUTS:
3421
- - Use values defined by the surrounding section/context.
3422
- STEPS:
3423
- - DO: export LOG_LEVEL=debug # Use 'debug' or 'trace' for development mode
3424
- OUTPUTS:
3425
- - Produces the section's intended result using equivalent logic.
2824
+ ```bash
2825
+ export LOG_LEVEL=debug # Use 'debug' or 'trace' for development mode
3426
2826
  ```
3427
2827
 
3428
2828
  This will provide detailed information about:
@@ -3435,96 +2835,84 @@ This will provide detailed information about:
3435
2835
  - Request/response details
3436
2836
 
3437
2837
  **Example debug output:**
3438
- ```text
3439
- PSEUDOCODE
3440
- INPUTS:
3441
- - Use values defined by the surrounding section/context.
3442
- STEPS:
3443
- - SET logger TO roditClient.getLogger()
3444
- - NOTE: Enable debug logging programmatically
3445
- - DO: logger.level = 'debug'
3446
- - NOTE: Debug authentication
3447
- - DO: logger.debug('Authenticating request', {
3448
- - FIELD: component: 'Authentication',
3449
- - FIELD: hasAuthHeader: !!req.headers.authorization,
3450
- - FIELD: path: req.path,
3451
- - FIELD: method: req.method
3452
- - DO: })
3453
- OUTPUTS:
3454
- - Produces the section's intended result using equivalent logic.
2838
+ ```javascript
2839
+ const logger = roditClient.getLogger()
2840
+ // Enable debug logging programmatically
2841
+ logger.level = 'debug'
2842
+ // Debug authentication
2843
+ logger.debug('Authenticating request', {
2844
+ component: 'Authentication',
2845
+ hasAuthHeader: !!req.headers.authorization,
2846
+ path: req.path,
2847
+ method: req.method
2848
+ })
3455
2849
  ```
3456
2850
 
3457
2851
  ### Health Checks
3458
2852
 
3459
2853
  Implement comprehensive health check endpoints:
3460
2854
 
3461
- ```text
3462
- PSEUDOCODE
3463
- INPUTS:
3464
- - Use values defined by the surrounding section/context.
3465
- STEPS:
3466
- - DO: app.get('/health', async (req, res) => {
3467
- - DO: try {
3468
- - SET client TO req.app.locals.roditClient
3469
- - CHECK CONDITION: if (!client) {
3470
- - RETURN res.status(503).json({
3471
- - FIELD: status: 'error',
3472
- - FIELD: message: 'RoditClient not available'
3473
- - DO: })
3474
- - }
3475
- - SET configObject TO await client.getConfigOwnRodit()
3476
- - SET sessionManager TO client.getSessionManager()
3477
- - SET performanceService TO client.getPerformanceService()
3478
- - SET health TO {
3479
- - FIELD: status: 'healthy',
3480
- - FIELD: timestamp: new Date().toISOString(),
3481
- - FIELD: logLevel: config.get('LOG_LEVEL', 'info'),
3482
- - FIELD: components: {
3483
- - FIELD: roditClient: !!client,
3484
- - FIELD: configuration: !!(configObject && configObject.own_rodit),
3485
- - FIELD: sessionManager: !!sessionManager,
3486
- - FIELD: performanceService: !!performanceService
3487
- - DO: },
3488
- - FIELD: metrics: {
3489
- - FIELD: activeSessions: await sessionManager.getActiveSessionCount(),
3490
- - FIELD: totalRequests: performanceService.getRequestCount(),
3491
- - FIELD: errorCount: performanceService.getErrorCount()
3492
- - DO: },
3493
- - FIELD: roditToken: {
3494
- - FIELD: tokenId: configObject?.own_rodit?.token_id,
3495
- - FIELD: apiUrl: configObject?.own_rodit?.metadata?.subjectuniqueidentifier_url,
3496
- - FIELD: jwtDuration: configObject?.own_rodit?.metadata?.jwt_duration
3497
- - }
3498
- - DO: }
3499
- - DO: res.json(health)
3500
- - DO: } catch (error) {
3501
- - DO: res.status(503).json({
3502
- - FIELD: status: 'error',
3503
- - FIELD: message: error.message,
3504
- - FIELD: timestamp: new Date().toISOString()
3505
- - DO: })
3506
- - }
3507
- - DO: })
3508
- - NOTE: Readiness check (for Kubernetes)
3509
- - DO: app.get('/ready', async (req, res) => {
3510
- - SET client TO req.app.locals.roditClient
3511
- - CHECK CONDITION: if (!client) {
3512
- - RETURN res.status(503).json({ ready: false })
3513
- - }
3514
- - DO: try {
3515
- - SET configObject TO await client.getConfigOwnRodit()
3516
- - SET ready TO !!(configObject && configObject.own_rodit)
3517
- - FIELD: res.status(ready ? 200 : 503).json({ ready })
3518
- - DO: } catch (error) {
3519
- - FIELD: res.status(503).json({ ready: false, error: error.message })
3520
- - }
3521
- - DO: })
3522
- - NOTE: Liveness check (for Kubernetes)
3523
- - DO: app.get('/live', (req, res) => {
3524
- - FIELD: res.json({ alive: true })
3525
- - DO: })
3526
- OUTPUTS:
3527
- - Produces the section's intended result using equivalent logic.
2855
+ ```javascript
2856
+ app.get('/health', async (req, res) => {
2857
+ try {
2858
+ const client = req.app.locals.roditClient
2859
+ if (!client) {
2860
+ res.status(503).json({
2861
+ status: 'error',
2862
+ message: 'RoditClient not available'
2863
+ })
2864
+ }
2865
+ const configObject = await client.getConfigOwnRodit()
2866
+ const sessionManager = client.getSessionManager()
2867
+ const performanceService = client.getPerformanceService()
2868
+ const health = {
2869
+ status: 'healthy',
2870
+ timestamp: new Date().toISOString(),
2871
+ logLevel: config.get('LOG_LEVEL', 'info'),
2872
+ components: {
2873
+ roditClient: !!client,
2874
+ configuration: !!(configObject && configObject.own_rodit),
2875
+ sessionManager: !!sessionManager,
2876
+ performanceService: !!performanceService
2877
+ },
2878
+ metrics: {
2879
+ activeSessions: await sessionManager.getActiveSessionCount(),
2880
+ totalRequests: performanceService.getRequestCount(),
2881
+ errorCount: performanceService.getErrorCount()
2882
+ },
2883
+ roditToken: {
2884
+ tokenId: configObject?.own_rodit?.token_id,
2885
+ apiUrl: configObject?.own_rodit?.metadata?.subjectuniqueidentifier_url,
2886
+ jwtDuration: configObject?.own_rodit?.metadata?.jwt_duration
2887
+ }
2888
+ }
2889
+ res.json(health)
2890
+ } catch (error) {
2891
+ res.status(503).json({
2892
+ status: 'error',
2893
+ message: error.message,
2894
+ timestamp: new Date().toISOString()
2895
+ })
2896
+ }
2897
+ })
2898
+ // Readiness check (for Kubernetes)
2899
+ app.get('/ready', async (req, res) => {
2900
+ const client = req.app.locals.roditClient
2901
+ if (!client) {
2902
+ res.status(503).json({ ready: false })
2903
+ }
2904
+ try {
2905
+ const configObject = await client.getConfigOwnRodit()
2906
+ const ready = !!(configObject && configObject.own_rodit)
2907
+ res.status(ready ? 200 : 503).json({ ready })
2908
+ } catch (error) {
2909
+ res.status(503).json({ ready: false, error: error.message })
2910
+ }
2911
+ })
2912
+ // Liveness check (for Kubernetes)
2913
+ app.get('/live', (req, res) => {
2914
+ res.json({ alive: true })
2915
+ })
3528
2916
  ```
3529
2917
 
3530
2918
  ### Support