@prosopo/client-example-server 2.7.123 → 2.7.124

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 (59) hide show
  1. package/.turbo/turbo-build$colon$cjs.log +4 -4
  2. package/.turbo/turbo-build$colon$tsc.log +21 -21
  3. package/.turbo/turbo-build.log +6 -6
  4. package/CHANGELOG.md +15 -0
  5. package/dist/app.d.ts +15 -1
  6. package/dist/app.d.ts.map +1 -1
  7. package/dist/app.js +71 -39
  8. package/dist/app.js.map +1 -1
  9. package/dist/cjs/app.cjs +79 -37
  10. package/dist/cjs/controllers/auth.cjs +11 -9
  11. package/dist/cjs/routes/routes.cjs +1 -1
  12. package/dist/controllers/auth.d.ts +1 -1
  13. package/dist/controllers/auth.d.ts.map +1 -1
  14. package/dist/controllers/auth.js +11 -9
  15. package/dist/controllers/auth.js.map +1 -1
  16. package/dist/routes/routes.d.ts.map +1 -1
  17. package/dist/routes/routes.js +1 -1
  18. package/dist/routes/routes.js.map +1 -1
  19. package/dist/tests/app.unit.test.d.ts +2 -0
  20. package/dist/tests/app.unit.test.d.ts.map +1 -0
  21. package/dist/tests/app.unit.test.js +318 -0
  22. package/dist/tests/app.unit.test.js.map +1 -0
  23. package/dist/tests/appEntrypoint.unit.test.d.ts +2 -0
  24. package/dist/tests/appEntrypoint.unit.test.d.ts.map +1 -0
  25. package/dist/tests/appEntrypoint.unit.test.js +100 -0
  26. package/dist/tests/appEntrypoint.unit.test.js.map +1 -0
  27. package/dist/tests/auth.unit.test.d.ts +2 -0
  28. package/dist/tests/auth.unit.test.d.ts.map +1 -0
  29. package/dist/tests/auth.unit.test.js +426 -0
  30. package/dist/tests/auth.unit.test.js.map +1 -0
  31. package/dist/tests/authHarness.d.ts +40 -0
  32. package/dist/tests/authHarness.d.ts.map +1 -0
  33. package/dist/tests/authHarness.js +57 -0
  34. package/dist/tests/authHarness.js.map +1 -0
  35. package/dist/tests/clientExampleServer.test-d.d.ts +2 -0
  36. package/dist/tests/clientExampleServer.test-d.d.ts.map +1 -0
  37. package/dist/tests/clientExampleServer.test-d.js +84 -0
  38. package/dist/tests/clientExampleServer.test-d.js.map +1 -0
  39. package/dist/tests/routes.unit.test.d.ts +2 -0
  40. package/dist/tests/routes.unit.test.d.ts.map +1 -0
  41. package/dist/tests/routes.unit.test.js +123 -0
  42. package/dist/tests/routes.unit.test.js.map +1 -0
  43. package/dist/tests/utils.unit.test.d.ts +2 -0
  44. package/dist/tests/utils.unit.test.d.ts.map +1 -0
  45. package/dist/tests/utils.unit.test.js +125 -0
  46. package/dist/tests/utils.unit.test.js.map +1 -0
  47. package/package.json +6 -3
  48. package/src/app.ts +114 -73
  49. package/src/controllers/auth.ts +40 -31
  50. package/src/routes/routes.ts +5 -2
  51. package/src/tests/app.unit.test.ts +428 -0
  52. package/src/tests/appEntrypoint.unit.test.ts +154 -0
  53. package/src/tests/auth.unit.test.ts +859 -0
  54. package/src/tests/authHarness.ts +131 -0
  55. package/src/tests/clientExampleServer.test-d.ts +146 -0
  56. package/src/tests/routes.unit.test.ts +166 -0
  57. package/src/tests/utils.unit.test.ts +169 -0
  58. package/tsconfig.tsbuildinfo +1 -1
  59. package/vite.test.config.ts +18 -0
