Skip to content

fix(main): serialize manager PIN verification and preserve failed login counter on 403 - #1222

Merged
khaira777 merged 6 commits into
mainfrom
fix/auth-pin-login-hardening
Oct 20, 2026
Merged

khaira777 merged 6 commits into
mainfrom
fix/auth-pin-login-hardening

Conversation

@khaira777

@khaira777 khaira777 commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

Intent

Harden manager PIN verification concurrency and preserve cross-server failed login counter on 403

What Changed

  • Centralized manager PIN rate limiting in main/middleware/security.ts with withPinVerificationLock and canonical IP normalization, serializing verification across bill and order routes to prevent concurrent brute-force races before bcrypt evaluations.
  • Moved resetSuccessfulLogin in KDS and Server App login handlers to execute only after permission checks pass, preserving accumulated failed login attempt counters on 403 responses.
  • Documented lock-based PIN serialization and 403 counter retention in authentication architecture and API docs, and added regression tests for PIN concurrency bursts and cross-server 403 behavior.

Risk Assessment

✅ Low: The changes are well-bounded, security-hardening bug fixes that correctly serialize manager PIN verification to eliminate race conditions and ensure cross-server failed login counters are preserved on 403 permission denials.

Testing

Exercised the real running FloCafe backend servers (Main Express API :3101, Standalone KDS :3102, Server App :3103) against a disposable SQLite database. Verified that cross-server 403 responses preserve accumulated failed login attempts up to the 5-attempt lockout, and verified that concurrent manager PIN bursts serialize correctly via withPinVerificationLock yielding exactly 5 403s before immediate 429 lockouts without false positives or rate-limit bypasses. All checks and live scenarios passed; no UI/renderer changes were altered in this backend auth/PIN hardening commit so no screenshot artifact applies.

  • Live validation: ✅ go - 5 of 5 scenarios driven live against the product
Scenario Result Live Evidence
Cross-server 403 permission denial preserves accumulated failed login attempt counter without resetting lockout ✅ pass live ~/.no-mistakes/evidence/01M585VAWFC75N8Q0W6RKGT03A/cross-server-403-rate-limit-preservation.json
Concurrent invalid manager PIN verification burst enforces 5-attempt limit with immediate 429 serialization ✅ pass live ~/.no-mistakes/evidence/01M585VAWFC75N8Q0W6RKGT03A/concurrent-invalid-pin-burst-lockout.json
Manager PIN lockout blocks subsequent attempts across endpoints until cooldown expires ✅ pass live ~/.no-mistakes/evidence/01M585VAWFC75N8Q0W6RKGT03A/pin-lockout-blocks-subsequent-attempts.json
Concurrent burst of valid manager PIN requests executes successfully without false lockouts ✅ pass live ~/.no-mistakes/evidence/01M585VAWFC75N8Q0W6RKGT03A/concurrent-valid-pin-burst-success.json
Manager PIN override on order item void with concurrent invalid PINs enforces rate limiting ✅ pass live ~/.no-mistakes/evidence/01M585VAWFC75N8Q0W6RKGT03A/order-item-void-pin-rate-limiting.json
Evidence: Cross-Server 403 Counter Preservation JSON Log

{ "scenario": "Cross-Server 403 Counter Preservation", "passed": true, "steps": 9, "verified": "403 on KDS and Server App does not reset failure counter; 5th failure triggers 429 lockout" }

