{"openapi":"3.0.3","info":{"title":"Olivetta Bank API","version":"1.0.0","description":"Mobile banking API for Olivetta Bank. **Sandbox demo only.** Several endpoints contain intentional weaknesses to showcase Cloudflare WAF and API Shield. See /security-demo on the web UI.","contact":{"name":"Olivetta Bank Demo","url":"https://banking.homesecurity.rocks/security-demo"}},"servers":[{"url":"https://banking.homesecurity.rocks","description":"Production demo"},{"url":"http://localhost:8787","description":"Local dev"}],"tags":[{"name":"auth","description":"Authentication"},{"name":"accounts","description":"Account operations"},{"name":"transfers","description":"Money transfers"},{"name":"cards","description":"Cards (sensitive data demo)"}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT"}},"schemas":{"User":{"type":"object","properties":{"id":{"type":"integer","example":1},"username":{"type":"string","example":"alice"},"full_name":{"type":"string","example":"Alice Greenfield"},"email":{"type":"string","format":"email","example":"alice@olivetta.example"}},"required":["id","username","full_name","email"]},"LoginResponse":{"type":"object","properties":{"token":{"type":"string","example":"eyJhbGciOi..."},"expires_at":{"type":"string","format":"date-time"},"user":{"$ref":"#/components/schemas/User"}},"required":["token","expires_at","user"]},"Error":{"type":"object","properties":{"error":{"type":"string","example":"unauthorized"},"message":{"type":"string","example":"Invalid credentials"}},"required":["error"]},"LoginRequest":{"type":"object","properties":{"username":{"type":"string","minLength":1,"maxLength":64,"example":"alice"},"password":{"type":"string","minLength":1,"maxLength":128,"example":"Password123!"}},"required":["username","password"]},"Account":{"type":"object","properties":{"id":{"type":"string","pattern":"^GL\\d{8}$","example":"GL00000001"},"user_id":{"type":"integer"},"type":{"type":"string","enum":["checking","savings"]},"currency":{"type":"string","minLength":3,"maxLength":3,"example":"EUR"},"balance_cents":{"type":"integer","example":482350}},"required":["id","user_id","type","currency","balance_cents"]},"Balance":{"type":"object","properties":{"account_id":{"type":"string"},"currency":{"type":"string","minLength":3,"maxLength":3},"balance_cents":{"type":"integer"},"available_cents":{"type":"integer"}},"required":["account_id","currency","balance_cents","available_cents"]},"Transaction":{"type":"object","properties":{"id":{"type":"integer"},"account_id":{"type":"string"},"amount_cents":{"type":"integer","description":"Signed amount in minor units. Positive = credit.","example":250000},"description":{"type":"string"},"created_at":{"type":"string"}},"required":["id","account_id","amount_cents","description","created_at"]},"TransactionList":{"type":"object","properties":{"account_id":{"type":"string"},"items":{"type":"array","items":{"$ref":"#/components/schemas/Transaction"}},"total":{"type":"integer"}},"required":["account_id","items","total"]},"MoneyMovement":{"type":"object","properties":{"amount_cents":{"type":"integer","minimum":0,"exclusiveMinimum":true,"example":5000},"description":{"type":"string","minLength":1,"maxLength":140,"example":"Cash deposit"}},"required":["amount_cents","description"]},"TransferRequest":{"type":"object","properties":{"from":{"type":"string","pattern":"^GL\\d{8}$","example":"GL00000001"},"to":{"type":"string","pattern":"^GL\\d{8}$","example":"GL00000002"},"amount_cents":{"type":"integer","minimum":0,"exclusiveMinimum":true,"example":10000},"description":{"type":"string","minLength":1,"maxLength":140}},"required":["from","to","amount_cents","description"]},"Card":{"type":"object","properties":{"id":{"type":"integer"},"account_id":{"type":"string"},"pan":{"type":"string","description":"Primary Account Number (intentionally exposed for SDD demo)","example":"4532-1488-0343-6467"},"exp":{"type":"string"},"cvv":{"type":"string"},"holder_name":{"type":"string"}},"required":["id","account_id","pan","exp","cvv","holder_name"]}},"parameters":{}},"paths":{"/api/v1/login":{"post":{"tags":["auth"],"summary":"Log in with username and password","description":"Returns a signed HS256 JWT. Demo note: this endpoint has no in-app rate limiting; protect it with a Cloudflare WAF rate-limiting rule.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginRequest"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginResponse"}}}},"401":{"description":"Bad credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/logout":{"post":{"tags":["auth"],"summary":"Log out (client should discard token)","security":[{"bearerAuth":[]}],"responses":{"204":{"description":"Logged out"},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/me":{"get":{"tags":["auth"],"summary":"Get the authenticated user","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/User"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/accounts":{"get":{"tags":["accounts"],"summary":"List accounts owned by the authenticated user","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Account"}}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/accounts/{id}":{"get":{"tags":["accounts"],"summary":"Get an account by id","description":"Demo note: this endpoint is intentionally vulnerable to BOLA. By default it does NOT verify that the authenticated user owns the account. Mitigate at the Cloudflare edge with API Shield BOLA Vulnerability Detection plus per-session rate limiting (counted by the JWT 'sub' claim). The real fix is in the application: pass ?fixed=1 (or set the Worker env var BOLA_FIX=1) to enable the ownership check inside the SQL query.","security":[{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","pattern":"^GL\\d{8}$","example":"GL00000001"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Account"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/accounts/{id}/balance":{"get":{"tags":["accounts"],"summary":"Get the current balance for an account","security":[{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","pattern":"^GL\\d{8}$"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Balance"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/accounts/{id}/transactions":{"get":{"tags":["accounts"],"summary":"List transactions for an account","description":"Optional ?q= filters by description (LIKE match). Demo note: the q parameter is reflected verbatim into the request payload to trigger WAF managed SQLi rules on hostile inputs.","security":[{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","pattern":"^GL\\d{8}$"},"required":true,"name":"id","in":"path"},{"schema":{"type":"string","maxLength":200},"required":false,"name":"q","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":100},"required":false,"name":"limit","in":"query"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TransactionList"}}}}}}},"/api/v1/accounts/{id}/deposit":{"post":{"tags":["accounts"],"summary":"Deposit money into an account","security":[{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","pattern":"^GL\\d{8}$"},"required":true,"name":"id","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MoneyMovement"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Transaction"}}}}}}},"/api/v1/accounts/{id}/withdraw":{"post":{"tags":["accounts"],"summary":"Withdraw money from an account","security":[{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","pattern":"^GL\\d{8}$"},"required":true,"name":"id","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MoneyMovement"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Transaction"}}}},"400":{"description":"Insufficient funds","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/transfers":{"post":{"tags":["transfers"],"summary":"Transfer money between accounts","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TransferRequest"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"from":{"type":"string"},"to":{"type":"string"},"amount_cents":{"type":"integer"}},"required":["ok","from","to","amount_cents"]}}}},"400":{"description":"Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/cards":{"get":{"tags":["cards"],"summary":"List cards (returns full PAN + CVV - SDD demo)","description":"Demo note: this endpoint intentionally returns full PAN and CVV so Cloudflare Sensitive Data Detection flags the response. Real banking APIs must never return clear PAN.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Card"}}}}}}}}}}