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.
This commit is contained in:
Stevan Freeborn
2026-08-15 22:07:31 -05:00
parent 6294910727
commit 9eca3c71e2
2 changed files with 104 additions and 1 deletions
+92 -1
View File
@@ -1,3 +1,94 @@
# Chirpy # 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).
+12
View File
@@ -1,3 +1,5 @@
// Package auth provides password hashing, JWT, and token helpers used to
// authenticate and authorize API requests.
package auth package auth
import ( import (
@@ -13,14 +15,18 @@ import (
"github.com/google/uuid" "github.com/google/uuid"
) )
// HashPassword hashes a plaintext password using argon2id.
func HashPassword(password string) (string, error) { func HashPassword(password string) (string, error) {
return argon2id.CreateHash(password, argon2id.DefaultParams) return argon2id.CreateHash(password, argon2id.DefaultParams)
} }
// CheckPasswordHash reports whether password matches the given argon2id hash.
func CheckPasswordHash(password string, hash string) (bool, error) { func CheckPasswordHash(password string, hash string) (bool, error) {
return argon2id.ComparePasswordAndHash(password, hash) 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) { func MakeJWT(userID uuid.UUID, tokenSecret []byte, expiresIn time.Duration) (string, error) {
token := jwt.New(jwt.GetSigningMethod(jwt.SigningMethodHS256.Name)) 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) 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) { func ValidateJWT(tokenString string, tokenSecret []byte) (uuid.UUID, error) {
token, err := jwt.ParseWithClaims(tokenString, &jwt.RegisteredClaims{}, func(t *jwt.Token) (any, error) { token, err := jwt.ParseWithClaims(tokenString, &jwt.RegisteredClaims{}, func(t *jwt.Token) (any, error) {
return tokenSecret, nil return tokenSecret, nil
@@ -58,6 +65,7 @@ func ValidateJWT(tokenString string, tokenSecret []byte) (uuid.UUID, error) {
return validUserId, nil return validUserId, nil
} }
// GetBearerToken extracts the "Bearer" token from the Authorization header.
func GetBearerToken(headers http.Header) (string, error) { func GetBearerToken(headers http.Header) (string, error) {
authorizationHeader := headers.Get("Authorization") authorizationHeader := headers.Get("Authorization")
@@ -70,6 +78,8 @@ func GetBearerToken(headers http.Header) (string, error) {
return token, nil return token, nil
} }
// GetAPIKey extracts the API key from the Authorization header using the
// "ApiKey" scheme.
func GetAPIKey(headers http.Header) (string, error) { func GetAPIKey(headers http.Header) (string, error) {
authorizationHeader := headers.Get("Authorization") authorizationHeader := headers.Get("Authorization")
@@ -82,6 +92,8 @@ func GetAPIKey(headers http.Header) (string, error) {
return apiKey, nil return apiKey, nil
} }
// MakeRefreshToken generates a cryptographically random, hex-encoded refresh
// token.
func MakeRefreshToken() string { func MakeRefreshToken() string {
bytes := make([]byte, 32) bytes := make([]byte, 32)
rand.Read(bytes) rand.Read(bytes)