Wasp 自定义注册 Action 完整指南:在 Email 与用户名密码认证中深度接管注册流程

发布时间:2026/9/15 20:15:22
Wasp 自定义注册 Action 完整指南:在 Email 与用户名密码认证中深度接管注册流程 Wasp 自定义注册 Action 完整指南在 Email 与用户名密码认证中深度接管注册流程【免费下载链接】waspThe batteries-included full-stack framework for the AI era. Develop JS/TS web apps (React, Node.js, and Prisma) using declarative code that abstracts away complex full-stack features like auth, background jobs, RPC, email sending, end-to-end type safety, single-command deployment, and more.项目地址: https://gitcode.com/GitHub_Trending/wa/wasp导读Wasp 默认的注册流程开箱即用但当你需要在注册时执行额外的校验、写入更多的用户数据、或调用任何自定义业务代码时可以绕过内置实现创建一个完全由自己掌控的注册 Actioncustom sign-up action。本文将基于 Wasp 0.17 的官方文档结合本仓库生成器源码完整讲解 Email 与用户名密码两种认证方式下自定义注册 Action 的写法、wasp/server/auth提供的底层 API、内置校验器规则以及相关的安全注意事项帮助你安全地深度定制注册逻辑。:::danger 高风险提示自定义注册 Action 是复杂且危险的高级功能官方不建议在缺乏充分理由时使用任何微小的疏漏都可能直接危及应用安全。在动手之前请先确认 Wasp 的自定义认证 UIcustom auth UI与auth hooks认证钩子是否已经能满足你的需求——大多数场景下这两个更轻量、更安全的方案就足够了。:::同时请特别注意使用自定义注册 Action 时你将无法使用 Wasp 生成的认证 UI因此必须自己实现前端界面并在自己的界面中调用你创建的自定义 Action。在 main.wasp 中声明自定义 Action无论使用哪种认证方式自定义注册 Action 的声明方式都相同在 main.wasp0.17 时代为main.wasp新版本为main.wasp.ts中新增一个action其fn指向src/auth/signup.js或.ts导出的signup函数// ... action customSignup { fn: import { signup } from src/auth/signup.js, }这一步的本质是Wasp 会把这个action编译为一个带类型安全的远程过程调用RPC端点前端可以像调用其他 Action 一样调用它例如通过useAction(customSignup)而signup函数内部的逻辑完全由你掌控。Email 认证的自定义注册 Action完整实现JavaScript / TypeScript下面是与 Wasp 内置实现几乎等价的起点代码你可以在此基础上自由定制。文档同时提供了 JS 与 TS 两种版本// ... action customSignup { fn: import { signup } from src/auth/signup.js, }import { HttpError } from wasp/server import { createEmailVerificationLink, createProviderId, createUser, ensurePasswordIsPresent, ensureValidEmail, ensureValidPassword, findAuthIdentity, getProviderData, sanitizeAndSerializeProviderData, sendEmailVerificationEmail, } from wasp/server/auth export const signup async (args, _context) { ensureValidEmail(args) ensurePasswordIsPresent(args) ensureValidPassword(args) try { const providerId createProviderId(email, args.email) const existingAuthIdentity await findAuthIdentity(providerId) let providerData if (existingAuthIdentity) { // User already exists, handle accordingly // For example, throw an error or return a message throw new HttpError(400, Email already exists.) // Or, another example, you can check if the user is already // verified and re-send the verification email if not providerData getProviderData(existingAuthIdentity.providerData) if (providerData.isEmailVerified) { throw new HttpError(400, Email already verified.) } } if (!providerData) { providerData await sanitizeAndSerializeProviderData({ // The provider will hash the password for us, so we dont need to do it here. hashedPassword: args.password, isEmailVerified: false, emailVerificationSentAt: null, passwordResetSentAt: null, }) await createUser( providerId, providerData, // Any additional data you want to store on the User entity {} ) } // Verification link links to a client route e.g. /email-verification const verificationLink await createEmailVerificationLink( args.email, /email-verification ) try { await sendEmailVerificationEmail(args.email, { from: { name: My App Postman, email: helloitsme.com, }, to: args.email, subject: Verify your email, text: Click the link below to verify your email: ${verificationLink}, html: pClick the link below to verify your email/p a href${verificationLink}Verify email/a , }) } catch (e) { console.error(Failed to send email verification email:, e) throw new HttpError(500, Failed to send email verification email.) } } catch (e) { return { success: false, message: e.message, } } // Your custom code after sign-up. // ... return { success: true, message: User created successfully, } }TypeScript 版本// ... action customSignup { fn: import { signup } from src/auth/signup.js, }import { HttpError } from wasp/server import { createEmailVerificationLink, createProviderId, createUser, ensurePasswordIsPresent, ensureValidEmail, ensureValidPassword, findAuthIdentity, getProviderData, sanitizeAndSerializeProviderData, sendEmailVerificationEmail, } from wasp/server/auth import type { CustomSignup } from wasp/server/operations type CustomSignupInput { email: string password: string } type CustomSignupOutput { success: boolean message: string } export const signup: CustomSignup CustomSignupInput, CustomSignupOutput async (args, _context) { ensureValidEmail(args) ensurePasswordIsPresent(args) ensureValidPassword(args) try { const providerId createProviderId(email, args.email) const existingAuthIdentity await findAuthIdentity(providerId) let providerData if (existingAuthIdentity) { // User already exists, handle accordingly // For example, throw an error or return a message throw new HttpError(400, Email already exists.) // Or, another example, you can check if the user is already // verified and re-send the verification email if not providerData getProviderDataemail(existingAuthIdentity.providerData) if (providerData.isEmailVerified) throw new HttpError(400, Email already verified.) } if (!providerData) { providerData await sanitizeAndSerializeProviderDataemail({ // The provider will hash the password for us, so we dont need to do it here. hashedPassword: args.password, isEmailVerified: false, emailVerificationSentAt: null, passwordResetSentAt: null, }) await createUser( providerId, providerData, // Any additional data you want to store on the User entity {} ) } // Verification link links to a client route e.g. /email-verification const verificationLink await createEmailVerificationLink( args.email, /email-verification ) try { await sendEmailVerificationEmail(args.email, { from: { name: My App Postman, email: helloitsme.com, }, to: args.email, subject: Verify your email, text: Click the link below to verify your email: ${verificationLink}, html: pClick the link below to verify your email/p a href${verificationLink}Verify email/a , }) } catch (e: unknown) { console.error(Failed to send email verification email:, e) throw new HttpError(500, Failed to send email verification email.) } } catch (e: any) { return { success: false, message: e.message, } } // Your custom code after sign-up. // ... return { success: true, message: User created successfully, } }关键流程逐段拆解字段校验入口处依次调用ensureValidEmail、ensurePasswordIsPresent、ensureValidPassword这与 Wasp 内置注册路由 email/signup.ts 中的ensureValidArgs完全一致——这组校验器正是 Wasp 默认认证流程内部使用的同一套实现。构建 Provider IDcreateProviderId(email, args.email)生成认证身份的唯一标识Email 在存储时是大小写不敏感的见 overview.md。查重与分支处理findAuthIdentity(providerId)查询是否已存在同名认证身份。文档示例给出了两种策略直接抛出HttpError(400, Email already exists.)或进一步检查isEmailVerified仅在未验证时重发验证邮件。注意原文档中if (existingAuthIdentity)分支在 throw 之后还有一段“不可达”的占位代码实际使用时请选择其一实现。而 Wasp 内置实现email/signup.ts还会做防用户枚举处理对已存在且已验证的用户执行doFakeWork()假装耗时避免攻击者探测哪些邮箱已注册——这是你自定义实现时应当参考的安全细节。写入用户与认证数据sanitizeAndSerializeProviderData负责把明文密码哈希化内部通过hashPassword处理见 server/auth/utils.ts并序列化为 JSON 字符串createUser(providerId, providerData, {})的第三个参数是你要额外存储到User实体的字段对象示例为空{}最终通过 Prisma 在User实体上级联创建Auth与AuthIdentity记录utils.ts。发送验证邮件createEmailVerificationLink(args.email, /email-verification)生成指向客户端路由的验证链接随后sendEmailVerificationEmail发送邮件发送失败时抛出HttpError(500, ...)。示例中的from、subject、text、html均可按需替换为你的品牌信息与邮件模板。注册后自定义逻辑在return之前的位置插入“Your custom code after sign-up”例如初始化用户默认资源、发送欢迎通知、接入 CRM 等。统一错误返回外层catch把所有异常收敛为{ success: false, message }结构正常路径返回{ success: true, message }方便前端统一处理。用户名密码认证的自定义注册 Action完整实现JavaScript / TypeScript// ... action customSignup { fn: import { signup } from src/auth/signup.js, }import { createProviderId, createUser, ensurePasswordIsPresent, ensureValidPassword, ensureValidUsername, sanitizeAndSerializeProviderData, } from wasp/server/auth export const signup async (args, _context) { ensureValidUsername(args) ensurePasswordIsPresent(args) ensureValidPassword(args) try { const providerId createProviderId(username, args.username) const providerData await sanitizeAndSerializeProviderData({ // The provider will hash the password for us, so we dont need to do it here. hashedPassword: args.password, }) await createUser(providerId, providerData, {}) } catch (e) { console.error(Error creating user:, e) return { success: false, message: e.message, } } return { success: true, message: User created successfully, } }TypeScript 版本// ... action customSignup { fn: import { signup } from src/auth/signup, }import { createProviderId, createUser, ensurePasswordIsPresent, ensureValidPassword, ensureValidUsername, sanitizeAndSerializeProviderData, } from wasp/server/auth import type { CustomSignup } from wasp/server/operations type CustomSignupInput { username: string password: string } type CustomSignupOutput { success: boolean message: string } export const signup: CustomSignup CustomSignupInput, CustomSignupOutput async (args, _context) { ensureValidUsername(args) ensurePasswordIsPresent(args) ensureValidPassword(args) try { const providerId createProviderId(username, args.username) const providerData await sanitizeAndSerializeProviderDatausername({ // The provider will hash the password for us, so we dont need to do it here. hashedPassword: args.password, }) await createUser(providerId, providerData, {}) } catch (e: any) { console.error(Error creating user:, e) return { success: false, message: e.message, } } return { success: true, message: User created successfully, } }与 Email 版的主要差异校验入口从ensureValidEmail换成了ensureValidUsername其余两个校验器相同——这与 Wasp 内置实现 username/signup.ts 的ensureValidArgs保持一致。createProviderId(username, args.username)使用用户名作为身份标识同样大小写不敏感存储。sanitizeAndSerializeProviderData只需hashedPassword一个字段无需邮箱验证相关字段。流程中没有“已存在身份”的查重分支也没有验证邮件环节——因为用户名密码认证不涉及邮箱验证。createUser的第三个参数同样用于传入需要额外写入User实体的字段。校验器ValidatorsAPI 参考官方建议直接使用wasp/server/auth导出的内置字段校验器——这些正是 Wasp 默认认证流程内部使用的同一套实现server/auth/index.ts 从auth/validation.js统一导出。从源码 validation.ts 可以看到每个校验器的确切规则用户名ensureValidUsername(args)校验用户名是否存在用户名不能为空username must be present。注意用户名以大小写不敏感方式存储。校验失败时抛出HttpError(422, Validation failed, { message })。邮箱ensureValidEmail(args)校验邮箱不能为空且必须是合法邮箱地址——校验实现使用wasp.sh/lib-auth的isValidEmail其定义比 HTML5 语法更宽支持 Unicode。因此官方建议前端输入框不要使用typeemail它遵循 HTML5 语法、不接受 Unicode而改用typetextinputModeemailautoCompleteemail。邮箱同样大小写不敏感存储。密码ensurePasswordIsPresent(args)校验密码是否存在密码不能为空password must be present。ensureValidPassword(args)校验密码强度长度至少 8 个字符并且必须包含一个数字分别对应password must be at least 8 characters与password must contain a number两条规则。上述默认校验规则的完整说明可参见 overview.md 的 Default Validations 小节在使用默认认证流程内置 Auth UI 或内置 auth actions时这些校验自动生效而一旦改用自定义注册 Action就必须由你在 Action 内自行调用这些校验器。自定义 Action 与 Wasp 底层实现的对照为了让你清楚“该改哪里、该保留哪里”这里把自定义实现与 Wasp 生成器内置实现做一个关键对照环节Wasp 内置实现你的自定义实现参数校验ensureValidArgsemail/signup.ts / username/signup.ts同样调用ensureValidEmail/ensureValidUsername/ensurePasswordIsPresent/ensureValidPassword密码哈希sanitizeAndSerializeProviderData内部调用hashPassword后JSON.stringify相同不要自行预先哈希密码交给该函数处理建号createUser(providerId, providerData, userFields)Prisma 级联创建AuthAuthIdentity相同第三个参数可传入你的额外用户字段防枚举对已存在且已验证的邮箱执行doFakeWork()假装耗时utils.ts需要自行考虑避免泄露“邮箱已注册”这一信息钩子内置路由会触发onBeforeSignupHook/onAfterSignupHook见 email/signup.ts自定义 Action不会自动触发这些钩子如有需要请自行在代码中调用错误处理rethrowPossibleAuthError把 Prisma P2002唯一约束冲突、P2003外键失败、P2021缺表等转换为 4xx/5xxutils.ts需要自行处理唯一约束等数据库错误文档示例采用{ success: false, message }结构统一返回一个重要的实际提醒自定义 Action 绕过了onBeforeSignupHook与onAfterSignupHook。如果你原本依赖这些钩子做校验或联动例如邀请码校验、欢迎邮件请把它们的能力内联到自定义 Action 中否则这些逻辑会悄然失效。何时应该使用自定义注册 Action结合 overview.md 的认证使用方式与本文文档决策路径建议如下需要调整 UI→ 使用自定义认证 UI各认证方法的 create-your-own-ui 文档这是官方推荐路径。需要在注册前后挂接逻辑→ 使用 auth hooks例如onBeforeSignupHook、onAfterSignupHook。需要补充用户字段如姓名、地址→ 使用auth.methods.{method}.userSignupFields配合defineUserSignupFields见 overview.md 的 Customizing the Signup Process字段值会在内置流程中自动写入User实体。以上都不够必须完全接管注册控制流例如深度定制的多步注册、与外部身份系统联动、完全自定义的建号策略→ 才考虑自定义注册 Action。也就是说自定义 Action 是最后的手段它把注册的每一步控制权都交到你手上同时也把所有安全责任交给了你。如果确实决定使用请务必保留全套字段校验、把密码交给sanitizeAndSerializeProviderData哈希、妥善处理“用户已存在”的分支与数据库唯一约束冲突并在生产环境前对注册流程做完整的安全审查。【免费下载链接】waspThe batteries-included full-stack framework for the AI era. Develop JS/TS web apps (React, Node.js, and Prisma) using declarative code that abstracts away complex full-stack features like auth, background jobs, RPC, email sending, end-to-end type safety, single-command deployment, and more.项目地址: https://gitcode.com/GitHub_Trending/wa/wasp创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询