// ---------------------------------------------------------------------------// Organization authorization — live membership check against WorkOS.// ---------------------------------------------------------------------------//// The sealed session cookie carries an organizationId that WorkOS signed at// login / refresh time. WorkOS does NOT invalidate existing sessions when a// membership is revoked, and `session.authenticate()` validates the JWT// locally without hitting the API — so a removed user keeps full access// until their access token naturally expires (~10 min).//// To close that gap we verify membership live on every protected request.// `listUserMemberships` is one WorkOS call per request. If this becomes a// hot path we can layer a short per-(user, org) TTL cache underneath, or// swap it for a local memberships table fed by the WorkOS Events API.//// Returns the resolved organization (via resolveOrganization) if the user// currently holds an *active* membership in it, otherwise null. Callers// should treat null as "no access" and route accordingly (onboarding page /// 403).import { Effect } from "effect";import { resolveOrganization } from "./resolve-organization";import { WorkOSAuth } from "./workos";export const authorizeOrganization = (userId: string, organizationId: string) => Effect.gen(function* () { const workos = yield* WorkOSAuth; const memberships = yield* workos.listUserMemberships(userId); const active = memberships.data.find( (m: { readonly organizationId: string; readonly status: string }) => m.organizationId === organizationId && m.status === "active", ); if (!active) return null; return yield* resolveOrganization(organizationId); });
// ---------------------------------------------------------------------------
// Organization authorization — live membership check against WorkOS.
// ---------------------------------------------------------------------------
//
// The sealed session cookie carries an organizationId that WorkOS signed at
// login / refresh time. WorkOS does NOT invalidate existing sessions when a
// membership is revoked, and `session.authenticate()` validates the JWT
// locally without hitting the API — so a removed user keeps full access
// until their access token naturally expires (~10 min).
//
// To close that gap we verify membership live on every protected request.
// `listUserMemberships` is one WorkOS call per request. If this becomes a
// hot path we can layer a short per-(user, org) TTL cache underneath, or
// swap it for a local memberships table fed by the WorkOS Events API.
//
// Returns the resolved organization (via resolveOrganization) if the user
// currently holds an *active* membership in it, otherwise null. Callers
// should treat null as "no access" and route accordingly (onboarding page /
// 403).
import { Effect } from "effect";
import { resolveOrganization } from "./resolve-organization";
import { WorkOSAuth } from "./workos";
export const authorizeOrganization = (userId: string, organizationId: string) =>
Effect.gen(function* () {
const workos = yield* WorkOSAuth;
const memberships = yield* workos.listUserMemberships(userId);
const active = memberships.data.find(
(m: { readonly organizationId: string; readonly status: string }) =>
m.organizationId === organizationId && m.status === "active",
);
if (!active) return null;
return yield* resolveOrganization(organizationId);
});