spec-3nweb-server 1.8.11 → 1.9.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.
Files changed (45) hide show
  1. package/.gitlab-ci-linux.yml +13 -0
  2. package/.gitlab-ci.yml +0 -10
  3. package/build/3nstorage/routes/owner/param-getter.js +1 -4
  4. package/build/3nstorage/routes/owner/session-params.js +1 -1
  5. package/build/asmail/resources/utils.d.ts +4 -0
  6. package/build/asmail/resources/utils.js +8 -1
  7. package/build/asmail/routes/delivery/restart-session.js +3 -1
  8. package/build/asmail/routes/retrieval/get-message-meta.js +4 -1
  9. package/build/asmail/routes/retrieval/get-message-obj.js +4 -1
  10. package/build/asmail/routes/retrieval/remove-message.js +4 -1
  11. package/build/lib.d.ts +1 -1
  12. package/build/lib.js +2 -1
  13. package/build/main.js +2 -1
  14. package/build/services.d.ts +6 -0
  15. package/build/services.js +11 -2
  16. package/build/tests/libs-for-tests/mailerid.js +1 -2
  17. package/build/tests/protocols/asmail/delivery/specs/restart-session.js +1 -1
  18. package/build/tests/protocols/asmail/delivery/test-utils.js +1 -2
  19. package/build/tests/shared-checks/check-mid-login.js +1 -1
  20. package/build/well-known/well-known-route-app.d.ts +3 -0
  21. package/build/well-known/well-known-route-app.js +53 -0
  22. package/package.json +1 -1
  23. package/ts-code/3nstorage/routes/owner/archive-obj-version.ts +1 -0
  24. package/ts-code/3nstorage/routes/owner/cancel-trans.ts +1 -0
  25. package/ts-code/3nstorage/routes/owner/delete-obj.ts +1 -0
  26. package/ts-code/3nstorage/routes/owner/get-archived-obj-ver.ts +1 -0
  27. package/ts-code/3nstorage/routes/owner/get-current-obj.ts +1 -0
  28. package/ts-code/3nstorage/routes/owner/get-obj-status.ts +1 -0
  29. package/ts-code/3nstorage/routes/owner/param-getter.ts +1 -5
  30. package/ts-code/3nstorage/routes/owner/put-current-obj.ts +1 -0
  31. package/ts-code/3nstorage/routes/owner/session-params.ts +1 -1
  32. package/ts-code/asmail/resources/utils.ts +10 -0
  33. package/ts-code/asmail/routes/delivery/restart-session.ts +6 -1
  34. package/ts-code/asmail/routes/retrieval/get-message-meta.ts +6 -2
  35. package/ts-code/asmail/routes/retrieval/get-message-obj.ts +6 -2
  36. package/ts-code/asmail/routes/retrieval/remove-message.ts +6 -2
  37. package/ts-code/lib.ts +1 -1
  38. package/ts-code/main.ts +3 -2
  39. package/ts-code/services.ts +18 -1
  40. package/ts-code/tests/libs-for-tests/mailerid.ts +1 -2
  41. package/ts-code/tests/libs-for-tests/pkl.ts +1 -1
  42. package/ts-code/tests/protocols/asmail/delivery/specs/restart-session.ts +2 -3
  43. package/ts-code/tests/protocols/asmail/delivery/test-utils.ts +6 -7
  44. package/ts-code/tests/shared-checks/check-mid-login.ts +2 -2
  45. package/ts-code/well-known/well-known-route-app.ts +66 -0
@@ -48,3 +48,16 @@ pack mock for linux:
48
48
  # paths:
49
49
  # - 3nweb-*.deb
50
50
  # expire_in: 3d
51
+
52
+
53
+ publish to npm:
54
+ stage: publish
55
+ rules:
56
+ - if: '$CI_COMMIT_BRANCH == "master"'
57
+ when: manual
58
+ needs:
59
+ - build for linux
60
+ - test on linux
61
+ image: node-24
62
+ script:
63
+ - bash publish-task.sh $NPM_PUBLISH_TOKEN
package/.gitlab-ci.yml CHANGED
@@ -47,16 +47,6 @@ pack mock:
47
47
  - npm run clear-for-packing-mock
48
48
 
49
49
 
50
- publish to npm:
51
- stage: publish
52
- rules:
53
- - if: '$CI_COMMIT_BRANCH == "master"'
54
- when: manual
55
- image: node-24
56
- script:
57
- - bash publish-task.sh $NPM_PUBLISH_TOKEN
58
-
59
-
60
50
  include:
61
51
  - .gitlab-ci-linux.yml
62
52
  - .gitlab-ci-windows.yml
@@ -20,9 +20,6 @@ exports.getParam = void 0;
20
20
  const users_1 = require("../../resources/users");
21
21
  const owner_1 = require("../../../lib-common/service-api/3nstorage/owner");
