Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ JDK 21 is required (Gradle's toolchain fetches one if absent). Node ≥ 20 + npm

Dependency arrows always point **inward**. Adapters depend on core; core depends on no adapter, no framework, no servlet/HTTP API, no JDBC/DynamoDB.

1. **`pk-auth-core`** — framework- and persistence-neutral. Knows WebAuthn (WebAuthn4J), the wire contract, and declares the SPIs. `PasskeyAuthenticationService` is the ceremony entry point. Only `api`, `spi`, and `config` packages are exported (enforced via `module-info.java`).
1. **`pk-auth-core`** — framework- and persistence-neutral. Knows WebAuthn (WebAuthn4J), the wire contract, and declares the SPIs. `PasskeyAuthenticationService` is the ceremony entry point. The exported packages are `api`, `ceremony`, `config`, `credential`, `error`, `json`, `lifecycle`, `metrics`, and `spi` (enforced via `module-info.java`); everything else is module-internal.
2. **SPIs (ports)** — narrow interfaces the host implements: `UserLookup`, `CredentialRepository`, `ChallengeStore` (required); `BackupCodeRepository`, `OtpRepository`, `EmailSender`, `SmsSender`, `RefreshTokenRepository`, `AccessTokenStore`, `TokenTtlPolicy`, `UserDeletionListener`, `AttestationTrustPolicy`, `OriginValidator`, `ClockProvider` (optional / feature-gated). See `DESIGN.md` §6 for the required-vs-optional table.
3. **Adapters** — `pk-auth-spring-boot-starter` (Spring Boot 4 / Security 7 autoconfigure), `pk-auth-dropwizard` (Dropwizard 5 `ConfiguredBundle` + Dagger 2), `pk-auth-micronaut` (Micronaut 4 `@Factory` + `@Filter`, deliberately **not** Micronaut Security). Each mounts the same `/auth/**` JSON contract and pattern-matches the core's sealed result sums into HTTP status codes.

Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ These mirror `pk-auth-build-brief.md` §12 — read that section for the full ra
4. **Dependencies.** New libraries go through `gradle/libs.versions.toml` and are justified in the commit message or an ADR.
5. **No `TODO` in main** unless paired with a GitHub issue link.
6. **SPDX header** on every Java source file: `// SPDX-License-Identifier: MIT`. Spotless enforces this.
7. **`@since` tags on public API.** Every new or modified public element (class, record, interface, method, field, or sealed-variant) gets an `@since X.Y.Z` Javadoc tag carrying the version it first ships in. The current target is `@since 1.1.0` for surfaces introduced in the in-flight 1.1 line; pre-1.1 surfaces keep the version they shipped with. When renaming a method or changing its signature in a MAJOR bump, update the `@since` on the new shape. The policy applies across `pk-auth-core`, `pk-auth-admin-api`, `pk-auth-jwt`, `pk-auth-otp`, `pk-auth-magic-link`, `pk-auth-backup-codes`, `pk-auth-refresh-tokens`, and the three adapter modules.
7. **`@since` tags on public API.** Every new or modified public element (class, record, interface, method, field, or sealed-variant) gets an `@since X.Y.Z` Javadoc tag carrying the version it first ships in. The current target is the in-flight version in `gradle.properties` (the `version` property, minus the `-SNAPSHOT` suffix) for newly introduced surfaces; existing surfaces keep the version they shipped with. When renaming a method or changing its signature in a MAJOR bump, update the `@since` on the new shape. The policy applies across `pk-auth-core`, `pk-auth-admin-api`, `pk-auth-jwt`, `pk-auth-otp`, `pk-auth-magic-link`, `pk-auth-backup-codes`, `pk-auth-refresh-tokens`, and the three adapter modules.
8. **Don't optimize prematurely.** Correct → tested → fast.
9. **Constructor-injection annotations.** Spring and Micronaut detect a single non-default constructor automatically; do not add `@Autowired` / `@Inject` in those adapters. Dropwizard's adapter is wired through Dagger 2, which requires `@Inject` on the injected constructor — keep the annotation. The asymmetry is by DI framework, not by style preference; new files in each adapter follow the conventions already present in that module.

Expand Down
16 changes: 9 additions & 7 deletions DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ exposes, and the conventions the codebase follows.
For specific topics:

- **What it does + how to run a demo** — [`README.md`](./README.md).
- **Per-decision rationale** — [`docs/adr/`](./docs/adr/) (16 ADRs,
numbered).
- **Per-decision rationale** — [`docs/adr/`](./docs/adr/) (numbered,
sequential).
- **Running it in production** — [`docs/operator-guide.md`](./docs/operator-guide.md).
- **Security posture** — [`docs/threat-model.md`](./docs/threat-model.md).
- **The original brief and phase plan** —
Expand Down Expand Up @@ -107,7 +107,7 @@ modules implement SPIs declared in core and are wired in by the host.

| Module | Purpose |
|---|---|
| `pk-auth-core` | Framework-neutral ceremony engine. `PasskeyAuthenticationService` is the entry point; `api/`, `spi/`, `config/`, and `lifecycle/` are exported. Hosts the `UserDeletionService` fan-out and `UserDeletionListener` SPI ([ADR 0016](./docs/adr/0016-user-deletion-fan-out.md)). |
| `pk-auth-core` | Framework-neutral ceremony engine. `PasskeyAuthenticationService` is the entry point; `api/`, `ceremony/`, `config/`, `credential/`, `error/`, `json/`, `lifecycle/`, `metrics/`, and `spi/` are exported (see `module-info.java`). Hosts the `UserDeletionService` fan-out and `UserDeletionListener` SPI ([ADR 0016](./docs/adr/0016-user-deletion-fan-out.md)). |
| `pk-auth-jwt` | HS256 JWT mint (`PkAuthJwtIssuer`) + validate (`PkAuthJwtValidator`). Nimbus JOSE+JWT under the hood. Hosts the `TokenTtlPolicy` SPI for per-audience access-token TTL dispatch ([ADR 0014](./docs/adr/0014-per-audience-ttl-policy.md)) and the `AccessTokenStore` SPI for stateful (server-revocable) access tokens ([ADR 0015](./docs/adr/0015-stateful-access-tokens.md)). |
| `pk-auth-backup-codes` | Alt flow: generate, hash (Argon2id), and atomically claim view-once backup codes. |
| `pk-auth-magic-link` | Alt flow: random-token magic links over the host's email dispatcher. |
Expand Down Expand Up @@ -237,8 +237,8 @@ storage SPIs against a real backend.

```java
// build.gradle.kts
implementation("io.codeheadsystems:pk-auth-spring-boot-starter:<version>")
implementation("io.codeheadsystems:pk-auth-persistence-jdbi:<version>") // optional
implementation("com.codeheadsystems:pk-auth-spring-boot-starter:<version>")
implementation("com.codeheadsystems:pk-auth-persistence-jdbi:<version>") // optional

// application.yml
pkauth:
Expand Down Expand Up @@ -475,8 +475,10 @@ Worth knowing if you're contributing:
- **Null discipline**: `@org.jspecify.annotations.NonNull` /
`@Nullable` on every public method parameter and return type.
JSpecify is loaded; Error Prone catches violations.
- **Public API is sealed** via `module-info.java` exports. Only `api`,
`spi`, and `config` packages are exported per module.
- **Public API is sealed** via `module-info.java` exports. In
`pk-auth-core` the exported packages are `api`, `ceremony`, `config`,
`credential`, `error`, `json`, `lifecycle`, `metrics`, and `spi`;
everything else is module-internal.
- **No reflection in hot paths.** The only reflection is Jackson's,
confined to (de)serialization boundaries.
- **Conventional commits** in commit messages; rationale for any
Expand Down
2 changes: 1 addition & 1 deletion GETTING_STARTED.md
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,6 @@ project, and the three demos are the proof.
- **The wire and class details** — [`DESIGN.md`](./DESIGN.md).
- **Running it in production** — [`docs/operator-guide.md`](./docs/operator-guide.md).
- **Security stance** — [`docs/threat-model.md`](./docs/threat-model.md).
- **Why a thing is the way it is** — [`docs/adr/`](./docs/adr/) (16 ADRs).
- **Why a thing is the way it is** — [`docs/adr/`](./docs/adr/).
- **SPI stability + versioning** — [`docs/stability.md`](./docs/stability.md).
- **Transactional behavior across SPIs** — [`docs/transactional-semantics.md`](./docs/transactional-semantics.md).
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,10 +151,13 @@ tests and Playwright end-to-end suites:
## Status

1.0.0 cut the stable baseline; the current development line is
**1.1.0-SNAPSHOT**, which adds per-audience JWT TTLs, the
**1.3.1-SNAPSHOT** (see `gradle.properties` for the authoritative
version). The 1.1 line added per-audience JWT TTLs, the
`AccessTokenStore` (stateful access tokens), the
`UserDeletionService` fan-out, and the `pk-auth-refresh-tokens`
module (rotating refresh tokens with family-based replay defense).
module (rotating refresh tokens with family-based replay defense);
1.2–1.3 followed with per-framework admin toggles and supply-chain
hardening.
See [`CHANGELOG.md`](./CHANGELOG.md) for the full delta and
[`docs/stability.md`](./docs/stability.md) for the versioning policy
and the list of SPI surfaces. The full build and end-to-end suites
Expand Down
10 changes: 6 additions & 4 deletions docs/stability.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
## Current status: 1.x

pk-auth is **post-1.0** as of the `v1.0.0` tag. The current development line is
**1.1.0-SNAPSHOT**; see [`CHANGELOG.md`](../CHANGELOG.md) for the in-flight delta.
**1.3.1-SNAPSHOT** (`gradle.properties` is authoritative); the 1.1, 1.2, and
1.3 releases have shipped. See [`CHANGELOG.md`](../CHANGELOG.md) for the
per-release delta.

The 1.x line follows [Semantic Versioning](https://semver.org/):

Expand All @@ -16,12 +18,12 @@ The 1.x line follows [Semantic Versioning](https://semver.org/):
Configuration key renames or removals are treated as breaking changes and
require a MAJOR bump.

> **1.1 caveat.** The 1.1.0 line is still in development and carries a small
> **1.1 migration note (historical).** The 1.1.0 release carried a small
> number of *intentional* breaking SPI changes against 1.0 — the
> `CredentialRepository.deleteByUserHandle` / `OtpRepository.deleteByUserHandle`
> additions, the `JwtConfig.tokenTtl` → `JwtConfig.ttlPolicy` swap, and the
> `JwtConfig.audience` → `JwtConfig.defaultAudience` rename. These will land
> together at 1.1.0 final; see `CHANGELOG.md` for the migration notes. From
> `JwtConfig.audience` → `JwtConfig.defaultAudience` rename. These landed
> together at 1.1.0; see `CHANGELOG.md` for the migration notes. From
> 1.1.0 onward the SemVer contract above is binding.

Pin to an exact version in your build file and review the changelog before
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@
import com.codeheadsystems.pkauth.api.UserHandle;
import java.util.Objects;

/** Wire-shape returned by {@code GET /auth/admin/account}. */
/**
* Wire-shape returned by {@code GET /auth/admin/account}.
*
* @since 0.9.0
*/
public record AccountSummary(
UserHandle userHandle,
String username,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
* Decides whether the JWT-authenticated {@code actor} is allowed to act on {@code target}'s
* account. Default implementation is subject-scoped — a user only acts on themselves. Adapter
* modules can plug in support-staff-impersonation flows by overriding this SPI.
*
* @since 0.9.0
*/
@FunctionalInterface
public interface AdminAuthorizer {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
* AdminResultMapper}.
*
* @param <T> the payload type for {@link Success}.
* @since 0.9.0
*/
public sealed interface AdminResult<T> {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
*
* @param allowDeleteWithoutBackupCodes when true, callers can delete the last credential even when
* no backup codes remain. The brief recommends keeping this {@code false}.
* @since 0.9.0
*/
public record AdminSafetyConfig(boolean allowDeleteWithoutBackupCodes) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
* Framework-neutral admin operations on a user's pk-auth account (brief §6.9). All authenticated
* methods take an {@code actor} which the adapter resolves from the JWT subject and apply the
* configured {@link AdminAuthorizer} before touching the underlying SPIs.
*
* @since 0.9.0
*/
public interface AdminService {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
/**
* One-time-view payload from {@code POST /auth/admin/backup-codes/regenerate}. The plaintext codes
* are returned to the caller exactly once; pk-auth stores only Argon2id hashes server-side.
*
* @since 0.9.0
*/
public record BackupCodesGenerated(List<String> codes) {
public BackupCodesGenerated {
Expand Down
Loading
Loading