feat(account): add option to allow unlinking all accounts (#1818)

This commit is contained in:
Bereket Engida
2025-03-14 20:22:59 +03:00
committed by GitHub
parent 1152c3dfe2
commit e157e06a51
5 changed files with 48 additions and 21 deletions

View File

@@ -316,7 +316,15 @@ export const auth = betterAuth({
- `modelName`: The model name for the account
- `fields`: Map fields to different column names
- `accountLinking`: Configuration for account linking
### `accountLinking`
Configuration for account linking.
- `enabled`: Enable account linking (default: `false`)
- `trustedProviders`: List of trusted providers
- `allowDifferentEmails`: Allow users to link accounts with different email addresses
- `allowUnlinkingAll`: Allow users to unlink all accounts
## `verification`

View File

@@ -59,6 +59,8 @@ describe("account", async () => {
},
});
const ctx = await auth.$context;
const { headers } = await signInWithTestUser();
it("should list all accounts", async () => {
@@ -198,6 +200,15 @@ describe("account", async () => {
headers,
},
});
await ctx.adapter.delete({
model: "account",
where: [
{
field: "providerId",
value: "google",
},
],
});
const unlinkAccountId = previousAccounts.data![0].accountId;
const unlinkRes = await client.unlinkAccount({
providerId: "credential",

View File

@@ -184,17 +184,14 @@ export const unlinkAccount = createAuthEndpoint(
message: BASE_ERROR_CODES.ACCOUNT_NOT_FOUND,
});
}
const remainingAccounts = accounts.filter(
(account) => account.providerId === ctx.body.providerId,
);
if (remainingAccounts.length === 1) {
if (
accounts.length === 1 &&
!ctx.context.options.account?.accountLinking?.allowUnlinkingAll
) {
throw new APIError("BAD_REQUEST", {
message: BASE_ERROR_CODES.FAILED_TO_UNLINK_LAST_ACCOUNT,
});
}
await ctx.context.internalAdapter.deleteAccount(accountExist.id);
return ctx.json({
status: true,

View File

@@ -440,6 +440,12 @@ export type BetterAuthOptions = {
* ⚠️ Warning: enabling this might lead to account takeovers, so proceed with caution.
*/
allowDifferentEmails?: boolean;
/**
* If enabled (true), this will allow users to unlink all accounts.
*
* @default false
*/
allowUnlinkingAll?: boolean;
};
};
/**

View File

@@ -1,20 +1,25 @@
{
"compilerOptions": {
"esModuleInterop": true,
"skipLibCheck": true,
"declaration": true,
"emitDeclarationOnly": true,
"declarationMap": true,
"outDir": "dist",
"noEmit": false,
"composite": false,
"target": "es2022",
"allowJs": true,
"resolveJsonModule": true,
"module": "ESNext",
"noEmit": true,
"incremental": true,
"moduleResolution": "Bundler",
"moduleDetection": "force",
"isolatedModules": true,
"verbatimModuleSyntax": true,
"strict": true,
"noImplicitOverride": true,
"noFallthroughCasesInSwitch": true
"moduleDetection": "force",
"module": "Preserve",
"skipLibCheck": true,
"types": ["node"],
"isolatedModules": true,
"tsBuildInfoFile": ".tsbuildinfo",
"preserveSymlinks": true,
"noImplicitOverride": true
},
"exclude": ["node_modules"],
"include": ["src"]
"exclude": ["node_modules", "dist"],
"references": [],
"include": ["src/**/*"]
}