
Reactive Resume 隐式社交注册用 Better Auth 原生行为统一登录页与注册页的社交认证【免费下载链接】reactive-resumeA one-of-a-kind resume builder that keeps your privacy in mind. Completely secure, customizable, portable, open-source and free forever. Try it out today!项目地址: https://gitcode.com/GitHub_Trending/re/reactive-resume本文基于 Reactive Resume 仓库中的实施计划 implicit-social-signup讲解如何让 Google、GitHub、LinkedIn 社交登录在任意入口都自动为陌生身份创建账号implicit signup同时把全局开关FLAG_DISABLE_SIGNUPS保留为唯一的服务端硬性拦截。读完后你将掌握Better Auth 中disableImplicitSignUp与disableSignUp两级开关的分工、如何用回归测试锁定社交提供方注册策略以及如何删除客户端“注册意图”分支来收敛前端认证逻辑。问题背景社交登录行为取决于入口页面在 Better Auth 中社交提供方默认具备“隐式注册”implicit signup行为当用某个社交身份认证、而本地不存在绑定该身份的用户时框架会自动创建用户并登录已绑定用户则直接登录。Reactive Resume 此前为 Google / GitHub / LinkedIn 三个内置提供方显式写入了disableImplicitSignUp: true把这一默认行为关掉并转而依赖客户端区分“从登录页发起”还是“从注册页发起”注册页通过SocialAuth requestSignUp /传递注册意图登录页则不带该 prop。这导致社交认证的行为取决于用户从哪个页面点击进入逻辑分散在前后端两处。该计划的配套设计文档 implicit-social-signup-design 将目标行为概括为四点已绑定到现有用户的社交身份直接登录该用户注册功能开启时陌生社交身份自动创建用户并登录FLAG_DISABLE_SIGNUPS开启时陌生社交身份被拒绝行为与认证从登录页还是注册页发起无关。计划总览目标、架构与全局约束计划文档对实施给出了明确的边界声明这是后续所有改动的“合同”项目内容Goal让每个内置社交登录自动为未知用户创建账号同时保留全局注册限制Architecture通过移除 provider 级disableImplicitSignUp退出开关改用 Better Auth 原生隐式注册行为删除客户端requestSignUp区分让所有页面走同一个社交登录调用disableSignUp: env.FLAG_DISABLE_SIGNUPS仍是服务端强制执行的一票否决Tech StackTypeScript、Better Auth计划写作时为 1.6.26当前仓库 packages/auth/package.json 已固定为better-auth: 1.7.2、React 19、Vitest、pnpm全局约束Global Constraints不新增依赖、不引入新抽象行为只应用于 Google、GitHub、LinkedIn 三个内置提供方自定义 OAuthcustom provider与 passkey 行为保持不变FLAG_DISABLE_SIGNUPS必须保持“服务端权威”不能退化成前端提示。涉及的文件共四个文件角色packages/auth/src/config.test.ts特征化characterize内置社交提供方的注册策略packages/auth/src/config.ts拥有 Better Auth 提供方配置与全局注册限制apps/web/src/features/auth/components/social-auth.tsx发起社交登录不再携带页面级注册意图apps/web/src/features/auth/pages/register.tsx以无注册意图 prop 的方式复用社交登录组件Task 1 第一步先写失败测试锁定注册策略计划采用 TDD先写一个注定失败的配置测试明确“什么样的配置才算正确”。测试直接断言auth.options.socialProviders上三个内置提供方的两个属性——disableImplicitSignUp必须不存在即没有退出隐式注册disableSignUp必须精确等于env.FLAG_DISABLE_SIGNUPSimport { describe, expect, it } from vitest; import { env } from reactive-resume/env/server; import { auth } from ./config; describe(social provider signup policy, () { it.each([google, github, linkedin] as const)( allows implicit signup through %s while honoring the global signup restriction, (provider) { const config auth.options.socialProviders?.[provider]; expect(config?.disableImplicitSignUp).toBeUndefined(); expect(config?.disableSignUp).toBe(env.FLAG_DISABLE_SIGNUPS); }, ); });这个测试的作用是让任何“某个提供方重新退出隐式注册”或“disableSignUp与FLAG_DISABLE_SIGNUPS脱钩”的回归都立刻在 CI 中失败——它锁住的是服务端配置本身的策略而不是依赖手工走一遍 OAuth 流程才能发现的集成层行为。Task 1 第二步运行测试并确认预期失败pnpm --filter reactive-resume/auth test -- src/config.test.ts预期结果为 FAIL改动前 Google、GitHub、LinkedIn 三个提供方的disableImplicitSignUp均为true而非undefined第一条断言会全部不通过。确认“失败是因为旧配置”而非环境错误是整个 TDD 循环的锚点。Task 1 第三步启用原生隐式注册删除客户端注册意图这一步包含服务端与前端两侧改动且两侧必须同步落地。服务端只删退出开关保留全局开关在 packages/auth/src/config.ts 中仅删除 google、github、linkedin 三个提供方里的disableImplicitSignUp: true三行每个提供方既有的这一行必须保留disableSignUp: env.FLAG_DISABLE_SIGNUPS,这正是计划 Architecture 一节的落点disableImplicitSignUp控制“陌生身份能否首次注册”disableSignUp控制“本实例是否全局禁止注册”。删掉前者让原生隐式注册生效保留后者让私有化部署仍可通过环境变量一键关闭所有注册。前端收敛为单一社交登录调用在 apps/web/src/features/auth/components/social-auth.tsx 中删除SocialAuthProps、SocialSignInOptions和getSocialSignInOptions组件签名简化为export function SocialAuth() {加载分支改为{isLoading ? SocialAuthSkeleton / : SocialAuthButtons providers{providers} /}从按钮 props 与函数参数中移除requestSignUptype SocialAuthButtonsProps { providers: RouterOutput[auth][providers][list]; }; function SocialAuthButtons({ providers }: SocialAuthButtonsProps) {Google、GitHub、LinkedIn 的调用统一替换为 Better Auth 客户端的原生入参形态authClient.signIn.social({ provider: google, callbackURL: /dashboard }); authClient.signIn.social({ provider: github, callbackURL: /dashboard }); authClient.signIn.social({ provider: linkedin, callbackURL: /dashboard });最后在 apps/web/src/features/auth/pages/register.tsx 中把SocialAuth requestSignUp /改为SocialAuth /登录页LoginPage无需改动它本就以SocialAuth /渲染。至此登录页与注册页的社交入口在代码层面完全同构“注册意图”这一前端概念被彻底消除。Task 1 第四、五步验证与静态检查确认目标测试通过pnpm --filter reactive-resume/auth test -- src/config.test.ts预期三个提供方全部 PASS。随后执行聚焦验证确保改动没有破坏类型与代码规范pnpm --filter reactive-resume/auth typecheck pnpm --filter web typecheck pnpm exec biome check packages/auth/src/config.test.ts packages/auth/src/config.ts apps/web/src/features/auth/components/social-auth.tsx apps/web/src/features/auth/pages/register.tsx预期所有命令成功退出、无诊断信息、无文件被自动改写。最后按计划提交git add packages/auth/src/config.test.ts packages/auth/src/config.ts apps/web/src/features/auth/components/social-auth.tsx apps/web/src/features/auth/pages/register.tsx git commit -m fix(auth): allow implicit social signup源码证据当前仓库中的落地形态该计划已在当前仓库中实施完成以下源码状态可以作为文章各结论的逐条印证。服务端配置三个提供方只保留disableSignUppackages/auth/src/config.ts 中socialProviders的现状与计划目标完全一致socialProviders: { google: { enabled: !!env.GOOGLE_CLIENT_ID !!env.GOOGLE_CLIENT_SECRET, disableSignUp: env.FLAG_DISABLE_SIGNUPS, clientId: env.GOOGLE_CLIENT_ID ?? , clientSecret: env.GOOGLE_CLIENT_SECRET ?? , mapProfileToUser: createProfileMapper({ providerName: Google, getName: (profile, context) profile.name ?? context.emailLocalPart, getImage: (profile) profile.picture, }), }, // github / linkedin 结构相同均无 disableImplicitSignUp },三点值得注意enabled由凭据存在性决定。任一提供方只有在*_CLIENT_ID与*_CLIENT_SECRET环境变量齐备时才激活如enabled: !!env.GOOGLE_CLIENT_ID !!env.GOOGLE_CLIENT_SECRET自托管实例配置了什么登录页就显示什么约束遵守情况custom OAuth 提供方config.ts#L118-L138本就只设置disableSignUp: env.FLAG_DISABLE_SIGNUPS而不退出隐式注册passkey 走passkey()插件config.ts#L292而非社交提供方两者均按全局约束保持行为不变邮箱注册的同源开关emailAndPassword.disableSignUp取值为env.FLAG_DISABLE_SIGNUPS || env.FLAG_DISABLE_EMAIL_AUTHconfig.ts#L197即社交通道与邮箱通道共享同一个全局注册开关FLAG_DISABLE_EMAIL_AUTH只在关闭邮箱认证这一条额外收紧。回归测试锁住策略的守门员packages/auth/src/config.test.ts 在计划版本基础上略有演化——因为 Better Auth 1.7 允许() config的惰性配置形态测试增加了对静态对象形态的防御性断言并将第一处断言改为expect(config).not.toHaveProperty(disableImplicitSignUp)describe(social provider signup policy, () { it.each([google, github, linkedin] as const)( allows implicit signup through %s while honoring the global signup restriction, (provider) { // Better Auth 1.7 allows a lazy () config form; ours are always static objects. const config auth.options.socialProviders?.[provider]; if (typeof config function) throw new TypeError(${provider} provider config should be a static object); expect(config).not.toHaveProperty(disableImplicitSignUp); expect(config?.disableSignUp).toBe(env.FLAG_DISABLE_SIGNUPS); }, ); });同一文件还附带了对session.freshAge: 0的断言用于防止会话新鲜度门槛回归——该配置config.ts#L246解决了 Better Auth 默认一天新鲜度导致一周内登录的用户无法解绑社交账号的问题。前端组件一个无 prop 的SocialAuth当前的 social-auth.tsx 已经完全收敛export function SocialAuth() { const { data: providers {}, isLoading } useQuery(orpc.auth.providers.list.queryOptions()); return ( {/* or continue with 分隔条 */} {isLoading ? SocialAuthSkeleton / : SocialAuthButtons providers{providers} /} / ); }从源码结构看SocialAuth通过 oRPC 端点auth.providers.list拉取本实例启用的提供方映射对应服务端 packages/api/src/features/auth/router.ts 暴露的GET /auth/providers返回“提供方标识 → 显示名”的映射按钮按google in providers等条件渲染未启用的提供方保持hidden。每个按钮经runSignIn包装统一处理 loading toast 与错误提示然后调用authClient.signIn.social({ provider, callbackURL: /dashboard })。authClient在 apps/web/src/libs/auth/client.ts 中由createAuthClient创建插件列表passkey、twoFactor、apiKey 等与服务端plugins数组一一对应保证客户端调用类型与服务端路由一致。页面侧register.tsx#L247 与登录页均以无 prop 的SocialAuth /收尾——计划中“删除requestSignUp区分”的目标在代码层面已完全兑现登录页与注册页不再存在行为差异。FLAG_DISABLE_SIGNUPS服务端权威的注册总闸隐式注册把“能否注册”的判断完全交还给服务端后FLAG_DISABLE_SIGNUPS的语义变得格外关键。它在仓库中的全链路如下定义与默认值packages/env/src/server.ts#L79 中FLAG_DISABLE_SIGNUPS: z.stringbool().default(false)默认允许注册类型经 zod 的stringbool校验自托管配置.env.example#L84-L90 给出示例与说明——“This flag disables new signups, both on the web app and the server”并提示FLAG_DISABLE_EMAIL_AUTH关闭邮箱登录时用户仍可通过社交通道注册除非FLAG_DISABLE_SIGNUPS同时为 truedocs/self-hosting/docker.mdx 在 Feature Flags 折叠区对其有一致描述适合私有实例服务端消费社交提供方三个内置 custom与邮箱通道统一读取该标志见上一节代码公开只读 APIpackages/api/src/features/flags/router.ts#L35-L41 通过无需鉴权的/flags端点公开disableSignups等实例级标志前端据此可以调整入口展示而真正的拒绝仍发生在 Better Auth 的服务端回调链路中——即使前端隐藏入口客户端仍可发起signIn.social陌生身份会在服务端被拒绝E2E 环境tests/e2e/README.md 的迁移、构建与测试命令中显式传入FLAG_DISABLE_SIGNUPSfalse保证端到端用例始终处于“注册开启”的受控前提。验证与复现在仓库根目录下可逐条复现计划中的验证流程前提已pnpm install并配置好本地数据库等运行环境# 社交提供方注册策略回归测试 pnpm --filter reactive-resume/auth test -- src/config.test.ts # 类型检查auth 包与 web 应用 pnpm --filter reactive-resume/auth typecheck pnpm --filter web typecheck # 代码规范检查涉及改动的四个文件 pnpm exec biome check packages/auth/src/config.test.ts packages/auth/src/config.ts \ apps/web/src/features/auth/components/social-auth.tsx \ apps/web/src/features/auth/pages/register.tsx小结这篇计划文档演示了一次小而完整的认证策略调整服务端只删三行disableImplicitSignUp: true、保留disableSignUp: env.FLAG_DISABLE_SIGNUPS前端删除requestSignUpprop 与意图构建函数让登录页与注册页复用同一个无参数SocialAuth /组件一个针对auth.options.socialProviders的回归测试把“内置社交提供方允许隐式注册且服从全局限制”的策略固化进 CI。最终效果是社交认证行为不再依赖入口页面而“能否注册”这一安全决策始终由服务端的FLAG_DISABLE_SIGNUPS单点决定——这对基于 Better Auth 构建的自托管应用是一个值得参考的注册策略收敛范式。【免费下载链接】reactive-resumeA one-of-a-kind resume builder that keeps your privacy in mind. Completely secure, customizable, portable, open-source and free forever. Try it out today!项目地址: https://gitcode.com/GitHub_Trending/re/reactive-resume创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考