diff --git a/apps/backend/src/policies/bot-policy.test.ts b/apps/backend/src/policies/bot-policy.test.ts index b539ffe89..1dc16b101 100644 --- a/apps/backend/src/policies/bot-policy.test.ts +++ b/apps/backend/src/policies/bot-policy.test.ts @@ -67,7 +67,6 @@ describe("BotPolicy", () => { }) const outsider = makeActor({ id: "00000000-0000-4000-8000-000000000403" as UserId, - organizationId: TEST_ORG_ID, }) const layer = makePolicyLayer( @@ -88,7 +87,7 @@ describe("BotPolicy", () => { const adminDenied = await runWithActorEither( BotPolicy.use((policy) => policy.canRead(BOT_ID)), layer, - makeActor({ ...admin, organizationId: TEST_ORG_ID }), + admin, ) const outsiderDenied = await runWithActorEither( BotPolicy.use((policy) => policy.canRead(BOT_ID)), diff --git a/apps/backend/src/policies/policy-test-helpers.ts b/apps/backend/src/policies/policy-test-helpers.ts index 73afa88be..0a0c517f5 100644 --- a/apps/backend/src/policies/policy-test-helpers.ts +++ b/apps/backend/src/policies/policy-test-helpers.ts @@ -20,7 +20,6 @@ export const makeActor = (overrides?: Partial): CurrentUser. role: "member", isOnboarded: true, timezone: "UTC", - organizationId: TEST_ORG_ID, settings: null, ...overrides, }) diff --git a/apps/backend/src/routes/api-v1/bot-auth.ts b/apps/backend/src/routes/api-v1/bot-auth.ts index d421f79ae..74817559f 100644 --- a/apps/backend/src/routes/api-v1/bot-auth.ts +++ b/apps/backend/src/routes/api-v1/bot-auth.ts @@ -57,7 +57,6 @@ export const botPrincipal = (bot: { email: `bot-${bot.name}@hazel.bot`, isOnboarded: true, timezone: null, - organizationId: null, settings: null, }), botScopes: bot.scopes === null ? null : bot.scopes.filter(isApiScope), diff --git a/apps/backend/src/routes/http-success-encoding.test.ts b/apps/backend/src/routes/http-success-encoding.test.ts index f332d1de1..d99463ce6 100644 --- a/apps/backend/src/routes/http-success-encoding.test.ts +++ b/apps/backend/src/routes/http-success-encoding.test.ts @@ -15,7 +15,6 @@ import { CurrentUser } from "@hazel/domain" const sessionUser = (id: UserId) => new CurrentUser.Schema({ id, - organizationId: null, role: "member", email: "caller@example.com", isOnboarded: true, diff --git a/apps/backend/src/routes/integrations.http.ts b/apps/backend/src/routes/integrations.http.ts index 4888f1245..f91e6810f 100644 --- a/apps/backend/src/routes/integrations.http.ts +++ b/apps/backend/src/routes/integrations.http.ts @@ -21,6 +21,7 @@ import { Cause, Config, DateTime, Effect, Layer, Option, Schedule, Schema, Schem import * as Duration from "effect/Duration" import { HazelApi } from "../api" import { Directory } from "../services/directory" +import { requireMember } from "./integration-resources.http" import { OAuthProviderRegistry } from "../services/oauth" import { callObject } from "../worker/objects/calls" import { orgTarget } from "../worker/objects/kinds" @@ -270,14 +271,7 @@ const handleGetOAuthUrl = Effect.fn("integrations.getOAuthUrl")(function* ( const { orgId, provider } = path const level = query.level ?? "organization" - if (!currentUser.organizationId || currentUser.organizationId !== orgId) { - return yield* Effect.fail( - new UnauthorizedError({ - message: "You are not authorized to access this organization", - detail: `organizationId=${orgId}`, - }), - ) - } + yield* requireMember(orgId) // Get the OAuth provider from registry const registry = yield* OAuthProviderRegistry @@ -913,14 +907,7 @@ const handleGetConnectionStatus = Effect.fn("integrations.getConnectionStatus")( // A T3 Code link is a member's own, with its own routes (`./t3code.http.ts`). if (provider === "t3code") return yield* Effect.fail(new UnsupportedProviderError({ provider })) - if (!currentUser.organizationId || currentUser.organizationId !== orgId) { - return yield* Effect.fail( - new UnauthorizedError({ - message: "You are not authorized to access this organization", - detail: `organizationId=${orgId}`, - }), - ) - } + yield* requireMember(orgId) const connectionOption = Option.fromNullishOr( yield* callObject(IntegrationConnectionCall, orgTarget(orgId), { @@ -967,14 +954,7 @@ const handleDisconnect = Effect.fn("integrations.disconnect")(function* ( // A T3 Code link is a member's own, with its own routes (`./t3code.http.ts`). if (provider === "t3code") return yield* Effect.fail(new UnsupportedProviderError({ provider })) - if (!currentUser.organizationId || currentUser.organizationId !== orgId) { - return yield* Effect.fail( - new UnauthorizedError({ - message: "You are not authorized to access this organization", - detail: `organizationId=${orgId}`, - }), - ) - } + yield* requireMember(orgId) // Tokens, the connection and (for a user link) re-attribution, in the org's object. const connection = yield* callObject(DisconnectIntegrationCall, orgTarget(orgId), { diff --git a/apps/backend/src/routes/object-routes.test.ts b/apps/backend/src/routes/object-routes.test.ts index 97a3d8cee..794ab6a2a 100644 --- a/apps/backend/src/routes/object-routes.test.ts +++ b/apps/backend/src/routes/object-routes.test.ts @@ -190,7 +190,6 @@ const invokeAs = (object: TestObject, objectId: string, tag: HazelRpcTag, payloa principal: yield* encodeInvokePrincipal({ user: new CurrentUser.Schema({ id: userId, - organizationId: null, role: "member", email: `${userId}@example.com`, isOnboarded: true, diff --git a/apps/backend/src/routes/t3code.http.ts b/apps/backend/src/routes/t3code.http.ts index fee5b6109..7488aca10 100644 --- a/apps/backend/src/routes/t3code.http.ts +++ b/apps/backend/src/routes/t3code.http.ts @@ -42,6 +42,7 @@ import { UpdateT3CodeSharingCall, } from "../worker/objects/org/t3code" import { validateBotToken } from "./bot-commands.http" +import { requireMember } from "./integration-resources.http" /** The seeded T3 Code bot's directory user (`IntegrationBotService`); the only caller of `getT3CodeAccess`. */ export const T3CODE_BOT_EXTERNAL_ID = "internal-bot-t3code" @@ -110,17 +111,8 @@ const environmentConfig = Effect.gen(function* () { const redirectUriOf = (apiBaseUrl: string) => `${new URL(apiBaseUrl).origin}/integrations/t3code/callback` -const requireOrg = (orgId: OrganizationId) => - Effect.gen(function* () { - const currentUser = yield* CurrentUser.Context - if (currentUser.organizationId !== orgId) { - return yield* new UnauthorizedError({ - message: "You are not authorized to access this organization", - detail: `organizationId=${orgId}`, - }) - } - return currentUser - }) +/** The caller, once they are an active member of `orgId` (sessions carry no organization). */ +const requireOrg = (orgId: OrganizationId) => requireMember(orgId).pipe(Effect.andThen(CurrentUser.Context)) const toDateTime = (value: Date | DateTime.Utc) => DateTime.isDateTime(value) ? value : DateTime.fromDateUnsafe(value) diff --git a/apps/backend/src/rpc/handlers/bots.ts b/apps/backend/src/rpc/handlers/bots.ts index 59791f32d..042ffd0b8 100644 --- a/apps/backend/src/rpc/handlers/bots.ts +++ b/apps/backend/src/rpc/handlers/bots.ts @@ -58,11 +58,9 @@ const generateBotToken = Effect.promise(async () => { return { token, tokenHash: Hex.encode(new Uint8Array(digest)) } }) -/** The org a bot call acts in: the routing hint, else the caller's active org. */ -const activeOrganization = (hint: OrganizationId | undefined) => +/** The org a bot call acts in: the routing hint. */ +const activeOrganization = (organizationId: OrganizationId | undefined) => Effect.gen(function* () { - const user = yield* CurrentUser.Context - const organizationId = hint ?? user.organizationId ?? undefined // The router sends calls with an org to its OrgObject; anywhere else there is none. const object = yield* currentObject if (organizationId === undefined || Option.isNone(object) || object.value.kind !== "org") { @@ -317,21 +315,19 @@ export const BotRpcLive = BotRpcs.toLayer( "bot.listPublic": ({ search, organizationId }) => Effect.gen(function* () { - const user = yield* CurrentUser.Context const botRepo = yield* BotRepo const userRepo = yield* UserRepo const directory = yield* Directory const publicBots = yield* botRepo.findPublic(search) // Installed here = the bot user is an active member (`membership_index`). - const orgId = organizationId ?? user.organizationId ?? undefined const installed = new Set() - if (orgId !== undefined && publicBots.length > 0) { + if (organizationId !== undefined && publicBots.length > 0) { const rows = yield* directory.db.run( SQLite.from(schema.membershipIndexTable) .select("userId") .where(($) => [ - $.organizationId.eq(orgId), + $.organizationId.eq(organizationId), $.deletedAt.isNull(), $.userId.in_(...publicBots.map((bot) => bot.userId)), ]), diff --git a/apps/backend/src/rpc/middleware/auth.test.ts b/apps/backend/src/rpc/middleware/auth.test.ts index 067b253e1..904faaf43 100644 --- a/apps/backend/src/rpc/middleware/auth.test.ts +++ b/apps/backend/src/rpc/middleware/auth.test.ts @@ -32,7 +32,6 @@ const makeCurrentUser = ( role: "member", isOnboarded: true, timezone: "UTC", - organizationId: null, settings: null, ...overrides, }) diff --git a/apps/backend/src/rpc/middleware/auth.ts b/apps/backend/src/rpc/middleware/auth.ts index ce9769bd6..221fb1d98 100644 --- a/apps/backend/src/rpc/middleware/auth.ts +++ b/apps/backend/src/rpc/middleware/auth.ts @@ -133,7 +133,6 @@ export const AuthMiddlewareLive = Layer.effect( role: "member", isOnboarded: true, timezone: user.timezone, - organizationId: null, settings: user.settings, } diff --git a/apps/backend/src/rpc/per-request.test.ts b/apps/backend/src/rpc/per-request.test.ts index 26ea158a7..4ccd7d442 100644 --- a/apps/backend/src/rpc/per-request.test.ts +++ b/apps/backend/src/rpc/per-request.test.ts @@ -46,7 +46,6 @@ const requestScheduler = (label: string): Scheduler.Scheduler => ({ const user = new CurrentUser.Schema({ id: "00000000-0000-4000-8000-000000000001" as UserId, - organizationId: null, role: "member", email: "me@example.com", isOnboarded: true, diff --git a/apps/backend/src/rpc/routing.ts b/apps/backend/src/rpc/routing.ts index f6e31ea3d..3fb7a531a 100644 --- a/apps/backend/src/rpc/routing.ts +++ b/apps/backend/src/rpc/routing.ts @@ -91,13 +91,11 @@ const orgHint = (method: string) => (payload: { readonly organizationId?: Organi Effect.flatMap(hint(method, "organizationId", payload.organizationId), org) /** - * The org a bot call acts in: the payload's `organizationId`, else the caller's active org; with - * neither, the directory path (which answers `ActiveOrganizationRequiredError`, or `[]`). + * The org a bot call acts in: the payload's `organizationId`; without one, the directory path + * (which answers `ActiveOrganizationRequiredError`, or `[]`). */ -const activeOrg = (payload: { readonly organizationId?: OrganizationId | undefined }, current: User) => { - const organizationId = payload.organizationId ?? current.organizationId ?? undefined - return organizationId === undefined ? directory() : org(organizationId) -} +const activeOrg = (payload: { readonly organizationId?: OrganizationId | undefined }) => + payload.organizationId === undefined ? directory() : org(payload.organizationId) const byOrg = (payload: { readonly organizationId: OrganizationId }) => org(payload.organizationId) const byChannel = (payload: { readonly channelId: ChannelId }) => channel(payload.channelId) diff --git a/apps/backend/src/services/chat-sync/chat-sync.objects.test.ts b/apps/backend/src/services/chat-sync/chat-sync.objects.test.ts index 0b80ac83a..97201633a 100644 --- a/apps/backend/src/services/chat-sync/chat-sync.objects.test.ts +++ b/apps/backend/src/services/chat-sync/chat-sync.objects.test.ts @@ -39,7 +39,6 @@ const DISCORD_CHANNEL = "222222222222222222" as ExternalChannelId const userOf = (id: string) => new CurrentUser.Schema({ id: id as UserId, - organizationId: null, role: "member", email: `${id}@example.com`, isOnboarded: true, diff --git a/apps/backend/src/services/hazel-session.ts b/apps/backend/src/services/hazel-session.ts index 7c5b5d29f..0e68a384f 100644 --- a/apps/backend/src/services/hazel-session.ts +++ b/apps/backend/src/services/hazel-session.ts @@ -59,7 +59,6 @@ export class HazelSession extends Context.Service()("@hazel/backen new CurrentUser.Schema({ id: user.id, role: "member", - organizationId: undefined, avatarUrl: user.avatarUrl ?? undefined, firstName: user.firstName, lastName: user.lastName, diff --git a/apps/backend/src/services/invitations.test.ts b/apps/backend/src/services/invitations.test.ts index 8d1640651..9953ce189 100644 --- a/apps/backend/src/services/invitations.test.ts +++ b/apps/backend/src/services/invitations.test.ts @@ -31,7 +31,6 @@ const actor = (userId: UserId) => new CurrentUser.Schema({ id: userId, role: "member", - organizationId: undefined, avatarUrl: undefined, firstName: "Test", lastName: "User", diff --git a/apps/backend/src/services/oauth-bearer-auth.ts b/apps/backend/src/services/oauth-bearer-auth.ts index 7a4e46772..124202f5e 100644 --- a/apps/backend/src/services/oauth-bearer-auth.ts +++ b/apps/backend/src/services/oauth-bearer-auth.ts @@ -112,7 +112,6 @@ export class OAuthBearerAuth extends Context.Service()("@hazel/ currentUser: new CurrentUser.Schema({ id: user.id, role: "member", - organizationId: undefined, avatarUrl: user.avatarUrl ?? undefined, firstName: user.firstName, lastName: user.lastName, diff --git a/apps/backend/src/worker/live/live.test.ts b/apps/backend/src/worker/live/live.test.ts index 313697526..06c349e01 100644 --- a/apps/backend/src/worker/live/live.test.ts +++ b/apps/backend/src/worker/live/live.test.ts @@ -32,7 +32,6 @@ const run = (effect: Effect.Effect) => const principalOf = (id: UserId): InvokePrincipal => ({ user: new CurrentUser.Schema({ id, - organizationId: null, role: "member", email: `${id}@example.com`, isOnboarded: true, diff --git a/apps/backend/src/worker/objects/channel-messages.test.ts b/apps/backend/src/worker/objects/channel-messages.test.ts index 795575069..ef4557a5f 100644 --- a/apps/backend/src/worker/objects/channel-messages.test.ts +++ b/apps/backend/src/worker/objects/channel-messages.test.ts @@ -18,7 +18,6 @@ const uuid = () => crypto.randomUUID() const userOf = (id: string) => new CurrentUser.Schema({ id: id as UserId, - organizationId: null, role: "member", email: `${id}@example.com`, isOnboarded: true, diff --git a/apps/backend/src/worker/objects/connect-objects.test.ts b/apps/backend/src/worker/objects/connect-objects.test.ts index ecb606e17..20c3c5dfb 100644 --- a/apps/backend/src/worker/objects/connect-objects.test.ts +++ b/apps/backend/src/worker/objects/connect-objects.test.ts @@ -23,7 +23,6 @@ const uuid = () => crypto.randomUUID() const userOf = (id: string) => new CurrentUser.Schema({ id: id as UserId, - organizationId: null, role: "member", email: `${id}@example.com`, isOnboarded: true, diff --git a/apps/backend/src/worker/objects/cross-org.test.ts b/apps/backend/src/worker/objects/cross-org.test.ts index 4e4a0e4d7..2ce295318 100644 --- a/apps/backend/src/worker/objects/cross-org.test.ts +++ b/apps/backend/src/worker/objects/cross-org.test.ts @@ -23,7 +23,6 @@ const uuid = () => crypto.randomUUID() const userOf = (id: string) => new CurrentUser.Schema({ id: id as UserId, - organizationId: null, role: "member", email: `${id}@example.com`, isOnboarded: true, diff --git a/apps/backend/src/worker/objects/directory-path.test.ts b/apps/backend/src/worker/objects/directory-path.test.ts index bdd071401..3d86372e5 100644 --- a/apps/backend/src/worker/objects/directory-path.test.ts +++ b/apps/backend/src/worker/objects/directory-path.test.ts @@ -25,7 +25,6 @@ const uuid = () => crypto.randomUUID() const userOf = (id: string, email = `${id}@example.com`) => new CurrentUser.Schema({ id: id as UserId, - organizationId: null, role: "member", email, isOnboarded: true, diff --git a/apps/backend/src/worker/objects/html-renders.test.ts b/apps/backend/src/worker/objects/html-renders.test.ts index f0eb8d6a9..dd883371c 100644 --- a/apps/backend/src/worker/objects/html-renders.test.ts +++ b/apps/backend/src/worker/objects/html-renders.test.ts @@ -17,7 +17,6 @@ const uuid = () => crypto.randomUUID() const userOf = (id: string) => new CurrentUser.Schema({ id: id as UserId, - organizationId: null, role: "member", email: `${id}@example.com`, isOnboarded: true, diff --git a/apps/backend/src/worker/objects/message-search.test.ts b/apps/backend/src/worker/objects/message-search.test.ts index 7a699c1a2..5abdec8b0 100644 --- a/apps/backend/src/worker/objects/message-search.test.ts +++ b/apps/backend/src/worker/objects/message-search.test.ts @@ -29,7 +29,6 @@ const uuid = () => crypto.randomUUID() const userOf = (id: string) => new CurrentUser.Schema({ id: id as UserId, - organizationId: null, role: "member", email: `${id}@example.com`, isOnboarded: true, diff --git a/apps/backend/src/worker/objects/objects.test.ts b/apps/backend/src/worker/objects/objects.test.ts index 160b5fb64..2983a7650 100644 --- a/apps/backend/src/worker/objects/objects.test.ts +++ b/apps/backend/src/worker/objects/objects.test.ts @@ -22,7 +22,6 @@ const uuid = () => crypto.randomUUID() const userOf = (id: string) => new CurrentUser.Schema({ id: id as UserId, - organizationId: null, role: "member", email: `${id}@example.com`, isOnboarded: true, diff --git a/apps/backend/src/worker/objects/org-objects.test.ts b/apps/backend/src/worker/objects/org-objects.test.ts index 452e294e4..9dcfe9a74 100644 --- a/apps/backend/src/worker/objects/org-objects.test.ts +++ b/apps/backend/src/worker/objects/org-objects.test.ts @@ -24,7 +24,6 @@ const uuid = () => crypto.randomUUID() const userOf = (id: string) => new CurrentUser.Schema({ id: id as UserId, - organizationId: null, role: "member", email: `${id}@example.com`, isOnboarded: true, @@ -578,7 +577,6 @@ describe("OrgObject: channels", () => { unnamed, new CurrentUser.Schema({ id: world.owner, - organizationId: null, role: "member", email: "owner@example.com", isOnboarded: true, diff --git a/apps/backend/src/worker/objects/org/organization.ts b/apps/backend/src/worker/objects/org/organization.ts index 636efbabf..088ff00a1 100644 --- a/apps/backend/src/worker/objects/org/organization.ts +++ b/apps/backend/src/worker/objects/org/organization.ts @@ -3,7 +3,6 @@ * cascade of an organization delete to its members' `membership_index` and `user_organizations`. */ import { Database, SQLite, schema } from "@hazel/db" -import { CurrentUser } from "@hazel/domain" import { type OrganizationId, OrganizationId as OrganizationIdSchema } from "@hazel/schema" import { Effect, Option } from "effect" import { currentObject } from "../../../lib/current-object" @@ -62,13 +61,11 @@ export const publishOrgRemoval = Effect.fn("OrgObject.publishOrgRemoval")(functi /** * The organization a `listByOrganization`-style call is about: the `OrgObject` it landed in (the - * api Worker routed it by its `organizationId` hint), else the session's active organization. + * api Worker routed it by its `organizationId` hint); none elsewhere. */ export const requestOrganizationId = Effect.gen(function* () { const object = yield* currentObject - if (Option.isSome(object) && object.value.kind === "org") { - return Option.some(OrganizationIdSchema.make(object.value.id)) - } - const user = yield* CurrentUser.Context - return Option.fromNullishOr(user.organizationId) + return Option.isSome(object) && object.value.kind === "org" + ? Option.some(OrganizationIdSchema.make(object.value.id)) + : Option.none() }) diff --git a/apps/web-foldkit/public/images/integrations/t3code.svg b/apps/web-foldkit/public/images/integrations/t3code.svg new file mode 100644 index 000000000..da47f1ba9 --- /dev/null +++ b/apps/web-foldkit/public/images/integrations/t3code.svg @@ -0,0 +1 @@ +T3 Code diff --git a/apps/web-foldkit/src/app/command.ts b/apps/web-foldkit/src/app/command.ts index 1c978d85a..3a1b8b7da 100644 --- a/apps/web-foldkit/src/app/command.ts +++ b/apps/web-foldkit/src/app/command.ts @@ -120,7 +120,6 @@ const fetchCurrentUser = Effect.gen(function* () { email: user.email, avatarUrl: user.avatarUrl ?? null, isOnboarded: user.isOnboarded, - organizationId: user.organizationId ?? null, }, }) }).pipe(catchCommon((reason) => Message.FailedFetchCurrentUser({ reason }))) diff --git a/apps/web-foldkit/src/core/lib/integrations/__data.ts b/apps/web-foldkit/src/core/lib/integrations/__data.ts index 387f7bfb8..e9d968556 100644 --- a/apps/web-foldkit/src/core/lib/integrations/__data.ts +++ b/apps/web-foldkit/src/core/lib/integrations/__data.ts @@ -226,12 +226,12 @@ export const integrations: Integration[] = [ }, { id: "t3code", + logoSrc: "/images/integrations/t3code.svg", name: "T3 Code", description: "Drive T3 Code threads on your own machine from Hazel.", fullDescription: "Link your T3 Code environment to start and steer its threads with /t3 from any conversation. Each member links their own machine and chooses who else in the organization may drive it; every action is posted in the Hazel thread.", - logoDomain: "t3.chat", - logoType: "icon", + logoDomain: "t3.codes", brandColor: "#1C1C1E", category: "developer-tools", connectionType: "environment", diff --git a/apps/web-foldkit/src/page/chat/channel/fixtures.test-support.ts b/apps/web-foldkit/src/page/chat/channel/fixtures.test-support.ts index 22a79199f..b8055cebf 100644 --- a/apps/web-foldkit/src/page/chat/channel/fixtures.test-support.ts +++ b/apps/web-foldkit/src/page/chat/channel/fixtures.test-support.ts @@ -29,7 +29,6 @@ export const shared: Shared = { email: "ada@hazel.sh", avatarUrl: null, isOnboarded: true, - organizationId, }, organization: { id: organizationId, name: "Hazel", slug: "hazel", logoUrl: null }, member: null, diff --git a/apps/web-foldkit/src/page/chat/channel/subscription.ts b/apps/web-foldkit/src/page/chat/channel/subscription.ts index 381f743a7..e99b1cdbc 100644 --- a/apps/web-foldkit/src/page/chat/channel/subscription.ts +++ b/apps/web-foldkit/src/page/chat/channel/subscription.ts @@ -364,7 +364,7 @@ const write = Subscription.make()((en { organizationId: Schema.NullOr(OrganizationId) }, { modelToDependencies: ({ shared }) => ({ - organizationId: shared.currentUser?.organizationId ?? null, + organizationId: shared.organization?.id ?? null, }), dependenciesToStream: ({ organizationId }) => organizationId === null @@ -378,7 +378,7 @@ const write = Subscription.make()((en { organizationId: Schema.NullOr(OrganizationId) }, { modelToDependencies: ({ shared }) => ({ - organizationId: shared.currentUser?.organizationId ?? null, + organizationId: shared.organization?.id ?? null, }), dependenciesToStream: ({ organizationId }) => organizationId === null diff --git a/apps/web-foldkit/src/page/chat/channel/write.ts b/apps/web-foldkit/src/page/chat/channel/write.ts index 4a744e862..835c53acb 100644 --- a/apps/web-foldkit/src/page/chat/channel/write.ts +++ b/apps/web-foldkit/src/page/chat/channel/write.ts @@ -83,7 +83,7 @@ const lastOwn = (messages: ReadonlyArray, currentUserId: string | n /** What a draft needs from the page: user, organization, membership, last own message. */ export const contextFor = (model: Model, which: Which, shared: Shared | null): Draft.Context => ({ currentUserId: model.currentUserId, - organizationId: shared?.currentUser?.organizationId ?? null, + organizationId: shared?.organization?.id ?? null, memberId: which === "channel" ? (model.members?.find((member) => member.userId === model.currentUserId)?.id ?? null) diff --git a/apps/web-foldkit/src/page/home/story.test.ts b/apps/web-foldkit/src/page/home/story.test.ts index 6ef14f4dc..6038318b5 100644 --- a/apps/web-foldkit/src/page/home/story.test.ts +++ b/apps/web-foldkit/src/page/home/story.test.ts @@ -37,7 +37,6 @@ const shared: Shared = { email: "ada@hazel.test", avatarUrl: null, isOnboarded: true, - organizationId, }, organization: { id: organizationId, name: "Hazel", slug: "hazel", logoUrl: null }, member: null, diff --git a/apps/web-foldkit/src/page/my-settings/linked-accounts/story.test.ts b/apps/web-foldkit/src/page/my-settings/linked-accounts/story.test.ts index 8cdc17b89..b2c02fe6a 100644 --- a/apps/web-foldkit/src/page/my-settings/linked-accounts/story.test.ts +++ b/apps/web-foldkit/src/page/my-settings/linked-accounts/story.test.ts @@ -3,7 +3,7 @@ import { routeOfPath } from "../../../route" import { Option } from "effect" import { Command, expectNoOutMessage, expectOutMessage, given, message, model, story } from "foldkit/story" import { describe, expect, test } from "vitest" -import { currentUser, makeShared, organizationId, storyUpdate } from "../../../test/pages-fixtures" +import { makeShared, organizationId, storyUpdate } from "../../../test/pages-fixtures" import { PageOutMessage } from "../../out-message" import { DisconnectDiscord, StartDiscordLink } from "./command" import { Message } from "./message" @@ -63,7 +63,7 @@ describe("link discord", () => { }) test("without an organization nothing starts", () => { - const orphan = makeShared({ currentUser: { ...currentUser, organizationId: null } }) + const orphan = makeShared({ organization: null }) story( storyUpdate(update, orphan), given(initial()), diff --git a/apps/web-foldkit/src/page/my-settings/linked-accounts/subscription.ts b/apps/web-foldkit/src/page/my-settings/linked-accounts/subscription.ts index 0f93e3191..48d33bfb6 100644 --- a/apps/web-foldkit/src/page/my-settings/linked-accounts/subscription.ts +++ b/apps/web-foldkit/src/page/my-settings/linked-accounts/subscription.ts @@ -20,7 +20,7 @@ const discordConnection = Subscription.make, Messag { organizationId: Schema.NullOr(OrganizationId), userId: Schema.NullOr(UserId) }, { modelToDependencies: ({ shared }) => ({ - organizationId: shared.currentUser?.organizationId ?? null, + organizationId: shared.organization?.id ?? null, userId: shared.currentUser?.id ?? null, }), dependenciesToStream: ({ organizationId, userId }) => diff --git a/apps/web-foldkit/src/page/my-settings/linked-accounts/update.ts b/apps/web-foldkit/src/page/my-settings/linked-accounts/update.ts index 2c4b66314..198602ca8 100644 --- a/apps/web-foldkit/src/page/my-settings/linked-accounts/update.ts +++ b/apps/web-foldkit/src/page/my-settings/linked-accounts/update.ts @@ -71,7 +71,7 @@ const unsupported = { } export const update = (model: Model, message: Message, shared: Shared): Return => { - const orgId = shared.currentUser?.organizationId ?? null + const orgId = shared.organization?.id ?? null const isIdle = model.link._tag === "Idle" const toIdle = () => modifyFields(model, { link: () => Link.Idle() }) return Message.match(message, { diff --git a/apps/web-foldkit/src/page/my-settings/notifications/story.test.ts b/apps/web-foldkit/src/page/my-settings/notifications/story.test.ts index 67010b51f..18480a553 100644 --- a/apps/web-foldkit/src/page/my-settings/notifications/story.test.ts +++ b/apps/web-foldkit/src/page/my-settings/notifications/story.test.ts @@ -27,7 +27,6 @@ const shared: Shared = { email: "ada@hazel.test", avatarUrl: null, isOnboarded: true, - organizationId: null, }, organization: null, member: null, diff --git a/apps/web-foldkit/src/page/my-settings/profile/profile.scene.test.ts b/apps/web-foldkit/src/page/my-settings/profile/profile.scene.test.ts index 4f36af8a3..6119696ae 100644 --- a/apps/web-foldkit/src/page/my-settings/profile/profile.scene.test.ts +++ b/apps/web-foldkit/src/page/my-settings/profile/profile.scene.test.ts @@ -32,7 +32,6 @@ const shared: Shared = { email: "ada@hazel.test", avatarUrl: null, isOnboarded: true, - organizationId: null, }, organization: null, member: null, diff --git a/apps/web-foldkit/src/page/my-settings/profile/profile.story.test.ts b/apps/web-foldkit/src/page/my-settings/profile/profile.story.test.ts index fa8165986..b198b650a 100644 --- a/apps/web-foldkit/src/page/my-settings/profile/profile.story.test.ts +++ b/apps/web-foldkit/src/page/my-settings/profile/profile.story.test.ts @@ -35,7 +35,6 @@ const shared: Shared = { email: "ada@hazel.test", avatarUrl: null, isOnboarded: true, - organizationId: null, }, organization: null, member: null, diff --git a/apps/web-foldkit/src/page/onboarding/scene.test.ts b/apps/web-foldkit/src/page/onboarding/scene.test.ts index 887109d34..c1cb2dd2e 100644 --- a/apps/web-foldkit/src/page/onboarding/scene.test.ts +++ b/apps/web-foldkit/src/page/onboarding/scene.test.ts @@ -39,7 +39,6 @@ const shared: Shared = { email: "nora@hazel.test", avatarUrl: null, isOnboarded: false, - organizationId: null, }, organization: null, member: null, diff --git a/apps/web-foldkit/src/page/root/index.ts b/apps/web-foldkit/src/page/root/index.ts index a8fd75327..540ad0f99 100644 --- a/apps/web-foldkit/src/page/root/index.ts +++ b/apps/web-foldkit/src/page/root/index.ts @@ -1,5 +1,5 @@ import { OrganizationId, UserId } from "@hazel/schema" -import { and, eq } from "@tanstack/db" +import { eq } from "@tanstack/db" import { Effect, Option, Schema, Stream } from "effect" import { Command, Submodel, Subscription } from "foldkit" import { modifyFields } from "foldkit/struct" @@ -78,13 +78,10 @@ interface MembershipRow { const subscriptions = Subscription.make, Message>()((entry) => ({ membership: entry( - { userId: Schema.NullOr(UserId), organizationId: Schema.NullOr(OrganizationId) }, + { userId: Schema.NullOr(UserId) }, { - modelToDependencies: ({ shared }) => ({ - userId: shared.currentUser?.id ?? null, - organizationId: shared.currentUser?.organizationId ?? null, - }), - dependenciesToStream: ({ userId, organizationId }) => + modelToDependencies: ({ shared }) => ({ userId: shared.currentUser?.id ?? null }), + dependenciesToStream: ({ userId }) => userId === null ? Stream.empty : liveQueryStream( @@ -94,15 +91,7 @@ const subscriptions = Subscription.make, Message>() .innerJoin({ org: organizationCollection }, ({ member, org }) => eq(member.organizationId, org.id), ) - // With an org in the JWT, only that membership counts. - .where(({ member }) => - organizationId - ? and( - eq(member.userId, userId), - eq(member.organizationId, organizationId), - ) - : eq(member.userId, userId), - ) + .where(({ member }) => eq(member.userId, userId)) .findOne(), (rows) => { const row = rows[0] diff --git a/apps/web-foldkit/src/page/settings/integrations/index/subscription.ts b/apps/web-foldkit/src/page/settings/integrations/index/subscription.ts index 0e0fd10ca..815ba2f39 100644 --- a/apps/web-foldkit/src/page/settings/integrations/index/subscription.ts +++ b/apps/web-foldkit/src/page/settings/integrations/index/subscription.ts @@ -16,9 +16,8 @@ export const subscriptions = Subscription.make, Mes connections: entry( { organizationId: Schema.NullOr(OrganizationId) }, { - // Legacy reads `useAuth().user.organizationId` here. modelToDependencies: ({ shared }) => ({ - organizationId: shared.currentUser?.organizationId ?? null, + organizationId: shared.organization?.id ?? null, }), dependenciesToStream: ({ organizationId }) => organizationId === null @@ -32,7 +31,7 @@ export const subscriptions = Subscription.make, Mes { organizationId: Schema.NullOr(OrganizationId), userId: Schema.NullOr(UserId) }, { modelToDependencies: ({ shared }) => ({ - organizationId: shared.currentUser?.organizationId ?? null, + organizationId: shared.organization?.id ?? null, userId: shared.currentUser?.id ?? null, }), dependenciesToStream: ({ organizationId, userId }) => diff --git a/apps/web-foldkit/src/page/settings/integrations/installed/subscription.ts b/apps/web-foldkit/src/page/settings/integrations/installed/subscription.ts index 3e4fa8171..4475660f3 100644 --- a/apps/web-foldkit/src/page/settings/integrations/installed/subscription.ts +++ b/apps/web-foldkit/src/page/settings/integrations/installed/subscription.ts @@ -12,7 +12,7 @@ export const subscriptions = Subscription.make, Mes { organizationId: Schema.NullOr(OrganizationId) }, { modelToDependencies: ({ shared }) => ({ - organizationId: shared.currentUser?.organizationId ?? null, + organizationId: shared.organization?.id ?? null, }), dependenciesToStream: ({ organizationId }) => organizationId === null diff --git a/apps/web-foldkit/src/page/settings/integrations/marketplace/subscription.ts b/apps/web-foldkit/src/page/settings/integrations/marketplace/subscription.ts index c2a1f9f41..6b3782691 100644 --- a/apps/web-foldkit/src/page/settings/integrations/marketplace/subscription.ts +++ b/apps/web-foldkit/src/page/settings/integrations/marketplace/subscription.ts @@ -25,7 +25,7 @@ const dataSubscriptions = Subscription.make, Messag { organizationId: Schema.NullOr(OrganizationId) }, { modelToDependencies: ({ shared }) => ({ - organizationId: shared.currentUser?.organizationId ?? null, + organizationId: shared.organization?.id ?? null, }), dependenciesToStream: ({ organizationId }) => organizationId === null diff --git a/apps/web-foldkit/src/route.test.ts b/apps/web-foldkit/src/route.test.ts index 1156ed654..fdee22ffa 100644 --- a/apps/web-foldkit/src/route.test.ts +++ b/apps/web-foldkit/src/route.test.ts @@ -172,7 +172,6 @@ describe("redirects", () => { email: "ada@hazel.test", avatarUrl: null, isOnboarded, - organizationId: null, }) it("resolves the root redirect like `_app/index.tsx`", () => { diff --git a/apps/web-foldkit/src/session.ts b/apps/web-foldkit/src/session.ts index 4d5da914d..5e730252a 100644 --- a/apps/web-foldkit/src/session.ts +++ b/apps/web-foldkit/src/session.ts @@ -10,8 +10,6 @@ export const CurrentUser = Schema.Struct({ email: Schema.String, avatarUrl: Schema.NullOr(Schema.String), isOnboarded: Schema.Boolean, - /** `user.me`'s organization, if any (legacy `useAuth().user.organizationId`). */ - organizationId: Schema.NullOr(OrganizationId), }) export type CurrentUser = typeof CurrentUser.Type diff --git a/apps/web-foldkit/src/test/pages-entry-fixtures.ts b/apps/web-foldkit/src/test/pages-entry-fixtures.ts index dc47453c7..67dddc886 100644 --- a/apps/web-foldkit/src/test/pages-entry-fixtures.ts +++ b/apps/web-foldkit/src/test/pages-entry-fixtures.ts @@ -13,7 +13,7 @@ export const newcomer: Shared = makeShared({ orgSlug: null, organization: null, member: null, - currentUser: { ...currentUser, firstName: "Nora", lastName: "Newcomer", isOnboarded: false, organizationId: null }, + currentUser: { ...currentUser, firstName: "Nora", lastName: "Newcomer", isOnboarded: false }, }) /** A membership with a slug: the user was invited into an existing organization. */ diff --git a/apps/web-foldkit/src/test/pages-fixtures.ts b/apps/web-foldkit/src/test/pages-fixtures.ts index 6c07590fc..f4a5b9144 100644 --- a/apps/web-foldkit/src/test/pages-fixtures.ts +++ b/apps/web-foldkit/src/test/pages-fixtures.ts @@ -26,7 +26,6 @@ export const currentUser: CurrentUser = { email: "ada@hazel.test", avatarUrl: null, isOnboarded: true, - organizationId, } export const memberWithRole = (role: Member["role"]): Member => ({ id: memberId, role }) diff --git a/apps/web-foldkit/src/test/root-fixtures.ts b/apps/web-foldkit/src/test/root-fixtures.ts index 4fe64ce0f..e1df9be86 100644 --- a/apps/web-foldkit/src/test/root-fixtures.ts +++ b/apps/web-foldkit/src/test/root-fixtures.ts @@ -24,7 +24,6 @@ export const ada: CurrentUser = { email: "ada@hazel.test", avatarUrl: null, isOnboarded: true, - organizationId: null, } export const hazelOrg: Organization = { diff --git a/packages/domain/src/current-user.ts b/packages/domain/src/current-user.ts index 91ff2aeb0..53c08365d 100644 --- a/packages/domain/src/current-user.ts +++ b/packages/domain/src/current-user.ts @@ -1,13 +1,12 @@ import { HttpApiMiddleware, HttpApiSecurity } from "effect/http-api" import { Context as EffectContext, Schema as S } from "effect" import { UnauthorizedError } from "./errors" -import { OrganizationId, UserId } from "@hazel/schema" +import { UserId } from "@hazel/schema" import { User } from "./models" import { InvalidBearerTokenError, SessionLoadError, SessionNotProvidedError } from "./session-errors" export class Schema extends S.Class("CurrentUserSchema")({ id: UserId, - organizationId: S.NullishOr(OrganizationId), role: S.Literals(["admin", "member", "owner"]), avatarUrl: S.optional(S.String), firstName: S.optional(S.String),