diff --git a/demo/nextjs/lib/auth-client.ts b/demo/nextjs/lib/auth-client.ts index 96b7d634..0e9ac305 100644 --- a/demo/nextjs/lib/auth-client.ts +++ b/demo/nextjs/lib/auth-client.ts @@ -46,3 +46,5 @@ export const { useListOrganizations, useActiveOrganization, } = client; + +client.$store.listen("$sessionSignal", async () => {}); diff --git a/docs/components/builder/social-provider.tsx b/docs/components/builder/social-provider.tsx index 72f3db78..64995ec2 100644 --- a/docs/components/builder/social-provider.tsx +++ b/docs/components/builder/social-provider.tsx @@ -173,27 +173,33 @@ export const socialProviders = { Icon: (props: SVGProps) => ( + + + ), - stringIcon: ` - + stringIcon: ` + + + + `, }, linkedin: { diff --git a/docs/content/docs/authentication/email-password.mdx b/docs/content/docs/authentication/email-password.mdx index 4387a043..ac2bfc0b 100644 --- a/docs/content/docs/authentication/email-password.mdx +++ b/docs/content/docs/authentication/email-password.mdx @@ -223,6 +223,24 @@ const { data, error } = await authClient.resetPassword({ }); ``` +### Update password + + + This only works on server-side, and the following code may change over time. + + +To set a password, you must hash it first: + +```ts +const ctx = await auth.$context; +const hash = await ctx.password.hash("your-new-password"); +``` + +Then, to set the password: +```ts +await ctx.internalAdapter.updatePassword("userId", hash) //(you can also use your orm directly) +``` + ### Configuration **Password** diff --git a/docs/content/docs/concepts/session-management.mdx b/docs/content/docs/concepts/session-management.mdx index a221a57c..75620103 100644 --- a/docs/content/docs/concepts/session-management.mdx +++ b/docs/content/docs/concepts/session-management.mdx @@ -73,7 +73,7 @@ The `getSession` function retrieves the current active session. ```ts client="client.ts" import { authClient } from "@/lib/client" -const session = await authClient.getSession() +const { data: session } = await authClient.getSession() ``` To learn how to customize the session response check the [Customizing Session Response](#customizing-session-response) section. @@ -85,7 +85,7 @@ The `useSession` action provides a reactive way to access the current session. ```ts client="client.ts" import { authClient } from "@/lib/client" -const session = await authClient.useSession() +const { data: session } = authClient.useSession() ``` ### List Sessions diff --git a/docs/content/docs/integrations/hono.mdx b/docs/content/docs/integrations/hono.mdx index c6338c25..e97061b3 100644 --- a/docs/content/docs/integrations/hono.mdx +++ b/docs/content/docs/integrations/hono.mdx @@ -37,7 +37,7 @@ import { cors } from "hono/cors"; const app = new Hono(); app.use( - "/api/auth/**", // or replace with "*" to enable cors for all routes + "/api/auth/*", // or replace with "*" to enable cors for all routes cors({ origin: "http://localhost:3001", // replace with your origin allowHeaders: ["Content-Type", "Authorization"], @@ -82,7 +82,7 @@ app.use("*", async (c, next) => { return next(); }); -app.on(["POST", "GET"], "/api/auth/**", (c) => { +app.on(["POST", "GET"], "/api/auth/*", (c) => { return auth.handler(c.req.raw); }); diff --git a/docs/content/docs/introduction.mdx b/docs/content/docs/introduction.mdx index f9c51200..03718c7e 100644 --- a/docs/content/docs/introduction.mdx +++ b/docs/content/docs/introduction.mdx @@ -7,7 +7,7 @@ Better Auth is a framework-agnostic authentication and authorization framework f ## Why Better Auth? -*Authentication in the TypeScript ecosystem has long been a half-solved problem. Other open-source libraries often require a lot of additional code for anything beyond basic authentication features.Rather than just pushing third-party services as the solution, I believe we can do better as a community—hence, Better Auth* +*Authentication in the TypeScript ecosystem has long been a half-solved problem. Other open-source libraries often require a lot of additional code for anything beyond basic authentication features. Rather than just pushing third-party services as the solution, I believe we can do better as a community—hence, Better Auth* ## Features diff --git a/docs/content/docs/plugins/email-otp.mdx b/docs/content/docs/plugins/email-otp.mdx index 59627207..2a0342ac 100644 --- a/docs/content/docs/plugins/email-otp.mdx +++ b/docs/content/docs/plugins/email-otp.mdx @@ -53,7 +53,7 @@ The Email OTP plugin allows user to sign-in, verify their email, or reset their First, send an OTP to the user's email address. ```ts title="example.ts" -await authClient.emailOtp.sendVerificationOtp({ +const { data, error } = await authClient.emailOtp.sendVerificationOtp({ email: "user-email@email.com", type: "sign-in" // or "email-verification", "forget-password" }) @@ -64,7 +64,7 @@ await authClient.emailOtp.sendVerificationOtp({ Once the user provides the OTP, you can sign in the user using the `signIn.emailOTP()` method. ```ts title="example.ts" -const user = await authClient.signIn.emailOtp({ +const { data, error } = await authClient.signIn.emailOtp({ email: "user-email@email.com", otp: "123456" }) @@ -77,7 +77,7 @@ If the user is not registered, they'll be automatically registered. If you want To verify the user's email address, use the `verifyEmail()` method. ```ts title="example.ts" -const user = await authClient.emailOtp.verifyEmail({ +const { data, error } = await authClient.emailOtp.verifyEmail({ email: "user-email@email.com", otp: "123456" }) @@ -88,7 +88,7 @@ const user = await authClient.emailOtp.verifyEmail({ To reset the user's password, use the `resetPassword()` method. ```ts title="example.ts" -await authClient.emailOtp.resetPassword({ +const { data, error } = await authClient.emailOtp.resetPassword({ email: "user-email@email.com", otp: "123456", password: "password" @@ -146,4 +146,4 @@ export const auth = betterAuth({ - `sendVerificationOnSignUp`: A boolean value that determines whether to send the OTP when a user signs up. Defaults to `false`. -- `disableSignUp`: A boolean value that determines whether to prevent automatic sign-up when the user is not registered. Defaults to `false`. \ No newline at end of file +- `disableSignUp`: A boolean value that determines whether to prevent automatic sign-up when the user is not registered. Defaults to `false`. diff --git a/docs/content/docs/plugins/generic-oauth.mdx b/docs/content/docs/plugins/generic-oauth.mdx index 0f2cd24f..5647902a 100644 --- a/docs/content/docs/plugins/generic-oauth.mdx +++ b/docs/content/docs/plugins/generic-oauth.mdx @@ -70,6 +70,17 @@ const response = await authClient.signIn.oauth2({ }); ``` +### Linking OAuth Accounts + +To link an OAuth account to an existing user: + +```ts title="link-account.ts" +const response = await authClient.oauth2.link({ + providerId: "provider-id", + callbackURL: "/dashboard" // the path to redirect to after the account is linked +}); +``` + ### Handle OAuth Callback The plugin mounts a route to handle the OAuth callback `/oauth2/callback/:providerId`. This means by default `${baseURL}/api/auth/oauth2/callback/:providerId` will be used as the callback URL. Make sure your OAuth provider is configured to use this URL. diff --git a/docs/content/docs/plugins/magic-link.mdx b/docs/content/docs/plugins/magic-link.mdx index 91bcd944..788bf36f 100644 --- a/docs/content/docs/plugins/magic-link.mdx +++ b/docs/content/docs/plugins/magic-link.mdx @@ -3,9 +3,10 @@ title: Magic link description: Magic link plugin --- -Magic link or email link is a way to authenticate users without a password. When a user enters their email, a link is sent to their email. When the user clicks on the link, they are authenticated. +Magic link or email link is a way to authenticate users without a password. When a user enters their email, a link is sent to their email. When the user clicks on the link, they are authenticated. ## Installation + ### Add the server Plugin @@ -43,18 +44,19 @@ Magic link or email link is a way to authenticate users without a password. When }); ``` + ## Usage ### Sign In with Magic Link -To sign in with a magic link, you need to call `signIn.magicLink` with the user's email address. The `sendMagicLink` function is called to send the magic link to the user's email. +To sign in with a magic link, you need to call `signIn.magicLink` with the user's email address. The `sendMagicLink` function is called to send the magic link to the user's email. ```ts title="magic-link.ts" const { data, error } = await authClient.signIn.magicLink({ - email: "user@email.com", - callbackURL: "/dashboard" //redirect after successful login (optional) + email: "user@email.com", + callbackURL: "/dashboard", //redirect after successful login (optional) }); ``` @@ -65,16 +67,16 @@ If the user has not signed up, unless `disableSignUp` is set to `true`, the user When you send the URL generated by the `sendMagicLink` function to a user, clicking the link will authenticate them and redirect them to the `callbackURL` specified in the `signIn.magicLink` function. If an error occurs, the user will be redirected to the `callbackURL` with an error query parameter. -If no `callbackURL` is provided, the user will be redirected to the root URL. + If no `callbackURL` is provided, the user will be redirected to the root URL. If you want to handle the verification manually, (e.g, if you send the user a different url), you can use the `verify` function. ```ts title="magic-link.ts" const { data, error } = await authClient.magicLink.verify({ - query: { - token - } + query: { + token, + }, }); ``` @@ -91,3 +93,13 @@ and a `request` object as the second parameter. **expiresIn**: specifies the time in seconds after which the magic link will expire. The default value is `300` seconds (5 minutes). **disableSignUp**: If set to `true`, the user will not be able to sign up using the magic link. The default value is `false`. + +**generateToken**: The `generateToken` function is called to generate a token which is used to uniquely identify the user. The default value is a random string. There is one parameter: + +- `email`: The email address of the user. + + + When using `generateToken`, ensure that the returned string is hard to guess + because it is used to verify who someone actually is in a confidential way. By + default, we return a long and cryptographically secure string. + diff --git a/docs/content/docs/plugins/organization.mdx b/docs/content/docs/plugins/organization.mdx index 466523f7..8e0c3e57 100644 --- a/docs/content/docs/plugins/organization.mdx +++ b/docs/content/docs/plugins/organization.mdx @@ -544,6 +544,16 @@ auth.api.addMember({ }) ``` +### Leave Organization + +To leave organization you can use `organization.leave` function. This function will remove the current user from the organization. + +```ts title="auth-client.ts" +await authClient.organization.leave({ + organizationId: "organization-id" +}) +``` + ## Access Control The organization plugin providers a very flexible access control system. You can control the access of the user based on the role they have in the organization. You can define your own set of permissions based on the role of the user. diff --git a/docs/content/docs/plugins/passkey.mdx b/docs/content/docs/plugins/passkey.mdx index 69e3e20c..bdb885d1 100644 --- a/docs/content/docs/plugins/passkey.mdx +++ b/docs/content/docs/plugins/passkey.mdx @@ -85,7 +85,6 @@ const authClient = createAuthClient({ passkeyClient(), // [!code highlight] ], // [!code highlight] }); -// ---cut--- const data = await authClient.passkey.addPasskey(); ``` @@ -111,7 +110,6 @@ const authClient = createAuthClient({ passkeyClient(), // [!code highlight] ], // [!code highlight] }); -// ---cut--- const data = await authClient.signIn.passkey(); ``` diff --git a/packages/better-auth/src/api/middlewares/origin-check.test.ts b/packages/better-auth/src/api/middlewares/origin-check.test.ts index d7a43890..79c38d68 100644 --- a/packages/better-auth/src/api/middlewares/origin-check.test.ts +++ b/packages/better-auth/src/api/middlewares/origin-check.test.ts @@ -141,6 +141,35 @@ describe("Origin Check", async (it) => { expect(res.error?.message).toBe("Invalid redirectURL"); }); + it("should work with list of trusted origins", async (ctx) => { + const client = createAuthClient({ + baseURL: "http://localhost:3000", + fetchOptions: { + customFetchImpl, + headers: { + origin: "https://trusted.com", + }, + }, + }); + const res = await client.forgetPassword({ + email: testUser.email, + redirectTo: "http://localhost:5000/reset-password", + }); + expect(res.data?.status).toBeTruthy(); + + const res2 = await client.signIn.email({ + email: testUser.email, + password: testUser.password, + fetchOptions: { + // @ts-expect-error - query is not defined in the type + query: { + currentURL: "http://localhost:5000", + }, + }, + }); + expect(res2.data?.user).toBeDefined(); + }); + it("should work with wildcard trusted origins", async (ctx) => { const client = createAuthClient({ baseURL: "https://sub-domain.my-site.com", diff --git a/packages/better-auth/src/api/middlewares/origin-check.ts b/packages/better-auth/src/api/middlewares/origin-check.ts index 2d30f2ed..08ed84a9 100644 --- a/packages/better-auth/src/api/middlewares/origin-check.ts +++ b/packages/better-auth/src/api/middlewares/origin-check.ts @@ -9,7 +9,7 @@ import type { GenericEndpointContext } from "../../types"; * trustedOrigins. */ export const originCheckMiddleware = createAuthMiddleware(async (ctx) => { - if (ctx.request?.method !== "POST") { + if (ctx.request?.method !== "POST" || !ctx.request) { return; } const { body, query, context } = ctx; @@ -19,7 +19,12 @@ export const originCheckMiddleware = createAuthMiddleware(async (ctx) => { const redirectURL = body?.redirectTo; const errorCallbackURL = body?.errorCallbackURL; const newUserCallbackURL = body?.newUserCallbackURL; - const trustedOrigins = context.trustedOrigins; + const trustedOrigins: string[] = Array.isArray(context.options.trustedOrigins) + ? context.trustedOrigins + : [ + ...context.trustedOrigins, + ...(context.options.trustedOrigins?.(ctx.request) || []), + ]; const usesCookies = ctx.headers?.has("cookie"); const matchesPattern = (url: string, pattern: string): boolean => { @@ -66,9 +71,19 @@ export const originCheck = ( getValue: (ctx: GenericEndpointContext) => string, ) => createAuthMiddleware(async (ctx) => { + if (!ctx.request) { + return; + } const { context } = ctx; const callbackURL = getValue(ctx); - const trustedOrigins = context.trustedOrigins; + const trustedOrigins: string[] = Array.isArray( + context.options.trustedOrigins, + ) + ? context.trustedOrigins + : [ + ...context.trustedOrigins, + ...(context.options.trustedOrigins?.(ctx.request) || []), + ]; const matchesPattern = (url: string, pattern: string): boolean => { if (url.startsWith("/")) { diff --git a/packages/better-auth/src/api/rate-limiter/index.ts b/packages/better-auth/src/api/rate-limiter/index.ts index 49a31c07..e87a7c90 100644 --- a/packages/better-auth/src/api/rate-limiter/index.ts +++ b/packages/better-auth/src/api/rate-limiter/index.ts @@ -35,7 +35,7 @@ function getRetryAfter(lastRequest: number, window: number) { } function createDBStorage(ctx: AuthContext, modelName?: string) { - const model = "rateLimit"; + const model = ctx.options.rateLimit?.modelName || "rateLimit"; const db = ctx.adapter; return { get: async (key: string) => { diff --git a/packages/better-auth/src/auth.ts b/packages/better-auth/src/auth.ts index 9304877d..562b914e 100644 --- a/packages/better-auth/src/auth.ts +++ b/packages/better-auth/src/auth.ts @@ -40,7 +40,11 @@ export const betterAuth = (options: O) => { ctx.baseURL = baseURL; } ctx.trustedOrigins = [ - ...(options.trustedOrigins || []), + ...(options.trustedOrigins + ? Array.isArray(options.trustedOrigins) + ? options.trustedOrigins + : options.trustedOrigins(request) + : []), ctx.baseURL, url.origin, ]; diff --git a/packages/better-auth/src/client/path-to-object.ts b/packages/better-auth/src/client/path-to-object.ts index 3eb1797a..7d263a74 100644 --- a/packages/better-auth/src/client/path-to-object.ts +++ b/packages/better-auth/src/client/path-to-object.ts @@ -94,8 +94,8 @@ export type InferRoute = API extends Record< ? C extends InputContext ? < FetchOptions extends BetterFetchOption< - C["body"] & Record, - C["query"] & Record, + Partial & Record, + Partial & Record, C["params"] >, >( diff --git a/packages/better-auth/src/client/react/index.ts b/packages/better-auth/src/client/react/index.ts index 3c6502f6..0412569f 100644 --- a/packages/better-auth/src/client/react/index.ts +++ b/packages/better-auth/src/client/react/index.ts @@ -100,8 +100,4 @@ export function createAuthClient