{
  "scenario": "Cross-Server 403 Counter Preservation",
  "passed": true,
  "steps": [
    {
      "step": 1,
      "listener": "Main API (:3101)",
      "status": 401,
      "body": {
        "error": "Invalid credentials",
        "attempts_remaining": 4
      }
    },
    {
      "step": 2,
      "listener": "Main API (:3101)",
      "status": 401,
      "body": {
        "error": "Invalid credentials",
        "attempts_remaining": 3
      }
    },
    {
      "step": 3,
      "listener": "KDS Server (:3102)",
      "status": 403,
      "body": {
        "error": "Access denied. Only kitchen staff allowed.",
        "code": "permission_denied"
      }
    },
    {
      "step": 4,
      "listener": "Main API (:3101)",
      "status": 401,
      "body": {
        "error": "Invalid credentials",
        "attempts_remaining": 2
      }
    },
    {
      "step": 5,
      "listener": "Server App (:3103)",
      "status": 403,
      "body": {
        "error": "Access denied. Only server, manager, or owner accounts allowed.",
        "code": "permission_denied"
      }
    },
    {
      "step": 6,
      "listener": "Server App (:3103)",
      "status": 401,
      "body": {
        "error": "Invalid credentials",
        "attempts_remaining": 1
      }
    },
    {
      "step": 7,
      "listener": "Server App (:3103)",
      "status": 401,
      "body": {
        "error": "Invalid credentials",
        "attempts_remaining": 0,
        "lockout_minutes": 15
      }
    },
    {
      "step": 8,
      "listener": "Main API (:3101)",
      "status": 429,
      "body": {
        "error": "Too many failed attempts. Try again in 15 minutes."
      }
    },
    {
      "step": 9,
      "listener": "Main API (:3101)",
      "status": 429,
      "body": {
        "error": "Too many failed attempts. Try again in 15 minutes."
      }
    }
  ]
}
Evidence: Concurrent Invalid PIN Burst Lockout JSON Log

{ "scenario": "Concurrent Invalid PIN Verification Burst", "totalRequests": 10, "durationMs": 998, "statusDistribution": { "403": 5, "429": 5 } }

{
  "scenario": "Concurrent Invalid PIN Verification Burst",
  "totalRequests": 10,
  "durationMs": 998,
  "statusDistribution": {
    "403": 5,
    "429": 5
  },
  "responses": [
    {
      "requestIndex": 1,
      "status": 403,
      "body": {
        "error": "Invalid manager PIN"
      }
    },
    {
      "requestIndex": 2,
      "status": 403,
      "body": {
        "error": "Invalid manager PIN"
      }
    },
    {
      "requestIndex": 3,
      "status": 403,
      "body": {
        "error": "Invalid manager PIN"
      }
    },
    {
      "requestIndex": 4,
      "status": 403,
      "body": {
        "error": "Invalid manager PIN"
      }
    },
    {
      "requestIndex": 5,
      "status": 403,
      "body": {
        "error": "Invalid manager PIN"
      }
    },
    {
      "requestIndex": 6,
      "status": 429,
      "body": {
        "error": "Too many PIN attempts. Try again in 15 minutes."
      }
    },
    {
      "requestIndex": 7,
      "status": 429,
      "body": {
        "error": "Too many PIN attempts. Try again in 15 minutes."
      }
    },
    {
      "requestIndex": 8,
      "status": 429,
      "body": {
        "error": "Too many PIN attempts. Try again in 15 minutes."
      }
    },
    {
      "requestIndex": 9,
      "status": 429,
      "body": {
        "error": "Too many PIN attempts. Try again in 15 minutes."
      }
    },
    {
      "requestIndex": 10,
      "status": 429,
      "body": {
        "error": "Too many PIN attempts. Try again in 15 minutes."
      }
    }
  ]
}
Evidence: PIN Lockout Persistence JSON Log

{ "scenario": "Manager PIN Lockout Persistence", "status": 429, "error": "Too many PIN attempts. Try again in 15 minutes.", "discount_amount": 0 }

{
  "scenario": "Manager PIN Lockout Persistence",
  "status": 429,
  "body": {
    "error": "Too many PIN attempts. Try again in 15 minutes."
  },
  "persistedBillState": {
    "id": 1,
    "bill_number": "BILL-LIVE-001",
    "order_id": 1,
    "customer_id": null,
    "subtotal": 100,
    "tax_amount": 10,
    "tax_breakdown": null,
    "tax_snapshot": null,
    "discount_amount": 0,
    "discount_type": null,
    "discount_value": null,
    "discount_reason": null,
    "delivery_charge": 0,
    "packaging_charge": 0,
    "service_charge": 0,
    "charges_breakdown": null,
    "round_off": 0,
    "total": 110,
    "paid_amount": 0,
    "balance": 110,
    "payment_status": "unpaid",
    "payment_details": null,
    "paid_at": null,
    "printed_at": null,
    "created_at": "2026-10-18 19:06:49",
    "updated_at": "2026-10-18 19:06:49",
    "split_group_id": null,
    "split_label": null
  }
}
Evidence: Concurrent Valid PIN Burst Success JSON Log

