161 lines
5.3 KiB
PowerShell
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 关闭窗口"
|
|
}
|