0Z4C0
// navigation
// @ozaco/server

Plugins

Web-layer plugins — authentication, CORS and OpenAPI/Swagger docs — that hook the gateway.

import from@ozaco/server/plugin/auth

These plugins extend the web layer by hooking the single Gateway protocol. Each is a std plugin you install once: it mounts routes with Gateway.actions.mount, and/or registers Gateway.before / Gateway.after hooks around the REST transformer and server lifecycle, so behaviour applies uniformly to every dispatch.

Authentication ships two interchangeable JWT strategies over one shared provider contract, plus request helpers for reading the bearer token inside an action. CORS is a zero-config header/preflight layer. Docs compiles your mounted services into an OpenAPI document and serves Swagger UI.

// additional entry points
  • @ozaco/server/plugin/authJWT authentication with two strategies (access+refresh and single-token session) over a pluggable provider, role/permission checks, email-style verification tokens and SSO.
  • @ozaco/server/plugin/corsCross-Origin Resource Sharing: applies CORS response headers and answers preflight `OPTIONS` requests, with flexible `origin` matching and credentials support.
  • @ozaco/server/plugin/docsGenerates an OpenAPI spec from mounted services and serves it plus Swagger UI at configurable routes, logging the URLs once the gateway starts.
// on this page
01

Auth

JWT authentication strategies, request helpers and shared types.

const

AccessRefreshAuth

Short-lived access token + long-lived, rotating refresh token strategy.

signature
// from '@ozaco/server/plugin/auth'
const AccessRefreshAuth // a std Plugin; install with AuthDef.AccessRefreshOptions

install(AccessRefreshAuth, options: AuthDef.AccessRefreshOptions)

AccessRefreshAuth.actions.provide(provider: AuthDef.Provider): Future<void>
AccessRefreshAuth.actions.signIn(credentials: unknown): Future<{
  user: AuthDef.User
  session: AuthDef.Session
} & AuthDef.AccessRefreshTokens>
AccessRefreshAuth.actions.refresh(refreshToken: string): Future<AuthDef.AccessRefreshTokens>
AccessRefreshAuth.actions.signOut(refreshToken: string): Future<void>
AccessRefreshAuth.actions.authorize(token: string): Future<AuthDef.Session>

Issues an access token (default TTL '15m') alongside a refresh token (default '7d', rotate defaulting to true) — refresh swaps the pair, atomically when the provider implements rotateRefreshToken and via non-atomic revoke+save otherwise. Signing uses secret (HMAC) or privateKey/publicKey (RS/ES/EdDSA PEM or CryptoKey; raw asymmetric bytes are rejected) with algorithm (default 'HS256').

You must call provide(provider) once to supply the AuthDef.Provider (authenticate, loadUser, refresh-token store, …). Beyond the token lifecycle the built plugin also exposes authorization checks (hasRole, hasPermission, requireRole, requirePermission), verification tokens (issueVerification, confirmVerification), SSO (ssoAuthorize, ssoCallback) and an events() emitter over AuthDef.Events.

example
import { install } from '@ozaco/std/plugin'
import { AccessRefreshAuth } from '@ozaco/server/plugin/auth'

yield* install(AccessRefreshAuth, {
  secret: process.env.JWT_SECRET,
  access: { expiresIn: '15m' },
  refresh: { expiresIn: '30d', rotate: true },
})
yield* AccessRefreshAuth.actions.provide(myProvider)

const { user, session, accessToken, refreshToken } =
  yield* AccessRefreshAuth.actions.signIn(credentials)
const

JWTSessionAuth

Classic single-token JWT session strategy (no refresh token).

signature
// from '@ozaco/server/plugin/auth'
const JWTSessionAuth // a std Plugin; install with AuthDef.JWTSessionOptions

install(JWTSessionAuth, options: AuthDef.JWTSessionOptions)

JWTSessionAuth.actions.provide(provider: AuthDef.Provider): Future<void>
JWTSessionAuth.actions.signIn(credentials: unknown): Future<{
  user: AuthDef.User
  session: AuthDef.Session
} & AuthDef.JWTSessionTokens>
JWTSessionAuth.actions.signOut(token: string): Future<void>
JWTSessionAuth.actions.authorize(token: string): Future<AuthDef.Session>

