Introduces code.nochebuena.dev/einherjar/auth-jwt — JWT authentication middleware and token lifecycle management for the Einherjar framework. Absorbs httpauth-jwt from micro-lib with three changes: logger parameter on AuthMiddleware, httputil.Error for consistent 401 responses, and added ECDSA support. Signers and Verifiers (one file per implementation — CT-6 compliant): - Verifier interface — Verify(tokenString string) (*jwt.Token, error) - Signer interface — extends Verifier; adds Sign(claims jwt.Claims) (string, error) - signer_hmac.go — NewHMACSigner(secret) → HS256; jwt.WithJSONNumber() on Verify - signer_rsa.go — NewRSASigner(key) + NewRSASignerFromPEM(pem) → RS256 - verifier_rsa.go — NewRSAPublicKeyVerifier + NewRSAPublicKeyVerifierFromPEM → RS256 verify-only - signer_ec.go — NewECSigner(key) + NewECSignerFromPEM(pem) → ES256/384/512; algorithm auto-detected from key curve (P-256→ES256, P-384→ES384, P-521→ES512) - verifier_ec.go — NewECPublicKeyVerifier + NewECPublicKeyVerifierFromPEM → EC verify-only Token lifecycle: - TokenConfig struct — AccessTTL, RefreshTTL, Issuer - TokenPair struct — AccessToken, RefreshToken, ExpiresIn - IssueTokenPair — access + refresh pair; customClaims merged at top level; refresh carries only sub/iss/iat/exp/jti/fam; jwt.WithJSONNumber() preserves int64 bitmasks - Blacklist interface — IsRevoked + Revoke; satisfied by cache-valkey via duck typing - ErrTokenRevoked — errors.New sentinel; errors.Is pattern for replay-attack detection - RefreshTokenPair — verifies token, checks blacklist, revokes old JTI, issues new pair HTTP middleware: - AuthMiddleware(logger, verifier, publicPaths) — verifies Bearer tokens; calls authmw.SetTokenData on success; 401 routed through httputil.Error (Warn level); publicPaths use path.Match wildcards; accepts Verifier (not Signer) to enforce narrowest-interface principle for verify-only services Compliance test (package authjwt_test) enforces CT-6 (≤1 exported TypeSpec/file), compile-time interface satisfaction, and behavioural coverage: HMAC/RSA/EC sign+verify, algorithm mismatch rejection, IssueTokenPair claims/jti/fam, RefreshTokenPair success/ revoked/blacklist-error/custom-claims, MaxInt64 json.Number precision, AuthMiddleware valid/invalid/expired/missing/public-path/wildcard/JSON-body/RSA-verifier/SetTokenData. Depends on auth v1.0.0, contracts v1.0.0, core v1.0.0, web v1.0.0, jwt/v5 v5.2.1. - identifiable.go: package-level Module variable (observability.Identifiable) for version identification — auth-jwt is a function library; not registered with the launcher
248 lines
9.9 KiB
Markdown
248 lines
9.9 KiB
Markdown
# Contributing to Einherjar
|
|
|
|
Thank you for your interest in contributing to Einherjar. This document explains everything you need to know before sending your first Pull Request.
|
|
|
|
Einherjar is developed and maintained by **NOCHEBUENADEV**, the trade name of its founder operating as a *Persona Física con Actividad Empresarial* (PFAE) under Mexican law. Contributions are welcome and valued — but they are accepted under the terms described here, so please read this document fully before you begin.
|
|
|
|
---
|
|
|
|
## Table of Contents
|
|
|
|
1. [Before You Start](#1-before-you-start)
|
|
2. [Legal: CLA and Copyright](#2-legal-cla-and-copyright)
|
|
3. [Development Setup](#3-development-setup)
|
|
4. [Code Standards](#4-code-standards)
|
|
5. [Commit Messages](#5-commit-messages)
|
|
6. [Submitting a Pull Request](#6-submitting-a-pull-request)
|
|
7. [Reporting Bugs](#7-reporting-bugs)
|
|
8. [Requesting Features](#8-requesting-features)
|
|
9. [What Gets Accepted](#9-what-gets-accepted)
|
|
|
|
---
|
|
|
|
## 1. Before You Start
|
|
|
|
**Open an issue first for anything non-trivial.**
|
|
|
|
Before you write a line of code, open an issue describing what you want to change and why. This protects your time: a change that seems straightforward may conflict with a planned refactor, an architectural decision, or the project's direction. Getting alignment before coding means your PR will not be rejected for reasons unrelated to its quality.
|
|
|
|
Exceptions where you can skip the issue:
|
|
- Typo or documentation-only fix
|
|
- Test coverage improvement for existing behavior
|
|
- Trivially obvious bug with a clear, contained fix
|
|
|
|
**Do not submit a PR that changes the public API of any module without prior discussion.** Every Einherjar module has a stability contract. Breaking changes require a major version bump and coordinated updates across dependent modules.
|
|
|
|
---
|
|
|
|
## 2. Legal: CLA and Copyright
|
|
|
|
### Contributor License Agreement
|
|
|
|
Before your first PR can be merged, you must sign the Contributor License Agreement by **posting a specific comment** on your Pull Request. The required comment text and full instructions are in [CLA.md](CLA.md).
|
|
|
|
> Checkboxes in PR descriptions are not used for CLA consent — they can be silently toggled by anyone. A comment is a timestamped, author-attributed record.
|
|
|
|
### Copyright
|
|
|
|
All original code in Einherjar is copyright **NOCHEBUENADEV**. NOCHEBUENADEV is the registered trade name of its founder, a natural person operating under the Mexican *Persona Física con Actividad Empresarial* (PFAE) regime.
|
|
|
|
When you contribute, you retain ownership of what you wrote. By signing the CLA you grant NOCHEBUENADEV a perpetual, irrevocable, worldwide license to use, modify, sublicense, and redistribute your Contribution — including the right to relicense it. See [CLA.md](CLA.md) for the full terms.
|
|
|
|
### License
|
|
|
|
Einherjar is licensed under the **GNU Affero General Public License v3.0** (AGPL-3.0). Your Contributions will be distributed under the same license unless the Maintainers exercise their relicensing rights under the CLA.
|
|
|
|
---
|
|
|
|
## 3. Development Setup
|
|
|
|
Einherjar uses a Go workspace (`go.work`) that spans all modules. You do not need to `go get` anything — local replacements are wired automatically.
|
|
|
|
### Prerequisites
|
|
|
|
- Go 1.26+
|
|
- Git
|
|
|
|
### Clone and initialize
|
|
|
|
```bash
|
|
git clone https://code.nochebuena.dev/einherjar/<module-name>
|
|
cd <module-name>
|
|
|
|
# If working across multiple modules, clone the workspace root instead
|
|
# and all modules will resolve from disk via go.work.
|
|
```
|
|
|
|
### Verify your setup
|
|
|
|
```bash
|
|
go build ./... # must compile clean
|
|
go vet ./... # no warnings
|
|
go test ./... # all tests pass
|
|
gofmt -l . # no output (no unformatted files)
|
|
```
|
|
|
|
All four commands must produce clean output before a PR will be reviewed.
|
|
|
|
---
|
|
|
|
## 4. Code Standards
|
|
|
|
These are non-negotiable. Every PR is checked against them.
|
|
|
|
### One exported type per file (CT-6)
|
|
|
|
Each non-test `.go` file may contain **at most one exported TypeSpec** (type, struct, or interface declaration). Unexported helpers, constants, and functions may coexist in the same file. `_test.go` files are exempt.
|
|
|
|
This rule exists to keep the codebase navigable: a developer who knows the type name can immediately predict the file name.
|
|
|
|
```
|
|
provider.go ← type Provider interface { ... } ✓ one exported type
|
|
component.go ← type Component interface { ... } ✓ one exported type
|
|
new.go ← func New(...) + unexported impl ✓ zero exported types
|
|
```
|
|
|
|
### Formatting
|
|
|
|
All code must be formatted with `gofmt`. No exceptions. If `gofmt -l .` produces output, the PR will not be merged.
|
|
|
|
Do not configure your editor to use `goimports` as a replacement — it may add import groups that diverge from the project style. Use `gofmt` + manual import management.
|
|
|
|
### Naming conventions
|
|
|
|
- Follow standard Go naming: `CamelCase` for exported, `camelCase` for unexported.
|
|
- Interfaces that represent a capability are named with an agent noun: `Provider`, `Sender`, `Checkable`.
|
|
- Interfaces that represent a full component are named `Component`.
|
|
- Config structs are named `Config`. One config struct per module root.
|
|
- Constructors are named `New` (main) or `NewXxx` (adapters and variants).
|
|
|
|
### Error handling
|
|
|
|
- Use `core/xerrors` for all errors returned from public API. Never return raw `errors.New` or `fmt.Errorf` from exported functions.
|
|
- Error codes must map to the gRPC canonical set defined in `xerrors`. If you need a new code, open an issue first.
|
|
- Do not swallow errors silently. Log at the appropriate level or return them.
|
|
|
|
### No comments unless necessary
|
|
|
|
Do not add comments that restate what the code already says. Only add a comment when the **why** is non-obvious: a hidden constraint, a subtle invariant, a workaround for a known upstream bug. If removing the comment would not confuse a future reader, do not write it.
|
|
|
|
### Dependencies
|
|
|
|
Do not add new external dependencies without opening an issue and getting explicit approval first. Einherjar modules are deliberately lean. Every new dependency increases the blast radius for downstream consumers.
|
|
|
|
---
|
|
|
|
## 5. Commit Messages
|
|
|
|
Follow the [Conventional Commits](https://www.conventionalcommits.org/) specification:
|
|
|
|
```
|
|
<type>(<scope>): <short description>
|
|
|
|
[optional body]
|
|
```
|
|
|
|
| Type | When to use |
|
|
|---|---|
|
|
| `feat` | New exported function, type, or behavior |
|
|
| `fix` | Bug fix in existing behavior |
|
|
| `docs` | Documentation only |
|
|
| `test` | Tests only, no production code change |
|
|
| `refactor` | Code restructure with no behavior change |
|
|
| `chore` | Build system, CI, dependency updates |
|
|
|
|
**Scope** is the module name without the `einherjar/` prefix: `core`, `web`, `db-postgres`, `cache-valkey`, etc.
|
|
|
|
Examples:
|
|
```
|
|
feat(cache-valkey): add IncrWithTTL for atomic fixed-window counters
|
|
fix(db-postgres): handle pgconn deadline exceeded as ErrDeadlineExceeded
|
|
docs(web): document rate limiter fail-open behavior
|
|
```
|
|
|
|
Keep the subject line under 72 characters. Write in the imperative mood ("add", "fix", "remove" — not "added", "fixes", "removed").
|
|
|
|
---
|
|
|
|
## 6. Submitting a Pull Request
|
|
|
|
1. **Open an issue first** (see §1) unless the change is trivial.
|
|
2. Fork the repository and create a branch from `main`:
|
|
```bash
|
|
git checkout -b feat/your-feature-name
|
|
```
|
|
3. Make your changes following the standards in §4.
|
|
4. Ensure all verification commands pass (§3).
|
|
5. Open the PR against `main` using the provided PR template.
|
|
6. **Post the CLA comment** on the PR before requesting review (see §2 and [CLA.md](CLA.md)).
|
|
7. Respond to review feedback. Keep the review cycle short by addressing all comments before re-requesting review.
|
|
|
|
### Branch naming
|
|
|
|
| Prefix | Use for |
|
|
|---|---|
|
|
| `feat/` | New features |
|
|
| `fix/` | Bug fixes |
|
|
| `docs/` | Documentation changes |
|
|
| `test/` | Test additions or improvements |
|
|
| `refactor/` | Refactors without behavior change |
|
|
|
|
### PR size
|
|
|
|
Keep PRs focused. A PR that does one thing is easier to review, faster to merge, and safer to revert if needed. If your change naturally spans multiple concerns, split it into multiple PRs.
|
|
|
|
---
|
|
|
|
## 7. Reporting Bugs
|
|
|
|
Open an issue with the following information:
|
|
|
|
- **Module** affected (`einherjar/db-postgres`, `einherjar/web`, etc.)
|
|
- **Go version** (`go version`)
|
|
- **Minimal reproduction** — the smallest code snippet that demonstrates the problem
|
|
- **Expected behavior** vs **actual behavior**
|
|
- **Error output** if applicable (sanitize any credentials or sensitive data)
|
|
|
|
Do not open a PR to fix a bug without first opening an issue. The bug may be intentional behavior, already fixed on `main`, or caused by something outside the module.
|
|
|
|
---
|
|
|
|
## 8. Requesting Features
|
|
|
|
Open an issue with:
|
|
|
|
- **What** you want to add and **why** it belongs in the framework (not in application code)
|
|
- **Which module** it affects, or whether it requires a new module
|
|
- **API sketch** — what the interface, function signature, or config field would look like
|
|
- **Alternative approaches** you considered
|
|
|
|
Feature requests that add a new external dependency, change a public interface, or cross module boundaries require longer discussion before approval.
|
|
|
|
---
|
|
|
|
## 9. What Gets Accepted
|
|
|
|
Einherjar is a **focused framework**. It covers a specific, well-defined set of infrastructure concerns. Contributions that fall outside that scope — however well-written — will not be merged.
|
|
|
|
What fits:
|
|
- Bug fixes in existing behavior
|
|
- Performance improvements with benchmarks
|
|
- Missing error mappings for existing drivers
|
|
- Documentation improvements and example corrections
|
|
- Test coverage for untested edge cases
|
|
|
|
What does not fit without prior architectural agreement:
|
|
- New modules (open an issue first)
|
|
- New external dependencies
|
|
- Changes to public interfaces in any module
|
|
- Features that belong in application code rather than the framework
|
|
|
|
If you are unsure, open an issue and ask. It costs nothing and saves everyone time.
|
|
|
|
---
|
|
|
|
*Einherjar was built for those who come after. Contributions that hold to that standard — clear, documented, tested, designed for the developer who was never in the room — are always welcome.*
|
|
|
|
— **NOCHEBUENADEV**
|