Authenticated bot-proof JSON API server
This is a minimal Go JSON API server to test guards against bots. It is an early prototype. More complete software is coming soon. For now:
-
Transport: HTTP.
-
Authentication: username/password with session cookies.
-
God-mode (CLI) for password resets.
-
Programmatic client: curl or ./tests in Go, no frontend.
## API Usage
### Register
curl -i -X POST \
-H "Content-Type: application/json" \
-d '{"username":"u1","password":"p1"}' \
http://localhost:8080/api/register
### Login (save cookie)
curl -i -c cookiejar.txt \
-X POST \
-H "Content-Type: application/json" \
-d '{"username":"u1","password":"p1"}' \
http://localhost:8080/api/login
### Profile (authenticated)
curl -i -b cookiejar.txt \
http://localhost:8080/api/profile
### Logout
curl -i -b cookiejar.txt \
-X POST \
http://localhost:8080/api/logout
Instruments against bots:
-
maximal request rate per IP (fixed window, in memory),
-
maximal request body size,
-
maximal account number per IP (persistent in SQLite).
Delve into ./tests and docs for tricky bits. See Architecture for more details.
Clone, cd, and run make all which should create two binaries inside ./bin: server and god.
First time (no DB):
mkdir data && touch data/data.db
sqlc generateAfter modifying Go code:
sqlc generate
makeAfter adding a new migration file to db/migrations:
sqlc generate
make
./bin/server./bin/server is a compiled .cmd/main.go which executes migrations via ./db/migrations.go. You must start/restart the server for migrations to take place.
Any change to DB takes place by creating a new migration file inside ./db/migrations.
The first one, 001_init.sql sets up the schema, so I keep the folder ./db/schema empty not to duplicate stuff. Once a migration takes place by starting ./bin/server, there is no way of modifying these files if you want to do things correctly with a running DB. There is no rolling back, deleting files, this is the SQL world.
Any modification takes place by adding a new migration which can add a variable or delete it via a new transaction which copies and recreates the whole table.
sqlc generate is a static tool to generate Go files once a migration file is added, and/or new queries are added inside ./db/queries.sql. It does not execute migrations, it only generates Go inside ./db.
Run cli tool god to manage users, SQLite allows that irrespectively whether server is running or not.
./bin/god user set --username alice --password passw0
./bin/server
time=2025-12-02T22:41:03.389+02:00 level=INFO msg="starting server" debug=true
time=2025-12-02T22:41:03.389+02:00 level=INFO msg="listening on :8080"
^Ctime=2025-12-02T22:41:40.035+02:00 level=INFO msg="shutting down"Adjust config.toml as you wish, but the tests will barf about the right values. The Go code uses defaults where needed if the values are wrong or omitted inside config.toml.
Use god to create/update (set) users, adjust (set) roles, delete users. It is a minimal CLI app which opens the same SQLite database (DB) file directly. It uses the same schema and sqlc queries.
SQLite supports multiple processes safely (file locking handles it), with one caveat. If the server is actively writing at the same moment, SQLite may briefly lock the DB. In that case god will just get a transient error; rerun is fine. So one can run god while the server is up or down.
# create admins
./bin/god user set --username test_user0 --role admin --password hunter2
./bin/god user set --username salomeja --role admin --password neris
# create regular users
./bin/god user set --username test_user1 --password pass1
./bin/god user set --username test_user2 --password pass2
# get user profile
./bin/god user get salomeja
# list users
./bin/god users list
# create/update user fields including role promotion/demotion and password rotation
./bin/god user set --username test_user0 --role user --password hunter2
./bin/god user set --username test_user1 --role admin --ip 1.2.3.4
./bin/god user set --username test_user0 --password newpass
# delete single user
./bin/god user delete --username test_user1
# bulk delete by prefix
./bin/god users delete --prefix test_
# bulk delete by creation date
./bin/god users delete --created-between 2025-12-02 2025-12-05Start the server, open another terminal and run one of these:
go run ./tests/register
go run ./tests/login
go run ./tests/profile
go run ./tests/logout
go run ./tests/req_rate_per_ip
go run ./tests/account_rate_per_ip
go run ./tests/req_body_size
go run ./tests/pow_registerKyle Conroy Gray: Introducing sqlc - Compile SQL queries to type-safe Go (2019)
sqlc is the way to deal with SQL in Go. No extra DSLs of ORMs, no SQL strings scattered in Go. Write SQL in SQL/AI, generate a succinct precise Go function per query with sqlc, and use that in Go. No magic objects, only plain functions.
Mat Ryer: How I write HTTP services in Go after 13 years (2024) on HN
Two useful things to take away: (i) graceful shutdown with signal.NotifyContext, and (ii) reducing application startup time with sync.Once. The first one is in ./cmd/server/main.go.
Rob Pike: Self-referential functions and the design of options (2014)
Do "builder design pattern" instead if you must, at least it is some sort of a "standard". I prefer plain Go, see ./internal/config/config.go.
This repo includes a huge number of medium.com clones. They are the most archetypical web apps with auth, users, posts, and comments. Most of them are over-architected: go kit, aws dynamoDB, hexagonal architecture, OpenAPI, JWTs... all the wrong ideas there ;).
