AzureBank

Portfolio project by Vladislav Aleshaev Full-stack engineer (.NET, React, Azure)

Progetto di portfolio di Vladislav Aleshaev Sviluppatore full-stack (.NET, React, Azure)

A working bank. Open it and try to break it. Una banca che funziona. Aprila e prova a romperla.

A banking demo with invented money: a .NET 10 API, a React app and SQL Server, live on Azure. I designed it, wrote part of the code and direct the AI coding agents that write the rest. The same payment is not applied twice (the same request arriving again after a network error, not a second payment made on purpose), the access token stays on the server, and security events are written to a hash‑chained log.

Una demo bancaria con denaro inventato: un'API .NET 10, un'app React e SQL Server, online su Azure. L'ho progettata io: una parte del codice l'ho scritta, il resto lo scrivono agenti AI che dirigo. Lo stesso pagamento non viene eseguito due volte (la stessa richiesta arrivata di nuovo per un errore di rete, non un secondo pagamento voluto), il token di accesso resta sul server e gli eventi di sicurezza finiscono in un registro concatenato con hash.

Open the live demo Apri la demo

The demo is asleep. La demo dorme. Checking the demo… Controllo la demo… Starting it takes about 25 seconds. Si sveglia in circa 25 secondi. Invented money, no sign-up. Denaro inventato, senza registrarsi.

The AzureBank dashboard on a desktop: the menu, the balance across two accounts, a transfer shortcut, deposit and withdrawal, and the latest movements. The AzureBank dashboard on a desktop, dark theme: the menu, the balance across two accounts, a transfer shortcut, deposit and withdrawal, and the latest movements.
The same dashboard on a phone: the available balance and the two accounts. The same dashboard on a phone, dark theme: the available balance and the two accounts.

What it proves Che cosa dimostra

  • The same payment is not applied twice. Lo stesso pagamento non viene eseguito due volte.

    Every money operation carries a key the server recognises, and is applied at most once per key: when the same request arrives again with the same key, after a retry or a network error, the money does not move a second time. That held under 24 identical parallel requests on a real SQL Server. A key is kept for 24 hours: the same request sent after that is a new payment. The screen has its own guard as well: it sends no second request while the first is in flight, so a double click sends one.

    Ogni operazione che muove denaro porta con sé una chiave che il server riconosce, ed è eseguita al massimo una volta per chiave: se la stessa richiesta arriva di nuovo con la stessa chiave, per un nuovo tentativo o un errore di rete, il denaro non si muove una seconda volta. Ha retto a 24 richieste identiche in parallelo su un vero SQL Server. Una chiave è conservata per 24 ore: la stessa richiesta inviata dopo è un nuovo pagamento. Anche la schermata ha la sua protezione: non invia una seconda richiesta mentre la prima è in corso, quindi un doppio clic ne invia una sola.

    Decision 0009 Decisione 0009 Test: 24 in parallel Test: 24 in parallelo Test: a double click Test: un doppio clic

  • The access token never reaches the browser. Il token di accesso non arriva mai al browser.

    A BFF keeps it in a session on the server; the browser gets a cookie that the page's scripts cannot read.

    Un BFF lo tiene in una sessione sul server; il browser riceve un cookie che gli script della pagina non possono leggere.

    SECURITY.md Test: the cookie Test: il cookie

  • One PIN entry authorises one payment. Un inserimento del PIN autorizza un solo pagamento.

    Bound to the account, the amount and, for a transfer, the payee; valid two minutes and spent once. Three wrong PINs in a row lock the PIN for 15 minutes.

    Legato al conto, all'importo e, per un bonifico, al beneficiario; valido due minuti e utilizzabile una sola volta. Tre PIN sbagliati di fila bloccano il PIN per 15 minuti.

    Decision 0042 Decisione 0042 Test: spent once Test: usata una volta

  • A security log that can be checked. Un registro di sicurezza che si può verificare.

    Append-only, each row chained to the one before by a keyed hash. A tool in the repository, AzureBank.AuditVerifier, walks the chain. It needs the database and the chain's two keys, so a visitor can run it on a local copy of the project, not on the live demo. It does not detect rows cut from the end of the table.

    Il registro riceve solo righe nuove, ognuna legata alla precedente da un hash con chiave. Uno strumento del repository, AzureBank.AuditVerifier, percorre la catena. Gli servono il database e le due chiavi della catena, quindi un visitatore non può usarlo sulla demo online; su una copia locale del progetto sì. Non rileva le righe tagliate dalla fine della tabella.

    Decision 0044 Decisione 0044 Test: the chain Test: la catena

  • The frontend is held to the API's contract. Il frontend è verificato sul contratto dell'API.

    The API generates the contract. The same contract tests run against the mock and against the real BFF, API and SQL Server, and the browser tests run on the built app as the BFF serves it.

    Il contratto lo genera l'API. Gli stessi test di contratto girano contro il mock e contro BFF, API e SQL Server reali, e i test nel browser girano sull'app compilata, così come la serve il BFF.

    Decision 0053 Decisione 0053 Test: the contract Test: il contratto

