mirror of
https://github.com/LukeHagar/better-auth.git
synced 2025-12-07 20:37:44 +00:00
feat: cross-subdomainCookies plugin
This commit is contained in:
@@ -1,5 +1,10 @@
|
|||||||
import { betterAuth } from "better-auth";
|
import { betterAuth } from "better-auth";
|
||||||
import { organization, passkey, twoFactor } from "better-auth/plugins";
|
import {
|
||||||
|
organization,
|
||||||
|
passkey,
|
||||||
|
twoFactor,
|
||||||
|
crossSubdomain,
|
||||||
|
} from "better-auth/plugins";
|
||||||
import { reactInvitationEmail } from "./email/invitation";
|
import { reactInvitationEmail } from "./email/invitation";
|
||||||
import { LibsqlDialect } from "@libsql/kysely-libsql";
|
import { LibsqlDialect } from "@libsql/kysely-libsql";
|
||||||
import { github, google } from "better-auth/social-providers";
|
import { github, google } from "better-auth/social-providers";
|
||||||
@@ -75,6 +80,7 @@ export const auth = betterAuth({
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
passkey(),
|
passkey(),
|
||||||
|
crossSubdomain(),
|
||||||
],
|
],
|
||||||
socialProviders: {
|
socialProviders: {
|
||||||
github: {
|
github: {
|
||||||
|
|||||||
@@ -174,6 +174,17 @@ export const router = <C extends AuthContext, Option extends BetterAuthOptions>(
|
|||||||
async onRequest(req) {
|
async onRequest(req) {
|
||||||
return onRequestRateLimit(req, ctx);
|
return onRequestRateLimit(req, ctx);
|
||||||
},
|
},
|
||||||
|
async onResponse(res) {
|
||||||
|
for (const plugin of ctx.options.plugins || []) {
|
||||||
|
if (plugin.onResponse) {
|
||||||
|
const response = await plugin.onResponse(res, ctx);
|
||||||
|
if (response) {
|
||||||
|
return response.response;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
},
|
||||||
onError(e) {
|
onError(e) {
|
||||||
const log = options.logger?.verboseLogging ? logger : undefined;
|
const log = options.logger?.verboseLogging ? logger : undefined;
|
||||||
if (options.logger?.disabled !== true) {
|
if (options.logger?.disabled !== true) {
|
||||||
|
|||||||
78
packages/better-auth/src/plugins/cross-subdomain/index.ts
Normal file
78
packages/better-auth/src/plugins/cross-subdomain/index.ts
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
import { createAuthMiddleware } from "../../api";
|
||||||
|
import type { BetterAuthPlugin } from "../../types";
|
||||||
|
|
||||||
|
interface Options {
|
||||||
|
/**
|
||||||
|
* By default, domain name will be extracted from base URL
|
||||||
|
* you can provide a custom domain name here
|
||||||
|
*/
|
||||||
|
domainName?: string;
|
||||||
|
/**
|
||||||
|
* List of cookies that should be shared across subdomains
|
||||||
|
*
|
||||||
|
* by default, only sessionToken, csrfToken and dontRememberToken
|
||||||
|
* cookies will be shared across subdomains
|
||||||
|
*/
|
||||||
|
eligibleCookies?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This plugin will update the domain of the cookies
|
||||||
|
* that are eligible to be shared across subdomains
|
||||||
|
* @param options
|
||||||
|
* @category Plugins
|
||||||
|
*/
|
||||||
|
export const crossSubdomainCookies = (options?: Options) => {
|
||||||
|
return {
|
||||||
|
id: "cross-subdomain-cookies",
|
||||||
|
async onResponse(response, ctx) {
|
||||||
|
const setCookie = response.headers.get("set-cookie");
|
||||||
|
if (!setCookie) return;
|
||||||
|
const baseURL = ctx.baseURL;
|
||||||
|
const cookieParts = setCookie.split(";");
|
||||||
|
const domain = options?.domainName || new URL(baseURL).hostname;
|
||||||
|
const authCookies = ctx.authCookies;
|
||||||
|
const cookieNamesEligibleForDomain = [
|
||||||
|
authCookies.sessionToken.name,
|
||||||
|
authCookies.csrfToken.name,
|
||||||
|
authCookies.dontRememberToken.name,
|
||||||
|
];
|
||||||
|
|
||||||
|
if (
|
||||||
|
!cookieNamesEligibleForDomain.some((name) => setCookie.includes(name))
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedCookies = cookieParts
|
||||||
|
.map((part) => {
|
||||||
|
if (
|
||||||
|
!cookieNamesEligibleForDomain.some((name) =>
|
||||||
|
part.toLowerCase().includes(name.toLowerCase()),
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return part;
|
||||||
|
}
|
||||||
|
|
||||||
|
const trimmedPart = part.trim();
|
||||||
|
if (trimmedPart.toLowerCase().startsWith("domain=")) {
|
||||||
|
return `Domain=${domain}`;
|
||||||
|
}
|
||||||
|
if (!trimmedPart.toLowerCase().includes("domain=")) {
|
||||||
|
return `${trimmedPart}; Domain=${domain}`;
|
||||||
|
}
|
||||||
|
return trimmedPart;
|
||||||
|
})
|
||||||
|
.filter(
|
||||||
|
(part, index, self) =>
|
||||||
|
index ===
|
||||||
|
self.findIndex((p) => p.split(";")[0] === part.split(";")[0]),
|
||||||
|
)
|
||||||
|
.join("; ");
|
||||||
|
response.headers.set("set-cookie", updatedCookies);
|
||||||
|
return {
|
||||||
|
response,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
} satisfies BetterAuthPlugin;
|
||||||
|
};
|
||||||
@@ -7,3 +7,4 @@ export * from "../types/plugins";
|
|||||||
export * from "../api/call";
|
export * from "../api/call";
|
||||||
export * from "../utils/hide-metadata";
|
export * from "../utils/hide-metadata";
|
||||||
export * from "./magic-link";
|
export * from "./magic-link";
|
||||||
|
export * from "./cross-subdomain";
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
import type { ContextTools } from "better-call";
|
import type { ContextTools } from "better-call";
|
||||||
import type { AuthContext } from "../init";
|
import type { AuthContext } from "../init";
|
||||||
|
|
||||||
export type HookEndpointContext = ContextTools & {
|
export type HookEndpointContext<C extends Record<string, any> = {}> =
|
||||||
context: AuthContext;
|
ContextTools & {
|
||||||
} & {
|
context: AuthContext & C;
|
||||||
body: any;
|
} & {
|
||||||
request?: Request;
|
body: any;
|
||||||
headers?: Headers;
|
request?: Request;
|
||||||
params?: Record<string, string> | undefined;
|
headers?: Headers;
|
||||||
query?: any;
|
params?: Record<string, string> | undefined;
|
||||||
method?: any;
|
query?: any;
|
||||||
};
|
method?: any;
|
||||||
|
};
|
||||||
|
|
||||||
export type GenericEndpointContext = ContextTools & {
|
export type GenericEndpointContext = ContextTools & {
|
||||||
context: AuthContext;
|
context: AuthContext;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import type { AuthEndpoint } from "../api/call";
|
|||||||
import type { FieldAttribute } from "../db/field";
|
import type { FieldAttribute } from "../db/field";
|
||||||
import type { HookEndpointContext } from "./context";
|
import type { HookEndpointContext } from "./context";
|
||||||
import type { LiteralString } from "./helper";
|
import type { LiteralString } from "./helper";
|
||||||
|
import type { AuthContext } from ".";
|
||||||
|
|
||||||
export type PluginSchema = {
|
export type PluginSchema = {
|
||||||
[table: string]: {
|
[table: string]: {
|
||||||
@@ -23,6 +24,12 @@ export type BetterAuthPlugin = {
|
|||||||
path: string;
|
path: string;
|
||||||
middleware: Endpoint;
|
middleware: Endpoint;
|
||||||
}[];
|
}[];
|
||||||
|
onResponse?: (
|
||||||
|
response: Response,
|
||||||
|
ctx: AuthContext,
|
||||||
|
) => Promise<{
|
||||||
|
response: Response;
|
||||||
|
} | void>;
|
||||||
hooks?: {
|
hooks?: {
|
||||||
before?: {
|
before?: {
|
||||||
matcher: (context: HookEndpointContext) => boolean;
|
matcher: (context: HookEndpointContext) => boolean;
|
||||||
@@ -33,9 +40,9 @@ export type BetterAuthPlugin = {
|
|||||||
after?: {
|
after?: {
|
||||||
matcher: (context: HookEndpointContext) => boolean;
|
matcher: (context: HookEndpointContext) => boolean;
|
||||||
handler: (
|
handler: (
|
||||||
context: HookEndpointContext & {
|
context: HookEndpointContext<{
|
||||||
returned: EndpointResponse;
|
returned: EndpointResponse;
|
||||||
},
|
}>,
|
||||||
) => Promise<void | {
|
) => Promise<void | {
|
||||||
response: EndpointResponse;
|
response: EndpointResponse;
|
||||||
}>;
|
}>;
|
||||||
|
|||||||
Reference in New Issue
Block a user