feat: complete remaining Go backend modules
This commit is contained in:
1 parent
cea2751dc5
commit
aef5a97165
145 files changed
+18376
-199
No files matched your search
@@ -0,0 +1,126 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"git.nianxx.cn/wangxuming/NianAIGC/backend/internal/administration"
|
||||
"git.nianxx.cn/wangxuming/NianAIGC/backend/internal/identity"
|
||||
)
|
||||
|
||||
const SelectPasswordChangeAccountSQL = `SELECT
|
||||
id, phone, display_name, role, organization_id, status,
|
||||
password_hash, password_salt, session_version
|
||||
FROM public.platform_users
|
||||
WHERE id = $1::text
|
||||
FOR UPDATE`
|
||||
|
||||
const UpdatePasswordChangeAccountSQL = `UPDATE public.platform_users
|
||||
SET password_hash = $2,
|
||||
password_salt = $3,
|
||||
session_version = session_version + 1,
|
||||
updated_at = $4
|
||||
WHERE id = $1::text
|
||||
RETURNING id, phone, display_name, role, organization_id, status, session_version`
|
||||
|
||||
// ChangeOwnPassword serializes password changes on the account row so two
|
||||
// concurrent requests cannot both verify the same previous credential.
|
||||
func (db *Database) ChangeOwnPassword(ctx context.Context, accountID, currentPassword, nextPassword string, now time.Time) (identity.AuthorizationSnapshot, error) {
|
||||
if db.config.Backend != BackendPostgres || db.transactions == nil {
|
||||
return identity.AuthorizationSnapshot{}, fmt.Errorf("PostgreSQL is unavailable when ZHINIAN_DATA_BACKEND=%s", db.config.Backend)
|
||||
}
|
||||
tx, err := db.transactions.Begin(ctx)
|
||||
if err != nil {
|
||||
return identity.AuthorizationSnapshot{}, fmt.Errorf("begin password change transaction: %w", err)
|
||||
}
|
||||
finished := false
|
||||
defer func() {
|
||||
if !finished {
|
||||
_ = tx.Rollback(ctx)
|
||||
}
|
||||
}()
|
||||
|
||||
account, hash, salt, found, err := loadPasswordChangeAccount(ctx, tx, accountID)
|
||||
if err != nil {
|
||||
return identity.AuthorizationSnapshot{}, err
|
||||
}
|
||||
if !found || account.Status != "active" {
|
||||
return identity.AuthorizationSnapshot{}, &identity.PasswordChangeError{Reason: identity.PasswordChangeNotFound}
|
||||
}
|
||||
valid, err := verifyNodeScryptPassword(currentPassword, hash, salt)
|
||||
if err != nil {
|
||||
return identity.AuthorizationSnapshot{}, fmt.Errorf("verify current password: %w", err)
|
||||
}
|
||||
if !valid {
|
||||
return identity.AuthorizationSnapshot{}, &identity.PasswordChangeError{Reason: identity.PasswordChangeCurrentIncorrect}
|
||||
}
|
||||
password, err := administration.HashPassword(nextPassword)
|
||||
if err != nil {
|
||||
return identity.AuthorizationSnapshot{}, fmt.Errorf("hash new password: %w", err)
|
||||
}
|
||||
updated, err := updatePasswordChangeAccount(ctx, tx, accountID, password, now)
|
||||
if err != nil {
|
||||
return identity.AuthorizationSnapshot{}, err
|
||||
}
|
||||
|
||||
snapshot := identity.AuthorizationSnapshot{Account: updated}
|
||||
if updated.OrganizationID != "" {
|
||||
organization, found, err := loadPasswordLoginOrganization(ctx, tx, updated.OrganizationID)
|
||||
if err != nil {
|
||||
return identity.AuthorizationSnapshot{}, err
|
||||
}
|
||||
if found {
|
||||
snapshot.Organization = &organization
|
||||
}
|
||||
}
|
||||
if err := tx.Commit(ctx); err != nil {
|
||||
return identity.AuthorizationSnapshot{}, fmt.Errorf("commit password change transaction: %w", err)
|
||||
}
|
||||
finished = true
|
||||
return snapshot, nil
|
||||
}
|
||||
|
||||
func loadPasswordChangeAccount(ctx context.Context, tx Transaction, id string) (identity.AccountSnapshot, string, string, bool, error) {
|
||||
rows, err := tx.Query(ctx, SelectPasswordChangeAccountSQL, id)
|
||||
if err != nil {
|
||||
return identity.AccountSnapshot{}, "", "", false, fmt.Errorf("query password change account: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
if !rows.Next() {
|
||||
return identity.AccountSnapshot{}, "", "", false, rows.Err()
|
||||
}
|
||||
var account identity.AccountSnapshot
|
||||
var organizationID sql.NullString
|
||||
var hash, salt string
|
||||
if err := rows.Scan(&account.ID, &account.Phone, &account.DisplayName, &account.Role, &organizationID, &account.Status, &hash, &salt, &account.SessionVersion); err != nil {
|
||||
return identity.AccountSnapshot{}, "", "", false, fmt.Errorf("scan password change account: %w", err)
|
||||
}
|
||||
if organizationID.Valid {
|
||||
account.OrganizationID = organizationID.String
|
||||
}
|
||||
return account, hash, salt, true, rows.Err()
|
||||
}
|
||||
|
||||
func updatePasswordChangeAccount(ctx context.Context, tx Transaction, id string, password administration.PasswordHash, now time.Time) (identity.AccountSnapshot, error) {
|
||||
rows, err := tx.Query(ctx, UpdatePasswordChangeAccountSQL, id, password.Hash, password.Salt, now)
|
||||
if err != nil {
|
||||
return identity.AccountSnapshot{}, fmt.Errorf("update password change account: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
if !rows.Next() {
|
||||
return identity.AccountSnapshot{}, &identity.PasswordChangeError{Reason: identity.PasswordChangeNotFound}
|
||||
}
|
||||
var account identity.AccountSnapshot
|
||||
var organizationID sql.NullString
|
||||
if err := rows.Scan(&account.ID, &account.Phone, &account.DisplayName, &account.Role, &organizationID, &account.Status, &account.SessionVersion); err != nil {
|
||||
return identity.AccountSnapshot{}, fmt.Errorf("scan updated password change account: %w", err)
|
||||
}
|
||||
if organizationID.Valid {
|
||||
account.OrganizationID = organizationID.String
|
||||
}
|
||||
return account, rows.Err()
|
||||
}
|
||||
|
||||
var _ identity.PasswordChanger = (*Database)(nil)
|
||||
Reference in new issue
Block a user