@marcoappio/marco-config 4.0.1 → 4.0.3
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.
|
@@ -263,7 +263,7 @@ declare const rowSchemas: {
|
|
|
263
263
|
readonly operation: v.PicklistSchema<readonly ["CONNECT", "REPLACE"], undefined>;
|
|
264
264
|
readonly profile: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>, v.MaxLengthAction<string, 255, undefined>]>;
|
|
265
265
|
readonly provider: v.PicklistSchema<readonly ["GMAIL", "OUTLOOK"], undefined>;
|
|
266
|
-
readonly returnUri: v.PicklistSchema<readonly ["https://app.marcoapp.io/onboarding/
|
|
266
|
+
readonly returnUri: v.PicklistSchema<readonly ["https://app.marcoapp.io/onboarding/loading"], undefined>;
|
|
267
267
|
readonly userId: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>, v.MaxLengthAction<string, 255, undefined>]>;
|
|
268
268
|
readonly $createdAt: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, undefined>]>;
|
|
269
269
|
readonly $databaseId: v.LiteralSchema<"marco", undefined>;
|
|
@@ -481,8 +481,8 @@ export declare const appwrite: {
|
|
|
481
481
|
readonly oauth: {
|
|
482
482
|
readonly operations: readonly ["CONNECT", "REPLACE"];
|
|
483
483
|
readonly providers: readonly ["GMAIL", "OUTLOOK"];
|
|
484
|
-
readonly returnUriSchema: v.PicklistSchema<readonly ["https://app.marcoapp.io/onboarding/
|
|
485
|
-
readonly returnUris: readonly ["https://app.marcoapp.io/onboarding/
|
|
484
|
+
readonly returnUriSchema: v.PicklistSchema<readonly ["https://app.marcoapp.io/onboarding/loading"], undefined>;
|
|
485
|
+
readonly returnUris: readonly ["https://app.marcoapp.io/onboarding/loading"];
|
|
486
486
|
};
|
|
487
487
|
readonly polling: {
|
|
488
488
|
readonly shardStates: readonly ["ACTIVE", "STOPPED", "FAILED"];
|
|
@@ -710,7 +710,7 @@ export declare const appwrite: {
|
|
|
710
710
|
readonly operation: v.PicklistSchema<readonly ["CONNECT", "REPLACE"], undefined>;
|
|
711
711
|
readonly profile: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>, v.MaxLengthAction<string, 255, undefined>]>;
|
|
712
712
|
readonly provider: v.PicklistSchema<readonly ["GMAIL", "OUTLOOK"], undefined>;
|
|
713
|
-
readonly returnUri: v.PicklistSchema<readonly ["https://app.marcoapp.io/onboarding/
|
|
713
|
+
readonly returnUri: v.PicklistSchema<readonly ["https://app.marcoapp.io/onboarding/loading"], undefined>;
|
|
714
714
|
readonly userId: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>, v.MaxLengthAction<string, 255, undefined>]>;
|
|
715
715
|
readonly $createdAt: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, undefined>]>;
|
|
716
716
|
readonly $databaseId: v.LiteralSchema<"marco", undefined>;
|
|
@@ -48,7 +48,7 @@ const mailboxSyncLaneStates = ['PENDING', 'RUNNING', 'COMPLETED', 'FAILED'];
|
|
|
48
48
|
const mailboxSyncStates = ['INITIAL', 'SYNCING', 'CURRENT', 'ERROR'];
|
|
49
49
|
const oauthOperations = ['CONNECT', 'REPLACE'];
|
|
50
50
|
const oauthProviders = ['GMAIL', 'OUTLOOK'];
|
|
51
|
-
const oauthReturnUris = ['https://app.marcoapp.io/onboarding/
|
|
51
|
+
const oauthReturnUris = ['https://app.marcoapp.io/onboarding/loading'];
|
|
52
52
|
const oauthReturnUriSchema = v.picklist(oauthReturnUris);
|
|
53
53
|
const pollShardStates = ['ACTIVE', 'STOPPED', 'FAILED'];
|
|
54
54
|
const appwriteEnvironments = ['development', 'production', 'staging'];
|
|
@@ -64,7 +64,7 @@ const appwriteDeployments = {
|
|
|
64
64
|
},
|
|
65
65
|
production: {
|
|
66
66
|
applicationUrl: 'https://app.marcoapp.io',
|
|
67
|
-
endpoint: 'https://
|
|
67
|
+
endpoint: 'https://platform.marcoapp.io/v1',
|
|
68
68
|
oauthClientIds: {
|
|
69
69
|
google: '338197922176-oml1o2iputhhu3vijgue9h8sbfq17qme.apps.googleusercontent.com',
|
|
70
70
|
microsoft: 'd34106a5-5210-417e-bf38-6ddbe3aa91a7',
|
|
@@ -180,7 +180,7 @@ const validRows = {
|
|
|
180
180
|
operation: 'CONNECT',
|
|
181
181
|
profile: 'user-1',
|
|
182
182
|
provider: 'GMAIL',
|
|
183
|
-
returnUri: 'https://app.marcoapp.io/onboarding/
|
|
183
|
+
returnUri: 'https://app.marcoapp.io/onboarding/loading',
|
|
184
184
|
userId: 'user-1',
|
|
185
185
|
},
|
|
186
186
|
pollShards: {
|
|
@@ -281,7 +281,7 @@ describe('appwrite', () => {
|
|
|
281
281
|
expect(appwrite.environments).toEqual(['development', 'production', 'staging']);
|
|
282
282
|
expect(appwrite.deployments.production).toEqual({
|
|
283
283
|
applicationUrl: 'https://app.marcoapp.io',
|
|
284
|
-
endpoint: 'https://
|
|
284
|
+
endpoint: 'https://platform.marcoapp.io/v1',
|
|
285
285
|
oauthClientIds: {
|
|
286
286
|
google: '338197922176-oml1o2iputhhu3vijgue9h8sbfq17qme.apps.googleusercontent.com',
|
|
287
287
|
microsoft: 'd34106a5-5210-417e-bf38-6ddbe3aa91a7',
|
|
@@ -326,9 +326,24 @@ describe('appwrite', () => {
|
|
|
326
326
|
expect(appwrite.oauth).toMatchObject({
|
|
327
327
|
operations: ['CONNECT', 'REPLACE'],
|
|
328
328
|
providers: ['GMAIL', 'OUTLOOK'],
|
|
329
|
-
returnUris: ['https://app.marcoapp.io/onboarding/
|
|
329
|
+
returnUris: ['https://app.marcoapp.io/onboarding/loading'],
|
|
330
330
|
});
|
|
331
331
|
});
|
|
332
|
+
for (const operation of ['CONNECT', 'REPLACE']) {
|
|
333
|
+
it(`accepts a persisted ${operation} OAuth state completing at production loading`, () => {
|
|
334
|
+
expect(v.safeParse(appwrite.rowSchemas.oauthStates, { ...validRows.oauthStates, operation }).success).toBe(true);
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
for (const returnUri of [
|
|
338
|
+
'https://app.marcoapp.io/onboarding/provider-selection',
|
|
339
|
+
'https://app.staging.marcoapp.io/onboarding/loading',
|
|
340
|
+
'https://attacker.example/onboarding/loading',
|
|
341
|
+
'https://app.marcoapp.io/onboarding/loading/unexpected',
|
|
342
|
+
]) {
|
|
343
|
+
it(`rejects persisted OAuth state with unapproved return URI ${returnUri}`, () => {
|
|
344
|
+
expect(v.safeParse(appwrite.rowSchemas.oauthStates, { ...validRows.oauthStates, returnUri }).success).toBe(false);
|
|
345
|
+
});
|
|
346
|
+
}
|
|
332
347
|
it('exports the final polling-only mailbox contract', () => {
|
|
333
348
|
expect(appwrite.mailboxSync).toEqual({
|
|
334
349
|
laneStates: ['PENDING', 'RUNNING', 'COMPLETED', 'FAILED'],
|
|
@@ -18,7 +18,7 @@ export declare const appwriteFunctions: {
|
|
|
18
18
|
readonly email: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.EmailAction<string, undefined>]>;
|
|
19
19
|
readonly kind: v.LiteralSchema<"OAUTH", undefined>;
|
|
20
20
|
readonly provider: v.PicklistSchema<["GMAIL", "OUTLOOK"], undefined>;
|
|
21
|
-
readonly returnUri: v.PicklistSchema<readonly ["https://app.marcoapp.io/onboarding/
|
|
21
|
+
readonly returnUri: v.PicklistSchema<readonly ["https://app.marcoapp.io/onboarding/loading"], undefined>;
|
|
22
22
|
}, undefined>, v.StrictObjectSchema<{
|
|
23
23
|
readonly imap: v.StrictObjectSchema<{
|
|
24
24
|
readonly host: v.NonOptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.NonEmptyAction<string, undefined>, v.MaxLengthAction<string, number, undefined>]>, undefined>;
|
|
@@ -69,7 +69,7 @@ export declare const appwriteFunctions: {
|
|
|
69
69
|
readonly email: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.EmailAction<string, undefined>]>;
|
|
70
70
|
readonly kind: v.LiteralSchema<"OAUTH", undefined>;
|
|
71
71
|
readonly provider: v.PicklistSchema<["GMAIL", "OUTLOOK"], undefined>;
|
|
72
|
-
readonly returnUri: v.PicklistSchema<readonly ["https://app.marcoapp.io/onboarding/
|
|
72
|
+
readonly returnUri: v.PicklistSchema<readonly ["https://app.marcoapp.io/onboarding/loading"], undefined>;
|
|
73
73
|
}, undefined>, v.StrictObjectSchema<{
|
|
74
74
|
readonly imap: v.StrictObjectSchema<{
|
|
75
75
|
readonly host: v.NonOptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.NonEmptyAction<string, undefined>, v.MaxLengthAction<string, number, undefined>]>, undefined>;
|
|
@@ -497,7 +497,7 @@ export declare const appwriteFunctions: {
|
|
|
497
497
|
context: "webhook";
|
|
498
498
|
method: "GET";
|
|
499
499
|
path: "/v1/wh/oauth/callback";
|
|
500
|
-
requestSchema: v.
|
|
500
|
+
requestSchema: v.ObjectSchema<{
|
|
501
501
|
readonly code: v.NonOptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.NonEmptyAction<string, undefined>, v.MaxLengthAction<string, number, undefined>]>, undefined>;
|
|
502
502
|
readonly state: v.NonOptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.NonEmptyAction<string, undefined>, v.MaxLengthAction<string, number, undefined>]>, undefined>;
|
|
503
503
|
}, undefined>;
|
|
@@ -54,7 +54,7 @@ const routeFixtures = [
|
|
|
54
54
|
email: 'test-user@example.com',
|
|
55
55
|
kind: 'OAUTH',
|
|
56
56
|
provider: 'GMAIL',
|
|
57
|
-
returnUri: 'https://app.marcoapp.io/onboarding/
|
|
57
|
+
returnUri: 'https://app.marcoapp.io/onboarding/loading',
|
|
58
58
|
},
|
|
59
59
|
},
|
|
60
60
|
response: {
|
|
@@ -190,9 +190,36 @@ describe('appwriteFunctions', () => {
|
|
|
190
190
|
expect(v.safeParse(route().requestSchema, request).success).toBe(true);
|
|
191
191
|
expect(v.safeParse(route().responseSchema, response).success).toBe(true);
|
|
192
192
|
});
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
193
|
+
if (name !== 'OAuth webhook') {
|
|
194
|
+
it(`rejects caller ownership in the ${name} request and response`, () => {
|
|
195
|
+
expect(v.safeParse(route().requestSchema, { ...request, userId: 'another-user' }).success).toBe(false);
|
|
196
|
+
expect(v.safeParse(route().responseSchema, { ...response, userId: 'another-user' }).success).toBe(false);
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
it('accepts provider-owned OAuth callback parameters without passing them to the handler', () => {
|
|
201
|
+
const result = v.safeParse(appwriteFunctions.api.routes.oauthWebhook.requestSchema, {
|
|
202
|
+
authuser: '0',
|
|
203
|
+
code: 'test-authorisation-code-1',
|
|
204
|
+
iss: 'https://accounts.google.com',
|
|
205
|
+
scope: 'openid email profile',
|
|
206
|
+
state: 'test-state-1',
|
|
207
|
+
});
|
|
208
|
+
expect(result.success).toBe(true);
|
|
209
|
+
if (result.success) {
|
|
210
|
+
expect(result.output).toEqual({ code: 'test-authorisation-code-1', state: 'test-state-1' });
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
for (const { description, request } of [
|
|
214
|
+
{ description: 'missing code', request: { state: 'test-state-1' } },
|
|
215
|
+
{ description: 'empty code', request: { code: '', state: 'test-state-1' } },
|
|
216
|
+
{ description: 'oversized code', request: { code: 'a'.repeat(4097), state: 'test-state-1' } },
|
|
217
|
+
{ description: 'missing state', request: { code: 'test-authorisation-code-1' } },
|
|
218
|
+
{ description: 'empty state', request: { code: 'test-authorisation-code-1', state: '' } },
|
|
219
|
+
{ description: 'oversized state', request: { code: 'test-authorisation-code-1', state: 'a'.repeat(4097) } },
|
|
220
|
+
]) {
|
|
221
|
+
it(`rejects an OAuth callback with ${description}`, () => {
|
|
222
|
+
expect(v.safeParse(appwriteFunctions.api.routes.oauthWebhook.requestSchema, request).success).toBe(false);
|
|
196
223
|
});
|
|
197
224
|
}
|
|
198
225
|
it('rejects caller-selected accounts for manual polling', () => {
|
|
@@ -287,16 +314,12 @@ describe('appwriteFunctions', () => {
|
|
|
287
314
|
description: 'a malformed mailbox email',
|
|
288
315
|
expected: false,
|
|
289
316
|
},
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
},
|
|
297
|
-
description: 'an unapproved return destination',
|
|
298
|
-
expected: false,
|
|
299
|
-
},
|
|
317
|
+
];
|
|
318
|
+
const forbiddenOAuthReturnUris = [
|
|
319
|
+
{ description: 'provider-selection', returnUri: 'https://app.marcoapp.io/onboarding/provider-selection' },
|
|
320
|
+
{ description: 'staging loading', returnUri: 'https://app.staging.marcoapp.io/onboarding/loading' },
|
|
321
|
+
{ description: 'arbitrary origin', returnUri: 'https://attacker.example/onboarding/loading' },
|
|
322
|
+
{ description: 'arbitrary path', returnUri: 'https://app.marcoapp.io/onboarding/loading/unexpected' },
|
|
300
323
|
];
|
|
301
324
|
const oauthConnectionRoutes = [
|
|
302
325
|
{
|
|
@@ -310,6 +333,18 @@ describe('appwriteFunctions', () => {
|
|
|
310
333
|
requestSchema: appwriteFunctions.api.routes.accountConnection.requestSchema,
|
|
311
334
|
},
|
|
312
335
|
];
|
|
336
|
+
for (const { description, includesAccountId, requestSchema } of oauthConnectionRoutes) {
|
|
337
|
+
it(`accepts ${description} OAuth connection completing at production loading`, () => {
|
|
338
|
+
const connection = {
|
|
339
|
+
email: 'test-user@example.com',
|
|
340
|
+
kind: 'OAUTH',
|
|
341
|
+
provider: 'GMAIL',
|
|
342
|
+
returnUri: 'https://app.marcoapp.io/onboarding/loading',
|
|
343
|
+
};
|
|
344
|
+
const request = includesAccountId ? { accountId: 'account_1', connection } : { connection };
|
|
345
|
+
expect(v.safeParse(requestSchema, request).success).toBe(true);
|
|
346
|
+
});
|
|
347
|
+
}
|
|
313
348
|
for (const { connection, description, expected } of invalidOAuthConnections) {
|
|
314
349
|
for (const { description: routeDescription, includesAccountId, requestSchema } of oauthConnectionRoutes) {
|
|
315
350
|
it(`rejects ${routeDescription} OAuth connection with ${description}`, () => {
|
|
@@ -318,4 +353,18 @@ describe('appwriteFunctions', () => {
|
|
|
318
353
|
});
|
|
319
354
|
}
|
|
320
355
|
}
|
|
356
|
+
for (const { description, returnUri } of forbiddenOAuthReturnUris) {
|
|
357
|
+
for (const { description: routeDescription, includesAccountId, requestSchema } of oauthConnectionRoutes) {
|
|
358
|
+
it(`rejects ${routeDescription} OAuth connection with ${description} return URI`, () => {
|
|
359
|
+
const connection = {
|
|
360
|
+
email: 'test-user@example.com',
|
|
361
|
+
kind: 'OAUTH',
|
|
362
|
+
provider: 'GMAIL',
|
|
363
|
+
returnUri,
|
|
364
|
+
};
|
|
365
|
+
const request = includesAccountId ? { accountId: 'account_1', connection } : { connection };
|
|
366
|
+
expect(v.safeParse(requestSchema, request).success).toBe(false);
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
}
|
|
321
370
|
});
|