{ "scenario": "Concurrent Valid PIN Requests", "count": 5, "statuses": [200, 200, 200, 200, 200] }

{
  "scenario": "Concurrent Valid PIN Requests",
  "count": 5,
  "statuses": [
    200,
    200,
    200,
    200,
    200
  ],
  "responses": [
    {
      "bill": {
        "id": 2,
        "bill_number": "BILL-CONCURRENT-1",
        "order_id": 1,
        "customer_id": null,
        "subtotal": 50,
        "tax_amount": 0,
        "tax_breakdown": [],
        "tax_snapshot": null,
        "discount_amount": 5,
        "discount_type": "percentage",
        "discount_value": 10,
        "discount_reason": "Valid burst 1",
        "delivery_charge": 0,
        "packaging_charge": 0,
        "service_charge": 0,
        "charges_breakdown": null,
        "round_off": 0,
        "total": 45,
        "paid_amount": 0,
        "balance": 45,
        "payment_status": "unpaid",
        "payment_details": null,
        "paid_at": null,
        "printed_at": null,
        "created_at": "2026-10-18 19:06:51",
        "updated_at": "2026-10-18 19:06:51",
        "split_group_id": null,
        "split_label": null
      }
    },
    {
      "bill": {
        "id": 3,
        "bill_number": "BILL-CONCURRENT-2",
        "order_id": 1,
        "customer_id": null,
        "subtotal": 50,
        "tax_amount": 0,
        "tax_breakdown": [],
        "tax_snapshot": null,
        "discount_amount": 5,
        "discount_type": "percentage",
        "discount_value": 10,
        "discount_reason": "Valid burst 2",
        "delivery_charge": 0,
        "packaging_charge": 0,
        "service_charge": 0,
        "charges_breakdown": null,
        "round_off": 0,
        "total": 45,
        "paid_amount": 0,
        "balance": 45,
        "payment_status": "unpaid",
        "payment_details": null,
        "paid_at": null,
        "printed_at": null,
        "created_at": "2026-10-18 19:06:51",
        "updated_at": "2026-10-18 19:06:52",
        "split_group_id": null,
        "split_label": null
      }
    },
    {
      "bill": {
        "id": 4,
        "bill_number": "BILL-CONCURRENT-3",
        "order_id": 1,
        "customer_id": null,
        "subtotal": 50,
        "tax_amount": 0,
        "tax_breakdown": [],
        "tax_snapshot": null,
        "discount_amount": 5,
        "discount_type": "percentage",
        "discount_value": 10,
        "discount_reason": "Valid burst 3",
        "delivery_charge": 0,
        "packaging_charge": 0,
        "service_charge": 0,
        "charges_breakdown": null,
        "round_off": 0,
        "total": 45,
        "paid_amount": 0,
        "balance": 45,
        "payment_status": "unpaid",
        "payment_details": null,
        "paid_at": null,
        "printed_at": null,
        "created_at": "2026-10-18 19:06:51",
        "updated_at": "2026-10-18 19:06:52",
        "split_group_id": null,
        "split_label": null
      }
    },
    {
      "bill": {
        "id": 5,
        "bill_number": "BILL-CONCURRENT-4",
        "order_id": 1,
        "customer_id": null,
        "subtotal": 50,
        "tax_amount": 0,
        "tax_breakdown": [],
        "tax_snapshot": null,
        "discount_amount": 5,
        "discount_type": "percentage",
        "discount_value": 10,
        "discount_reason": "Valid burst 4",
        "delivery_charge": 0,
        "packaging_charge": 0,
        "service_charge": 0,
        "charges_breakdown": null,
        "round_off": 0,
        "total": 45,
        "paid_amount": 0,
        "balance": 45,
        "payment_status": "unpaid",
        "payment_details": null,
        "paid_at": null,
        "printed_at": null,
        "created_at": "2026-10-18 19:06:51",
        "updated_at": "2026-10-18 19:06:52",
        "split_group_id": null,
        "split_label": null
      }
    },
    {
      "bill": {
        "id": 6,
        "bill_number": "BILL-CONCURRENT-5",
        "order_id": 1,
        "customer_id": null,
        "subtotal": 50,
        "tax_amount": 0,
        "tax_breakdown": [],
        "tax_snapshot": null,
        "discount_amount": 5,
        "discount_type": "percentage",
        "discount_value": 10,
        "discount_reason": "Valid burst 5",
        "delivery_charge": 0,
        "packaging_charge": 0,
        "service_charge": 0,
        "charges_breakdown": null,
        "round_off": 0,
        "total": 45,
        "paid_amount": 0,
        "balance": 45,
        "payment_status": "unpaid",
        "payment_details": null,
        "paid_at": null,
        "printed_at": null,
        "created_at": "2026-10-18 19:06:51",
        "updated_at": "2026-10-18 19:06:52",
        "split_group_id": null,
        "split_label": null
      }
    }
  ]
}
Evidence: Order Item Void PIN Concurrency JSON Log

