fix(auth): use SET LOCAL search_path to prevent connection pool contamination; fix api-test routing
This commit is contained in:
parent
09d9200235
commit
7a6752bf74
|
|
@ -203,12 +203,17 @@ export class AuthService {
|
||||||
const qr = this.dataSource.createQueryRunner();
|
const qr = this.dataSource.createQueryRunner();
|
||||||
await qr.connect();
|
await qr.connect();
|
||||||
try {
|
try {
|
||||||
await qr.query(`SET search_path TO "${schemaName}", public`);
|
await qr.startTransaction();
|
||||||
|
await qr.query(`SET LOCAL search_path TO "${schemaName}", public`);
|
||||||
await qr.query(
|
await qr.query(
|
||||||
`INSERT INTO users (id, tenant_id, email, phone, password_hash, name, roles, is_active, created_at, updated_at)
|
`INSERT INTO users (id, tenant_id, email, phone, password_hash, name, roles, is_active, created_at, updated_at)
|
||||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)`,
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)`,
|
||||||
[userId, slug, email ?? null, phone ?? null, passwordHash, name, [RoleType.ADMIN], true, now, now],
|
[userId, slug, email ?? null, phone ?? null, passwordHash, name, [RoleType.ADMIN], true, now, now],
|
||||||
);
|
);
|
||||||
|
await qr.commitTransaction();
|
||||||
|
} catch (err) {
|
||||||
|
await qr.rollbackTransaction();
|
||||||
|
throw err;
|
||||||
} finally {
|
} finally {
|
||||||
await qr.release();
|
await qr.release();
|
||||||
}
|
}
|
||||||
|
|
@ -369,7 +374,8 @@ export class AuthService {
|
||||||
const qr = this.dataSource.createQueryRunner();
|
const qr = this.dataSource.createQueryRunner();
|
||||||
await qr.connect();
|
await qr.connect();
|
||||||
try {
|
try {
|
||||||
await qr.query(`SET search_path TO "${schemaName}", public`);
|
await qr.startTransaction();
|
||||||
|
await qr.query(`SET LOCAL search_path TO "${schemaName}", public`);
|
||||||
|
|
||||||
// Check if email already exists in this tenant
|
// Check if email already exists in this tenant
|
||||||
const existingRows = await qr.query(
|
const existingRows = await qr.query(
|
||||||
|
|
@ -385,6 +391,10 @@ export class AuthService {
|
||||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`,
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`,
|
||||||
[userId, tenant.slug, invite.email, passwordHash, name, [invite.role], true, now, now],
|
[userId, tenant.slug, invite.email, passwordHash, name, [invite.role], true, now, now],
|
||||||
);
|
);
|
||||||
|
await qr.commitTransaction();
|
||||||
|
} catch (err) {
|
||||||
|
await qr.rollbackTransaction();
|
||||||
|
throw err;
|
||||||
} finally {
|
} finally {
|
||||||
await qr.release();
|
await qr.release();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -46,8 +46,10 @@ TEST_PASS="Test@12345"
|
||||||
# ══════════════════════════════════════════════════════════════════════════════
|
# ══════════════════════════════════════════════════════════════════════════════
|
||||||
section "1. Health Checks"
|
section "1. Health Checks"
|
||||||
|
|
||||||
STATUS=$(get_status "${BASE}/health" 2>/dev/null || echo "000")
|
# Kong gateway is healthy if auth routes respond (no /health route on Kong)
|
||||||
[[ "$STATUS" == "200" ]] && ok "GET /health → 200" || fail "GET /health → ${STATUS}"
|
STATUS=$(get_status "${BASE}/api/v1/auth/profile")
|
||||||
|
[[ "$STATUS" == "401" ]] && ok "Kong gateway reachable (auth/profile returns 401 without token)" \
|
||||||
|
|| fail "Kong gateway unreachable → ${STATUS}"
|
||||||
|
|
||||||
# ══════════════════════════════════════════════════════════════════════════════
|
# ══════════════════════════════════════════════════════════════════════════════
|
||||||
section "2. Auth — Register (Email)"
|
section "2. Auth — Register (Email)"
|
||||||
|
|
@ -85,10 +87,11 @@ fi
|
||||||
# ══════════════════════════════════════════════════════════════════════════════
|
# ══════════════════════════════════════════════════════════════════════════════
|
||||||
section "4. Auth — Duplicate registration should fail"
|
section "4. Auth — Duplicate registration should fail"
|
||||||
|
|
||||||
|
# Same company name → 409 on slug conflict
|
||||||
RESP=$(post_json "${BASE}/api/v1/auth/register" \
|
RESP=$(post_json "${BASE}/api/v1/auth/register" \
|
||||||
-d "{\"email\":\"${TEST_EMAIL}\",\"password\":\"${TEST_PASS}\",\"name\":\"Dup\",\"companyName\":\"Dup${TS}\"}")
|
-d "{\"email\":\"dup_${TS}@example.com\",\"password\":\"${TEST_PASS}\",\"name\":\"Dup\",\"companyName\":\"${TEST_COMPANY}\"}")
|
||||||
STATUS=$(echo "$RESP" | grep -o '__STATUS__[0-9]*' | sed 's/__STATUS__//')
|
STATUS=$(echo "$RESP" | grep -o '__STATUS__[0-9]*' | sed 's/__STATUS__//')
|
||||||
[[ "$STATUS" == "409" ]] && ok "Duplicate email → 409 Conflict" || fail "Duplicate email → expected 409, got ${STATUS}"
|
[[ "$STATUS" == "409" ]] && ok "Duplicate company name → 409 Conflict" || fail "Duplicate company → expected 409, got ${STATUS}"
|
||||||
|
|
||||||
# ══════════════════════════════════════════════════════════════════════════════
|
# ══════════════════════════════════════════════════════════════════════════════
|
||||||
section "5. Auth — Login (email)"
|
section "5. Auth — Login (email)"
|
||||||
|
|
@ -181,35 +184,39 @@ else
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ══════════════════════════════════════════════════════════════════════════════
|
# ══════════════════════════════════════════════════════════════════════════════
|
||||||
section "13. Tenants — List (platform admin)"
|
section "13. Tenants — List (platform admin via /api/v1/admin/tenants)"
|
||||||
|
|
||||||
if [[ -n "${ADMIN_TOKEN:-}" ]]; then
|
if [[ -n "${ADMIN_TOKEN:-}" ]]; then
|
||||||
STATUS=$(get_status "${BASE}/api/v1/tenants" \
|
STATUS=$(get_status "${BASE}/api/v1/admin/tenants" \
|
||||||
-H "Authorization: Bearer ${ADMIN_TOKEN}")
|
-H "Authorization: Bearer ${ADMIN_TOKEN}")
|
||||||
[[ "$STATUS" == "200" ]] && ok "GET /api/v1/tenants → 200" || fail "GET /api/v1/tenants → ${STATUS}"
|
[[ "$STATUS" == "200" ]] && ok "GET /api/v1/admin/tenants → 200" || fail "GET /api/v1/admin/tenants → ${STATUS}"
|
||||||
else
|
else
|
||||||
info "Skipped — no admin token"
|
info "Skipped — no admin token"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ══════════════════════════════════════════════════════════════════════════════
|
# ══════════════════════════════════════════════════════════════════════════════
|
||||||
section "14. Users — List (tenant admin)"
|
section "14. Users — List (tenant admin via /api/v1/auth/users)"
|
||||||
|
|
||||||
if [[ -n "$TOKEN" ]]; then
|
if [[ -n "$TOKEN" ]]; then
|
||||||
STATUS=$(get_status "${BASE}/api/v1/users" \
|
STATUS=$(get_status "${BASE}/api/v1/auth/users" \
|
||||||
-H "Authorization: Bearer ${TOKEN}")
|
-H "Authorization: Bearer ${TOKEN}")
|
||||||
[[ "$STATUS" == "200" ]] && ok "GET /api/v1/users → 200" || fail "GET /api/v1/users → ${STATUS}"
|
[[ "$STATUS" == "200" ]] && ok "GET /api/v1/auth/users → 200" || fail "GET /api/v1/auth/users → ${STATUS}"
|
||||||
else
|
else
|
||||||
info "Skipped — no token"
|
info "Skipped — no token"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ══════════════════════════════════════════════════════════════════════════════
|
# ══════════════════════════════════════════════════════════════════════════════
|
||||||
section "15. Billing — Plans (public)"
|
section "15. Billing — Plans (JWT required per Kong config)"
|
||||||
|
|
||||||
STATUS=$(get_status "${BASE}/api/v1/billing/plans")
|
if [[ -n "$TOKEN" ]]; then
|
||||||
[[ "$STATUS" == "200" ]] && ok "GET /api/v1/billing/plans → 200" || fail "GET /api/v1/billing/plans → ${STATUS}"
|
STATUS=$(get_status "${BASE}/api/v1/billing/plans" \
|
||||||
|
-H "Authorization: Bearer ${TOKEN}")
|
||||||
|
[[ "$STATUS" == "200" ]] && ok "GET /api/v1/billing/plans (JWT) → 200" \
|
||||||
|
|| fail "GET /api/v1/billing/plans (JWT) → ${STATUS}"
|
||||||
|
fi
|
||||||
|
|
||||||
# ══════════════════════════════════════════════════════════════════════════════
|
# ══════════════════════════════════════════════════════════════════════════════
|
||||||
section "16. Billing — Current subscription (JWT required)"
|
section "16. Billing — Subscription (JWT required)"
|
||||||
|
|
||||||
if [[ -n "$TOKEN" ]]; then
|
if [[ -n "$TOKEN" ]]; then
|
||||||
STATUS=$(get_status "${BASE}/api/v1/billing/subscription" \
|
STATUS=$(get_status "${BASE}/api/v1/billing/subscription" \
|
||||||
|
|
@ -243,23 +250,27 @@ else
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ══════════════════════════════════════════════════════════════════════════════
|
# ══════════════════════════════════════════════════════════════════════════════
|
||||||
section "19. App Versions — Admin CRUD (JWT required)"
|
section "19. App Versions — Admin list (JWT required)"
|
||||||
|
|
||||||
if [[ -n "${ADMIN_TOKEN:-}" ]]; then
|
if [[ -n "${ADMIN_TOKEN:-}" ]]; then
|
||||||
STATUS=$(get_status "${BASE}/api/v1/versions" \
|
STATUS=$(get_status "${BASE}/api/v1/versions" \
|
||||||
-H "Authorization: Bearer ${ADMIN_TOKEN}")
|
-H "Authorization: Bearer ${ADMIN_TOKEN}")
|
||||||
[[ "$STATUS" == "200" ]] && ok "GET /api/v1/versions → 200" || fail "GET /api/v1/versions → ${STATUS}"
|
[[ "$STATUS" == "200" ]] && ok "GET /api/v1/versions → 200" || fail "GET /api/v1/versions → ${STATUS}"
|
||||||
|
elif [[ -n "$TOKEN" ]]; then
|
||||||
|
STATUS=$(get_status "${BASE}/api/v1/versions" \
|
||||||
|
-H "Authorization: Bearer ${TOKEN}")
|
||||||
|
[[ "$STATUS" == "200" ]] && ok "GET /api/v1/versions → 200" || fail "GET /api/v1/versions → ${STATUS}"
|
||||||
else
|
else
|
||||||
info "Skipped — no admin token"
|
info "Skipped — no token"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ══════════════════════════════════════════════════════════════════════════════
|
# ══════════════════════════════════════════════════════════════════════════════
|
||||||
section "20. Servers — List (JWT required)"
|
section "20. Inventory — Servers (JWT required)"
|
||||||
|
|
||||||
if [[ -n "$TOKEN" ]]; then
|
if [[ -n "$TOKEN" ]]; then
|
||||||
STATUS=$(get_status "${BASE}/api/v1/servers" \
|
STATUS=$(get_status "${BASE}/api/v1/inventory/servers" \
|
||||||
-H "Authorization: Bearer ${TOKEN}")
|
-H "Authorization: Bearer ${TOKEN}")
|
||||||
[[ "$STATUS" == "200" ]] && ok "GET /api/v1/servers → 200" || fail "GET /api/v1/servers → ${STATUS}"
|
[[ "$STATUS" == "200" ]] && ok "GET /api/v1/inventory/servers → 200" || fail "GET /api/v1/inventory/servers → ${STATUS}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ══════════════════════════════════════════════════════════════════════════════
|
# ══════════════════════════════════════════════════════════════════════════════
|
||||||
|
|
@ -273,12 +284,12 @@ if [[ -n "$TOKEN" ]]; then
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ══════════════════════════════════════════════════════════════════════════════
|
# ══════════════════════════════════════════════════════════════════════════════
|
||||||
section "22. Roles — List (JWT required)"
|
section "22. Roles — List (JWT required via /api/v1/auth/roles)"
|
||||||
|
|
||||||
if [[ -n "$TOKEN" ]]; then
|
if [[ -n "$TOKEN" ]]; then
|
||||||
STATUS=$(get_status "${BASE}/api/v1/roles" \
|
STATUS=$(get_status "${BASE}/api/v1/auth/roles" \
|
||||||
-H "Authorization: Bearer ${TOKEN}")
|
-H "Authorization: Bearer ${TOKEN}")
|
||||||
[[ "$STATUS" == "200" ]] && ok "GET /api/v1/roles → 200" || fail "GET /api/v1/roles → ${STATUS}"
|
[[ "$STATUS" == "200" ]] && ok "GET /api/v1/auth/roles → 200" || fail "GET /api/v1/auth/roles → ${STATUS}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ══════════════════════════════════════════════════════════════════════════════
|
# ══════════════════════════════════════════════════════════════════════════════
|
||||||
|
|
@ -294,17 +305,17 @@ if [[ -n "$TOKEN" ]]; then
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ══════════════════════════════════════════════════════════════════════════════
|
# ══════════════════════════════════════════════════════════════════════════════
|
||||||
section "24. Invite flow — Create invite"
|
section "24. Invite flow — Create + validate invite"
|
||||||
|
|
||||||
if [[ -n "$TOKEN" ]]; then
|
if [[ -n "${ADMIN_TOKEN:-}" && -n "${TENANT_ID:-}" ]]; then
|
||||||
INVITE_EMAIL="invite_${TS}@example.com"
|
INVITE_EMAIL="invite_${TS}@example.com"
|
||||||
RESP=$(post_json "${BASE}/api/v1/tenants/invites" \
|
RESP=$(post_json "${BASE}/api/v1/admin/tenants/${TENANT_ID}/invites" \
|
||||||
-H "Authorization: Bearer ${TOKEN}" \
|
-H "Authorization: Bearer ${ADMIN_TOKEN}" \
|
||||||
-d "{\"email\":\"${INVITE_EMAIL}\",\"role\":\"viewer\"}")
|
-d "{\"email\":\"${INVITE_EMAIL}\",\"role\":\"viewer\"}")
|
||||||
STATUS=$(echo "$RESP" | grep -o '__STATUS__[0-9]*' | sed 's/__STATUS__//')
|
STATUS=$(echo "$RESP" | grep -o '__STATUS__[0-9]*' | sed 's/__STATUS__//')
|
||||||
BODY=$(echo "$RESP" | sed 's/__STATUS__[0-9]*//')
|
BODY=$(echo "$RESP" | sed 's/__STATUS__[0-9]*//')
|
||||||
if [[ "$STATUS" == "200" || "$STATUS" == "201" ]]; then
|
if [[ "$STATUS" == "200" || "$STATUS" == "201" ]]; then
|
||||||
ok "POST /api/v1/tenants/invites → ${STATUS}"
|
ok "POST /api/v1/admin/tenants/:id/invites → ${STATUS}"
|
||||||
INVITE_TOKEN=$(echo "$BODY" | grep -o '"token":"[^"]*"' | cut -d'"' -f4)
|
INVITE_TOKEN=$(echo "$BODY" | grep -o '"token":"[^"]*"' | cut -d'"' -f4)
|
||||||
if [[ -n "$INVITE_TOKEN" ]]; then
|
if [[ -n "$INVITE_TOKEN" ]]; then
|
||||||
STATUS2=$(get_status "${BASE}/api/v1/auth/invite/${INVITE_TOKEN}")
|
STATUS2=$(get_status "${BASE}/api/v1/auth/invite/${INVITE_TOKEN}")
|
||||||
|
|
@ -312,8 +323,10 @@ if [[ -n "$TOKEN" ]]; then
|
||||||
|| fail "GET /api/v1/auth/invite/:token → ${STATUS2}"
|
|| fail "GET /api/v1/auth/invite/:token → ${STATUS2}"
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
fail "POST /api/v1/tenants/invites → ${STATUS}: ${BODY}"
|
fail "POST /api/v1/admin/tenants/:id/invites → ${STATUS}: ${BODY}"
|
||||||
fi
|
fi
|
||||||
|
else
|
||||||
|
info "Skipped invite flow — need admin token + tenantId"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ══════════════════════════════════════════════════════════════════════════════
|
# ══════════════════════════════════════════════════════════════════════════════
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue