@wopr-network/platform-core 1.0.4 → 1.0.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/.env.example ADDED
@@ -0,0 +1,5 @@
1
+ # Platform UI URL (used for redirect allowlist)
2
+ # PLATFORM_UI_URL=https://platform.example.com
3
+
4
+ # Extra allowed redirect origins (comma-separated, for staging/preview environments)
5
+ # EXTRA_ALLOWED_REDIRECT_ORIGINS=https://staging.wopr.bot,https://preview.wopr.bot
@@ -1,10 +1,29 @@
1
1
  const STATIC_ORIGINS = ["https://app.wopr.bot", "https://wopr.network"];
2
+ function parseExtraOrigins() {
3
+ const raw = process.env.EXTRA_ALLOWED_REDIRECT_ORIGINS;
4
+ if (!raw)
5
+ return [];
6
+ return raw
7
+ .split(",")
8
+ .map((s) => s.trim())
9
+ .filter(Boolean)
10
+ .filter((entry) => {
11
+ try {
12
+ new URL(entry);
13
+ return true;
14
+ }
15
+ catch {
16
+ console.warn(`[redirect-allowlist] Malformed entry in EXTRA_ALLOWED_REDIRECT_ORIGINS, skipping: ${entry}`);
17
+ return false;
18
+ }
19
+ });
20
+ }
2
21
  function getAllowedOrigins() {
3
22
  return [
4
23
  ...STATIC_ORIGINS,
5
24
  ...(process.env.NODE_ENV !== "production" ? ["http://localhost:3000", "http://localhost:3001"] : []),
6
25
  ...(process.env.PLATFORM_UI_URL ? [process.env.PLATFORM_UI_URL] : []),
7
- ...(process.env.NODE_ENV !== "production" ? ["https://example.com"] : []),
26
+ ...(process.env.NODE_ENV !== "production" ? parseExtraOrigins() : []),
8
27
  ];
9
28
  }