{ "scenario": "Order Item Void PIN Concurrency & Rate Limiting", "burstCounts": { "403": 5, "429": 5 }, "itemStatusBefore": "preparing", "validVoidStatus": 200, "itemStatusAfter": "voided" }

{
  "scenario": "Order Item Void PIN Concurrency & Rate Limiting",
  "burstCounts": {
    "403": 5,
    "429": 5
  },
  "itemStatusBefore": "preparing",
  "validVoidStatus": 200,
  "itemStatusAfter": "voided"
}
Evidence: Live Product Validation Summary Report

# Live Product Validation: Manager PIN Concurrency & 403 Counter Preservation All 5 live scenarios passed against real running backend servers with full evidence captured.

# Live Product Validation: Manager PIN Concurrency & 403 Counter Preservation

## Overview
- **User Intent:** Harden manager PIN verification concurrency and preserve cross-server failed login counter on 403
- **Execution Target:** Live FloCafe Express API (:3101), KDS Server (:3102), and Server App (:3103) running against disposable SQLite database
- **Verification Timestamp:** 2026-10-18T19:06:53.331Z

## Scenarios Driven

| # | Scenario | Result | Observable Evidence |
|---|---|---|---|
| 1 | Cross-server 403 permission denial preserves accumulated failed login attempt counter | PASS | 403 on KDS (:3102) & Server App (:3103) preserved counter; remaining decremented from 3 -> 2 -> 1, triggering 429 lockout on 5th attempt |
| 2 | Concurrent invalid manager PIN verification burst enforces 5-attempt limit with immediate 429 serialization | PASS | 10 concurrent requests to applyDiscount: exactly 5 returned 403 and 5 returned 429 via `withPinVerificationLock` |
| 3 | Manager PIN lockout blocks subsequent attempts across endpoints until cooldown expires | PASS | Valid manager PIN rejected with 429 during active lockout; DB bill totals preserved |
| 4 | Concurrent burst of valid manager PIN requests executes successfully without false lockouts | PASS | 5 concurrent valid PIN requests succeeded with 200 OK |
| 5 | Manager PIN override on order item void with concurrent invalid PINs enforces rate limiting | PASS | 10 concurrent invalid void requests: 5 returned 403, 5 returned 429; item remained `preparing`; valid PIN transitioned item to `voided` |