Test coverage Copertura dei test

A CI job counts the lines of hand-written code that the tests run, on every pull request to main, and fails when the backend's application code or the frontend's falls under 95%. It is not one of the eleven required checks. Tests and their helpers, generated code and database migrations are in no figure.

Un job di CI conta, a ogni pull request verso main, le righe di codice scritto a mano che i test eseguono, e fallisce quando il codice applicativo del backend o quello del frontend scende sotto il 95%. Non è uno degli undici controlli obbligatori. I test con i loro strumenti, il codice generato e le migrazioni del database non entrano in nessun numero.

A line that ran is not a line that was checked. Una riga eseguita non è una riga verificata. Counted on Contati il

Try it in three minutes Provala in tre minuti

  1. The balance card of a demo copy: 14,750.00 euros, Main Savings 12,450.00 and Checking 2,300.00. The balance card of a demo copy, dark theme: 14,750.00 euros, Main Savings 12,450.00 and Checking 2,300.00.

    Open the demo and press “Try the demo”: you get a private copy that no other visitor sees, two accounts (Main Savings €12,450.00 and Checking €2,300.00) and two months of history.

    Apri la demo e premi «Try the demo»: ricevi una copia privata che nessun altro visitatore vede, due conti (Main Savings €12,450.00 e Checking €2,300.00) e due mesi di movimenti.

  2. The confirmation of a transfer of 40.00 euros to a contact: six boxes for the PIN. The confirmation of a transfer of 40.00 euros to a contact, dark theme: six boxes for the PIN.

    Send money to a contact: type the handle, press “Verify”, enter an amount and confirm with the PIN 123456, which the page shows. Then make a deposit and a withdrawal, and open the history.

    Invia denaro a un contatto: scrivi il suo handle, premi «Verify», inserisci un importo e conferma con il PIN 123456, indicato nella pagina. Poi fai un deposito e un prelievo, e apri lo storico.

  3. The receipt of one deposit: Deposit Successful, plus 250.00 euros, to Main Savings. The receipt of one deposit, dark theme: Deposit Successful, plus 250.00 euros, to Main Savings.

    Then try to break it.

    • Double-click the button that sends a deposit: one deposit, not two.
    • Send more than the account holds: the form stops you, and nothing is sent.
    • Type a wrong PIN three times: the PIN locks for 15 minutes. Leave this one for last.

    Poi prova a romperla.

    • Fai doppio clic sul pulsante che invia un deposito: un solo deposito, non due.
    • Invia più di quanto c'è sul conto: il modulo ti ferma, e non parte nulla.
    • Sbaglia il PIN tre volte: il PIN si blocca per 15 minuti. Lascia questa prova per ultima.

Invented money, no sign-up. You can come back to your copy for 24 hours; it is deleted later, by a job that runs every four hours. Don't enter real personal data. More things to try

Denaro inventato, nessuna registrazione. In ogni copia si può entrare per 24 ore; viene cancellata più tardi, da un processo che parte ogni quattro ore. Non inserire dati personali veri. L'interfaccia della demo è in inglese. Altre prove da fare (in inglese)

How it is built Com'è costruita

The path of a request Il percorso di una richiesta

What each stop guarantees, with the document that states it.

