2026-05-21 11:52:06 +08:00

91 lines
4.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package auth
import (
"context"
"database/sql"
"hyapp/pkg/appcode"
"hyapp/pkg/xerr"
authdomain "hyapp/services/user-service/internal/domain/auth"
invitedomain "hyapp/services/user-service/internal/domain/invite"
userdomain "hyapp/services/user-service/internal/domain/user"
identitystorage "hyapp/services/user-service/internal/storage/mysql/identity"
invitestorage "hyapp/services/user-service/internal/storage/mysql/invite"
userstorage "hyapp/services/user-service/internal/storage/mysql/user"
)
func (r *Repository) SetPassword(ctx context.Context, account authdomain.PasswordAccount) error {
// password_accounts 以 user_id 唯一v1 只允许首次设置密码。
_, err := r.db.ExecContext(ctx, `
INSERT INTO password_accounts (app_code, user_id, password_hash, hash_alg, created_at_ms, updated_at_ms)
VALUES (?, ?, ?, ?, ?, ?)
`, appcode.Normalize(account.AppCode), account.UserID, account.PasswordHash, account.HashAlg, account.CreatedAtMs, account.UpdatedAtMs)
if err != nil {
// 唯一键冲突映射为 PASSWORD_ALREADY_SET。
return mapAuthDuplicateError(err)
}
return nil
}
// CreatePasswordUser 原子创建完整密码账号,服务快捷建号和联调账号准备。
func (r *Repository) CreatePasswordUser(ctx context.Context, user userdomain.User, identity userdomain.Identity, account authdomain.PasswordAccount, session authdomain.Session, inviteBind invitedomain.BindCommand) error {
// 账号创建必须和默认短号、密码身份、refresh session 同事务提交,避免返回不可登录的 uid。
tx, err := r.db.BeginTx(ctx, nil)
if err != nil {
return err
}
defer tx.Rollback()
if err := userstorage.InsertUserIdentity(ctx, tx, user, identity); err != nil {
// 默认短号唯一键冲突交给 service 使用新 user_id/display_user_id 重试。
return mapAuthDuplicateError(err)
}
if _, err := invitestorage.EnsurePrimaryCodeForUser(ctx, tx, user.AppCode, user.UserID, user.CreatedAtMs); err != nil {
return err
}
if _, err := invitestorage.BindRelation(ctx, tx, inviteBind); err != nil {
return err
}
if _, err := tx.ExecContext(ctx, `
INSERT INTO password_accounts (app_code, user_id, password_hash, hash_alg, created_at_ms, updated_at_ms)
VALUES (?, ?, ?, ?, ?, ?)
`, appcode.Normalize(account.AppCode), account.UserID, account.PasswordHash, account.HashAlg, account.CreatedAtMs, account.UpdatedAtMs); err != nil {
return mapAuthDuplicateError(err)
}
if err := insertSession(ctx, tx, session); err != nil {
// session 写入失败时必须回滚用户和密码身份,否则客户端拿不到 refresh token 却占用账号。
return err
}
return tx.Commit()
}
// FindPasswordByDisplayUserID 通过当前 active display_user_id 查找用户密码身份。
func (r *Repository) FindPasswordByDisplayUserID(ctx context.Context, displayUserID string, nowMs int64) (authdomain.PasswordAccount, error) {
// 密码登录前先按输入短号触发靓号懒过期。
if err := identitystorage.ExpirePrettyByDisplayUserID(ctx, r.db, displayUserID, nowMs, "lazy_password_login"); err != nil {
return authdomain.PasswordAccount{}, err
}
var account authdomain.PasswordAccount
err := r.db.QueryRowContext(ctx, `
SELECT pa.app_code, pa.user_id, u.current_display_user_id, pa.password_hash, pa.hash_alg, pa.created_at_ms, pa.updated_at_ms
FROM users u
INNER JOIN password_accounts pa ON pa.app_code = u.app_code AND pa.user_id = u.user_id
WHERE u.app_code = ? AND u.current_display_user_id = ?
`, appcode.FromContext(ctx), displayUserID).Scan(&account.AppCode, &account.UserID, &account.DisplayUserID, &account.PasswordHash, &account.HashAlg, &account.CreatedAtMs, &account.UpdatedAtMs)
if err == sql.ErrNoRows {
// 短号不存在或未设置密码都返回 not found由 service 统一映射 AUTH_FAILED。
return authdomain.PasswordAccount{}, xerr.New(xerr.NotFound, "password account not found")
}
if err != nil {
return authdomain.PasswordAccount{}, err
}
return account, nil
}
// CreateSession 创建新的 refresh session。