page wrnexushelpers {
  seo {
    title = "@wrnexus/helpers"
    description = "Safe Context URL helpers and forward-auth login redirects."
  }

  view {
    <div class="docs-shell">
      <SkipLink label="Skip to content" href="#main" class="docs-skip-link" />
      <header class="topbar">
        <a class="brand" href="/"><span>W</span> WRNexusJS</a>
        <nav aria-label="Primary"><a href="/getting-started">Get started</a><a href="/packages">Packages</a><a href="/components">Components</a><a href="/language">Language</a><a href="/architecture">Architecture</a></nav>
        <div class="topbar-actions"><a class="preview-pill" href="/access">Private preview · v0.3.2</a><button data-wire-theme-toggle class="theme-button" aria-label="Toggle color theme" title="Toggle color theme">◐</button></div>
      </header>
      <div class="mobile-doc-nav"><details><summary>Browse documentation</summary><nav><a href="/getting-started">Get started</a><a href="/packages">Packages</a><a href="/components">Components</a><a href="/language">Language</a><a href="/architecture">Architecture</a><a href="/tutorial">Tutorial</a><a href="/guides/project-structure">Guides</a><a href="/examples">Examples</a><a href="/search">Search</a></nav></details></div>
      <main class="portal-main docs-layout">
      <article id="main" class="documentation prose standalone package-document"><nav class="breadcrumbs" aria-label="Breadcrumb"><a href="/">Home</a><span>/</span><a href="/packages">Packages</a><span>/</span><span aria-current="page">@wrnexus/helpers</span></nav><section class="doc-intro"><span class="eyebrow">Tooling · Package reference</span><h1>@wrnexus/helpers</h1><p>Safe Context URL helpers and forward-auth login redirects.</p><div class="doc-meta"><span>v0.3.2</span><span>Private registry</span><span>Tooling</span></div><section id="access" class="access-callout"><h2>Install the package</h2><p>After WorkRoot approves private registry access, install the release-aligned package:</p><pre><code>bun add @wrnexus/helpers@0.3.2</code><button type="button" class="copy-button" aria-label="Copy installation command">Copy</button></pre><p><a href="/access">Request preview access</a>. Never put registry tokens in source control.</p></section></section><section id="guide"><p>Safe convenience helpers for common WRNexusJS application flows. The package uses standard <code>Context</code>, <code>URL</code>, and <code>Response</code> values and has no runtime dependency beyond <code>@wrnexus/core</code>.</p>
<pre data-language="bash"><code>bun add @wrnexus/helpers</code></pre>
<p>The package is private, so the machine must be authenticated to the <code>wrnexus</code> npm organization.</p>
<h3 id="usage">Usage</h3>
<h4 id="redirect-an-unauthenticated-forward-auth-request">Redirect an unauthenticated forward-auth request</h4>
<p>The gateway calls an SSO verifier on a different URL from the original application. These helpers reconstruct the original URL from the gateway headers and safely place it in the login redirect:</p>
<pre data-language="ts"><code>import type &#123; Context &#125; from &quot;@wrnexus/core&quot;;
import &#123; redirectToLogin &#125; from &quot;@wrnexus/helpers&quot;;

export const GET = async (ctx: Context) =&gt; &#123;
  if (await hasValidSession(ctx)) &#123;
    return new Response(null, &#123; status: 204 &#125;);
  &#125;

  return redirectToLogin(ctx, &quot;/login&quot;, &#123;
    allowedHosts: [&quot;admin.localhost:3000&quot;, &quot;reports.localhost:3000&quot;],
  &#125;);
&#125;;</code></pre>
<p>This creates a response such as:</p>
<pre data-language="text"><code>Location: http://sso.localhost:3000/login?returnTo=http%3A%2F%2Fadmin.localhost%3A3000%2F</code></pre>
<p>Always list the application hosts that are valid redirect destinations. Forwarded host headers are rejected when <code>allowedHosts</code> is absent or does not match, preventing an open redirect.</p>
<p>The SSO hostname is the login destination, not an <code>allowedHosts</code> entry. For example, when protecting <code>admin.localhost:3000</code>, keep <code>admin.localhost:3000</code> in the allowlist even though the verifier runs at <code>sso.localhost:3000</code>. WRNexus preserves both hosts across a nested gateway request.</p>
<h4 id="support-dynamic-tenant-domains">Support dynamic tenant domains</h4>
<pre data-language="ts"><code>import type &#123; Context &#125; from &quot;@wrnexus/core&quot;;
import &#123; getOriginalRequestOrigin, redirectToLogin &#125; from &quot;@wrnexus/helpers&quot;;

export const GET = async (ctx: Context) =&gt; &#123;
  const allowedHosts = (host: string) =&gt; host === &quot;example.test&quot; || host.endsWith(&quot;.example.test&quot;);

  console.info(&quot;Authentication requested by&quot;, getOriginalRequestOrigin(ctx, &#123; allowedHosts &#125;));
  return redirectToLogin(ctx, &quot;https://auth.example.test/login&quot;, &#123;
    allowedHosts,
    returnToParam: &quot;continue&quot;,
    status: 303,
  &#125;);
&#125;;</code></pre>
<h3 id="api">API</h3>
<ul>
<li><code>getOriginalRequestUrl(ctx, options): URL</code> — reconstruct the gateway URL.</li>
<li><code>getOriginalRequestOrigin(ctx, options): string</code> — return only its origin.</li>
<li><code>getOriginalRequestPath(ctx): string</code> — return its path and query string.</li>
<li><code>getOriginalRequestMethod(ctx): string</code> — return its HTTP method.</li>
<li><code>redirectToLogin(ctx, loginUrl, options): Response</code> — create a login redirect with an</li>
<p>encoded <code>returnTo</code> parameter.</p>
</ul>
<p>For direct requests without gateway headers, URL helpers use <code>ctx.url</code>.</p></section><section id="api" class="api"><h2>Complete TypeScript API</h2><p>Generated from the exact installed package declarations.</p><pre data-language="typescript"><code>import &#123; Context &#125; from '@wrnexus/core';

declare function appOrigin(appName: string): string;
declare function appUrl(appName: string, path?: string): string;
declare function currentAppName(): string | undefined;
declare function currentAppOrigin(): string | undefined;
declare function workspaceAppOrigins(): Readonly&lt;Record&lt;string, string&gt;&gt;;

/**
 * @wrnexus/helpers — safe conveniences for common WRNexusJS application flows.
 *
 * Helpers stay small and composable. They accept the standard WRNexusJS Context
 * and return web-platform values such as URL and Response.
 */

type RequestContext = Pick&lt;Context, &quot;req&quot; | &quot;url&quot;&gt;;
type AllowedHosts = readonly string[] | ReadonlySet&lt;string&gt; | ((host: string, ctx: RequestContext) =&gt; boolean);
interface OriginalRequestOptions &#123;
    /**
     * Hosts that the application permits as redirect destinations. This is
     * required when a proxy supplied X-Forwarded-Host is present.
     */
    allowedHosts?: AllowedHosts;
&#125;
interface LoginRedirectOptions extends OriginalRequestOptions &#123;
    /** Query parameter that receives the original absolute URL. */
    returnToParam?: string;
    /** Browser redirect status. Defaults to 302. */
    status?: 301 | 302 | 303 | 307 | 308;
&#125;
/** Get the original path and query string seen by the gateway. */
declare function getOriginalRequestPath(ctx: RequestContext): string;
/** Get the original HTTP method seen by the gateway. */
declare function getOriginalRequestMethod(ctx: RequestContext): string;
/**
 * Reconstruct the absolute URL that reached the gateway.
 *
 * Forwarded hosts are never trusted implicitly: pass allowedHosts when this is
 * used behind the WRNexusJS gateway. Direct requests fall back to ctx.url.
 */
declare function getOriginalRequestUrl(ctx: RequestContext, options?: OriginalRequestOptions): URL;
/** Get the original request origin, for example http://admin.localhost:3000. */
declare function getOriginalRequestOrigin(ctx: RequestContext, options?: OriginalRequestOptions): string;
/**
 * Redirect to a login page with the original absolute URL encoded as returnTo.
 * Relative login URLs resolve against the current app (normally the SSO app).
 */
declare function redirectToLogin(ctx: RequestContext, loginUrl: string | URL, options?: LoginRedirectOptions): Response;

export &#123; type AllowedHosts, type LoginRedirectOptions, type OriginalRequestOptions, type RequestContext, appOrigin, appUrl, currentAppName, currentAppOrigin, getOriginalRequestMethod, getOriginalRequestOrigin, getOriginalRequestPath, getOriginalRequestUrl, redirectToLogin, workspaceAppOrigins &#125;;
</code></pre></section><section id="examples" class="examples"><h2>Examples</h2><p>Copy-ready examples from the installed package documentation.</p><div class="example-grid"><article class="example-card"><h3>Redirect an unauthenticated forward-auth request</h3><pre data-language="ts"><code>import type &#123; Context &#125; from &quot;@wrnexus/core&quot;;
import &#123; redirectToLogin &#125; from &quot;@wrnexus/helpers&quot;;

export const GET = async (ctx: Context) =&gt; &#123;
  if (await hasValidSession(ctx)) &#123;
    return new Response(null, &#123; status: 204 &#125;);
  &#125;

  return redirectToLogin(ctx, &quot;/login&quot;, &#123;
    allowedHosts: [&quot;admin.localhost:3000&quot;, &quot;reports.localhost:3000&quot;],
  &#125;);
&#125;;</code></pre></article><article class="example-card"><h3>Support dynamic tenant domains</h3><pre data-language="ts"><code>import type &#123; Context &#125; from &quot;@wrnexus/core&quot;;
import &#123; getOriginalRequestOrigin, redirectToLogin &#125; from &quot;@wrnexus/helpers&quot;;

export const GET = async (ctx: Context) =&gt; &#123;
  const allowedHosts = (host: string) =&gt; host === &quot;example.test&quot; || host.endsWith(&quot;.example.test&quot;);

  console.info(&quot;Authentication requested by&quot;, getOriginalRequestOrigin(ctx, &#123; allowedHosts &#125;));
  return redirectToLogin(ctx, &quot;https://auth.example.test/login&quot;, &#123;
    allowedHosts,
    returnToParam: &quot;continue&quot;,
    status: 303,
  &#125;);
&#125;;</code></pre></article></div></section></article>
      <aside class="on-this-page"><h2>On this page</h2><nav><a class="toc-level-2" href="#guide">Guide</a><a class="toc-level-3" href="#usage">Usage</a><a class="toc-level-4" href="#redirect-an-unauthenticated-forward-auth-request">Redirect an unauthenticated forward-auth request</a><a class="toc-level-4" href="#support-dynamic-tenant-domains">Support dynamic tenant domains</a><a class="toc-level-3" href="#api">API</a><a class="toc-level-2" href="#api">Complete API</a><a class="toc-level-2" href="#examples">Examples</a></nav></aside>
    </main>
      <footer><div class="footer-brand"><span class="footer-mark" aria-hidden="true">W</span><p><strong>WRNexusJS 0.3.2</strong><span>Complete API documentation generated from installed package declarations.</span></p></div><nav aria-label="Footer"><a href="/packages">All packages</a><a href="/getting-started">Get started</a><a href="/security">Security</a><a href="/support">Support</a><a href="/llms.txt">AI guide</a></nav><p class="footer-meta">Private Developer Preview · Bun-native</p></footer>
      <BackToTop />
    </div>
  }
}
