71 lines · 2.6 KB · TypeScript
RawDownload
import { Effect, Layer } from "effect";import { HttpRouter, HttpServerRequest, HttpServerResponse } from "effect/unstable/http"; import { AuthContext, requestScopedMiddleware } from "@executor-js/api/server"; import { UserStoreService } from "../auth/context";import { sessionFromSealed } from "../auth/middleware";import { ORG_SELECTOR_HEADER, authorizeOrganizationSelector } from "../auth/organization";import { WorkOSClient } from "../auth/workos";import { DbService } from "../db/db"; const unauthorized = () =>  HttpServerResponse.jsonUnsafe(    {      error: "Invalid or expired session",      code: "invalid_session",    },    { status: 401 },  ); const noOrganization = () =>  HttpServerResponse.jsonUnsafe(    {      error: "No organization in session",      code: "no_organization",    },    { status: 403 },  ); const OrgAuthMiddleware = HttpRouter.middleware<{ provides: AuthContext }>()(  Effect.gen(function* () {    const captured = yield* Effect.context<WorkOSClient>();    const workos = yield* WorkOSClient;    return (httpEffect) =>      Effect.gen(function* () {        const request = yield* HttpServerRequest.HttpServerRequest;        const cookieValue = request.cookies["wos-session"] ?? "";        const result = yield* workos          .authenticateSealedSession(cookieValue)          .pipe(Effect.orElseSucceed(() => null));        if (!result) return unauthorized();         // FAIL CLOSED: no header, no org — the sealed cookie's org is a        // browser-global pinned to whichever org WorkOS last touched, so        // falling back to it scopes a multi-org user's request to the WRONG        // org (see workos-auth-provider.resolveSessionPrincipal).        const selector = request.headers[ORG_SELECTOR_HEADER];        if (!selector) return noOrganization();         const org = yield* authorizeOrganizationSelector(result.userId, selector).pipe(          Effect.orElseSucceed(() => null),        );        if (!org) return noOrganization();         const session = sessionFromSealed(result, cookieValue);        const auth = AuthContext.of({          accountId: session.accountId,          organizationId: org.id,          email: session.email,          name: session.name,          avatarUrl: session.avatarUrl,          roles: [],        });         return yield* Effect.provideService(httpEffect, AuthContext, auth);      }).pipe(Effect.provideContext(captured));  }),); export const orgAuthMiddleware = (rsLive: Layer.Layer<DbService | UserStoreService>) =>  OrgAuthMiddleware.combine(requestScopedMiddleware(rsLive)).layer;