修复认证挑战与可信代理安全边界(07-17-auth-challenge-trusted-proxy-hardening)
这个提交包含在:
@@ -7,6 +7,15 @@ ADMIN_IDENTIFIER="${ADMIN_IDENTIFIER:[email protected]}"
|
||||
ADMIN_PASSWORD="${ADMIN_PASSWORD:-AdminPassword1234}"
|
||||
|
||||
LOGIN_FAIL_MAX_ATTEMPTS="${LOGIN_FAIL_MAX_ATTEMPTS:-${AUTH_LOGIN_FAIL_MAX_ATTEMPTS:-5}}"
|
||||
RUN_AUTH_POSTGRES_CONCURRENCY="${RUN_AUTH_POSTGRES_CONCURRENCY:-0}"
|
||||
|
||||
case "$RUN_AUTH_POSTGRES_CONCURRENCY" in
|
||||
0|1) ;;
|
||||
*)
|
||||
echo "[AUTH-REGRESSION][FAIL] invalid_concurrency_mode" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
if ! command -v curl >/dev/null 2>&1; then
|
||||
echo "[ERROR] curl not found" >&2
|
||||
@@ -18,6 +27,23 @@ if ! command -v jq >/dev/null 2>&1; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v node >/dev/null 2>&1; then
|
||||
echo "[ERROR] node not found" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SERVER_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
if ! node -e '
|
||||
try {
|
||||
require(process.argv[1]).validateAuthRegressionEnvironment(process.env);
|
||||
} catch {
|
||||
process.exit(1);
|
||||
}
|
||||
' "$SERVER_DIR/src/scripts/check-auth-postgres-concurrency.js"; then
|
||||
echo "[AUTH-REGRESSION][FAIL] environment_guard_rejected" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
TMP_DIR="$(mktemp -d)"
|
||||
cleanup() {
|
||||
rm -rf "$TMP_DIR"
|
||||
@@ -34,12 +60,8 @@ assert_status() {
|
||||
local actual="$1"
|
||||
local expected="$2"
|
||||
local name="$3"
|
||||
local body_file="${4:-}"
|
||||
if [[ "$actual" != "$expected" ]]; then
|
||||
echo "[ERROR] $name expected HTTP $expected but got $actual" >&2
|
||||
if [[ -n "$body_file" && -f "$body_file" ]]; then
|
||||
cat "$body_file" >&2
|
||||
fi
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
@@ -52,7 +74,6 @@ assert_error_code() {
|
||||
actual_code="$(jq -r '.error.code // empty' "$file_path")"
|
||||
if [[ "$actual_code" != "$expected_code" ]]; then
|
||||
echo "[ERROR] $name expected error code $expected_code but got $actual_code" >&2
|
||||
cat "$file_path" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
@@ -80,7 +101,7 @@ ADMIN_TOKEN="$(extract_json_field "$admin_login_body" '.data.accessToken')"
|
||||
|
||||
echo "[2/8] Weak password should be rejected"
|
||||
weak_employee_no="W$(date +%s)$RANDOM"
|
||||
weak_employee_email="weak_${weak_employee_no}@hyinda.com"
|
||||
weak_employee_email="weak_${weak_employee_no,,}@hyinda.com"
|
||||
weak_employee_phone="139${RANDOM}${RANDOM}"
|
||||
weak_create_body="$TMP_DIR/weak-create.json"
|
||||
weak_create_status="$(curl -sS -o "$weak_create_body" -w "%{http_code}" \
|
||||
@@ -91,14 +112,13 @@ weak_create_status="$(curl -sS -o "$weak_create_body" -w "%{http_code}" \
|
||||
assert_status "$weak_create_status" "400" "weak password rejected" "$weak_create_body"
|
||||
assert_error_code "$weak_create_body" "VALID_001" "weak password rejected"
|
||||
if ! jq -e '.error.details.password | type == "array" and (map(tostring) | join(" ") | contains("常见"))' "$weak_create_body" >/dev/null; then
|
||||
echo "[ERROR] weak password error message missing" >&2
|
||||
cat "$weak_create_body" >&2
|
||||
echo "[ERROR] weak password validation category missing" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "[3/8] Create employee (initial password => mustChangePassword)"
|
||||
employee_no="A$(date +%s)$RANDOM"
|
||||
employee_email="auth_${employee_no}@hyinda.com"
|
||||
employee_email="auth_${employee_no,,}@hyinda.com"
|
||||
employee_phone="138${RANDOM}${RANDOM}"
|
||||
INITIAL_PASSWORD="InitA1$(date +%s%N)Xy"
|
||||
create_body="$TMP_DIR/create.json"
|
||||
@@ -115,7 +135,6 @@ login_status_1="$(login "$employee_email" "$INITIAL_PASSWORD" "${DEVICE_FINGERPR
|
||||
assert_status "$login_status_1" "200" "initial login" "$login_body_1"
|
||||
if [[ "$(jq -r '.data.mustChangePassword' "$login_body_1")" != "true" ]]; then
|
||||
echo "[ERROR] expected mustChangePassword=true on initial login" >&2
|
||||
cat "$login_body_1" >&2
|
||||
exit 1
|
||||
fi
|
||||
ACCESS_TOKEN_1="$(extract_json_field "$login_body_1" '.data.accessToken')"
|
||||
@@ -133,7 +152,6 @@ assert_status "$change_status" "200" "change password" "$change_body"
|
||||
ACCESS_TOKEN_2="$(extract_json_field "$change_body" '.data.accessToken')"
|
||||
if [[ "$(jq -r '.data.mustChangePassword' "$change_body")" != "false" ]]; then
|
||||
echo "[ERROR] expected mustChangePassword=false after change" >&2
|
||||
cat "$change_body" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -165,7 +183,6 @@ login_status_2="$(login "$employee_email" "$ONE_TIME_PASSWORD" "${DEVICE_FINGERP
|
||||
assert_status "$login_status_2" "200" "login with reset password" "$login_body_2"
|
||||
if [[ "$(jq -r '.data.mustChangePassword' "$login_body_2")" != "true" ]]; then
|
||||
echo "[ERROR] expected mustChangePassword=true after admin reset" >&2
|
||||
cat "$login_body_2" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -183,7 +200,6 @@ for i in $(seq 1 "$LOGIN_FAIL_MAX_ATTEMPTS"); do
|
||||
assert_error_code "$attempt_body" "AUTH_002" "lock attempt $i triggers lock"
|
||||
if [[ "$(jq -r '.error.details.lockedUntil // empty' "$attempt_body")" == "" ]]; then
|
||||
echo "[ERROR] expected lockedUntil in AUTH_002 details" >&2
|
||||
cat "$attempt_body" >&2
|
||||
exit 1
|
||||
fi
|
||||
locked=1
|
||||
@@ -211,5 +227,19 @@ post_unlock_login_body="$TMP_DIR/post-unlock-login.json"
|
||||
post_unlock_login_status="$(login "$employee_email" "$ONE_TIME_PASSWORD" "${DEVICE_FINGERPRINT}-post-unlock" "$post_unlock_login_body")"
|
||||
assert_status "$post_unlock_login_status" "200" "login after unlock" "$post_unlock_login_body"
|
||||
|
||||
echo "[8/8] Done"
|
||||
echo "[OK] Auth security regression passed"
|
||||
if [[ "$RUN_AUTH_POSTGRES_CONCURRENCY" == "1" ]]; then
|
||||
echo "[8/9] PostgreSQL auth challenge concurrency"
|
||||
AUTH_SECURITY_EMPLOYEE_ID="$EMPLOYEE_ID" \
|
||||
AUTH_SECURITY_EMPLOYEE_EMAIL="$employee_email" \
|
||||
AUTH_SECURITY_CURRENT_PASSWORD="$ONE_TIME_PASSWORD" \
|
||||
node src/scripts/check-auth-postgres-concurrency.js
|
||||
else
|
||||
echo "[8/9] PostgreSQL auth challenge concurrency [SKIP: explicit opt-in required]"
|
||||
fi
|
||||
|
||||
echo "[9/9] Done"
|
||||
if [[ "$RUN_AUTH_POSTGRES_CONCURRENCY" == "1" ]]; then
|
||||
echo "[AUTH-REGRESSION][PASS] FULL"
|
||||
else
|
||||
echo "[AUTH-REGRESSION][PARTIAL] BASE_ONLY"
|
||||
fi
|
||||
|
||||
在新工单中引用
屏蔽一个用户