From 9eca3c71e2c5527c468823068a795f39ce93f38c Mon Sep 17 00:00:00 2001 From: Stevan Freeborn Date: Sat, 15 Aug 2026 22:07:31 -0500 Subject: [PATCH] docs: document auth helpers and expand README Add godoc comments to the exported auth package helpers. Expand the README with feature and tech stack overviews, setup instructions, an API reference, and make targets for development workflows. --- README.md | 93 ++++++++++++++++++++++++++++++++++++++++++- internal/auth/auth.go | 12 ++++++ 2 files changed, 104 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d044606..5fa4ae6 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,94 @@ # Chirpy -This is a project I built as part of [boot.dev](https://boot.dev). +A Twitter-style microblogging API built in Go as part of the [boot.dev](https://boot.dev) back-end course. + +## Features + +- Create, list, sort, and delete chirps (140-char limit, profanity-filtered) +- User registration and login with password hashing (argon2id) +- JWT access tokens and revocable refresh tokens +- Chirpy Red subscription upgrades via Polka webhooks +- Graceful shutdown and a lightweight admin metrics/reset surface + +## Tech stack + +- Go 1.26 (standard `net/http` with `http.ServeMux` routing) +- PostgreSQL +- [sqlc](https://sqlc.dev) for type-safe query generation +- [goose](https://pressly.github.io/goose/) for migrations +- [golang-jwt/jwt](https://github.com/golang-jwt/jwt) and [argon2id](https://github.com/alexedwards/argon2id) + +## Getting started + +### Prerequisites + +- Go 1.26+ +- PostgreSQL running locally +- `sqlc`, `goose`, and `make` (run `make install-tools` to install the first two) + +### Setup + +```sh +# 1. Configure environment variables +cp .env.example .env + +# fill in DB_URL, PLATFORM, JWT_SECRET, and POLKA_KEY + +# 2. Apply database migrations +make migrate-up + +# 3. Run the server +make run +``` + +The server listens on `http://localhost:8080`. + +### Development + +Run `make help` to see the full list of targets. Highlights: + +| Target | Description | +| --------------- | ---------------------------------------------- | +| `make run` | Build and run the server | +| `make dev` | Run with the race detector enabled | +| `make test` | Run all tests | +| `make test-race`| Run all tests with the race detector | +| `make check` | Run formatting, vet, and test checks | +| `make fmt` | Format all Go source files | +| `make lint` | Run golangci-lint (if installed) | +| `make build` | Compile the binary into `bin/` | +| `make generate` | Regenerate sqlc query code | +| `make migrate-up` / `migrate-down` / `migrate-status` / `migrate-reset` | Database migrations | +| `make install-tools` | Install goose, sqlc, and golangci-lint | +| `make clean` | Remove build artifacts | + +## API reference + +| Method | Path | Description | +| ------ | --------------------- | ----------------------------------------------- | +| POST | `/api/users` | Register a user | +| PUT | `/api/users` | Update email/password (Bearer access token) | +| POST | `/api/login` | Log in, receive access + refresh tokens | +| POST | `/api/refresh` | Exchange a refresh token for a new access token | +| POST | `/api/revoke` | Revoke a refresh token | +| GET | `/api/chirps` | List chirps (`?author_id=`, `?sort=asc\|desc`) | +| GET | `/api/chirps/{id}` | Get a single chirp | +| POST | `/api/chirps` | Create a chirp (Bearer access token) | +| DELETE | `/api/chirps/{id}` | Delete a chirp you authored | +| POST | `/api/polka/webhooks` | Polka webhook (`user.upgraded` -> Chirpy Red) | +| GET | `/api/healthz` | Health check | +| GET | `/app/` | Static index page | +| GET | `/admin/metrics` | File server hit counter (dev only) | +| POST | `/admin/reset` | Reset the database (dev only) | + +## Regenerating sqlc code + +After changing `sql/queries/`, run: + +```sh +make generate +``` + +## License + +MIT — see [LICENSE.md](LICENSE.md). diff --git a/internal/auth/auth.go b/internal/auth/auth.go index 1aad80a..f7ed4eb 100644 --- a/internal/auth/auth.go +++ b/internal/auth/auth.go @@ -1,3 +1,5 @@ +// Package auth provides password hashing, JWT, and token helpers used to +// authenticate and authorize API requests. package auth import ( @@ -13,14 +15,18 @@ import ( "github.com/google/uuid" ) +// HashPassword hashes a plaintext password using argon2id. func HashPassword(password string) (string, error) { return argon2id.CreateHash(password, argon2id.DefaultParams) } +// CheckPasswordHash reports whether password matches the given argon2id hash. func CheckPasswordHash(password string, hash string) (bool, error) { return argon2id.ComparePasswordAndHash(password, hash) } +// MakeJWT creates a signed HS256 JWT for the given user that expires after +// expiresIn. func MakeJWT(userID uuid.UUID, tokenSecret []byte, expiresIn time.Duration) (string, error) { token := jwt.New(jwt.GetSigningMethod(jwt.SigningMethodHS256.Name)) @@ -34,6 +40,7 @@ func MakeJWT(userID uuid.UUID, tokenSecret []byte, expiresIn time.Duration) (str return token.SignedString(tokenSecret) } +// ValidateJWT verifies the token signature and returns the subject's user ID. func ValidateJWT(tokenString string, tokenSecret []byte) (uuid.UUID, error) { token, err := jwt.ParseWithClaims(tokenString, &jwt.RegisteredClaims{}, func(t *jwt.Token) (any, error) { return tokenSecret, nil @@ -58,6 +65,7 @@ func ValidateJWT(tokenString string, tokenSecret []byte) (uuid.UUID, error) { return validUserId, nil } +// GetBearerToken extracts the "Bearer" token from the Authorization header. func GetBearerToken(headers http.Header) (string, error) { authorizationHeader := headers.Get("Authorization") @@ -70,6 +78,8 @@ func GetBearerToken(headers http.Header) (string, error) { return token, nil } +// GetAPIKey extracts the API key from the Authorization header using the +// "ApiKey" scheme. func GetAPIKey(headers http.Header) (string, error) { authorizationHeader := headers.Get("Authorization") @@ -82,6 +92,8 @@ func GetAPIKey(headers http.Header) (string, error) { return apiKey, nil } +// MakeRefreshToken generates a cryptographically random, hex-encoded refresh +// token. func MakeRefreshToken() string { bytes := make([]byte, 32) rand.Read(bytes)