Merge remote-tracking branch 'origin/main' into v1.3

This commit is contained in:
Bereket Engida
2025-06-27 22:57:26 -07:00
11 changed files with 509 additions and 47 deletions

View File

@@ -159,38 +159,38 @@ export default function UserCard(props: {
Please verify your email address. Check your inbox for the
verification email. If you haven't received the email, click the
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>
<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>
)}

View File

@@ -597,7 +597,7 @@ await authClient.organization.updateMemberRole({
### 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"
const member = await authClient.organization.getActiveMember()

View File

@@ -674,7 +674,7 @@
"@simplewebauthn/server": "^13.0.0",
"better-call": "catalog:",
"defu": "^6.1.4",
"jose": "^5.9.6",
"jose": "^6.0.11",
"kysely": "^0.28.2",
"nanostores": "^0.11.3",
"zod": "^3.24.1"

View File

@@ -159,11 +159,20 @@ export const createAdapter =
* then we should return the model name ending with an `s`.
*/
const getModelName = (model: string) => {
return schema[getDefaultModelName(model)].modelName !== model
? schema[getDefaultModelName(model)].modelName
: config.usePlural
? `${model}s`
: model;
const defaultModelKey = getDefaultModelName(model);
const usePlural = config && config.usePlural;
const useCustomModelName =
schema &&
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.
@@ -477,6 +486,8 @@ export const createAdapter =
? undefined
: CleanedWhere[] => {
if (!where) return undefined as any;
const newMappedKeys = config.mapKeysTransformInput ?? {};
return where.map((w) => {
const {
field: unsafe_field,
@@ -495,11 +506,13 @@ export const createAdapter =
field: unsafe_field,
model,
});
const fieldName: string =
newMappedKeys[defaultFieldName] ||
getFieldName({
field: defaultFieldName,
model: defaultModelName,
});
const fieldName = getFieldName({
field: defaultFieldName,
model: defaultModelName,
});
const fieldAttr = getFieldAttributes({
field: defaultFieldName,
model: defaultModelName,

View File

@@ -1,6 +1,10 @@
import { describe, test, expect } from "vitest";
import { createAdapter } from "..";
import type { AdapterConfig, CreateCustomAdapter } from "../types";
import type {
AdapterConfig,
CleanedWhere,
CreateCustomAdapter,
} from "../types";
import type { BetterAuthOptions, User, Where } from "../../../types";
import { betterAuth } from "../../../auth";
@@ -161,6 +165,31 @@ describe("Create Adapter Helper", async () => {
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 () => {
let error: any | null = null;
try {
@@ -559,6 +588,7 @@ describe("Create Adapter Helper", async () => {
};
},
});
const res = (await adapter.create({
model: "user",
data: { email: "test@test.com" },
@@ -572,6 +602,33 @@ describe("Create Adapter Helper", async () => {
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 () => {
const parameters: {
data: { email: string };

View File

@@ -94,7 +94,10 @@ export const originCheck = (
if (pattern.includes("*")) {
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) => {

View File

@@ -256,6 +256,7 @@ export const linkSocialAccount = createAuthEndpoint(
if (hasBeenLinked) {
return c.json({
redirect: false,
url: "", // this is for type inference
status: true,
});
}
@@ -316,6 +317,7 @@ export const linkSocialAccount = createAuthEndpoint(
return c.json({
redirect: false,
url: "", // this is for type inference
status: true,
});
}

View File

@@ -5,6 +5,8 @@ import { parseSetCookieHeader } from "../cookies";
let isBuilding: boolean | undefined;
let isBuilding: boolean | undefined;
export const toSvelteKitHandler = (auth: {
handler: (request: Request) => any;
options: BetterAuthOptions;

View File

@@ -392,6 +392,10 @@ export const emailOTP = (options: EmailOTPOptions) => {
},
ctx,
);
await ctx.context.options.emailVerification?.onEmailVerification?.(
updatedUser,
ctx.request,
);
if (
ctx.context.options.emailVerification?.autoSignInAfterVerification

View File

@@ -430,7 +430,7 @@ export const getActiveMember = createAuthEndpoint(
use: [orgMiddleware, orgSessionMiddleware],
metadata: {
openapi: {
description: "Get the active member in the organization",
description: "Get the member details of the active organization",
responses: {
"200": {
description: "Success",

385
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff