@aglyn/shared-util-http
Small HTTP helpers for Fetch API and Next.js request handlers: CORS, request headers, an ID-token cookie, same-origin redirect checks, URL scheme allowlists, and an authenticated fetch. It is mainly a dependency of the larger Aglyn packages (@aglyn/aglyn, the plugins, the tenant runtime) and is usable on its own.
Published to npm from the Aglyn monorepo. Versions follow semantic versioning.
Install
npm install @aglyn/shared-util-http
Peer dependency: next (16.3.3), optional. You need it when you use the helpers that take Next.js objects (NextRequest, NextResponse, NextApiRequest), such as the cookie helpers. The subpath modules below use only web standards.
What's in it
From the package root:
- CORS:
corsHandleResponse(req, res, options?)andcorsBuildResponseHandler(options?), which curries a reusable configuration. Deny-by-default: with nooriginoption the response is returned untouched.credentialsis never paired with a*origin. PreflightOPTIONSrequests are answered for you unlesspreflightContinueis set. Lower-level pieces:getOriginHeaders,getOriginHeadersFromRequest,getAllowedHeaders,isOriginAllowed. getRequestHeader(request, name)- reads a header from either a FetchRequestor aNextApiRequest.getAbsoluteUrl(path, request?)- builds an absolute URL from thehostheader (server) orwindow.location(browser).cookieGetUserIdToken(request)/cookieSetUserIdToken(request, response, token, options?)- read and write the user ID-token cookie,httpOnlyby default.- Types:
CorsOptions,StaticOrigin,OriginFn.
By subpath only (not re-exported from the root):
@aglyn/shared-util-http/safe-redirect-isSameOriginPath(candidate)andsafeSameOriginPath(candidate, fallback = '/'). Instead of pattern-matching the string, they resolve it with the URL parser against a probe origin and accept it only if the origin is unchanged, which catches//host,/\host, and tab or newline tricks alike.@aglyn/shared-util-http/safe-url-scheme-hasSafeLinkScheme(value)(http,https,mailto,tel,sms, or no scheme) andhasSafeMediaScheme(value)(http,https, or no scheme). They check the scheme only; you still have to escape the value into the attribute afterwards.@aglyn/shared-util-http/bounded-await-boundedAwait(work, ms, fallback, label)andcreateSettledValueCache(). The first answers withwork's result, or withfallbackoncemshas passed, using a real timer rather than trusting an abort signal;workmay be a function of anAbortSignalthat aborts at the deadline. A deadline that wins writes one structuredconsole.warnper label a minute. The second is a TTL cache that holds settled values and refuses a promise, with areadThrough(key, load, ttlFor)that stores only onceloadhas settled.@aglyn/shared-util-http/authorized-token-resolveIdToken(user, options?),authorizedFetch(user, input, init?, options?),describeCallFailure(error, fallback),AuthorizationUnavailableError,ID_TOKEN_TIMEOUT_MS.useris anything with agetIdToken()method. The token wait has a deadline, and when no token can be hadauthorizedFetchsends nothing and resolves to a401-shaped response carrying the reason.
Usage
import { corsBuildResponseHandler } from '@aglyn/shared-util-http'
import { safeSameOriginPath } from '@aglyn/shared-util-http/safe-redirect'
const cors = corsBuildResponseHandler({
origin: ['https://app.example.com'],
credentials: true,
})
export async function GET(req: Request) {
return cors(req, Response.json({ ok: true }))
}
// Anything that would leave the origin becomes '/'.
const next = safeSameOriginPath(new URL(location.href).searchParams.get('next'))
How it fits
A shared package: generic, with no knowledge of Aglyn's model. Shared packages may only import other shared packages; this one depends on @aglyn/shared-data-enums (HTTP status codes and the cookie key). @aglyn/shared-util-next, @aglyn/shared-util-rest-api, @aglyn/shared-util-email, @aglyn/aglyn, the tenant runtime packages and most plugins depend on it.
License
Apache-2.0. Source: https://github.com/aglyn/aglyn/tree/main/libs/shared/util/http