feat(auth): 已登录账号绑定微信改为一键(不再要求填姓名密码)
站长反馈:登陆进去的账号绑定微信就不用再写账号信息了吧。
原来的绑定只有一个入口 POST /api/auth/bind,它是**未登录**场景用的:
微信扫出来的是个陌生身份,得先"姓名 + 密码"验证身份,才知道该绑到谁头上。
用户已经在账号页里登录了,再让他输一遍账号信息就很别扭。
新增 POST /api/auth/wechat/bind-self { bindToken }:
- 必须已登录(用 event.context.user 直接落库,不再要密码);
- bindToken 走原有 verifyBindingToken 校验;
- 该微信已经绑给**别人** → 409(不许抢绑);
- 已经绑给**自己** → 幂等返回成功(重复点不报错);
- 并发下唯一约束(23505)冲突也会复判一次,避免误报失败。
小程序端:
- utils/api.js 新增 bindWechatToSelf()(wx.login → wechat/login 拿 bindToken → 调新接口);
- 「账号管理 → 绑定微信」与首页那条绑定提示都改成一步完成,
不再跳绑定页填账号密码(绑定页保留给未登录场景:如登录页发现微信未绑账号时去注册/绑定)。
This commit is contained in:
@@ -0,0 +1,92 @@
|
|||||||
|
// 与同目录/相邻的认证接口保持一致:db / eq / and / 表 都从 ~/drizzle/db 取
|
||||||
|
import { db, eq, and, userIdentities } from '~/drizzle/db'
|
||||||
|
import { verifyBindingToken } from '~~/server/utils/oauth-token'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 给**当前已登录的账号**绑定第三方身份(目前是微信)—— 一键绑定,不用再填姓名密码。
|
||||||
|
*
|
||||||
|
* 为什么需要它:原来的绑定只能走 `POST /api/auth/bind`,那个接口是"用姓名 + 密码
|
||||||
|
* 验证身份后再绑定"(因为它是**未登录**场景:微信扫出来的是个陌生身份,得先确认绑到谁头上)。
|
||||||
|
* 但用户**已经登录**时再让他输一遍账号密码就很别扭了 —— 站长也是这么反馈的:
|
||||||
|
* 「登陆进去的账号绑定微信就不用再写账号信息了吧」。
|
||||||
|
*
|
||||||
|
* 这个接口用 `event.context.user`(登录态)直接落库:
|
||||||
|
* · 必须先登录;
|
||||||
|
* · body.bindToken 是刚才 wx.login 换来的第三方身份令牌(未绑定过的才算);
|
||||||
|
* · 该身份已经绑给别人 → 409(不许抢绑);
|
||||||
|
* · 已经绑给**自己** → 直接当成功(幂等,重复点不报错)。
|
||||||
|
*/
|
||||||
|
export default defineEventHandler(async (event) => {
|
||||||
|
const user = event.context.user
|
||||||
|
if (!user) {
|
||||||
|
throw createError({ statusCode: 401, message: '未登录' })
|
||||||
|
}
|
||||||
|
|
||||||
|
const body = await readBody(event)
|
||||||
|
const bindToken = String(body?.bindToken || '').trim()
|
||||||
|
if (!bindToken) {
|
||||||
|
throw createError({ statusCode: 400, message: '缺少绑定令牌,请重新用微信一键登录后再试' })
|
||||||
|
}
|
||||||
|
|
||||||
|
let payload: { provider?: string; providerUserId?: string; providerUsername?: string }
|
||||||
|
try {
|
||||||
|
payload = verifyBindingToken(bindToken)
|
||||||
|
} catch (e) {
|
||||||
|
throw createError({ statusCode: 400, message: '绑定令牌无效或已过期,请重新发起绑定' })
|
||||||
|
}
|
||||||
|
if (!payload?.provider || !payload?.providerUserId) {
|
||||||
|
throw createError({ statusCode: 400, message: '绑定令牌内容不完整,请重新发起绑定' })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 这个第三方身份是不是已经绑给谁了?
|
||||||
|
const existing = await db
|
||||||
|
.select({ id: userIdentities.id, userId: userIdentities.userId })
|
||||||
|
.from(userIdentities)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(userIdentities.provider, payload.provider),
|
||||||
|
eq(userIdentities.providerUserId, payload.providerUserId)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.limit(1)
|
||||||
|
|
||||||
|
if (existing.length > 0) {
|
||||||
|
if (existing[0].userId === user.id) {
|
||||||
|
return { success: true, alreadyMine: true, message: '这个微信已经绑定在你的账号上了' }
|
||||||
|
}
|
||||||
|
throw createError({
|
||||||
|
statusCode: 409,
|
||||||
|
message: '这个微信已经绑定在其它账号上了,请先在那边解绑'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await db.insert(userIdentities).values({
|
||||||
|
userId: user.id,
|
||||||
|
provider: payload.provider,
|
||||||
|
providerUserId: payload.providerUserId,
|
||||||
|
providerUsername: payload.providerUsername || null
|
||||||
|
})
|
||||||
|
} catch (e: any) {
|
||||||
|
if (e?.code === '23505') {
|
||||||
|
// 并发下被抢先插入:再查一次,若属于自己就算成功
|
||||||
|
const again = await db
|
||||||
|
.select({ userId: userIdentities.userId })
|
||||||
|
.from(userIdentities)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(userIdentities.provider, payload.provider),
|
||||||
|
eq(userIdentities.providerUserId, payload.providerUserId)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.limit(1)
|
||||||
|
if (again.length > 0 && again[0].userId === user.id) {
|
||||||
|
return { success: true, alreadyMine: true, message: '这个微信已经绑定在你的账号上了' }
|
||||||
|
}
|
||||||
|
throw createError({ statusCode: 409, message: '这个微信已经绑定在其它账号上了,请先在那边解绑' })
|
||||||
|
}
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
|
||||||
|
return { success: true, message: '微信绑定成功' }
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user