@grantjs/schema 1.21.0 → 1.22.1
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 +1 -1
- package/src/generated/graphql.ts +85 -1
- package/src/generated/resolvers.ts +48 -0
- package/src/generated/schema-types.ts +106 -7
- package/src/operations/project-users/exportProjectUserCredentials.graphql +6 -0
- package/src/operations/projects/set-project-exportable-api-key-secrets.graphql +7 -0
- package/src/schema/project-users/queries/export-project-user-credentials.graphql +8 -5
- package/src/schema/project-users/types/credential-kind.graphql +5 -0
- package/src/schema/project-users/types/project-user-api-key-credential.graphql +21 -0
- package/src/schema/project-users/types/project-user-credential-export.graphql +29 -2
- package/src/schema/projects/inputs/set-project-exportable-api-key-secrets.graphql +12 -0
- package/src/schema/projects/mutations/set-project-exportable-api-key-secrets.graphql +11 -0
- package/src/schema/projects/types/project.graphql +7 -0
- package/src/schema/users/types/user.graphql +7 -0
- package/src/sdl-contract.test.ts +2 -1
package/package.json
CHANGED
package/src/generated/graphql.ts
CHANGED
|
@@ -1316,7 +1316,16 @@ export type ExportProjectUserCredentialsQueryVariables = Exact<{
|
|
|
1316
1316
|
|
|
1317
1317
|
export type ExportProjectUserCredentialsQuery = {
|
|
1318
1318
|
exportProjectUserCredentials: {
|
|
1319
|
-
users: Array<{
|
|
1319
|
+
users: Array<{
|
|
1320
|
+
userId: string;
|
|
1321
|
+
password: { algorithm: string; hash: string } | null;
|
|
1322
|
+
apiKeySecrets: Array<{
|
|
1323
|
+
apiKeyId: string;
|
|
1324
|
+
clientId: string;
|
|
1325
|
+
fingerprint: string | null;
|
|
1326
|
+
secret: string | null;
|
|
1327
|
+
}> | null;
|
|
1328
|
+
}>;
|
|
1320
1329
|
};
|
|
1321
1330
|
};
|
|
1322
1331
|
|
|
@@ -1548,6 +1557,18 @@ export type RequestProjectSyncUploadUrlMutation = {
|
|
|
1548
1557
|
};
|
|
1549
1558
|
};
|
|
1550
1559
|
|
|
1560
|
+
export type SetProjectExportableApiKeySecretsMutationVariables = Exact<{
|
|
1561
|
+
input: Types.SetProjectExportableApiKeySecretsInput;
|
|
1562
|
+
}>;
|
|
1563
|
+
|
|
1564
|
+
export type SetProjectExportableApiKeySecretsMutation = {
|
|
1565
|
+
setProjectExportableApiKeySecrets: {
|
|
1566
|
+
id: string;
|
|
1567
|
+
exportableApiKeySecrets: boolean;
|
|
1568
|
+
updatedAt: Date;
|
|
1569
|
+
};
|
|
1570
|
+
};
|
|
1571
|
+
|
|
1551
1572
|
export type StartProjectExportMutationVariables = Exact<{
|
|
1552
1573
|
id: string | number;
|
|
1553
1574
|
scope: Types.Scope;
|
|
@@ -7546,6 +7567,19 @@ export const ExportProjectUserCredentialsDocument = {
|
|
|
7546
7567
|
],
|
|
7547
7568
|
},
|
|
7548
7569
|
},
|
|
7570
|
+
{
|
|
7571
|
+
kind: 'Field',
|
|
7572
|
+
name: { kind: 'Name', value: 'apiKeySecrets' },
|
|
7573
|
+
selectionSet: {
|
|
7574
|
+
kind: 'SelectionSet',
|
|
7575
|
+
selections: [
|
|
7576
|
+
{ kind: 'Field', name: { kind: 'Name', value: 'apiKeyId' } },
|
|
7577
|
+
{ kind: 'Field', name: { kind: 'Name', value: 'clientId' } },
|
|
7578
|
+
{ kind: 'Field', name: { kind: 'Name', value: 'fingerprint' } },
|
|
7579
|
+
{ kind: 'Field', name: { kind: 'Name', value: 'secret' } },
|
|
7580
|
+
],
|
|
7581
|
+
},
|
|
7582
|
+
},
|
|
7549
7583
|
],
|
|
7550
7584
|
},
|
|
7551
7585
|
},
|
|
@@ -8407,6 +8441,56 @@ export const RequestProjectSyncUploadUrlDocument = {
|
|
|
8407
8441
|
RequestProjectSyncUploadUrlMutation,
|
|
8408
8442
|
RequestProjectSyncUploadUrlMutationVariables
|
|
8409
8443
|
>;
|
|
8444
|
+
export const SetProjectExportableApiKeySecretsDocument = {
|
|
8445
|
+
kind: 'Document',
|
|
8446
|
+
definitions: [
|
|
8447
|
+
{
|
|
8448
|
+
kind: 'OperationDefinition',
|
|
8449
|
+
operation: 'mutation',
|
|
8450
|
+
name: { kind: 'Name', value: 'SetProjectExportableApiKeySecrets' },
|
|
8451
|
+
variableDefinitions: [
|
|
8452
|
+
{
|
|
8453
|
+
kind: 'VariableDefinition',
|
|
8454
|
+
variable: { kind: 'Variable', name: { kind: 'Name', value: 'input' } },
|
|
8455
|
+
type: {
|
|
8456
|
+
kind: 'NonNullType',
|
|
8457
|
+
type: {
|
|
8458
|
+
kind: 'NamedType',
|
|
8459
|
+
name: { kind: 'Name', value: 'SetProjectExportableApiKeySecretsInput' },
|
|
8460
|
+
},
|
|
8461
|
+
},
|
|
8462
|
+
},
|
|
8463
|
+
],
|
|
8464
|
+
selectionSet: {
|
|
8465
|
+
kind: 'SelectionSet',
|
|
8466
|
+
selections: [
|
|
8467
|
+
{
|
|
8468
|
+
kind: 'Field',
|
|
8469
|
+
name: { kind: 'Name', value: 'setProjectExportableApiKeySecrets' },
|
|
8470
|
+
arguments: [
|
|
8471
|
+
{
|
|
8472
|
+
kind: 'Argument',
|
|
8473
|
+
name: { kind: 'Name', value: 'input' },
|
|
8474
|
+
value: { kind: 'Variable', name: { kind: 'Name', value: 'input' } },
|
|
8475
|
+
},
|
|
8476
|
+
],
|
|
8477
|
+
selectionSet: {
|
|
8478
|
+
kind: 'SelectionSet',
|
|
8479
|
+
selections: [
|
|
8480
|
+
{ kind: 'Field', name: { kind: 'Name', value: 'id' } },
|
|
8481
|
+
{ kind: 'Field', name: { kind: 'Name', value: 'exportableApiKeySecrets' } },
|
|
8482
|
+
{ kind: 'Field', name: { kind: 'Name', value: 'updatedAt' } },
|
|
8483
|
+
],
|
|
8484
|
+
},
|
|
8485
|
+
},
|
|
8486
|
+
],
|
|
8487
|
+
},
|
|
8488
|
+
},
|
|
8489
|
+
],
|
|
8490
|
+
} as unknown as DocumentNode<
|
|
8491
|
+
SetProjectExportableApiKeySecretsMutation,
|
|
8492
|
+
SetProjectExportableApiKeySecretsMutationVariables
|
|
8493
|
+
>;
|
|
8410
8494
|
export const StartProjectExportDocument = {
|
|
8411
8495
|
kind: 'Document',
|
|
8412
8496
|
definitions: [
|
|
@@ -283,6 +283,7 @@ export type ResolversTypes = ResolversObject<{
|
|
|
283
283
|
ExchangeApiKeyInput: Types.ExchangeApiKeyInput;
|
|
284
284
|
ExchangeApiKeyResult: ResolverTypeWrapper<Types.ExchangeApiKeyResult>;
|
|
285
285
|
ExportProjectUserCredentialsInput: Types.ExportProjectUserCredentialsInput;
|
|
286
|
+
ExportedApiKeySecret: ResolverTypeWrapper<Types.ExportedApiKeySecret>;
|
|
286
287
|
ExportedPasswordCredential: ResolverTypeWrapper<Types.ExportedPasswordCredential>;
|
|
287
288
|
ExportedProjectUserCredential: ResolverTypeWrapper<Types.ExportedProjectUserCredential>;
|
|
288
289
|
GenerateMyMfaRecoveryCodesInput: Types.GenerateMyMfaRecoveryCodesInput;
|
|
@@ -389,6 +390,7 @@ export type ResolversTypes = ResolversObject<{
|
|
|
389
390
|
ProjectTag: ResolverTypeWrapper<Types.ProjectTag>;
|
|
390
391
|
ProjectUser: ResolverTypeWrapper<Types.ProjectUser>;
|
|
391
392
|
ProjectUserApiKey: ResolverTypeWrapper<Types.ProjectUserApiKey>;
|
|
393
|
+
ProjectUserApiKeyCredential: ResolverTypeWrapper<Types.ProjectUserApiKeyCredential>;
|
|
392
394
|
ProjectUserCredentialExport: ResolverTypeWrapper<Types.ProjectUserCredentialExport>;
|
|
393
395
|
ProjectUserGroup: ResolverTypeWrapper<Types.ProjectUserGroup>;
|
|
394
396
|
ProjectUserPasswordCredential: ResolverTypeWrapper<Types.ProjectUserPasswordCredential>;
|
|
@@ -507,6 +509,7 @@ export type ResolversTypes = ResolversObject<{
|
|
|
507
509
|
SessionExportData: ResolverTypeWrapper<Types.SessionExportData>;
|
|
508
510
|
SetMyNotificationPreferenceInput: Types.SetMyNotificationPreferenceInput;
|
|
509
511
|
SetMyPrimaryMfaDeviceInput: Types.SetMyPrimaryMfaDeviceInput;
|
|
512
|
+
SetProjectExportableApiKeySecretsInput: Types.SetProjectExportableApiKeySecretsInput;
|
|
510
513
|
SetProjectUserEmailInput: Types.SetProjectUserEmailInput;
|
|
511
514
|
SetProjectUserPasswordInput: Types.SetProjectUserPasswordInput;
|
|
512
515
|
SigningKey: ResolverTypeWrapper<Types.SigningKey>;
|
|
@@ -700,6 +703,7 @@ export type ResolversParentTypes = ResolversObject<{
|
|
|
700
703
|
ExchangeApiKeyInput: Types.ExchangeApiKeyInput;
|
|
701
704
|
ExchangeApiKeyResult: Types.ExchangeApiKeyResult;
|
|
702
705
|
ExportProjectUserCredentialsInput: Types.ExportProjectUserCredentialsInput;
|
|
706
|
+
ExportedApiKeySecret: Types.ExportedApiKeySecret;
|
|
703
707
|
ExportedPasswordCredential: Types.ExportedPasswordCredential;
|
|
704
708
|
ExportedProjectUserCredential: Types.ExportedProjectUserCredential;
|
|
705
709
|
GenerateMyMfaRecoveryCodesInput: Types.GenerateMyMfaRecoveryCodesInput;
|
|
@@ -782,6 +786,7 @@ export type ResolversParentTypes = ResolversObject<{
|
|
|
782
786
|
ProjectTag: Types.ProjectTag;
|
|
783
787
|
ProjectUser: Types.ProjectUser;
|
|
784
788
|
ProjectUserApiKey: Types.ProjectUserApiKey;
|
|
789
|
+
ProjectUserApiKeyCredential: Types.ProjectUserApiKeyCredential;
|
|
785
790
|
ProjectUserCredentialExport: Types.ProjectUserCredentialExport;
|
|
786
791
|
ProjectUserGroup: Types.ProjectUserGroup;
|
|
787
792
|
ProjectUserPasswordCredential: Types.ProjectUserPasswordCredential;
|
|
@@ -896,6 +901,7 @@ export type ResolversParentTypes = ResolversObject<{
|
|
|
896
901
|
SessionExportData: Types.SessionExportData;
|
|
897
902
|
SetMyNotificationPreferenceInput: Types.SetMyNotificationPreferenceInput;
|
|
898
903
|
SetMyPrimaryMfaDeviceInput: Types.SetMyPrimaryMfaDeviceInput;
|
|
904
|
+
SetProjectExportableApiKeySecretsInput: Types.SetProjectExportableApiKeySecretsInput;
|
|
899
905
|
SetProjectUserEmailInput: Types.SetProjectUserEmailInput;
|
|
900
906
|
SetProjectUserPasswordInput: Types.SetProjectUserPasswordInput;
|
|
901
907
|
SigningKey: Types.SigningKey;
|
|
@@ -1338,6 +1344,17 @@ export type ExchangeApiKeyResultResolvers<
|
|
|
1338
1344
|
expiresIn?: Resolver<ResolversTypes['Int'], ParentType, ContextType>;
|
|
1339
1345
|
}>;
|
|
1340
1346
|
|
|
1347
|
+
export type ExportedApiKeySecretResolvers<
|
|
1348
|
+
ContextType = any,
|
|
1349
|
+
ParentType extends ResolversParentTypes['ExportedApiKeySecret'] =
|
|
1350
|
+
ResolversParentTypes['ExportedApiKeySecret'],
|
|
1351
|
+
> = ResolversObject<{
|
|
1352
|
+
apiKeyId?: Resolver<ResolversTypes['ID'], ParentType, ContextType>;
|
|
1353
|
+
clientId?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
|
|
1354
|
+
fingerprint?: Resolver<Types.Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
|
1355
|
+
secret?: Resolver<Types.Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
|
1356
|
+
}>;
|
|
1357
|
+
|
|
1341
1358
|
export type ExportedPasswordCredentialResolvers<
|
|
1342
1359
|
ContextType = any,
|
|
1343
1360
|
ParentType extends ResolversParentTypes['ExportedPasswordCredential'] =
|
|
@@ -1352,6 +1369,11 @@ export type ExportedProjectUserCredentialResolvers<
|
|
|
1352
1369
|
ParentType extends ResolversParentTypes['ExportedProjectUserCredential'] =
|
|
1353
1370
|
ResolversParentTypes['ExportedProjectUserCredential'],
|
|
1354
1371
|
> = ResolversObject<{
|
|
1372
|
+
apiKeySecrets?: Resolver<
|
|
1373
|
+
Types.Maybe<Array<ResolversTypes['ExportedApiKeySecret']>>,
|
|
1374
|
+
ParentType,
|
|
1375
|
+
ContextType
|
|
1376
|
+
>;
|
|
1355
1377
|
password?: Resolver<
|
|
1356
1378
|
Types.Maybe<ResolversTypes['ExportedPasswordCredential']>,
|
|
1357
1379
|
ParentType,
|
|
@@ -1997,6 +2019,12 @@ export type MutationResolvers<
|
|
|
1997
2019
|
ContextType,
|
|
1998
2020
|
RequireFields<Types.MutationSetMyPrimaryMfaDeviceArgs, 'input'>
|
|
1999
2021
|
>;
|
|
2022
|
+
setProjectExportableApiKeySecrets?: Resolver<
|
|
2023
|
+
ResolversTypes['Project'],
|
|
2024
|
+
ParentType,
|
|
2025
|
+
ContextType,
|
|
2026
|
+
RequireFields<Types.MutationSetProjectExportableApiKeySecretsArgs, 'input'>
|
|
2027
|
+
>;
|
|
2000
2028
|
setProjectUserEmail?: Resolver<
|
|
2001
2029
|
ResolversTypes['User'],
|
|
2002
2030
|
ParentType,
|
|
@@ -2577,6 +2605,7 @@ export type ProjectResolvers<
|
|
|
2577
2605
|
createdAt?: Resolver<ResolversTypes['Date'], ParentType, ContextType>;
|
|
2578
2606
|
deletedAt?: Resolver<Types.Maybe<ResolversTypes['Date']>, ParentType, ContextType>;
|
|
2579
2607
|
description?: Resolver<Types.Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
|
2608
|
+
exportableApiKeySecrets?: Resolver<ResolversTypes['Boolean'], ParentType, ContextType>;
|
|
2580
2609
|
groups?: Resolver<Types.Maybe<Array<ResolversTypes['Group']>>, ParentType, ContextType>;
|
|
2581
2610
|
id?: Resolver<ResolversTypes['ID'], ParentType, ContextType>;
|
|
2582
2611
|
name?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
|
|
@@ -2887,6 +2916,18 @@ export type ProjectUserApiKeyResolvers<
|
|
|
2887
2916
|
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
|
2888
2917
|
}>;
|
|
2889
2918
|
|
|
2919
|
+
export type ProjectUserApiKeyCredentialResolvers<
|
|
2920
|
+
ContextType = any,
|
|
2921
|
+
ParentType extends ResolversParentTypes['ProjectUserApiKeyCredential'] =
|
|
2922
|
+
ResolversParentTypes['ProjectUserApiKeyCredential'],
|
|
2923
|
+
> = ResolversObject<{
|
|
2924
|
+
apiKeyId?: Resolver<ResolversTypes['ID'], ParentType, ContextType>;
|
|
2925
|
+
clientId?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
|
|
2926
|
+
exportable?: Resolver<ResolversTypes['Boolean'], ParentType, ContextType>;
|
|
2927
|
+
fingerprint?: Resolver<Types.Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
|
2928
|
+
updatedAt?: Resolver<ResolversTypes['Date'], ParentType, ContextType>;
|
|
2929
|
+
}>;
|
|
2930
|
+
|
|
2890
2931
|
export type ProjectUserCredentialExportResolvers<
|
|
2891
2932
|
ContextType = any,
|
|
2892
2933
|
ParentType extends ResolversParentTypes['ProjectUserCredentialExport'] =
|
|
@@ -3501,6 +3542,11 @@ export type UserResolvers<
|
|
|
3501
3542
|
ParentType extends ResolversParentTypes['User'] = ResolversParentTypes['User'],
|
|
3502
3543
|
> = ResolversObject<{
|
|
3503
3544
|
accounts?: Resolver<Types.Maybe<Array<ResolversTypes['Account']>>, ParentType, ContextType>;
|
|
3545
|
+
apiKeyCredentials?: Resolver<
|
|
3546
|
+
Types.Maybe<Array<ResolversTypes['ProjectUserApiKeyCredential']>>,
|
|
3547
|
+
ParentType,
|
|
3548
|
+
ContextType
|
|
3549
|
+
>;
|
|
3504
3550
|
authenticationMethods?: Resolver<
|
|
3505
3551
|
Types.Maybe<Array<ResolversTypes['UserAuthenticationMethod']>>,
|
|
3506
3552
|
ParentType,
|
|
@@ -3837,6 +3883,7 @@ export type Resolvers<ContextType = any> = ResolversObject<{
|
|
|
3837
3883
|
CredentialExportData?: CredentialExportDataResolvers<ContextType>;
|
|
3838
3884
|
Date?: GraphQLScalarType;
|
|
3839
3885
|
ExchangeApiKeyResult?: ExchangeApiKeyResultResolvers<ContextType>;
|
|
3886
|
+
ExportedApiKeySecret?: ExportedApiKeySecretResolvers<ContextType>;
|
|
3840
3887
|
ExportedPasswordCredential?: ExportedPasswordCredentialResolvers<ContextType>;
|
|
3841
3888
|
ExportedProjectUserCredential?: ExportedProjectUserCredentialResolvers<ContextType>;
|
|
3842
3889
|
Group?: GroupResolvers<ContextType>;
|
|
@@ -3897,6 +3944,7 @@ export type Resolvers<ContextType = any> = ResolversObject<{
|
|
|
3897
3944
|
ProjectTag?: ProjectTagResolvers<ContextType>;
|
|
3898
3945
|
ProjectUser?: ProjectUserResolvers<ContextType>;
|
|
3899
3946
|
ProjectUserApiKey?: ProjectUserApiKeyResolvers<ContextType>;
|
|
3947
|
+
ProjectUserApiKeyCredential?: ProjectUserApiKeyCredentialResolvers<ContextType>;
|
|
3900
3948
|
ProjectUserCredentialExport?: ProjectUserCredentialExportResolvers<ContextType>;
|
|
3901
3949
|
ProjectUserGroup?: ProjectUserGroupResolvers<ContextType>;
|
|
3902
3950
|
ProjectUserPasswordCredential?: ProjectUserPasswordCredentialResolvers<ContextType>;
|
|
@@ -818,6 +818,11 @@ export type CredentialExportData = {
|
|
|
818
818
|
* not as a second export.
|
|
819
819
|
*/
|
|
820
820
|
export enum CredentialKind {
|
|
821
|
+
/**
|
|
822
|
+
* The API keys of the project user, with the plaintext secret of each key the project keeps an
|
|
823
|
+
* exportable copy of (a project that opted in to exportable API-key secrets).
|
|
824
|
+
*/
|
|
825
|
+
ApiKeySecret = 'API_KEY_SECRET',
|
|
821
826
|
/** The project password credential (bcrypt hash). */
|
|
822
827
|
Password = 'PASSWORD',
|
|
823
828
|
}
|
|
@@ -876,6 +881,26 @@ export type ExportProjectUserCredentialsInput = {
|
|
|
876
881
|
userIds: Array<Scalars['ID']['input']>;
|
|
877
882
|
};
|
|
878
883
|
|
|
884
|
+
/** One API key of a project user in an export. `secret` is a credential: handle it like one. */
|
|
885
|
+
export type ExportedApiKeySecret = {
|
|
886
|
+
__typename?: 'ExportedApiKeySecret';
|
|
887
|
+
apiKeyId: Scalars['ID']['output'];
|
|
888
|
+
/** The key's public client id. */
|
|
889
|
+
clientId: Scalars['String']['output'];
|
|
890
|
+
/**
|
|
891
|
+
* Lowercase hex SHA-256 of the secret string as issued, to compare with `sha256(secret)` of the
|
|
892
|
+
* value a receiver holds. `null` for a key created before the fingerprint existed and not rotated
|
|
893
|
+
* since.
|
|
894
|
+
*/
|
|
895
|
+
fingerprint?: Maybe<Scalars['String']['output']>;
|
|
896
|
+
/**
|
|
897
|
+
* The plaintext secret: 88 characters, standard base64 of 64 random bytes. `null` for a key the
|
|
898
|
+
* project holds no copy of (created before the project opted in, created by a CDM import, or
|
|
899
|
+
* rotated by a CDM replace): Grant cannot recover it.
|
|
900
|
+
*/
|
|
901
|
+
secret?: Maybe<Scalars['String']['output']>;
|
|
902
|
+
};
|
|
903
|
+
|
|
879
904
|
/** A stored password hash in CDM shape. Secret-equivalent: handle like a credential. */
|
|
880
905
|
export type ExportedPasswordCredential = {
|
|
881
906
|
__typename?: 'ExportedPasswordCredential';
|
|
@@ -887,7 +912,12 @@ export type ExportedPasswordCredential = {
|
|
|
887
912
|
|
|
888
913
|
export type ExportedProjectUserCredential = {
|
|
889
914
|
__typename?: 'ExportedProjectUserCredential';
|
|
890
|
-
/**
|
|
915
|
+
/**
|
|
916
|
+
* The member's live (not revoked, not deleted) project-user API keys, oldest first. `null` when the
|
|
917
|
+
* `API_KEY_SECRET` kind was not requested; `[]` for a member with no key.
|
|
918
|
+
*/
|
|
919
|
+
apiKeySecrets?: Maybe<Array<ExportedApiKeySecret>>;
|
|
920
|
+
/** `null` when the member has no project password credential, and when the `PASSWORD` kind was not requested. */
|
|
891
921
|
password?: Maybe<ExportedPasswordCredential>;
|
|
892
922
|
userId: Scalars['ID']['output'];
|
|
893
923
|
};
|
|
@@ -1329,6 +1359,15 @@ export type Mutation = {
|
|
|
1329
1359
|
setMyNotificationPreference: NotificationPreference;
|
|
1330
1360
|
setMyPrimaryAuthenticationMethod: UserAuthenticationMethod;
|
|
1331
1361
|
setMyPrimaryMfaDevice: MfaDevice;
|
|
1362
|
+
/**
|
|
1363
|
+
* Opt a project in or out of keeping an exportable, encrypted copy of a project user's API-key
|
|
1364
|
+
* secret. Project scopes only. Requires `Project:SetExportableApiKeySecrets`, held by the Owner and
|
|
1365
|
+
* Admin roles and not by Dev. Enabling fails with a configuration error while
|
|
1366
|
+
* `API_KEY_EXPORTABLE_SECRET_ENCRYPTION_KEY` is not set; disabling never needs it. Each change is
|
|
1367
|
+
* audited on the project with the old and new value; the same value is a no-op. Nothing is
|
|
1368
|
+
* back-filled: keys that exist before the opt-in keep only their hash.
|
|
1369
|
+
*/
|
|
1370
|
+
setProjectExportableApiKeySecrets: Project;
|
|
1332
1371
|
/**
|
|
1333
1372
|
* Set or replace a project member's Email authentication method as unverified.
|
|
1334
1373
|
* `project.user_email_changed` carries ids only, never the address.
|
|
@@ -1704,6 +1743,10 @@ export type MutationSetMyPrimaryMfaDeviceArgs = {
|
|
|
1704
1743
|
input: SetMyPrimaryMfaDeviceInput;
|
|
1705
1744
|
};
|
|
1706
1745
|
|
|
1746
|
+
export type MutationSetProjectExportableApiKeySecretsArgs = {
|
|
1747
|
+
input: SetProjectExportableApiKeySecretsInput;
|
|
1748
|
+
};
|
|
1749
|
+
|
|
1707
1750
|
export type MutationSetProjectUserEmailArgs = {
|
|
1708
1751
|
input: SetProjectUserEmailInput;
|
|
1709
1752
|
};
|
|
@@ -2241,6 +2284,13 @@ export type Project = Auditable & {
|
|
|
2241
2284
|
createdAt: Scalars['Date']['output'];
|
|
2242
2285
|
deletedAt?: Maybe<Scalars['Date']['output']>;
|
|
2243
2286
|
description?: Maybe<Scalars['String']['output']>;
|
|
2287
|
+
/**
|
|
2288
|
+
* Whether this project keeps an exportable, encrypted copy of a project user's API-key secret at
|
|
2289
|
+
* create and rotate. Off by default; set with `setProjectExportableApiKeySecrets`. A plain
|
|
2290
|
+
* configuration flag: it says nothing about any key, and nothing is back-filled for keys that
|
|
2291
|
+
* already exist.
|
|
2292
|
+
*/
|
|
2293
|
+
exportableApiKeySecrets: Scalars['Boolean']['output'];
|
|
2244
2294
|
groups?: Maybe<Array<Group>>;
|
|
2245
2295
|
id: Scalars['ID']['output'];
|
|
2246
2296
|
name: Scalars['String']['output'];
|
|
@@ -2585,7 +2635,33 @@ export type ProjectUserApiKey = Auditable & {
|
|
|
2585
2635
|
userId: Scalars['ID']['output'];
|
|
2586
2636
|
};
|
|
2587
2637
|
|
|
2588
|
-
/**
|
|
2638
|
+
/**
|
|
2639
|
+
* Non-secret facts about one of a project user's API keys. Never contains the secret, the hash or the
|
|
2640
|
+
* encrypted copy.
|
|
2641
|
+
*/
|
|
2642
|
+
export type ProjectUserApiKeyCredential = {
|
|
2643
|
+
__typename?: 'ProjectUserApiKeyCredential';
|
|
2644
|
+
apiKeyId: Scalars['ID']['output'];
|
|
2645
|
+
/** The key's public client id. */
|
|
2646
|
+
clientId: Scalars['String']['output'];
|
|
2647
|
+
/** An encrypted copy of the key's current secret exists (only in a project that opted in). */
|
|
2648
|
+
exportable: Scalars['Boolean']['output'];
|
|
2649
|
+
/**
|
|
2650
|
+
* Lowercase hex SHA-256 of the secret string as issued (the text the client received, never the
|
|
2651
|
+
* stored hash): a change detector a holder of the secret can compare against. `null` for a key
|
|
2652
|
+
* created before this field existed and not rotated since. Grant issues secrets of 32 or 64 random
|
|
2653
|
+
* bytes, so the digest cannot be guessed offline; a secret chosen by an importer is only as strong
|
|
2654
|
+
* as the importer made it.
|
|
2655
|
+
*/
|
|
2656
|
+
fingerprint?: Maybe<Scalars['String']['output']>;
|
|
2657
|
+
/** When the key last changed (created, rotated or edited). */
|
|
2658
|
+
updatedAt: Scalars['Date']['output'];
|
|
2659
|
+
};
|
|
2660
|
+
|
|
2661
|
+
/**
|
|
2662
|
+
* Result of `exportProjectUserCredentials`. This is the one place a credential hash, and the plaintext
|
|
2663
|
+
* secret of an exportable API key, leaves Grant.
|
|
2664
|
+
*/
|
|
2589
2665
|
export type ProjectUserCredentialExport = {
|
|
2590
2666
|
__typename?: 'ProjectUserCredentialExport';
|
|
2591
2667
|
users: Array<ExportedProjectUserCredential>;
|
|
@@ -2634,11 +2710,14 @@ export type Query = {
|
|
|
2634
2710
|
/** Public signup policy. No authentication required. */
|
|
2635
2711
|
authSignupPolicy: AuthSignupPolicy;
|
|
2636
2712
|
/**
|
|
2637
|
-
* Export project members'
|
|
2638
|
-
*
|
|
2639
|
-
*
|
|
2640
|
-
*
|
|
2641
|
-
*
|
|
2713
|
+
* Export project members' credentials for a downstream identity mirror: password hashes in CDM
|
|
2714
|
+
* shape (`PASSWORD`) and the plaintext secrets of their API keys (`API_KEY_SECRET`, only for keys
|
|
2715
|
+
* of a project that opted in to exportable API-key secrets). This is the one audited exception to
|
|
2716
|
+
* "APIs expose only whether a password exists" and to "an API-key secret is shown once": it
|
|
2717
|
+
* requires `ProjectUser:ExportCredentials` (held by no standard group), every call is audited
|
|
2718
|
+
* (one summary row, plus one row per exported API-key secret), and it returns credentials. Any
|
|
2719
|
+
* non-member in `userIds` fails the whole call; members without a project credential are listed
|
|
2720
|
+
* with `password: null`, and a key without a copy with `secret: null`.
|
|
2642
2721
|
*/
|
|
2643
2722
|
exportProjectUserCredentials: ProjectUserCredentialExport;
|
|
2644
2723
|
groups: GroupPage;
|
|
@@ -3638,6 +3717,19 @@ export type SetMyPrimaryMfaDeviceInput = {
|
|
|
3638
3717
|
factorId: Scalars['ID']['input'];
|
|
3639
3718
|
};
|
|
3640
3719
|
|
|
3720
|
+
export type SetProjectExportableApiKeySecretsInput = {
|
|
3721
|
+
/**
|
|
3722
|
+
* `true` opts the project in; `false` opts it out and deletes every exportable copy the project
|
|
3723
|
+
* holds. Setting the value the project already has changes nothing and is not audited.
|
|
3724
|
+
*/
|
|
3725
|
+
enabled: Scalars['Boolean']['input'];
|
|
3726
|
+
/**
|
|
3727
|
+
* The project to change, as the scope the request is authorized in: `accountProject` or
|
|
3728
|
+
* `organizationProject` only.
|
|
3729
|
+
*/
|
|
3730
|
+
scope: Scope;
|
|
3731
|
+
};
|
|
3732
|
+
|
|
3641
3733
|
export type SetProjectUserEmailInput = {
|
|
3642
3734
|
/**
|
|
3643
3735
|
* Unverified Email authentication method. Project-scope only.
|
|
@@ -4227,6 +4319,13 @@ export type UpsertProjectOAuthConnectionInput = {
|
|
|
4227
4319
|
export type User = Auditable & {
|
|
4228
4320
|
__typename?: 'User';
|
|
4229
4321
|
accounts?: Maybe<Array<Account>>;
|
|
4322
|
+
/**
|
|
4323
|
+
* Non-secret facts about the user's live (not revoked, not deleted) project-user API keys in the
|
|
4324
|
+
* request's project scope, oldest first. `null` outside a project scope or when the caller lacks
|
|
4325
|
+
* `ProjectUser:ExportCredentials` (never an error); `[]` for a member with no key. The secret, the
|
|
4326
|
+
* hash and the encrypted copy are never returned here.
|
|
4327
|
+
*/
|
|
4328
|
+
apiKeyCredentials?: Maybe<Array<ProjectUserApiKeyCredential>>;
|
|
4230
4329
|
authenticationMethods?: Maybe<Array<UserAuthenticationMethod>>;
|
|
4231
4330
|
createdAt: Scalars['Date']['output'];
|
|
4232
4331
|
deletedAt?: Maybe<Scalars['Date']['output']>;
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
extend type Query {
|
|
2
2
|
"""
|
|
3
|
-
Export project members'
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
Export project members' credentials for a downstream identity mirror: password hashes in CDM
|
|
4
|
+
shape (`PASSWORD`) and the plaintext secrets of their API keys (`API_KEY_SECRET`, only for keys
|
|
5
|
+
of a project that opted in to exportable API-key secrets). This is the one audited exception to
|
|
6
|
+
"APIs expose only whether a password exists" and to "an API-key secret is shown once": it
|
|
7
|
+
requires `ProjectUser:ExportCredentials` (held by no standard group), every call is audited
|
|
8
|
+
(one summary row, plus one row per exported API-key secret), and it returns credentials. Any
|
|
9
|
+
non-member in `userIds` fails the whole call; members without a project credential are listed
|
|
10
|
+
with `password: null`, and a key without a copy with `secret: null`.
|
|
8
11
|
"""
|
|
9
12
|
exportProjectUserCredentials(
|
|
10
13
|
input: ExportProjectUserCredentialsInput!
|
|
@@ -5,4 +5,9 @@ not as a second export.
|
|
|
5
5
|
enum CredentialKind {
|
|
6
6
|
"The project password credential (bcrypt hash)."
|
|
7
7
|
PASSWORD
|
|
8
|
+
"""
|
|
9
|
+
The API keys of the project user, with the plaintext secret of each key the project keeps an
|
|
10
|
+
exportable copy of (a project that opted in to exportable API-key secrets).
|
|
11
|
+
"""
|
|
12
|
+
API_KEY_SECRET
|
|
8
13
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Non-secret facts about one of a project user's API keys. Never contains the secret, the hash or the
|
|
3
|
+
encrypted copy.
|
|
4
|
+
"""
|
|
5
|
+
type ProjectUserApiKeyCredential {
|
|
6
|
+
apiKeyId: ID!
|
|
7
|
+
"The key's public client id."
|
|
8
|
+
clientId: String!
|
|
9
|
+
"""
|
|
10
|
+
Lowercase hex SHA-256 of the secret string as issued (the text the client received, never the
|
|
11
|
+
stored hash): a change detector a holder of the secret can compare against. `null` for a key
|
|
12
|
+
created before this field existed and not rotated since. Grant issues secrets of 32 or 64 random
|
|
13
|
+
bytes, so the digest cannot be guessed offline; a secret chosen by an importer is only as strong
|
|
14
|
+
as the importer made it.
|
|
15
|
+
"""
|
|
16
|
+
fingerprint: String
|
|
17
|
+
"An encrypted copy of the key's current secret exists (only in a project that opted in)."
|
|
18
|
+
exportable: Boolean!
|
|
19
|
+
"When the key last changed (created, rotated or edited)."
|
|
20
|
+
updatedAt: Date!
|
|
21
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""
|
|
2
|
-
Result of `exportProjectUserCredentials`. This is the one place a credential hash
|
|
2
|
+
Result of `exportProjectUserCredentials`. This is the one place a credential hash, and the plaintext
|
|
3
|
+
secret of an exportable API key, leaves Grant.
|
|
3
4
|
"""
|
|
4
5
|
type ProjectUserCredentialExport {
|
|
5
6
|
users: [ExportedProjectUserCredential!]!
|
|
@@ -7,8 +8,34 @@ type ProjectUserCredentialExport {
|
|
|
7
8
|
|
|
8
9
|
type ExportedProjectUserCredential {
|
|
9
10
|
userId: ID!
|
|
10
|
-
"`null` when the member has no project password credential."
|
|
11
|
+
"`null` when the member has no project password credential, and when the `PASSWORD` kind was not requested."
|
|
11
12
|
password: ExportedPasswordCredential
|
|
13
|
+
"""
|
|
14
|
+
The member's live (not revoked, not deleted) project-user API keys, oldest first. `null` when the
|
|
15
|
+
`API_KEY_SECRET` kind was not requested; `[]` for a member with no key.
|
|
16
|
+
"""
|
|
17
|
+
apiKeySecrets: [ExportedApiKeySecret!]
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
"""
|
|
21
|
+
One API key of a project user in an export. `secret` is a credential: handle it like one.
|
|
22
|
+
"""
|
|
23
|
+
type ExportedApiKeySecret {
|
|
24
|
+
apiKeyId: ID!
|
|
25
|
+
"The key's public client id."
|
|
26
|
+
clientId: String!
|
|
27
|
+
"""
|
|
28
|
+
Lowercase hex SHA-256 of the secret string as issued, to compare with `sha256(secret)` of the
|
|
29
|
+
value a receiver holds. `null` for a key created before the fingerprint existed and not rotated
|
|
30
|
+
since.
|
|
31
|
+
"""
|
|
32
|
+
fingerprint: String
|
|
33
|
+
"""
|
|
34
|
+
The plaintext secret: 88 characters, standard base64 of 64 random bytes. `null` for a key the
|
|
35
|
+
project holds no copy of (created before the project opted in, created by a CDM import, or
|
|
36
|
+
rotated by a CDM replace): Grant cannot recover it.
|
|
37
|
+
"""
|
|
38
|
+
secret: String
|
|
12
39
|
}
|
|
13
40
|
|
|
14
41
|
"""
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
input SetProjectExportableApiKeySecretsInput {
|
|
2
|
+
"""
|
|
3
|
+
The project to change, as the scope the request is authorized in: `accountProject` or
|
|
4
|
+
`organizationProject` only.
|
|
5
|
+
"""
|
|
6
|
+
scope: Scope!
|
|
7
|
+
"""
|
|
8
|
+
`true` opts the project in; `false` opts it out and deletes every exportable copy the project
|
|
9
|
+
holds. Setting the value the project already has changes nothing and is not audited.
|
|
10
|
+
"""
|
|
11
|
+
enabled: Boolean!
|
|
12
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
extend type Mutation {
|
|
2
|
+
"""
|
|
3
|
+
Opt a project in or out of keeping an exportable, encrypted copy of a project user's API-key
|
|
4
|
+
secret. Project scopes only. Requires `Project:SetExportableApiKeySecrets`, held by the Owner and
|
|
5
|
+
Admin roles and not by Dev. Enabling fails with a configuration error while
|
|
6
|
+
`API_KEY_EXPORTABLE_SECRET_ENCRYPTION_KEY` is not set; disabling never needs it. Each change is
|
|
7
|
+
audited on the project with the old and new value; the same value is a no-op. Nothing is
|
|
8
|
+
back-filled: keys that exist before the opt-in keep only their hash.
|
|
9
|
+
"""
|
|
10
|
+
setProjectExportableApiKeySecrets(input: SetProjectExportableApiKeySecretsInput!): Project!
|
|
11
|
+
}
|
|
@@ -12,6 +12,13 @@ type Project implements Auditable {
|
|
|
12
12
|
Whether the OAuth help panel is shown. Null uses Grant's default (shown).
|
|
13
13
|
"""
|
|
14
14
|
showHelpPanel: Boolean
|
|
15
|
+
"""
|
|
16
|
+
Whether this project keeps an exportable, encrypted copy of a project user's API-key secret at
|
|
17
|
+
create and rotate. Off by default; set with `setProjectExportableApiKeySecrets`. A plain
|
|
18
|
+
configuration flag: it says nothing about any key, and nothing is back-filled for keys that
|
|
19
|
+
already exist.
|
|
20
|
+
"""
|
|
21
|
+
exportableApiKeySecrets: Boolean!
|
|
15
22
|
roles: [Role!]
|
|
16
23
|
groups: [Group!]
|
|
17
24
|
permissions: [Permission!]
|
|
@@ -21,6 +21,13 @@ type User implements Auditable {
|
|
|
21
21
|
stored hash string: a change detector, not a secret. The password and the hash are never returned here.
|
|
22
22
|
"""
|
|
23
23
|
passwordCredential: ProjectUserPasswordCredential
|
|
24
|
+
"""
|
|
25
|
+
Non-secret facts about the user's live (not revoked, not deleted) project-user API keys in the
|
|
26
|
+
request's project scope, oldest first. `null` outside a project scope or when the caller lacks
|
|
27
|
+
`ProjectUser:ExportCredentials` (never an error); `[]` for a member with no key. The secret, the
|
|
28
|
+
hash and the encrypted copy are never returned here.
|
|
29
|
+
"""
|
|
30
|
+
apiKeyCredentials: [ProjectUserApiKeyCredential!]
|
|
24
31
|
createdAt: Date!
|
|
25
32
|
updatedAt: Date!
|
|
26
33
|
deletedAt: Date
|
package/src/sdl-contract.test.ts
CHANGED
|
@@ -53,10 +53,11 @@ describe('operation documents', () => {
|
|
|
53
53
|
// 137 after CDM imports by reference: RequestProjectSyncUploadUrlDocument.
|
|
54
54
|
// 138 after operator-set-email: SetProjectUserEmailDocument.
|
|
55
55
|
// 140 after operator-set-password: SetProjectUserPasswordDocument, ExportProjectUserCredentialsDocument.
|
|
56
|
+
// 141 after exportable-api-key-secret opt-in: SetProjectExportableApiKeySecretsDocument.
|
|
56
57
|
// Updating this number is the point of the pin, not a nuisance: every change
|
|
57
58
|
// to it should be a line in a commit message.
|
|
58
59
|
it('finds every document under src/operations', () => {
|
|
59
|
-
expect(files).toHaveLength(
|
|
60
|
+
expect(files).toHaveLength(141);
|
|
60
61
|
});
|
|
61
62
|
|
|
62
63
|
it.each(files.map((f) => [relative(process.cwd(), f), f]))('%s validates', (_label, file) => {
|