## Artifacts Generated
- `cross-server-403-rate-limit-preservation.json`: Full 9-step request/response transcript demonstrating that 403 responses across KDS and Server App do not reset the failure counter, correctly reaching lockout at 5 attempts.
- `concurrent-invalid-pin-burst-lockout.json`: 10-request parallel burst test output showing 5x 403 and 5x 429 status distribution with fast serialization via `withPinVerificationLock`.
- `pin-lockout-blocks-subsequent-attempts.json`: Post-lockout request log verifying that even valid PIN submissions receive HTTP 429 and do not alter database bill/discount records.
- `concurrent-valid-pin-burst-success.json`: Parallel valid PIN submissions verifying 5x 200 OK without false rate-limit rejections.
- `order-item-void-pin-rate-limiting.json`: Order item void concurrency and PIN override verification showing transition from preparing -> voided only upon valid manager authorization.
- `live-product-validation-summary.md`: This comprehensive summary report.

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

✅ **Review** - passed

✅ No issues found.

✅ **Test** - passed

✅ No issues found.

  • Live validation: ✅ go - 5 of 5 scenarios driven live against the product
Scenario Result Live Evidence
Cross-server 403 permission denial preserves accumulated failed login attempt counter without resetting lockout ✅ pass live ~/.no-mistakes/evidence/01M585VAWFC75N8Q0W6RKGT03A/cross-server-403-rate-limit-preservation.json
Concurrent invalid manager PIN verification burst enforces 5-attempt limit with immediate 429 serialization ✅ pass live ~/.no-mistakes/evidence/01M585VAWFC75N8Q0W6RKGT03A/concurrent-invalid-pin-burst-lockout.json
Manager PIN lockout blocks subsequent attempts across endpoints until cooldown expires ✅ pass live ~/.no-mistakes/evidence/01M585VAWFC75N8Q0W6RKGT03A/pin-lockout-blocks-subsequent-attempts.json
Concurrent burst of valid manager PIN requests executes successfully without false lockouts ✅ pass live ~/.no-mistakes/evidence/01M585VAWFC75N8Q0W6RKGT03A/concurrent-valid-pin-burst-success.json
Manager PIN override on order item void with concurrent invalid PINs enforces rate limiting ✅ pass live ~/.no-mistakes/evidence/01M585VAWFC75N8Q0W6RKGT03A/order-item-void-pin-rate-limiting.json
  • node tests/run-electron-node-test.cjs tests/manager-pin-verification.test.ts
  • npm run test:cross-server-login-rate-limits
  • node scratch/live-product-hardening-test.js (Main Express API :3101, KDS Server :3102, Server App :3103)
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

…s-server failed login counter (#1221)

- Centralize PIN rate limiting and concurrency serialization with withPinVerificationLock in security middleware
- Re-check isPinRateLimited inside lock so concurrent bursts reject immediately with 429 without redundant bcrypt executions
- Defer resetSuccessfulLogin in KDS and Server App until after role and permission validation so 403 Forbidden does not clear the failed-login counter
- Add regression tests covering 403 failure counter preservation and concurrent PIN verification bursts
@coderabbitai

coderabbitai Bot commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration
  • Configuration used: Repository: FreeOpenSourcePOS/FloCafe/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 8255ce46-f37e-4bad-aa81-5c74987f61b4

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@khaira777 khaira777 changed the title fix(auth): prevent concurrent PIN limit bypass race and preserve cross-server failed login counter (#1221) fix(main): serialize manager PIN verification and preserve failed login counter on 403 Oct 18, 2026
khaira777 and others added 4 commits October 20, 2026 01:32
…1222-1229-r1

# Conflicts:
#	main/routes/bills.ts
…1222-1229-r1

# Conflicts:
#	main/routes/bills.ts
#	main/routes/orders.ts
…1222-1229-r1

# Conflicts:
#	docs/architecture/authentication-and-authorization.md
#	main/routes/orders.ts
#	tests/manager-pin-verification.test.ts
@khaira777
khaira777 merged commit 80769e0 into main Oct 20, 2026
23 checks passed
@khaira777
khaira777 deleted the fix/auth-pin-login-hardening branch October 20, 2026 06:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant