@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/provider-selection", "https://app.staging.marcoapp.io/onboarding/provider-selection"], undefined>;
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/provider-selection", "https://app.staging.marcoapp.io/onboarding/provider-selection"], undefined>;
485
- readonly returnUris: readonly ["https://app.marcoapp.io/onboarding/provider-selection", "https://app.staging.marcoapp.io/onboarding/provider-selection"];
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/provider-selection", "https://app.staging.marcoapp.io/onboarding/provider-selection"], undefined>;
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/provider-selection', 'https://app.staging.marcoapp.io/onboarding/provider-selection'];
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://api.marcoapp.io/v1',
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/provider-selection',
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://api.marcoapp.io/v1',
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/provider-selection', 'https://app.staging.marcoapp.io/onboarding/provider-selection'],
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/provider-selection", "https://app.staging.marcoapp.io/onboarding/provider-selection"], undefined>;
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/provider-selection", "https://app.staging.marcoapp.io/onboarding/provider-selection"], undefined>;
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.StrictObjectSchema<{
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>;
@@ -134,7 +134,7 @@ const apiRoutes = {
134
134
  context: 'webhook',
135
135
  method: 'GET',
136
136
  path: oauth.redirectPath,
137
- requestSchema: v.strictObject({
137
+ requestSchema: v.object({
138
138
  code: string.required(4096),
139
139
  state: string.required(4096),
140
140
  }),
@@ -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/provider-selection',
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
- it(`rejects caller ownership in the ${name} request and response`, () => {
194
- expect(v.safeParse(route().requestSchema, { ...request, userId: 'another-user' }).success).toBe(false);
195
- expect(v.safeParse(route().responseSchema, { ...response, userId: 'another-user' }).success).toBe(false);
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
- connection: {
292
- email: 'test-user@example.com',
293
- kind: 'OAUTH',
294
- provider: 'GMAIL',
295
- returnUri: 'https://attacker.example/onboarding/provider-selection',
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
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marcoappio/marco-config",
3
- "version": "4.0.1",
3
+ "version": "4.0.3",
4
4
  "author": "team@marcoapp.io",
5
5
  "main": "dist/index.js",
6
6
  "repository": "git@github.com:marcoappio/marco-config.git",