@zapier/zapier-sdk 0.15.3 → 0.15.8
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/CHANGELOG.md +30 -0
- package/dist/api/auth.d.ts +10 -0
- package/dist/api/auth.d.ts.map +1 -1
- package/dist/api/auth.js +45 -0
- package/dist/api/auth.test.d.ts +2 -0
- package/dist/api/auth.test.d.ts.map +1 -0
- package/dist/api/auth.test.js +220 -0
- package/dist/api/client.d.ts.map +1 -1
- package/dist/api/client.js +18 -32
- package/dist/api/client.methods.test.d.ts +2 -0
- package/dist/api/client.methods.test.d.ts.map +1 -0
- package/dist/api/client.methods.test.js +158 -0
- package/dist/api/client.test.js +27 -11
- package/dist/api/router.d.ts +16 -0
- package/dist/api/router.d.ts.map +1 -0
- package/dist/api/router.js +37 -0
- package/dist/api/router.test.d.ts +2 -0
- package/dist/api/router.test.d.ts.map +1 -0
- package/dist/api/router.test.js +109 -0
- package/dist/api/schemas.d.ts +38 -38
- package/dist/auth.d.ts +15 -0
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +25 -0
- package/dist/index.cjs +350 -87
- package/dist/index.d.mts +430 -269
- package/dist/index.mjs +350 -88
- package/dist/plugins/eventEmission/index.d.ts +1 -1
- package/dist/plugins/eventEmission/index.d.ts.map +1 -1
- package/dist/plugins/eventEmission/index.js +94 -22
- package/dist/plugins/eventEmission/index.test.js +340 -2
- package/dist/plugins/getAuthentication/index.d.ts +2 -5
- package/dist/plugins/getAuthentication/index.d.ts.map +1 -1
- package/dist/plugins/getAuthentication/index.js +3 -24
- package/dist/plugins/getAuthentication/index.test.js +32 -144
- package/dist/plugins/getAuthentication/schemas.d.ts +4 -13
- package/dist/plugins/getAuthentication/schemas.d.ts.map +1 -1
- package/dist/plugins/getAuthentication/schemas.js +1 -11
- package/dist/schemas/Action.d.ts +1 -1
- package/dist/schemas/Auth.d.ts +6 -6
- package/dist/sdk.d.ts +1 -1
- package/dist/temporary-internal-core/handlers/getAuthentication.d.ts +94 -0
- package/dist/temporary-internal-core/handlers/getAuthentication.d.ts.map +1 -0
- package/dist/temporary-internal-core/handlers/getAuthentication.js +68 -0
- package/dist/temporary-internal-core/handlers/getAuthentication.test.d.ts +2 -0
- package/dist/temporary-internal-core/handlers/getAuthentication.test.d.ts.map +1 -0
- package/dist/temporary-internal-core/handlers/getAuthentication.test.js +248 -0
- package/dist/temporary-internal-core/handlers/listApps.js +1 -1
- package/dist/temporary-internal-core/index.d.ts +2 -0
- package/dist/temporary-internal-core/index.d.ts.map +1 -1
- package/dist/temporary-internal-core/index.js +2 -0
- package/dist/temporary-internal-core/schemas/authentications/index.d.ts +454 -0
- package/dist/temporary-internal-core/schemas/authentications/index.d.ts.map +1 -0
- package/dist/temporary-internal-core/schemas/authentications/index.js +96 -0
- package/dist/temporary-internal-core/schemas/errors/index.d.ts +139 -0
- package/dist/temporary-internal-core/schemas/errors/index.d.ts.map +1 -0
- package/dist/temporary-internal-core/schemas/errors/index.js +129 -0
- package/dist/temporary-internal-core/utils/app-locators.d.ts +0 -20
- package/dist/temporary-internal-core/utils/app-locators.d.ts.map +1 -1
- package/dist/temporary-internal-core/utils/app-locators.js +1 -45
- package/dist/temporary-internal-core/utils/string-utils.d.ts +28 -0
- package/dist/temporary-internal-core/utils/string-utils.d.ts.map +1 -0
- package/dist/temporary-internal-core/utils/string-utils.js +52 -0
- package/dist/temporary-internal-core/utils/transformations.d.ts +14 -0
- package/dist/temporary-internal-core/utils/transformations.d.ts.map +1 -1
- package/dist/temporary-internal-core/utils/transformations.js +37 -1
- package/package.json +1 -1
|
@@ -6,14 +6,18 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { createTransport } from "./transport";
|
|
8
8
|
import { generateEventId, getCurrentTimestamp, getReleaseId } from "./utils";
|
|
9
|
+
import { extractUserIdsFromJwt } from "../../api/auth";
|
|
10
|
+
import { resolveAuthToken } from "../../auth";
|
|
9
11
|
import { buildApplicationLifecycleEvent, buildErrorEventWithContext, } from "./builders";
|
|
10
12
|
import { getTrackingBaseUrl } from "../../utils/url-utils";
|
|
13
|
+
// Maximum time to wait for telemetry emission before allowing process to exit
|
|
14
|
+
const TELEMETRY_EMIT_TIMEOUT_MS = 300;
|
|
11
15
|
const APPLICATION_LIFECYCLE_EVENT_SUBJECT = "platform.sdk.ApplicationLifecycleEvent";
|
|
12
16
|
const ERROR_OCCURRED_EVENT_SUBJECT = "platform.sdk.ErrorOccurredEvent";
|
|
13
17
|
// Track transport success/failure so we only log failure once.
|
|
14
18
|
const transportStates = new WeakMap();
|
|
15
19
|
// Silent emission wrapper with smart first-failure logging
|
|
16
|
-
async function silentEmit(transport, subject, event) {
|
|
20
|
+
async function silentEmit(transport, subject, event, userContextPromise) {
|
|
17
21
|
try {
|
|
18
22
|
// Get or initialize state for this transport
|
|
19
23
|
let state = transportStates.get(transport);
|
|
@@ -21,9 +25,21 @@ async function silentEmit(transport, subject, event) {
|
|
|
21
25
|
state = { hasWorked: false, hasLoggedFailure: false };
|
|
22
26
|
transportStates.set(transport, state);
|
|
23
27
|
}
|
|
28
|
+
// Resolve user context and merge into event
|
|
29
|
+
let enrichedEvent = event;
|
|
30
|
+
if (userContextPromise) {
|
|
31
|
+
try {
|
|
32
|
+
const userContext = await userContextPromise;
|
|
33
|
+
// Use Object.assign to safely merge user context into event
|
|
34
|
+
enrichedEvent = Object.assign({}, event, userContext);
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
// If user context promise fails, continue with original event
|
|
38
|
+
}
|
|
39
|
+
}
|
|
24
40
|
// Fire and forget - don't await the transport
|
|
25
41
|
transport
|
|
26
|
-
.emit(subject,
|
|
42
|
+
.emit(subject, enrichedEvent)
|
|
27
43
|
.then(() => {
|
|
28
44
|
// Mark as working if any emit succeeds
|
|
29
45
|
state.hasWorked = true;
|
|
@@ -76,6 +92,27 @@ export const eventEmissionPlugin = ({ context }) => {
|
|
|
76
92
|
: // Otherwise, use option transport or default
|
|
77
93
|
(context.options.eventEmission?.transport ?? defaultTransport),
|
|
78
94
|
};
|
|
95
|
+
// Create getUserContext promise for dynamic user context injection
|
|
96
|
+
const getUserContext = (async () => {
|
|
97
|
+
try {
|
|
98
|
+
const token = await resolveAuthToken({
|
|
99
|
+
token: context.options.token,
|
|
100
|
+
getToken: context.options.getToken,
|
|
101
|
+
baseUrl: context.options.baseUrl,
|
|
102
|
+
authBaseUrl: context.options.authBaseUrl,
|
|
103
|
+
authClientId: context.options.authClientId,
|
|
104
|
+
onEvent: context.options.onEvent,
|
|
105
|
+
fetch: context.options.fetch,
|
|
106
|
+
});
|
|
107
|
+
if (token) {
|
|
108
|
+
return extractUserIdsFromJwt(token);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
// Fall back to null context on any error
|
|
113
|
+
}
|
|
114
|
+
return { customuser_id: null, account_id: null };
|
|
115
|
+
})();
|
|
79
116
|
const startupTime = Date.now();
|
|
80
117
|
let shutdownStartTime = null;
|
|
81
118
|
// If disabled, return noop implementations
|
|
@@ -86,7 +123,7 @@ export const eventEmissionPlugin = ({ context }) => {
|
|
|
86
123
|
transport: createTransport({ type: "noop" }),
|
|
87
124
|
config,
|
|
88
125
|
emit: () => { },
|
|
89
|
-
createBaseEvent: () => ({
|
|
126
|
+
createBaseEvent: async () => ({
|
|
90
127
|
event_id: generateEventId(),
|
|
91
128
|
timestamp_ms: getCurrentTimestamp(),
|
|
92
129
|
release_id: getReleaseId(),
|
|
@@ -109,23 +146,34 @@ export const eventEmissionPlugin = ({ context }) => {
|
|
|
109
146
|
transport = createTransport({ type: "noop" });
|
|
110
147
|
}
|
|
111
148
|
// Helper to create base event
|
|
112
|
-
const createBaseEventHelper = () =>
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
149
|
+
const createBaseEventHelper = async () => {
|
|
150
|
+
const baseEvent = {
|
|
151
|
+
event_id: generateEventId(),
|
|
152
|
+
timestamp_ms: getCurrentTimestamp(),
|
|
153
|
+
release_id: getReleaseId(),
|
|
154
|
+
customuser_id: null,
|
|
155
|
+
account_id: null,
|
|
156
|
+
identity_id: null,
|
|
157
|
+
visitor_id: null,
|
|
158
|
+
correlation_id: null,
|
|
159
|
+
};
|
|
160
|
+
// Enrich with user context if available
|
|
161
|
+
try {
|
|
162
|
+
const userContext = await getUserContext;
|
|
163
|
+
return { ...baseEvent, ...userContext };
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
// Return base event if user context fails
|
|
167
|
+
return baseEvent;
|
|
168
|
+
}
|
|
169
|
+
};
|
|
122
170
|
// Register lifecycle event handlers if enabled
|
|
123
171
|
if (config.enabled) {
|
|
124
172
|
// Emit startup event
|
|
125
173
|
const startupEvent = buildApplicationLifecycleEvent({
|
|
126
174
|
lifecycle_event_type: "startup",
|
|
127
175
|
});
|
|
128
|
-
silentEmit(transport, APPLICATION_LIFECYCLE_EVENT_SUBJECT, startupEvent);
|
|
176
|
+
silentEmit(transport, APPLICATION_LIFECYCLE_EVENT_SUBJECT, startupEvent, getUserContext);
|
|
129
177
|
// Register process event handlers (Node.js only)
|
|
130
178
|
if (typeof process?.on === "function") {
|
|
131
179
|
// Handle normal process exit
|
|
@@ -141,11 +189,11 @@ export const eventEmissionPlugin = ({ context }) => {
|
|
|
141
189
|
is_graceful_shutdown: code === 0,
|
|
142
190
|
shutdown_duration_ms: shutdownDuration,
|
|
143
191
|
});
|
|
144
|
-
silentEmit(transport, APPLICATION_LIFECYCLE_EVENT_SUBJECT, exitEvent);
|
|
192
|
+
silentEmit(transport, APPLICATION_LIFECYCLE_EVENT_SUBJECT, exitEvent, getUserContext);
|
|
145
193
|
});
|
|
146
194
|
// Handle uncaught exceptions
|
|
147
195
|
process.on("uncaughtException", async (error) => {
|
|
148
|
-
|
|
196
|
+
let errorEvent = buildErrorEventWithContext({
|
|
149
197
|
error_message: error.message || "Unknown error",
|
|
150
198
|
error_type: "UncaughtException",
|
|
151
199
|
error_stack_trace: error.stack || null,
|
|
@@ -154,11 +202,19 @@ export const eventEmissionPlugin = ({ context }) => {
|
|
|
154
202
|
is_recoverable: false,
|
|
155
203
|
execution_start_time: startupTime,
|
|
156
204
|
});
|
|
205
|
+
// Enrich with user context if available
|
|
206
|
+
try {
|
|
207
|
+
const userContext = await getUserContext;
|
|
208
|
+
errorEvent = { ...errorEvent, ...userContext };
|
|
209
|
+
}
|
|
210
|
+
catch {
|
|
211
|
+
// Continue with original event if user context fails
|
|
212
|
+
}
|
|
157
213
|
// Wait up to 300ms for telemetry to send before allowing process to exit
|
|
158
214
|
try {
|
|
159
215
|
await Promise.race([
|
|
160
216
|
transport.emit(ERROR_OCCURRED_EVENT_SUBJECT, errorEvent),
|
|
161
|
-
new Promise((resolve) => setTimeout(resolve,
|
|
217
|
+
new Promise((resolve) => setTimeout(resolve, TELEMETRY_EMIT_TIMEOUT_MS)),
|
|
162
218
|
]);
|
|
163
219
|
}
|
|
164
220
|
catch {
|
|
@@ -174,7 +230,7 @@ export const eventEmissionPlugin = ({ context }) => {
|
|
|
174
230
|
? reason
|
|
175
231
|
: "Unhandled promise rejection";
|
|
176
232
|
const errorStack = reason instanceof Error ? reason.stack : null;
|
|
177
|
-
|
|
233
|
+
let errorEvent = buildErrorEventWithContext({
|
|
178
234
|
error_message: errorMessage,
|
|
179
235
|
error_type: "UnhandledRejection",
|
|
180
236
|
error_stack_trace: errorStack,
|
|
@@ -186,11 +242,19 @@ export const eventEmissionPlugin = ({ context }) => {
|
|
|
186
242
|
promise: String(promise),
|
|
187
243
|
},
|
|
188
244
|
});
|
|
245
|
+
// Enrich with user context if available
|
|
246
|
+
try {
|
|
247
|
+
const userContext = await getUserContext;
|
|
248
|
+
errorEvent = { ...errorEvent, ...userContext };
|
|
249
|
+
}
|
|
250
|
+
catch {
|
|
251
|
+
// Continue with original event if user context fails
|
|
252
|
+
}
|
|
189
253
|
// Wait up to 300ms for telemetry to send
|
|
190
254
|
try {
|
|
191
255
|
await Promise.race([
|
|
192
256
|
transport.emit(ERROR_OCCURRED_EVENT_SUBJECT, errorEvent),
|
|
193
|
-
new Promise((resolve) => setTimeout(resolve,
|
|
257
|
+
new Promise((resolve) => setTimeout(resolve, TELEMETRY_EMIT_TIMEOUT_MS)),
|
|
194
258
|
]);
|
|
195
259
|
}
|
|
196
260
|
catch {
|
|
@@ -201,17 +265,25 @@ export const eventEmissionPlugin = ({ context }) => {
|
|
|
201
265
|
const handleSignal = async (signal) => {
|
|
202
266
|
shutdownStartTime = Date.now();
|
|
203
267
|
const uptime = Date.now() - startupTime;
|
|
204
|
-
|
|
268
|
+
let signalEvent = buildApplicationLifecycleEvent({
|
|
205
269
|
lifecycle_event_type: "signal_termination",
|
|
206
270
|
signal_name: signal,
|
|
207
271
|
uptime_ms: uptime,
|
|
208
272
|
is_graceful_shutdown: true,
|
|
209
273
|
});
|
|
274
|
+
// Enrich with user context if available
|
|
275
|
+
try {
|
|
276
|
+
const userContext = await getUserContext;
|
|
277
|
+
signalEvent = { ...signalEvent, ...userContext };
|
|
278
|
+
}
|
|
279
|
+
catch {
|
|
280
|
+
// Continue with original event if user context fails
|
|
281
|
+
}
|
|
210
282
|
// Wait up to 300ms for telemetry to send
|
|
211
283
|
try {
|
|
212
284
|
await Promise.race([
|
|
213
285
|
transport.emit(APPLICATION_LIFECYCLE_EVENT_SUBJECT, signalEvent),
|
|
214
|
-
new Promise((resolve) => setTimeout(resolve,
|
|
286
|
+
new Promise((resolve) => setTimeout(resolve, TELEMETRY_EMIT_TIMEOUT_MS)),
|
|
215
287
|
]);
|
|
216
288
|
}
|
|
217
289
|
catch {
|
|
@@ -231,7 +303,7 @@ export const eventEmissionPlugin = ({ context }) => {
|
|
|
231
303
|
transport,
|
|
232
304
|
config,
|
|
233
305
|
emit: (subject, event) => {
|
|
234
|
-
silentEmit(transport, subject, event);
|
|
306
|
+
silentEmit(transport, subject, event, getUserContext);
|
|
235
307
|
},
|
|
236
308
|
createBaseEvent: createBaseEventHelper,
|
|
237
309
|
},
|
|
@@ -12,9 +12,16 @@ const mockTransport = {
|
|
|
12
12
|
vi.mock("./transport", () => ({
|
|
13
13
|
createTransport: vi.fn(() => mockTransport),
|
|
14
14
|
}));
|
|
15
|
+
// Mock CLI login package - default to returning null context
|
|
16
|
+
const mockGetToken = vi.fn().mockResolvedValue(undefined);
|
|
17
|
+
vi.mock("@zapier/zapier-sdk-cli-login", () => ({
|
|
18
|
+
getToken: mockGetToken,
|
|
19
|
+
}));
|
|
15
20
|
describe("eventEmissionPlugin", () => {
|
|
16
21
|
beforeEach(() => {
|
|
17
22
|
vi.clearAllMocks();
|
|
23
|
+
// Reset to default behavior - no token available
|
|
24
|
+
mockGetToken.mockResolvedValue(undefined);
|
|
18
25
|
});
|
|
19
26
|
it("should create plugin with default configuration", () => {
|
|
20
27
|
const plugin = eventEmissionPlugin({
|
|
@@ -64,8 +71,13 @@ describe("eventEmissionPlugin", () => {
|
|
|
64
71
|
const testSubject = "test.event.TestEvent";
|
|
65
72
|
plugin.context.eventEmission.emit(testSubject, testEvent);
|
|
66
73
|
// Give async emission time to complete
|
|
67
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
68
|
-
|
|
74
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
75
|
+
// The event will be enriched with user context (null values)
|
|
76
|
+
expect(mockTransport.emit).toHaveBeenCalledWith(testSubject, {
|
|
77
|
+
...testEvent,
|
|
78
|
+
customuser_id: null,
|
|
79
|
+
account_id: null,
|
|
80
|
+
});
|
|
69
81
|
});
|
|
70
82
|
it("should handle transport creation failures silently", () => {
|
|
71
83
|
// Mock createTransport to throw an error
|
|
@@ -220,6 +232,8 @@ describe("eventEmissionPlugin", () => {
|
|
|
220
232
|
mockConsoleWarn.mockRestore();
|
|
221
233
|
});
|
|
222
234
|
it("should merge options with defaults", () => {
|
|
235
|
+
// Override env var to ensure proper transport is used
|
|
236
|
+
vi.stubEnv("ZAPIER_SDK_TELEMETRY_TRANSPORT", undefined);
|
|
223
237
|
const plugin = eventEmissionPlugin({
|
|
224
238
|
sdk: {},
|
|
225
239
|
context: {
|
|
@@ -239,5 +253,329 @@ describe("eventEmissionPlugin", () => {
|
|
|
239
253
|
type: "http",
|
|
240
254
|
endpoint: "https://example.com",
|
|
241
255
|
});
|
|
256
|
+
vi.unstubAllEnvs();
|
|
257
|
+
});
|
|
258
|
+
it("should extract user IDs from JWT token and include in events", async () => {
|
|
259
|
+
// Create a test JWT token with user data
|
|
260
|
+
// JWT format: header.payload.signature
|
|
261
|
+
const header = { alg: "HS256", typ: "JWT" };
|
|
262
|
+
const payload = {
|
|
263
|
+
"zap:acc": "12345",
|
|
264
|
+
sub: "67890",
|
|
265
|
+
sub_type: "customuser",
|
|
266
|
+
"zap:uname": "test@example.com",
|
|
267
|
+
};
|
|
268
|
+
const encodedHeader = Buffer.from(JSON.stringify(header)).toString("base64url");
|
|
269
|
+
const encodedPayload = Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
270
|
+
const testJwt = `${encodedHeader}.${encodedPayload}.fake-signature`;
|
|
271
|
+
// Mock getToken to return the JWT
|
|
272
|
+
mockGetToken.mockResolvedValue(testJwt);
|
|
273
|
+
const plugin = eventEmissionPlugin({
|
|
274
|
+
sdk: {},
|
|
275
|
+
context: {
|
|
276
|
+
meta: {},
|
|
277
|
+
options: {
|
|
278
|
+
eventEmission: {
|
|
279
|
+
enabled: true,
|
|
280
|
+
transport: { type: "console" },
|
|
281
|
+
},
|
|
282
|
+
},
|
|
283
|
+
},
|
|
284
|
+
});
|
|
285
|
+
// Test that createBaseEvent includes the extracted user IDs
|
|
286
|
+
const baseEvent = await plugin.context.eventEmission.createBaseEvent();
|
|
287
|
+
expect(baseEvent.customuser_id).toBe(67890);
|
|
288
|
+
expect(baseEvent.account_id).toBe(12345);
|
|
289
|
+
});
|
|
290
|
+
it("should handle service tokens with nested JWT", async () => {
|
|
291
|
+
// Create a nested JWT for service token testing
|
|
292
|
+
const nestedHeader = { alg: "HS256", typ: "JWT" };
|
|
293
|
+
const nestedPayload = {
|
|
294
|
+
"zap:acc": "99999",
|
|
295
|
+
sub: "88888",
|
|
296
|
+
sub_type: "customuser",
|
|
297
|
+
};
|
|
298
|
+
const nestedEncodedHeader = Buffer.from(JSON.stringify(nestedHeader)).toString("base64url");
|
|
299
|
+
const nestedEncodedPayload = Buffer.from(JSON.stringify(nestedPayload)).toString("base64url");
|
|
300
|
+
const nestedJwt = `${nestedEncodedHeader}.${nestedEncodedPayload}.nested-signature`;
|
|
301
|
+
// Create the service token that wraps the nested JWT
|
|
302
|
+
const serviceHeader = { alg: "HS256", typ: "JWT" };
|
|
303
|
+
const servicePayload = {
|
|
304
|
+
"zap:acc": "11111",
|
|
305
|
+
sub: "22222",
|
|
306
|
+
sub_type: "service",
|
|
307
|
+
njwt: nestedJwt,
|
|
308
|
+
};
|
|
309
|
+
const serviceEncodedHeader = Buffer.from(JSON.stringify(serviceHeader)).toString("base64url");
|
|
310
|
+
const serviceEncodedPayload = Buffer.from(JSON.stringify(servicePayload)).toString("base64url");
|
|
311
|
+
const serviceJwt = `${serviceEncodedHeader}.${serviceEncodedPayload}.service-signature`;
|
|
312
|
+
// Mock getToken to return the service JWT
|
|
313
|
+
mockGetToken.mockResolvedValue(serviceJwt);
|
|
314
|
+
const plugin = eventEmissionPlugin({
|
|
315
|
+
sdk: {},
|
|
316
|
+
context: {
|
|
317
|
+
meta: {},
|
|
318
|
+
options: {
|
|
319
|
+
eventEmission: {
|
|
320
|
+
enabled: true,
|
|
321
|
+
transport: { type: "console" },
|
|
322
|
+
},
|
|
323
|
+
},
|
|
324
|
+
},
|
|
325
|
+
});
|
|
326
|
+
const baseEvent = await plugin.context.eventEmission.createBaseEvent();
|
|
327
|
+
// Should extract from nested JWT, not the service token
|
|
328
|
+
expect(baseEvent.customuser_id).toBe(88888);
|
|
329
|
+
expect(baseEvent.account_id).toBe(99999);
|
|
330
|
+
});
|
|
331
|
+
it("should handle invalid JWT tokens gracefully", async () => {
|
|
332
|
+
// Mock getToken to return an invalid JWT
|
|
333
|
+
mockGetToken.mockResolvedValue("not-a-valid-jwt-token");
|
|
334
|
+
const plugin = eventEmissionPlugin({
|
|
335
|
+
sdk: {},
|
|
336
|
+
context: {
|
|
337
|
+
meta: {},
|
|
338
|
+
options: {
|
|
339
|
+
eventEmission: {
|
|
340
|
+
enabled: true,
|
|
341
|
+
transport: { type: "console" },
|
|
342
|
+
},
|
|
343
|
+
},
|
|
344
|
+
},
|
|
345
|
+
});
|
|
346
|
+
const baseEvent = await plugin.context.eventEmission.createBaseEvent();
|
|
347
|
+
// Should default to null when JWT is invalid
|
|
348
|
+
expect(baseEvent.customuser_id).toBe(null);
|
|
349
|
+
expect(baseEvent.account_id).toBe(null);
|
|
350
|
+
});
|
|
351
|
+
it("should handle missing token gracefully", async () => {
|
|
352
|
+
// mockGetToken defaults to returning undefined (no token)
|
|
353
|
+
const plugin = eventEmissionPlugin({
|
|
354
|
+
sdk: {},
|
|
355
|
+
context: {
|
|
356
|
+
meta: {},
|
|
357
|
+
options: {
|
|
358
|
+
eventEmission: {
|
|
359
|
+
enabled: true,
|
|
360
|
+
transport: { type: "console" },
|
|
361
|
+
},
|
|
362
|
+
},
|
|
363
|
+
},
|
|
364
|
+
});
|
|
365
|
+
const baseEvent = await plugin.context.eventEmission.createBaseEvent();
|
|
366
|
+
// Should default to null when no token is provided
|
|
367
|
+
expect(baseEvent.customuser_id).toBe(null);
|
|
368
|
+
expect(baseEvent.account_id).toBe(null);
|
|
369
|
+
});
|
|
370
|
+
it("should extract user IDs when getToken returns valid JWT", async () => {
|
|
371
|
+
// Create a test JWT token
|
|
372
|
+
const header = { alg: "HS256", typ: "JWT" };
|
|
373
|
+
const payload = {
|
|
374
|
+
"zap:acc": "98765",
|
|
375
|
+
sub: "54321",
|
|
376
|
+
sub_type: "customuser",
|
|
377
|
+
};
|
|
378
|
+
const encodedHeader = Buffer.from(JSON.stringify(header)).toString("base64url");
|
|
379
|
+
const encodedPayload = Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
380
|
+
const testJwt = `${encodedHeader}.${encodedPayload}.test-signature`;
|
|
381
|
+
// Mock getToken to return the JWT
|
|
382
|
+
mockGetToken.mockResolvedValue(testJwt);
|
|
383
|
+
const plugin = eventEmissionPlugin({
|
|
384
|
+
sdk: {},
|
|
385
|
+
context: {
|
|
386
|
+
meta: {},
|
|
387
|
+
options: {
|
|
388
|
+
eventEmission: {
|
|
389
|
+
enabled: true,
|
|
390
|
+
transport: { type: "console" },
|
|
391
|
+
},
|
|
392
|
+
},
|
|
393
|
+
},
|
|
394
|
+
});
|
|
395
|
+
// Test that createBaseEvent includes the extracted user IDs
|
|
396
|
+
const baseEvent = await plugin.context.eventEmission.createBaseEvent();
|
|
397
|
+
expect(baseEvent.customuser_id).toBe(54321);
|
|
398
|
+
expect(baseEvent.account_id).toBe(98765);
|
|
399
|
+
});
|
|
400
|
+
it("should handle getToken failures gracefully", async () => {
|
|
401
|
+
// Mock getToken to reject/throw
|
|
402
|
+
mockGetToken.mockRejectedValue(new Error("Token fetch failed"));
|
|
403
|
+
const plugin = eventEmissionPlugin({
|
|
404
|
+
sdk: {},
|
|
405
|
+
context: {
|
|
406
|
+
meta: {},
|
|
407
|
+
options: {
|
|
408
|
+
eventEmission: {
|
|
409
|
+
enabled: true,
|
|
410
|
+
transport: { type: "console" },
|
|
411
|
+
},
|
|
412
|
+
},
|
|
413
|
+
},
|
|
414
|
+
});
|
|
415
|
+
const baseEvent = await plugin.context.eventEmission.createBaseEvent();
|
|
416
|
+
// Should gracefully fall back to null context
|
|
417
|
+
expect(baseEvent.customuser_id).toBe(null);
|
|
418
|
+
expect(baseEvent.account_id).toBe(null);
|
|
419
|
+
});
|
|
420
|
+
it("should extract user IDs from static token in SDK options", async () => {
|
|
421
|
+
// Create a test JWT token
|
|
422
|
+
const header = { alg: "HS256", typ: "JWT" };
|
|
423
|
+
const payload = {
|
|
424
|
+
"zap:acc": "11111",
|
|
425
|
+
sub: "22222",
|
|
426
|
+
sub_type: "customuser",
|
|
427
|
+
};
|
|
428
|
+
const encodedHeader = Buffer.from(JSON.stringify(header)).toString("base64url");
|
|
429
|
+
const encodedPayload = Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
430
|
+
const testJwt = `${encodedHeader}.${encodedPayload}.static-signature`;
|
|
431
|
+
const plugin = eventEmissionPlugin({
|
|
432
|
+
sdk: {},
|
|
433
|
+
context: {
|
|
434
|
+
meta: {},
|
|
435
|
+
options: {
|
|
436
|
+
token: testJwt,
|
|
437
|
+
eventEmission: {
|
|
438
|
+
enabled: true,
|
|
439
|
+
transport: { type: "console" },
|
|
440
|
+
},
|
|
441
|
+
},
|
|
442
|
+
},
|
|
443
|
+
});
|
|
444
|
+
const baseEvent = await plugin.context.eventEmission.createBaseEvent();
|
|
445
|
+
// Should extract from static token in options
|
|
446
|
+
expect(baseEvent.customuser_id).toBe(22222);
|
|
447
|
+
expect(baseEvent.account_id).toBe(11111);
|
|
448
|
+
// CLI login package should not be called when token is in options
|
|
449
|
+
expect(mockGetToken).not.toHaveBeenCalled();
|
|
450
|
+
});
|
|
451
|
+
it("should extract user IDs from getToken function in SDK options", async () => {
|
|
452
|
+
// Create a test JWT token
|
|
453
|
+
const header = { alg: "HS256", typ: "JWT" };
|
|
454
|
+
const payload = {
|
|
455
|
+
"zap:acc": "33333",
|
|
456
|
+
sub: "44444",
|
|
457
|
+
sub_type: "customuser",
|
|
458
|
+
};
|
|
459
|
+
const encodedHeader = Buffer.from(JSON.stringify(header)).toString("base64url");
|
|
460
|
+
const encodedPayload = Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
461
|
+
const testJwt = `${encodedHeader}.${encodedPayload}.custom-signature`;
|
|
462
|
+
const customGetToken = vi.fn().mockResolvedValue(testJwt);
|
|
463
|
+
const plugin = eventEmissionPlugin({
|
|
464
|
+
sdk: {},
|
|
465
|
+
context: {
|
|
466
|
+
meta: {},
|
|
467
|
+
options: {
|
|
468
|
+
getToken: customGetToken,
|
|
469
|
+
eventEmission: {
|
|
470
|
+
enabled: true,
|
|
471
|
+
transport: { type: "console" },
|
|
472
|
+
},
|
|
473
|
+
},
|
|
474
|
+
},
|
|
475
|
+
});
|
|
476
|
+
const baseEvent = await plugin.context.eventEmission.createBaseEvent();
|
|
477
|
+
// Should extract from custom getToken function
|
|
478
|
+
expect(baseEvent.customuser_id).toBe(44444);
|
|
479
|
+
expect(baseEvent.account_id).toBe(33333);
|
|
480
|
+
expect(customGetToken).toHaveBeenCalled();
|
|
481
|
+
// CLI login package should not be called when getToken is in options
|
|
482
|
+
expect(mockGetToken).not.toHaveBeenCalled();
|
|
483
|
+
});
|
|
484
|
+
it("should prioritize static token over getToken function", async () => {
|
|
485
|
+
// Create test JWT tokens
|
|
486
|
+
const staticHeader = { alg: "HS256", typ: "JWT" };
|
|
487
|
+
const staticPayload = {
|
|
488
|
+
"zap:acc": "55555",
|
|
489
|
+
sub: "66666",
|
|
490
|
+
sub_type: "customuser",
|
|
491
|
+
};
|
|
492
|
+
const staticEncodedHeader = Buffer.from(JSON.stringify(staticHeader)).toString("base64url");
|
|
493
|
+
const staticEncodedPayload = Buffer.from(JSON.stringify(staticPayload)).toString("base64url");
|
|
494
|
+
const staticJwt = `${staticEncodedHeader}.${staticEncodedPayload}.static-sig`;
|
|
495
|
+
const customGetToken = vi.fn().mockResolvedValue("should-not-be-used");
|
|
496
|
+
const plugin = eventEmissionPlugin({
|
|
497
|
+
sdk: {},
|
|
498
|
+
context: {
|
|
499
|
+
meta: {},
|
|
500
|
+
options: {
|
|
501
|
+
token: staticJwt,
|
|
502
|
+
getToken: customGetToken,
|
|
503
|
+
eventEmission: {
|
|
504
|
+
enabled: true,
|
|
505
|
+
transport: { type: "console" },
|
|
506
|
+
},
|
|
507
|
+
},
|
|
508
|
+
},
|
|
509
|
+
});
|
|
510
|
+
const baseEvent = await plugin.context.eventEmission.createBaseEvent();
|
|
511
|
+
// Should use static token, not getToken
|
|
512
|
+
expect(baseEvent.customuser_id).toBe(66666);
|
|
513
|
+
expect(baseEvent.account_id).toBe(55555);
|
|
514
|
+
expect(customGetToken).not.toHaveBeenCalled();
|
|
515
|
+
expect(mockGetToken).not.toHaveBeenCalled();
|
|
516
|
+
});
|
|
517
|
+
it("should fall back to CLI login when SDK options have no token", async () => {
|
|
518
|
+
// Create a test JWT token for CLI login
|
|
519
|
+
const header = { alg: "HS256", typ: "JWT" };
|
|
520
|
+
const payload = {
|
|
521
|
+
"zap:acc": "77777",
|
|
522
|
+
sub: "88888",
|
|
523
|
+
sub_type: "customuser",
|
|
524
|
+
};
|
|
525
|
+
const encodedHeader = Buffer.from(JSON.stringify(header)).toString("base64url");
|
|
526
|
+
const encodedPayload = Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
527
|
+
const testJwt = `${encodedHeader}.${encodedPayload}.cli-signature`;
|
|
528
|
+
mockGetToken.mockResolvedValue(testJwt);
|
|
529
|
+
const plugin = eventEmissionPlugin({
|
|
530
|
+
sdk: {},
|
|
531
|
+
context: {
|
|
532
|
+
meta: {},
|
|
533
|
+
options: {
|
|
534
|
+
// No token or getToken in options
|
|
535
|
+
eventEmission: {
|
|
536
|
+
enabled: true,
|
|
537
|
+
transport: { type: "console" },
|
|
538
|
+
},
|
|
539
|
+
},
|
|
540
|
+
},
|
|
541
|
+
});
|
|
542
|
+
const baseEvent = await plugin.context.eventEmission.createBaseEvent();
|
|
543
|
+
// Should fall back to CLI login package
|
|
544
|
+
expect(baseEvent.customuser_id).toBe(88888);
|
|
545
|
+
expect(baseEvent.account_id).toBe(77777);
|
|
546
|
+
expect(mockGetToken).toHaveBeenCalled();
|
|
547
|
+
});
|
|
548
|
+
it("should handle custom getToken returning undefined", async () => {
|
|
549
|
+
const customGetToken = vi.fn().mockResolvedValue(undefined);
|
|
550
|
+
// Also mock CLI login to return a token
|
|
551
|
+
const header = { alg: "HS256", typ: "JWT" };
|
|
552
|
+
const payload = {
|
|
553
|
+
"zap:acc": "99999",
|
|
554
|
+
sub: "10101",
|
|
555
|
+
sub_type: "customuser",
|
|
556
|
+
};
|
|
557
|
+
const encodedHeader = Buffer.from(JSON.stringify(header)).toString("base64url");
|
|
558
|
+
const encodedPayload = Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
559
|
+
const testJwt = `${encodedHeader}.${encodedPayload}.fallback-signature`;
|
|
560
|
+
mockGetToken.mockResolvedValue(testJwt);
|
|
561
|
+
const plugin = eventEmissionPlugin({
|
|
562
|
+
sdk: {},
|
|
563
|
+
context: {
|
|
564
|
+
meta: {},
|
|
565
|
+
options: {
|
|
566
|
+
getToken: customGetToken,
|
|
567
|
+
eventEmission: {
|
|
568
|
+
enabled: true,
|
|
569
|
+
transport: { type: "console" },
|
|
570
|
+
},
|
|
571
|
+
},
|
|
572
|
+
},
|
|
573
|
+
});
|
|
574
|
+
const baseEvent = await plugin.context.eventEmission.createBaseEvent();
|
|
575
|
+
// Should fall back to CLI login when custom getToken returns undefined
|
|
576
|
+
expect(baseEvent.customuser_id).toBe(10101);
|
|
577
|
+
expect(baseEvent.account_id).toBe(99999);
|
|
578
|
+
expect(customGetToken).toHaveBeenCalled();
|
|
579
|
+
expect(mockGetToken).toHaveBeenCalled();
|
|
242
580
|
});
|
|
243
581
|
});
|
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
import type { Plugin } from "../../types/plugin";
|
|
2
2
|
import type { ApiClient } from "../../api";
|
|
3
|
-
import type
|
|
4
|
-
import { GetAuthenticationSchema, type GetAuthenticationOptions } from "./schemas";
|
|
3
|
+
import { GetAuthenticationSchema, type GetAuthenticationOptions, type GetAuthenticationResponse } from "./schemas";
|
|
5
4
|
export interface GetAuthenticationPluginProvides {
|
|
6
|
-
getAuthentication: (options: GetAuthenticationOptions) => Promise<
|
|
7
|
-
data: AuthenticationItem;
|
|
8
|
-
}>;
|
|
5
|
+
getAuthentication: (options: GetAuthenticationOptions) => Promise<GetAuthenticationResponse>;
|
|
9
6
|
context: {
|
|
10
7
|
meta: {
|
|
11
8
|
getAuthentication: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/plugins/getAuthentication/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/plugins/getAuthentication/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EACL,uBAAuB,EACvB,KAAK,wBAAwB,EAC7B,KAAK,yBAAyB,EAC/B,MAAM,WAAW,CAAC;AAKnB,MAAM,WAAW,+BAA+B;IAC9C,iBAAiB,EAAE,CACjB,OAAO,EAAE,wBAAwB,KAC9B,OAAO,CAAC,yBAAyB,CAAC,CAAC;IACxC,OAAO,EAAE;QACP,IAAI,EAAE;YACJ,iBAAiB,EAAE;gBACjB,WAAW,EAAE,OAAO,uBAAuB,CAAC;aAC7C,CAAC;SACH,CAAC;KACH,CAAC;CACH;AAED,eAAO,MAAM,uBAAuB,EAAE,MAAM,CAC1C,EAAE,EAAE,sBAAsB;AAC1B;IAAE,GAAG,EAAE,SAAS,CAAA;CAAE,EAAE,0BAA0B;AAC9C,+BAA+B,CA+BhC,CAAC"}
|
|
@@ -1,34 +1,13 @@
|
|
|
1
1
|
import { GetAuthenticationSchema, } from "./schemas";
|
|
2
|
-
import { ZapierAuthenticationError, ZapierResourceNotFoundError, } from "../../types/errors";
|
|
3
2
|
import { createFunction } from "../../utils/function-utils";
|
|
4
|
-
import { normalizeAuthenticationItem } from "../../utils/domain-utils";
|
|
5
3
|
import { authenticationIdGenericResolver } from "../../resolvers";
|
|
6
4
|
import { AuthenticationItemSchema } from "../../schemas/Auth";
|
|
7
5
|
export const getAuthenticationPlugin = ({ context }) => {
|
|
8
6
|
const getAuthentication = createFunction(async function getAuthentication(options) {
|
|
9
7
|
const { api } = context;
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
if (status === 401) {
|
|
14
|
-
return new ZapierAuthenticationError(`Authentication failed. Your token may not have permission to access authentications or may be expired. (HTTP ${status})`, { statusCode: status });
|
|
15
|
-
}
|
|
16
|
-
if (status === 403) {
|
|
17
|
-
return new ZapierAuthenticationError(`Access forbidden. Your token may not have the required scopes to get authentication ${authenticationId}. (HTTP ${status})`, { statusCode: status });
|
|
18
|
-
}
|
|
19
|
-
if (status === 404) {
|
|
20
|
-
return new ZapierResourceNotFoundError(`Authentication ${authenticationId} not found. It may not exist or you may not have access to it. (HTTP ${status})`, {
|
|
21
|
-
resourceType: "Authentication",
|
|
22
|
-
resourceId: String(authenticationId),
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
return undefined;
|
|
26
|
-
},
|
|
27
|
-
authRequired: true,
|
|
28
|
-
});
|
|
29
|
-
return {
|
|
30
|
-
data: normalizeAuthenticationItem(data),
|
|
31
|
-
};
|
|
8
|
+
// Call the SDK API endpoint, which will be routed to the handler via handlerOverride in pathConfig.
|
|
9
|
+
// When the handler is migrated to SDK API, this same API call will go over the network.
|
|
10
|
+
return await api.get(`/api/v0/authentications/${options.authenticationId}`);
|
|
32
11
|
}, GetAuthenticationSchema);
|
|
33
12
|
return {
|
|
34
13
|
getAuthentication,
|