diff --git a/docs/content/docs/reference/options.mdx b/docs/content/docs/reference/options.mdx index 196aa96d54..a65112b2a0 100644 --- a/docs/content/docs/reference/options.mdx +++ b/docs/content/docs/reference/options.mdx @@ -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` diff --git a/packages/better-auth/src/api/routes/account.test.ts b/packages/better-auth/src/api/routes/account.test.ts index 13dd442736..d8073ecf25 100644 --- a/packages/better-auth/src/api/routes/account.test.ts +++ b/packages/better-auth/src/api/routes/account.test.ts @@ -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", diff --git a/packages/better-auth/src/api/routes/account.ts b/packages/better-auth/src/api/routes/account.ts index a5e8253b13..61d8ae2eed 100644 --- a/packages/better-auth/src/api/routes/account.ts +++ b/packages/better-auth/src/api/routes/account.ts @@ -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, diff --git a/packages/better-auth/src/types/options.ts b/packages/better-auth/src/types/options.ts index e9846e42e3..a675371390 100644 --- a/packages/better-auth/src/types/options.ts +++ b/packages/better-auth/src/types/options.ts @@ -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; }; }; /** diff --git a/packages/expo/tsconfig.json b/packages/expo/tsconfig.json index c347c9346a..0781be228c 100644 --- a/packages/expo/tsconfig.json +++ b/packages/expo/tsconfig.json @@ -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/**/*"] }