Skip to content

Commit 7eb9898

Browse files
committed
refactor(config): centralize configuration
- Add CODEBAR_AUTH_URL for OAuth callback (not BETTER_AUTH_URL) - Add wildcard support in redirect validation - Add unit tests for redirect validation (15 tests, 93% coverage) - Remove unused host config (not used for server binding)
1 parent 772aca4 commit 7eb9898

5 files changed

Lines changed: 119 additions & 10 deletions

File tree

src/app/utils/redirect.js

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
import config from "../../config.js";
22

3-
/**
4-
* Checks if a redirect URL is valid (in the allowlist)
5-
* @param {string} redirectUrl - The URL to validate
6-
* @returns {boolean} - True if valid, false otherwise
7-
*/
3+
const matchesGlob = (url, pattern) => {
4+
if (!pattern.includes("*")) return false;
5+
const escaped = pattern
6+
.replace(/[.+?^${}()|[\]\\]/g, "\\$&")
7+
.replace(/\*/g, ".*");
8+
const regex = new RegExp(`^${escaped}$`);
9+
return regex.test(url);
10+
};
11+
812
const isValidRedirectUrl = (redirectUrl) => {
913
if (!redirectUrl || typeof redirectUrl !== "string") {
1014
return false;
@@ -15,7 +19,9 @@ const isValidRedirectUrl = (redirectUrl) => {
1519
return false;
1620
}
1721

18-
return config.allowed_redirects.includes(trimmed);
22+
return config.allowed_redirects.some(
23+
(pattern) => trimmed === pattern || matchesGlob(trimmed, pattern),
24+
);
1925
};
2026

2127
/**

src/auth.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export { db };
3737

3838
export const auth = betterAuth({
3939
database: db,
40-
baseURL: `http://${appConfig.host}:${appConfig.port}`,
40+
baseURL: appConfig.base_url,
4141
logger: {
4242
disabled: false,
4343
level: "debug",

src/config.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
const config = {
22
port: process.env.PORT || 3000,
3-
host: "localhost",
3+
base_url: process.env.CODEBAR_AUTH_URL || "http://localhost:3000",
44
database_url: process.env.DATABASE_URL || "./auth.db",
5-
// TODO(till): a wildcard pattern would be nice here
65
allowed_redirects: ["http://localhost:3000/demo"],
76
social: {
87
github: {

src/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ const server = serve(
1111
port: appConfig.port,
1212
},
1313
(info) => {
14-
console.log(`Server is running on http://${appConfig.host}:${info.port}`);
14+
console.log(`Server is running on ${appConfig.base_url}`);
1515
},
1616
);
1717

test/unit/redirect.test.js

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import { test } from "tap";
2+
import { validateRedirectUrl } from "../../src/app/utils/redirect.js";
3+
import config from "../../src/config.js";
4+
5+
const original = config.allowed_redirects;
6+
7+
test("redirect validation - exact match", async (t) => {
8+
config.allowed_redirects = ["http://localhost:3000/demo"];
9+
10+
t.after(() => {
11+
config.allowed_redirects = original;
12+
});
13+
14+
t.equal(
15+
validateRedirectUrl("http://localhost:3000/demo"),
16+
"http://localhost:3000/demo",
17+
);
18+
t.equal(validateRedirectUrl("http://evil.com/steal"), "/profile");
19+
t.equal(validateRedirectUrl(""), "/profile");
20+
t.equal(validateRedirectUrl(null), "/profile");
21+
t.equal(validateRedirectUrl(" "), "/profile");
22+
});
23+
24+
test("allows subdomain wildcard", async (t) => {
25+
config.allowed_redirects = ["https://*.codebar.io"];
26+
27+
t.after(() => {
28+
config.allowed_redirects = original;
29+
});
30+
31+
t.equal(
32+
validateRedirectUrl("https://auth.codebar.io"),
33+
"https://auth.codebar.io",
34+
);
35+
t.equal(
36+
validateRedirectUrl("https://staging.codebar.io"),
37+
"https://staging.codebar.io",
38+
);
39+
t.equal(
40+
validateRedirectUrl("https://app.codebar.io"),
41+
"https://app.codebar.io",
42+
);
43+
});
44+
45+
test("rejects non-matching domain with wildcard", async (t) => {
46+
config.allowed_redirects = ["https://*.codebar.io"];
47+
48+
t.after(() => {
49+
config.allowed_redirects = original;
50+
});
51+
52+
t.equal(validateRedirectUrl("https://codebar.io"), "/profile");
53+
t.equal(validateRedirectUrl("https://evil.com"), "/profile");
54+
});
55+
56+
test("allows path wildcard", async (t) => {
57+
config.allowed_redirects = ["https://codebar.io/*"];
58+
59+
t.after(() => {
60+
config.allowed_redirects = original;
61+
});
62+
63+
t.equal(
64+
validateRedirectUrl("https://codebar.io/profile"),
65+
"https://codebar.io/profile",
66+
);
67+
t.equal(
68+
validateRedirectUrl("https://codebar.io/anything/here"),
69+
"https://codebar.io/anything/here",
70+
);
71+
});
72+
73+
test("allows multiple wildcards", async (t) => {
74+
config.allowed_redirects = ["https://*.example.com/*"];
75+
76+
t.after(() => {
77+
config.allowed_redirects = original;
78+
});
79+
80+
t.equal(
81+
validateRedirectUrl("https://auth.example.com/page"),
82+
"https://auth.example.com/page",
83+
);
84+
t.equal(
85+
validateRedirectUrl("https://api.example.com/v1/users"),
86+
"https://api.example.com/v1/users",
87+
);
88+
});
89+
90+
test("exact match takes precedence over wildcard", async (t) => {
91+
config.allowed_redirects = [
92+
"http://localhost:3000/demo",
93+
"http://localhost:3000/*",
94+
];
95+
96+
t.after(() => {
97+
config.allowed_redirects = original;
98+
});
99+
100+
t.equal(
101+
validateRedirectUrl("http://localhost:3000/demo"),
102+
"http://localhost:3000/demo",
103+
);
104+
});

0 commit comments

Comments
 (0)