import { type AuthBearerBootstrapResult, type AuthClientSession, type AuthBootstrapResult, type AuthPairingCredentialResult, type AuthSessionState, type AuthWebSocketTokenResult,} from "@t3tools/contracts";import { DateTime, Effect, Layer, Option } from "effect";import * as HttpServerRequest from "effect/unstable/http/HttpServerRequest"; import { AuthControlPlane } from "../Services/AuthControlPlane.ts";import { ServerAuthPolicyLive } from "./ServerAuthPolicy.ts";import { BootstrapCredentialService } from "../Services/BootstrapCredentialService.ts";import { BootstrapCredentialError } from "../Services/BootstrapCredentialService.ts";import { ServerAuthPolicy } from "../Services/ServerAuthPolicy.ts";import { ServerAuth, type AuthenticatedSession, AuthError, type ServerAuthShape,} from "../Services/ServerAuth.ts";import { SessionCredentialError, SessionCredentialService,} from "../Services/SessionCredentialService.ts";import { AuthControlPlaneLive, AuthCoreLive } from "./AuthControlPlane.ts"; type BootstrapExchangeResult = { readonly response: AuthBootstrapResult; readonly sessionToken: string;}; const AUTHORIZATION_PREFIX = "Bearer ";const WEBSOCKET_TOKEN_QUERY_PARAM = "wsToken"; export function toBootstrapExchangeAuthError(cause: BootstrapCredentialError): AuthError { if (cause.status === 500) { return new AuthError({ message: "Failed to validate bootstrap credential.", status: 500, cause, }); } return new AuthError({ message: "Invalid bootstrap credential.", status: 401, cause, });} function parseBearerToken(request: HttpServerRequest.HttpServerRequest): string | null { const header = request.headers["authorization"]; if (typeof header !== "string" || !header.startsWith(AUTHORIZATION_PREFIX)) { return null; } const token = header.slice(AUTHORIZATION_PREFIX.length).trim(); return token.length > 0 ? token : null;} export const makeServerAuth = Effect.gen(function* () { const policy = yield* ServerAuthPolicy; const bootstrapCredentials = yield* BootstrapCredentialService; const authControlPlane = yield* AuthControlPlane; const sessions = yield* SessionCredentialService; const descriptor = yield* policy.getDescriptor(); const authenticateToken = (token: string): Effect.Effect<AuthenticatedSession, AuthError> => sessions.verify(token).pipe( Effect.tapError((cause: SessionCredentialError) => Effect.logWarning("Rejected authenticated session credential.").pipe( Effect.annotateLogs({ reason: cause.message, }), ), ), Effect.map((session) => ({ sessionId: session.sessionId, subject: session.subject, method: session.method, role: session.role, ...(session.expiresAt ? { expiresAt: session.expiresAt } : {}), })), Effect.mapError( (cause) => new AuthError({ message: "Unauthorized request.", status: 401, cause, }), ), ); const authenticateRequest = (request: HttpServerRequest.HttpServerRequest) => { const cookieToken = request.cookies[sessions.cookieName]; const bearerToken = parseBearerToken(request); const credential = cookieToken ?? bearerToken; if (!credential) { return Effect.fail( new AuthError({ message: "Authentication required.", status: 401, }), ); } return authenticateToken(credential); }; const getSessionState: ServerAuthShape["getSessionState"] = (request) => authenticateRequest(request).pipe( Effect.map( (session) => ({ authenticated: true, auth: descriptor, role: session.role, sessionMethod: session.method, ...(session.expiresAt ? { expiresAt: DateTime.toUtc(session.expiresAt) } : {}), }) satisfies AuthSessionState, ), Effect.catchTag("AuthError", () => Effect.succeed({ authenticated: false, auth: descriptor, } satisfies AuthSessionState), ), ); const exchangeBootstrapCredential: ServerAuthShape["exchangeBootstrapCredential"] = ( credential, requestMetadata, ) => bootstrapCredentials.consume(credential).pipe( Effect.mapError(toBootstrapExchangeAuthError), Effect.flatMap((grant) => sessions .issue({ method: "browser-session-cookie", subject: grant.subject, role: grant.role, client: { ...requestMetadata, ...(grant.label ? { label: grant.label } : {}), }, }) .pipe( Effect.mapError( (cause) => new AuthError({ message: "Failed to issue authenticated session.", cause, }), ), ), ), Effect.map( (session) => ({ response: { authenticated: true, role: session.role, sessionMethod: session.method, expiresAt: DateTime.toUtc(session.expiresAt), } satisfies AuthBootstrapResult, sessionToken: session.token, }) satisfies BootstrapExchangeResult, ), ); const exchangeBootstrapCredentialForBearerSession: ServerAuthShape["exchangeBootstrapCredentialForBearerSession"] = (credential, requestMetadata) => bootstrapCredentials.consume(credential).pipe( Effect.mapError(toBootstrapExchangeAuthError), Effect.flatMap((grant) => sessions .issue({ method: "bearer-session-token", subject: grant.subject, role: grant.role, client: { ...requestMetadata, ...(grant.label ? { label: grant.label } : {}), }, }) .pipe( Effect.mapError( (cause) => new AuthError({ message: "Failed to issue authenticated session.", cause, }), ), ), ), Effect.map( (session) => ({ authenticated: true, role: session.role, sessionMethod: "bearer-session-token", expiresAt: DateTime.toUtc(session.expiresAt), sessionToken: session.token, }) satisfies AuthBearerBootstrapResult, ), ); const issuePairingCredential: ServerAuthShape["issuePairingCredential"] = (input) => authControlPlane .createPairingLink({ role: input?.role ?? "client", subject: input?.role === "owner" ? "owner-bootstrap" : "one-time-token", ...(input?.label ? { label: input.label } : {}), }) .pipe( Effect.mapError( (cause) => new AuthError({ message: "Failed to issue pairing credential.", cause, }), ), Effect.map( (issued) => ({ id: issued.id, credential: issued.credential, ...(issued.label ? { label: issued.label } : {}), expiresAt: issued.expiresAt, }) satisfies AuthPairingCredentialResult, ), ); const listPairingLinks: ServerAuthShape["listPairingLinks"] = () => authControlPlane .listPairingLinks({ role: "client", excludeSubjects: ["owner-bootstrap"], }) .pipe( Effect.mapError( (cause) => new AuthError({ message: "Failed to load pairing links.", cause, }), ), ); const revokePairingLink: ServerAuthShape["revokePairingLink"] = (id) => authControlPlane.revokePairingLink(id).pipe( Effect.mapError( (cause) => new AuthError({ message: "Failed to revoke pairing link.", cause, }), ), ); const listClientSessions: ServerAuthShape["listClientSessions"] = (currentSessionId) => authControlPlane.listSessions().pipe( Effect.mapError( (cause) => new AuthError({ message: "Failed to load paired clients.", cause, }), ), Effect.map((clientSessions) => clientSessions.map( (clientSession): AuthClientSession => ({ ...clientSession, current: clientSession.sessionId === currentSessionId, }), ), ), ); const revokeClientSession: ServerAuthShape["revokeClientSession"] = ( currentSessionId, targetSessionId, ) => Effect.gen(function* () { if (currentSessionId === targetSessionId) { return yield* new AuthError({ message: "Use revoke other clients to keep the current owner session active.", status: 403, }); } return yield* authControlPlane.revokeSession(targetSessionId).pipe( Effect.mapError( (cause) => new AuthError({ message: "Failed to revoke client session.", cause, }), ), ); }); const revokeOtherClientSessions: ServerAuthShape["revokeOtherClientSessions"] = ( currentSessionId, ) => authControlPlane.revokeOtherSessionsExcept(currentSessionId).pipe( Effect.mapError( (cause) => new AuthError({ message: "Failed to revoke other client sessions.", cause, }), ), ); const issueStartupPairingUrl: ServerAuthShape["issueStartupPairingUrl"] = (baseUrl) => issuePairingCredential({ role: "owner" }).pipe( Effect.map((issued) => { const url = new URL(baseUrl); url.pathname = "/pair"; url.searchParams.delete("token"); url.hash = new URLSearchParams([["token", issued.credential]]).toString(); return url.toString(); }), ); const issueWebSocketToken: ServerAuthShape["issueWebSocketToken"] = (session) => sessions.issueWebSocketToken(session.sessionId).pipe( Effect.mapError( (cause) => new AuthError({ message: "Failed to issue websocket token.", cause, }), ), Effect.map( (issued) => ({ token: issued.token, expiresAt: DateTime.toUtc(issued.expiresAt), }) satisfies AuthWebSocketTokenResult, ), ); const authenticateWebSocketUpgrade: ServerAuthShape["authenticateWebSocketUpgrade"] = (request) => Effect.gen(function* () { const requestUrl = HttpServerRequest.toURL(request); if (Option.isSome(requestUrl)) { const websocketToken = requestUrl.value.searchParams.get(WEBSOCKET_TOKEN_QUERY_PARAM); if (websocketToken && websocketToken.trim().length > 0) { return yield* sessions.verifyWebSocketToken(websocketToken).pipe( Effect.map((session) => ({ sessionId: session.sessionId, subject: session.subject, method: session.method, role: session.role, ...(session.expiresAt ? { expiresAt: session.expiresAt } : {}), })), Effect.mapError( (cause) => new AuthError({ message: "Unauthorized request.", status: 401, cause, }), ), ); } } return yield* authenticateRequest(request); }); return { getDescriptor: () => Effect.succeed(descriptor), getSessionState, exchangeBootstrapCredential, exchangeBootstrapCredentialForBearerSession, issuePairingCredential, listPairingLinks, revokePairingLink, listClientSessions, revokeClientSession, revokeOtherClientSessions, authenticateHttpRequest: authenticateRequest, authenticateWebSocketUpgrade, issueWebSocketToken, issueStartupPairingUrl, } satisfies ServerAuthShape;}); export const ServerAuthLive = Layer.effect(ServerAuth, makeServerAuth).pipe( Layer.provideMerge(AuthControlPlaneLive), Layer.provideMerge(AuthCoreLive), Layer.provideMerge(ServerAuthPolicyLive),);
import {
type AuthBearerBootstrapResult,
type AuthClientSession,
type AuthBootstrapResult,
type AuthPairingCredentialResult,
type AuthSessionState,
type AuthWebSocketTokenResult,
} from "@t3tools/contracts";
import { DateTime, Effect, Layer, Option } from "effect";
import * as HttpServerRequest from "effect/unstable/http/HttpServerRequest";
import { AuthControlPlane } from "../Services/AuthControlPlane.ts";
import { ServerAuthPolicyLive } from "./ServerAuthPolicy.ts";
import { BootstrapCredentialService } from "../Services/BootstrapCredentialService.ts";
import { BootstrapCredentialError } from "../Services/BootstrapCredentialService.ts";
import { ServerAuthPolicy } from "../Services/ServerAuthPolicy.ts";
import {
ServerAuth,
type AuthenticatedSession,
AuthError,
type ServerAuthShape,
} from "../Services/ServerAuth.ts";
import {
SessionCredentialError,
SessionCredentialService,
} from "../Services/SessionCredentialService.ts";
import { AuthControlPlaneLive, AuthCoreLive } from "./AuthControlPlane.ts";
type BootstrapExchangeResult = {
readonly response: AuthBootstrapResult;
readonly sessionToken: string;
};
const AUTHORIZATION_PREFIX = "Bearer ";
const WEBSOCKET_TOKEN_QUERY_PARAM = "wsToken";
export function toBootstrapExchangeAuthError(cause: BootstrapCredentialError): AuthError {
if (cause.status === 500) {
return new AuthError({
message: "Failed to validate bootstrap credential.",
status: 500,
cause,
});
}
return new AuthError({
message: "Invalid bootstrap credential.",
status: 401,
cause,
});
}
function parseBearerToken(request: HttpServerRequest.HttpServerRequest): string | null {
const header = request.headers["authorization"];
if (typeof header !== "string" || !header.startsWith(AUTHORIZATION_PREFIX)) {
return null;
}
const token = header.slice(AUTHORIZATION_PREFIX.length).trim();
return token.length > 0 ? token : null;
}
export const makeServerAuth = Effect.gen(function* () {
const policy = yield* ServerAuthPolicy;
const bootstrapCredentials = yield* BootstrapCredentialService;
const authControlPlane = yield* AuthControlPlane;
const sessions = yield* SessionCredentialService;
const descriptor = yield* policy.getDescriptor();
const authenticateToken = (token: string): Effect.Effect<AuthenticatedSession, AuthError> =>
sessions.verify(token).pipe(
Effect.tapError((cause: SessionCredentialError) =>
Effect.logWarning("Rejected authenticated session credential.").pipe(
Effect.annotateLogs({
reason: cause.message,
}),
),
),
Effect.map((session) => ({
sessionId: session.sessionId,
subject: session.subject,
method: session.method,
role: session.role,
...(session.expiresAt ? { expiresAt: session.expiresAt } : {}),
})),
Effect.mapError(
(cause) =>
new AuthError({
message: "Unauthorized request.",
status: 401,
cause,
}),
),
);
const authenticateRequest = (request: HttpServerRequest.HttpServerRequest) => {
const cookieToken = request.cookies[sessions.cookieName];
const bearerToken = parseBearerToken(request);
const credential = cookieToken ?? bearerToken;
if (!credential) {
return Effect.fail(
new AuthError({
message: "Authentication required.",
status: 401,
}),
);
}
return authenticateToken(credential);
};
const getSessionState: ServerAuthShape["getSessionState"] = (request) =>
authenticateRequest(request).pipe(
Effect.map(
(session) =>
({
authenticated: true,
auth: descriptor,
role: session.role,
sessionMethod: session.method,
...(session.expiresAt ? { expiresAt: DateTime.toUtc(session.expiresAt) } : {}),
}) satisfies AuthSessionState,
),
Effect.catchTag("AuthError", () =>
Effect.succeed({
authenticated: false,
auth: descriptor,
} satisfies AuthSessionState),
),
);
const exchangeBootstrapCredential: ServerAuthShape["exchangeBootstrapCredential"] = (
credential,
requestMetadata,
) =>
bootstrapCredentials.consume(credential).pipe(
Effect.mapError(toBootstrapExchangeAuthError),
Effect.flatMap((grant) =>
sessions
.issue({
method: "browser-session-cookie",
subject: grant.subject,
role: grant.role,
client: {
...requestMetadata,
...(grant.label ? { label: grant.label } : {}),
},
})
.pipe(
Effect.mapError(
(cause) =>
new AuthError({
message: "Failed to issue authenticated session.",
cause,
}),
),
),
),
Effect.map(
(session) =>
({
response: {
authenticated: true,
role: session.role,
sessionMethod: session.method,
expiresAt: DateTime.toUtc(session.expiresAt),
} satisfies AuthBootstrapResult,
sessionToken: session.token,
}) satisfies BootstrapExchangeResult,
),
);
const exchangeBootstrapCredentialForBearerSession: ServerAuthShape["exchangeBootstrapCredentialForBearerSession"] =
(credential, requestMetadata) =>
bootstrapCredentials.consume(credential).pipe(
Effect.mapError(toBootstrapExchangeAuthError),
Effect.flatMap((grant) =>
sessions
.issue({
method: "bearer-session-token",
subject: grant.subject,
role: grant.role,
client: {
...requestMetadata,
...(grant.label ? { label: grant.label } : {}),
},
})
.pipe(
Effect.mapError(
(cause) =>
new AuthError({
message: "Failed to issue authenticated session.",
cause,
}),
),
),
),
Effect.map(
(session) =>
({
authenticated: true,
role: session.role,
sessionMethod: "bearer-session-token",
expiresAt: DateTime.toUtc(session.expiresAt),
sessionToken: session.token,
}) satisfies AuthBearerBootstrapResult,
),
);
const issuePairingCredential: ServerAuthShape["issuePairingCredential"] = (input) =>
authControlPlane
.createPairingLink({
role: input?.role ?? "client",
subject: input?.role === "owner" ? "owner-bootstrap" : "one-time-token",
...(input?.label ? { label: input.label } : {}),
})
.pipe(
Effect.mapError(
(cause) =>
new AuthError({
message: "Failed to issue pairing credential.",
cause,
}),
),
Effect.map(
(issued) =>
({
id: issued.id,
credential: issued.credential,
...(issued.label ? { label: issued.label } : {}),
expiresAt: issued.expiresAt,
}) satisfies AuthPairingCredentialResult,
),
);
const listPairingLinks: ServerAuthShape["listPairingLinks"] = () =>
authControlPlane
.listPairingLinks({
role: "client",
excludeSubjects: ["owner-bootstrap"],
})
.pipe(
Effect.mapError(
(cause) =>
new AuthError({
message: "Failed to load pairing links.",
cause,
}),
),
);
const revokePairingLink: ServerAuthShape["revokePairingLink"] = (id) =>
authControlPlane.revokePairingLink(id).pipe(
Effect.mapError(
(cause) =>
new AuthError({
message: "Failed to revoke pairing link.",
cause,
}),
),
);
const listClientSessions: ServerAuthShape["listClientSessions"] = (currentSessionId) =>
authControlPlane.listSessions().pipe(
Effect.mapError(
(cause) =>
new AuthError({
message: "Failed to load paired clients.",
cause,
}),
),
Effect.map((clientSessions) =>
clientSessions.map(
(clientSession): AuthClientSession => ({
...clientSession,
current: clientSession.sessionId === currentSessionId,
}),
),
),
);
const revokeClientSession: ServerAuthShape["revokeClientSession"] = (
currentSessionId,
targetSessionId,
) =>
Effect.gen(function* () {
if (currentSessionId === targetSessionId) {
return yield* new AuthError({
message: "Use revoke other clients to keep the current owner session active.",
status: 403,
});
}
return yield* authControlPlane.revokeSession(targetSessionId).pipe(
Effect.mapError(
(cause) =>
new AuthError({
message: "Failed to revoke client session.",
cause,
}),
),
);
});
const revokeOtherClientSessions: ServerAuthShape["revokeOtherClientSessions"] = (
currentSessionId,
) =>
authControlPlane.revokeOtherSessionsExcept(currentSessionId).pipe(
Effect.mapError(
(cause) =>
new AuthError({
message: "Failed to revoke other client sessions.",
cause,
}),
),
);
const issueStartupPairingUrl: ServerAuthShape["issueStartupPairingUrl"] = (baseUrl) =>
issuePairingCredential({ role: "owner" }).pipe(
Effect.map((issued) => {
const url = new URL(baseUrl);
url.pathname = "/pair";
url.searchParams.delete("token");
url.hash = new URLSearchParams([["token", issued.credential]]).toString();
return url.toString();
}),
);
const issueWebSocketToken: ServerAuthShape["issueWebSocketToken"] = (session) =>
sessions.issueWebSocketToken(session.sessionId).pipe(
Effect.mapError(
(cause) =>
new AuthError({
message: "Failed to issue websocket token.",
cause,
}),
),
Effect.map(
(issued) =>
({
token: issued.token,
expiresAt: DateTime.toUtc(issued.expiresAt),
}) satisfies AuthWebSocketTokenResult,
),
);
const authenticateWebSocketUpgrade: ServerAuthShape["authenticateWebSocketUpgrade"] = (request) =>
Effect.gen(function* () {
const requestUrl = HttpServerRequest.toURL(request);
if (Option.isSome(requestUrl)) {
const websocketToken = requestUrl.value.searchParams.get(WEBSOCKET_TOKEN_QUERY_PARAM);
if (websocketToken && websocketToken.trim().length > 0) {
return yield* sessions.verifyWebSocketToken(websocketToken).pipe(
Effect.map((session) => ({
sessionId: session.sessionId,
subject: session.subject,
method: session.method,
role: session.role,
...(session.expiresAt ? { expiresAt: session.expiresAt } : {}),
})),
Effect.mapError(
(cause) =>
new AuthError({
message: "Unauthorized request.",
status: 401,
cause,
}),
),
);
}
}
return yield* authenticateRequest(request);
});
return {
getDescriptor: () => Effect.succeed(descriptor),
getSessionState,
exchangeBootstrapCredential,
exchangeBootstrapCredentialForBearerSession,
issuePairingCredential,
listPairingLinks,
revokePairingLink,
listClientSessions,
revokeClientSession,
revokeOtherClientSessions,
authenticateHttpRequest: authenticateRequest,
authenticateWebSocketUpgrade,
issueWebSocketToken,
issueStartupPairingUrl,
} satisfies ServerAuthShape;
});
export const ServerAuthLive = Layer.effect(ServerAuth, makeServerAuth).pipe(
Layer.provideMerge(AuthControlPlaneLive),
Layer.provideMerge(AuthCoreLive),
Layer.provideMerge(ServerAuthPolicyLive),
);