retold-harness 1.1.3 → 1.1.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "retold-harness",
3
- "version": "1.1.3",
3
+ "version": "1.1.5",
4
4
  "description": "Restful API harness. Serves on 8086.",
5
5
  "main": "source/Retold-Harness.js",
6
6
  "bin": {
@@ -48,6 +48,7 @@
48
48
  "pict-application": "^1.0.33",
49
49
  "pict-terminalui": "^0.0.3",
50
50
  "pict-view": "^1.0.67",
51
+ "orator-authentication": "file:../../orator/orator-authentication",
51
52
  "retold-data-service": "^2.0.19"
52
53
  },
53
54
  "devDependencies": {
@@ -146,7 +146,8 @@ class RetoldHarnessMeadowProviderConfigurator extends libFableServiceProviderBas
146
146
  */
147
147
  serveWebUI(fCallback)
148
148
  {
149
- let tmpWebUIPath = libPath.join(__dirname, '..', 'web', 'index.html');
149
+ let tmpWebFolder = libPath.join(__dirname, '..', 'web');
150
+ let tmpWebUIPath = libPath.join(tmpWebFolder, 'index.html');
150
151
 
151
152
  if (!libFS.existsSync(tmpWebUIPath))
152
153
  {
@@ -156,6 +157,7 @@ class RetoldHarnessMeadowProviderConfigurator extends libFableServiceProviderBas
156
157
 
157
158
  let tmpWebUIHTML = libFS.readFileSync(tmpWebUIPath, 'utf8');
158
159
 
160
+ // Serve index.html at the root URL
159
161
  this.fable.OratorServiceServer.server.get('/',
160
162
  (pRequest, pResponse, fNext) =>
161
163
  {
@@ -164,6 +166,51 @@ class RetoldHarnessMeadowProviderConfigurator extends libFableServiceProviderBas
164
166
  return fNext();
165
167
  });
166
168
 
169
+ // Serve static files (JS, CSS, etc.) from the web folder.
170
+ // Files are served at their plain filename, e.g. /pict.js → source/web/pict.js.
171
+ // This route is registered after all API routes, so API endpoints take priority.
172
+ let tmpMimeTypes =
173
+ {
174
+ '.js': 'application/javascript',
175
+ '.css': 'text/css',
176
+ '.html': 'text/html',
177
+ '.json': 'application/json',
178
+ '.map': 'application/json',
179
+ '.png': 'image/png',
180
+ '.jpg': 'image/jpeg',
181
+ '.svg': 'image/svg+xml',
182
+ '.ico': 'image/x-icon'
183
+ };
184
+
185
+ this.fable.OratorServiceServer.server.get('/:filename',
186
+ (pRequest, pResponse, fNext) =>
187
+ {
188
+ let tmpRequestedFile = pRequest.params.filename;
189
+
190
+ // Only serve files with recognized extensions
191
+ let tmpExt = libPath.extname(tmpRequestedFile).toLowerCase();
192
+ if (!tmpMimeTypes[tmpExt])
193
+ {
194
+ return fNext();
195
+ }
196
+
197
+ // Prevent directory traversal
198
+ let tmpSafeName = libPath.basename(tmpRequestedFile);
199
+ let tmpFilePath = libPath.join(tmpWebFolder, tmpSafeName);
200
+
201
+ if (!libFS.existsSync(tmpFilePath) || !libFS.statSync(tmpFilePath).isFile())
202
+ {
203
+ return fNext();
204
+ }
205
+
206
+ let tmpContentType = tmpMimeTypes[tmpExt];
207
+ let tmpFileContent = libFS.readFileSync(tmpFilePath);
208
+
209
+ pResponse.writeHead(200, { 'Content-Type': tmpContentType });
210
+ pResponse.end(tmpFileContent);
211
+ return fNext();
212
+ });
213
+
167
214
  return fCallback();
168
215
  }
169
216
 
@@ -189,6 +236,7 @@ class RetoldHarnessMeadowProviderConfigurator extends libFableServiceProviderBas
189
236
  let tmpPort = this.fable.settings.APIServerPort || 8086;
190
237
  this.log.info(`Retold Harness running on port ${tmpPort}`);
191
238
  this.log.info(`Web UI available at http://localhost:${tmpPort}/`);
239
+ this.log.info(`Authentication: http://localhost:${tmpPort}/1.0/Authenticate/{user}/{pass}`);
192
240
  return fStepComplete();
193
241
  });
194
242
 
