Central login service: Becoming an OpenID Connect provider - Part 4
~28 min read
Everything so far assumed one thing: that the app I'm protecting can sit behind my reverse proxy. The proxy asks the login service "is this request allowed?", the answer arrives as a status code and a few headers, and the app never has to know how any of it happened.
Then I wanted Jenkins to use the same accounts. Jenkins can't play that game. It isn't a little app I wrote; it has its own idea of users, sessions and security realms, and it ships with a plugin that wants to speak O
Personal finance aggregator: Keeping data fresh: manual sync, resilience, and real stats - Part 7
~10 min read
Everything up to this point assumed the nightly sync just works. It mostly does - right up until one bank connection has a problem, at which point the version of fetch() I'd had since the very first CLI prototype turned "one bank's data is temporarily unavailable" into "nobody's data updates tonight." This post is about the sync loop growing up: a manual trigger for when 3am is too long to wait, failure isolation so one bad connection can't take the rest down with it, error message
Central login service: An admin page and a proper frontend - Part 3
~25 min read
By the end of Part 2 the login service was deployed and doing its job, and its entire user interface was one HTML form built from a Python string. That was fine for a service whose only visible surface was "type your password". It stopped being fine within a day, because I immediately wanted three things the form couldn't give me:
1. A way to see who has an account and when they last signed in.
2. A landing page listing the apps that sit behind the login, so the service is somew
Personal finance aggregator: Auth, two-factor, and account settings - Part 6
~10 min read
Status: superseded. Everything described below was built, shipped and then deleted within about two days. On 28 September, two commits moved login, passwords and two-factor out of this app entirely and into a separate identity-system service that nginx consults before any request reaches the app. The post stays because the design reasoning (and the gaps it ran into) are the reason that move happened - the full story is in post 9.
This is a single-user app sitting in front of real
Central login service: Shipping it - Jenkins, uv and a Debian container - Part 2
~25 min read
Part 1 ended with a login service that passed its tests and ran on my laptop. That's the easy half. A login service that isn't reachable from the other machines in the house isn't a login service, and "reachable" brought a pile of small decisions and an unglamorous run of commits with messages like ci: add uv.lock, fix python missmatch and fix deploy script.
This post is about all of that: the deployment target, the Jenkins pipeline, moving the build to uv, the rsync --delete that
Personal finance aggregator: Building the frontend: React, routing, and a design system - Part 5
~10 min read
For a while, the frontend was one component: log in, see a table of transactions, link a bank from a text input. That was fine for checking the backend actually worked, but it wasn't a tool I wanted to open every day. This post covers turning that single page into a proper multi-page app - Home, Accounts, Transactions, Settings - with real navigation and a design system underneath it, instead of default browser styling on a scattering of <div>s. It's the work of one commit (Add rou
Central login service: One place to type a password - Part 1
~25 min read
Every small tool I build for myself eventually needs a login. A finance tracker, an invoicing app, a dashboard for something plugged in to the wall. The first time, I wrote the whole thing inline: a user table, Argon2 hashing, sessions, a password-change form, two-factor enrolment with recovery codes. It worked, it was even fairly pleasant, and then I looked at the second app on my list and realised I was about to write all of it again.
This series is about the project I built ins
Personal finance aggregator: From script to service - Part 4
~10 min read
The CLI prototype proved the OAuth flow worked. It also had a very short list of things it fundamentally couldn't do: run anywhere but my laptop, keep transaction history instead of overwriting it, or be looked at by anything other than cat transactions.json. This post covers the two commits that fixed all three: init: basic backend/frontend, which turned the script into a FastAPI package with SQLite storage and typed settings, and Add TrueLayer web linking, provider/storage packag
Blog automation: Markdown to Ghost on every merge - Part 1
~9 min read
Every project I build gets a blog series. The finance system has nineteen posts, the identity system has seven, and each one is a markdown file that sits in the repo next to the code it describes. That part is easy to love: the posts are diffable, they live in git, and I can write them in the same editor as the code. The part I got tired of was the other half - copying each post into Ghost, fixing the links by hand, setting a publish date, and then doing it all over again when I cor
Personal finance aggregator: The first prototype: a CLI script that links a bank - Part 3
~10 min read
By this point I had sandbox credentials, a registered redirect URI, and a scope string I'd thought through (see the previous post). What I didn't have was any actual code, and I've learned not to trust an integration design until I've built the ugliest possible version of it and watched it work. So the first real artifact in this project wasn't an API, or a database, or a frontend - it was a single ~180-line script, truelayer_sync.py, that did exactly two things: link <bank> and fe
Personal finance aggregator: Developer access and app setup - Part 2
~10 min read
With TrueLayer picked as the data provider (see the previous post), the next step was the unglamorous part every API integration starts with: get an account, register an application, get credentials, and figure out exactly what permissions to ask for. This post is that setup, written down properly so I don't have to reconstruct it from memory the next time I need to do it again - for a second project, or when the production credentials need rotating.
Signing up and creating an ap
Personal finance aggregator: Choosing a data provider: TrueLayer vs Enable Banking - Part 1
~10 min read
I wanted one place to see everything I was spending, across six accounts at five banks that I actually use day-to-day: a Nationwide current account, a Nationwide credit card, Monzo, Tesco Bank, Virgin Money, and an M&S Bank credit card. None of these banks talk to each other, none of them export in a format the others understand, and I was tired of opening five apps to answer "how much did I actually spend this month." So: a personal finance aggregator. Pull everything into one SQL