@scrypted/server 0.0.183 → 0.1.3

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.

Potentially problematic release.


This version of @scrypted/server might be problematic. Click here for more details.

Files changed (45) hide show
  1. package/dist/io.js +3 -0
  2. package/dist/io.js.map +1 -0
  3. package/dist/plugin/buffer-serializer.js +18 -1
  4. package/dist/plugin/buffer-serializer.js.map +1 -1
  5. package/dist/plugin/plugin-host-api.js +2 -0
  6. package/dist/plugin/plugin-host-api.js.map +1 -1
  7. package/dist/plugin/plugin-host.js +83 -14
  8. package/dist/plugin/plugin-host.js.map +1 -1
  9. package/dist/plugin/plugin-http.js +7 -5
  10. package/dist/plugin/plugin-http.js.map +1 -1
  11. package/dist/plugin/plugin-remote-websocket.js +0 -2
  12. package/dist/plugin/plugin-remote-websocket.js.map +1 -1
  13. package/dist/plugin/plugin-remote-worker.js +12 -10
  14. package/dist/plugin/plugin-remote-worker.js.map +1 -1
  15. package/dist/plugin/plugin-remote.js +4 -2
  16. package/dist/plugin/plugin-remote.js.map +1 -1
  17. package/dist/rpc.js +18 -27
  18. package/dist/rpc.js.map +1 -1
  19. package/dist/runtime.js +96 -3
  20. package/dist/runtime.js.map +1 -1
  21. package/dist/scrypted-server-main.js +25 -24
  22. package/dist/scrypted-server-main.js.map +1 -1
  23. package/dist/server-settings.js +74 -15
  24. package/dist/server-settings.js.map +1 -1
  25. package/dist/services/cors.js +17 -0
  26. package/dist/services/cors.js.map +1 -0
  27. package/package.json +6 -4
  28. package/python/plugin-remote.py +22 -17
  29. package/python/rpc.py +0 -6
  30. package/src/io.ts +25 -0
  31. package/src/plugin/buffer-serializer.ts +19 -0
  32. package/src/plugin/plugin-host-api.ts +2 -0
  33. package/src/plugin/plugin-host.ts +70 -17
  34. package/src/plugin/plugin-http.ts +11 -8
  35. package/src/plugin/plugin-remote-websocket.ts +0 -2
  36. package/src/plugin/plugin-remote-worker.ts +12 -10
  37. package/src/plugin/plugin-remote.ts +4 -2
  38. package/src/rpc.ts +22 -36
  39. package/src/runtime.ts +87 -4
  40. package/src/scrypted-server-main.ts +29 -30
  41. package/src/server-settings.ts +62 -16
  42. package/src/services/cors.ts +19 -0
  43. package/dist/addresses.js +0 -12
  44. package/dist/addresses.js.map +0 -1
  45. package/src/addresses.ts +0 -5
package/src/rpc.ts CHANGED
@@ -43,10 +43,6 @@ interface RpcResult extends RpcMessage {
43
43
  result?: any;
44
44
  }
45
45
 
