@memberjunction/server 6.1.0-edge.5 → 6.1.0-edge.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -11
- package/dist/.tsbuildinfo +1 -1
- package/dist/agentSessions/SessionJanitor.js +2 -2
- package/dist/agentSessions/SessionJanitor.js.map +1 -1
- package/dist/auth/exampleNewUserSubClass.d.ts.map +1 -1
- package/dist/auth/exampleNewUserSubClass.js +9 -4
- package/dist/auth/exampleNewUserSubClass.js.map +1 -1
- package/dist/auth/magicLink/MagicLinkService.d.ts.map +1 -1
- package/dist/auth/magicLink/MagicLinkService.js +2 -8
- package/dist/auth/magicLink/MagicLinkService.js.map +1 -1
- package/dist/auth/newUsers.d.ts.map +1 -1
- package/dist/auth/newUsers.js +4 -12
- package/dist/auth/newUsers.js.map +1 -1
- package/dist/auth/principals.d.ts +92 -0
- package/dist/auth/principals.d.ts.map +1 -0
- package/dist/auth/principals.js +193 -0
- package/dist/auth/principals.js.map +1 -0
- package/dist/config.d.ts +106 -12
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +41 -4
- package/dist/config.js.map +1 -1
- package/dist/entitySubclasses/MJEntityPermissionEntityServer.server.d.ts +30 -0
- package/dist/entitySubclasses/MJEntityPermissionEntityServer.server.d.ts.map +1 -1
- package/dist/entitySubclasses/MJEntityPermissionEntityServer.server.js +58 -5
- package/dist/entitySubclasses/MJEntityPermissionEntityServer.server.js.map +1 -1
- package/dist/generated/graphql-schemas/__mj.d.ts +7607 -6853
- package/dist/generated/graphql-schemas/__mj.d.ts.map +1 -1
- package/dist/generated/graphql-schemas/__mj.js +25141 -21645
- package/dist/generated/graphql-schemas/__mj.js.map +1 -1
- package/dist/generic/ResolverBase.d.ts +152 -2
- package/dist/generic/ResolverBase.d.ts.map +1 -1
- package/dist/generic/ResolverBase.js +389 -10
- package/dist/generic/ResolverBase.js.map +1 -1
- package/dist/generic/RunViewResolver.d.ts.map +1 -1
- package/dist/generic/RunViewResolver.js +26 -2
- package/dist/generic/RunViewResolver.js.map +1 -1
- package/dist/generic/refusalExtensions.d.ts +52 -0
- package/dist/generic/refusalExtensions.d.ts.map +1 -0
- package/dist/generic/refusalExtensions.js +16 -0
- package/dist/generic/refusalExtensions.js.map +1 -0
- package/dist/index.d.ts +11 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +131 -93
- package/dist/index.js.map +1 -1
- package/dist/realtimeWidget/WidgetSessionService.d.ts.map +1 -1
- package/dist/realtimeWidget/WidgetSessionService.js +2 -12
- package/dist/realtimeWidget/WidgetSessionService.js.map +1 -1
- package/dist/realtimeWidget/visitorIdentity.d.ts.map +1 -1
- package/dist/realtimeWidget/visitorIdentity.js +20 -5
- package/dist/realtimeWidget/visitorIdentity.js.map +1 -1
- package/dist/resolvers/EntityRecordNameResolver.d.ts +34 -5
- package/dist/resolvers/EntityRecordNameResolver.d.ts.map +1 -1
- package/dist/resolvers/EntityRecordNameResolver.js +64 -7
- package/dist/resolvers/EntityRecordNameResolver.js.map +1 -1
- package/dist/resolvers/FileCategoryResolver.d.ts.map +1 -1
- package/dist/resolvers/FileCategoryResolver.js +1 -2
- package/dist/resolvers/FileCategoryResolver.js.map +1 -1
- package/dist/resolvers/IntegrationDiscoveryResolver.d.ts.map +1 -1
- package/dist/resolvers/IntegrationDiscoveryResolver.js +2 -0
- package/dist/resolvers/IntegrationDiscoveryResolver.js.map +1 -1
- package/dist/resolvers/QuerySystemUserResolver.js +1 -1
- package/dist/resolvers/QuerySystemUserResolver.js.map +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts +10 -2
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.js +80 -4
- package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -1
- package/dist/resolvers/RemoteBrowserActionResolver.d.ts +13 -8
- package/dist/resolvers/RemoteBrowserActionResolver.d.ts.map +1 -1
- package/dist/resolvers/RemoteBrowserActionResolver.js +45 -8
- package/dist/resolvers/RemoteBrowserActionResolver.js.map +1 -1
- package/dist/resolvers/SyncRolesUsersResolver.d.ts.map +1 -1
- package/dist/resolvers/SyncRolesUsersResolver.js +11 -2
- package/dist/resolvers/SyncRolesUsersResolver.js.map +1 -1
- package/dist/resolvers/UserViewResolver.d.ts.map +1 -1
- package/dist/resolvers/UserViewResolver.js +8 -4
- package/dist/resolvers/UserViewResolver.js.map +1 -1
- package/dist/rest/EntityCRUDHandler.d.ts +2 -1
- package/dist/rest/EntityCRUDHandler.d.ts.map +1 -1
- package/dist/rest/EntityCRUDHandler.js +3 -18
- package/dist/rest/EntityCRUDHandler.js.map +1 -1
- package/dist/rest/RESTEndpointHandler.d.ts +2 -1
- package/dist/rest/RESTEndpointHandler.d.ts.map +1 -1
- package/dist/rest/RESTEndpointHandler.js +3 -16
- package/dist/rest/RESTEndpointHandler.js.map +1 -1
- package/dist/rest/RealtimeSdpBrokerHandler.d.ts +23 -0
- package/dist/rest/RealtimeSdpBrokerHandler.d.ts.map +1 -0
- package/dist/rest/RealtimeSdpBrokerHandler.js +78 -0
- package/dist/rest/RealtimeSdpBrokerHandler.js.map +1 -0
- package/dist/serverExtensionReservedRoots.d.ts.map +1 -1
- package/dist/serverExtensionReservedRoots.js +2 -3
- package/dist/serverExtensionReservedRoots.js.map +1 -1
- package/package.json +92 -91
- package/src/__tests__/EntityCRUDHandler.test.ts +18 -5
- package/src/__tests__/EntityRecordNameResolver.fieldSecurity.test.ts +118 -0
- package/src/__tests__/RealtimeClientSessionResolver.test.ts +20 -0
- package/src/__tests__/RealtimeSdpBrokerHandler.test.ts +191 -0
- package/src/__tests__/RemoteBrowserAudioStream.test.ts +81 -2
- package/src/__tests__/ResolverBase.filterEscaping.test.ts +190 -3
- package/src/__tests__/ResolverBase.readableFieldsTransport.test.ts +106 -0
- package/src/__tests__/UserViewResolver.updateWhereClause.test.ts +114 -0
- package/src/__tests__/WidgetSessionService.test.ts +4 -1
- package/src/__tests__/generatedGraphQLSchema.structural.test.ts +46 -0
- package/src/__tests__/newUserRolesDefault.test.ts +81 -0
- package/src/__tests__/newUsers.test.ts +36 -14
- package/src/__tests__/principals.callSites.test.ts +112 -0
- package/src/__tests__/principals.logging.test.ts +160 -0
- package/src/__tests__/principals.test.ts +305 -0
- package/src/__tests__/resolverBase.fls.test.ts +483 -0
- package/src/__tests__/resolverBase.recordChangesFls.test.ts +358 -0
- package/src/__tests__/resolverBase.refusalExtensions.test.ts +101 -0
- package/src/__tests__/resolverBase.refusalMessage.test.ts +88 -0
- package/src/__tests__/serverExtensionReservedRoots.test.ts +79 -11
- package/src/__tests__/visitorIdentity.resolve.test.ts +61 -0
- package/src/agentSessions/SessionJanitor.ts +2 -2
- package/src/auth/exampleNewUserSubClass.ts +9 -6
- package/src/auth/magicLink/MagicLinkService.ts +2 -8
- package/src/auth/newUsers.ts +7 -15
- package/src/auth/principals.ts +257 -0
- package/src/config.ts +44 -4
- package/src/entitySubclasses/MJEntityPermissionEntityServer.server.ts +65 -6
- package/src/generated/graphql-schemas/__mj.ts +18966 -16452
- package/src/generic/ResolverBase.ts +467 -9
- package/src/generic/RunViewResolver.ts +28 -2
- package/src/generic/refusalExtensions.ts +59 -0
- package/src/index.ts +154 -102
- package/src/realtimeWidget/WidgetSessionService.ts +2 -14
- package/src/realtimeWidget/visitorIdentity.ts +21 -6
- package/src/resolvers/EntityRecordNameResolver.ts +74 -8
- package/src/resolvers/FileCategoryResolver.ts +2 -3
- package/src/resolvers/IntegrationDiscoveryResolver.ts +2 -0
- package/src/resolvers/QuerySystemUserResolver.ts +1 -1
- package/src/resolvers/RealtimeClientSessionResolver.ts +128 -2
- package/src/resolvers/RemoteBrowserActionResolver.ts +47 -8
- package/src/resolvers/SyncRolesUsersResolver.ts +11 -2
- package/src/resolvers/UserViewResolver.ts +8 -4
- package/src/rest/EntityCRUDHandler.ts +3 -19
- package/src/rest/RESTEndpointHandler.ts +3 -17
- package/src/rest/RealtimeSdpBrokerHandler.ts +101 -0
- package/src/serverExtensionReservedRoots.ts +2 -3
- package/dist/resolvers/RingCentralTelephonyResolver.d.ts +0 -27
- package/dist/resolvers/RingCentralTelephonyResolver.d.ts.map +0 -1
- package/dist/resolvers/RingCentralTelephonyResolver.js +0 -87
- package/dist/resolvers/RingCentralTelephonyResolver.js.map +0 -1
- package/dist/resolvers/TeamsMeetingsResolver.d.ts +0 -28
- package/dist/resolvers/TeamsMeetingsResolver.d.ts.map +0 -1
- package/dist/resolvers/TeamsMeetingsResolver.js +0 -91
- package/dist/resolvers/TeamsMeetingsResolver.js.map +0 -1
- package/dist/resolvers/TelephonyResolver.d.ts +0 -26
- package/dist/resolvers/TelephonyResolver.d.ts.map +0 -1
- package/dist/resolvers/TelephonyResolver.js +0 -86
- package/dist/resolvers/TelephonyResolver.js.map +0 -1
- package/dist/resolvers/VonageTelephonyResolver.d.ts +0 -26
- package/dist/resolvers/VonageTelephonyResolver.d.ts.map +0 -1
- package/dist/resolvers/VonageTelephonyResolver.js +0 -86
- package/dist/resolvers/VonageTelephonyResolver.js.map +0 -1
- package/dist/telephony/RingCentralTelephonyService.d.ts +0 -115
- package/dist/telephony/RingCentralTelephonyService.d.ts.map +0 -1
- package/dist/telephony/RingCentralTelephonyService.js +0 -262
- package/dist/telephony/RingCentralTelephonyService.js.map +0 -1
- package/dist/telephony/TeamsMeetingsRouter.d.ts +0 -40
- package/dist/telephony/TeamsMeetingsRouter.d.ts.map +0 -1
- package/dist/telephony/TeamsMeetingsRouter.js +0 -96
- package/dist/telephony/TeamsMeetingsRouter.js.map +0 -1
- package/dist/telephony/TeamsMeetingsService.d.ts +0 -113
- package/dist/telephony/TeamsMeetingsService.d.ts.map +0 -1
- package/dist/telephony/TeamsMeetingsService.js +0 -196
- package/dist/telephony/TeamsMeetingsService.js.map +0 -1
- package/dist/telephony/TwilioTelephonyRouter.d.ts +0 -36
- package/dist/telephony/TwilioTelephonyRouter.d.ts.map +0 -1
- package/dist/telephony/TwilioTelephonyRouter.js +0 -143
- package/dist/telephony/TwilioTelephonyRouter.js.map +0 -1
- package/dist/telephony/TwilioTelephonyService.d.ts +0 -95
- package/dist/telephony/TwilioTelephonyService.d.ts.map +0 -1
- package/dist/telephony/TwilioTelephonyService.js +0 -193
- package/dist/telephony/TwilioTelephonyService.js.map +0 -1
- package/dist/telephony/VonageTelephonyRouter.d.ts +0 -41
- package/dist/telephony/VonageTelephonyRouter.d.ts.map +0 -1
- package/dist/telephony/VonageTelephonyRouter.js +0 -201
- package/dist/telephony/VonageTelephonyRouter.js.map +0 -1
- package/dist/telephony/VonageTelephonyService.d.ts +0 -90
- package/dist/telephony/VonageTelephonyService.d.ts.map +0 -1
- package/dist/telephony/VonageTelephonyService.js +0 -191
- package/dist/telephony/VonageTelephonyService.js.map +0 -1
- package/dist/telephony/calendar-scheduler.d.ts +0 -67
- package/dist/telephony/calendar-scheduler.d.ts.map +0 -1
- package/dist/telephony/calendar-scheduler.js +0 -133
- package/dist/telephony/calendar-scheduler.js.map +0 -1
- package/dist/telephony/index.d.ts +0 -29
- package/dist/telephony/index.d.ts.map +0 -1
- package/dist/telephony/index.js +0 -38
- package/dist/telephony/index.js.map +0 -1
- package/dist/telephony/media-upgrade-router.d.ts +0 -47
- package/dist/telephony/media-upgrade-router.d.ts.map +0 -1
- package/dist/telephony/media-upgrade-router.js +0 -72
- package/dist/telephony/media-upgrade-router.js.map +0 -1
- package/dist/telephony/ringcentral-runtime.d.ts +0 -14
- package/dist/telephony/ringcentral-runtime.d.ts.map +0 -1
- package/dist/telephony/ringcentral-runtime.js +0 -18
- package/dist/telephony/ringcentral-runtime.js.map +0 -1
- package/dist/telephony/teams-meetings-runtime.d.ts +0 -16
- package/dist/telephony/teams-meetings-runtime.d.ts.map +0 -1
- package/dist/telephony/teams-meetings-runtime.js +0 -20
- package/dist/telephony/teams-meetings-runtime.js.map +0 -1
- package/dist/telephony/teamsAcsMediaRegistry.d.ts +0 -69
- package/dist/telephony/teamsAcsMediaRegistry.d.ts.map +0 -1
- package/dist/telephony/teamsAcsMediaRegistry.js +0 -118
- package/dist/telephony/teamsAcsMediaRegistry.js.map +0 -1
- package/dist/telephony/telephony-runtime.d.ts +0 -14
- package/dist/telephony/telephony-runtime.d.ts.map +0 -1
- package/dist/telephony/telephony-runtime.js +0 -18
- package/dist/telephony/telephony-runtime.js.map +0 -1
- package/dist/telephony/twilioMediaRegistry.d.ts +0 -60
- package/dist/telephony/twilioMediaRegistry.d.ts.map +0 -1
- package/dist/telephony/twilioMediaRegistry.js +0 -106
- package/dist/telephony/twilioMediaRegistry.js.map +0 -1
- package/dist/telephony/vonage-runtime.d.ts +0 -14
- package/dist/telephony/vonage-runtime.d.ts.map +0 -1
- package/dist/telephony/vonage-runtime.js +0 -18
- package/dist/telephony/vonage-runtime.js.map +0 -1
- package/dist/telephony/vonageMediaRegistry.d.ts +0 -78
- package/dist/telephony/vonageMediaRegistry.d.ts.map +0 -1
- package/dist/telephony/vonageMediaRegistry.js +0 -158
- package/dist/telephony/vonageMediaRegistry.js.map +0 -1
- package/src/__tests__/media-upgrade-router.test.ts +0 -43
- package/src/__tests__/teams-meetings-service.test.ts +0 -65
- package/src/__tests__/teamsAcsMediaRegistry.test.ts +0 -82
- package/src/__tests__/twilio-telephony-service.test.ts +0 -23
- package/src/__tests__/twilioMediaRegistry.test.ts +0 -103
- package/src/__tests__/vonageMediaRegistry.test.ts +0 -180
- package/src/resolvers/RingCentralTelephonyResolver.ts +0 -64
- package/src/resolvers/TeamsMeetingsResolver.ts +0 -68
- package/src/resolvers/TelephonyResolver.ts +0 -63
- package/src/resolvers/VonageTelephonyResolver.ts +0 -63
- package/src/telephony/RingCentralTelephonyService.ts +0 -342
- package/src/telephony/TeamsMeetingsRouter.ts +0 -124
- package/src/telephony/TeamsMeetingsService.ts +0 -268
- package/src/telephony/TwilioTelephonyRouter.ts +0 -184
- package/src/telephony/TwilioTelephonyService.ts +0 -261
- package/src/telephony/VonageTelephonyRouter.ts +0 -239
- package/src/telephony/VonageTelephonyService.ts +0 -259
- package/src/telephony/calendar-scheduler.ts +0 -176
- package/src/telephony/index.ts +0 -43
- package/src/telephony/media-upgrade-router.ts +0 -79
- package/src/telephony/ringcentral-runtime.ts +0 -22
- package/src/telephony/teams-meetings-runtime.ts +0 -24
- package/src/telephony/teamsAcsMediaRegistry.ts +0 -152
- package/src/telephony/telephony-runtime.ts +0 -22
- package/src/telephony/twilioMediaRegistry.ts +0 -137
- package/src/telephony/vonage-runtime.ts +0 -22
- package/src/telephony/vonageMediaRegistry.ts +0 -200
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolution of an operator-named context user — regression suite for issue #4209.
|
|
3
|
+
*
|
|
4
|
+
* MJServer has three settings that name a user in config (`contextUserForNewUserCreation`,
|
|
5
|
+
* `contextUserForProvisioning`, `contextUserForLookup`). All three were resolved through
|
|
6
|
+
* `UserCache.UserByName`, which matches `User.Name` — but the shipped default for the first is
|
|
7
|
+
* `'not.set@nowhere.com'`, an EMAIL. On a stock database the seeded system user is
|
|
8
|
+
* `Name='System'` / `Email='not.set@nowhere.com'`, so the default names the very user it was
|
|
9
|
+
* aiming at and can never reach it. Every magic-link redeem logged an error and provisioned
|
|
10
|
+
* under whichever user happened to sort first as an Owner.
|
|
11
|
+
*
|
|
12
|
+
* These tests drive the pure core of the resolver, so they need neither the `UserCache`
|
|
13
|
+
* singleton nor a database — the seam is `(candidate, users) -> resolution`.
|
|
14
|
+
*/
|
|
15
|
+
import { describe, it, expect } from 'vitest';
|
|
16
|
+
import { resolvePrincipalFrom, type ResolvablePrincipal } from '../auth/principals.js';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The system user's ID, pinned by `UserCache` (`SYSTEM_USER_ID`) and seeded by every baseline.
|
|
20
|
+
* Spelled in the mixed case the migration writes to prove the match is case-insensitive.
|
|
21
|
+
*/
|
|
22
|
+
const SYSTEM_USER_ID = 'ECAFCCEC-6A37-EF11-86D4-000D3A4E707E';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The shipped default for `userHandling.contextUserForNewUserCreation` — see `src/config.ts`.
|
|
26
|
+
* It is email-shaped, which is the whole defect: it is matched against `Name`.
|
|
27
|
+
*/
|
|
28
|
+
const SHIPPED_DEFAULT = 'not.set@nowhere.com';
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Rows exactly as seeded by `migrations/v5/B202607091514__v5.46.x__Baseline.sql`. `Type` is
|
|
32
|
+
* `NCHAR(15)`, so the seeded values carry trailing padding — the resolver must trim before
|
|
33
|
+
* comparing, and these fixtures keep the padding so that stays tested.
|
|
34
|
+
*/
|
|
35
|
+
const SYSTEM: ResolvablePrincipal = {
|
|
36
|
+
ID: SYSTEM_USER_ID,
|
|
37
|
+
Name: 'System',
|
|
38
|
+
Email: 'not.set@nowhere.com',
|
|
39
|
+
Type: 'Owner ',
|
|
40
|
+
IsActive: true,
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const ANONYMOUS: ResolvablePrincipal = {
|
|
44
|
+
ID: '273910DF-28F1-45C1-A8F8-6E9AD8E5F008',
|
|
45
|
+
Name: 'Anonymous',
|
|
46
|
+
Email: 'anonymous@magic-link.local',
|
|
47
|
+
Type: 'User ',
|
|
48
|
+
IsActive: true,
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/** A human admin, as every real host has: created by MJ, so `Name` IS the email address. */
|
|
52
|
+
const HOST_ADMIN: ResolvablePrincipal = {
|
|
53
|
+
ID: 'AAAA1111-0000-0000-0000-000000000001',
|
|
54
|
+
Name: 'jane.admin@acme.com',
|
|
55
|
+
Email: 'jane.admin@acme.com',
|
|
56
|
+
Type: 'Owner',
|
|
57
|
+
IsActive: true,
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/** The stock seed plus one human admin — the shape of an ordinary deployment. */
|
|
61
|
+
const STOCK_HOST: ResolvablePrincipal[] = [SYSTEM, ANONYMOUS, HOST_ADMIN];
|
|
62
|
+
|
|
63
|
+
describe('resolvePrincipalFrom', () => {
|
|
64
|
+
describe('the shipped default (#4209)', () => {
|
|
65
|
+
it('resolves the email-shaped default to the seeded System user', () => {
|
|
66
|
+
const result = resolvePrincipalFrom(SHIPPED_DEFAULT, STOCK_HOST, SYSTEM_USER_ID);
|
|
67
|
+
|
|
68
|
+
expect(result.user).toBe(SYSTEM);
|
|
69
|
+
expect(result.reason).toBe('email');
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('reports no warning when the default resolves, so a stock host logs nothing', () => {
|
|
73
|
+
const result = resolvePrincipalFrom(SHIPPED_DEFAULT, STOCK_HOST, SYSTEM_USER_ID);
|
|
74
|
+
|
|
75
|
+
expect(result.warning).toBeUndefined();
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
describe('attribution determinism', () => {
|
|
80
|
+
it('resolves identically however the user cache happens to be ordered', () => {
|
|
81
|
+
// `SELECT * FROM vwUsers` carries no ORDER BY and the cache is mutated in place at
|
|
82
|
+
// runtime, so row order differs between boots AND within a process. Attribution must
|
|
83
|
+
// not depend on it: this is what made CreatedByUserID incidental.
|
|
84
|
+
const ordered = resolvePrincipalFrom(SHIPPED_DEFAULT, STOCK_HOST, SYSTEM_USER_ID);
|
|
85
|
+
const reversed = resolvePrincipalFrom(SHIPPED_DEFAULT, [...STOCK_HOST].reverse(), SYSTEM_USER_ID);
|
|
86
|
+
|
|
87
|
+
expect(reversed.user).toBe(ordered.user);
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
describe('explicitly configured principals', () => {
|
|
92
|
+
it('keeps a Name match ahead of another user whose Email is that same string', () => {
|
|
93
|
+
// The compatibility guarantee: a host that resolves today must resolve to the SAME
|
|
94
|
+
// user after the Email fallback is added. Name is tried first for exactly this reason.
|
|
95
|
+
const namedSupport: ResolvablePrincipal = {
|
|
96
|
+
ID: 'BBBB2222-0000-0000-0000-000000000002',
|
|
97
|
+
Name: 'support@acme.com',
|
|
98
|
+
Email: 'someone.else@acme.com',
|
|
99
|
+
Type: 'User',
|
|
100
|
+
IsActive: true,
|
|
101
|
+
};
|
|
102
|
+
const emailSupport: ResolvablePrincipal = {
|
|
103
|
+
ID: 'CCCC3333-0000-0000-0000-000000000003',
|
|
104
|
+
Name: 'Bob Support',
|
|
105
|
+
Email: 'support@acme.com',
|
|
106
|
+
Type: 'User',
|
|
107
|
+
IsActive: true,
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const result = resolvePrincipalFrom('support@acme.com', [emailSupport, namedSupport], SYSTEM_USER_ID);
|
|
111
|
+
|
|
112
|
+
expect(result.user).toBe(namedSupport);
|
|
113
|
+
expect(result.reason).toBe('name');
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
describe('duplicate matches resolve deterministically', () => {
|
|
118
|
+
// `User.Name` carries NO unique constraint. MJ's own rows do not collide — `NewUserBase`
|
|
119
|
+
// sets `Name = email` and `UQ_User_Email` makes Email unique, so MJ-created Names are
|
|
120
|
+
// unique BY CONSEQUENCE, not by rule — but a hand-created or externally-synced row can
|
|
121
|
+
// collide, and then `find()` returns whichever the cache happens to hold first. That is
|
|
122
|
+
// the same cache-order dependence the Owner rung is sorted to remove, so every rung
|
|
123
|
+
// resolves ties the same way: lowest ID.
|
|
124
|
+
const LOW: ResolvablePrincipal = { ID: '11111111-0000-0000-0000-000000000001', Name: 'shared.name', Email: 'low@acme.com', Type: 'User', IsActive: true };
|
|
125
|
+
const HIGH: ResolvablePrincipal = { ID: 'FFFFFFFF-0000-0000-0000-00000000000F', Name: 'shared.name', Email: 'high@acme.com', Type: 'User', IsActive: true };
|
|
126
|
+
|
|
127
|
+
it('picks the lowest-ID user when two users share the configured Name', () => {
|
|
128
|
+
const result = resolvePrincipalFrom('shared.name', [HIGH, LOW, SYSTEM], SYSTEM_USER_ID);
|
|
129
|
+
|
|
130
|
+
expect(result.user).toBe(LOW);
|
|
131
|
+
expect(result.reason).toBe('name');
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('resolves a duplicated Name identically however the cache is ordered', () => {
|
|
135
|
+
const forward = resolvePrincipalFrom('shared.name', [HIGH, LOW, SYSTEM], SYSTEM_USER_ID);
|
|
136
|
+
const reversed = resolvePrincipalFrom('shared.name', [SYSTEM, LOW, HIGH], SYSTEM_USER_ID);
|
|
137
|
+
|
|
138
|
+
expect(reversed.user).toBe(forward.user);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('applies the same tiebreak to the Email rung', () => {
|
|
142
|
+
// `UQ_User_Email` should make this unreachable from the database, but `UserCache` is
|
|
143
|
+
// also written through `SetUsers` and mutated in place by `Users.push`, so the rung
|
|
144
|
+
// must not depend on the constraint holding in memory.
|
|
145
|
+
const lowEmail: ResolvablePrincipal = { ...LOW, Name: 'a.person', Email: 'shared@acme.com' };
|
|
146
|
+
const highEmail: ResolvablePrincipal = { ...HIGH, Name: 'b.person', Email: 'shared@acme.com' };
|
|
147
|
+
|
|
148
|
+
const result = resolvePrincipalFrom('shared@acme.com', [highEmail, lowEmail, SYSTEM], SYSTEM_USER_ID);
|
|
149
|
+
|
|
150
|
+
expect(result.user).toBe(lowEmail);
|
|
151
|
+
expect(result.reason).toBe('email');
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
describe('a DEACTIVATED configured candidate', () => {
|
|
156
|
+
/**
|
|
157
|
+
* The case an operator actually hits: the admin whose address sits in
|
|
158
|
+
* `contextUserForProvisioning` leaves, and their account is deactivated. Every other rung
|
|
159
|
+
* requires `IsActive`, so without this one the ladder would read "we never act as a
|
|
160
|
+
* disabled user, except the one the operator named" — and provisioning would keep running,
|
|
161
|
+
* silently, as someone who no longer works here.
|
|
162
|
+
*/
|
|
163
|
+
const RETIRED: ResolvablePrincipal = {
|
|
164
|
+
ID: 'BBBB2222-0000-0000-0000-000000000002',
|
|
165
|
+
Name: 'departed@acme.com',
|
|
166
|
+
Email: 'departed@acme.com',
|
|
167
|
+
Type: 'Owner ',
|
|
168
|
+
IsActive: false,
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
it('does not resolve a deactivated candidate by Email', () => {
|
|
172
|
+
const result = resolvePrincipalFrom('departed@acme.com', [...STOCK_HOST, RETIRED], SYSTEM_USER_ID);
|
|
173
|
+
|
|
174
|
+
expect(result.user).toBe(SYSTEM);
|
|
175
|
+
expect(result.reason).toBe('system');
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it('does not resolve a deactivated candidate by Name', () => {
|
|
179
|
+
// The Name rung is the compatibility rung and is tried first, so it needs the guard
|
|
180
|
+
// most: without it the guarantee holds only for the rungs nobody configures.
|
|
181
|
+
const renamed: ResolvablePrincipal = { ...RETIRED, Name: 'ops-service-account', Email: 'ops@acme.com' };
|
|
182
|
+
|
|
183
|
+
const result = resolvePrincipalFrom('ops-service-account', [...STOCK_HOST, renamed], SYSTEM_USER_ID);
|
|
184
|
+
|
|
185
|
+
expect(result.user).toBe(SYSTEM);
|
|
186
|
+
expect(result.reason).toBe('system');
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it('reports that the candidate matched but is inactive, not that it matched nothing', () => {
|
|
190
|
+
const result = resolvePrincipalFrom('departed@acme.com', [...STOCK_HOST, RETIRED], SYSTEM_USER_ID);
|
|
191
|
+
|
|
192
|
+
// "matched no user's Name or Email" would send the operator hunting for a typo in a
|
|
193
|
+
// value that is spelled correctly. The fix here is to reactivate the account or name a
|
|
194
|
+
// different user, and the message has to point at that one.
|
|
195
|
+
expect(result.warning).toMatch(/inactive/i);
|
|
196
|
+
expect(result.warning).not.toMatch(/matched no user/i);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it('advances to the next rung rather than stopping at the inactive match', () => {
|
|
200
|
+
// An inactive Name match must not shadow an ACTIVE Email match. The ladder's job is to
|
|
201
|
+
// find the user the string names, and at that point it has not run out of places to
|
|
202
|
+
// look — this is #4209's own shape, with the Name holder deactivated.
|
|
203
|
+
const inactiveByName: ResolvablePrincipal = {
|
|
204
|
+
ID: 'CCCC3333-0000-0000-0000-000000000003',
|
|
205
|
+
Name: 'ops@acme.com',
|
|
206
|
+
Email: 'old-ops@acme.com',
|
|
207
|
+
Type: 'User ',
|
|
208
|
+
IsActive: false,
|
|
209
|
+
};
|
|
210
|
+
const activeByEmail: ResolvablePrincipal = {
|
|
211
|
+
ID: 'DDDD4444-0000-0000-0000-000000000004',
|
|
212
|
+
Name: 'Ops Service Account',
|
|
213
|
+
Email: 'ops@acme.com',
|
|
214
|
+
Type: 'User ',
|
|
215
|
+
IsActive: true,
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
const result = resolvePrincipalFrom('ops@acme.com', [inactiveByName, activeByEmail], SYSTEM_USER_ID);
|
|
219
|
+
|
|
220
|
+
expect(result.user).toBe(activeByEmail);
|
|
221
|
+
expect(result.reason).toBe('email');
|
|
222
|
+
expect(result.warning).toBeUndefined();
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
describe('fallbacks', () => {
|
|
226
|
+
it('falls back to the System user rather than an arbitrary Owner when the candidate is unresolvable', () => {
|
|
227
|
+
const result = resolvePrincipalFrom('nobody@nowhere.example', STOCK_HOST, SYSTEM_USER_ID);
|
|
228
|
+
|
|
229
|
+
expect(result.user).toBe(SYSTEM);
|
|
230
|
+
expect(result.reason).toBe('system');
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it('warns when a configured candidate did not resolve, naming the candidate', () => {
|
|
234
|
+
const result = resolvePrincipalFrom('nobody@nowhere.example', STOCK_HOST, SYSTEM_USER_ID);
|
|
235
|
+
|
|
236
|
+
expect(result.warning).toContain('nobody@nowhere.example');
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it('falls back to the System user WITHOUT warning when no candidate is configured', () => {
|
|
240
|
+
// Zod defaults this setting to '', so an unset/blank value must not be reported as a
|
|
241
|
+
// misconfiguration — but it must still land somewhere deterministic.
|
|
242
|
+
const result = resolvePrincipalFrom('', STOCK_HOST, SYSTEM_USER_ID);
|
|
243
|
+
|
|
244
|
+
expect(result.user).toBe(SYSTEM);
|
|
245
|
+
expect(result.warning).toBeUndefined();
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it('picks the lowest-ID active Owner when the deployment has no System user', () => {
|
|
249
|
+
// Last resort, and still deterministic — never "whichever Owner sorts first in the cache".
|
|
250
|
+
const laterOwner: ResolvablePrincipal = {
|
|
251
|
+
ID: 'FFFF9999-0000-0000-0000-000000000009',
|
|
252
|
+
Name: 'zoe@acme.com',
|
|
253
|
+
Email: 'zoe@acme.com',
|
|
254
|
+
Type: 'Owner ',
|
|
255
|
+
IsActive: true,
|
|
256
|
+
};
|
|
257
|
+
const users = [laterOwner, HOST_ADMIN, ANONYMOUS];
|
|
258
|
+
|
|
259
|
+
const result = resolvePrincipalFrom('nobody@nowhere.example', users, SYSTEM_USER_ID);
|
|
260
|
+
|
|
261
|
+
expect(result.user).toBe(HOST_ADMIN);
|
|
262
|
+
expect(result.reason).toBe('owner');
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it('never returns an inactive user as the Owner fallback', () => {
|
|
266
|
+
const inactiveOwner: ResolvablePrincipal = {
|
|
267
|
+
ID: '11111111-0000-0000-0000-000000000000', // sorts first, so only IsActive can exclude it
|
|
268
|
+
Name: 'retired@acme.com',
|
|
269
|
+
Email: 'retired@acme.com',
|
|
270
|
+
Type: 'Owner',
|
|
271
|
+
IsActive: false,
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
const result = resolvePrincipalFrom('nobody@nowhere.example', [inactiveOwner, HOST_ADMIN], SYSTEM_USER_ID);
|
|
275
|
+
|
|
276
|
+
expect(result.user).toBe(HOST_ADMIN);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it('does not fall back to a DEACTIVATED system user', () => {
|
|
280
|
+
// The Owner rung requires IsActive; the system rung must too, or the guarantee is
|
|
281
|
+
// "we never act as a disabled user, except as the one user we reach first".
|
|
282
|
+
const inactiveSystem: ResolvablePrincipal = { ...SYSTEM, IsActive: false };
|
|
283
|
+
|
|
284
|
+
const result = resolvePrincipalFrom('nobody@nowhere.example', [inactiveSystem, HOST_ADMIN], SYSTEM_USER_ID);
|
|
285
|
+
|
|
286
|
+
expect(result.user).toBe(HOST_ADMIN);
|
|
287
|
+
expect(result.reason).toBe('owner');
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
it('returns no principal when the system user is deactivated and no active Owner remains', () => {
|
|
291
|
+
const inactiveSystem: ResolvablePrincipal = { ...SYSTEM, IsActive: false };
|
|
292
|
+
|
|
293
|
+
const result = resolvePrincipalFrom('nobody@nowhere.example', [inactiveSystem, ANONYMOUS], SYSTEM_USER_ID);
|
|
294
|
+
|
|
295
|
+
expect(result.user).toBeNull();
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
it('returns no principal at all when the cache is empty', () => {
|
|
299
|
+
const result = resolvePrincipalFrom(SHIPPED_DEFAULT, [], SYSTEM_USER_ID);
|
|
300
|
+
|
|
301
|
+
expect(result.user).toBeNull();
|
|
302
|
+
expect(result.reason).toBe('none');
|
|
303
|
+
});
|
|
304
|
+
});
|
|
305
|
+
});
|