A single self-contained session token (default TTL '7d') — there is no refresh token, so signOut merely emits the signed-out event (stateless logout is handled client-side by dropping the token). It shares the same provider contract, signing options and the full authorization / verification / SSO / events() surface as AccessRefreshAuth; choose it when you do not need refresh-token rotation.

example
import { install } from '@ozaco/std/plugin'
import { JWTSessionAuth } from '@ozaco/server/plugin/auth'

yield* install(JWTSessionAuth, {
  secret: process.env.JWT_SECRET,
  session: { expiresIn: '7d' },
})
yield* JWTSessionAuth.actions.provide(myProvider)
fn

useAuth

Authorize the current request by verifying its bearer token against a strategy.

signature
function useAuth<T extends AuthDef.Strategy>(
  strategy: T,
): Operation<AuthDef.Session>

Reads the incoming request's Authorization: Bearer <token> header via getBearerToken, then calls strategy.actions.authorize(token) to verify and decode it into an AuthDef.Session. Use it inside an action to gate access; it fails with AuthErrorCode.MissingToken when no bearer token is present, and propagates the strategy's verification failures otherwise.

example
import { useAuth, AccessRefreshAuth } from '@ozaco/server/plugin/auth'
import { defineAction } from '@ozaco/server/core'

const me = defineAction(function* () {
  const session = yield* useAuth(AccessRefreshAuth)
  return session.user
})
fn

getBearerToken

Read the raw bearer token from the current request, failing if absent.

signature
function getBearerToken(): Operation<string>

Pulls the authorization (or Authorization) header off the request read via useRequest, strips the Bearer prefix and returns the token. Fails with AuthErrorCode.MissingToken when the header is missing or not a bearer token. useAuth builds on it; call it directly when you need the raw token rather than a verified session.

ns

AuthDef

The shared auth types: users, providers, sessions, tokens and per-strategy options.

signature
namespace AuthDef {
  interface User {
    id: string
    [key: string]: unknown
  }
  interface Session<TUser extends User = User> {
    sub: string
    jti: string
    iat: number
    exp: number
    type: string
    user: TUser
    roles: string[]
    permissions: string[]
  }
  interface Provider<TUser extends User = User, TCredentials = unknown> {
    authenticate(credentials: TCredentials): Operation<TUser | null>
    loadUser(userId: string): Operation<TUser | null>
    saveRefreshToken?(record: RefreshRecord): Operation<void>
    findRefreshToken?(jti: string): Operation<RefreshRecord | null>
    revokeRefreshToken?(jti: string): Operation<void>
    rotateRefreshToken?(oldJti: string, newRecord: RefreshRecord): Operation<void>
    getRoles?(user: TUser): Operation<string[]>
    getPermissions?(user: TUser): Operation<string[]>
    ssoProviders?: Record<string, SSOProvider>
    // ...verification + linkSSO hooks
  }
  interface AccessRefreshOptions extends BaseOptions {
    access?: { expiresIn?: string }
    refresh?: { expiresIn?: string; rotate?: boolean }
  }
  interface JWTSessionOptions extends BaseOptions {
    session?: { expiresIn?: string }
  }
  // also: BaseOptions, JWTAlgorithm, JWTKey, RefreshRecord, VerificationRecord,
  // SSOProvider, SSOProfile, Events, AccessRefreshTokens, JWTSessionTokens, Strategy
}

BaseOptions carries the signing material (secret for HMAC, or privateKey/publicKey for asymmetric), issuer, audience, algorithm and a verification.expiresIn. Events maps lifecycle events (signed-in, signed-out, refreshed, authorized, denied, verified, sso-linked) to their payloads. Strategy is the minimal { actions: { authorize } } shape useAuth accepts.

const

AuthErrorCode

The tag values auth failures are raised with.

signature
const AuthErrorCode: {
  InvalidCredentials: 'invalid-credentials'
  InvalidToken: 'invalid-token'
  ExpiredToken: 'expired-token'
  RevokedToken: 'revoked-token'
  MissingToken: 'missing-token'
  NotProvided: 'not-provided'
  UnknownProvider: 'unknown-provider'
  VerificationConsumed: 'verification-consumed'
  InvalidDuration: 'invalid-duration'
  InvalidState: 'invalid-state'
}