46
- interface RpcOob extends RpcMessage {
47
- oob: any;
48
- }
49
-
50
46
  interface RpcRemoteProxyValue {
51
47
  __remote_proxy_id: string | undefined;
52
48
  __remote_proxy_finalizer_id: string | undefined;
@@ -127,8 +123,9 @@ class RpcProxy implements PrimitiveProxyHandler<any> {
127
123
  // undefined is not JSON serializable.
128
124
  const method = target() || null;
129
125
  const args: any[] = [];
126
+ const serializationContext: any = {};
130
127
  for (const arg of (argArray || [])) {
131
- args.push(this.peer.serialize(arg));
128
+ args.push(this.peer.serialize(arg, serializationContext));
132
129
  }
133
130
 
134
131
  const rpcApply: RpcApply = {
@@ -147,7 +144,7 @@ class RpcProxy implements PrimitiveProxyHandler<any> {
147
144
 
148
145
  return this.peer.createPendingResult((id, reject) => {
149
146
  rpcApply.id = id;
150
- this.peer.send(rpcApply, reject);
147
+ this.peer.send(rpcApply, reject, serializationContext);
151
148
  })
152
149
  }
153
150
  }
@@ -193,8 +190,8 @@ catch (e) {
193
190
  }
194
191
 
195
192
  export interface RpcSerializer {
196
- serialize(value: any): any;
197
- deserialize(serialized: any): any;
193
+ serialize(value: any, serializationContext?: any): any;
194
+ deserialize(serialized: any, serializationContext?: any): any;
198
195
  }
199
196
 
200
197
  interface LocalProxiedEntry {
@@ -204,7 +201,6 @@ interface LocalProxiedEntry {
204
201
 
205
202
  export class RpcPeer {
206
203
  idCounter = 1;
207
- onOob?: (oob: any) => void;
208
204
  params: { [name: string]: any } = {};
209
205
  pendingResults: { [id: string]: Deferred } = {};
210
206
  proxyCounter = 1;
@@ -213,7 +209,7 @@ export class RpcPeer {
213
209
  remoteWeakProxies: { [id: string]: WeakRef<any> } = {};
214
210
  finalizers = new FinalizationRegistry(entry => this.finalize(entry as LocalProxiedEntry));
215
211
  nameDeserializerMap = new Map<string, RpcSerializer>();
216
- constructorSerializerMap = new Map<string, string>();
212
+ constructorSerializerMap = new Map<any, string>();
217
213
  transportSafeArgumentTypes = RpcPeer.getDefaultTransportSafeArgumentTypes();
218
214
 
219
215
  static readonly finalizerIdSymbol = Symbol('rpcFinalizerId');
@@ -251,7 +247,7 @@ export class RpcPeer {
251
247
  static readonly PROPERTY_PROXY_PROPERTIES = '__proxy_props';
252
248
  static readonly PROPERTY_JSON_COPY_SERIALIZE_CHILDREN = '__json_copy_serialize_children';
253
249
 
254
- constructor(public selfName: string, public peerName: string, public send: (message: RpcMessage, reject?: (e: Error) => void) => void) {
250
+ constructor(public selfName: string, public peerName: string, public send: (message: RpcMessage, reject?: (e: Error) => void, serializationContext?: any) => void) {
255
251
  }
256
252
 
257
253
  createPendingResult(cb: (id: string, reject: (e: Error) => void) => void): Promise<any> {
@@ -310,13 +306,6 @@ export class RpcPeer {
310
306
  });
311
307
  }
312
308
 
313
- sendOob(oob: any) {
314
- this.send({
315
- type: 'oob',
316
- oob,
317
- } as RpcOob)
318
- }
319
-
320
309
  evalLocal<T>(script: string, filename?: string, coercedParams?: { [name: string]: any }): T {
321
310
  const params = Object.assign({}, this.params, coercedParams);
322
311
  let compile: CompileFunction;
@@ -339,7 +328,7 @@ export class RpcPeer {
339
328
  result.message = (e as Error).message || 'no message';
340
329
  }
341
330
 
342
- deserialize(value: any): any {
331
+ deserialize(value: any, deserializationContext: any): any {
343
332
  if (!value)
344
333
  return value;
345
334
 
@@ -347,7 +336,7 @@ export class RpcPeer {
347
336
  if (copySerializeChildren) {
348
337
  const ret: any = {};
349
338
  for (const [key, val] of Object.entries(value)) {
350
- ret[key] = this.deserialize(val);
339
+ ret[key] = this.deserialize(val, deserializationContext);
351
340
  }
352
341
  return ret;
353
342
  }
@@ -370,17 +359,17 @@ export class RpcPeer {
370
359
 
371
360
  const deserializer = this.nameDeserializerMap.get(__remote_constructor_name);
372
361
  if (deserializer) {
373
- return deserializer.deserialize(__serialized_value);
362
+ return deserializer.deserialize(__serialized_value, deserializationContext);
374
363
  }
375
364
 
376
365
  return value;
377
366
  }
378
367
 
379
- serialize(value: any): any {
368
+ serialize(value: any, serializationContext: any): any {
380
369
  if (value?.[RpcPeer.PROPERTY_JSON_COPY_SERIALIZE_CHILDREN] === true) {
381
370
  const ret: any = {};
382
371
  for (const [key, val] of Object.entries(value)) {
383
- ret[key] = this.serialize(val);
372
+ ret[key] = this.serialize(val, serializationContext);
384
373
  }
385
374
  return ret;
386
375
  }
@@ -419,7 +408,7 @@ export class RpcPeer {
419
408
  const serializer = this.nameDeserializerMap.get(serializerMapName);
420
409
  if (!serializer)
421
410
  throw new Error('serializer not found for ' + serializerMapName);
422
- const serialized = serializer.serialize(value);
411
+ const serialized = serializer.serialize(value, serializationContext);
423
412
  const ret: RpcRemoteProxyValue = {
424
413
  __remote_proxy_id: undefined,
425
414
  __remote_proxy_finalizer_id: undefined,
@@ -464,17 +453,18 @@ export class RpcPeer {
464
453
  return proxy;
465
454
  }
466
455
 
467
- async handleMessage(message: RpcMessage) {
456
+ async handleMessage(message: RpcMessage, deserializationContext?: any) {
468
457
  try {
469
458
  switch (message.type) {
470
459
  case 'param': {
471
460
  const rpcParam = message as RpcParam;
461
+ const serializationContext: any = {};
472
462
  const result: RpcResult = {
473
463
  type: 'result',
474
464
  id: rpcParam.id,
475
- result: this.serialize(this.params[rpcParam.param])
465
+ result: this.serialize(this.params[rpcParam.param], serializationContext)
476
466
  };
477
- this.send(result);
467
+ this.send(result, undefined, serializationContext);
478
468
  break;
479
469
  }
480
470
  case 'apply': {
@@ -483,6 +473,7 @@ export class RpcPeer {
483
473
  type: 'result',
484
474
  id: rpcApply.id || '',
485
475
  };
476
+ const serializationContext: any = {};
486
477
 
487
478
  try {
488
479
  const target = this.localProxyMap[rpcApply.proxyId];
@@ -491,7 +482,7 @@ export class RpcPeer {
491
482
 
492
483
  const args = [];
493
484
  for (const arg of (rpcApply.args || [])) {
494
- args.push(this.deserialize(arg));
485
+ args.push(this.deserialize(arg, deserializationContext));
495
486
  }
496
487
 
497
488
  let value: any;
@@ -505,7 +496,7 @@ export class RpcPeer {
505
496
  value = await target(...args);
506
497
  }
507
498
 
508
- result.result = this.serialize(value);
499
+ result.result = this.serialize(value, serializationContext);
509
500
  }
510
501
  catch (e) {
511
502
  // console.error('failure', rpcApply.method, e);
@@ -513,7 +504,7 @@ export class RpcPeer {
513
504
  }
514
505
 
515
506
  if (!rpcApply.oneway)
516
- this.send(result);
507
+ this.send(result, undefined, serializationContext);
517
508
  break;
518
509
  }
519
510
  case 'result': {
@@ -530,7 +521,7 @@ export class RpcPeer {
530
521
  deferred.reject(e);
531
522
  return;
532
523
  }
533
- deferred.resolve(this.deserialize(rpcResult.result));
524
+ deferred.resolve(this.deserialize(rpcResult.result, deserializationContext));
534
525
  break;
535
526
  }
536
527
  case 'finalize': {
@@ -547,11 +538,6 @@ export class RpcPeer {
547
538
  }
548
539
  break;
549
540
  }
550
- case 'oob': {
551
- const rpcOob = message as RpcOob;
552
- this.onOob?.(rpcOob.oob);
553
- break;
554
- }
555
541
  default:
556
542
  throw new Error(`unknown rpc message type ${message.type}`);
557
543
  }
package/src/runtime.ts CHANGED
@@ -6,7 +6,7 @@ import { Plugin, PluginDevice, ScryptedAlert } from './db-types';
6
6
  import { getState, ScryptedStateManager, setState } from './state';
7
7
  import { Request, Response } from 'express';
8
8
  import { createResponseInterface } from './http-interfaces';
9
- import http, { ServerResponse } from 'http';
9
+ import http, { ServerResponse, IncomingHttpHeaders } from 'http';
10
10
  import https from 'https';
11
11
  import express from 'express';
12
12
  import { LogEntry, Logger, makeAlertId } from './logger';
@@ -25,20 +25,22 @@ import semver from 'semver';
25
25
  import { ServiceControl } from './services/service-control';
26
26
  import { Alerts } from './services/alerts';
27
27
  import { Info } from './services/info';
28
- import io from 'engine.io';
28
+ import * as io from 'engine.io';
29
29
  import { spawn as ptySpawn } from 'node-pty';
30
30
  import rimraf from 'rimraf';
31
31
  import { getPluginVolume } from './plugin/plugin-volume';
32
32
  import { PluginHttp } from './plugin/plugin-http';
33
33
  import AdmZip from 'adm-zip';
34
34
  import path from 'path';
35
+ import { CORSControl, CORSServer } from './services/cors';
36
+ import { IOServer, IOServerSocket } from './io';
35
37
 
36
38
  interface DeviceProxyPair {
37
39
  handler: PluginDeviceProxyHandler;
38
40
  proxy: ScryptedDevice;
39
41
  }
40
42
 
41
- const MIN_SCRYPTED_CORE_VERSION = 'v0.0.238';
43
+ const MIN_SCRYPTED_CORE_VERSION = 'v0.1.5';
42
44
  const PLUGIN_DEVICE_STATE_VERSION = 2;
43
45
 
44
46
  interface HttpPluginData {
@@ -56,9 +58,18 @@ export class ScryptedRuntime extends PluginHttp<HttpPluginData> {
56
58
  devicesLogger = this.logger.getLogger('device', 'Devices');
57
59
  wss = new WebSocketServer({ noServer: true });
58
60
  wsAtomic = 0;
59
- shellio = io(undefined, {
61
+ shellio: IOServer = new io.Server({
60
62
  pingTimeout: 120000,
63
+ perMessageDeflate: true,
64
+ cors: (req, callback) => {
65
+ const header = this.getAccessControlAllowOrigin(req.headers);
66
+ callback(undefined, {
67
+ origin: header,
68
+ credentials: true,
69
+ })
70
+ },
61
71
  });
72
+ cors: CORSServer[] = [];
62
73
 
63
74
  constructor(datastore: Level, insecure: http.Server, secure: https.Server, app: express.Application) {
64
75
  super(app);
@@ -67,6 +78,48 @@ export class ScryptedRuntime extends PluginHttp<HttpPluginData> {
67
78
 
68
79
  app.disable('x-powered-by');
69
80
 
81
+ this.app.options(['/endpoint/@:owner/:pkg/engine.io/api/activate', '/endpoint/@:owner/:pkg/engine.io/api/activate'], (req, res) => {
82
+ this.addAccessControlHeaders(req, res);
83
+ res.setHeader('Access-Control-Allow-Methods', 'POST, OPTIONS');
84
+ res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, Content-Length, X-Requested-With');
85
+ res.send(200);
86
+ });
87
+
88
+ this.app.post(['/endpoint/@:owner/:pkg/engine.io/api/activate', '/endpoint/@:owner/:pkg/engine.io/api/activate'], (req, res) => {
89
+ const { username } = (req as any);
90
+ if (!username) {
91
+ res.status(401);
92
+ res.send('Not Authorized');
93
+ return;
94
+ }
95
+
96
+ const { owner, pkg } = req.params;
97
+ let endpoint = pkg;
98
+ if (owner)
99
+ endpoint = `@${owner}/${endpoint}`;
100
+
101
+ const { id } = req.body;
102
+ try {
103
+ const host = this.plugins?.[endpoint];
104
+ if (!host)
105
+ throw new Error('invalid plugin');
106
+ // @ts-expect-error
107
+ const socket: IOServerSocket = host.io.clients[id];
108
+ if (!socket)
109
+ throw new Error('invalid socket');
110
+ socket.emit('/api/activate');
111
+ res.send({
112
+ id,
113
+ })
114
+ }
115
+ catch (e) {
116
+ res.status(500);
117
+ res.end();
118
+ }
119
+ });
120
+
121
+ this.addMiddleware();
122
+
70
123
  app.get('/web/oauth/callback', (req, res) => {
71
124
  this.oauthCallback(req, res);
72
125
  });
@@ -122,6 +175,34 @@ export class ScryptedRuntime extends PluginHttp<HttpPluginData> {
122
175
  }, 60 * 60 * 1000);
123
176
  }
124
177
 
178
+ addAccessControlHeaders(req: http.IncomingMessage, res: http.ServerResponse) {
179
+ res.setHeader('Vary', 'Origin,Referer');
180
+ const header = this.getAccessControlAllowOrigin(req.headers);
181
+ if (header)
182
+ res.setHeader('Access-Control-Allow-Origin', header);
183
+ }
184
+
185
+ getAccessControlAllowOrigin(headers: http.IncomingHttpHeaders) {
186
+ let { origin, referer } = headers;
187
+ if (!origin && referer) {
188
+ try {
189
+ const u = new URL(headers.referer)
190
+ origin = u.origin;
191
+ }
192
+ catch (e) {
193
+ return;
194
+ }
195
+ }
196
+ if (!origin)
197
+ return;
198
+ const servers: string[] = process.env.SCRYPTED_ACCESS_CONTROL_ALLOW_ORIGINS?.split(',') || [];
199
+ servers.push(...Object.values(this.cors).map(entry => entry.server));
200
+ if (!servers.includes(origin))
201
+ return;
202
+
203
+ return origin;
204
+ }
205
+
125
206
  getDeviceLogger(device: PluginDevice): Logger {
126
207
  return this.devicesLogger.getLogger(device._id, getState(device, ScryptedInterfaceProperty.name));
127
208
  }
@@ -311,6 +392,8 @@ export class ScryptedRuntime extends PluginHttp<HttpPluginData> {
311
392
  return this.logger;
312
393
  case 'alerts':
313
394
  return new Alerts(this);
395
+ case 'cors':
396
+ return new CORSControl(this);
314
397
  }
315
398
  }
316
399
 
@@ -8,7 +8,7 @@ import net from 'net';
8
8
  import { ScryptedRuntime } from './runtime';
9
9
  import level from './level';
10
10
  import { Plugin, ScryptedUser, Settings } from './db-types';
11
- import { SCRYPTED_DEBUG_PORT, SCRYPTED_INSECURE_PORT, SCRYPTED_SECURE_PORT } from './server-settings';
11
+ import { getHostAddresses, SCRYPTED_DEBUG_PORT, SCRYPTED_INSECURE_PORT, SCRYPTED_SECURE_PORT } from './server-settings';
12
12
  import crypto from 'crypto';
13
13
  import cookieParser from 'cookie-parser';
14
14
  import axios from 'axios';
@@ -20,7 +20,6 @@ import { install as installSourceMapSupport } from 'source-map-support';
20
20
  import httpAuth from 'http-auth';
21
21
  import semver from 'semver';
22
22
  import { Info } from './services/info';
23
- import { getAddresses } from './addresses';
24
23
  import { sleep } from './sleep';
25
24
  import { createSelfSignedCertificate, CURRENT_SELF_SIGNED_CERTIFICATE_VERSION } from './cert';
26
25
  import { PluginError } from './plugin/plugin-error';
@@ -55,7 +54,7 @@ let workerInspectPort: number = undefined;
55
54
 
56
55
  async function doconnect(): Promise<net.Socket> {
57
56
  return new Promise((resolve, reject) => {
58
- const target = net.connect(workerInspectPort);
57
+ const target = net.connect(workerInspectPort, '127.0.0.1');
59
58
  target.once('error', reject)
60
59
  target.once('connect', () => resolve(target))
61
60
  })
@@ -123,7 +122,6 @@ async function start() {
123
122
  certSetting = await db.upsert(certSetting);
124
123
  }
125
124
 
126
-
127
125
  const basicAuth = httpAuth.basic({
128
126
  realm: 'Scrypted',
129
127
  }, async (username, password, callback) => {
@@ -184,6 +182,7 @@ async function start() {
184
182
  // }
185
183
 
186
184
  res.locals.username = username;
185
+ (req as any).username = username;
187
186
  }
188
187
  next();
189
188
  });
@@ -193,6 +192,7 @@ async function start() {
193
192
  if (req.protocol === 'https' && req.headers.authorization && req.headers.authorization.toLowerCase()?.indexOf('basic') !== -1) {
194
193
  const basicChecker = basicAuth.check((req) => {
195
194
  res.locals.username = req.user;
195
+ (req as any).username = req.user;
196
196
  next();
197
197
  });
198
198
 
@@ -226,7 +226,7 @@ async function start() {
226
226
 
227
227
  console.log('#######################################################');
228
228
  console.log(`Scrypted Server (Local) : https://localhost:${SCRYPTED_SECURE_PORT}/`);
229
- for (const address of getAddresses()) {
229
+ for (const address of getHostAddresses(true, true)) {
230
230
  console.log(`Scrypted Server (Remote) : https://${address}:${SCRYPTED_SECURE_PORT}/`);
231
231
  }
232
232
  console.log(`Version: : ${await new Info().getVersion()}`);
@@ -360,10 +360,20 @@ async function start() {
360
360
 
361
361
  let hasLogin = await db.getCount(ScryptedUser) > 0;
362
362
 
363
+ app.options('/login', (req, res) => {
364
+ scrypted.addAccessControlHeaders(req, res);
365
+ res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
366
+ res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, Content-Length, X-Requested-With');
367
+ res.send(200);
368
+ });
369
+
363
370
  app.post('/login', async (req, res) => {
371
+ scrypted.addAccessControlHeaders(req, res);
372
+
364
373
  const { username, password, change_password } = req.body;
365
374
  const timestamp = Date.now();
366
375
  const maxAge = 86400000;
376
+ const addresses = getHostAddresses(true, true).map(address => `https://${address}:${SCRYPTED_SECURE_PORT}`);
367
377
 
368
378
  if (hasLogin) {
369
379
  const user = await db.tryGet(ScryptedUser, username);
@@ -390,9 +400,11 @@ async function start() {
390
400
  const login_user_token = `${username}#${timestamp}`;
391
401
  res.cookie('login_user_token', login_user_token, {
392
402
  maxAge,
393
- secure: true,
403
+ // enabling this will disable insecure http login...
404
+ // secure: true,
394
405
  signed: true,
395
406
  httpOnly: true,
407
+ sameSite: 'none',
396
408
  });
397
409
 
398
410
  if (change_password) {
@@ -405,6 +417,7 @@ async function start() {
405
417
  res.send({
406
418
  username,
407
419
  expiration: maxAge,
420
+ addresses,
408
421
  });
409
422
 
410
423
  return;
@@ -423,25 +436,32 @@ async function start() {
423
436
  user.salt = crypto.randomBytes(64).toString('base64');
424
437
  user.passwordHash = crypto.createHash('sha256').update(user.salt + password).digest().toString('hex');
425
438
  user.passwordDate = timestamp;
439
+ user.token = crypto.randomBytes(16).toString('hex');
426
440
  await db.upsert(user);
427
441
  hasLogin = true;
428
442
 
429
443
  const login_user_token = `${username}#${timestamp}`
430
444
  res.cookie('login_user_token', login_user_token, {
431
445
  maxAge,
432
- secure: true,
446
+ // enabling this will disable insecure http login...
447
+ // secure: true,
433
448
  signed: true,
434
449
  httpOnly: true,
450
+ sameSite: 'none',
435
451
  });
436
452
 
437
453
  res.send({
438
454
  username,
455
+ token: user.token,
439
456
  expiration: maxAge,
457
+ addresses,
440
458
  });
441
459
  });
442
460
 
443
-
444
461
  app.get('/login', async (req, res) => {
462
+ scrypted.addAccessControlHeaders(req, res);
463
+
464
+ const addresses = getHostAddresses(true, true).map(address => `https://${address}:${SCRYPTED_SECURE_PORT}`);
445
465
  if (req.protocol === 'https' && req.headers.authorization) {
446
466
  const username = await new Promise(resolve => {
447
467
  const basicChecker = basicAuth.check((req) => {
@@ -484,31 +504,10 @@ async function start() {
484
504
  return;
485
505
  }
486
506
 
487
- // this database lookup on every web request is not necessary, the cookie
488
- // itself is the auth, and is signed. furthermore, this is currently
489
- // a single user setup anywyas. revisit this at some point when
490
- // multiple users are implemented.
491
-
492
- // const user = await db.tryGet(ScryptedUser, username);
493
- // if (!user) {
494
- // res.send({
495
- // error: 'User not found.',
496
- // hasLogin,
497
- // })
498
- // return;
499
- // }
500
-
501
- // if (timestamp < user.passwordDate) {
502
- // res.send({
503
- // error: 'Login invalid. Password has changed.',
504
- // hasLogin,
505
- // })
506
- // return;
507
- // }
508
-
509
507
  res.send({
510
508
  expiration: 86400000 - (Date.now() - timestamp),
511
509
  username,
510
+ addresses,
512
511
  })
513
512
  });
514
513
 
@@ -1,24 +1,70 @@
1
1
  import os from 'os';
2
+ import * as nodeIp from 'ip';
2
3
 
3
4
  export const SCRYPTED_INSECURE_PORT = parseInt(process.env.SCRYPTED_INSECURE_PORT) || 11080;
4
5
  export const SCRYPTED_SECURE_PORT = parseInt(process.env.SCRYPTED_SECURE_PORT) || 10443;
5
6
  export const SCRYPTED_DEBUG_PORT = parseInt(process.env.SCRYPTED_DEBUG_PORT) || 10081;
6
7
 
7
8
  export function getIpAddress(): string {
8
- const ni = os.networkInterfaces();
9
- for (const i of [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) {
10
- let ipv4: os.NetworkInterfaceInfo;
11
- let ipv6: os.NetworkInterfaceInfo;
12
- for (const en of (ni[`en${i}`] || [])) {
13
- if (en.family === 'IPv4')
14
- ipv4 = en;
15
- else if (en.family === 'IPv6')
16
- ipv6 = en;
17
- }
18
-
19
- if (ipv4 || ipv6)
20
- return (ipv4 || ipv6).address;
21
- }
22
-
23
- return '127.0.0.1';
9
+ return nodeIp.address();
10
+ }
11
+
12
+ function normalizeFamilyNodeV18(family: string|number) {
13
+ if (family === 'IPv4')
14
+ return 4;
15
+ if (family === 'IPv6')
16
+ return 6;
17
+ return family;
18
+ }
19
+
20
+ function nodeIpAddress(family: number): string[] {
21
+ // https://chromium.googlesource.com/external/webrtc/+/master/rtc_base/network.cc#236
22
+ const costlyNetworks = ["ipsec", "tun", "utun", "tap"];
23
+
24
+ const ignoreNetworks = [
25
+ // seen these on macos
26
+ 'llw',
27
+ 'awdl',
28
+
29
+ ...costlyNetworks,
30
+ ];
31
+
32
+ const interfaces = os.networkInterfaces();
33
+
34
+ const all = Object.keys(interfaces)
35
+ .map((nic) => {
36
+ for (const costly of ignoreNetworks) {
37
+ if (nic.startsWith(costly)) {
38
+ return {
39
+ nic,
40
+ addresses: [],
41
+ };
42
+ }
43
+ }
44
+ const addresses = interfaces[nic]!.filter(
45
+ (details) =>
46
+ normalizeFamilyNodeV18(details.family) === family
47
+ && !nodeIp.isLoopback(details.address)
48
+ );
49
+ return {
50
+ nic,
51
+ addresses: addresses.map((address) => address.address),
52
+ };
53
+ })
54
+ .filter((address) => !!address);
55
+
56
+ // os.networkInterfaces doesn't actually return addresses in a good order.
57
+ // have seen instances where en0 (ethernet) is after en1 (wlan), etc.
58
+ // eth0 > eth1
59
+ all.sort((a, b) => a.nic.localeCompare(b.nic));
60
+ return Object.values(all)
61
+ .map((entry) => entry.addresses)
62
+ .flat();
63
+ }
64
+
65
+ export function getHostAddresses(useIpv4: boolean, useIpv6: boolean) {
66
+ const address: string[] = [];
67
+ if (useIpv4) address.push(...nodeIpAddress(4));
68
+ if (useIpv6) address.push(...nodeIpAddress(6));
69
+ return address;
24
70
  }
@@ -0,0 +1,19 @@
1
+ import { ScryptedRuntime } from "../runtime";
2
+
3
+ export interface CORSServer {
4
+ tag: string;
5
+ server: string;
6
+ }
7
+
8
+ export class CORSControl {
9
+ constructor(public runtime: ScryptedRuntime) {
10
+ }
11
+
12
+ async getCORS(): Promise<CORSServer[]> {
13
+ return this.runtime.cors;
14
+ }
15
+
16
+ async setCORS(servers: CORSServer[]) {
17
+ this.runtime.cors = servers;
18
+ }
19
+ }
package/dist/addresses.js DELETED
@@ -1,12 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.getAddresses = void 0;
7
- const os_1 = __importDefault(require("os"));
8
- function getAddresses() {
9
- return Object.entries(os_1.default.networkInterfaces()).filter(([iface]) => iface.startsWith('en') || iface.startsWith('eth') || iface.startsWith('wlan')).map(([_, addr]) => addr).flat().map(info => info.address).filter(address => address);
10
- }
11
- exports.getAddresses = getAddresses;
12
- //# sourceMappingURL=addresses.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"addresses.js","sourceRoot":"","sources":["../src/addresses.ts"],"names":[],"mappings":";;;;;;AAAA,4CAAoB;AAEpB,SAAgB,YAAY;IACxB,OAAO,MAAM,CAAC,OAAO,CAAC,YAAE,CAAC,iBAAiB,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC;AAC1O,CAAC;AAFD,oCAEC"}
package/src/addresses.ts DELETED
@@ -1,5 +0,0 @@
1
- import os from 'os';
2
-
3
- export function getAddresses() {
4
- return Object.entries(os.networkInterfaces()).filter(([iface]) => iface.startsWith('en') || iface.startsWith('eth') || iface.startsWith('wlan')).map(([_, addr]) => addr).flat().map(info => info.address).filter(address => address);
5
- }