@@ -0,0 +1,84 @@
1
+ import { assertType, describe, expectTypeOf, test } from "vitest";
2
+ import { createApp, isDevelopmentOrTest, main, resolvePort, resolveVerifyEndpoint, resolveVerifyType, startServer, } from "../app.js";
3
+ import { isAuth, login, signup } from "../controllers/auth.js";
4
+ import getRoutes from "../routes/routes.js";
5
+ import connectionFactory from "../utils/connection.js";
6
+ import memoryServerSetup from "../utils/database.js";
7
+ describe("the app module's types", () => {
8
+ test("the environment readers take no arguments", () => {
9
+ expectTypeOf(resolveVerifyEndpoint).parameters.toEqualTypeOf();
10
+ expectTypeOf(resolveVerifyEndpoint).returns.toEqualTypeOf();
11
+ expectTypeOf(resolveVerifyType).returns.toEqualTypeOf();
12
+ expectTypeOf(isDevelopmentOrTest).returns.toEqualTypeOf();
13
+ });
14
+ test("the verification type is a closed set", () => {
15
+ expectTypeOf().toEqualTypeOf();
16
+ assertType("carrier-pigeon");
17
+ });
18
+ test("the port comes from the shared server config, not a loose object", () => {
19
+ expectTypeOf(resolvePort)
20
+ .parameter(0)
21
+ .toEqualTypeOf();
22
+ expectTypeOf(resolvePort).returns.toEqualTypeOf();
23
+ });
24
+ test("createApp yields an express app the caller can mount routes on", () => {
25
+ expectTypeOf(createApp).returns.toEqualTypeOf();
26
+ });
27
+ test("startServer's module directory is optional and it returns a server", () => {
28
+ expectTypeOf(startServer).parameter(1).toEqualTypeOf();
29
+ expectTypeOf(startServer).parameter(2).toEqualTypeOf();
30
+ expectTypeOf(startServer).returns.toEqualTypeOf();
31
+ });
32
+ test("main resolves to the server it started, so a caller can close it", () => {
33
+ expectTypeOf(main).parameters.toEqualTypeOf();
34
+ expectTypeOf(main).returns.toEqualTypeOf();
35
+ });
36
+ });
37
+ describe("the router's types", () => {
38
+ test("routes are built from a connection, config and verification settings", () => {
39
+ expectTypeOf(getRoutes).parameters.toEqualTypeOf();
40
+ expectTypeOf(getRoutes).returns.toEqualTypeOf();
41
+ });
42
+ test("every argument is required", () => {
43
+ getRoutes({}, {}, "endpoint");
44
+ });
45
+ });
46
+ describe("the controllers' types", () => {
47
+ test("signup and login are express handlers with their context bound first", () => {
48
+ expectTypeOf(signup).parameter(0).toEqualTypeOf();
49
+ expectTypeOf(signup)
50
+ .parameter(1)
51
+ .toEqualTypeOf();
52
+ expectTypeOf(signup).parameter(2).toEqualTypeOf();
53
+ expectTypeOf(signup).parameter(3).toEqualTypeOf();
54
+ expectTypeOf(signup).returns.toExtend();
55
+ expectTypeOf(login).parameter(0).toEqualTypeOf();
56
+ expectTypeOf(login).returns.toEqualTypeOf();
57
+ });
58
+ test("isAuth needs nothing bound, so it mounts directly", () => {
59
+ expectTypeOf(isAuth).parameters.toEqualTypeOf();
60
+ });
61
+ });
62
+ describe("the persistence types", () => {
63
+ test("connectionFactory takes a URI and returns a mongoose connection", () => {
64
+ expectTypeOf(connectionFactory).parameters.toEqualTypeOf();
65
+ expectTypeOf(connectionFactory).returns.toEqualTypeOf();
66
+ });
67
+ test("the memory server hands back a URI string", () => {
68
+ expectTypeOf(memoryServerSetup).parameters.toEqualTypeOf();
69
+ expectTypeOf(memoryServerSetup).returns.toEqualTypeOf();
70
+ });
71
+ test("the user schema is typed by the interface it stores", () => {
72
+ expectTypeOf().toEqualTypeOf();
73
+ expectTypeOf().toEqualTypeOf();
74
+ });
75
+ test("a user document cannot be built without credentials", () => {
76
+ assertType({
77
+ id: 1,
78
+ email: "user@example.com",
79
+ name: "user",
80
+ password: "0xhash",
81
+ });
82
+ });
83
+ });
84
+ //# sourceMappingURL=clientExampleServer.test-d.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clientExampleServer.test-d.js","sourceRoot":"","sources":["../../src/tests/clientExampleServer.test-d.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=routes.unit.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.unit.test.d.ts","sourceRoot":"","sources":["../../src/tests/routes.unit.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,123 @@
1
+ import express from "express";
2
+ import { afterEach, describe, expect, test, vi } from "vitest";
3
+ import getRoutes from "../routes/routes.js";
4
+ import { VERIFY_ENDPOINT, serverConfig } from "./authHarness.js";
5
+ const mocks = vi.hoisted(() => ({
6
+ calls: [],
7
+ }));
8
+ vi.mock("../controllers/auth.js", () => {
9
+ const record = (handler) => (...args) => {
10
+ mocks.calls.push({ handler, args: args.slice(0, 4) });
11
+ const res = args[args.length - 2];
12
+ res.status(200).json({ handler });
13
+ };
14
+ return {
15
+ signup: record("signup"),
16
+ login: record("login"),
17
+ isAuth: (_req, res) => res
18
+ .status(200)
19
+ .json({ handler: "isAuth" }),
20
+ };
21
+ });
22
+ const connection = {};
23
+ let server;
24
+ const listen = () => {
25
+ const app = express();
26
+ app.use(express.json());
27
+ app.use(getRoutes(connection, serverConfig(), VERIFY_ENDPOINT, "api"));
28
+ return new Promise((resolve) => {
29
+ server = app.listen(0, () => {
30
+ const { port } = server?.address();
31
+ resolve(`http://127.0.0.1:${port}`);
32
+ });
33
+ });
34
+ };
35
+ afterEach(async () => {
36
+ mocks.calls.length = 0;
37
+ await new Promise((resolve) => {
38
+ if (!server)
39
+ return resolve();
40
+ server.close(() => resolve());
41
+ server = undefined;
42
+ });
43
+ });
44
+ describe("the router", () => {
45
+ test("serves the public resource without any authentication", async () => {
46
+ const base = await listen();
47
+ const response = await fetch(`${base}/public`);
48
+ expect(response.status).toBe(200);
49
+ expect(await response.json()).toEqual({
50
+ message: "here is your public resource",
51
+ });
52
+ });
53
+ test("answers health checks", async () => {
54
+ const base = await listen();
55
+ const response = await fetch(`${base}/health`);
56
+ expect(response.status).toBe(200);
57
+ expect(await response.json()).toEqual({ status: "ok" });
58
+ });
59
+ test("routes a signup to the signup handler, with the bound context", async () => {
60
+ const base = await listen();
61
+ const response = await fetch(`${base}/signup`, {
62
+ method: "POST",
63
+ headers: { "content-type": "application/json" },
64
+ body: JSON.stringify({ email: "user@example.com" }),
65
+ });
66
+ expect(response.status).toBe(200);
67
+ expect(mocks.calls[0]?.handler).toBe("signup");
68
+ expect(mocks.calls[0]?.args).toEqual([
69
+ connection,
70
+ serverConfig(),
71
+ VERIFY_ENDPOINT,
72
+ "api",
73
+ ]);
74
+ });
75
+ test("routes a login to the login handler", async () => {
76
+ const base = await listen();
77
+ await fetch(`${base}/login`, {
78
+ method: "POST",
79
+ headers: { "content-type": "application/json" },
80
+ body: JSON.stringify({}),
81
+ });
82
+ expect(mocks.calls[0]?.handler).toBe("login");
83
+ });
84
+ test("guards the private resource", async () => {
85
+ const base = await listen();
86
+ const response = await fetch(`${base}/private`);
87
+ expect(await response.json()).toEqual({ handler: "isAuth" });
88
+ });
89
+ test("answers anything else with a 404 body, not express's HTML", async () => {
90
+ const base = await listen();
91
+ const response = await fetch(`${base}/nope`);
92
+ expect(response.status).toBe(404);
93
+ expect(await response.json()).toEqual({ error: "page not found" });
94
+ });
95
+ test("a GET to a POST-only path falls through to the catch-all", async () => {
96
+ const base = await listen();
97
+ const response = await fetch(`${base}/login`);
98
+ expect(response.status).toBe(404);
99
+ expect(mocks.calls).toHaveLength(0);
100
+ });
101
+ test("each call builds a fresh router", async () => {
102
+ const first = getRoutes(connection, serverConfig(), "https://one", "api");
103
+ const second = getRoutes(connection, serverConfig(), "https://two", "api");
104
+ expect(first).not.toBe(second);
105
+ const app = express();
106
+ app.use(express.json());
107
+ app.use(second);
108
+ const base = await new Promise((resolve) => {
109
+ server = app.listen(0, () => {
110
+ const { port } = server?.address();
111
+ resolve(`http://127.0.0.1:${port}`);
112
+ });
113
+ });
114
+ await fetch(`${base}/signup`, {
115
+ method: "POST",
116
+ headers: { "content-type": "application/json" },
117
+ body: JSON.stringify({}),
118
+ });
119
+ expect(mocks.calls).toHaveLength(1);
120
+ expect(mocks.calls[0]?.args[2]).toBe("https://two");
121
+ });
122
+ });
123
+ //# sourceMappingURL=routes.unit.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.unit.test.js","sourceRoot":"","sources":["../../src/tests/routes.unit.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=utils.unit.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.unit.test.d.ts","sourceRoot":"","sources":["../../src/tests/utils.unit.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,125 @@
1
+ import { beforeEach, describe, expect, test, vi } from "vitest";
2
+ import UserSchema from "../models/user.js";
3
+ import connectionFactory from "../utils/connection.js";
4
+ import memoryServerSetup from "../utils/database.js";
5
+ const mocks = vi.hoisted(() => ({
6
+ createConnection: vi.fn(),
7
+ models: {},
8
+ model: vi.fn(),
9
+ plugin: vi.fn(),
10
+ connectionOptions: vi.fn(),
11
+ memoryUri: "mongodb://memory:27017/",
12
+ memoryCreate: vi.fn(),
13
+ }));
14
+ vi.mock("mongoose", async (importOriginal) => {
15
+ const actual = await importOriginal();
16
+ return {
17
+ ...actual,
18
+ default: {
19
+ ...actual.default,
20
+ createConnection: (...args) => {
21
+ mocks.createConnection(...args);
22
+ return {
23
+ models: mocks.models,
24
+ model: mocks.model,
25
+ };
26
+ },
27
+ },
28
+ };
29
+ });
30
+ vi.mock("@typegoose/auto-increment", () => ({
31
+ AutoIncrementID: "auto-increment-plugin",
32
+ }));
33
+ vi.mock("@prosopo/database", async (importOriginal) => {
34
+ const actual = await importOriginal();
35
+ return {
36
+ ...actual,
37
+ getMongoConnectionOptions: (args) => {
38
+ mocks.connectionOptions(args);
39
+ return { appName: args.appName };
40
+ },
41
+ };
42
+ });
43
+ vi.mock("mongodb-memory-server", () => ({
44
+ MongoMemoryServer: {
45
+ create: () => {
46
+ mocks.memoryCreate();
47
+ return Promise.resolve({ getUri: () => mocks.memoryUri });
48
+ },
49
+ },
50
+ }));
51
+ beforeEach(() => {
52
+ mocks.createConnection.mockClear();
53
+ mocks.model.mockClear();
54
+ mocks.plugin.mockClear();
55
+ mocks.connectionOptions.mockClear();
56
+ mocks.memoryCreate.mockClear();
57
+ mocks.models = {};
58
+ vi.spyOn(UserSchema, "plugin").mockImplementation((...args) => {
59
+ mocks.plugin(...args);
60
+ return UserSchema;
61
+ });
62
+ });
63
+ describe("connectionFactory", () => {
64
+ const URI = "mongodb://localhost:27017/client-example";
65
+ test("connects to the URI it was given", () => {
66
+ connectionFactory(URI);
67
+ expect(mocks.createConnection).toHaveBeenCalledTimes(1);
68
+ expect(mocks.createConnection.mock.calls[0]?.[0]).toBe(URI);
69
+ });
70
+ test("names the connection after the module, for mongo's server logs", () => {
71
+ connectionFactory(URI);
72
+ const options = mocks.createConnection.mock.calls[0]?.[1];
73
+ expect(options.appName).toContain("connection");
74
+ expect(mocks.connectionOptions).toHaveBeenCalledWith({
75
+ url: URI,
76
+ appName: options.appName,
77
+ });
78
+ });
79
+ test("registers the User model with auto-incrementing ids", () => {
80
+ connectionFactory(URI);
81
+ expect(mocks.plugin).toHaveBeenCalledWith("auto-increment-plugin", {
82
+ field: "id",
83
+ });
84
+ expect(mocks.model).toHaveBeenCalledWith("User", UserSchema);
85
+ });
86
+ test("leaves an already-registered model alone", () => {
87
+ mocks.models = { user: {} };
88
+ connectionFactory(URI);
89
+ expect(mocks.model).not.toHaveBeenCalled();
90
+ expect(mocks.plugin).not.toHaveBeenCalled();
91
+ });
92
+ test("returns the connection it created", () => {
93
+ const connection = connectionFactory(URI);
94
+ expect(connection.model).toBe(mocks.model);
95
+ });
96
+ });
97
+ describe("memoryServerSetup", () => {
98
+ test("boots an in-memory mongo and returns its URI", async () => {
99
+ await expect(memoryServerSetup()).resolves.toBe(mocks.memoryUri);
100
+ expect(mocks.memoryCreate).toHaveBeenCalledTimes(1);
101
+ });
102
+ test("each call gets its own server", async () => {
103
+ await memoryServerSetup();
104
+ await memoryServerSetup();
105
+ expect(mocks.memoryCreate).toHaveBeenCalledTimes(2);
106
+ });
107
+ });
108
+ describe("the User schema", () => {
109
+ test("requires the fields a login needs to work", () => {
110
+ for (const field of ["email", "password", "salt"]) {
111
+ expect(UserSchema.path(field).isRequired).toBe(true);
112
+ }
113
+ });
114
+ test("leaves name and id optional", () => {
115
+ expect(UserSchema.path("name").isRequired).toBeFalsy();
116
+ expect(UserSchema.path("id").isRequired).toBeFalsy();
117
+ });
118
+ test("stores the id as a number and the rest as strings", () => {
119
+ expect(UserSchema.path("id").instance).toBe("Number");
120
+ for (const field of ["email", "name", "password", "salt"]) {
121
+ expect(UserSchema.path(field).instance).toBe("String");
122
+ }
123
+ });
124
+ });
125
+ //# sourceMappingURL=utils.unit.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.unit.test.js","sourceRoot":"","sources":["../../src/tests/utils.unit.test.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prosopo/client-example-server",
3
- "version": "2.7.123",
3
+ "version": "2.7.124",
4
4
  "description": "Backend for client-example",
