Files
ai-contract-platform/scripts/rotate-admin-password.ps1
T

161 lines
5.3 KiB
PowerShell

param(
[string]$ApiBase = "http://192.168.3.90:8001/api/v1",
[string]$AdminUsername = "admin@example.com",
[string]$ArtifactRoot = ""
)
$ErrorActionPreference = "Stop"
$repoRoot = Resolve-Path (Join-Path $PSScriptRoot "..")
if (-not $ArtifactRoot) {
$ArtifactRoot = Join-Path $repoRoot "project-docs\qa-artifacts\phase4-password-rotation"
}
$timestamp = Get-Date -Format "yyyy-MM-ddTHH-mm-ss"
$artifactDir = Join-Path $ArtifactRoot $timestamp
$summaryJsonPath = Join-Path $artifactDir "admin-password-rotation-summary.json"
$summaryMdPath = Join-Path $artifactDir "admin-password-rotation-summary.md"
New-Item -ItemType Directory -Force -Path $artifactDir | Out-Null
function Convert-SecureStringToPlainText {
param([Security.SecureString]$SecureValue)
$ptr = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($SecureValue)
try {
return [Runtime.InteropServices.Marshal]::PtrToStringBSTR($ptr)
} finally {
[Runtime.InteropServices.Marshal]::ZeroFreeBSTR($ptr)
}
}
function Invoke-JsonPost {
param(
[string]$Url,
[hashtable]$Payload
)
$body = $Payload | ConvertTo-Json -Depth 8
Invoke-RestMethod -Uri $Url -Method Post -ContentType "application/json; charset=utf-8" -Body $body
}
function Test-Login {
param([string]$Password)
try {
$response = Invoke-JsonPost -Url "$ApiBase/auth/login" -Payload @{ account = $AdminUsername; password = $Password }
return @{
ok = $response.code -eq 0
response = $response
status = 200
}
} catch {
$status = $null
if ($_.Exception.Response) {
$status = [int]$_.Exception.Response.StatusCode
}
return @{
ok = $false
response = $null
status = $status
}
}
}
function Write-Summary {
param([hashtable]$Summary)
$Summary | ConvertTo-Json -Depth 8 | Set-Content -Encoding UTF8 -Path $summaryJsonPath
@(
"# Admin Password Rotation",
"",
"- API: ``$($Summary.api_base)``",
"- Admin: ``$($Summary.admin_username)``",
"- Status: $($Summary.status)",
"- Old password rejected: $($Summary.old_password_rejected)",
"- New password login: $($Summary.new_password_login_pass)",
"- Readiness: $($Summary.readiness_status)",
"- Artifact: ``$artifactDir``",
""
) | Set-Content -Encoding UTF8 -Path $summaryMdPath
}
try {
Write-Host "AI合同平台管理员密码轮换" -ForegroundColor Cyan
Write-Host "API: $ApiBase"
Write-Host "管理员账号: $AdminUsername"
Write-Host "密码不会写入日志或产物。" -ForegroundColor Yellow
$currentPasswordSecure = Read-Host "请输入当前管理员密码" -AsSecureString
$newPasswordSecure = Read-Host "请输入新管理员密码" -AsSecureString
$confirmPasswordSecure = Read-Host "请再次输入新管理员密码" -AsSecureString
$currentPassword = Convert-SecureStringToPlainText $currentPasswordSecure
$newPassword = Convert-SecureStringToPlainText $newPasswordSecure
$confirmPassword = Convert-SecureStringToPlainText $confirmPasswordSecure
if ($newPassword.Length -lt 6) {
throw "新密码至少需要 6 位。"
}
if ($newPassword -eq "123456") {
throw "新密码不能继续使用默认验收密码 123456。"
}
if ($newPassword -ne $confirmPassword) {
throw "两次输入的新密码不一致。"
}
$currentLogin = Test-Login -Password $currentPassword
if (-not $currentLogin.ok) {
throw "当前管理员密码校验失败,未执行轮换。"
}
$token = $currentLogin.response.data.access_token
$displayName = $currentLogin.response.data.user.display_name
$headers = @{ Authorization = "Bearer $token" }
$updateBody = @{
display_name = $displayName
password = $newPassword
}
$updateResponse = Invoke-RestMethod -Uri "$ApiBase/auth/me" -Method Put -Headers $headers -ContentType "application/x-www-form-urlencoded" -Body $updateBody
if ($updateResponse.code -ne 0) {
throw "管理员密码更新失败。"
}
$oldPasswordLogin = Test-Login -Password $currentPassword
$newPasswordLogin = Test-Login -Password $newPassword
if ($oldPasswordLogin.ok) {
throw "旧密码仍可登录,轮换未达到安全闭环。"
}
if (-not $newPasswordLogin.ok) {
throw "新密码无法登录,轮换后验证失败。"
}
$readiness = Invoke-RestMethod -Uri "$ApiBase/system/readiness" -Method Get
$summary = @{
status = "PASS"
api_base = $ApiBase
admin_username = $AdminUsername
rotated_at = (Get-Date).ToString("o")
old_password_rejected = -not $oldPasswordLogin.ok
old_password_reject_status = $oldPasswordLogin.status
new_password_login_pass = $newPasswordLogin.ok
readiness_status = $readiness.data.status
readiness_warnings = $readiness.data.summary.warnings
artifact_dir = $artifactDir
}
Write-Summary -Summary $summary
Write-Host "ADMIN_PASSWORD_ROTATION_PASS $artifactDir" -ForegroundColor Green
} catch {
$summary = @{
status = "FAIL"
api_base = $ApiBase
admin_username = $AdminUsername
rotated_at = (Get-Date).ToString("o")
error = $_.Exception.Message
artifact_dir = $artifactDir
}
Write-Summary -Summary $summary
Write-Host "ADMIN_PASSWORD_ROTATION_FAIL $($_.Exception.Message)" -ForegroundColor Red
exit 1
} finally {
if ($currentPassword) { $currentPassword = $null }
if ($newPassword) { $newPassword = $null }
if ($confirmPassword) { $confirmPassword = $null }
Read-Host "按 Enter 关闭窗口"
}