MemberJunction / MemberJunction/MJ

Realtime/GraphQL: AppContext carries no request origin, so a resolver cannot enforce an embed allowlist

Open Beginner friendly
#3,910 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
TSQL
Stars
29
Forks
6
Avg merge
2d 1h
Merged PRs (30d)
323

Description

## Summary

`AppContext` gives resolvers `{ dataSource, dataSources, userPayload, providers }` and nothing about the HTTP request. So a consumer that wants to make an authorization decision based on **where the request came from** — an embed origin allowlist, the browser equivalent of an IP allowlist — cannot, at any layer it owns.

MJ already computes the value. `packages/MJServer/src/context.ts` does:

```ts
const requestDomain = parseRequestHostname(req.headers.origin);
```

and threads it through authentication and login auditing. It is simply never put on the context the resolvers receive (`contextFunction` returns `{ dataSource, dataSources, userPayload, providers }`).

Verified on `next` @ `6.1.0-edge.2`.

## Why a consumer cannot work around it

Three routes, all bad:

1. **Take the origin as a mutation argument.** Client-supplied and trivially forged, so it is not a control — but it *looks* like one in the Studio, which is worse than not offering it. This is the "authored field that silently does nothing" failure mode of #3374 / #3859 / #3854, in its most dangerous form: a security setting.
2. **Enforce in Express middleware instead.** Works only for routes the consumer owns. An embedded widget on a customer's own site talks to `/graphql` directly, which is exactly the case an embed allowlist is for.
3. **Read it from an async-local store.** Nothing exposes one, and reaching around the context to grab the request would be depending on an implementation detail.

## The ask

Surface what is already computed. Either shape works:

```ts
export type AppContext = {

/** The request's `Origin` header, normalized, or undefined when the client sent none. */
requestOrigin?: string;
};
```

or the raw headers / `req` itself, if that is more in keeping — the origin is what is needed, but a consumer with the request can answer questions nobody has asked yet.

`requestDomain` already exists a few lines away in the same function, so this looks like roughly a field on a type and a line in `contextFunction`. Happy to open the PR if the shape is agreed — I did not want to guess between "just the origin" and "the whole request".

## What it unblocks downstream

A downstream product with an embeddable widget needs origin allow-listing, and today there is no MJ-side primitive to build it on — the host application cannot even SEE the request origin at the resolver layer, so any origin control has to be reinvented outside MJ per product. The column, grammar and inheritance for an allowlist are built and merged behind this; the authoring UI is deliberately withheld until the check can actually run, precisely so it does not become another silently-ignored setting.

Contributor guide

Open the contributing guide

Research direction

Start in packages/MJServer/src/context.ts at contextFunction and the existing requestDomain calculation. Confirm the agreed AppContext shape, then ensure the normalized request origin or request information reaches resolvers; done means a resolver can inspect the request origin for authorization decisions, including when the client sends no Origin header.

Written by the indexing model from the issue text.

Assessment

Tech stack
graphql, typescript
Domain
api, authorization, backend, security
Issue type
Feature
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
72/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.