5
5
  "main": "dist/app.js",
6
6
  "types": "dist/app.d.ts",
@@ -25,6 +25,9 @@
25
25
  "build:tsc": "tsc --build --verbose",
26
26
  "build:cjs": "NODE_ENV=${NODE_ENV:-development}; vite build --config vite.cjs.config.ts --mode $NODE_ENV",
27
27
  "typecheck": "tsc --project tsconfig.types.json",
28
+ "test": "NODE_ENV=${NODE_ENV:-test}; npx vitest run --config ./vite.test.config.ts",
29
+ "test:watch": "NODE_ENV=${NODE_ENV:-test}; npx vitest --config ./vite.test.config.ts",
30
+ "test:coverage": "NODE_ENV=${NODE_ENV:-test}; npx vitest run --coverage --config ./vite.test.config.ts",
28
31
  "bundle": "NODE_ENV=${NODE_ENV:-development}; vite build --config vite.config.ts --mode $NODE_ENV"
29
32
  },
30
33
  "repository": {
@@ -43,11 +46,11 @@
43
46
  "@polkadot/util": "13.5.7",
44
47
  "@polkadot/util-crypto": "13.5.7",
45
48
  "@prosopo/common": "3.1.48",
46
- "@prosopo/database": "3.15.20",
49
+ "@prosopo/database": "3.15.21",
47
50
  "@prosopo/logger": "2.0.4",
48
51
  "@prosopo/dotenv": "3.0.50",
49
52
  "@prosopo/keyring": "2.9.61",
50
- "@prosopo/server": "2.10.41",
53
+ "@prosopo/server": "2.10.42",
51
54
  "@prosopo/types": "4.10.0",
52
55
  "@prosopo/util": "3.3.5",
53
56
  "@typegoose/auto-increment": "4.13.0",
package/src/app.ts CHANGED
@@ -14,6 +14,7 @@
14
14
 
15
15
  import fs from "node:fs";
16
16
  import http from "node:http";
17
+ import type { Server } from "node:http";
17
18
  import https from "node:https";
18
19
  import type { ServerOptions } from "node:https";
19
20
  import path from "node:path";
@@ -22,41 +23,50 @@ import { ProsopoEnvError } from "@prosopo/common";
22
23
  import { loadEnv } from "@prosopo/dotenv";
23
24
  import { getLogger } from "@prosopo/logger";
24
25
  import { getServerConfig } from "@prosopo/server";
25
- import { at } from "@prosopo/util";
26
+ import type { ProsopoServerConfigOutput } from "@prosopo/types";
27
+ import { at, isMain } from "@prosopo/util";
26
28
  import cors from "cors";
27
29
  import express from "express";
28
30
  import routesFactory from "./routes/routes.js";
29
31
  import connectionFactory from "./utils/connection.js";
30
32
  import memoryServerSetup from "./utils/database.js";
31
33
 
32
- loadEnv();
33
-
34
- enum ProsopoVerificationType {
34
+ export enum ProsopoVerificationType {
35
35
  api = "api",
36
36
  local = "local",
37
37
  }
38
38
 
39
39
  const logger = getLogger("info", "client-example-server:app");
40
40
 
41
- async function main() {
42
- loadEnv();
43
-
41
+ /**
42
+ * The siteverify endpoint the deployment talks to. Non-production
43
+ * environments get their name as a subdomain prefix. An unset NODE_ENV is
44
+ * treated as production: prefixing it produced `undefined-api.prosopo.io`,
45
+ * a host that does not exist.
46
+ */
47
+ export const resolveVerifyEndpoint = (): string => {
48
+ const environment = process.env.NODE_ENV;
44
49
  const apiPrefix =
45
- process.env.NODE_ENV && process.env.NODE_ENV === "production"
46
- ? ""
47
- : `${process.env.NODE_ENV}-`;
48
- const verifyEndpoint =
50
+ !environment || environment === "production" ? "" : `${environment}-`;
51
+ return (
49
52
  process.env.PROSOPO_VERIFY_ENDPOINT ||
50
- `https://${apiPrefix}api.prosopo.io/siteverify`;
51
-
52
- logger.info(() => ({ data: { verifyEndpoint } }));
53
-
54
- const verifyType: ProsopoVerificationType = Object.keys(
55
- ProsopoVerificationType,
56
- ).includes(process.env.PROSOPO_VERIFICATION_TYPE as string)
53
+ `https://${apiPrefix}api.prosopo.io/siteverify`
54
+ );
55
+ };
56
+
57
+ /** Anything the enum doesn't name falls back to the hosted API. */
58
+ export const resolveVerifyType = (): ProsopoVerificationType =>
59
+ Object.keys(ProsopoVerificationType).includes(
60
+ process.env.PROSOPO_VERIFICATION_TYPE as string,
61
+ )
57
62
  ? (process.env.PROSOPO_VERIFICATION_TYPE as ProsopoVerificationType)
58
63
  : ProsopoVerificationType.api;
59
64
 
65
+ export const isDevelopmentOrTest = (): boolean =>
66
+ process.env.NODE_ENV === "development" || process.env.NODE_ENV === "test";
67
+
68
+ /** The express app, minus the routes, which need a database connection. */
69
+ export const createApp = (): express.Express => {
60
70
  const app = express();
61
71
 
62
72
  // https://express-rate-limit.mintlify.app/guides/troubleshooting-proxy-issues
@@ -81,15 +91,79 @@ async function main() {
81
91
  next();
82
92
  });
83
93
 
84
- app.options("/*", (_, res) => {
85
- res.sendStatus(200);
94
+ // No explicit OPTIONS handler: cors() above already answers preflight
95
+ // requests with a 204 and never calls through, so the handler that used to
96
+ // sit here could not run.
97
+
98
+ return app;
99
+ };
100
+
101
+ export const DEFAULT_PORT = 9228;
102
+
103
+ /**
104
+ * The port is the last segment of the configured server URL. A URL without
105
+ * one used to yield NaN, which node turns into an arbitrary free port — the
106
+ * server came up on an address nothing was configured to talk to.
107
+ */
108
+ export const resolvePort = (config: ProsopoServerConfigOutput): number => {
109
+ if (!config.serverUrl) return DEFAULT_PORT;
110
+ const segments = config.serverUrl.split(":");
111
+ if (segments.length < 3) return DEFAULT_PORT;
112
+ const port = Number.parseInt(at(segments, 2));
113
+ return Number.isNaN(port) ? DEFAULT_PORT : port;
114
+ };
115
+
116
+ /**
117
+ * HTTPS in development/test when the repo's certificates exist, plain HTTP
118
+ * otherwise — in production Caddy terminates TLS.
119
+ */
120
+ export const startServer = (
121
+ app: express.Express,
122
+ port: number,
123
+ dirname: string = path.dirname(fileURLToPath(import.meta.url)),
124
+ ): Server => {
125
+ if (isDevelopmentOrTest()) {
126
+ const certsDir = path.resolve(dirname, "../../../certs");
127
+
128
+ const keyPath = path.join(certsDir, "server.key");
129
+ const certPath = path.join(certsDir, "server.crt");
130
+
131
+ if (fs.existsSync(keyPath) && fs.existsSync(certPath)) {
132
+ const httpsOptions: ServerOptions = {
133
+ key: fs.readFileSync(keyPath),
134
+ cert: fs.readFileSync(certPath),
135
+ };
136
+
137
+ return https.createServer(httpsOptions, app).listen(port, () => {
138
+ logger.info(() => ({ msg: `HTTPS server started on port ${port}` }));
139
+ });
140
+ }
141
+ logger.warn(() => ({
142
+ msg: "Certificates not found, starting HTTP server instead. Run ./setup_certs.sh to enable HTTPS in development.",
143
+ }));
144
+ return http.createServer(app).listen(port, () => {
145
+ logger.info(() => ({ msg: `HTTP server started on port ${port}` }));
146
+ });
147
+ }
148
+ return http.createServer(app).listen(port, () => {
149
+ logger.info(() => ({
150
+ msg: `HTTP server started on port ${port} (TLS handled by reverse proxy)`,
151
+ }));
86
152
  });
153
+ };
154
+
155
+ export async function main(): Promise<Server> {
156
+ loadEnv();
87
157
 
88
- if (
89
- !process.env.MONGO_URI &&
90
- process.env.NODE_ENV !== "development" &&
91
- process.env.NODE_ENV !== "test"
92
- ) {
158
+ const verifyEndpoint = resolveVerifyEndpoint();
159
+
160
+ logger.info(() => ({ data: { verifyEndpoint } }));
161
+
162
+ const verifyType = resolveVerifyType();
163
+
164
+ const app = createApp();
165
+
166
+ if (!process.env.MONGO_URI && !isDevelopmentOrTest()) {
93
167
  throw new Error(
94
168
  "Cannot run mongo memory when NODE_ENV is neither development nor test",
95
169
  );
@@ -113,57 +187,24 @@ async function main() {
113
187
 
114
188
  app.use(routesFactory(mongoose, config, verifyEndpoint, verifyType));
115
189
 
116
- const port = config.serverUrl
117
- ? Number.parseInt(at(config.serverUrl.split(":"), 2))
118
- : 9228;
190
+ const port = resolvePort(config);
119
191
 
120
192
  logger.info(() => ({ msg: "Listening on port", data: { port } }));
121
193
 
122
- // Use HTTPS only in development/test when we have certificates
123
- // In production, use HTTP because Caddy handles TLS termination
124
- const isDev =
125
- process.env.NODE_ENV === "development" || process.env.NODE_ENV === "test";
126
-
127
- if (isDev) {
128
- const __filename = fileURLToPath(import.meta.url);
129
- const __dirname = path.dirname(__filename);
130
- const certsDir = path.resolve(__dirname, "../../../certs");
131
-
132
- const keyPath = path.join(certsDir, "server.key");
133
- const certPath = path.join(certsDir, "server.crt");
134
-
135
- if (fs.existsSync(keyPath) && fs.existsSync(certPath)) {
136
- const httpsOptions: ServerOptions = {
137
- key: fs.readFileSync(keyPath),
138
- cert: fs.readFileSync(certPath),
139
- };
194
+ return startServer(app, port);
195
+ }
140
196
 
141
- https.createServer(httpsOptions, app).listen(port, () => {
142
- logger.info(() => ({ msg: `HTTPS server started on port ${port}` }));
143
- });
144
- } else {
145
- logger.warn(() => ({
146
- msg: "Certificates not found, starting HTTP server instead. Run ./setup_certs.sh to enable HTTPS in development.",
147
- }));
148
- http.createServer(app).listen(port, () => {
149
- logger.info(() => ({ msg: `HTTP server started on port ${port}` }));
150
- });
151
- }
152
- } else {
153
- // Production: use plain HTTP, Caddy handles TLS
154
- http.createServer(app).listen(port, () => {
155
- logger.info(() => ({
156
- msg: `HTTP server started on port ${port} (TLS handled by reverse proxy)`,
157
- }));
197
+ // Only boot when run as a binary: importing this module (a test, or another
198
+ // entrypoint reusing createApp) must not open a port or spin up mongo.
199
+ if (isMain(import.meta.url)) {
200
+ main()
201
+ .then(() => {
202
+ logger.info(() => ({ msg: "Server started" }));
203
+ })
204
+ .catch((err) => {
205
+ logger.error(() => ({ err }));
206
+ // Non-zero, so a boot failure is visible to whatever supervises the
207
+ // process rather than reading as a clean shutdown.
208
+ process.exit(1);
158
209
  });
159
- }
160
210
  }
161
-
162
- main()
163
- .then(() => {
164
- logger.info(() => ({ msg: "Server started" }));
165
- })
166
- .catch((err) => {
167
- logger.error(() => ({ err }));
168
- process.exit();
169
- });