MemberJunction / MemberJunction/MJ
Three principal lookups in MJServer/src/index.ts fall back to an arbitrary cache entry
- 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
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