完成50%

This commit is contained in:
2026-07-23 08:23:59 +08:00
parent 9b0ad62df4
commit f1edc6b533
218 changed files with 24318 additions and 5514 deletions
+128
View File
@@ -0,0 +1,128 @@
$ErrorActionPreference = 'Stop'
$root = Split-Path -Parent $PSScriptRoot
$json = Get-Content -LiteralPath (Join-Path $root 'APP.openapi.json') -Raw -Encoding UTF8 | ConvertFrom-Json
$yaml = Get-Content -LiteralPath (Join-Path $root 'APP.openapi.yaml') -Raw -Encoding UTF8
$blockers = [System.Collections.Generic.List[string]]::new()
function Require-Operation {
param([string]$Path, [string]$Method)
$pathProperty = $json.paths.PSObject.Properties[$Path]
if ($null -eq $pathProperty -or $null -eq $pathProperty.Value.PSObject.Properties[$Method]) {
throw "认证源合同缺少操作:$($Method.ToUpperInvariant()) $Path"
}
if ($yaml -notmatch [regex]::Escape(" $Path`:") -or $yaml -notmatch "(?m)^ $Method`:\s*$") {
throw "YAML 认证源合同缺少操作:$($Method.ToUpperInvariant()) $Path"
}
return $pathProperty.Value.PSObject.Properties[$Method].Value
}
function Require-Schema {
param([string]$Name)
$property = $json.components.schemas.PSObject.Properties[$Name]
if ($null -eq $property) { throw "认证源合同缺少 schema$Name" }
if ($yaml -notmatch "(?m)^ $([regex]::Escape($Name)):\s*$") { throw "YAML 认证源合同缺少 schema$Name" }
return $property.Value
}
function Assert-ExactSet {
param([object[]]$Actual, [object[]]$Expected, [string]$Label)
$actualSet = @($Actual | ForEach-Object { [string]$_ } | Sort-Object -Unique)
$expectedSet = @($Expected | ForEach-Object { [string]$_ } | Sort-Object -Unique)
if (($actualSet -join ',') -ne ($expectedSet -join ',')) {
throw "$Label 漂移:actual=[$($actualSet -join ',')] expected=[$($expectedSet -join ',')]"
}
}
$operations = @(
@('/captcha/require', 'get'),
@('/captcha/challenge', 'post'),
@('/captcha/verify', 'post'),
@('/genealogy/app/auth/sms/code', 'post'),
@('/genealogy/app/auth/login', 'post'),
@('/genealogy/app/auth/login/sms', 'post'),
@('/genealogy/app/auth/register', 'post'),
@('/genealogy/app/auth/password/reset', 'put')
)
foreach ($entry in $operations) { [void](Require-Operation -Path $entry[0] -Method $entry[1]) }
$expectedVerificationScenes = @('APP_SMS_LOGIN', 'APP_REGISTER', 'APP_FORGOT_PASSWORD', 'APP_PHONE_CHANGE', 'APP_ACCOUNT_DEACTIVATE')
foreach ($schemaName in @('VerificationChallengeBody', 'VerificationCheckBody')) {
$schema = Require-Schema -Name $schemaName
Assert-ExactSet -Actual @($schema.properties.sceneCode.enum) -Expected $expectedVerificationScenes -Label "$schemaName.sceneCode"
}
$check = Require-Schema -Name 'VerificationCheckBody'
Assert-ExactSet -Actual @($check.properties.payload.oneOf.'$ref') -Expected @('#/components/schemas/TianaiVerificationPayload', '#/components/schemas/SystemImageVerificationPayload') -Label 'VerificationCheckBody.payload.oneOf'
$requiredCheckFields = @('tenantId', 'clientId', 'sceneCode', 'subject', 'challengeId', 'providerCode', 'captchaType', 'payload')
$missingCheckFields = @($requiredCheckFields | Where-Object { $_ -notin @($check.required) })
if ($missingCheckFields.Count -gt 0) {
$blockers.Add("VerificationCheckBody 未强制字段:$($missingCheckFields -join '、')")
}
if ($check.additionalProperties -ne $false) {
$blockers.Add('VerificationCheckBody 未设置 additionalProperties=false,服务端校验边界仍可接受未声明字段。')
}
if (@($check.oneOf).Count -lt 2 -or $check.discriminator.propertyName -ne 'providerCode') {
$blockers.Add('VerificationCheckBody 未用 providerCode 判别至少两个 oneOf 分支,providerCode、captchaType 与 payload 形态无法被原子约束。')
}
$tianaiPayload = Require-Schema -Name 'TianaiVerificationPayload'
Assert-ExactSet -Actual @($tianaiPayload.required) -Expected @('track') -Label 'TianaiVerificationPayload.required'
if ($tianaiPayload.properties.track.'$ref' -ne '#/components/schemas/TianaiCaptchaTrack') { throw '天爱校验载荷必须唯一包装为 payload.track' }
if ($tianaiPayload.additionalProperties -ne $false) {
$blockers.Add('TianaiVerificationPayload 未设置 additionalProperties=false,历史直传字段仍可能绕过 payload.track 约束。')
}
$systemImagePayload = Require-Schema -Name 'SystemImageVerificationPayload'
if ($systemImagePayload.additionalProperties -ne $false) {
$blockers.Add('SystemImageVerificationPayload 未设置 additionalProperties=false,系统图形验证码载荷边界未闭合。')
}
$track = Require-Schema -Name 'TianaiCaptchaTrack'
Assert-ExactSet -Actual @($track.required) -Expected @('bgImageWidth', 'bgImageHeight', 'startTime', 'stopTime', 'trackList') -Label 'TianaiCaptchaTrack.required'
if ($track.properties.trackList.minItems -ne 1) { throw '天爱行为轨迹不得为空' }
$smsCode = Require-Schema -Name 'SmsCodeBody'
Assert-ExactSet -Actual @($smsCode.required) -Expected @('clientId', 'grantType', 'tenantId', 'sceneCode', 'phone', 'validToken') -Label 'SmsCodeBody.required'
if ($smsCode.additionalProperties -ne $false) { throw 'SmsCodeBody 必须拒绝历史供应商字段' }
$expectedPublicSmsScenes = @('APP_SMS_LOGIN', 'APP_REGISTER', 'APP_FORGOT_PASSWORD', 'APP_ACCOUNT_DEACTIVATE')
$actualPublicSmsScenes = @($smsCode.properties.sceneCode.enum | ForEach-Object { [string]$_ } | Sort-Object -Unique)
$expectedPublicSmsScenes = @($expectedPublicSmsScenes | Sort-Object -Unique)
if (($actualPublicSmsScenes -join ',') -ne ($expectedPublicSmsScenes -join ',')) {
$blockers.Add('公共 SmsCodeBody.sceneCode 必须删除 APP_PHONE_CHANGE;换绑发码只能由需要 SaToken 的专用 /auth/phone/sms/code operation 持有。')
}
$smsSecretProperty = $json.components.schemas.PSObject.Properties['SmsCodeSecret']
if ($null -eq $smsSecretProperty) {
$blockers.Add('缺少全认证场景共用的 SmsCodeSecret;当前四位码必须原子升级为严格六位 ASCII 数字,不能保留 4/6 双接受。')
} else {
$smsSecret = $smsSecretProperty.Value
if ($smsSecret.type -ne 'string' -or $smsSecret.writeOnly -ne $true -or
[int]$smsSecret.minLength -ne 6 -or [int]$smsSecret.maxLength -ne 6 -or
[string]$smsSecret.pattern -ne '^[0-9]{6}$' -or $smsSecret.example) {
$blockers.Add('SmsCodeSecret 必须是无示例、保留前导零的 writeOnly 六位 ASCII 数字字符串。')
}
}
foreach ($schemaName in @('SmsLoginBody', 'PasswordRegisterBody', 'PasswordResetBody')) {
$schema = Require-Schema -Name $schemaName
$actualRef = [string]$schema.properties.smsCode.'$ref'
if ($actualRef -ne '#/components/schemas/SmsCodeSecret') {
$blockers.Add("$schemaName.smsCode 必须引用唯一 SmsCodeSecret,禁止继续内联四位码或接受双长度。")
}
}
$passwordLogin = Require-Schema -Name 'PasswordLoginBody'
$passwordFields = @($passwordLogin.properties.PSObject.Properties.Name)
$passwordRequired = @($passwordLogin.required)
if ('validToken' -notin $passwordFields -or 'validToken' -notin $passwordRequired) {
$blockers.Add('PasswordLoginBody 未定义并强制消费 validToken,密码登录无法形成服务端 TAC 闭环,客户端先滑后登录仍可被绕过。')
}
if ($blockers.Count -gt 0) {
$details = $blockers | ForEach-Object { "- $_" }
throw (@(
'AUTH-TAC-OPENAPI-CONTRACT BLOCKED'
$details
'- 关闭条件:后端同步更新同版本 JSON/YAML;校验体按 providerCode 严格区分供应商并拒绝缺字段/多余字段;密码登录原子消费绑定租户、客户端、场景、手机号的一次性 TAC 票据;全活动短信码原子迁移为六位;APP_PHONE_CHANGE 改由专用受保护发码 operation;全部部署到 HTTPS 环境并通过反向用例。'
) -join [Environment]::NewLine)
}
Write-Output 'AUTH-TAC-OPENAPI-CONTRACT PASS'