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
13 changes: 13 additions & 0 deletions .changeset/core-public-surface.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
"@seamless-auth/core": minor
---

Export the remaining handlers and verifiers from the package root.

The admin, session, internal-metrics, and system-config handlers, along with `verifySignedAuthResponse` and `verifyRefreshCookie`, were reachable only through a subpath import. Everything else came from the root, so which import an adapter needed depended on which handler it wanted. 27 names are now available from `@seamless-auth/core` as well.

Purely additive. Nothing is removed or renamed, the `./handlers/*` subpaths keep working, and a test asserts that a subpath import and a root import resolve to the same function rather than two copies.

The README's public API overview is rewritten to match, grouped by what an adapter author is looking for, and now covers the response contract, proxy, delivery, and contract-value exports added earlier in this epic that it had never listed.

Part of #72.
56 changes: 50 additions & 6 deletions packages/core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,21 +82,65 @@ Core helpers enforce transitions between these states.

## Public API (Overview)

Key exports include:
Everything below is available from the package root. The `./handlers/*` subpaths
remain for direct imports.

**Sessions and cookies**

- `ensureCookies(...)` – validates and refreshes session cookies
- `refreshAccessToken(...)` – rotates expired access sessions
- `verifyCookieJwt(...)` – verifies signed cookie payloads
- `createServiceToken(...)` – creates short-lived M2M assertions
- `verifyRefreshCookie(...)` – verifies a refresh cookie, returning `null` on failure
- `verifySignedAuthResponse(...)` – verifies an auth API response signature against its JWKS
- `getSeamlessUser(...)` – resolves the hydrated user, typed as `SeamlessUser | null`
- `hasScopedRole(...)` – checks scoped role grants such as `admin:read`

**Building an adapter**

- `applyResult(result, adapter, opts)` – turns a handler result into a response
- `applyCookies(result, adapter, opts)` – cookie instructions only, for middleware
- `ResponseAdapter` – the three methods an adapter provides: `setCookie`, `clearCookie`, `send`
- `proxyRequest(...)` – forwards a request to the auth API and returns its status and body
- `checkProxyIdentity(...)` – checks a request carries the session a proxied route requires
- `buildQueryString(...)` / `buildUpstreamUrl(...)` – build an upstream URL
- `signSessionCookie(...)` / `resolveCookieSameSite(...)` – cookie format and policy
- `authFetch(...)` – calls the auth API with the adapter's headers and a tolerant `json()`

**Auth flow handlers**

`loginHandler`, `finishLoginHandler`, `registerHandler`, `finishRegisterHandler`,
`logoutHandler`, `meHandler`, `requestOtpHandler`, `verifyLoginOtpHandler`,
`verifyRegistrationOtpHandler`, `requestMagicLinkHandler`, `verifyMagicLinkHandler`,
`pollMagicLinkConfirmationHandler`, `switchOrganizationHandler`,
`listOAuthProvidersHandler`, `startOAuthLoginHandler`, `finishOAuthLoginHandler`.

**Admin and operations handlers**

User, session, auth-event, metrics, and system-config handlers, for example
`getUsersHandler`, `updateUserHandler`, `listSessionsHandler`,
`getAuthEventsHandler`, `getDashboardMetricsHandler`, and
`getAvailableRolesHandler`.

**Message delivery**

- `deliverAuthMessage(...)` – delivers an auth message through the configured transports
- `applyExternalDelivery(...)` – delivers the payload on a response body and strips it
- `stripDelivery(...)` – removes the delivery payload from a body

**Auth API contract**

- `SERVICE_TOKEN_ISSUER` / `SERVICE_TOKEN_AUDIENCE` – the fixed identity for M2M service tokens
- `AUTH_DELIVERY_MODE_HEADER` / `EXTERNAL_DELIVERY_MODE` / `EXTERNAL_DELIVERY_HEADERS`
- `DEV_JWKS_KID` – the fallback key id, which is a misconfiguration to deploy on
- `createServiceToken(...)` / `buildExternalDeliveryAuthorization(...)` – mint service tokens

**Utilities**

- `assertSecretStrength(...)` / `assertSecrets(...)` – enforce the minimum secret length
- `redactSensitiveText(...)` – masks tokens, bearer values, and secrets before logging
- `listOAuthProvidersHandler(...)` – retrieves public OAuth provider metadata
- `startOAuthLoginHandler(...)` – starts an OAuth authorization-code login
- `finishOAuthLoginHandler(...)` – finishes OAuth login and returns cookie instructions

These functions return **descriptive results**, not HTTP responses.
Handlers return **descriptive results**, not HTTP responses. `applyResult` is what
turns one into a response, and it is the only place that decides how.

### Secret strength

