mirror of
https://github.com/LukeHagar/better-auth.git
synced 2025-12-09 20:27:44 +00:00
Merge remote-tracking branch 'origin/main' into v1.3
This commit is contained in:
@@ -159,38 +159,38 @@ export default function UserCard(props: {
|
|||||||
Please verify your email address. Check your inbox for the
|
Please verify your email address. Check your inbox for the
|
||||||
verification email. If you haven't received the email, click the
|
verification email. If you haven't received the email, click the
|
||||||
button below to resend.
|
button below to resend.
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="secondary"
|
||||||
|
className="mt-2"
|
||||||
|
onClick={async () => {
|
||||||
|
await client.sendVerificationEmail(
|
||||||
|
{
|
||||||
|
email: session?.user.email || "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
onRequest(context) {
|
||||||
|
setEmailVerificationPending(true);
|
||||||
|
},
|
||||||
|
onError(context) {
|
||||||
|
toast.error(context.error.message);
|
||||||
|
setEmailVerificationPending(false);
|
||||||
|
},
|
||||||
|
onSuccess() {
|
||||||
|
toast.success("Verification email sent successfully");
|
||||||
|
setEmailVerificationPending(false);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{emailVerificationPending ? (
|
||||||
|
<Loader2 size={15} className="animate-spin" />
|
||||||
|
) : (
|
||||||
|
"Resend Verification Email"
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
</AlertDescription>
|
</AlertDescription>
|
||||||
<Button
|
|
||||||
size="sm"
|
|
||||||
variant="secondary"
|
|
||||||
className="mt-2"
|
|
||||||
onClick={async () => {
|
|
||||||
await client.sendVerificationEmail(
|
|
||||||
{
|
|
||||||
email: session?.user.email || "",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
onRequest(context) {
|
|
||||||
setEmailVerificationPending(true);
|
|
||||||
},
|
|
||||||
onError(context) {
|
|
||||||
toast.error(context.error.message);
|
|
||||||
setEmailVerificationPending(false);
|
|
||||||
},
|
|
||||||
onSuccess() {
|
|
||||||
toast.success("Verification email sent successfully");
|
|
||||||
setEmailVerificationPending(false);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{emailVerificationPending ? (
|
|
||||||
<Loader2 size={15} className="animate-spin" />
|
|
||||||
) : (
|
|
||||||
"Resend Verification Email"
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
</Alert>
|
</Alert>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -597,7 +597,7 @@ await authClient.organization.updateMemberRole({
|
|||||||
|
|
||||||
### Get Active Member
|
### Get Active Member
|
||||||
|
|
||||||
To get the current member of the organization you can use the `organization.getActiveMember` function. This function will return the current active member.
|
To get the member details of the active organization you can use the `organization.getActiveMember` function.
|
||||||
|
|
||||||
```ts title="auth-client.ts"
|
```ts title="auth-client.ts"
|
||||||
const member = await authClient.organization.getActiveMember()
|
const member = await authClient.organization.getActiveMember()
|
||||||
|
|||||||
@@ -674,7 +674,7 @@
|
|||||||
"@simplewebauthn/server": "^13.0.0",
|
"@simplewebauthn/server": "^13.0.0",
|
||||||
"better-call": "catalog:",
|
"better-call": "catalog:",
|
||||||
"defu": "^6.1.4",
|
"defu": "^6.1.4",
|
||||||
"jose": "^5.9.6",
|
"jose": "^6.0.11",
|
||||||
"kysely": "^0.28.2",
|
"kysely": "^0.28.2",
|
||||||
"nanostores": "^0.11.3",
|
"nanostores": "^0.11.3",
|
||||||
"zod": "^3.24.1"
|
"zod": "^3.24.1"
|
||||||
|
|||||||
@@ -159,11 +159,20 @@ export const createAdapter =
|
|||||||
* then we should return the model name ending with an `s`.
|
* then we should return the model name ending with an `s`.
|
||||||
*/
|
*/
|
||||||
const getModelName = (model: string) => {
|
const getModelName = (model: string) => {
|
||||||
return schema[getDefaultModelName(model)].modelName !== model
|
const defaultModelKey = getDefaultModelName(model);
|
||||||
? schema[getDefaultModelName(model)].modelName
|
const usePlural = config && config.usePlural;
|
||||||
: config.usePlural
|
const useCustomModelName =
|
||||||
? `${model}s`
|
schema &&
|
||||||
: model;
|
schema[defaultModelKey] &&
|
||||||
|
schema[defaultModelKey].modelName !== model;
|
||||||
|
|
||||||
|
if (useCustomModelName) {
|
||||||
|
return usePlural
|
||||||
|
? `${schema[defaultModelKey].modelName}s`
|
||||||
|
: schema[defaultModelKey].modelName;
|
||||||
|
}
|
||||||
|
|
||||||
|
return usePlural ? `${model}s` : model;
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* Get the field name which is expected to be saved in the database based on the user's schema.
|
* Get the field name which is expected to be saved in the database based on the user's schema.
|
||||||
@@ -477,6 +486,8 @@ export const createAdapter =
|
|||||||
? undefined
|
? undefined
|
||||||
: CleanedWhere[] => {
|
: CleanedWhere[] => {
|
||||||
if (!where) return undefined as any;
|
if (!where) return undefined as any;
|
||||||
|
const newMappedKeys = config.mapKeysTransformInput ?? {};
|
||||||
|
|
||||||
return where.map((w) => {
|
return where.map((w) => {
|
||||||
const {
|
const {
|
||||||
field: unsafe_field,
|
field: unsafe_field,
|
||||||
@@ -495,11 +506,13 @@ export const createAdapter =
|
|||||||
field: unsafe_field,
|
field: unsafe_field,
|
||||||
model,
|
model,
|
||||||
});
|
});
|
||||||
|
const fieldName: string =
|
||||||
|
newMappedKeys[defaultFieldName] ||
|
||||||
|
getFieldName({
|
||||||
|
field: defaultFieldName,
|
||||||
|
model: defaultModelName,
|
||||||
|
});
|
||||||
|
|
||||||
const fieldName = getFieldName({
|
|
||||||
field: defaultFieldName,
|
|
||||||
model: defaultModelName,
|
|
||||||
});
|
|
||||||
const fieldAttr = getFieldAttributes({
|
const fieldAttr = getFieldAttributes({
|
||||||
field: defaultFieldName,
|
field: defaultFieldName,
|
||||||
model: defaultModelName,
|
model: defaultModelName,
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
import { describe, test, expect } from "vitest";
|
import { describe, test, expect } from "vitest";
|
||||||
import { createAdapter } from "..";
|
import { createAdapter } from "..";
|
||||||
import type { AdapterConfig, CreateCustomAdapter } from "../types";
|
import type {
|
||||||
|
AdapterConfig,
|
||||||
|
CleanedWhere,
|
||||||
|
CreateCustomAdapter,
|
||||||
|
} from "../types";
|
||||||
import type { BetterAuthOptions, User, Where } from "../../../types";
|
import type { BetterAuthOptions, User, Where } from "../../../types";
|
||||||
import { betterAuth } from "../../../auth";
|
import { betterAuth } from "../../../auth";
|
||||||
|
|
||||||
@@ -161,6 +165,31 @@ describe("Create Adapter Helper", async () => {
|
|||||||
expect(res.id).toBe("HARD-CODED-ID");
|
expect(res.id).toBe("HARD-CODED-ID");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("Should not generate an id if `advanced.database.generateId` is not defined or false", async () => {
|
||||||
|
const adapter = await createTestAdapter({
|
||||||
|
config: {},
|
||||||
|
options: {
|
||||||
|
advanced: {
|
||||||
|
database: { generateId: false, useNumberId: false },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
adapter(args_0) {
|
||||||
|
return {
|
||||||
|
async create(data) {
|
||||||
|
expect(data.data.id).not.toBeDefined();
|
||||||
|
return data.data;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const testResult = await adapter.create({
|
||||||
|
model: "user",
|
||||||
|
data: { name: "test-name" },
|
||||||
|
});
|
||||||
|
expect(testResult.id).not.toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
test("Should throw an error if the database doesn't support numeric ids and the user has enabled `useNumberId`", async () => {
|
test("Should throw an error if the database doesn't support numeric ids and the user has enabled `useNumberId`", async () => {
|
||||||
let error: any | null = null;
|
let error: any | null = null;
|
||||||
try {
|
try {
|
||||||
@@ -559,6 +588,7 @@ describe("Create Adapter Helper", async () => {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const res = (await adapter.create({
|
const res = (await adapter.create({
|
||||||
model: "user",
|
model: "user",
|
||||||
data: { email: "test@test.com" },
|
data: { email: "test@test.com" },
|
||||||
@@ -572,6 +602,33 @@ describe("Create Adapter Helper", async () => {
|
|||||||
expect(parameters.data.email_address).toEqual("test@test.com");
|
expect(parameters.data.email_address).toEqual("test@test.com");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("Should allow custom transform input to transform the where clause", async () => {
|
||||||
|
const parameters: CleanedWhere[] = await new Promise(async (r) => {
|
||||||
|
const adapter = await createTestAdapter({
|
||||||
|
config: {
|
||||||
|
debugLogs: {},
|
||||||
|
mapKeysTransformInput: {
|
||||||
|
id: "_id",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
adapter(args_0) {
|
||||||
|
return {
|
||||||
|
async findOne({ model, where, select }) {
|
||||||
|
r(where);
|
||||||
|
return {} as any;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
adapter.findOne({
|
||||||
|
model: "user",
|
||||||
|
where: [{ field: "id", value: "123" }],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(parameters[0]!.field).toEqual("_id");
|
||||||
|
});
|
||||||
|
|
||||||
test("Should allow custom map output key transformation", async () => {
|
test("Should allow custom map output key transformation", async () => {
|
||||||
const parameters: {
|
const parameters: {
|
||||||
data: { email: string };
|
data: { email: string };
|
||||||
|
|||||||
@@ -94,7 +94,10 @@ export const originCheck = (
|
|||||||
if (pattern.includes("*")) {
|
if (pattern.includes("*")) {
|
||||||
return wildcardMatch(pattern)(getHost(url));
|
return wildcardMatch(pattern)(getHost(url));
|
||||||
}
|
}
|
||||||
return url.startsWith(pattern);
|
const protocol = getProtocol(url);
|
||||||
|
return protocol === "http:" || protocol === "https:" || !protocol
|
||||||
|
? pattern === getOrigin(url)
|
||||||
|
: url.startsWith(pattern);
|
||||||
};
|
};
|
||||||
|
|
||||||
const validateURL = (url: string | undefined, label: string) => {
|
const validateURL = (url: string | undefined, label: string) => {
|
||||||
|
|||||||
@@ -256,6 +256,7 @@ export const linkSocialAccount = createAuthEndpoint(
|
|||||||
if (hasBeenLinked) {
|
if (hasBeenLinked) {
|
||||||
return c.json({
|
return c.json({
|
||||||
redirect: false,
|
redirect: false,
|
||||||
|
url: "", // this is for type inference
|
||||||
status: true,
|
status: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -316,6 +317,7 @@ export const linkSocialAccount = createAuthEndpoint(
|
|||||||
|
|
||||||
return c.json({
|
return c.json({
|
||||||
redirect: false,
|
redirect: false,
|
||||||
|
url: "", // this is for type inference
|
||||||
status: true,
|
status: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import { parseSetCookieHeader } from "../cookies";
|
|||||||
|
|
||||||
let isBuilding: boolean | undefined;
|
let isBuilding: boolean | undefined;
|
||||||
|
|
||||||
|
let isBuilding: boolean | undefined;
|
||||||
|
|
||||||
export const toSvelteKitHandler = (auth: {
|
export const toSvelteKitHandler = (auth: {
|
||||||
handler: (request: Request) => any;
|
handler: (request: Request) => any;
|
||||||
options: BetterAuthOptions;
|
options: BetterAuthOptions;
|
||||||
|
|||||||
@@ -392,6 +392,10 @@ export const emailOTP = (options: EmailOTPOptions) => {
|
|||||||
},
|
},
|
||||||
ctx,
|
ctx,
|
||||||
);
|
);
|
||||||
|
await ctx.context.options.emailVerification?.onEmailVerification?.(
|
||||||
|
updatedUser,
|
||||||
|
ctx.request,
|
||||||
|
);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
ctx.context.options.emailVerification?.autoSignInAfterVerification
|
ctx.context.options.emailVerification?.autoSignInAfterVerification
|
||||||
|
|||||||
@@ -430,7 +430,7 @@ export const getActiveMember = createAuthEndpoint(
|
|||||||
use: [orgMiddleware, orgSessionMiddleware],
|
use: [orgMiddleware, orgSessionMiddleware],
|
||||||
metadata: {
|
metadata: {
|
||||||
openapi: {
|
openapi: {
|
||||||
description: "Get the active member in the organization",
|
description: "Get the member details of the active organization",
|
||||||
responses: {
|
responses: {
|
||||||
"200": {
|
"200": {
|
||||||
description: "Success",
|
description: "Success",
|
||||||
|
|||||||
385
pnpm-lock.yaml
generated
385
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user