@grantjs/schema 1.9.1 → 1.10.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@grantjs/schema",
3
- "version": "1.9.1",
3
+ "version": "1.10.0",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -23,18 +23,18 @@
23
23
  },
24
24
  "devDependencies": {
25
25
  "@graphql-codegen/add": "^7.1.0",
26
- "@graphql-codegen/cli": "^7.4.0",
26
+ "@graphql-codegen/cli": "^7.4.1",
27
27
  "@graphql-codegen/typed-document-node": "^7.1.0",
28
28
  "@graphql-codegen/typescript": "^6.1.0",
29
29
  "@graphql-codegen/typescript-operations": "^6.1.6",
30
30
  "@graphql-codegen/typescript-resolvers": "^6.1.0",
31
31
  "@parcel/watcher": "^2.6.0",
32
- "@types/node": "^26.4.1",
32
+ "@types/node": "^26.6.1",
33
33
  "eslint": "^10.10.0",
34
34
  "graphql": "^16.14.2",
35
35
  "@typescript/native": "npm:typescript@^7.0.2",
36
36
  "typescript": "npm:@typescript/typescript6@^6.0.2",
37
- "vitest": "^4.1.11"
37
+ "vitest": "^5.0.1"
38
38
  },
39
39
  "scripts": {
40
40
  "dev": "graphql-codegen --config codegen.ts generate --watch",
@@ -1257,6 +1257,44 @@ export type UploadProjectAppPictureMutation = {
1257
1257
  uploadProjectAppPicture: { url: string; path: string };
1258
1258
  };
1259
1259
 
1260
+ export type ClearProjectOAuthConnectionMutationVariables = Exact<{
1261
+ input: Types.ClearProjectOAuthConnectionInput;
1262
+ }>;
1263
+
1264
+ export type ClearProjectOAuthConnectionMutation = { clearProjectOAuthConnection: boolean };
1265
+
1266
+ export type GetProjectOAuthConnectionsQueryVariables = Exact<{
1267
+ scope: Types.Scope;
1268
+ }>;
1269
+
1270
+ export type GetProjectOAuthConnectionsQuery = {
1271
+ projectOAuthConnections: Array<{
1272
+ id: string;
1273
+ projectId: string;
1274
+ provider: Types.ProjectOAuthConnectionProvider;
1275
+ clientId: string;
1276
+ isConfigured: boolean;
1277
+ createdAt: Date;
1278
+ updatedAt: Date;
1279
+ }>;
1280
+ };
1281
+
1282
+ export type UpsertProjectOAuthConnectionMutationVariables = Exact<{
1283
+ input: Types.UpsertProjectOAuthConnectionInput;
1284
+ }>;
1285
+
1286
+ export type UpsertProjectOAuthConnectionMutation = {
1287
+ upsertProjectOAuthConnection: {
1288
+ id: string;
1289
+ projectId: string;
1290
+ provider: Types.ProjectOAuthConnectionProvider;
1291
+ clientId: string;
1292
+ isConfigured: boolean;
1293
+ createdAt: Date;
1294
+ updatedAt: Date;
1295
+ };
1296
+ };
1297
+
1260
1298
  export type CancelProjectSyncMutationVariables = Exact<{
1261
1299
  id: string | number;
1262
1300
  scope: Types.Scope;
@@ -7188,6 +7226,153 @@ export const UploadProjectAppPictureDocument = {
7188
7226
  UploadProjectAppPictureMutation,
7189
7227
  UploadProjectAppPictureMutationVariables
7190
7228
  >;
7229
+ export const ClearProjectOAuthConnectionDocument = {
7230
+ kind: 'Document',
7231
+ definitions: [
7232
+ {
7233
+ kind: 'OperationDefinition',
7234
+ operation: 'mutation',
7235
+ name: { kind: 'Name', value: 'ClearProjectOAuthConnection' },
7236
+ variableDefinitions: [
7237
+ {
7238
+ kind: 'VariableDefinition',
7239
+ variable: { kind: 'Variable', name: { kind: 'Name', value: 'input' } },
7240
+ type: {
7241
+ kind: 'NonNullType',
7242
+ type: {
7243
+ kind: 'NamedType',
7244
+ name: { kind: 'Name', value: 'ClearProjectOAuthConnectionInput' },
7245
+ },
7246
+ },
7247
+ },
7248
+ ],
7249
+ selectionSet: {
7250
+ kind: 'SelectionSet',
7251
+ selections: [
7252
+ {
7253
+ kind: 'Field',
7254
+ name: { kind: 'Name', value: 'clearProjectOAuthConnection' },
7255
+ arguments: [
7256
+ {
7257
+ kind: 'Argument',
7258
+ name: { kind: 'Name', value: 'input' },
7259
+ value: { kind: 'Variable', name: { kind: 'Name', value: 'input' } },
7260
+ },
7261
+ ],
7262
+ },
7263
+ ],
7264
+ },
7265
+ },
7266
+ ],
7267
+ } as unknown as DocumentNode<
7268
+ ClearProjectOAuthConnectionMutation,
7269
+ ClearProjectOAuthConnectionMutationVariables
7270
+ >;
7271
+ export const GetProjectOAuthConnectionsDocument = {
7272
+ kind: 'Document',
7273
+ definitions: [
7274
+ {
7275
+ kind: 'OperationDefinition',
7276
+ operation: 'query',
7277
+ name: { kind: 'Name', value: 'GetProjectOAuthConnections' },
7278
+ variableDefinitions: [
7279
+ {
7280
+ kind: 'VariableDefinition',
7281
+ variable: { kind: 'Variable', name: { kind: 'Name', value: 'scope' } },
7282
+ type: {
7283
+ kind: 'NonNullType',
7284
+ type: { kind: 'NamedType', name: { kind: 'Name', value: 'Scope' } },
7285
+ },
7286
+ },
7287
+ ],
7288
+ selectionSet: {
7289
+ kind: 'SelectionSet',
7290
+ selections: [
7291
+ {
7292
+ kind: 'Field',
7293
+ name: { kind: 'Name', value: 'projectOAuthConnections' },
7294
+ arguments: [
7295
+ {
7296
+ kind: 'Argument',
7297
+ name: { kind: 'Name', value: 'scope' },
7298
+ value: { kind: 'Variable', name: { kind: 'Name', value: 'scope' } },
7299
+ },
7300
+ ],
7301
+ selectionSet: {
7302
+ kind: 'SelectionSet',
7303
+ selections: [
7304
+ { kind: 'Field', name: { kind: 'Name', value: 'id' } },
7305
+ { kind: 'Field', name: { kind: 'Name', value: 'projectId' } },
7306
+ { kind: 'Field', name: { kind: 'Name', value: 'provider' } },
7307
+ { kind: 'Field', name: { kind: 'Name', value: 'clientId' } },
7308
+ { kind: 'Field', name: { kind: 'Name', value: 'isConfigured' } },
7309
+ { kind: 'Field', name: { kind: 'Name', value: 'createdAt' } },
7310
+ { kind: 'Field', name: { kind: 'Name', value: 'updatedAt' } },
7311
+ ],
7312
+ },
7313
+ },
7314
+ ],
7315
+ },
7316
+ },
7317
+ ],
7318
+ } as unknown as DocumentNode<
7319
+ GetProjectOAuthConnectionsQuery,
7320
+ GetProjectOAuthConnectionsQueryVariables
7321
+ >;
7322
+ export const UpsertProjectOAuthConnectionDocument = {
7323
+ kind: 'Document',
7324
+ definitions: [
7325
+ {
7326
+ kind: 'OperationDefinition',
7327
+ operation: 'mutation',
7328
+ name: { kind: 'Name', value: 'UpsertProjectOAuthConnection' },
7329
+ variableDefinitions: [
7330
+ {
7331
+ kind: 'VariableDefinition',
7332
+ variable: { kind: 'Variable', name: { kind: 'Name', value: 'input' } },
7333
+ type: {
7334
+ kind: 'NonNullType',
7335
+ type: {
7336
+ kind: 'NamedType',
7337
+ name: { kind: 'Name', value: 'UpsertProjectOAuthConnectionInput' },
7338
+ },
7339
+ },
7340
+ },
7341
+ ],
7342
+ selectionSet: {
7343
+ kind: 'SelectionSet',
7344
+ selections: [
7345
+ {
7346
+ kind: 'Field',
7347
+ name: { kind: 'Name', value: 'upsertProjectOAuthConnection' },
7348
+ arguments: [
7349
+ {
7350
+ kind: 'Argument',
7351
+ name: { kind: 'Name', value: 'input' },
7352
+ value: { kind: 'Variable', name: { kind: 'Name', value: 'input' } },
7353
+ },
7354
+ ],
7355
+ selectionSet: {
7356
+ kind: 'SelectionSet',
7357
+ selections: [
7358
+ { kind: 'Field', name: { kind: 'Name', value: 'id' } },
7359
+ { kind: 'Field', name: { kind: 'Name', value: 'projectId' } },
7360
+ { kind: 'Field', name: { kind: 'Name', value: 'provider' } },
7361
+ { kind: 'Field', name: { kind: 'Name', value: 'clientId' } },
7362
+ { kind: 'Field', name: { kind: 'Name', value: 'isConfigured' } },
7363
+ { kind: 'Field', name: { kind: 'Name', value: 'createdAt' } },
7364
+ { kind: 'Field', name: { kind: 'Name', value: 'updatedAt' } },
7365
+ ],
7366
+ },
7367
+ },
7368
+ ],
7369
+ },
7370
+ },
7371
+ ],
7372
+ } as unknown as DocumentNode<
7373
+ UpsertProjectOAuthConnectionMutation,
7374
+ UpsertProjectOAuthConnectionMutationVariables
7375
+ >;
7191
7376
  export const CancelProjectSyncDocument = {
7192
7377
  kind: 'Document',
7193
7378
  definitions: [
@@ -128,6 +128,7 @@ export type ResolversInterfaceTypes<_RefType extends Record<string, unknown>> =
128
128
  | Types.ProjectApp
129
129
  | Types.ProjectAppTag
130
130
  | Types.ProjectGroup
131
+ | Types.ProjectOAuthConnection
131
132
  | Types.ProjectPermission
132
133
  | Types.ProjectResource
133
134
  | Types.ProjectRole
@@ -238,6 +239,7 @@ export type ResolversTypes = ResolversObject<{
238
239
  ChangeMyPasswordInput: Types.ChangeMyPasswordInput;
239
240
  ChangeMyPasswordResult: ResolverTypeWrapper<Types.ChangeMyPasswordResult>;
240
241
  ClearProjectAppPictureInput: Types.ClearProjectAppPictureInput;
242
+ ClearProjectOAuthConnectionInput: Types.ClearProjectOAuthConnectionInput;
241
243
  ClearProjectPictureInput: Types.ClearProjectPictureInput;
242
244
  ConfirmMyProjectMembershipPictureUploadInput: Types.ConfirmMyProjectMembershipPictureUploadInput;
243
245
  ConfirmMyUserPictureUploadInput: Types.ConfirmMyUserPictureUploadInput;
@@ -359,6 +361,8 @@ export type ResolversTypes = ResolversObject<{
359
361
  ProjectAppTag: ResolverTypeWrapper<Types.ProjectAppTag>;
360
362
  ProjectGroup: ResolverTypeWrapper<Types.ProjectGroup>;
361
363
  ProjectMembershipExportData: ResolverTypeWrapper<Types.ProjectMembershipExportData>;
364
+ ProjectOAuthConnection: ResolverTypeWrapper<Types.ProjectOAuthConnection>;
365
+ ProjectOAuthConnectionProvider: Types.ProjectOAuthConnectionProvider;
362
366
  ProjectPage: ResolverTypeWrapper<Types.ProjectPage>;
363
367
  ProjectPermission: ResolverTypeWrapper<Types.ProjectPermission>;
364
368
  ProjectResource: ResolverTypeWrapper<Types.ProjectResource>;
@@ -545,6 +549,7 @@ export type ResolversTypes = ResolversObject<{
545
549
  UploadUrlHeader: ResolverTypeWrapper<Types.UploadUrlHeader>;
546
550
  UploadUserPictureInput: Types.UploadUserPictureInput;
547
551
  UploadUserPictureResult: ResolverTypeWrapper<Types.UploadUserPictureResult>;
552
+ UpsertProjectOAuthConnectionInput: Types.UpsertProjectOAuthConnectionInput;
548
553
  User: ResolverTypeWrapper<Types.User>;
549
554
  UserApiKeyCdmInput: Types.UserApiKeyCdmInput;
550
555
  UserAuthenticationEmailProviderAction: Types.UserAuthenticationEmailProviderAction;
@@ -639,6 +644,7 @@ export type ResolversParentTypes = ResolversObject<{
639
644
  ChangeMyPasswordInput: Types.ChangeMyPasswordInput;
640
645
  ChangeMyPasswordResult: Types.ChangeMyPasswordResult;
641
646
  ClearProjectAppPictureInput: Types.ClearProjectAppPictureInput;
647
+ ClearProjectOAuthConnectionInput: Types.ClearProjectOAuthConnectionInput;
642
648
  ClearProjectPictureInput: Types.ClearProjectPictureInput;
643
649
  ConfirmMyProjectMembershipPictureUploadInput: Types.ConfirmMyProjectMembershipPictureUploadInput;
644
650
  ConfirmMyUserPictureUploadInput: Types.ConfirmMyUserPictureUploadInput;
@@ -741,6 +747,7 @@ export type ResolversParentTypes = ResolversObject<{
741
747
  ProjectAppTag: Types.ProjectAppTag;
742
748
  ProjectGroup: Types.ProjectGroup;
743
749
  ProjectMembershipExportData: Types.ProjectMembershipExportData;
750
+ ProjectOAuthConnection: Types.ProjectOAuthConnection;
744
751
  ProjectPage: Types.ProjectPage;
745
752
  ProjectPermission: Types.ProjectPermission;
746
753
  ProjectResource: Types.ProjectResource;
@@ -913,6 +920,7 @@ export type ResolversParentTypes = ResolversObject<{
913
920
  UploadUrlHeader: Types.UploadUrlHeader;
914
921
  UploadUserPictureInput: Types.UploadUserPictureInput;
915
922
  UploadUserPictureResult: Types.UploadUserPictureResult;
923
+ UpsertProjectOAuthConnectionInput: Types.UpsertProjectOAuthConnectionInput;
916
924
  User: Types.User;
917
925
  UserApiKeyCdmInput: Types.UserApiKeyCdmInput;
918
926
  UserAuthenticationMethod: Types.UserAuthenticationMethod;
@@ -1143,6 +1151,7 @@ export type AuditableResolvers<
1143
1151
  | 'ProjectApp'
1144
1152
  | 'ProjectAppTag'
1145
1153
  | 'ProjectGroup'
1154
+ | 'ProjectOAuthConnection'
1146
1155
  | 'ProjectPermission'
1147
1156
  | 'ProjectResource'
1148
1157
  | 'ProjectRole'
@@ -1512,6 +1521,12 @@ export type MutationResolvers<
1512
1521
  ContextType,
1513
1522
  RequireFields<Types.MutationClearProjectAppPictureArgs, 'input'>
1514
1523
  >;
1524
+ clearProjectOAuthConnection?: Resolver<
1525
+ ResolversTypes['Boolean'],
1526
+ ParentType,
1527
+ ContextType,
1528
+ RequireFields<Types.MutationClearProjectOAuthConnectionArgs, 'input'>
1529
+ >;
1515
1530
  clearProjectPicture?: Resolver<
1516
1531
  ResolversTypes['Project'],
1517
1532
  ParentType,
@@ -2030,6 +2045,12 @@ export type MutationResolvers<
2030
2045
  ContextType,
2031
2046
  RequireFields<Types.MutationUploadUserPictureArgs, 'input'>
2032
2047
  >;
2048
+ upsertProjectOAuthConnection?: Resolver<
2049
+ ResolversTypes['ProjectOAuthConnection'],
2050
+ ParentType,
2051
+ ContextType,
2052
+ RequireFields<Types.MutationUpsertProjectOAuthConnectionArgs, 'input'>
2053
+ >;
2033
2054
  verifyEmail?: Resolver<
2034
2055
  ResolversTypes['VerifyEmailResponse'],
2035
2056
  ParentType,
@@ -2568,6 +2589,22 @@ export type ProjectMembershipExportDataResolvers<
2568
2589
  role?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
2569
2590
  }>;
2570
2591
 
2592
+ export type ProjectOAuthConnectionResolvers<
2593
+ ContextType = any,
2594
+ ParentType extends ResolversParentTypes['ProjectOAuthConnection'] =
2595
+ ResolversParentTypes['ProjectOAuthConnection'],
2596
+ > = ResolversObject<{
2597
+ clientId?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
2598
+ createdAt?: Resolver<ResolversTypes['Date'], ParentType, ContextType>;
2599
+ deletedAt?: Resolver<Types.Maybe<ResolversTypes['Date']>, ParentType, ContextType>;
2600
+ id?: Resolver<ResolversTypes['ID'], ParentType, ContextType>;
2601
+ isConfigured?: Resolver<ResolversTypes['Boolean'], ParentType, ContextType>;
2602
+ projectId?: Resolver<ResolversTypes['ID'], ParentType, ContextType>;
2603
+ provider?: Resolver<ResolversTypes['ProjectOAuthConnectionProvider'], ParentType, ContextType>;
2604
+ updatedAt?: Resolver<ResolversTypes['Date'], ParentType, ContextType>;
2605
+ __isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
2606
+ }>;
2607
+
2571
2608
  export type ProjectPageResolvers<
2572
2609
  ContextType = any,
2573
2610
  ParentType extends ResolversParentTypes['ProjectPage'] = ResolversParentTypes['ProjectPage'],
@@ -2883,6 +2920,12 @@ export type QueryResolvers<
2883
2920
  ContextType,
2884
2921
  RequireFields<Types.QueryProjectAppsArgs, 'scope'>
2885
2922
  >;
2923
+ projectOAuthConnections?: Resolver<
2924
+ Array<ResolversTypes['ProjectOAuthConnection']>,
2925
+ ParentType,
2926
+ ContextType,
2927
+ RequireFields<Types.QueryProjectOAuthConnectionsArgs, 'scope'>
2928
+ >;
2886
2929
  projectSyncJob?: Resolver<
2887
2930
  ResolversTypes['ProjectSyncJob'],
2888
2931
  ParentType,
@@ -3696,6 +3739,7 @@ export type Resolvers<ContextType = any> = ResolversObject<{
3696
3739
  ProjectAppTag?: ProjectAppTagResolvers<ContextType>;
3697
3740
  ProjectGroup?: ProjectGroupResolvers<ContextType>;
3698
3741
  ProjectMembershipExportData?: ProjectMembershipExportDataResolvers<ContextType>;
3742
+ ProjectOAuthConnection?: ProjectOAuthConnectionResolvers<ContextType>;
3699
3743
  ProjectPage?: ProjectPageResolvers<ContextType>;
3700
3744
  ProjectPermission?: ProjectPermissionResolvers<ContextType>;
3701
3745
  ProjectResource?: ProjectResourceResolvers<ContextType>;
@@ -488,6 +488,11 @@ export type ClearProjectAppPictureInput = {
488
488
  scope: Scope;
489
489
  };
490
490
 
491
+ export type ClearProjectOAuthConnectionInput = {
492
+ provider: ProjectOAuthConnectionProvider;
493
+ scope: Scope;
494
+ };
495
+
491
496
  export type ClearProjectPictureInput = {
492
497
  projectId: Scalars['ID']['input'];
493
498
  scope: Scope;
@@ -1038,6 +1043,12 @@ export type Mutation = {
1038
1043
  changeMyPassword: ChangeMyPasswordResult;
1039
1044
  /** Clear the project-app logo. Does not delete the stored object. */
1040
1045
  clearProjectAppPicture: ProjectApp;
1046
+ /**
1047
+ * Remove the project's GitHub or Google OAuth connection.
1048
+ * Ciphertext is wiped; the secret is never returned.
1049
+ * Requires Project Update on the scoped project.
1050
+ */
1051
+ clearProjectOAuthConnection: Scalars['Boolean']['output'];
1041
1052
  /** Clear the project logo. Does not delete the stored object. */
1042
1053
  clearProjectPicture: Project;
1043
1054
  /**
@@ -1254,6 +1265,12 @@ export type Mutation = {
1254
1265
  /** Upload a project logo and persist its public URL. */
1255
1266
  uploadProjectPicture: UploadProjectPictureResult;
1256
1267
  uploadUserPicture: UploadUserPictureResult;
1268
+ /**
1269
+ * Create or replace the project's GitHub or Google OAuth connection.
1270
+ * The client secret is write-only and is never returned.
1271
+ * Requires Project Update on the scoped project.
1272
+ */
1273
+ upsertProjectOAuthConnection: ProjectOAuthConnection;
1257
1274
  verifyEmail: VerifyEmailResponse;
1258
1275
  verifyMfa: MfaVerifyResponse;
1259
1276
  verifyMfaRecoveryCode: MfaVerifyResponse;
@@ -1286,6 +1303,10 @@ export type MutationClearProjectAppPictureArgs = {
1286
1303
  input: ClearProjectAppPictureInput;
1287
1304
  };
1288
1305
 
1306
+ export type MutationClearProjectOAuthConnectionArgs = {
1307
+ input: ClearProjectOAuthConnectionInput;
1308
+ };
1309
+
1289
1310
  export type MutationClearProjectPictureArgs = {
1290
1311
  input: ClearProjectPictureInput;
1291
1312
  };
@@ -1650,6 +1671,10 @@ export type MutationUploadUserPictureArgs = {
1650
1671
  input: UploadUserPictureInput;
1651
1672
  };
1652
1673
 
1674
+ export type MutationUpsertProjectOAuthConnectionArgs = {
1675
+ input: UpsertProjectOAuthConnectionInput;
1676
+ };
1677
+
1653
1678
  export type MutationVerifyEmailArgs = {
1654
1679
  input: VerifyEmailInput;
1655
1680
  };
@@ -2180,6 +2205,33 @@ export type ProjectMembershipExportData = {
2180
2205
  role: Scalars['String']['output'];
2181
2206
  };
2182
2207
 
2208
+ /**
2209
+ * Per-project GitHub or Google OAuth app (Auth0-style connection).
2210
+ * The client secret is write-only and is never returned.
2211
+ */
2212
+ export type ProjectOAuthConnection = Auditable & {
2213
+ __typename?: 'ProjectOAuthConnection';
2214
+ /** Public OAuth client id of the customer's GitHub or Google app. Not a secret. */
2215
+ clientId: Scalars['String']['output'];
2216
+ createdAt: Scalars['Date']['output'];
2217
+ deletedAt?: Maybe<Scalars['Date']['output']>;
2218
+ id: Scalars['ID']['output'];
2219
+ /** True when a BYO client secret is stored for this provider. The secret itself is never returned. */
2220
+ isConfigured: Scalars['Boolean']['output'];
2221
+ projectId: Scalars['ID']['output'];
2222
+ provider: ProjectOAuthConnectionProvider;
2223
+ updatedAt: Scalars['Date']['output'];
2224
+ };
2225
+
2226
+ /**
2227
+ * Social identity providers that can have a per-project OAuth connection.
2228
+ * Email magic-link is not a connection.
2229
+ */
2230
+ export enum ProjectOAuthConnectionProvider {
2231
+ Github = 'github',
2232
+ Google = 'google',
2233
+ }
2234
+
2183
2235
  export type ProjectPage = PaginatedResults & {
2184
2236
  __typename?: 'ProjectPage';
2185
2237
  hasNextPage: Scalars['Boolean']['output'];
@@ -2420,6 +2472,12 @@ export type Query = {
2420
2472
  permissions: PermissionPage;
2421
2473
  /** List OAuth apps for the given project scope. Allowed scopes: accountProject, organizationProject. */
2422
2474
  projectApps: ProjectAppPage;
2475
+ /**
2476
+ * List GitHub/Google OAuth connections for the project in `scope`.
2477
+ * Secrets and ciphertext are never returned.
2478
+ * Allowed scopes: accountProject, organizationProject.
2479
+ */
2480
+ projectOAuthConnections: Array<ProjectOAuthConnection>;
2423
2481
  /**
2424
2482
  * Read the current state of a project CDM sync job. Use this to poll
2425
2483
  * the lifecycle of a job started via `startProjectSync`.
@@ -2541,6 +2599,10 @@ export type QueryProjectAppsArgs = {
2541
2599
  tagIds?: InputMaybe<Array<Scalars['ID']['input']>>;
2542
2600
  };
2543
2601
 
2602
+ export type QueryProjectOAuthConnectionsArgs = {
2603
+ scope: Scope;
2604
+ };
2605
+
2544
2606
  export type QueryProjectSyncJobArgs = {
2545
2607
  id: Scalars['ID']['input'];
2546
2608
  jobId: Scalars['ID']['input'];
@@ -3853,6 +3915,15 @@ export type UploadUserPictureResult = {
3853
3915
  url: Scalars['String']['output'];
3854
3916
  };
3855
3917
 
3918
+ export type UpsertProjectOAuthConnectionInput = {
3919
+ /** Public OAuth client id of the customer's GitHub or Google app. */
3920
+ clientId: Scalars['String']['input'];
3921
+ /** OAuth client secret. Write-only; never returned on the connection type or in logs. */
3922
+ clientSecret: Scalars['String']['input'];
3923
+ provider: ProjectOAuthConnectionProvider;
3924
+ scope: Scope;
3925
+ };
3926
+
3856
3927
  export type User = Auditable & {
3857
3928
  __typename?: 'User';
3858
3929
  accounts?: Maybe<Array<Account>>;
@@ -0,0 +1,3 @@
1
+ mutation ClearProjectOAuthConnection($input: ClearProjectOAuthConnectionInput!) {
2
+ clearProjectOAuthConnection(input: $input)
3
+ }
@@ -0,0 +1,11 @@
1
+ query GetProjectOAuthConnections($scope: Scope!) {
2
+ projectOAuthConnections(scope: $scope) {
3
+ id
4
+ projectId
5
+ provider
6
+ clientId
7
+ isConfigured
8
+ createdAt
9
+ updatedAt
10
+ }
11
+ }
@@ -0,0 +1,11 @@
1
+ mutation UpsertProjectOAuthConnection($input: UpsertProjectOAuthConnectionInput!) {
2
+ upsertProjectOAuthConnection(input: $input) {
3
+ id
4
+ projectId
5
+ provider
6
+ clientId
7
+ isConfigured
8
+ createdAt
9
+ updatedAt
10
+ }
11
+ }
@@ -0,0 +1,61 @@
1
+ import { parse, validate } from 'graphql';
2
+ import { describe, expect, it } from 'vitest';
3
+
4
+ import { buildMergedSchema, operationFiles, readAll } from './test-support/sdl-fixture';
5
+
6
+ const SECRET_FIELD = /secret|encrypted|ciphertext|^iv$/i;
7
+
8
+ describe('ProjectOAuthConnection public types', () => {
9
+ const schema = buildMergedSchema();
10
+
11
+ it('does not expose secrets on the connection output type', () => {
12
+ const connection = schema.getType('ProjectOAuthConnection');
13
+ expect(connection).toBeDefined();
14
+ expect(connection?.astNode?.kind).toBe('ObjectTypeDefinition');
15
+ const fields =
16
+ connection && 'getFields' in connection ? Object.keys(connection.getFields()) : [];
17
+
18
+ expect(fields).toEqual(
19
+ expect.arrayContaining([
20
+ 'id',
21
+ 'projectId',
22
+ 'provider',
23
+ 'clientId',
24
+ 'isConfigured',
25
+ 'createdAt',
26
+ 'updatedAt',
27
+ ])
28
+ );
29
+ expect(fields.filter((name) => SECRET_FIELD.test(name))).toEqual([]);
30
+ });
31
+
32
+ it('accepts the client secret only as a write-only upsert input', () => {
33
+ const input = schema.getType('UpsertProjectOAuthConnectionInput');
34
+ expect(input).toBeDefined();
35
+ const fields = input && 'getFields' in input ? Object.keys(input.getFields()) : [];
36
+
37
+ expect(fields).toEqual(
38
+ expect.arrayContaining(['scope', 'provider', 'clientId', 'clientSecret'])
39
+ );
40
+ expect(schema.getType('ClearProjectOAuthConnectionInput')).toBeDefined();
41
+ expect(schema.getQueryType()?.getFields().projectOAuthConnections).toBeDefined();
42
+ expect(schema.getMutationType()?.getFields().upsertProjectOAuthConnection).toBeDefined();
43
+ expect(schema.getMutationType()?.getFields().clearProjectOAuthConnection).toBeDefined();
44
+ });
45
+
46
+ it('keeps operation documents from selecting secret fields', () => {
47
+ const files = operationFiles().filter((file) => file.includes('project-oauth-connections'));
48
+ expect(files.length).toBe(3);
49
+
50
+ for (const file of files) {
51
+ const errors = validate(schema, parse(readAll([file])));
52
+ expect(errors.map((e) => e.message)).toEqual([]);
53
+ }
54
+
55
+ const documents = readAll(files);
56
+ expect(documents).not.toMatch(/\bclientSecret\b/);
57
+ expect(documents).not.toMatch(/\bencryptedSecret\b/);
58
+ expect(documents).not.toMatch(/\bsecretIv\b/);
59
+ expect(documents).not.toMatch(/\bsecretTag\b/);
60
+ });
61
+ });
@@ -1,5 +1,6 @@
1
1
  import { describe, expect, it } from 'vitest';
2
2
 
3
+ import type { ProjectAppPublicInfo } from './project-oauth.types';
3
4
  import { isProjectOAuthThemeMode } from './project-oauth.types';
4
5
 
5
6
  describe('isProjectOAuthThemeMode', () => {
@@ -16,3 +17,23 @@ describe('isProjectOAuthThemeMode', () => {
16
17
  expect(isProjectOAuthThemeMode('DARK')).toBe(false);
17
18
  });
18
19
  });
20
+
21
+ const SECRET_FIELD = /secret|encrypted|ciphertext|^iv$/i;
22
+
23
+ describe('ProjectAppPublicInfo', () => {
24
+ it('does not declare secret or ciphertext fields', () => {
25
+ const sample: ProjectAppPublicInfo = {
26
+ name: null,
27
+ projectName: null,
28
+ pictureUrl: null,
29
+ primaryColor: null,
30
+ showHelpPanel: true,
31
+ themeMode: null,
32
+ enabledProviders: ['github'],
33
+ configuredProviders: ['github'],
34
+ scopes: [],
35
+ };
36
+
37
+ expect(Object.keys(sample).filter((key) => SECRET_FIELD.test(key))).toEqual([]);
38
+ });
39
+ });
@@ -29,6 +29,11 @@ export interface ProjectAppPublicInfo {
29
29
  showHelpPanel: boolean;
30
30
  themeMode: ProjectOAuthThemeMode | null;
31
31
  enabledProviders: string[] | null;
32
+ /**
33
+ * Social providers this project can offer (BYO connection and/or platform fallback).
34
+ * Hosted sign-in uses this instead of GET /api/auth/providers.
35
+ */
36
+ configuredProviders?: string[];
32
37
  scopes: ProjectAppScopeInfo[];
33
38
  }
34
39
 
@@ -0,0 +1,4 @@
1
+ input ClearProjectOAuthConnectionInput {
2
+ scope: Scope!
3
+ provider: ProjectOAuthConnectionProvider!
4
+ }
@@ -0,0 +1,12 @@
1
+ input UpsertProjectOAuthConnectionInput {
2
+ scope: Scope!
3
+ provider: ProjectOAuthConnectionProvider!
4
+ """
5
+ Public OAuth client id of the customer's GitHub or Google app.
6
+ """
7
+ clientId: String!
8
+ """
9
+ OAuth client secret. Write-only; never returned on the connection type or in logs.
10
+ """
11
+ clientSecret: String!
12
+ }
@@ -0,0 +1,8 @@
1
+ extend type Mutation {
2
+ """
3
+ Remove the project's GitHub or Google OAuth connection.
4
+ Ciphertext is wiped; the secret is never returned.
5
+ Requires Project Update on the scoped project.
6
+ """
7
+ clearProjectOAuthConnection(input: ClearProjectOAuthConnectionInput!): Boolean!
8
+ }
@@ -0,0 +1,8 @@
1
+ extend type Mutation {
2
+ """
3
+ Create or replace the project's GitHub or Google OAuth connection.
4
+ The client secret is write-only and is never returned.
5
+ Requires Project Update on the scoped project.
6
+ """
7
+ upsertProjectOAuthConnection(input: UpsertProjectOAuthConnectionInput!): ProjectOAuthConnection!
8
+ }
@@ -0,0 +1,8 @@
1
+ extend type Query {
2
+ """
3
+ List GitHub/Google OAuth connections for the project in `scope`.
4
+ Secrets and ciphertext are never returned.
5
+ Allowed scopes: accountProject, organizationProject.
6
+ """
7
+ projectOAuthConnections(scope: Scope!): [ProjectOAuthConnection!]!
8
+ }
@@ -0,0 +1,8 @@
1
+ """
2
+ Social identity providers that can have a per-project OAuth connection.
3
+ Email magic-link is not a connection.
4
+ """
5
+ enum ProjectOAuthConnectionProvider {
6
+ github
7
+ google
8
+ }
@@ -0,0 +1,20 @@
1
+ """
2
+ Per-project GitHub or Google OAuth app (Auth0-style connection).
3
+ The client secret is write-only and is never returned.
4
+ """
5
+ type ProjectOAuthConnection implements Auditable {
6
+ id: ID!
7
+ projectId: ID!
8
+ provider: ProjectOAuthConnectionProvider!
9
+ """
10
+ Public OAuth client id of the customer's GitHub or Google app. Not a secret.
11
+ """
12
+ clientId: String!
13
+ """
14
+ True when a BYO client secret is stored for this provider. The secret itself is never returned.
15
+ """
16
+ isConfigured: Boolean!
17
+ createdAt: Date!
18
+ updatedAt: Date!
19
+ deletedAt: Date
20
+ }
@@ -48,10 +48,11 @@ describe('operation documents', () => {
48
48
  // 124 after slice 17b: organization request/confirm (the last upload target).
49
49
  // 132 after project branding: request/confirm/upload/clear for project and
50
50
  // project-app pictures (eight operation documents).
51
+ // 135 after project OAuth connections: get/upsert/clear (three operation documents).
51
52
  // Updating this number is the point of the pin, not a nuisance: every change
52
53
  // to it should be a line in a commit message.
53
54
  it('finds every document under src/operations', () => {
54
- expect(files).toHaveLength(132);
55
+ expect(files).toHaveLength(135);
55
56
  });
56
57
 
57
58
  it.each(files.map((f) => [relative(process.cwd(), f), f]))('%s validates', (_label, file) => {