mirror of
https://github.com/better-auth/better-auth.git
synced 2026-05-26 08:56:40 -05:00
feat(magic-link): add request metadata to sendMagicLink
This commit is contained in:
@@ -20,7 +20,7 @@ Magic link or email link is a way to authenticate users without a password. When
|
||||
export const auth = betterAuth({
|
||||
plugins: [
|
||||
magicLink({ // [!code highlight]
|
||||
sendMagicLink: async ({ email, token, url }, ctx) => { // [!code highlight]
|
||||
sendMagicLink: async ({ email, token, url, metadata }, ctx) => { // [!code highlight]
|
||||
// send email to user // [!code highlight]
|
||||
} // [!code highlight]
|
||||
}) // [!code highlight]
|
||||
@@ -85,6 +85,10 @@ type signInMagicLink = {
|
||||
* redirected to the callbackURL with an `error` query parameter.
|
||||
*/
|
||||
errorCallbackURL?: string = "/error"
|
||||
/**
|
||||
* Additional metadata forwarded to the sendMagicLink callback.
|
||||
*/
|
||||
metadata?: Record<string, any> = { inviteId: "123" }
|
||||
}
|
||||
```
|
||||
</APIMethod>
|
||||
@@ -130,6 +134,7 @@ type magicLinkVerify = {
|
||||
- `email`: The email address of the user.
|
||||
- `url`: The URL to be sent to the user. This URL contains the token.
|
||||
- `token`: The token if you want to send the token with custom URL.
|
||||
- `metadata`: Additional request metadata passed from `signIn.magicLink`.
|
||||
|
||||
and a `ctx` context object as the second parameter.
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
deviceAuthorizationClient,
|
||||
emailOTPClient,
|
||||
genericOAuthClient,
|
||||
magicLinkClient,
|
||||
multiSessionClient,
|
||||
oidcClient,
|
||||
organizationClient,
|
||||
@@ -270,6 +271,36 @@ describe("type", () => {
|
||||
expectTypeOf(client.test.signOut).toEqualTypeOf<() => Promise<void>>();
|
||||
});
|
||||
|
||||
it("should infer magic link metadata in sign-in request", () => {
|
||||
const client = createReactClient({
|
||||
plugins: [magicLinkClient()],
|
||||
});
|
||||
|
||||
type SignInMagicLinkInput = NonNullable<
|
||||
Parameters<typeof client.signIn.magicLink>[0]
|
||||
>;
|
||||
|
||||
expectTypeOf<SignInMagicLinkInput>().toMatchTypeOf<{
|
||||
email: string;
|
||||
name?: string | undefined;
|
||||
callbackURL?: string | undefined;
|
||||
newUserCallbackURL?: string | undefined;
|
||||
errorCallbackURL?: string | undefined;
|
||||
metadata?: Record<string, any> | undefined;
|
||||
}>();
|
||||
|
||||
const request: SignInMagicLinkInput = {
|
||||
email: "test@email.com",
|
||||
metadata: {
|
||||
inviteId: "123",
|
||||
},
|
||||
};
|
||||
|
||||
expectTypeOf(request.metadata).toEqualTypeOf<
|
||||
Record<string, any> | undefined
|
||||
>();
|
||||
});
|
||||
|
||||
it("should infer session", () => {
|
||||
const client = createSolidClient({
|
||||
plugins: [testClientPlugin(), testClientPlugin2(), twoFactorClient()],
|
||||
|
||||
@@ -39,6 +39,7 @@ export interface MagicLinkOptions {
|
||||
email: string;
|
||||
url: string;
|
||||
token: string;
|
||||
metadata?: Record<string, any>;
|
||||
},
|
||||
ctx?: GenericEndpointContext | undefined,
|
||||
) => Awaitable<void>;
|
||||
@@ -112,6 +113,12 @@ const signInMagicLinkBodySchema = z.object({
|
||||
description: "URL to redirect after error.",
|
||||
})
|
||||
.optional(),
|
||||
metadata: z
|
||||
.record(z.string(), z.any())
|
||||
.meta({
|
||||
description: "Additional metadata to pass to sendMagicLink.",
|
||||
})
|
||||
.optional(),
|
||||
});
|
||||
const magicLinkVerifyQuerySchema = z.object({
|
||||
token: z.string().meta({
|
||||
@@ -208,7 +215,7 @@ export const magicLink = (options: MagicLinkOptions) => {
|
||||
},
|
||||
},
|
||||
async (ctx) => {
|
||||
const { email } = ctx.body;
|
||||
const { email, metadata } = ctx.body;
|
||||
|
||||
const verificationToken = opts?.generateToken
|
||||
? await opts.generateToken(email)
|
||||
@@ -243,6 +250,7 @@ export const magicLink = (options: MagicLinkOptions) => {
|
||||
email,
|
||||
url: url.toString(),
|
||||
token: verificationToken,
|
||||
metadata,
|
||||
},
|
||||
ctx,
|
||||
);
|
||||
|
||||
@@ -9,6 +9,7 @@ type VerificationEmail = {
|
||||
email: string;
|
||||
token: string;
|
||||
url: string;
|
||||
metadata?: Record<string, any>;
|
||||
};
|
||||
|
||||
describe("magic link", async () => {
|
||||
@@ -50,6 +51,23 @@ describe("magic link", async () => {
|
||||
"http://localhost:3000/api/auth/magic-link/verify",
|
||||
),
|
||||
});
|
||||
expect(verificationEmail.metadata).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should forward metadata to sendMagicLink", async () => {
|
||||
await client.signIn.magicLink({
|
||||
email: testUser.email,
|
||||
metadata: {
|
||||
inviteId: "123",
|
||||
},
|
||||
});
|
||||
|
||||
expect(verificationEmail).toMatchObject({
|
||||
email: testUser.email,
|
||||
metadata: {
|
||||
inviteId: "123",
|
||||
},
|
||||
});
|
||||
});
|
||||
it("should verify magic link", async () => {
|
||||
const headers = new Headers();
|
||||
|
||||
Reference in New Issue
Block a user