Expand Down
6 changes: 6 additions & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ export * from "./authMessaging.js";
export * from "./deliverAuthMessage.js";
export * from "./ensureCookies.js";
export * from "./verifyCookieJwt.js";
export * from "./verifyRefreshCookie.js";
export * from "./verifySignedAuthResponse.js";
export * from "./refreshAccessToken.js";
export * from "./getSeamlessUser.js";
export * from "./createServiceToken.js";
Expand Down Expand Up @@ -34,3 +36,7 @@ export * from "./handlers/requestMagicLinkHandler.js";
export * from "./handlers/pollMagicLinkConfirmationHandler.js";
export * from "./handlers/switchOrganizationHandler.js";
export * from "./handlers/oauthHandlers.js";
export * from "./handlers/admin.js";
export * from "./handlers/sessions.js";
export * from "./handlers/internalMetrics.js";
export * from "./handlers/systemConfig.js";
124 changes: 118 additions & 6 deletions packages/core/tests/publicExports.test.js
Original file line number Diff line number Diff line change
@@ -1,49 +1,161 @@
// Named imports from the built dist, mirroring the "Public API (Overview)" README section.
// A missing named export fails this file at module link time, before any assertion runs.
import {
applyCookies,
applyExternalDelivery,
applyResult,
assertSecretStrength,
assertSecrets,
authFetch,
buildExternalDeliveryAuthorization,
buildQueryString,
buildUpstreamUrl,
checkProxyIdentity,
createServiceToken,
deliverAuthMessage,
ensureCookies,
finishLoginHandler,
finishOAuthLoginHandler,
finishRegisterHandler,
getAuthEventsHandler,
getAvailableRolesHandler,
getDashboardMetricsHandler,
getSeamlessUser,
getUsersHandler,
hasScopedRole,
listOAuthProvidersHandler,
listSessionsHandler,
loginHandler,
logoutHandler,
meHandler,
pollMagicLinkConfirmationHandler,
proxyRequest,
redactSensitiveText,
refreshAccessToken,
registerHandler,
requestMagicLinkHandler,
requestOtpHandler,
resolveCookieSameSite,
roleGrantsAccess,
signSessionCookie,
startOAuthLoginHandler,
stripDelivery,
switchOrganizationHandler,
updateUserHandler,
verifyCookieJwt,
verifyLoginOtpHandler,
verifyMagicLinkHandler,
verifyRefreshCookie,
verifyRegistrationOtpHandler,
verifySignedAuthResponse,
AUTH_DELIVERY_MODE_HEADER,
DEV_JWKS_KID,
EXTERNAL_DELIVERY_HEADERS,
EXTERNAL_DELIVERY_MODE,
SERVICE_TOKEN_AUDIENCE,
SERVICE_TOKEN_ISSUER,
} from "../dist/index.js";

const DOCUMENTED_EXPORTS = {
const DOCUMENTED_FUNCTIONS = {
applyCookies,
applyExternalDelivery,
applyResult,
assertSecretStrength,
assertSecrets,
authFetch,
buildExternalDeliveryAuthorization,
buildQueryString,
buildUpstreamUrl,
checkProxyIdentity,
createServiceToken,
deliverAuthMessage,
ensureCookies,
finishLoginHandler,
finishOAuthLoginHandler,
finishRegisterHandler,
getAuthEventsHandler,
getAvailableRolesHandler,
getDashboardMetricsHandler,
getSeamlessUser,
getUsersHandler,
hasScopedRole,
listOAuthProvidersHandler,
listSessionsHandler,
loginHandler,
logoutHandler,
meHandler,
pollMagicLinkConfirmationHandler,
proxyRequest,
redactSensitiveText,
refreshAccessToken,
registerHandler,
requestMagicLinkHandler,
requestOtpHandler,
resolveCookieSameSite,
roleGrantsAccess,
signSessionCookie,
startOAuthLoginHandler,
stripDelivery,
switchOrganizationHandler,
updateUserHandler,
verifyCookieJwt,
verifyLoginOtpHandler,
verifyMagicLinkHandler,
verifyRefreshCookie,
verifyRegistrationOtpHandler,
verifySignedAuthResponse,
};

const DOCUMENTED_CONSTANTS = {
AUTH_DELIVERY_MODE_HEADER,
DEV_JWKS_KID,
EXTERNAL_DELIVERY_MODE,
SERVICE_TOKEN_AUDIENCE,
SERVICE_TOKEN_ISSUER,
};

describe("@seamless-auth/core public exports", () => {
it.each(Object.keys(DOCUMENTED_EXPORTS))(
it.each(Object.keys(DOCUMENTED_FUNCTIONS))(
"exports %s as a named function",
(name) => {
expect(typeof DOCUMENTED_EXPORTS[name]).toBe("function");
expect(typeof DOCUMENTED_FUNCTIONS[name]).toBe("function");
},
);

it.each(Object.keys(DOCUMENTED_CONSTANTS))(
"exports %s as a named string constant",
(name) => {
expect(typeof DOCUMENTED_CONSTANTS[name]).toBe("string");
},
);

it("exports the external delivery headers as an object", () => {
expect(EXTERNAL_DELIVERY_HEADERS).toEqual({
[AUTH_DELIVERY_MODE_HEADER]: EXTERNAL_DELIVERY_MODE,
});
});

it("exposes every documented name on the module namespace", async () => {
const namespace = await import("../dist/index.js");
const missing = Object.keys(DOCUMENTED_EXPORTS).filter(
(name) => typeof namespace[name] !== "function",
);
const missing = [
...Object.keys(DOCUMENTED_FUNCTIONS),
...Object.keys(DOCUMENTED_CONSTANTS),
].filter((name) => namespace[name] === undefined);

expect(missing).toEqual([]);
});

// The subpaths predate the root exports and adopters import from them, so they
// have to keep resolving to the same functions.
it.each([
["admin", "getUsersHandler"],
["sessions", "listSessionsHandler"],
["internalMetrics", "getDashboardMetricsHandler"],
["systemConfig", "getAvailableRolesHandler"],
])("keeps handlers/%s reachable by subpath", async (module, exported) => {
const namespace = await import(`../dist/handlers/${module}.js`);
const root = await import("../dist/index.js");

expect(namespace[exported]).toBe(root[exported]);
});
});
Loading