MemberJunction / MemberJunction/MJ

Three principal lookups in MJServer/src/index.ts fall back to an arbitrary cache entry

Open
#4,233 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

**Found by** the PR gauntlet on #4231 (`fix/4209-provisioning-context-user`), 2026-09-04. Not caused by that PR: verified pre-existing at `938cd9e9e8f9425924dc7d7bd3b55c90dfd90e46`.

### Where
`packages/MJServer/src/index.ts:424`, `:568`, `:1676`; and the same idiom (correct variant) at
`packages/MJServer/src/telephony/TwilioTelephonyRouter.ts:178`,
`VonageTelephonyRouter.ts:233`, `RingCentralTelephonyService.ts:301`.

### What happens
`index.ts:424`, `:568` and `:1676` resolve a server-side principal with

```ts
UserCache.Instance.Users.find(u => u.Type?.trim().toLowerCase() === 'owner') ?? UserCache.Instance.Users[0]
```

— no `IsActive` check, and an `?? Users[0]` fallback that will hand back *any* user, including the
seeded `Anonymous` principal, when a deployment has no Owner. `UserCache.Users` is filled by an
`ORDER BY`-less `SELECT * FROM vwUsers` (`UserCache.ts:112`) and is mutated in place at runtime
(`auth/index.ts:291`, `MagicLinkService.ts:683`), so which user that is changes across boots and
within a process.

### Why it matters
This is exactly the attribution non-determinism #4209 documents, in three more places — and two of
them (`:424`, `:568`) supply the `currentUser` for **CodeGen**, so generated-metadata writes are
attributed to an arbitrary user. `:1676` picks the principal that processes the RSU work queue.
The `?? Users[0]` tail is worse than the Owner miss it covers: acting as `Anonymous` is a silent
privilege *mismatch*, not just bad attribution.

### Repro
```bash
# On a scratch database (never a shared one), deactivate every Owner, then boot MJAPI and
# observe which principal index.ts:1676 selects across two restarts.
```

### Evidence
PR #4231 extracted this ladder into `packages/MJServer/src/auth/principals.ts` and migrated the
three *config-driven* call sites. `plans/provisioning-context-user-resolution.md` §6 lists these
remaining sites as deliberately out of that PR's scope.

### Suggested fix
Migrate each to `ResolveConfiguredPrincipal(undefined, '')` (exported from
`@memberjunction/server` as of #4231), or to the pure `resolvePrincipalFrom` where the module must
not depend on MJServer config. The telephony trio already uses the better variant and is the cheap
first move. Drop every `?? Users[0]` tail: the ladder returns `null`, and a caller that cannot
proceed should say so rather than act as an arbitrary user.

### Definition of done
- [ ] A failing test that reproduces it, then green
- [ ] No `?? Users[0]` principal fallback remains in `packages/MJServer/src/index.ts`
- [ ] Existing suite and gates green; no changed expectation in an existing test

### Verify by
`cd packages/MJServer && pnpm test` plus `pnpm run test:integration`, and
`grep -n "Users\[0\]" packages/MJServer/src/index.ts` returns nothing for principal selection.

Contributor guide

Open the contributing guide

Research direction

Start with the three principal-selection sites in packages/MJServer/src/index.ts and compare them with packages/MJServer/src/auth/principals.ts and the telephony routers' existing variant. Add a failing test for the missing-Owner case, then run cd packages/MJServer && pnpm test and pnpm run test:integration. Done means no principal-selection Users[0] fallback remains in index.ts and all gates are green.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
authentication, backend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
64/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.