@@ -98,6 +98,96 @@ class RetoldHarnessSchemaBookstore extends libRetoldHarnessSchemaProvider
98
98
  {
99
99
  let tmpFable = this.fable;
100
100
 
101
+ // --- Enable body parsing for POST routes ---
102
+ if (tmpFable.OratorServiceServer && typeof tmpFable.OratorServiceServer.bodyParser === 'function')
103
+ {
104
+ tmpFable.OratorServiceServer.server.use(tmpFable.OratorServiceServer.bodyParser());
105
+ }
106
+
107
+ // --- Authentication Setup ---
108
+ let libOratorAuthentication = require('orator-authentication');
109
+
110
+ tmpFable.serviceManager.addServiceType('OratorAuthentication', libOratorAuthentication);
111
+ tmpFable.serviceManager.instantiateServiceProvider('OratorAuthentication',
112
+ {
113
+ DeniedPasswords: ['abc', 'badpassword', '111']
114
+ });
115
+
116
+ // Plug in a fake authenticator that accepts any existing User by LoginID.
117
+ // Password is ignored — the only failures come from the denied list or
118
+ // a LoginID that doesn't exist in the database.
119
+ tmpFable.OratorAuthentication.setAuthenticator(
120
+ (pUsername, pPassword, fAuthCallback) =>
121
+ {
122
+ if (!tmpFable.DAL || !tmpFable.DAL.User)
123
+ {
124
+ tmpFable.log.warn('BookStore authenticator: DAL.User not available, allowing login with stub record.');
125
+ return fAuthCallback(null, { LoginID: pUsername, IDUser: 0 });
126
+ }
127
+
128
+ let tmpQuery = tmpFable.DAL.User.query.addFilter('LoginID', pUsername);
129
+ tmpFable.DAL.User.doRead(tmpQuery,
130
+ (pReadError, pReadQuery, pUserRecord) =>
131
+ {
132
+ if (pReadError)
133
+ {
134
+ tmpFable.log.error(`BookStore authenticator error: ${pReadError}`);
135
+ return fAuthCallback(pReadError, null);
136
+ }
137
+
138
+ if (!pUserRecord || !pUserRecord.IDUser || pUserRecord.IDUser < 1)
139
+ {
140
+ tmpFable.log.info(`BookStore authenticator: User [${pUsername}] not found.`);
141
+ return fAuthCallback(null, null);
142
+ }
143
+
144
+ tmpFable.log.info(`BookStore authenticator: User [${pUsername}] found (IDUser ${pUserRecord.IDUser}).`);
145
+ return fAuthCallback(null, pUserRecord);
146
+ });
147
+ });
148
+
149
+ tmpFable.OratorAuthentication.connectRoutes();
150
+ this.log.info('BookStore authentication routes registered.');
151
+
152
+ // --- Demo-Only Endpoint: list valid LoginIDs without requiring auth ---
153
+ // This bypasses Meadow's authorization so the web UI can show a
154
+ // convenient user picker. Not something you would do in production!
155
+ tmpFable.Orator.serviceServer.get('/1.0/Demo/Users',
156
+ (pRequest, pResponse, fNext) =>
157
+ {
158
+ if (!tmpFable.DAL || !tmpFable.DAL.User)
159
+ {
160
+ pResponse.send({ Users: [] });
161
+ return fNext();
162
+ }
163
+
164
+ let tmpQuery = tmpFable.DAL.User.query;
165
+ tmpFable.DAL.User.doReads(tmpQuery,
166
+ (pReadError, pReadQuery, pUserRecords) =>
167
+ {
168
+ if (pReadError || !pUserRecords)
169
+ {
170
+ pResponse.send({ Users: [] });
171
+ return fNext();
172
+ }
173
+
174
+ let tmpUsers = [];
175
+ for (let i = 0; i < pUserRecords.length; i++)
176
+ {
177
+ tmpUsers.push(
178
+ {
179
+ IDUser: pUserRecords[i].IDUser,
180
+ LoginID: pUserRecords[i].LoginID,
181
+ FullName: pUserRecords[i].FullName || ''
182
+ });
183
+ }
184
+
185
+ pResponse.send({ Users: tmpUsers });
186
+ return fNext();
187
+ });
188
+ });
189
+ this.log.info('BookStore demo user list endpoint registered at /1.0/Demo/Users');
190
+
101
191
  if (!tmpFable.MeadowEndpoints || !tmpFable.MeadowEndpoints.Book)
102
192
  {
103
193
  this.log.warn('BookStore applyBehaviors: MeadowEndpoints.Book not available, skipping author enrichment behavior.');