Initial Commit
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
# services/auth-service
|
||||
|
||||
## Purpose
|
||||
|
||||
The `auth-service` module owns the `AuthService` interface, its
|
||||
constructor (`createAuthService`), and the side-effect module
|
||||
augmentation that adds the `authService` slot to
|
||||
`@suaveplan/api-plugin/service-registry`'s `ServiceRegistry`. Together
|
||||
they let sibling plugins resolve the current principal and the
|
||||
underlying Better-Auth instance with full type safety — no casts at the
|
||||
call site.
|
||||
|
||||
## Features
|
||||
|
||||
- `AuthService` interface with three methods:
|
||||
`getCurrentPrincipal(c)`, `getBetterAuthInstance()`, and
|
||||
`getSession(headers)`.
|
||||
- `createAuthService` constructor wired to a Better-Auth instance and
|
||||
consumed exclusively by `createAuthPlugin`'s `onInit`.
|
||||
- Side-effect module augmentation in `./registry-augmentation.ts` so
|
||||
importing anything from `@suaveplan/api-plugin-auth` makes
|
||||
`ctx.getService("authService")` type-narrow to `AuthService |
|
||||
undefined`.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { createAuthService } from "@suaveplan/api-plugin-auth/services/auth-service";
|
||||
|
||||
const service = createAuthService({ auth });
|
||||
const principal = service.getCurrentPrincipal(c);
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
From a sibling plugin's `onInit`, resolve the service and use it to
|
||||
authenticate a stand-alone request (e.g. a queued job that carries a
|
||||
serialised cookie jar):
|
||||
|
||||
```ts
|
||||
import { definePlugin } from "@suaveplan/api-plugin";
|
||||
import type { Principal } from "@suaveplan/api-plugin-auth/principal/principal";
|
||||
|
||||
export const vaultPlugin = definePlugin({
|
||||
name: "vault",
|
||||
version: "0.1.0",
|
||||
dependencies: ["auth"],
|
||||
async onInit(ctx) {
|
||||
const authService = ctx.getService("authService");
|
||||
if (!authService) {
|
||||
throw new Error("authService not registered — is the auth plugin enabled?");
|
||||
}
|
||||
const headers = new Headers(savedCookieJar);
|
||||
const result = await authService.getSession(headers);
|
||||
if (result === null) return;
|
||||
// result.user / result.session are typed via BetterAuthSessionResult
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `interface AuthService`
|
||||
|
||||
- `getCurrentPrincipal(c: Context): Principal | null` — reads
|
||||
`c.get("user")`; returns `null` for anonymous requests.
|
||||
- `getBetterAuthInstance(): Auth` — returns the Better-Auth instance
|
||||
constructed by `createAuthPlugin`.
|
||||
- `getSession(headers: Headers): Promise<BetterAuthSessionResult>` —
|
||||
resolves a Better-Auth session from arbitrary request headers, useful
|
||||
outside the request scope.
|
||||
|
||||
### `type BetterAuthSessionResult`
|
||||
|
||||
`{ user: Record<string, unknown>; session: Record<string, unknown> } |
|
||||
null`.
|
||||
|
||||
### `interface CreateAuthServiceOptions`
|
||||
|
||||
`{ auth: Auth }`.
|
||||
|
||||
### `function createAuthService(options): AuthService`
|
||||
|
||||
Pure constructor — wraps the Better-Auth instance with the three-method
|
||||
service shape.
|
||||
|
||||
### Module augmentation
|
||||
|
||||
`./registry-augmentation.ts` declares
|
||||
`declare module "@suaveplan/api-plugin/service-registry" { interface
|
||||
ServiceRegistry { authService: AuthService } }`. Side-effect import.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- The service holds a reference to the Better-Auth instance — call
|
||||
sites that need the raw handler (e.g. the Hono catch-all route
|
||||
installed by `createAuthPlugin`) reach for
|
||||
`getBetterAuthInstance().handler` rather than re-constructing the
|
||||
Better-Auth client.
|
||||
- `BetterAuthSessionResult` is intentionally loose because Better-Auth
|
||||
plugins (organisations, passkeys, twoFactor) extend the `session` row
|
||||
with additional fields. The auth plugin's own `Principal` mapping
|
||||
decides which of those fields surface to consumers.
|
||||
- Session resolution is asynchronous; downstream plugins that want a
|
||||
synchronous principal-read use `getCurrentPrincipal(c)` from inside a
|
||||
Hono handler instead.
|
||||
Reference in New Issue
Block a user