91 lines
4.0 KiB
Go
91 lines
4.0 KiB
Go
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。
|