Che cosa garantisce ogni tappa, con il documento che lo afferma.

  1. Browser side Lato browser

    Browser

    Runs the React 19 app (TypeScript, Fluent UI).

    Esegue l'app React 19 (TypeScript, Fluent UI).

    To the next stop goes: Alla tappa successiva passa: cookie

  2. Server side Lato server

    BFF

    Holds the session and the access token the API issues at sign-in; adds the token to each request.

    Tiene la sessione e il token di accesso che l'API emette all'accesso; aggiunge il token a ogni richiesta.

    To the next stop goes: Alla tappa successiva passa: + token

  3. API

    The money rules, the PIN and the audit trail.

    Le regole sul denaro, il PIN e il registro di controllo.

    To the next stop, through: Alla tappa successiva, attraverso: EF Core

  4. SQL Server

    Azure SQL Database: the accounts, the movements and the audit rows.

    Azure SQL Database: i conti, i movimenti e le righe del registro.

Also in the stack: .NET 10, ASP.NET Core, GitHub Actions, CodeQL, Azure Container Apps, Bicep.

Nello stack anche: .NET 10, ASP.NET Core, GitHub Actions, CodeQL, Azure Container Apps, Bicep.

How I work with AI coding agents Come lavoro con gli agenti AI

I am the project's architect and sole engineer. I designed the system and decide what gets built. I have written part of the code myself, and I direct the AI coding agents that write the rest under rules I set. I read the code I merge. Every merge is my decision and my responsibility. The agents are made by others. What I build around them is the harness: what they read, what runs on their changes and what limits them.

Sono l'architetto del progetto e il suo unico ingegnere. Ho progettato il sistema e decido che cosa si costruisce. Una parte del codice l'ho scritta io, e dirigo gli agenti AI che scrivono il resto, secondo regole fissate da me. Leggo il codice di cui faccio il merge. Ogni merge è una mia decisione e una mia responsabilità. Attorno a questi agenti, sviluppati da altri, costruisco io l'harness: che cosa leggono, quali controlli girano sulle loro modifiche, che cosa li limita.

Limits, and further reading Limiti, e dove approfondire

Limits Limiti

  • The first load after a quiet spell takes about 25 seconds, and the first sign-in after it can take a few seconds. With the app awake, a sign-in took under a second when I measured it. It is a demo: invented data, one small instance, no availability guarantee.
  • The check of the security log shows a change made by someone who holds the database but not the chain's key; it does not show rows cut from the end of the table. It is run by hand: nothing runs it on a schedule.
  • The PIN is a second secret, not a one-time code: that needs a second channel, which this project does not have.
  • On the demo the browser keeps the sign-in details of the visitor's own throwaway copy, so that the visitor can come back to it. A script injected in the page could read them; they open invented money only.
  • I merge every pull request, and no second person reviews one: the 11 required checks, the code scan and an AI review are automated.
  • Il primo caricamento dopo una pausa richiede circa 25 secondi, e il primo accesso che segue può richiederne ancora qualcuno. Con l'app sveglia, nelle mie misure un accesso ha richiesto meno di un secondo. È una demo: dati inventati, una sola piccola istanza, nessuna garanzia di disponibilità.
  • La verifica del registro di sicurezza rivela una modifica fatta da chi ha il database ma non la chiave della catena; non rivela le righe tagliate dalla fine della tabella. Si lancia a mano: non è programmata.
  • Il PIN è un secondo segreto, non un codice usa e getta: servirebbe un secondo canale, che questo progetto non ha.
  • Nella demo il browser conserva i dati di accesso della copia usa e getta del visitatore, perché possa tornarci. Uno script iniettato nella pagina potrebbe leggerli; aprono solo denaro inventato.
  • Il merge di ogni pull request lo faccio io, e nessun'altra persona la rivede: gli 11 controlli obbligatori, la scansione del codice e una revisione AI sono automatici.

Read further Per approfondire (in inglese)

Vladislav Aleshaev

Full-stack engineer (.NET, React, Azure). AzureBank is my portfolio project.

Sviluppatore full-stack (.NET, React, Azure). AzureBank è il mio progetto di portfolio.