22
22
  function getParam(paramGetter) {
23
- if ('function' !== typeof paramGetter) {
24
- throw new TypeError("Given argument 'paramGetter' must be function, but is not.");
25
- }
26
23
  return async (req, res, next) => {
27
24
  const session = req.session;
28
25
  const userId = session.params.userId;
@@ -32,7 +29,7 @@ function getParam(paramGetter) {
32
29
  }
33
30
  catch (err) {
34
31
  if (err === users_1.SC.USER_UNKNOWN) {
35
- res.status(owner_1.ERR_SC.server).send("Recipient disappeared from the system.");
32
+ res.status(owner_1.ERR_SC.server).send("User disappeared from the system.");
36
33
  session.close();
37
34
  }
38
35
  else {
@@ -35,7 +35,7 @@ function sessionParams(maxChunk) {
35
35
  next(err);
36
36
  }
37
37
  else if (err === users_1.SC.USER_UNKNOWN) {
38
- res.status(owner_1.ERR_SC.server).send("Recipient disappeared from the system.");
38
+ res.status(owner_1.ERR_SC.server).send("User disappeared from the system.");
39
39
  req.session.close();
40
40
  }
41
41
  else {
@@ -3,4 +3,8 @@ export declare function getObjIdFromParams(req: Request): {
3
3
  objId: string;
4
4
  objIdParseErr?: string;
5
5
  };
6
+ export declare function getMsgIdFromParams(req: Request): {
7
+ msgId: string;
8
+ msgIdParseErr?: string;
9
+ };
6
10
  export declare function replyWithErr(status: number, msg: string, res: Response, reqToDrainBytesFrom?: Request): void;
@@ -16,7 +16,7 @@
16
16
  this program. If not, see <http://www.gnu.org/licenses/>.
17
17
  */
18
18
  Object.defineProperty(exports, "__esModule", { value: true });
19
- exports.replyWithErr = exports.getObjIdFromParams = void 0;
19
+ exports.replyWithErr = exports.getMsgIdFromParams = exports.getObjIdFromParams = void 0;
20
20
  const buffer_utils_1 = require("../../lib-common/buffer-utils");
21
21
  const body_parsers_1 = require("../../lib-server/middleware/body-parsers");
22
22
  function getObjIdFromParams(req) {
@@ -26,6 +26,13 @@ function getObjIdFromParams(req) {
26
26
  { objId: undefined, objIdParseErr: "Bad object id" });
27
27
  }
28
28
  exports.getObjIdFromParams = getObjIdFromParams;
29
+ function getMsgIdFromParams(req) {
30
+ const msgId = req.params.msgId;
31
+ return ((!!msgId && buffer_utils_1.base64urlSafe.allCharsFromAlphabet(msgId)) ?
32
+ { msgId } :
33
+ { msgId: undefined, msgIdParseErr: "Bad message id" });
34
+ }
35
+ exports.getMsgIdFromParams = getMsgIdFromParams;
29
36
  function replyWithErr(status, msg, res, reqToDrainBytesFrom) {
30
37
  if (reqToDrainBytesFrom) {
31
38
  (0, body_parsers_1.attachByteDrainToRequest)(reqToDrainBytesFrom);
@@ -22,6 +22,7 @@ const delivery_1 = require("../../../lib-common/service-api/asmail/delivery");
22
22
  const canonical_address_1 = require("../../../lib-common/canonical-address");
23
23
  const confUtil = require("../../../lib-server/conf-util");
24
24
  const utils_1 = require("../../resources/utils");
25
+ const buffer_utils_1 = require("../../../lib-common/buffer-utils");
25
26
  /**
26
27
  * This creates a session restart route handler.
27
28
  * @param sessionGenFunc is a promise returning function that generates new session objects.
@@ -69,7 +70,8 @@ function restartSession(sessionGenFunc, sessionForMsgFunc, incompleteMsgFunc, ma
69
70
  return (0, utils_1.replyWithErr)(delivery_1.ERR_SC.malformed, "Recipient is either missing in the request, or is malformed", res);
70
71
  }
71
72
  // check message id
72
- if (typeof msgId !== 'string') {
73
+ if ((typeof msgId !== 'string') || !msgId || !buffer_utils_1.base64urlSafe.allCharsFromAlphabet(msgId)) {
74
+ console.log(`msgId ->`, msgId, '\n', `!base64urlSafe.allCharsFromAlphabet(msgId) ->`, !buffer_utils_1.base64urlSafe.allCharsFromAlphabet(msgId));
73
75
  return (0, utils_1.replyWithErr)(delivery_1.ERR_SC.malformed, "Message id is either missing, or is malformed", res);
74
76
  }
75
77
  try {
@@ -23,7 +23,10 @@ const utils_1 = require("../../resources/utils");
23
23
  function getMsgMeta(getMsgMetaFunc) {
24
24
  return async (req, res, next) => {
25
25
  const userId = req.session.params.userId;
26
- const msgId = req.params.msgId;
26
+ const { msgId, msgIdParseErr } = (0, utils_1.getMsgIdFromParams)(req);
27
+ if (msgIdParseErr) {
28
+ return (0, utils_1.replyWithErr)(retrieval_1.ERR_SC.malformed, msgIdParseErr, res, req);
29
+ }
27
30
  try {
28
31
  const meta = await getMsgMetaFunc(userId, msgId);
29
32
  res.status(retrieval_1.msgMetadata.SC.ok).json(meta);
@@ -41,7 +41,10 @@ function extractQueryOptions(req) {
41
41
  function getMsgObj(getMsgObjFunc) {
42
42
  return async (req, res, next) => {
43
43
  const userId = req.session.params.userId;
44
- const msgId = req.params.msgId;
44
+ const { msgId, msgIdParseErr } = (0, utils_1.getMsgIdFromParams)(req);
45
+ if (msgIdParseErr) {
46
+ return (0, utils_1.replyWithErr)(retrieval_1.ERR_SC.malformed, msgIdParseErr, res, req);
47
+ }
45
48
  const { objId, objIdParseErr } = (0, utils_1.getObjIdFromParams)(req);
46
49
  if (objIdParseErr) {
47
50
  return (0, utils_1.replyWithErr)(retrieval_1.ERR_SC.malformed, objIdParseErr, res);
@@ -23,7 +23,10 @@ const utils_1 = require("../../resources/utils");
23
23
  function deleteMsg(delMsgFunc) {
24
24
  return async (req, res, next) => {
25
25
  const userId = req.session.params.userId;
26
- const msgId = req.params.msgId;
26
+ const { msgId, msgIdParseErr } = (0, utils_1.getMsgIdFromParams)(req);
27
+ if (msgIdParseErr) {
28
+ return (0, utils_1.replyWithErr)(retrieval_1.ERR_SC.malformed, msgIdParseErr, res, req);
29
+ }
27
30
  try {
28
31
  await delMsgFunc(userId, msgId);
29
32
  res.status(retrieval_1.rmMsg.SC.ok).send();
package/build/lib.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { AppWithWSs } from './lib-server/web-sockets/app';
2
- export { Configurations, servicesApp, accountsApp } from './services';
2
+ export { Configurations, servicesApp, accountsApp, wellKnownApp } from './services';
3
3
  export { addSingleUserSignup } from './config/signup';
4
4
  export import errors = require('./lib-server/middleware/error-handler');
5
5
  export * as mock from './mock/run-in-proc';
package/build/lib.js CHANGED
@@ -16,12 +16,13 @@
16
16
  this program. If not, see <http://www.gnu.org/licenses/>.
17
17
  */
18
18
  Object.defineProperty(exports, "__esModule", { value: true });
19
- exports.mock = exports.addSingleUserSignup = exports.accountsApp = exports.servicesApp = exports.AppWithWSs = void 0;
19
+ exports.mock = exports.addSingleUserSignup = exports.wellKnownApp = exports.accountsApp = exports.servicesApp = exports.AppWithWSs = void 0;
20
20
  var app_1 = require("./lib-server/web-sockets/app");
21
21
  Object.defineProperty(exports, "AppWithWSs", { enumerable: true, get: function () { return app_1.AppWithWSs; } });
22
22
  var services_1 = require("./services");
23
23
  Object.defineProperty(exports, "servicesApp", { enumerable: true, get: function () { return services_1.servicesApp; } });
24
24
  Object.defineProperty(exports, "accountsApp", { enumerable: true, get: function () { return services_1.accountsApp; } });
25
+ Object.defineProperty(exports, "wellKnownApp", { enumerable: true, get: function () { return services_1.wellKnownApp; } });
25
26
  var signup_1 = require("./config/signup");
26
27
  Object.defineProperty(exports, "addSingleUserSignup", { enumerable: true, get: function () { return signup_1.addSingleUserSignup; } });
27
28
  exports.errors = require("./lib-server/middleware/error-handler");
package/build/main.js CHANGED
@@ -36,6 +36,7 @@ async function run(conf) {
36
36
  const app = new lib_1.AppWithWSs();
37
37
  app.use((0, lib_1.servicesApp)(conf, 'console', 'console'));
38
38
  app.use((0, lib_1.accountsApp)(conf, 'console', 'console'));
39
+ app.use((0, lib_1.wellKnownApp)(conf));
39
40
  return app;
40
41
  }
41
42
  async function stopProcess() {
@@ -61,7 +62,7 @@ async function run(conf) {
61
62
  }
62
63
  async function startAppWithoutTLS(port, hostname) {
63
64
  await app.start(undefined, port, hostname);
64
- console.log(`🚀 Started 3NWeb server on port ${port},${hostname ? ` hostname ${hostname},` : ''} without TLS, and requiring TLS reverse proxy infront.`);
65
+ console.log(`🚀 Started 3NWeb server on port ${port},${hostname ? ` hostname ${hostname},` : ''} without TLS, and requiring TLS or Tor-like reverse proxy infront.`);
65
66
  }
66
67
  try {
67
68
  app = createApp(conf);
@@ -27,6 +27,12 @@ export interface Configurations {
27
27
  noTokenFile?: string;
28
28
  serviceUrl?: string;
29
29
  };
30
+ wellKnown3NWeb?: {
31
+ mailerid?: string;
32
+ asmail?: string;
33
+ "3nstorage"?: string;
34
+ };
30
35
  }
31
36
  export declare function servicesApp(conf: Configurations, errLogger?: ErrLogger | 'console', logSetup?: 'console'): AppWithWSs;
32
37
  export declare function accountsApp(conf: Configurations, errLogger?: ErrLogger | 'console', logSetup?: 'console'): AppWithWSs;
38
+ export declare function wellKnownApp(conf: Configurations): AppWithWSs;
package/build/services.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  /*
3
- Copyright (C) 2015 - 2017, 2020, 2024 - 2025 3NSoft Inc.
3
+ Copyright (C) 2015 - 2017, 2020, 2024 - 2026 3NSoft Inc.
4
4
 
5
5
  This program is free software: you can redistribute it and/or modify it under
6
6
  the terms of the GNU General Public License as published by the Free Software
@@ -16,7 +16,7 @@
16
16
  this program. If not, see <http://www.gnu.org/licenses/>.
17
17
  */
18
18
  Object.defineProperty(exports, "__esModule", { value: true });
19
- exports.accountsApp = exports.servicesApp = void 0;
19
+ exports.wellKnownApp = exports.accountsApp = exports.servicesApp = void 0;
20
20
  const mailerid_authorizer_1 = require("./lib-server/resources/mailerid-authorizer");
21
21
  const app_1 = require("./lib-server/web-sockets/app");
22
22
  const mailerId_app_1 = require("./mailerId/mailerId-app");
@@ -25,6 +25,7 @@ const _3nstorage_app_1 = require("./3nstorage/3nstorage-app");
25
25
  const signup_app_1 = require("./signup/signup-app");
26
26
  const error_handler_1 = require("./lib-server/middleware/error-handler");
27
27
  const locker_app_1 = require("./locker/locker-app");
28
+ const well_known_route_app_1 = require("./well-known/well-known-route-app");
28
29
  function servicesApp(conf, errLogger, logSetup) {
29
30
  var _a;
30
31
  if (!conf.enabledServices || (typeof conf.enabledServices !== 'object')) {
@@ -75,4 +76,12 @@ function accountsApp(conf, errLogger, logSetup) {
75
76
  return app;
76
77
  }
77
78
  exports.accountsApp = accountsApp;
79
+ function wellKnownApp(conf) {
80
+ const app = new app_1.AppWithWSs();
81
+ if (conf.wellKnown3NWeb) {
82
+ app.http.use('/.well-known', (0, well_known_route_app_1.makeWellKnownApp)(conf.wellKnown3NWeb));
83
+ }
84
+ return app;
85
+ }
86
+ exports.wellKnownApp = wellKnownApp;
78
87
  Object.freeze(exports);
@@ -24,7 +24,6 @@ const provisioning_1 = require("../../lib-common/service-api/mailer-id/provision
24
24
  const mid_sigs_NaCl_Ed_1 = require("../../lib-common/mid-sigs-NaCl-Ed");
25
25
  const pkl_1 = require("./pkl");
26
26
  const random_node_1 = require("../../lib-common/random-node");
27
- const url_1 = require("url");
28
27
  async function getMidData(midUrl) {
29
28
  let reqOpts = {
30
29
  url: midUrl,
@@ -97,7 +96,7 @@ async function doMailerIdLogin(loginUrl, user) {
97
96
  responseType: 'json',
98
97
  sessionId
99
98
  };
100
- let serviceDomain = (0, url_1.parse)(loginUrl).hostname;
99
+ let serviceDomain = (new URL(loginUrl)).hostname;
101
100
  if (!serviceDomain) {
102
101
  throw new Error(`Cannot parse hostname from login url ${loginUrl}`);
103
102
  }
@@ -63,7 +63,7 @@ exports.specs.definition = (setup) => (() => {
63
63
  (0, async_jasmine_1.itAsync)(`returns error status when message is not known in delivery`, async () => {
64
64
  const req = {
65
65
  recipient: user2.id,
66
- msgId: 'unknown message'
66
+ msgId: 'unknown_message'
67
67
  };
68
68
  const rep = await (0, xhr_utils_1.doJsonRequest)(reqOpts, req);
69
69
  expect(rep.status).withContext('status for unknown message').toBe(delivery_1.sessionRestart.SC.unknownMsg);
@@ -36,8 +36,7 @@ exports.msg = {
36
36
  msgObjs: [exports.obj1, exports.obj2]
37
37
  };
38
38
  async function startSessionAndSendMsgObjs(deliveryUrl, recipient, msg) {
39
- const sessInfo = await (0, asmail_1.startMsgDelivery)(deliveryUrl, { recipient }, { pid: msg.cryptoMeta.pid,
40
- objIds: [msg.msgObjs[0].objId, msg.msgObjs[1].objId] });
39
+ const sessInfo = await (0, asmail_1.startMsgDelivery)(deliveryUrl, { recipient }, { pid: msg.cryptoMeta.pid, objIds: [msg.msgObjs[0].objId, msg.msgObjs[1].objId] });
41
40
  await (0, asmail_1.sendMsgObj)(deliveryUrl, sessInfo.sessionId, msg.msgObjs[0], sessInfo.maxChunkSize);
42
41
  await (0, asmail_1.sendMsgObj)(deliveryUrl, sessInfo.sessionId, msg.msgObjs[1], sessInfo.maxChunkSize);
43
42
  return sessInfo;
@@ -71,7 +71,7 @@ function midLoginSpecs(loginUrl, user) {
71
71
  responseType: 'json',
72
72
  sessionId
73
73
  };
74
- const serviceDomain = (0, url_1.parse)(loginUrl()).hostname;
74
+ const serviceDomain = (new URL(loginUrl())).hostname;
75
75
  if (!serviceDomain) {
76
76
  throw new Error(`Cannot get hostname from ${loginUrl()}`);
77
77
  }
@@ -0,0 +1,3 @@
1
+ import * as express from 'express';
2
+ import { Configurations } from '../services';
3
+ export declare function makeWellKnownApp(w3n: NonNullable<Configurations['wellKnown3NWeb']>): express.Application;
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ /*
3
+ Copyright (C) 2026 3NSoft Inc.
4
+
5
+ This program is free software: you can redistribute it and/or modify it under
6
+ the terms of the GNU General Public License as published by the Free Software
7
+ Foundation, either version 3 of the License, or (at your option) any later
8
+ version.
9
+
10
+ This program is distributed in the hope that it will be useful, but
11
+ WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13
+ See the GNU General Public License for more details.
14
+
15
+ You should have received a copy of the GNU General Public License along with
16
+ this program. If not, see <http://www.gnu.org/licenses/>.
17
+ */
18
+ Object.defineProperty(exports, "__esModule", { value: true });
19
+ exports.makeWellKnownApp = void 0;
20
+ const express = require("express");
21
+ function makeWellKnownApp(w3n) {
22
+ const app = express();
23
+ const w3nJson = confTo3NWebJSON(w3n);
24
+ app.get('3nweb.json', (req, res) => {
25
+ // the following implicitly sets content type application/json
26
+ res.status(200).json(w3nJson);
27
+ });
28
+ return app;
29
+ }
30
+ exports.makeWellKnownApp = makeWellKnownApp;
31
+ function confTo3NWebJSON(conf) {
32
+ const json = {};
33
+ const fields = ['3nstorage', 'asmail', 'mailerid'];
34
+ for (const field in fields) {
35
+ const href = conf[field];
36
+ if (href) {
37
+ let u;
38
+ try {
39
+ u = new URL(href);
40
+ }
41
+ catch (err) {
42
+ console.error(` ❌ Configuration field ${field} for well-known needs potocol section`);
43
+ throw err;
44
+ }
45
+ if ((u.protocol !== 'https:') && (u.protocol !== 'http:')) {
46
+ throw new Error(` ❌ Configuration field ${field} for well-known needs potocol section`);
47
+ }
48
+ json[field] = href;
49
+ }
50
+ }
51
+ return json;
52
+ }
53
+ Object.freeze(exports);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spec-3nweb-server",
3
- "version": "1.8.11",
3
+ "version": "1.9.0",
4
4
  "description": "3NWeb spec server.",
5
5
  "main": "build/lib.js",
6
6
  "types": "build/lib.d.ts",
@@ -30,6 +30,7 @@ export function archiveCurrentObjVersion(
30
30
  return async (req: Request, res, next) => {
31
31
 
32
32
  const userId = req.session.params.userId;
33
+
33
34
  const { objId, objIdParseErr } = getObjIdFromParams(root, req);
34
35
  if (objIdParseErr) {
35
36
  return replyWithErr(ERR_SC.malformed, objIdParseErr, res);
@@ -28,6 +28,7 @@ export function cancelTransaction(
28
28
  return async (req: Request, res, next) => {
29
29
 
30
30
  const userId = req.session.params.userId;
31
+
31
32
  const { objId, objIdParseErr } = getObjIdFromParams(root, req);
32
33
  if (objIdParseErr) {
33
34
  return replyWithErr(ERR_SC.malformed, objIdParseErr, res);
@@ -33,6 +33,7 @@ function deleteObj(
33
33
  return async (req: Request, res, next) => {
34
34
 
35
35
  const userId = req.session.params.userId;
36
+
36
37
  const { objId, objIdParseErr } = getObjIdFromParams(root, req);
37
38
  if (objIdParseErr) {
38
39
  return replyWithErr(ERR_SC.malformed, objIdParseErr, res);
@@ -56,6 +56,7 @@ export function getArchivedObjVersion(
56
56
  return async (req: Request, res, next) => {
57
57
 
58
58
  const userId = req.session.params.userId;
59
+
59
60
  const { objId, objIdParseErr } = getObjIdFromParams(root, req);
60
61
  if (objIdParseErr) {
61
62
  return replyWithErr(ERR_SC.malformed, objIdParseErr, res);
@@ -63,6 +63,7 @@ export function getCurrentObj(
63
63
  return async (req: Request, res, next) => {
64
64
 
65
65
  const userId = req.session.params.userId;
66
+
66
67
  const { objId, objIdParseErr } = getObjIdFromParams(root, req);
67
68
  if (objIdParseErr) {
68
69
  return replyWithErr(ERR_SC.malformed, objIdParseErr, res);
@@ -28,6 +28,7 @@ export function getObjStatus(
28
28
  return async (req: Request, res, next) => {
29
29
 
30
30
  const userId = req.session.params.userId;
31
+
31
32
  const { objId, objIdParseErr } = getObjIdFromParams(root, req);
32
33
  if (objIdParseErr) {
33
34
  return replyWithErr(ERR_SC.malformed, objIdParseErr, res);
@@ -24,9 +24,6 @@ export function getParam<T>(
24
24
  paramGetter: (userId: string) => Promise<T>
25
25
  ): RequestHandler {
26
26
 
27
- if ('function' !== typeof paramGetter) { throw new TypeError(
28
- "Given argument 'paramGetter' must be function, but is not."); }
29
-
30
27
  return async (req: Request, res, next) => {
31
28
 
32
29
  const session = req.session;
@@ -37,8 +34,7 @@ export function getParam<T>(
37
34
  res.status(PARAM_SC.ok).json(value);
38
35
  } catch (err) {
39
36
  if (err === storeSC.USER_UNKNOWN) {
40
- res.status(ERR_SC.server).send(
41
- "Recipient disappeared from the system.");
37
+ res.status(ERR_SC.server).send("User disappeared from the system.");
42
38
  session.close();
43
39
  } else {
44
40
  next(err);
@@ -47,6 +47,7 @@ export function saveCurrentObj(
47
47
  }
48
48
 
49
49
  const userId = req.session.params.userId;
50
+
50
51
  const { objId, objIdParseErr } = getObjIdFromParams(root, req);
51
52
  if (objIdParseErr) {
52
53
  return replyWithErr(ERR_SC.malformed, objIdParseErr, res, req);
@@ -37,7 +37,7 @@ export function sessionParams(maxChunk: number|string): RequestHandler {
37
37
  if ("string" !== typeof err) {
38
38
  next(err);
39
39
  } else if (err === storeSC.USER_UNKNOWN) {
40
- res.status(ERR_SC.server).send("Recipient disappeared from the system.");
40
+ res.status(ERR_SC.server).send("User disappeared from the system.");
41
41
  req.session.close();
42
42
  } else {
43
43
  next(new Error("Unhandled storage error code: "+err));
@@ -30,6 +30,16 @@ export function getObjIdFromParams(req: Request): {
30
30
  );
31
31
  }
32
32
 
33
+ export function getMsgIdFromParams(req: Request): {
34
+ msgId: string; msgIdParseErr?: string;
35
+ } {
36
+ const msgId = req.params.msgId;
37
+ return ((!!msgId && base64urlSafe.allCharsFromAlphabet(msgId)) ?
38
+ { msgId } :
39
+ { msgId: undefined as any, msgIdParseErr: "Bad message id" }
40
+ );
41
+ }
42
+
33
43
  export function replyWithErr(status: number, msg: string, res: Response, reqToDrainBytesFrom?: Request): void {
34
44
  if (reqToDrainBytesFrom) {
35
45
  attachByteDrainToRequest(reqToDrainBytesFrom);
@@ -23,6 +23,7 @@ import { Redirect } from './start-session';
23
23
  import { checkAndTransformAddress } from '../../../lib-common/canonical-address';
24
24
  import * as confUtil from '../../../lib-server/conf-util';
25
25
  import { replyWithErr } from '../../resources/utils';
26
+ import { base64urlSafe } from '../../../lib-common/buffer-utils';
26
27
 
27
28
  /**
28
29
  * This creates a session restart route handler.
@@ -84,7 +85,11 @@ export function restartSession(
84
85
  }
85
86
 
86
87
  // check message id
87
- if (typeof msgId !== 'string') {
88
+ if ((typeof msgId !== 'string') || !msgId || !base64urlSafe.allCharsFromAlphabet(msgId)) {
89
+ console.log(
90
+ `msgId ->`, msgId, '\n',
91
+ `!base64urlSafe.allCharsFromAlphabet(msgId) ->`, !base64urlSafe.allCharsFromAlphabet(msgId)
92
+ );
88
93
  return replyWithErr(ERR_SC.malformed, "Message id is either missing, or is malformed", res);
89
94
  }
90
95
 
@@ -19,14 +19,18 @@ import { RequestHandler } from 'express';
19
19
  import { SC as recipSC, MsgRetrieval } from '../../resources/recipients';
20
20
  import { msgMetadata as api, ERR_SC } from '../../../lib-common/service-api/asmail/retrieval';
21
21
  import { Request } from '../../resources/sessions';
22
- import { replyWithErr } from '../../resources/utils';
22
+ import { getMsgIdFromParams, replyWithErr } from '../../resources/utils';
23
23
 
24
24
  export function getMsgMeta(
25
25
  getMsgMetaFunc: MsgRetrieval['getMsgMeta']
26
26
  ): RequestHandler {
27
27
  return async (req: Request, res, next) => {
28
28
  const userId = req.session.params.userId;
29
- const msgId: string = req.params.msgId;
29
+
30
+ const { msgId, msgIdParseErr } = getMsgIdFromParams(req);
31
+ if (msgIdParseErr) {
32
+ return replyWithErr(ERR_SC.malformed, msgIdParseErr, res, req);
33
+ }
30
34
 
31
35
  try {
32
36
  const meta = await getMsgMetaFunc(userId, msgId);
@@ -21,7 +21,7 @@ import { GetObjQueryOpts, ERR_SC, msgObj as api, HTTP_HEADER } from '../../../li
21
21
  import { Request } from '../../resources/sessions';
22
22
  import { errWithCause } from '../../../lib-common/exceptions/error';
23
23
  import { EMPTY_BUFFER } from '../../../lib-common/buffer-utils';
24
- import { getObjIdFromParams, replyWithErr } from '../../resources/utils';
24
+ import { getMsgIdFromParams, getObjIdFromParams, replyWithErr } from '../../resources/utils';
25
25
 
26
26
  function extractQueryOptions(req: Request): undefined|{
27
27
  header: boolean; limit: number|undefined; ofs: number;
@@ -52,7 +52,11 @@ export function getMsgObj(
52
52
  return async (req: Request, res, next) => {
53
53
 
54
54
  const userId = req.session.params.userId;
55
- const msgId: string = req.params.msgId;
55
+
56
+ const { msgId, msgIdParseErr } = getMsgIdFromParams(req);
57
+ if (msgIdParseErr) {
58
+ return replyWithErr(ERR_SC.malformed, msgIdParseErr, res, req);
59
+ }
56
60
 
57
61
  const { objId, objIdParseErr } = getObjIdFromParams(req);
58
62
  if (objIdParseErr) {
@@ -19,12 +19,16 @@ import { RequestHandler } from 'express';
19
19
  import { SC as recipSC, MsgRetrieval } from '../../resources/recipients';
20
20
  import { rmMsg as api, ERR_SC } from '../../../lib-common/service-api/asmail/retrieval';
21
21
  import { Request } from '../../resources/sessions';
22
- import { replyWithErr } from '../../resources/utils';
22
+ import { getMsgIdFromParams, replyWithErr } from '../../resources/utils';
23
23
 
24
24
  export function deleteMsg(delMsgFunc: MsgRetrieval['deleteMsg']): RequestHandler {
25
25
  return async (req: Request, res, next) => {
26
26
  const userId = req.session.params.userId;
27
- const msgId: string = req.params.msgId;
27
+
28
+ const { msgId, msgIdParseErr } = getMsgIdFromParams(req);
29
+ if (msgIdParseErr) {
30
+ return replyWithErr(ERR_SC.malformed, msgIdParseErr, res, req);
31
+ }
28
32
 
29
33
  try {
30
34
  await delMsgFunc(userId, msgId);
package/ts-code/lib.ts CHANGED
@@ -17,7 +17,7 @@
17
17
 
18
18
  export { AppWithWSs } from './lib-server/web-sockets/app';
19
19
 
20
- export { Configurations, servicesApp, accountsApp } from './services';
20
+ export { Configurations, servicesApp, accountsApp, wellKnownApp } from './services';
21
21
 
22
22
  export { addSingleUserSignup } from './config/signup';
23
23
 
package/ts-code/main.ts CHANGED
@@ -25,7 +25,7 @@ import { ServerOptions } from 'https';
25
25
  import { CliUsageDisplay, parseProcessArgv } from './cli';
26
26
  import { readYamlConfFile } from './config/from-yaml';
27
27
  import { sslOptsFromConfig } from './config/letsencrypt';
28
- import { Configurations, servicesApp, accountsApp, AppWithWSs } from './lib';
28
+ import { Configurations, servicesApp, accountsApp, AppWithWSs, wellKnownApp } from './lib';
29
29
  import { addMultiDomainSignup, addSingleUserSignup, readAllSignupTokens, readNoTokensFile, readTokenFile } from './config/signup';
30
30
  import { DEFAULT_CONFIG_PATH } from './config/default-confs';
31
31
  import { Code } from './lib-common/exceptions/file';
@@ -45,6 +45,7 @@ async function run(conf: Configurations): Promise<void> {
45
45
  const app = new AppWithWSs();
46
46
  app.use(servicesApp(conf, 'console', 'console'));
47
47
  app.use(accountsApp(conf, 'console', 'console'));
48
+ app.use(wellKnownApp(conf));
48
49
  return app;
49
50
  }
50
51
 
@@ -73,7 +74,7 @@ async function run(conf: Configurations): Promise<void> {
73
74
  port: number, hostname: string|undefined
74
75
  ): Promise<void> {
75
76
  await app!.start(undefined, port, hostname);
76
- console.log(`🚀 Started 3NWeb server on port ${port},${hostname ? ` hostname ${hostname},` : ''} without TLS, and requiring TLS reverse proxy infront.`);
77
+ console.log(`🚀 Started 3NWeb server on port ${port},${hostname ? ` hostname ${hostname},` : ''} without TLS, and requiring TLS or Tor-like reverse proxy infront.`);
77
78
  }
78
79
 
79
80
  try {
@@ -1,5 +1,5 @@
1
1
  /*
2
- Copyright (C) 2015 - 2017, 2020, 2024 - 2025 3NSoft Inc.
2
+ Copyright (C) 2015 - 2017, 2020, 2024 - 2026 3NSoft Inc.
3
3
 
4
4
  This program is free software: you can redistribute it and/or modify it under
5
5
  the terms of the GNU General Public License as published by the Free Software
@@ -30,6 +30,7 @@ import { makeSignupApp } from './signup/signup-app';
30
30
  import { ErrLogger, makeErrLoggerToConsole } from "./lib-server/middleware/error-handler";
31
31
  import { makeLockerApp } from "./locker/locker-app";
32
32
  import { MidAuthorizer } from "./lib-server/routes/sessions/mid-auth";
33
+ import { makeWellKnownApp } from "./well-known/well-known-route-app";
33
34
 
34
35
  export interface Configurations {
35
36
  enabledServices: {
@@ -56,6 +57,11 @@ export interface Configurations {
56
57
  noTokenFile?: string;
57
58
  serviceUrl?: string;
58
59
  };
60
+ wellKnown3NWeb?: {
61
+ mailerid?: string;
62
+ asmail?: string;
63
+ "3nstorage"?: string;
64
+ };
59
65
  }
60
66
 
61
67
  export function servicesApp(
@@ -135,4 +141,15 @@ export function accountsApp(
135
141
  return app;
136
142
  }
137
143
 
144
+ export function wellKnownApp(conf: Configurations): AppWithWSs {
145
+
146
+ const app = new AppWithWSs();
147
+
148
+ if (conf.wellKnown3NWeb) {
149
+ app.http.use('/.well-known', makeWellKnownApp(conf.wellKnown3NWeb));
150
+ }
151
+
152
+ return app;
153
+ }
154
+
138
155
  Object.freeze(exports);
@@ -25,7 +25,6 @@ import { certify as certApi }
25
25
  import { user as midUser } from '../../lib-common/mid-sigs-NaCl-Ed';
26
26
  import { doPubKeyLogin, User } from './pkl';
27
27
  import { bytesSync as randomBytes } from '../../lib-common/random-node';
28
- import { parse as parseUrl } from 'url';
29
28
 
30
29
  export { User } from './pkl';
31
30
 
@@ -106,7 +105,7 @@ export async function doMailerIdLogin(loginUrl: string, user: User):
106
105
  responseType: 'json',
107
106
  sessionId
108
107
  };
109
- let serviceDomain = parseUrl(loginUrl).hostname;
108
+ let serviceDomain = (new URL(loginUrl)).hostname;
110
109
  if (!serviceDomain) { throw new Error(
111
110
  `Cannot parse hostname from login url ${loginUrl}`); }
112
111
  let req: authSession.Request = {
@@ -23,7 +23,7 @@ import { makeSessionEncryptor, SessionEncryptor }
23
23
  from '../../lib-common/session-encryptor';
24
24
  import { doJsonRequest, RequestOpts, doBinaryRequest }
25
25
  from './xhr-utils';
26
- import { parse as parseUrl, resolve as resolveUrl } from 'url';
26
+ import { resolve as resolveUrl } from 'url';
27
27
  import { bytesEqual } from './bytes-equal';
28
28
 
29
29
  export interface User {
@@ -36,8 +36,7 @@ specs.definition = (setup: () => TestSetup) => (() => {
36
36
  beforeAllAsync(async () => {
37
37
  user2 = setup().user2;
38
38
  const deliveryUrl = await setup().asmailServer.getDeliveryUrl();
39
- initialSession = await startSessionAndSendMsgObjs(
40
- deliveryUrl, user2.id, msg);
39
+ initialSession = await startSessionAndSendMsgObjs(deliveryUrl, user2.id, msg);
41
40
  reqOpts = {
42
41
  url: resolveUrl(deliveryUrl, api.URL_END),
43
42
  method: 'POST',
@@ -74,7 +73,7 @@ specs.definition = (setup: () => TestSetup) => (() => {
74
73
  async () => {
75
74
  const req: api.Request = {
76
75
  recipient: user2.id,
77
- msgId: 'unknown message'
76
+ msgId: 'unknown_message'
78
77
  };
79
78
  const rep = await doJsonRequest<api.Reply>(reqOpts, req);
80
79
  expect(rep.status).withContext('status for unknown message').toBe(api.SC.unknownMsg);
@@ -50,14 +50,13 @@ export const msg: Msg = {
50
50
 
51
51
  export async function startSessionAndSendMsgObjs(deliveryUrl: string,
52
52
  recipient: string, msg: Msg): Promise<DeliveryInfo> {
53
- const sessInfo = await startMsgDelivery(deliveryUrl,
53
+ const sessInfo = await startMsgDelivery(
54
+ deliveryUrl,
54
55
  { recipient },
55
- { pid: msg.cryptoMeta.pid,
56
- objIds: [ msg.msgObjs[0].objId, msg.msgObjs[1].objId ] });
57
- await sendMsgObj(deliveryUrl, sessInfo.sessionId, msg.msgObjs[0],
58
- sessInfo.maxChunkSize);
59
- await sendMsgObj(deliveryUrl, sessInfo.sessionId, msg.msgObjs[1],
60
- sessInfo.maxChunkSize);
56
+ { pid: msg.cryptoMeta.pid, objIds: [ msg.msgObjs[0].objId, msg.msgObjs[1].objId ] }
57
+ );
58
+ await sendMsgObj(deliveryUrl, sessInfo.sessionId, msg.msgObjs[0], sessInfo.maxChunkSize);
59
+ await sendMsgObj(deliveryUrl, sessInfo.sessionId, msg.msgObjs[1], sessInfo.maxChunkSize);
61
60
  return sessInfo;
62
61
  }
63
62
 
@@ -22,7 +22,7 @@ import { startMidSession, provisionMidSigner, User } from '../libs-for-tests/mai
22
22
  import { expectNonAcceptanceOfBadJsonRequest, expectNonAcceptanceOfBadSessionId } from './requests';
23
23
  import * as mid from '../../lib-common/mid-sigs-NaCl-Ed';
24
24
  import { bytesSync as randomBytes } from '../../lib-common/random-node';
25
- import { parse as parseUrl, resolve as resolveUrl } from 'url';
25
+ import { resolve as resolveUrl } from 'url';
26
26
 
27
27
  export function midLoginSpecs(
28
28
  loginUrl: () => string,
@@ -86,7 +86,7 @@ export function midLoginSpecs(
86
86
  sessionId
87
87
  };
88
88
 
89
- const serviceDomain = parseUrl(loginUrl()).hostname;
89
+ const serviceDomain = (new URL(loginUrl())).hostname;
90
90
  if (!serviceDomain) { throw new Error(
91
91
  `Cannot get hostname from ${loginUrl()}`); }
92
92
  const req: authSession.Request = {
@@ -0,0 +1,66 @@
1
+ /*
2
+ Copyright (C) 2026 3NSoft Inc.
3
+
4
+ This program is free software: you can redistribute it and/or modify it under
5
+ the terms of the GNU General Public License as published by the Free Software
6
+ Foundation, either version 3 of the License, or (at your option) any later
7
+ version.
8
+
9
+ This program is distributed in the hope that it will be useful, but
10
+ WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12
+ See the GNU General Public License for more details.
13
+
14
+ You should have received a copy of the GNU General Public License along with
15
+ this program. If not, see <http://www.gnu.org/licenses/>.
16
+ */
17
+
18
+ import * as express from 'express';
19
+ import { Configurations } from '../services';
20
+
21
+ export function makeWellKnownApp(w3n: NonNullable<Configurations['wellKnown3NWeb']>): express.Application {
22
+
23
+ const app = express();
24
+
25
+ const w3nJson = confTo3NWebJSON(w3n);
26
+
27
+ app.get('3nweb.json', (req, res) => {
28
+ // the following implicitly sets content type application/json
29
+ res.status(200).json(w3nJson);
30
+ });
31
+
32
+ return app;
33
+ }
34
+
35
+ interface WellKnown3NWeb {
36
+ mailerid?: string;
37
+ asmail?: string;
38
+ '3nstorage'?: string;
39
+ report?: string;
40
+ 'w3n-app'?: string;
41
+ }
42
+
43
+ function confTo3NWebJSON(conf: NonNullable<Configurations['wellKnown3NWeb']>): WellKnown3NWeb {
44
+ const json: WellKnown3NWeb = {};
45
+ const fields: (keyof WellKnown3NWeb)[] = [ '3nstorage', 'asmail', 'mailerid' ];
46
+ for (const field in fields) {
47
+ const href = conf[field];
48
+ if (href) {
49
+ let u: URL;
50
+ try {
51
+ u = new URL(href);
52
+ } catch (err) {
53
+ console.error(` ❌ Configuration field ${field} for well-known needs potocol section`);
54
+ throw err;
55
+ }
56
+ if ((u.protocol !== 'https:') && (u.protocol !== 'http:')) {
57
+ throw new Error(` ❌ Configuration field ${field} for well-known needs potocol section`);
58
+ }
59
+ json[field] = href;
60
+ }
61
+ }
62
+ return json;
63
+ }
64
+
65
+
66
+ Object.freeze(exports);