10
29
  /**
@@ -34,6 +34,40 @@ describe("assertSafeRedirectUrl", () => {
34
34
  it("rejects empty string", () => {
35
35
  expect(() => assertSafeRedirectUrl("")).toThrow("Invalid redirect URL");
36
36
  });
37
+ it("rejects https://example.com", () => {
38
+ expect(() => assertSafeRedirectUrl("https://example.com/callback")).toThrow("Invalid redirect URL");
39
+ });
40
+ describe("EXTRA_ALLOWED_REDIRECT_ORIGINS env-driven entries", () => {
41
+ beforeEach(() => {
42
+ vi.resetModules();
43
+ });
44
+ afterEach(() => {
45
+ delete process.env.EXTRA_ALLOWED_REDIRECT_ORIGINS;
46
+ vi.resetModules();
47
+ });
48
+ it("allows origins listed in EXTRA_ALLOWED_REDIRECT_ORIGINS", async () => {
49
+ process.env.EXTRA_ALLOWED_REDIRECT_ORIGINS = "https://staging.wopr.bot";
50
+ const { assertSafeRedirectUrl: assertSafe } = await import("./redirect-allowlist.js");
51
+ expect(() => assertSafe("https://staging.wopr.bot/billing")).not.toThrow();
52
+ });
53
+ it("allows multiple comma-separated origins", async () => {
54
+ process.env.EXTRA_ALLOWED_REDIRECT_ORIGINS = "https://staging.wopr.bot,https://preview.wopr.bot";
55
+ const { assertSafeRedirectUrl: assertSafe } = await import("./redirect-allowlist.js");
56
+ expect(() => assertSafe("https://staging.wopr.bot/billing")).not.toThrow();
57
+ expect(() => assertSafe("https://preview.wopr.bot/dashboard")).not.toThrow();
58
+ });
59
+ it("ignores empty/whitespace entries in comma-separated list", async () => {
60
+ process.env.EXTRA_ALLOWED_REDIRECT_ORIGINS = "https://staging.wopr.bot, , ,";
61
+ const { assertSafeRedirectUrl: assertSafe } = await import("./redirect-allowlist.js");
62
+ expect(() => assertSafe("https://staging.wopr.bot/billing")).not.toThrow();
63
+ expect(() => assertSafe("https://evil.com/phishing")).toThrow("Invalid redirect URL");
64
+ });
65
+ it("defaults to empty when env var is unset", async () => {
66
+ delete process.env.EXTRA_ALLOWED_REDIRECT_ORIGINS;
67
+ const { assertSafeRedirectUrl: assertSafe } = await import("./redirect-allowlist.js");
68
+ expect(() => assertSafe("https://random.example.org")).toThrow("Invalid redirect URL");
69
+ });
70
+ });
37
71
  describe("PLATFORM_UI_URL env-driven entry", () => {
38
72
  beforeEach(() => {
39
73
  process.env.PLATFORM_UI_URL = "https://platform.example.com";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wopr-network/platform-core",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -46,6 +46,47 @@ describe("assertSafeRedirectUrl", () => {
46
46
  expect(() => assertSafeRedirectUrl("")).toThrow("Invalid redirect URL");
47
47
  });
48
48
 
49
+ it("rejects https://example.com", () => {
50
+ expect(() => assertSafeRedirectUrl("https://example.com/callback")).toThrow("Invalid redirect URL");
51
+ });
52
+
53
+ describe("EXTRA_ALLOWED_REDIRECT_ORIGINS env-driven entries", () => {
54
+ beforeEach(() => {
55
+ vi.resetModules();
56
+ });
57
+
58
+ afterEach(() => {
59
+ delete process.env.EXTRA_ALLOWED_REDIRECT_ORIGINS;
60
+ vi.resetModules();
61
+ });
62
+
63
+ it("allows origins listed in EXTRA_ALLOWED_REDIRECT_ORIGINS", async () => {
64
+ process.env.EXTRA_ALLOWED_REDIRECT_ORIGINS = "https://staging.wopr.bot";
65
+ const { assertSafeRedirectUrl: assertSafe } = await import("./redirect-allowlist.js");
66
+ expect(() => assertSafe("https://staging.wopr.bot/billing")).not.toThrow();
67
+ });
68
+
69
+ it("allows multiple comma-separated origins", async () => {
70
+ process.env.EXTRA_ALLOWED_REDIRECT_ORIGINS = "https://staging.wopr.bot,https://preview.wopr.bot";
71
+ const { assertSafeRedirectUrl: assertSafe } = await import("./redirect-allowlist.js");
72
+ expect(() => assertSafe("https://staging.wopr.bot/billing")).not.toThrow();
73
+ expect(() => assertSafe("https://preview.wopr.bot/dashboard")).not.toThrow();
74
+ });
75
+
76
+ it("ignores empty/whitespace entries in comma-separated list", async () => {
77
+ process.env.EXTRA_ALLOWED_REDIRECT_ORIGINS = "https://staging.wopr.bot, , ,";
78
+ const { assertSafeRedirectUrl: assertSafe } = await import("./redirect-allowlist.js");
79
+ expect(() => assertSafe("https://staging.wopr.bot/billing")).not.toThrow();
80
+ expect(() => assertSafe("https://evil.com/phishing")).toThrow("Invalid redirect URL");
81
+ });
82
+
83
+ it("defaults to empty when env var is unset", async () => {
84
+ delete process.env.EXTRA_ALLOWED_REDIRECT_ORIGINS;
85
+ const { assertSafeRedirectUrl: assertSafe } = await import("./redirect-allowlist.js");
86
+ expect(() => assertSafe("https://random.example.org")).toThrow("Invalid redirect URL");
87
+ });
88
+ });
89
+
49
90
  describe("PLATFORM_UI_URL env-driven entry", () => {
50
91
  beforeEach(() => {
51
92
  process.env.PLATFORM_UI_URL = "https://platform.example.com";
@@ -1,11 +1,29 @@
1
1
  const STATIC_ORIGINS: string[] = ["https://app.wopr.bot", "https://wopr.network"];
2
2
 
3
+ function parseExtraOrigins(): string[] {
4
+ const raw = process.env.EXTRA_ALLOWED_REDIRECT_ORIGINS;
5
+ if (!raw) return [];
6
+ return raw
7
+ .split(",")
8
+ .map((s) => s.trim())
9
+ .filter(Boolean)
10
+ .filter((entry) => {
11
+ try {
12
+ new URL(entry);
13
+ return true;
14
+ } catch {
15
+ console.warn(`[redirect-allowlist] Malformed entry in EXTRA_ALLOWED_REDIRECT_ORIGINS, skipping: ${entry}`);
16
+ return false;
17
+ }
18
+ });
19
+ }
20
+
3
21
  function getAllowedOrigins(): string[] {
4
22
  return [
5
23
  ...STATIC_ORIGINS,
6
24
  ...(process.env.NODE_ENV !== "production" ? ["http://localhost:3000", "http://localhost:3001"] : []),
7
25
  ...(process.env.PLATFORM_UI_URL ? [process.env.PLATFORM_UI_URL] : []),
8
- ...(process.env.NODE_ENV !== "production" ? ["https://example.com"] : []),
26
+ ...(process.env.NODE_ENV !== "production" ? parseExtraOrigins() : []),
9
27
  ];
10
28
  }
11
29