@memberjunction/server 5.49.0 → 5.51.0
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 +1 -1
- package/dist/agentSessions/SessionManager.d.ts.map +1 -1
- package/dist/agentSessions/SessionManager.js +6 -1
- package/dist/agentSessions/SessionManager.js.map +1 -1
- package/dist/auth/newUsers.d.ts.map +1 -1
- package/dist/auth/newUsers.js +6 -4
- package/dist/auth/newUsers.js.map +1 -1
- package/dist/generated/generated.d.ts +259 -14
- package/dist/generated/generated.d.ts.map +1 -1
- package/dist/generated/generated.js +1380 -73
- package/dist/generated/generated.js.map +1 -1
- package/dist/realtimeWidget/WidgetSessionService.d.ts +12 -1
- package/dist/realtimeWidget/WidgetSessionService.d.ts.map +1 -1
- package/dist/realtimeWidget/WidgetSessionService.js +23 -8
- package/dist/realtimeWidget/WidgetSessionService.js.map +1 -1
- package/dist/realtimeWidget/widgetGuestElevation.d.ts +22 -0
- package/dist/realtimeWidget/widgetGuestElevation.d.ts.map +1 -1
- package/dist/realtimeWidget/widgetGuestElevation.js +36 -0
- package/dist/realtimeWidget/widgetGuestElevation.js.map +1 -1
- package/dist/resolvers/IntegrationDiscoveryResolver.d.ts.map +1 -1
- package/dist/resolvers/IntegrationDiscoveryResolver.js +9 -4
- package/dist/resolvers/IntegrationDiscoveryResolver.js.map +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts +21 -0
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.js +75 -16
- package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -1
- package/package.json +91 -89
- package/src/__tests__/RealtimeClientSessionResolver.test.ts +420 -0
- package/src/__tests__/SessionManager.test.ts +62 -0
- package/src/__tests__/WidgetSessionService.test.ts +96 -0
- package/src/__tests__/widgetGuestElevation.test.ts +70 -2
- package/src/agentSessions/SessionManager.ts +6 -1
- package/src/auth/newUsers.ts +6 -5
- package/src/generated/generated.ts +967 -55
- package/src/realtimeWidget/WidgetSessionService.ts +24 -8
- package/src/realtimeWidget/widgetGuestElevation.ts +41 -0
- package/src/resolvers/IntegrationDiscoveryResolver.ts +9 -4
- package/src/resolvers/RealtimeClientSessionResolver.ts +84 -16
|
@@ -1,6 +1,22 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
2
|
import type { DatabaseProviderBase, UserInfo } from '@memberjunction/core';
|
|
3
|
-
|
|
3
|
+
|
|
4
|
+
// Controls what UserCache.Instance.GetSystemUser() serves — undefined simulates an unpopulated
|
|
5
|
+
// cache (no system user available), the fail-closed path.
|
|
6
|
+
const getSystemUserMock = vi.fn<[], UserInfo | undefined>();
|
|
7
|
+
vi.mock('@memberjunction/sqlserver-dataprovider', () => ({
|
|
8
|
+
UserCache: {
|
|
9
|
+
get Instance() {
|
|
10
|
+
return { GetSystemUser: getSystemUserMock };
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
}));
|
|
14
|
+
|
|
15
|
+
import {
|
|
16
|
+
resolveWidgetGuestRunContext,
|
|
17
|
+
elevateUserPayload,
|
|
18
|
+
ResolveScopedAnonymousRunUser,
|
|
19
|
+
} from '../realtimeWidget/widgetGuestElevation.js';
|
|
4
20
|
import type { UserPayload } from '../types.js';
|
|
5
21
|
|
|
6
22
|
/** A provider stub — the guard paths under test return before ever touching the provider. */
|
|
@@ -31,6 +47,58 @@ describe('widgetGuestElevation — resolveWidgetGuestRunContext (guard paths, no
|
|
|
31
47
|
});
|
|
32
48
|
});
|
|
33
49
|
|
|
50
|
+
describe('widgetGuestElevation — ResolveScopedAnonymousRunUser (issue #3371)', () => {
|
|
51
|
+
const systemUser = { ID: 'system-1', Email: 'system@system.org' } as UserInfo;
|
|
52
|
+
|
|
53
|
+
/** Builds a UserInfo carrying the given per-session guest flags. */
|
|
54
|
+
function userWith(flags: Partial<UserInfo>): UserInfo {
|
|
55
|
+
return { ID: 'anon-1', Email: 'anonymous@magic-link.local', ...flags } as UserInfo;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
beforeEach(() => {
|
|
59
|
+
getSystemUserMock.mockReset();
|
|
60
|
+
getSystemUserMock.mockReturnValue(systemUser);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('returns the caller unchanged for a normal authenticated user', () => {
|
|
64
|
+
const named = userWith({ IsMagicLinkAnonymous: false, MagicLinkScope: { ResourceID: 'res-1' } });
|
|
65
|
+
expect(ResolveScopedAnonymousRunUser(named)).toBe(named);
|
|
66
|
+
expect(getSystemUserMock).not.toHaveBeenCalled();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('returns the caller unchanged for an anonymous session with no resource scope', () => {
|
|
70
|
+
const unscoped = userWith({ IsMagicLinkAnonymous: true, MagicLinkScope: undefined });
|
|
71
|
+
expect(ResolveScopedAnonymousRunUser(unscoped)).toBe(unscoped);
|
|
72
|
+
expect(getSystemUserMock).not.toHaveBeenCalled();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('returns the caller unchanged for a scoped anonymous session with an empty ResourceID', () => {
|
|
76
|
+
const emptyScope = userWith({ IsMagicLinkAnonymous: true, MagicLinkScope: { ResourceID: '' } });
|
|
77
|
+
expect(ResolveScopedAnonymousRunUser(emptyScope)).toBe(emptyScope);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('returns the caller unchanged for a PUBLIC WEB-WIDGET guest (widget guests keep their own path)', () => {
|
|
81
|
+
const widgetGuest = userWith({
|
|
82
|
+
IsMagicLinkAnonymous: true,
|
|
83
|
+
MagicLinkScope: { ResourceID: 'res-1' },
|
|
84
|
+
WidgetGuestContext: { WidgetID: 'widget-1' },
|
|
85
|
+
});
|
|
86
|
+
expect(ResolveScopedAnonymousRunUser(widgetGuest)).toBe(widgetGuest);
|
|
87
|
+
expect(getSystemUserMock).not.toHaveBeenCalled();
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('FAILS CLOSED to the caller when no system user is available', () => {
|
|
91
|
+
getSystemUserMock.mockReturnValue(undefined);
|
|
92
|
+
const scopedAnon = userWith({ IsMagicLinkAnonymous: true, MagicLinkScope: { ResourceID: 'res-1' } });
|
|
93
|
+
expect(ResolveScopedAnonymousRunUser(scopedAnon)).toBe(scopedAnon);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('returns the SYSTEM user for a scoped anonymous (non-widget) magic-link session', () => {
|
|
97
|
+
const scopedAnon = userWith({ IsMagicLinkAnonymous: true, MagicLinkScope: { ResourceID: 'res-1' } });
|
|
98
|
+
expect(ResolveScopedAnonymousRunUser(scopedAnon)).toBe(systemUser);
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
34
102
|
describe('widgetGuestElevation — elevateUserPayload', () => {
|
|
35
103
|
it('swaps in the elevated principal while preserving the guest sessionId for PubSub routing', () => {
|
|
36
104
|
const guestPayload = payloadWith({ IsMagicLinkAnonymous: true });
|
|
@@ -8,6 +8,7 @@ import { AIAgentPermissionHelper } from '@memberjunction/ai-engine-base';
|
|
|
8
8
|
import { RealtimeClientSessionService, RealtimeChannelServerHost } from '@memberjunction/ai-agents';
|
|
9
9
|
import { GetHostInstanceID } from './HostInstance.js';
|
|
10
10
|
import { writeReturningVisitorRecap } from './ReturningVisitorRecap.js';
|
|
11
|
+
import { ResolveScopedAnonymousRunUser } from '../realtimeWidget/widgetGuestElevation.js';
|
|
11
12
|
|
|
12
13
|
/** Entity names — centralised so the `MJ:`-prefix convention is applied in exactly one place. */
|
|
13
14
|
const SESSION_ENTITY = 'MJ: AI Agent Sessions';
|
|
@@ -230,10 +231,14 @@ export class SessionManager {
|
|
|
230
231
|
return;
|
|
231
232
|
}
|
|
232
233
|
try {
|
|
234
|
+
// SCOPED-ANONYMOUS ELEVATION (issue #3371): the runs were CREATED under the system user
|
|
235
|
+
// for a scoped anonymous session, so an owner-initiated (or error) close must finalize
|
|
236
|
+
// under it too — the caller's role holds no grants on the AI run entities. Janitor and
|
|
237
|
+
// shutdown sweeps already close as the system user and pass through unchanged.
|
|
233
238
|
await new RealtimeClientSessionService().FinalizeCoAgentRun(
|
|
234
239
|
config.coAgentRunID ?? null,
|
|
235
240
|
config.promptRunID ?? null,
|
|
236
|
-
contextUser,
|
|
241
|
+
ResolveScopedAnonymousRunUser(contextUser),
|
|
237
242
|
provider,
|
|
238
243
|
true,
|
|
239
244
|
config.coAgentRunStepID ?? null,
|
package/src/auth/newUsers.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { ApplicationInfo, DatabaseProviderBase, EntitySaveOptions, LogError, Log
|
|
|
2
2
|
import { RegisterClass } from "@memberjunction/global";
|
|
3
3
|
import { UserCache } from "@memberjunction/sqlserver-dataprovider";
|
|
4
4
|
import { configInfo } from "../config.js";
|
|
5
|
-
import { MJUserEntity, MJUserRoleEntity, MJUserApplicationEntity, MJUserApplicationEntityEntity, MJApplicationEntityType, MJApplicationEntityEntityType } from "@memberjunction/core-entities";
|
|
5
|
+
import { MJUserEntity, MJUserRoleEntity, MJUserApplicationEntity, MJUserApplicationEntityEntity, MJApplicationEntityType, MJApplicationEntityEntityType, UserInfoEngine } from "@memberjunction/core-entities";
|
|
6
6
|
|
|
7
7
|
export class NewUserBase {
|
|
8
8
|
public async createNewUser(firstName: string, lastName: string, email: string, linkedRecordType: string = 'None', linkedEntityId?: string, linkedEntityRecordId?: string): Promise<MJUserEntity | null> {
|
|
@@ -90,10 +90,11 @@ export class NewUserBase {
|
|
|
90
90
|
}
|
|
91
91
|
} else {
|
|
92
92
|
LogStatus(`No UserApplications configured, using DefaultForNewUser applications for new user ${user.Name}`);
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
93
|
+
// Bug F2: use the shared selector, which filters to Active apps flagged
|
|
94
|
+
// DefaultForNewUser (in DefaultSequence order). This path previously omitted the
|
|
95
|
+
// Status = 'Active' check, so an inactive default app could be provisioned here.
|
|
96
|
+
applicationsToCreate = UserInfoEngine.GetDefaultApplicationsForNewUser(md);
|
|
97
|
+
LogStatus(`Found ${applicationsToCreate.length} active applications with DefaultForNewUser=true`);
|
|
97
98
|
}
|
|
98
99
|
|
|
99
100
|
for (const [appIndex, application] of applicationsToCreate.entries()) {
|