feat: add ctx.isTrustedDomain helper (#6462)

This commit is contained in:
Jonathan Samines
2025-12-04 12:45:10 -08:00
committed by GitHub
parent a2b2f60f0f
commit d09c580bcb
9 changed files with 422 additions and 352 deletions
+42
View File
@@ -125,6 +125,8 @@ Create Auth endpoints wraps around `createEndpoint` from Better Call. Inside the
- `adapter`: This is the same as db but it give you `orm` like functions to interact with the database. (we recommend using this over `db` unless you need raw sql queries or for performance reasons)
- `internalAdapter`: These are internal db calls that are used by Better Auth. For example, you can use these calls to create a session instead of using `adapter` directly. `internalAdapter.createSession(userId)`
- `createAuthCookie`: This is a helper function that lets you get a cookie `name` and `options` for either to `set` or `get` cookies. It implements things like `__Secure-` prefix for cookies based on whether the connection is secure (HTTPS) or the application is running in production mode.
- `trustedOrigins`: This is the list of trusted origins that you specified via `options.trustedOrigins`.
- `isTrustedOrigin`: This is a helper function that allows you to quickly check whether a given url or path is trusted based on the trusted origins configuration.
For other properties, you can check the <Link href="https://github.com/bekacru/better-call">Better Call</Link> documentation and the <Link href="https://github.com/better-auth/better-auth/blob/main/packages/better-auth/src/init.ts">source code </Link>.
@@ -355,6 +357,46 @@ const myPlugin = () => {
}
```
### Trusted origins
If you're building custom plugins or endpoints, you can use the `isTrustedOrigin()` method available on the auth context to validate URLs against your trusted origins configuration.
This ensures your custom endpoints respect the same security settings as Better Auth's built-in endpoints.
```ts title="plugin.ts"
import { createAuthEndpoint, APIError } from "better-auth/api";
import * as z from "zod"
const myPlugin = () => {
return {
id: "my-plugin",
trustedOrigins: [
"http://trusted.com"
],
endpoints: {
getTrustedHelloWorld: createAuthEndpoint("/my-plugin/hello-world", {
method: "GET",
query: z.object({
url: z.string()
}),
}, async (ctx) => {
// The allowRelativePaths option can be used to either allow or disallow relative paths
if (!ctx.context.isTrustedOrigin(ctx.query.url, { allowRelativePaths: false })) {
throw new APIError("FORBIDDEN", {
message: "origin is not trusted."
});
}
return ctx.json({
message: "Hello World"
})
})
}
} satisfies BetterAuthPlugin
}
```
See the [trusted origins and security](/docs/reference/security#trusted-origins) docs for more info.
### Server-plugin helper functions
Some additional helper functions for creating server plugins.
+18 -1
View File
@@ -120,7 +120,7 @@ The most basic usage is to specify exact origins:
}
```
### Wildcard Domains
### Wildcard Origins
Better Auth supports wildcard patterns in trusted origins, which allows you to trust multiple subdomains with a single entry:
@@ -162,6 +162,23 @@ Trusted origins also support custom schemes for mobile apps and browser extensio
}
```
### Dynamic origin list
You can also dynamically set the list of trusted origins by providing a function that returns it:
```typescript
{
trustedOrigins: async (request) => {
const trustedOrigins = await queryTrustedDomains();
return trustedOrigins;
}
}
```
<Callout>
**Important**: This function will be invoked per incoming request, so be careful if you decide to dynamically fetch your list of trusted domains.
</Callout>
## Reporting Vulnerabilities
If you discover a security vulnerability in Better Auth, please report it to us at [security@better-auth.com](mailto:security@better-auth.com). We address all reports promptly, and credits will be given for validated discoveries.
@@ -11,7 +11,6 @@ describe("Origin Check", async (it) => {
"http://localhost:5000",
"https://trusted.com",
"*.my-site.com",
"https://*.protocol-site.com",
],
emailAndPassword: {
enabled: true,
@@ -57,115 +56,6 @@ describe("Origin Check", async (it) => {
expect(res.error?.message).toBe("Invalid callbackURL");
});
it("should allow query params in callback url", async (ctx) => {
const client = createAuthClient({
baseURL: "http://localhost:3000",
fetchOptions: {
customFetchImpl,
headers: {
origin: "https://localhost:3000",
},
},
});
const res = await client.signIn.email({
email: testUser.email,
password: testUser.password,
callbackURL: "/dashboard?test=123",
});
expect(res.data?.user).toBeDefined();
});
it("should allow plus signs in the callback url", async (ctx) => {
const client = createAuthClient({
baseURL: "http://localhost:3000",
fetchOptions: {
customFetchImpl,
headers: {
origin: "https://localhost:3000",
},
},
});
const res = await client.signIn.email({
email: testUser.email,
password: testUser.password,
callbackURL: "/dashboard+page?test=123+456",
});
expect(res.data?.user).toBeDefined();
});
it("should reject callback url with double slash", async (ctx) => {
const client = createAuthClient({
baseURL: "http://localhost:3000",
fetchOptions: {
customFetchImpl,
headers: {
origin: "https://localhost:3000",
},
},
});
const res = await client.signIn.email({
email: testUser.email,
password: testUser.password,
callbackURL: "//evil.com",
});
expect(res.error?.status).toBe(403);
});
it("should reject callback urls with encoded malicious content", async (ctx) => {
const client = createAuthClient({
baseURL: "http://localhost:3000",
fetchOptions: {
customFetchImpl,
headers: {
origin: "https://localhost:3000",
},
},
});
const maliciousPatterns = [
"/%5C/evil.com",
`/\\/\\/evil.com`,
"/%5C/evil.com",
"/..%2F..%2Fevil.com",
"javascript:alert('xss')",
"data:text/html,<script>alert('xss')</script>",
];
for (const pattern of maliciousPatterns) {
const res = await client.signIn.email({
email: testUser.email,
password: testUser.password,
callbackURL: pattern,
});
expect(res.error?.status).toBe(403);
}
});
it("should reject callback url with malicious domain with wildcard trusted origins", async (ctx) => {
const { customFetchImpl, testUser } = await getTestInstance({
trustedOrigins: ["*.example.com"],
emailAndPassword: {
enabled: true,
async sendResetPassword(url, user) {},
},
});
const client = createAuthClient({
baseURL: "http://localhost:3000",
fetchOptions: {
customFetchImpl,
headers: {
cookie: "session=123",
},
},
});
const res = await client.signIn.email({
email: testUser.email,
password: testUser.password,
callbackURL: "malicious.com?.example.com",
});
expect(res.error?.status).toBe(403);
});
it("should reject untrusted origin headers", async (ctx) => {
const client = createAuthClient({
baseURL: "http://localhost:3000",
@@ -184,42 +74,6 @@ describe("Origin Check", async (it) => {
expect(res.error?.status).toBe(403);
});
it("should reject untrusted origin headers which start with trusted origin", async (ctx) => {
const client = createAuthClient({
baseURL: "http://localhost:3000",
fetchOptions: {
customFetchImpl,
headers: {
origin: "https://trusted.com.malicious.com",
cookie: "session=123",
},
},
});
const res = await client.signIn.email({
email: testUser.email,
password: testUser.password,
});
expect(res.error?.status).toBe(403);
});
it("should reject untrusted origin subdomains", async (ctx) => {
const client = createAuthClient({
baseURL: "http://localhost:3000",
fetchOptions: {
customFetchImpl,
headers: {
origin: "http://sub-domain.trusted.com",
cookie: "session=123",
},
},
});
const res = await client.signIn.email({
email: testUser.email,
password: testUser.password,
});
expect(res.error?.status).toBe(403);
});
it("should allow untrusted origin if they don't contain cookies", async (ctx) => {
const client = createAuthClient({
baseURL: "http://localhost:3000",
@@ -296,23 +150,6 @@ describe("Origin Check", async (it) => {
callbackURL: "https://sub-domain.my-site.com/callback",
});
expect(res.data?.user).toBeDefined();
// Test another subdomain with the wildcard pattern
const client2 = createAuthClient({
baseURL: "https://another-sub.my-site.com",
fetchOptions: {
customFetchImpl,
headers: {
origin: "https://another-sub.my-site.com",
},
},
});
const res2 = await client2.signIn.email({
email: testUser.email,
password: testUser.password,
callbackURL: "https://another-sub.my-site.com/callback",
});
expect(res2.data?.user).toBeDefined();
});
it("should work with GET requests", async (ctx) => {
@@ -365,112 +202,6 @@ describe("Origin Check", async (it) => {
});
expect(invalidRes.error?.status).toBe(403);
});
it("should work with relative callbackURL with query params", async (ctx) => {
const client = createAuthClient({
baseURL: "http://localhost:3000",
fetchOptions: {
customFetchImpl,
},
});
const res = await client.signIn.email({
email: testUser.email,
password: testUser.password,
callbackURL: "/dashboard?email=123@email.com",
});
expect(res.data?.user).toBeDefined();
});
it("should work with protocol specific wildcard trusted origins", async () => {
// Test HTTPS protocol specific wildcard - should work
const httpsClient = createAuthClient({
baseURL: "http://localhost:3000",
fetchOptions: {
customFetchImpl,
headers: {
origin: "https://api.protocol-site.com",
cookie: "session=123",
},
},
});
const httpsRes = await httpsClient.signIn.email({
email: testUser.email,
password: testUser.password,
callbackURL: "https://app.protocol-site.com/dashboard",
});
expect(httpsRes.data?.user).toBeDefined();
// Test HTTP with HTTPS protocol wildcard - should fail
const httpClient = createAuthClient({
baseURL: "http://localhost:3000",
fetchOptions: {
customFetchImpl,
headers: {
origin: "http://api.protocol-site.com",
cookie: "session=123",
},
},
});
const httpRes = await httpClient.signIn.email({
email: testUser.email,
password: testUser.password,
});
expect(httpRes.error?.status).toBe(403);
});
it("should work with custom scheme wildcards (e.g. exp:// for Expo)", async () => {
const { customFetchImpl, testUser } = await getTestInstance({
trustedOrigins: [
"exp://10.0.0.*:*/*",
"exp://192.168.*.*:*/*",
"exp://172.*.*.*:*/*",
],
emailAndPassword: {
enabled: true,
async sendResetPassword(url, user) {},
},
});
// Test custom scheme with wildcard - should work
const expoClient = createAuthClient({
baseURL: "http://localhost:3000",
fetchOptions: {
customFetchImpl,
},
});
// Test with IP matching the wildcard pattern
const resWithIP = await expoClient.signIn.email({
email: testUser.email,
password: testUser.password,
callbackURL: "exp://10.0.0.29:8081/--/",
});
expect(resWithIP.data?.user).toBeDefined();
// Test with different IP range that matches
const resWithIP2 = await expoClient.signIn.email({
email: testUser.email,
password: testUser.password,
callbackURL: "exp://192.168.1.100:8081/--/",
});
expect(resWithIP2.data?.user).toBeDefined();
// Test with different IP range that matches
const resWithIP3 = await expoClient.signIn.email({
email: testUser.email,
password: testUser.password,
callbackURL: "exp://172.16.0.1:8081/--/",
});
expect(resWithIP3.data?.user).toBeDefined();
// Test with IP that doesn't match any pattern - should fail
const resWithUnmatchedIP = await expoClient.signIn.email({
email: testUser.email,
password: testUser.password,
callbackURL: "exp://203.0.113.0:8081/--/",
});
expect(resWithUnmatchedIP.error?.status).toBe(403);
});
});
describe("origin check middleware", async (it) => {
@@ -1,8 +1,6 @@
import type { GenericEndpointContext } from "@better-auth/core";
import { createAuthMiddleware } from "@better-auth/core/api";
import { APIError } from "better-call";
import { getHost, getOrigin, getProtocol } from "../../utils/url";
import { wildcardMatch } from "../../utils/wildcard";
/**
* A middleware to validate callbackURL and origin against
@@ -19,59 +17,27 @@ export const originCheckMiddleware = createAuthMiddleware(async (ctx) => {
return;
}
const headers = ctx.request?.headers;
const request = ctx.request;
const { body, query, context } = ctx;
const { body, query } = ctx;
const originHeader = headers?.get("origin") || headers?.get("referer") || "";
const callbackURL = body?.callbackURL || query?.callbackURL;
const redirectURL = body?.redirectTo;
const errorCallbackURL = body?.errorCallbackURL;
const newUserCallbackURL = body?.newUserCallbackURL;
const trustedOrigins: string[] = Array.isArray(context.options.trustedOrigins)
? context.trustedOrigins
: [
...context.trustedOrigins,
...((await context.options.trustedOrigins?.(request)) || []),
];
const useCookies = headers?.has("cookie");
const matchesPattern = (url: string, pattern: string): boolean => {
if (url.startsWith("/")) {
return false;
}
if (pattern.includes("*")) {
// For protocol-specific wildcards, match the full origin
if (pattern.includes("://")) {
return wildcardMatch(pattern)(getOrigin(url) || url);
}
const host = getHost(url);
if (!host) {
return false;
}
return wildcardMatch(pattern)(host);
}
const protocol = getProtocol(url);
return protocol === "http:" || protocol === "https:" || !protocol
? pattern === getOrigin(url)
: url.startsWith(pattern);
};
const validateURL = (url: string | undefined, label: string) => {
if (!url) {
return;
}
const isTrustedOrigin = trustedOrigins.some(
(origin) =>
matchesPattern(url, origin) ||
(url?.startsWith("/") &&
label !== "origin" &&
/^\/(?!\/|\\|%2f|%5c)[\w\-.\+/@]*(?:\?[\w\-.\+/=&%@]*)?$/.test(url)),
);
const isTrustedOrigin = ctx.context.isTrustedOrigin(url, {
allowRelativePaths: label !== "origin",
});
if (!isTrustedOrigin) {
ctx.context.logger.error(`Invalid ${label}: ${url}`);
ctx.context.logger.info(
`If it's a valid URL, please add ${url} to trustedOrigins in your auth config\n`,
`Current list of trustedOrigins: ${trustedOrigins}`,
`Current list of trustedOrigins: ${ctx.context.trustedOrigins}`,
);
throw new APIError("FORBIDDEN", { message: `Invalid ${label}` });
}
@@ -99,57 +65,20 @@ export const originCheck = (
if (!ctx.request) {
return;
}
const { context } = ctx;
const callbackURL = getValue(ctx);
const trustedOrigins: string[] = Array.isArray(
context.options.trustedOrigins,
)
? context.trustedOrigins
: [
...context.trustedOrigins,
...((await context.options.trustedOrigins?.(ctx.request)) || []),
];
const matchesPattern = (url: string, pattern: string): boolean => {
if (url.startsWith("/")) {
return false;
}
if (pattern.includes("*")) {
// For protocol-specific wildcards, match the full origin
if (pattern.includes("://")) {
return wildcardMatch(pattern)(getOrigin(url) || url);
}
const host = getHost(url);
if (!host) {
return false;
}
// For host-only wildcards, match just the host
return wildcardMatch(pattern)(host);
}
const protocol = getProtocol(url);
return protocol === "http:" || protocol === "https:" || !protocol
? pattern === getOrigin(url)
: url.startsWith(pattern);
};
const validateURL = (url: string | undefined, label: string) => {
if (!url) {
return;
}
const isTrustedOrigin = trustedOrigins.some(
(origin) =>
matchesPattern(url, origin) ||
(url?.startsWith("/") &&
label !== "origin" &&
/^\/(?!\/|\\|%2f|%5c)[\w\-.\+/@]*(?:\?[\w\-.\+/=&%@]*)?$/.test(
url,
)),
);
const isTrustedOrigin = ctx.context.isTrustedOrigin(url, {
allowRelativePaths: label !== "origin",
});
if (!isTrustedOrigin) {
ctx.context.logger.error(`Invalid ${label}: ${url}`);
ctx.context.logger.info(
`If it's a valid URL, please add ${url} to trustedOrigins in your auth config\n`,
`Current list of trustedOrigins: ${trustedOrigins}`,
`Current list of trustedOrigins: ${ctx.context.trustedOrigins}`,
);
throw new APIError("FORBIDDEN", { message: `Invalid ${label}` });
}
@@ -0,0 +1,289 @@
import { createAuthEndpoint } from "@better-auth/core/api";
import { describe, expect, it } from "vitest";
import * as z from "zod";
import { getTestInstance } from "../test-utils";
import type { BetterAuthOptions } from "../types";
async function createAuthTestInstance(overrides?: Partial<BetterAuthOptions>) {
const testServerPlugin = {
id: "test-plugin",
endpoints: {
testTrustedOrigin: createAuthEndpoint(
"/test-trusted-origin",
{
method: "GET",
query: z.object({
url: z.string(),
allowRelativePaths: z.coerce.boolean().optional(),
}),
},
async (ctx) => {
const settings =
typeof ctx.query.allowRelativePaths === "boolean"
? { allowRelativePaths: ctx.query.allowRelativePaths }
: undefined;
return ctx.context.isTrustedOrigin(ctx.query.url, settings);
},
),
},
};
const testClientPlugin = {
id: "test-client-plugin",
$InferServerPlugin: {} as typeof testServerPlugin,
};
const { auth, client } = await getTestInstance(
{
plugins: [testServerPlugin],
...overrides,
},
{ clientOptions: { plugins: [testClientPlugin] } },
);
const isTrustedOrigin = async (
url: string,
settings?: { allowRelativePaths: boolean },
) => {
const result = await client.testTrustedOrigin({
query: { url, ...settings },
});
if (result.error) {
throw result.error;
}
return result.data;
};
return { auth, client, isTrustedOrigin };
}
describe("trusted origins", () => {
describe("trusted origins list support", () => {
it("should always allow the app's origin", async () => {
const { isTrustedOrigin } = await createAuthTestInstance();
await expect(isTrustedOrigin("http://localhost:3000")).resolves.toBe(
true,
);
await expect(
isTrustedOrigin("http://localhost:3000/some/path"),
).resolves.toBe(true);
});
it("should reject origins that start with a trusted origin", async () => {
const { isTrustedOrigin } = await createAuthTestInstance({
trustedOrigins: ["https://trusted.com"],
});
await expect(
isTrustedOrigin("https://trusted.com.malicious.com"),
).resolves.toBe(false);
});
it("should reject untrusted origin subdomains", async () => {
const { isTrustedOrigin } = await createAuthTestInstance({
trustedOrigins: ["https://trusted.com"],
});
await expect(
isTrustedOrigin("http://sub-domain.trusted.com"),
).resolves.toBe(false);
});
it("should allow origins that directly match a trusted origin", async () => {
const { isTrustedOrigin } = await createAuthTestInstance({
trustedOrigins: ["https://trusted.com"],
});
await expect(isTrustedOrigin("https://trusted.com")).resolves.toBe(true);
await expect(
isTrustedOrigin("https://trusted.com/some/path"),
).resolves.toBe(true);
});
});
describe("relative paths support", () => {
it("should reject relative paths by default", async () => {
const { isTrustedOrigin } = await createAuthTestInstance();
await expect(isTrustedOrigin("/")).resolves.toBe(false);
await expect(isTrustedOrigin("/some-absolute-url")).resolves.toBe(false);
});
it("should allow relative paths", async () => {
const { isTrustedOrigin } = await createAuthTestInstance();
try {
await isTrustedOrigin("/", { allowRelativePaths: true });
} catch (error) {
console.log(error);
}
await expect(
isTrustedOrigin("/", { allowRelativePaths: true }),
).resolves.toBe(true);
await expect(
isTrustedOrigin("/dashboard", { allowRelativePaths: true }),
).resolves.toBe(true);
});
it("should allow relative paths with query params", async () => {
const { isTrustedOrigin } = await createAuthTestInstance();
await expect(
isTrustedOrigin("/dashboard?email=123@email.com", {
allowRelativePaths: true,
}),
).resolves.toBe(true);
});
it("should allow relative paths with plus signs", async () => {
const { isTrustedOrigin } = await createAuthTestInstance();
await expect(
isTrustedOrigin("/dashboard+page?test=123+456", {
allowRelativePaths: true,
}),
).resolves.toBe(true);
});
it("should reject urls with double dash", async () => {
const { isTrustedOrigin } = await createAuthTestInstance();
await expect(
isTrustedOrigin("//evil.com", { allowRelativePaths: true }),
).resolves.toBe(false);
});
it("should reject urls with encoded malicious content", async () => {
const { isTrustedOrigin } = await createAuthTestInstance();
const maliciousPatterns = [
"/%5C/evil.com",
`/\\/\\/evil.com`,
"/%5C/evil.com",
"/..%2F..%2Fevil.com",
"javascript:alert('xss')",
"data:text/html,<script>alert('xss')</script>",
];
for (const pattern of maliciousPatterns) {
await expect(
isTrustedOrigin(pattern, { allowRelativePaths: true }),
).resolves.toBe(false);
}
});
});
describe("wildcards support", () => {
it("should allow origins that match a wildcard trusted origin", async () => {
const { isTrustedOrigin } = await createAuthTestInstance({
trustedOrigins: ["*.my-site.com"],
});
await expect(
isTrustedOrigin("https://sub-domain.my-site.com"),
).resolves.toBe(true);
await expect(
isTrustedOrigin("https://sub-domain.my-site.com/callback"),
).resolves.toBe(true);
await expect(
isTrustedOrigin("https://another-sub.my-site.com"),
).resolves.toBe(true);
await expect(
isTrustedOrigin("https://another-sub.my-site.com/callback"),
).resolves.toBe(true);
});
it("should reject urls with malicious domain with wildcard trusted origins", async (ctx) => {
const { isTrustedOrigin } = await createAuthTestInstance({
trustedOrigins: ["*.example.com"],
});
await expect(isTrustedOrigin("malicious.com?.example.com")).resolves.toBe(
false,
);
});
it("should work with protocol specific wildcard trusted origins", async () => {
const { isTrustedOrigin } = await createAuthTestInstance({
trustedOrigins: ["https://*.protocol-site.com"],
});
// Test HTTPS protocol specific wildcard - should work
await expect(
isTrustedOrigin("https://api.protocol-site.com"),
).resolves.toBe(true);
// Test HTTP with HTTPS protocol wildcard - should fail
await expect(
isTrustedOrigin("http://api.protocol-site.com"),
).resolves.toBe(false);
});
it("should work with custom scheme wildcards (e.g. exp:// for Expo)", async () => {
const { isTrustedOrigin } = await createAuthTestInstance({
trustedOrigins: [
"exp://10.0.0.*:*/*",
"exp://192.168.*.*:*/*",
"exp://172.*.*.*:*/*",
],
});
// Test with IP matching the wildcard pattern
await expect(isTrustedOrigin("exp://10.0.0.29:8081/--/")).resolves.toBe(
true,
);
// Test with different IP range that matches
await expect(
isTrustedOrigin("exp://192.168.1.100:8081/--/"),
).resolves.toBe(true);
// Test with different IP range that matches
await expect(isTrustedOrigin("exp://172.16.0.1:8081/--/")).resolves.toBe(
true,
);
// Test with IP that doesn't match any pattern - should fail
await expect(isTrustedOrigin("exp://203.0.113.0:8081/--/")).resolves.toBe(
false,
);
});
});
describe("dynamic trusted origins", () => {
it("should allow dynamically computed trusted origins", async () => {
const { isTrustedOrigin } = await createAuthTestInstance({
trustedOrigins: async (request) => {
const url = new URL(
new URL(request.url).searchParams.get("url") ?? "unknown",
);
return [url.origin];
},
});
await expect(
isTrustedOrigin("http://localhost:5000/callback"),
).resolves.toBe(true);
});
it("should not allow dynamically computed trusted origins", async () => {
const { isTrustedOrigin } = await createAuthTestInstance({
trustedOrigins: async (request) => {
return []; // no additional trusted origins
},
});
await expect(
isTrustedOrigin("http://localhost:5000/callback"),
).resolves.toBe(false);
});
});
});
@@ -0,0 +1,45 @@
import { getHost, getOrigin, getProtocol } from "../utils/url";
import { wildcardMatch } from "../utils/wildcard";
/**
* Matches the given url against an origin or origin pattern
* See "options.trustedOrigins" for details of supported patterns
*
* @param url The url to test
* @param pattern The origin pattern
* @param [settings] Specify supported pattern matching settings
* @returns {boolean} true if the URL matches the origin pattern, false otherwise.
*/
export const matchesOriginPattern = (
url: string,
pattern: string,
settings?: { allowRelativePaths: boolean },
): boolean => {
if (url.startsWith("/")) {
if (settings?.allowRelativePaths) {
return (
url.startsWith("/") &&
/^\/(?!\/|\\|%2f|%5c)[\w\-.\+/@]*(?:\?[\w\-.\+/=&%@]*)?$/.test(url)
);
}
return false;
}
if (pattern.includes("*")) {
// For protocol-specific wildcards, match the full origin
if (pattern.includes("://")) {
return wildcardMatch(pattern)(getOrigin(url) || url);
}
const host = getHost(url);
if (!host) {
return false;
}
// For host-only wildcards, match just the host
return wildcardMatch(pattern)(host);
}
const protocol = getProtocol(url);
return protocol === "http:" || protocol === "https:" || !protocol
? pattern === getOrigin(url)
: url.startsWith(pattern);
};
@@ -112,6 +112,7 @@ exports[`base context creation > should match config 1`] = `
"updateUserByEmail": [Function],
"updateVerificationValue": [Function],
},
"isTrustedOrigin": [Function],
"logger": {
"debug": [Function],
"error": [Function],
@@ -10,6 +10,7 @@ import { createTelemetry } from "@better-auth/telemetry";
import defu from "defu";
import type { Entries } from "type-fest";
import { checkEndpointConflicts } from "../api";
import { matchesOriginPattern } from "../auth/trusted-origins";
import { createCookieGetter, getCookies } from "../cookies";
import { hashPassword, verifyPassword } from "../crypto/password";
import { createInternalAdapter } from "../db/internal-adapter";
@@ -174,6 +175,11 @@ export async function createAuthContext(
},
tables,
trustedOrigins: getTrustedOrigins(options),
isTrustedOrigin(url: string, settings?: { allowRelativePaths: boolean }) {
return ctx.trustedOrigins.some((origin) =>
matchesOriginPattern(url, origin, settings),
);
},
baseURL: baseURL || "",
sessionConfig: {
updateAge:
+10
View File
@@ -165,6 +165,16 @@ export type AuthContext<Options extends BetterAuthOptions = BetterAuthOptions> =
appName: string;
baseURL: string;
trustedOrigins: string[];
/**
* Verifies whether url is a trusted origin according to the "trustedOrigins" configuration
* @param url The url to verify against the "trustedOrigins" configuration
* @param settings Specify supported pattern matching settings
* @returns {boolean} true if the URL matches the origin pattern, false otherwise.
*/
isTrustedOrigin: (
url: string,
settings?: { allowRelativePaths: boolean },
) => boolean;
oauthConfig: {
/**
* This is dangerous and should only be used in dev or staging environments.