release: WRNexusJS 0.5.10
This commit is contained in:
@@ -9,7 +9,11 @@ import {
|
||||
getAuthUser,
|
||||
} from "../middleware.ts";
|
||||
import { resolveAuthSchemas, type AuthSchemaOverrides, type AuthSchemaSet } from "../validation.ts";
|
||||
import type { AuthSignedInHandler, AuthSignedOutHandler } from "../types.ts";
|
||||
import type {
|
||||
AuthSessionVerificationHandler,
|
||||
AuthSignedInHandler,
|
||||
AuthSignedOutHandler,
|
||||
} from "../types.ts";
|
||||
|
||||
function text(value: unknown): string {
|
||||
return typeof value === "string" ? value : value == null ? "" : String(value);
|
||||
@@ -53,6 +57,7 @@ export interface AuthHttpOptions {
|
||||
onSignedIn?: AuthSignedInHandler;
|
||||
/** @deprecated Prefer createAuthEngine({ onSignedOut }). */
|
||||
onSignedOut?: AuthSignedOutHandler;
|
||||
onSessionVerification?: AuthSessionVerificationHandler;
|
||||
}
|
||||
|
||||
export function createAuthHttpHandlers(options: AuthHttpOptions) {
|
||||
@@ -83,6 +88,21 @@ export function createAuthHttpHandlers(options: AuthHttpOptions) {
|
||||
}
|
||||
|
||||
return {
|
||||
async verifySession(ctx: Context): Promise<Response> {
|
||||
const user = getAuthUser(ctx);
|
||||
if (options.onSessionVerification) {
|
||||
return options.onSessionVerification(ctx, user);
|
||||
}
|
||||
if (!user) return json({ ok: false, error: "Unauthorized" }, 401);
|
||||
if ((ctx.req.headers.get("accept") ?? "").includes("application/json")) {
|
||||
return json({ ok: true, user });
|
||||
}
|
||||
return new Response(null, {
|
||||
status: 204,
|
||||
headers: { "cache-control": "no-store" },
|
||||
});
|
||||
},
|
||||
|
||||
async register(ctx: Context): Promise<Response> {
|
||||
const validation = await parseBody(schemas.register, ctx.req);
|
||||
if (!validation.ok) return validation.response;
|
||||
|
||||
@@ -12,9 +12,16 @@ function wantsJson(ctx: Context): boolean {
|
||||
return accept.includes("application/json") && !accept.includes("text/html");
|
||||
}
|
||||
|
||||
export function authSession(engine: AuthEngine): Middleware {
|
||||
export interface AuthSessionOptions {
|
||||
/** Optional shared SSO cookie containing an AuthEngine session id. */
|
||||
cookieName?: string;
|
||||
}
|
||||
|
||||
export function authSession(engine: AuthEngine, options: AuthSessionOptions = {}): Middleware {
|
||||
return async (ctx, next) => {
|
||||
const sessionId = ctx.session.get<string>(AUTH_SESSION_KEY);
|
||||
const sessionId =
|
||||
(options.cookieName ? ctx.cookies.get(options.cookieName) : undefined) ??
|
||||
ctx.session.get<string>(AUTH_SESSION_KEY);
|
||||
if (!sessionId) {
|
||||
ctx.user = null;
|
||||
ctx.locals.authUser = null;
|
||||
|
||||
@@ -4,7 +4,11 @@ import { fileURLToPath } from "node:url";
|
||||
import { definePlugin, type PluginContext } from "@wrnexus/plugin";
|
||||
import type { AuthEngine } from "./engine.ts";
|
||||
import type { AuthPasskeyHttpOptions } from "./http/index.ts";
|
||||
import type { AuthSignedInHandler, AuthSignedOutHandler } from "./types.ts";
|
||||
import type {
|
||||
AuthSessionVerificationHandler,
|
||||
AuthSignedInHandler,
|
||||
AuthSignedOutHandler,
|
||||
} from "./types.ts";
|
||||
import { AUTH_ROUTE_DEFINITIONS, type AuthRouteGroup } from "./routes/definitions.ts";
|
||||
import {
|
||||
clearDefaultAuthEngine,
|
||||
@@ -52,6 +56,10 @@ export interface AuthConfig {
|
||||
onSignedIn?: AuthSignedInHandler;
|
||||
/** @deprecated Prefer createAuthEngine({ onSignedOut }). */
|
||||
onSignedOut?: AuthSignedOutHandler;
|
||||
/** Shared SSO cookie whose value is an AuthEngine session id. */
|
||||
sessionCookieName?: string;
|
||||
/** Customize the package forward-auth verification response. */
|
||||
onSessionVerification?: AuthSessionVerificationHandler;
|
||||
}
|
||||
|
||||
/** Explicit plugin options remain supported for compatibility. Prefer config.auth. */
|
||||
@@ -88,14 +96,22 @@ interface ResolvedAuthConfig {
|
||||
passkey?: AuthPasskeyHttpOptions;
|
||||
onSignedIn?: AuthSignedInHandler;
|
||||
onSignedOut?: AuthSignedOutHandler;
|
||||
sessionCookieName?: string;
|
||||
onSessionVerification?: AuthSessionVerificationHandler;
|
||||
}
|
||||
|
||||
const packageRoot = dirname(dirname(fileURLToPath(import.meta.url)));
|
||||
const moduleRoot = dirname(fileURLToPath(import.meta.url));
|
||||
const packageRoot = dirname(moduleRoot);
|
||||
const compiledPackage = moduleRoot === join(packageRoot, "dist");
|
||||
const clientRuntime = join(packageRoot, "assets", "client", "auth.js");
|
||||
const migrationsFile = join(packageRoot, "migrations", "001_auth.sql");
|
||||
const otpPurposeMigrationFile = join(packageRoot, "migrations", "002_auth_otp_purpose.sql");
|
||||
const apiRoutesDir = join(packageRoot, "src", "routes", "api");
|
||||
const middlewareFile = join(packageRoot, "src", "routes", "middleware.ts");
|
||||
const apiRoutesDir = compiledPackage
|
||||
? join(moduleRoot, "routes", "api")
|
||||
: join(packageRoot, "src", "routes", "api");
|
||||
const middlewareFile = compiledPackage
|
||||
? join(moduleRoot, "routes", "middleware.js")
|
||||
: join(packageRoot, "src", "routes", "middleware.ts");
|
||||
const resolvedConfigKey = "@wrnexus/auth:resolved-config";
|
||||
|
||||
export function authComponentsDir(): string {
|
||||
@@ -104,7 +120,7 @@ export function authComponentsDir(): string {
|
||||
|
||||
function authApiRouteEntry(path: string): string {
|
||||
const routeName = path.replace(/^\/api\/auth\/?/, "").replace(/\//g, "-") || "index";
|
||||
return join(apiRoutesDir, routeName + ".ts");
|
||||
return join(apiRoutesDir, routeName + (compiledPackage ? ".js" : ".ts"));
|
||||
}
|
||||
|
||||
function resolveConfig(
|
||||
@@ -142,6 +158,8 @@ function resolveConfig(
|
||||
passkey: raw.passkey,
|
||||
onSignedIn: raw.onSignedIn ?? raw.engine?.onSignedIn,
|
||||
onSignedOut: raw.onSignedOut ?? raw.engine?.onSignedOut,
|
||||
sessionCookieName: raw.sessionCookieName,
|
||||
onSessionVerification: raw.onSessionVerification,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -377,6 +395,10 @@ export function authPlugin(options: AuthPluginOptions = {}) {
|
||||
onSignedIn: value.onSignedIn,
|
||||
|
||||
onSignedOut: value.onSignedOut,
|
||||
|
||||
sessionCookieName: value.sessionCookieName,
|
||||
|
||||
onSessionVerification: value.onSessionVerification,
|
||||
});
|
||||
|
||||
context.metadata.set("@wrnexus/auth:component-dir", value.componentDir);
|
||||
|
||||
@@ -62,6 +62,7 @@ function handlersFor(ctx: Context): AuthHttpHandlers | undefined {
|
||||
passkey: routeOptions.passkey,
|
||||
onSignedIn: routeOptions.onSignedIn,
|
||||
onSignedOut: routeOptions.onSignedOut,
|
||||
onSessionVerification: routeOptions.onSessionVerification,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import type { Context } from "@wrnexus/core";
|
||||
import { invokeAuthHandler } from "../api.ts";
|
||||
|
||||
export function GET(ctx: Context): Promise<Response> {
|
||||
return invokeAuthHandler("verifySession", ctx);
|
||||
}
|
||||
@@ -23,6 +23,12 @@ export interface AuthRouteDefinition {
|
||||
|
||||
/** Single source of truth for package-contributed auth endpoints. */
|
||||
export const AUTH_ROUTE_DEFINITIONS = [
|
||||
{
|
||||
path: "/api/auth/session/verify",
|
||||
group: "sessions",
|
||||
handler: "verifySession",
|
||||
methods: ["GET"],
|
||||
},
|
||||
{ path: "/api/auth/register", group: "registration", handler: "register", methods: ["POST"] },
|
||||
{ path: "/api/auth/login", group: "login", handler: "login", methods: ["POST"] },
|
||||
{ path: "/api/auth/logout", group: "login", handler: "logout", methods: ["POST"] },
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
import type { Middleware } from "@wrnexus/core";
|
||||
import { authSession } from "../middleware.ts";
|
||||
import { getDefaultAuthEngine, hasDefaultAuthEngine } from "../runtime.ts";
|
||||
import {
|
||||
getDefaultAuthEngine,
|
||||
getDefaultAuthRouteOptions,
|
||||
hasDefaultAuthEngine,
|
||||
} from "../runtime.ts";
|
||||
|
||||
/** Package middleware: hydrates auth state when a default engine is configured. */
|
||||
const middleware: Middleware = async (ctx, next) => {
|
||||
if (!hasDefaultAuthEngine()) return next();
|
||||
return authSession(getDefaultAuthEngine())(ctx, next);
|
||||
return authSession(getDefaultAuthEngine(), {
|
||||
cookieName: getDefaultAuthRouteOptions().sessionCookieName,
|
||||
})(ctx, next);
|
||||
};
|
||||
|
||||
export default middleware;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { Context } from "@wrnexus/core";
|
||||
import type { AuthEngine } from "./engine.ts";
|
||||
import type { AuthPasskeyHttpOptions } from "./http/index.ts";
|
||||
import type { AuthSessionVerificationHandler } from "./types.ts";
|
||||
import { resolveAuthSchemas, type AuthSchemaOverrides, type AuthSchemaSet } from "./validation.ts";
|
||||
|
||||
export interface DefaultAuthRouteOptions {
|
||||
@@ -11,6 +12,8 @@ export interface DefaultAuthRouteOptions {
|
||||
onSignedIn?: (ctx: Context, returnTo?: string) => Response | Promise<Response>;
|
||||
|
||||
onSignedOut?: (ctx: Context) => Response | Promise<Response>;
|
||||
sessionCookieName?: string;
|
||||
onSessionVerification?: AuthSessionVerificationHandler;
|
||||
}
|
||||
|
||||
interface AuthRuntimeState {
|
||||
|
||||
@@ -241,6 +241,12 @@ export type AuthSignedInHandler = (ctx: Context, returnTo?: string) => Response
|
||||
/** HTTP response hook invoked after the auth engine clears a signed-in session. */
|
||||
export type AuthSignedOutHandler = (ctx: Context) => Response | Promise<Response>;
|
||||
|
||||
/** Forward-auth response hook. A null user means the request is unauthenticated. */
|
||||
export type AuthSessionVerificationHandler = (
|
||||
ctx: Context,
|
||||
user: AuthPublicUser | null,
|
||||
) => Response | Promise<Response>;
|
||||
|
||||
export interface AuthSuccessfulSignUpAction {
|
||||
/**
|
||||
* Run the normal login policy with the newly registered credentials and
|
||||
|
||||
Reference in New Issue
Block a user