mirror of
https://github.com/better-auth/better-auth.git
synced 2026-10-19 23:53:17 -05:00
fix(sso): handle bare domains in domain verification (#8369)
This commit is contained in:
@@ -39,3 +39,4 @@ authjs
|
||||
vite
|
||||
electronjs
|
||||
wagmi
|
||||
tldts
|
||||
|
||||
@@ -69,6 +69,7 @@
|
||||
"fast-xml-parser": "^5.4.1",
|
||||
"jose": "^6.1.3",
|
||||
"samlify": "^2.10.2",
|
||||
"tldts": "^6.1.0",
|
||||
"zod": "^4.3.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -530,6 +530,53 @@ describe("Domain verification", async () => {
|
||||
);
|
||||
});
|
||||
|
||||
/**
|
||||
* @see https://github.com/better-auth/better-auth/issues/8361
|
||||
*/
|
||||
it("should verify a provider domain ownership with a bare domain", async () => {
|
||||
const { auth, getAuthHeaders } = createTestAuth();
|
||||
const headers = await getAuthHeaders(testUser);
|
||||
|
||||
await auth.api.registerSSOProvider({
|
||||
body: {
|
||||
providerId: "bare-domain-provider",
|
||||
issuer: "http://hello.com:8081",
|
||||
domain: "hello.com",
|
||||
samlConfig: {
|
||||
entryPoint: "http://idp.com:",
|
||||
cert: "the-cert",
|
||||
callbackUrl: "http://hello.com:8081/api/sso/saml2/callback",
|
||||
spMetadata: {},
|
||||
},
|
||||
},
|
||||
headers,
|
||||
});
|
||||
|
||||
const requestResponse = await auth.api.requestDomainVerification({
|
||||
body: { providerId: "bare-domain-provider" },
|
||||
headers,
|
||||
asResponse: true,
|
||||
});
|
||||
|
||||
expect(requestResponse.status).toBe(201);
|
||||
const { domainVerificationToken } = await requestResponse.json();
|
||||
|
||||
dnsMock.resolveTxt.mockResolvedValue([
|
||||
[`_better-auth-token-bare-domain-provider=${domainVerificationToken}`],
|
||||
]);
|
||||
|
||||
const verifyResponse = await auth.api.verifyDomain({
|
||||
body: { providerId: "bare-domain-provider" },
|
||||
headers,
|
||||
asResponse: true,
|
||||
});
|
||||
|
||||
expect(verifyResponse.status).toBe(204);
|
||||
expect(dnsMock.resolveTxt).toHaveBeenCalledWith(
|
||||
"_better-auth-token-bare-domain-provider.hello.com",
|
||||
);
|
||||
});
|
||||
|
||||
it("should return bad request when provider ID exceeds DNS label limit", async () => {
|
||||
const longProviderId = "a".repeat(50);
|
||||
const { auth, getAuthHeaders } = createTestAuth();
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
import { generateRandomString } from "better-auth/crypto";
|
||||
import * as z from "zod/v4";
|
||||
import type { SSOOptions, SSOProvider } from "../types";
|
||||
import { getHostnameFromDomain } from "../utils";
|
||||
|
||||
const DNS_LABEL_MAX_LENGTH = 63;
|
||||
const DEFAULT_TOKEN_PREFIX = "better-auth-token";
|
||||
@@ -241,8 +242,15 @@ export const verifyDomain = (options: SSOOptions) => {
|
||||
});
|
||||
}
|
||||
|
||||
const hostname = getHostnameFromDomain(provider.domain);
|
||||
if (!hostname) {
|
||||
throw new APIError("BAD_REQUEST", {
|
||||
message: "Invalid domain",
|
||||
code: "INVALID_DOMAIN",
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
const hostname = new URL(provider.domain).hostname;
|
||||
const dnsRecords = await dns.resolveTxt(`${identifier}.${hostname}`);
|
||||
records = dnsRecords.flat();
|
||||
} catch (error) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { validateEmailDomain } from "./utils";
|
||||
import { getHostnameFromDomain, validateEmailDomain } from "./utils";
|
||||
|
||||
/**
|
||||
* @see https://github.com/better-auth/better-auth/issues/7324
|
||||
@@ -104,3 +104,34 @@ describe("validateEmailDomain", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* @see https://github.com/better-auth/better-auth/issues/8361
|
||||
*/
|
||||
describe("getHostnameFromDomain", () => {
|
||||
it("should extract hostname from a bare domain", () => {
|
||||
expect(getHostnameFromDomain("github.com")).toBe("github.com");
|
||||
});
|
||||
|
||||
it("should extract hostname from a full URL", () => {
|
||||
expect(getHostnameFromDomain("https://github.com")).toBe("github.com");
|
||||
});
|
||||
|
||||
it("should extract hostname from a URL with port", () => {
|
||||
expect(getHostnameFromDomain("https://github.com:8081")).toBe("github.com");
|
||||
});
|
||||
|
||||
it("should extract hostname from a subdomain", () => {
|
||||
expect(getHostnameFromDomain("auth.github.com")).toBe("auth.github.com");
|
||||
});
|
||||
|
||||
it("should extract hostname from a URL with path", () => {
|
||||
expect(getHostnameFromDomain("https://github.com/path/to/resource")).toBe(
|
||||
"github.com",
|
||||
);
|
||||
});
|
||||
|
||||
it("should return null for an empty string", () => {
|
||||
expect(getHostnameFromDomain("")).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { X509Certificate } from "node:crypto";
|
||||
import { getHostname } from "tldts";
|
||||
|
||||
/**
|
||||
* Safely parses a value that might be a JSON string or already a parsed object.
|
||||
@@ -73,6 +74,10 @@ export function parseCertificate(certPem: string) {
|
||||
};
|
||||
}
|
||||
|
||||
export function getHostnameFromDomain(domain: string): string | null {
|
||||
return getHostname(domain) || null;
|
||||
}
|
||||
|
||||
export function maskClientId(clientId: string): string {
|
||||
if (clientId.length <= 4) {
|
||||
return "****";
|
||||
|
||||
Generated
+16
@@ -1593,6 +1593,9 @@ importers:
|
||||
samlify:
|
||||
specifier: ^2.10.2
|
||||
version: 2.10.2
|
||||
tldts:
|
||||
specifier: ^6.1.0
|
||||
version: 6.1.86
|
||||
zod:
|
||||
specifier: ^4.3.6
|
||||
version: 4.3.6
|
||||
@@ -15611,9 +15614,16 @@ packages:
|
||||
resolution: {integrity: sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
|
||||
tldts-core@6.1.86:
|
||||
resolution: {integrity: sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==}
|
||||
|
||||
tldts-core@7.0.23:
|
||||
resolution: {integrity: sha512-0g9vrtDQLrNIiCj22HSe9d4mLVG3g5ph5DZ8zCKBr4OtrspmNB6ss7hVyzArAeE88ceZocIEGkyW1Ime7fxPtQ==}
|
||||
|
||||
tldts@6.1.86:
|
||||
resolution: {integrity: sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==}
|
||||
hasBin: true
|
||||
|
||||
tldts@7.0.23:
|
||||
resolution: {integrity: sha512-ASdhgQIBSay0R/eXggAkQ53G4nTJqTXqC2kbaBbdDwM7SkjyZyO0OaaN1/FH7U/yCeqOHDwFO5j8+Os/IS1dXw==}
|
||||
hasBin: true
|
||||
@@ -34137,8 +34147,14 @@ snapshots:
|
||||
|
||||
tinyrainbow@3.0.3: {}
|
||||
|
||||
tldts-core@6.1.86: {}
|
||||
|
||||
tldts-core@7.0.23: {}
|
||||
|
||||
tldts@6.1.86:
|
||||
dependencies:
|
||||
tldts-core: 6.1.86
|
||||
|
||||
tldts@7.0.23:
|
||||
dependencies:
|
||||
tldts-core: 7.0.23
|
||||
|
||||
Reference in New Issue
Block a user