MemberJunction / MemberJunction/MJ
Realtime/GraphQL: AppContext carries no request origin, so a resolver cannot enforce an embed allowlist
- 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
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