Auth actions yield* fail(AuthErrorCode.InvalidCredentials, message) (etc.) instead of throwing, so callers can pattern-match on the tag. NotProvided signals that an optional Provider hook required by the requested operation was not implemented.

02

CORS

Cross-Origin Resource Sharing headers and preflight.

const

Cors

Apply CORS response headers and answer preflight `OPTIONS` requests.

signature
// from '@ozaco/server/plugin/cors'
const Cors // a std Plugin; install with CorsDef.Options (no actions)

install(Cors, options?: CorsDef.Options)

On install, Cors mounts a catch-all preflight route and registers a Gateway.before({ fromInternal }) hook that writes the resolved CORS headers onto every response and short-circuits OPTIONS requests with preflightStatus. origin accepts '*', true, a string, a string list, a RegExp, or a predicate; set credentials: true to emit Access-Control-Allow-Credentials. methods, allowedHeaders, exposedHeaders and maxAge fill the remaining preflight headers. The plugin exposes no actions — configuration is entirely through install options.

example
import { install } from '@ozaco/std/plugin'
import { Cors } from '@ozaco/server/plugin/cors'

yield* install(Cors, {
  origin: ['https://app.example.com'],
  credentials: true,
  maxAge: '10m',
})
ns

CorsDef

Origin matcher, install options and resolved context for the CORS plugin.

signature
namespace CorsDef {
  type Origin =
    | '*'
    | true
    | string
    | readonly string[]
    | RegExp
    | ((origin: string) => boolean)
  interface Options {
    origin?: CorsDef.Origin
    methods?: readonly string[]
    allowedHeaders?: readonly string[]
    exposedHeaders?: readonly string[]
    credentials?: boolean
    maxAge?: number | string
    preflightStatus?: number
  }
  interface Context {
    origin: CorsDef.Origin
    methods: string
    allowedHeaders: string
    exposedHeaders: string | null
    credentials: boolean
    maxAge: string
    preflightStatus: number
  }
  interface ResolvedOrigin {
    allow: string | null
    vary: boolean
  }
}
03

Docs

OpenAPI generation and Swagger UI.

const

Docs

Compile mounted services into an OpenAPI spec and serve it plus Swagger UI.

signature
// from '@ozaco/server/plugin/docs'
const Docs // a std Plugin; install with DocsDef.Options

install(Docs, options?: DocsDef.Options)

Docs.actions.from(...services: Service[]): Future<void>

On install, Docs mounts two routes — the OpenAPI JSON at openapi (default /docs/openapi) and Swagger UI at swagger (default /docs/swagger) — and registers a Gateway.after({ start }) hook that logs both URLs once the server is listening (when a Logger is installed and silent is false). Call from(...services) to (re)compile the spec from the given services' action metadata; calling it again replaces the entries for those services and leaves others intact. auth (true or an AuthOptions) adds a security scheme to the document.

example
import { install } from '@ozaco/std/plugin'
import { Docs } from '@ozaco/server/plugin/docs'

yield* install(Docs, { title: 'My API', version: '1.0.0', auth: true })
yield* Docs.actions.from(UserService, OrderService)
// Swagger UI at /docs/swagger, spec at /docs/openapi
ns

DocsDef

Install options, context and the OpenAPI document model.

signature
namespace DocsDef {
  interface Options {
    title?: string // default 'Docs'
    version?: string // default '0.0.0'
    description?: string
    swagger?: string // default '/docs/swagger'
    openapi?: string // default '/docs/openapi'
    silent?: boolean // default false
    auth?: boolean | AuthOptions
  }
  interface AuthOptions {
    type?: 'bearer' | 'basic' | 'apiKey'
    name?: string
    in?: 'header' | 'query' | 'cookie'
    bearerFormat?: string
    description?: string
  }
  interface Context {
    title: string
    version: string
    description: string
    swagger: string
    openapi: string
    silent?: boolean
    auth: AuthOptions | null
  }
  // plus the OpenAPI model: OpenAPIDocument, OperationObject, ParameterObject,
  // JsonSchema, SecurityScheme, CompiledEntry, SwaggerHtmlOptions
}