first commit
This commit is contained in:
69
docs/api-stack/api/api-schemas/src/auth/context.md
Normal file
69
docs/api-stack/api/api-schemas/src/auth/context.md
Normal file
@@ -0,0 +1,69 @@
|
||||
# context
|
||||
|
||||
## Purpose
|
||||
|
||||
Defines the `AuthContext` Zod schema representing the authenticated caller's identity as it flows through a request lifecycle. After a server middleware validates the incoming access token, it extracts the user's identifier, email, roles, and optional tenant identifier into an `AuthContext` object. This schema ensures that the shape of that context is validated on construction and typed consistently across every server plugin, middleware, and service function that consumes it.
|
||||
|
||||
## Features
|
||||
|
||||
- `AuthContext` schema — `{userId, email, roles, tenantId?}` with strict Zod validation
|
||||
- `roles` as a typed `string[]` — supports role-based access control checks without custom parsing
|
||||
- Optional `tenantId` for multi-tenant deployments — present only when the tenant scope is known
|
||||
- Non-empty validation on all string fields to prevent blank identity tokens
|
||||
- Full TypeScript inference via `z.infer<>` — no manual interface duplication
|
||||
- Zod v4 compatible
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { AuthContext } from "@suaveplan/api-schemas/auth";
|
||||
|
||||
// Validate the decoded JWT claims after signature verification
|
||||
const ctx = AuthContext.parse({
|
||||
userId: "user_01J8ZGCZ7ECTFAKEUID123456",
|
||||
email: "alice@example.com",
|
||||
roles: ["member"],
|
||||
});
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { AuthContext } from "@suaveplan/api-schemas/auth";
|
||||
|
||||
// Middleware that builds the AuthContext from a validated JWT
|
||||
function buildAuthContext(claims: JwtClaims): AuthContext {
|
||||
return AuthContext.parse({
|
||||
userId: claims.sub,
|
||||
email: claims.email,
|
||||
roles: claims.roles ?? [],
|
||||
tenantId: claims.tenant_id,
|
||||
});
|
||||
}
|
||||
|
||||
// RBAC helper using the context
|
||||
function requireRole(ctx: AuthContext, role: string): void {
|
||||
if (!ctx.roles.includes(role)) {
|
||||
throw new Error("Forbidden");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `AuthContext`
|
||||
|
||||
Zod object schema — authenticated request context.
|
||||
|
||||
| Field | Type | Description |
|
||||
|------------|--------------------|-----------------------------------------------------|
|
||||
| `userId` | `string` (min 1) | Unique identifier of the authenticated user |
|
||||
| `email` | `string` (email) | Email address of the authenticated user |
|
||||
| `roles` | `string[]` | Role identifiers for RBAC checks |
|
||||
| `tenantId` | `string` (optional)| Tenant scope for multi-tenant deployments |
|
||||
|
||||
**Type export:** `type AuthContext = z.infer<typeof AuthContext>`
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
`AuthContext` does not include the raw token or session identifier intentionally — the context represents decoded, validated identity, not the credential itself. Services that need to revoke or refresh tokens should do so through a separate token store, not through the auth context. The `roles` array is left as a plain `string[]` to remain agnostic about whether roles are stored as flat strings, hierarchical paths, or RBAC/ABAC expressions; the data layer is responsible for populating it correctly.
|
||||
87
docs/api-stack/api/api-schemas/src/auth/login.md
Normal file
87
docs/api-stack/api/api-schemas/src/auth/login.md
Normal file
@@ -0,0 +1,87 @@
|
||||
# login
|
||||
|
||||
## Purpose
|
||||
|
||||
Defines Zod schemas for the login request and response shapes used by Suaveplan's authentication endpoints. These schemas serve as the single source of truth consumed by the server route handler for `POST /auth/login`, the React login mutation hook, and the OpenAPI document generator — ensuring every layer shares the same field names, validation rules, and TypeScript types without duplication.
|
||||
|
||||
## Features
|
||||
|
||||
- `LoginInput` schema — validates `{email, password}` credentials with RFC 5322 email validation
|
||||
- `LoginUser` embedded object schema — user identifier, email, display name, and roles array returned inside the login response
|
||||
- `LoginOutput` schema — wraps a short-lived access `token`, a long-lived `refreshToken`, and a `LoginUser` object
|
||||
- All fields non-empty with appropriate Zod refinements
|
||||
- Full TypeScript inference via `z.infer<>` — no manual interface duplication
|
||||
- Zod v4 compatible
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { LoginInput, LoginOutput } from "@suaveplan/api-schemas/auth";
|
||||
|
||||
// Validate the parsed request body
|
||||
const input = LoginInput.parse(req.body);
|
||||
// → { email: "user@example.com", password: "secret" }
|
||||
|
||||
// Validate the response before serialising to JSON
|
||||
const output = LoginOutput.parse({
|
||||
token: "eyJhbGciOiJFZERTQSJ9...",
|
||||
refreshToken: "rt_abc123",
|
||||
user: { id: "user_01", email: "user@example.com", name: "Alice", roles: ["member"] },
|
||||
});
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { LoginInput, LoginOutput } from "@suaveplan/api-schemas/auth";
|
||||
|
||||
async function handleLogin(rawBody: unknown): Promise<LoginOutput> {
|
||||
const { email, password } = LoginInput.parse(rawBody);
|
||||
const user = await authService.verifyPassword(email, password);
|
||||
const token = await tokenService.issueAccessToken(user.id);
|
||||
const refreshToken = await tokenService.issueRefreshToken(user.id);
|
||||
return LoginOutput.parse({ token, refreshToken, user });
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `LoginInput`
|
||||
|
||||
Zod object schema — login request body.
|
||||
|
||||
| Field | Type | Description |
|
||||
|------------|---------------------|---------------------------------|
|
||||
| `email` | `string` (email) | RFC 5322 valid email address |
|
||||
| `password` | `string` (min 1) | Plaintext password (TLS-only) |
|
||||
|
||||
**Type export:** `type LoginInput = z.infer<typeof LoginInput>`
|
||||
|
||||
### `LoginUser`
|
||||
|
||||
Zod object schema — user payload embedded in the login response.
|
||||
|
||||
| Field | Type | Description |
|
||||
|---------|--------------------|------------------------------------------|
|
||||
| `id` | `string` (min 1) | Unique user identifier |
|
||||
| `email` | `string` (email) | User email address |
|
||||
| `name` | `string` (min 1) | Display name |
|
||||
| `roles` | `string[]` | Array of role identifiers for this user |
|
||||
|
||||
**Type export:** `type LoginUser = z.infer<typeof LoginUser>`
|
||||
|
||||
### `LoginOutput`
|
||||
|
||||
Zod object schema — login success response.
|
||||
|
||||
| Field | Type | Description |
|
||||
|----------------|-----------------|-------------------------------------------|
|
||||
| `token` | `string` (min 1)| Short-lived access token |
|
||||
| `refreshToken` | `string` (min 1)| Long-lived refresh token |
|
||||
| `user` | `LoginUser` | Authenticated user details |
|
||||
|
||||
**Type export:** `type LoginOutput = z.infer<typeof LoginOutput>`
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The `password` field uses `z.string().min(1)` rather than a minimum-length policy constraint because the schema layer validates wire format, not security policy. Minimum password length requirements belong in the registration flow and are enforced by the auth service. The access token format (JWT, opaque, etc.) is also not validated here — `z.string().min(1)` accepts any non-empty token string, keeping the schema decoupled from the token implementation.
|
||||
65
docs/api-stack/api/api-schemas/src/auth/logout.md
Normal file
65
docs/api-stack/api/api-schemas/src/auth/logout.md
Normal file
@@ -0,0 +1,65 @@
|
||||
# logout
|
||||
|
||||
## Purpose
|
||||
|
||||
Provides Zod schemas for the logout request and response shapes used by Suaveplan's `POST /auth/logout` endpoint. Logout is a server-side operation: the client sends its refresh token so the server can revoke it, preventing silent background re-authentication after the user has explicitly signed out. These schemas are shared between the server route handler, React logout mutation hooks, and OpenAPI document generation.
|
||||
|
||||
## Features
|
||||
|
||||
- `LogoutInput` schema — accepts a `refreshToken` string (non-empty) for server-side revocation
|
||||
- `LogoutOutput` schema — returns `{success: true}` as a typed literal, making the success state unambiguous in TypeScript
|
||||
- `success` field uses `z.literal(true)` rather than `z.boolean()` so TypeScript narrows the type to `true` rather than `boolean`
|
||||
- Full TypeScript inference via `z.infer<>` — no manual interface duplication
|
||||
- Zod v4 compatible
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { LogoutInput, LogoutOutput } from "@suaveplan/api-schemas/auth";
|
||||
|
||||
// Validate the request body
|
||||
const input = LogoutInput.parse(req.body);
|
||||
// → { refreshToken: "rt_abc123" }
|
||||
|
||||
// Validate the response
|
||||
const output = LogoutOutput.parse({ success: true });
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { LogoutInput, LogoutOutput } from "@suaveplan/api-schemas/auth";
|
||||
|
||||
async function handleLogout(rawBody: unknown): Promise<LogoutOutput> {
|
||||
const { refreshToken } = LogoutInput.parse(rawBody);
|
||||
await sessionStore.revoke(refreshToken);
|
||||
// Also clear access token from an allowlist if using stateful tokens
|
||||
return LogoutOutput.parse({ success: true });
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `LogoutInput`
|
||||
|
||||
Zod object schema — logout request body.
|
||||
|
||||
| Field | Type | Description |
|
||||
|----------------|------------------|---------------------------------------------------|
|
||||
| `refreshToken` | `string` (min 1) | Refresh token to revoke on the server |
|
||||
|
||||
**Type export:** `type LogoutInput = z.infer<typeof LogoutInput>`
|
||||
|
||||
### `LogoutOutput`
|
||||
|
||||
Zod object schema — logout success response.
|
||||
|
||||
| Field | Type | Description |
|
||||
|-----------|----------------|------------------------------------------------|
|
||||
| `success` | `true` (literal)| Always `true` on a successful logout |
|
||||
|
||||
**Type export:** `type LogoutOutput = z.infer<typeof LogoutOutput>`
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
Using `z.literal(true)` rather than `z.boolean()` means TypeScript will infer `success: true` rather than `success: boolean`, enabling exhaustiveness checks in client code that branches on the success field. Logout endpoints conventionally return 204 No Content with no body, but Suaveplan uses a 200 with a JSON body to accommodate clients (such as React hooks) that expect a typed response. Clients that receive a 401 or 404 should still clear local tokens regardless of the server response.
|
||||
67
docs/api-stack/api/api-schemas/src/auth/refresh.md
Normal file
67
docs/api-stack/api/api-schemas/src/auth/refresh.md
Normal file
@@ -0,0 +1,67 @@
|
||||
# refresh
|
||||
|
||||
## Purpose
|
||||
|
||||
Defines Zod schemas for the token-refresh request and response shapes used by Suaveplan's `POST /auth/refresh` endpoint. The refresh flow implements token rotation — each use of a refresh token returns both a new access token and a new refresh token, invalidating the consumed one. These schemas are shared between the server route handler, the React auth hook that silently re-authenticates expiring sessions, and the OpenAPI generator.
|
||||
|
||||
## Features
|
||||
|
||||
- `RefreshInput` schema — accepts a single `refreshToken` string (non-empty)
|
||||
- `RefreshOutput` schema — returns a new `token` (access) and new `refreshToken` (rotation pattern)
|
||||
- Non-empty validation on both token fields to prevent silent empty-string bypasses
|
||||
- Full TypeScript inference via `z.infer<>` — no manual interface duplication
|
||||
- Zod v4 compatible
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { RefreshInput, RefreshOutput } from "@suaveplan/api-schemas/auth";
|
||||
|
||||
// Validate the request body
|
||||
const input = RefreshInput.parse(req.body);
|
||||
// → { refreshToken: "rt_abc123..." }
|
||||
|
||||
// Validate the response
|
||||
const output = RefreshOutput.parse({ token: "new-access-token", refreshToken: "new-rt_xyz" });
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { RefreshInput, RefreshOutput } from "@suaveplan/api-schemas/auth";
|
||||
|
||||
async function handleRefresh(rawBody: unknown): Promise<RefreshOutput> {
|
||||
const { refreshToken } = RefreshInput.parse(rawBody);
|
||||
const session = await sessionStore.consume(refreshToken); // invalidates old token
|
||||
const newAccess = await tokenService.issueAccessToken(session.userId);
|
||||
const newRefresh = await sessionStore.issue(session.userId);
|
||||
return RefreshOutput.parse({ token: newAccess, refreshToken: newRefresh });
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `RefreshInput`
|
||||
|
||||
Zod object schema — token refresh request body.
|
||||
|
||||
| Field | Type | Description |
|
||||
|----------------|------------------|---------------------------------------------------|
|
||||
| `refreshToken` | `string` (min 1) | Long-lived refresh token from a prior login/refresh|
|
||||
|
||||
**Type export:** `type RefreshInput = z.infer<typeof RefreshInput>`
|
||||
|
||||
### `RefreshOutput`
|
||||
|
||||
Zod object schema — token refresh success response.
|
||||
|
||||
| Field | Type | Description |
|
||||
|----------------|------------------|------------------------------------------------|
|
||||
| `token` | `string` (min 1) | New short-lived access token |
|
||||
| `refreshToken` | `string` (min 1) | New long-lived refresh token (rotation) |
|
||||
|
||||
**Type export:** `type RefreshOutput = z.infer<typeof RefreshOutput>`
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The output deliberately echoes the same shape as `LoginOutput`'s token fields, allowing React hooks to handle both login and refresh responses through a shared token-update function. Token rotation (issuing a new refresh token each time) is recommended by OAuth 2.0 Security Best Practices to limit the damage from a stolen refresh token. The server should record and compare the previous refresh token before issuing the new pair, rejecting any reuse attempts.
|
||||
74
docs/api-stack/api/api-schemas/src/auth/register.md
Normal file
74
docs/api-stack/api/api-schemas/src/auth/register.md
Normal file
@@ -0,0 +1,74 @@
|
||||
# register
|
||||
|
||||
## Purpose
|
||||
|
||||
Provides Zod schemas for the user registration request and response shapes used by Suaveplan's `POST /auth/register` endpoint. These schemas are the single source of truth for the server route validator, the React registration mutation hook, and the OpenAPI document generator, ensuring that field names, length constraints, and TypeScript types are consistent across every layer without duplication.
|
||||
|
||||
## Features
|
||||
|
||||
- `RegisterInput` schema — validates email (RFC 5322), password (min 8 chars), and display name (1–200 chars)
|
||||
- `RegisterOutput` schema — returns the new user's `id`, `email`, and ISO 8601 `createdAt` timestamp
|
||||
- Password minimum-length policy applied at schema layer (8 characters)
|
||||
- Name length capped at 200 characters to prevent database overflow
|
||||
- Full TypeScript inference via `z.infer<>` — no manual interface duplication
|
||||
- Zod v4 compatible
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { RegisterInput } from "@suaveplan/api-schemas/auth";
|
||||
|
||||
const input = RegisterInput.parse({
|
||||
email: "alice@example.com",
|
||||
password: "securePassword1",
|
||||
name: "Alice Smith",
|
||||
});
|
||||
// → { email: "alice@example.com", password: "securePassword1", name: "Alice Smith" }
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { RegisterInput, RegisterOutput } from "@suaveplan/api-schemas/auth";
|
||||
|
||||
async function handleRegister(rawBody: unknown): Promise<RegisterOutput> {
|
||||
const { email, password, name } = RegisterInput.parse(rawBody);
|
||||
const hashedPassword = await crypto.hashPassword(password);
|
||||
const user = await db.user.create({ data: { email, passwordHash: hashedPassword, name } });
|
||||
return RegisterOutput.parse({
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
createdAt: user.createdAt.toISOString(),
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `RegisterInput`
|
||||
|
||||
Zod object schema — registration request body.
|
||||
|
||||
| Field | Type | Description |
|
||||
|------------|---------------------------|----------------------------------------------|
|
||||
| `email` | `string` (email) | RFC 5322 valid email address |
|
||||
| `password` | `string` (min 8) | Plaintext password; minimum 8 characters |
|
||||
| `name` | `string` (min 1, max 200) | Display name for the new user |
|
||||
|
||||
**Type export:** `type RegisterInput = z.infer<typeof RegisterInput>`
|
||||
|
||||
### `RegisterOutput`
|
||||
|
||||
Zod object schema — successful registration response.
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------------|----------------------------------|----------------------------------------|
|
||||
| `id` | `string` (min 1) | Newly created user identifier |
|
||||
| `email` | `string` (email) | Email address of the new user |
|
||||
| `createdAt` | `string` (ISO 8601 with offset) | Account creation timestamp |
|
||||
|
||||
**Type export:** `type RegisterOutput = z.infer<typeof RegisterOutput>`
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The 8-character minimum on `password` is a deliberate schema-level policy choice for Suaveplan, aligning with NIST SP 800-63B guidance. Password complexity (uppercase, digits, special chars) is not enforced here — entropy-based policies are the responsibility of the auth service. The `createdAt` field uses `z.string().datetime({ offset: true })` rather than `z.date()` because JSON serialisation produces strings, not `Date` objects, and the schema layer should validate what actually travels over the wire.
|
||||
Reference in New Issue
Block a user