feat: harden auth and study workflows
- replace plaintext login and unlock requests with RSA-OAEP/AES-GCM encrypted payloads - add login challenge replay protection, production RSA key validation, and auth tests - wire compose to environment-driven dev/prod settings without committing local secrets - update setup-config smoke scripts and Postman docs for encrypted login - add visit schedule migrations/tests and update study/subject setup workflows
This commit is contained in:
@@ -7,10 +7,65 @@ PASSWORD="${PASSWORD:-admin123}"
|
||||
STUDY_ID="${STUDY_ID:-aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa}"
|
||||
|
||||
echo "[1/6] login: $EMAIL"
|
||||
TOKEN=$(curl -sS -X POST "$BASE_URL/api/v1/auth/login" \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d "{\"email\":\"$EMAIL\",\"password\":\"$PASSWORD\"}" | \
|
||||
python3 -c 'import json,sys; print(json.load(sys.stdin).get("access_token",""))')
|
||||
TOKEN=$(BASE_URL="$BASE_URL" EMAIL="$EMAIL" PASSWORD="$PASSWORD" python3 - <<'PY'
|
||||
import base64
|
||||
import json
|
||||
import os
|
||||
import urllib.request
|
||||
|
||||
from cryptography.hazmat.primitives import hashes, serialization
|
||||
from cryptography.hazmat.primitives.asymmetric import padding
|
||||
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
|
||||
|
||||
base_url = os.environ["BASE_URL"]
|
||||
email = os.environ["EMAIL"]
|
||||
password = os.environ["PASSWORD"]
|
||||
|
||||
with urllib.request.urlopen(f"{base_url}/api/v1/auth/login-key", timeout=20) as resp:
|
||||
login_key = json.loads(resp.read().decode())
|
||||
|
||||
public_key = serialization.load_pem_public_key(login_key["public_key"].encode("utf-8"))
|
||||
plaintext = json.dumps(
|
||||
{"email": email, "password": password, "challenge": login_key["challenge"]},
|
||||
separators=(",", ":"),
|
||||
).encode("utf-8")
|
||||
aes_key = AESGCM.generate_key(bit_length=256)
|
||||
iv = os.urandom(12)
|
||||
encrypted_data = AESGCM(aes_key).encrypt(iv, plaintext, None)
|
||||
encrypted_key = public_key.encrypt(
|
||||
aes_key,
|
||||
padding.OAEP(
|
||||
mgf=padding.MGF1(algorithm=hashes.SHA256()),
|
||||
algorithm=hashes.SHA256(),
|
||||
label=None,
|
||||
),
|
||||
)
|
||||
payload = json.dumps(
|
||||
{
|
||||
"key_id": login_key["key_id"],
|
||||
"challenge": login_key["challenge"],
|
||||
"ciphertext": base64.b64encode(
|
||||
json.dumps(
|
||||
{
|
||||
"encrypted_key": base64.b64encode(encrypted_key).decode("ascii"),
|
||||
"iv": base64.b64encode(iv).decode("ascii"),
|
||||
"data": base64.b64encode(encrypted_data).decode("ascii"),
|
||||
},
|
||||
separators=(",", ":"),
|
||||
).encode("utf-8")
|
||||
).decode("ascii"),
|
||||
}
|
||||
).encode()
|
||||
req = urllib.request.Request(
|
||||
f"{base_url}/api/v1/auth/login",
|
||||
method="POST",
|
||||
headers={"Content-Type": "application/json"},
|
||||
data=payload,
|
||||
)
|
||||
with urllib.request.urlopen(req, timeout=20) as resp:
|
||||
print(json.loads(resp.read().decode()).get("access_token", ""))
|
||||
PY
|
||||
)
|
||||
|
||||
if [[ -z "$TOKEN" ]]; then
|
||||
echo "login failed: access_token empty"
|
||||
|
||||
Reference in New Issue
Block a user