129 lines
5.0 KiB
Go
129 lines
5.0 KiB
Go
package business
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"gorm.io/gorm"
|
|
"gorm.io/gorm/clause"
|
|
"kra/internal/biz/business"
|
|
"time"
|
|
)
|
|
|
|
type resetCodePO struct {
|
|
UserID uint `gorm:"primaryKey;autoIncrement:false"`
|
|
CodeHash string `gorm:"size:64"`
|
|
ExpiresAt, SendAfter *time.Time
|
|
UpdatedAt time.Time
|
|
}
|
|
|
|
func (resetCodePO) TableName() string { return "sys_business_password_codes" }
|
|
|
|
type resetTokenPO struct {
|
|
Hash string `gorm:"column:token_hash;primaryKey;size:64"`
|
|
UserID uint `gorm:"index"`
|
|
ExpiresAt time.Time `gorm:"index"`
|
|
CreatedAt time.Time
|
|
Failures int
|
|
}
|
|
|
|
func (resetTokenPO) TableName() string { return "sys_business_password_resets" }
|
|
|
|
type passwordResetRepo struct{ data Provider }
|
|
|
|
func NewPasswordResetRepo(data Provider) business.PasswordResetRepo {
|
|
return &passwordResetRepo{data: data}
|
|
}
|
|
|
|
type resetTxKey struct{}
|
|
|
|
func (r *passwordResetRepo) db(ctx context.Context) *gorm.DB {
|
|
if tx, ok := ctx.Value(resetTxKey{}).(*gorm.DB); ok {
|
|
return tx.WithContext(ctx)
|
|
}
|
|
return r.data.DB().WithContext(ctx)
|
|
}
|
|
func resetUser(po accountPO) *business.ResetUser {
|
|
return &business.ResetUser{ID: po.ID, Username: po.Username, Phone: po.Phone, Enabled: po.Enable == 1, Status: po.BusinessStatus}
|
|
}
|
|
func (r *passwordResetRepo) UserByName(ctx context.Context, name string) (*business.ResetUser, error) {
|
|
var po accountPO
|
|
err := r.db(ctx).Select("id,username,phone,enable,business_status").Where("username = ?", name).First(&po).Error
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil, nil
|
|
}
|
|
if err != nil {
|
|
return nil, business.ErrStorage
|
|
}
|
|
return resetUser(po), nil
|
|
}
|
|
func (r *passwordResetRepo) UserByID(ctx context.Context, id uint) (*business.ResetUser, error) {
|
|
var po accountPO
|
|
err := r.db(ctx).Clauses(clause.Locking{Strength: "UPDATE"}).Select("id,username,phone,enable,business_status").First(&po, id).Error
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil, nil
|
|
}
|
|
if err != nil {
|
|
return nil, business.ErrStorage
|
|
}
|
|
return resetUser(po), nil
|
|
}
|
|
func (r *passwordResetRepo) WithCodeLock(ctx context.Context, id uint, fn func(context.Context) error) error {
|
|
err := r.data.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
|
po := resetCodePO{UserID: id}
|
|
if err := tx.Clauses(clause.OnConflict{DoNothing: true}).Create(&po).Error; err != nil {
|
|
return err
|
|
}
|
|
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&po, id).Error; err != nil {
|
|
return err
|
|
}
|
|
return fn(context.WithValue(ctx, resetTxKey{}, tx))
|
|
})
|
|
return storageError(err)
|
|
}
|
|
func (r *passwordResetRepo) Code(ctx context.Context, id uint) (*business.ResetCode, error) {
|
|
var po resetCodePO
|
|
if err := r.db(ctx).First(&po, id).Error; err != nil {
|
|
return nil, storageError(err)
|
|
}
|
|
return &business.ResetCode{UserID: po.UserID, CodeHash: po.CodeHash, ExpiresAt: po.ExpiresAt, SendAfter: po.SendAfter}, nil
|
|
}
|
|
func (r *passwordResetRepo) SaveCode(ctx context.Context, c *business.ResetCode) error {
|
|
return storageError(r.db(ctx).Model(&resetCodePO{}).Where("user_id = ?", c.UserID).Updates(map[string]any{"code_hash": c.CodeHash, "expires_at": c.ExpiresAt, "send_after": c.SendAfter}).Error)
|
|
}
|
|
func (r *passwordResetRepo) CreateToken(ctx context.Context, t *business.ResetToken) error {
|
|
if err := r.db(ctx).Where("expires_at < ?", t.CreatedAt).Delete(&resetTokenPO{}).Error; err != nil {
|
|
return storageError(err)
|
|
}
|
|
return storageError(r.db(ctx).Create(&resetTokenPO{Hash: t.Hash, UserID: t.UserID, ExpiresAt: t.ExpiresAt, CreatedAt: t.CreatedAt, Failures: t.Failures}).Error)
|
|
}
|
|
func (r *passwordResetRepo) WithTokenLock(ctx context.Context, key string, fn func(context.Context) error) error {
|
|
return storageError(r.data.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error { return fn(context.WithValue(ctx, resetTxKey{}, tx)) }))
|
|
}
|
|
func (r *passwordResetRepo) Token(ctx context.Context, key string) (*business.ResetToken, error) {
|
|
var po resetTokenPO
|
|
err := r.db(ctx).Clauses(clause.Locking{Strength: "UPDATE"}).First(&po, "token_hash = ?", key).Error
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil, nil
|
|
}
|
|
if err != nil {
|
|
return nil, storageError(err)
|
|
}
|
|
return &business.ResetToken{Hash: po.Hash, UserID: po.UserID, ExpiresAt: po.ExpiresAt, CreatedAt: po.CreatedAt, Failures: po.Failures}, nil
|
|
}
|
|
func (r *passwordResetRepo) SaveToken(ctx context.Context, t *business.ResetToken) error {
|
|
return storageError(r.db(ctx).Model(&resetTokenPO{}).Where("token_hash = ?", t.Hash).Update("failures", t.Failures).Error)
|
|
}
|
|
func (r *passwordResetRepo) DeleteToken(ctx context.Context, key string) error {
|
|
return storageError(r.db(ctx).Where("token_hash = ?", key).Delete(&resetTokenPO{}).Error)
|
|
}
|
|
func (r *passwordResetRepo) UpdatePassword(ctx context.Context, id uint, hash string) error {
|
|
result := r.db(ctx).Model(&accountPO{}).Where("id = ?", id).Updates(map[string]any{"password": hash, "password_updated_at": time.Now(), "must_change_password": false})
|
|
if result.Error != nil {
|
|
return storageError(result.Error)
|
|
}
|
|
if result.RowsAffected == 0 {
|
|
return business.ErrResetToken
|
|
}
|
|
return nil
|
|
}
|