@grantjs/schema 1.19.0 → 1.20.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.19.0",
3
+ "version": "1.20.0",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -152,6 +152,7 @@ export const EVENT_TYPES = [
152
152
  'user.deleted',
153
153
  'project.user_updated',
154
154
  'project.user_email_changed',
155
+ 'project.user_password_set',
155
156
  'api_key.deleted',
156
157
  'tag.created',
157
158
  'tag.updated',
@@ -228,6 +229,8 @@ export const EVENT_CATALOG: Readonly<Record<EventType, EventCatalogEntry>> = {
228
229
  'user.deleted': webhookOnly('membership'),
229
230
  'project.user_updated': webhookOnly('membership'),
230
231
  'project.user_email_changed': webhookOnly('membership'),
232
+ // Every project-credential write that changes the stored hash, any source. data.after = { projectId, userId, source }: never a hash or a password.
233
+ 'project.user_password_set': webhookOnly('membership'),
231
234
  'api_key.deleted': webhookOnly('security'),
232
235
  'tag.created': webhookOnly('iam'),
233
236
  'tag.updated': webhookOnly('iam'),
@@ -1310,6 +1310,16 @@ export type UpsertProjectOAuthConnectionMutation = {
1310
1310
  };
1311
1311
  };
1312
1312
 
1313
+ export type ExportProjectUserCredentialsQueryVariables = Exact<{
1314
+ input: Types.ExportProjectUserCredentialsInput;
1315
+ }>;
1316
+
1317
+ export type ExportProjectUserCredentialsQuery = {
1318
+ exportProjectUserCredentials: {
1319
+ users: Array<{ userId: string; password: { algorithm: string; hash: string } | null }>;
1320
+ };
1321
+ };
1322
+
1313
1323
  export type CancelProjectSyncMutationVariables = Exact<{
1314
1324
  id: string | number;
1315
1325
  scope: Types.Scope;
@@ -2188,6 +2198,20 @@ export type SetProjectUserEmailMutation = {
2188
2198
  };
2189
2199
  };
2190
2200
 
2201
+ export type SetProjectUserPasswordMutationVariables = Exact<{
2202
+ input: Types.SetProjectUserPasswordInput;
2203
+ }>;
2204
+
2205
+ export type SetProjectUserPasswordMutation = {
2206
+ setProjectUserPassword: {
2207
+ id: string;
2208
+ name: string;
2209
+ metadata: Record<string, unknown>;
2210
+ createdAt: Date;
2211
+ updatedAt: Date;
2212
+ };
2213
+ };
2214
+
2191
2215
  export type UpdateUserMutationVariables = Exact<{
2192
2216
  id: string | number;
2193
2217
  input: Types.UpdateUserInput;
@@ -7467,6 +7491,74 @@ export const UpsertProjectOAuthConnectionDocument = {
7467
7491
  UpsertProjectOAuthConnectionMutation,
7468
7492
  UpsertProjectOAuthConnectionMutationVariables
7469
7493
  >;
7494
+ export const ExportProjectUserCredentialsDocument = {
7495
+ kind: 'Document',
7496
+ definitions: [
7497
+ {
7498
+ kind: 'OperationDefinition',
7499
+ operation: 'query',
7500
+ name: { kind: 'Name', value: 'ExportProjectUserCredentials' },
7501
+ variableDefinitions: [
7502
+ {
7503
+ kind: 'VariableDefinition',
7504
+ variable: { kind: 'Variable', name: { kind: 'Name', value: 'input' } },
7505
+ type: {
7506
+ kind: 'NonNullType',
7507
+ type: {
7508
+ kind: 'NamedType',
7509
+ name: { kind: 'Name', value: 'ExportProjectUserCredentialsInput' },
7510
+ },
7511
+ },
7512
+ },
7513
+ ],
7514
+ selectionSet: {
7515
+ kind: 'SelectionSet',
7516
+ selections: [
7517
+ {
7518
+ kind: 'Field',
7519
+ name: { kind: 'Name', value: 'exportProjectUserCredentials' },
7520
+ arguments: [
7521
+ {
7522
+ kind: 'Argument',
7523
+ name: { kind: 'Name', value: 'input' },
7524
+ value: { kind: 'Variable', name: { kind: 'Name', value: 'input' } },
7525
+ },
7526
+ ],
7527
+ selectionSet: {
7528
+ kind: 'SelectionSet',
7529
+ selections: [
7530
+ {
7531
+ kind: 'Field',
7532
+ name: { kind: 'Name', value: 'users' },
7533
+ selectionSet: {
7534
+ kind: 'SelectionSet',
7535
+ selections: [
7536
+ { kind: 'Field', name: { kind: 'Name', value: 'userId' } },
7537
+ {
7538
+ kind: 'Field',
7539
+ name: { kind: 'Name', value: 'password' },
7540
+ selectionSet: {
7541
+ kind: 'SelectionSet',
7542
+ selections: [
7543
+ { kind: 'Field', name: { kind: 'Name', value: 'algorithm' } },
7544
+ { kind: 'Field', name: { kind: 'Name', value: 'hash' } },
7545
+ ],
7546
+ },
7547
+ },
7548
+ ],
7549
+ },
7550
+ },
7551
+ ],
7552
+ },
7553
+ },
7554
+ ],
7555
+ },
7556
+ },
7557
+ ],
7558
+ } as unknown as DocumentNode<
7559
+ ExportProjectUserCredentialsQuery,
7560
+ ExportProjectUserCredentialsQueryVariables
7561
+ >;
7470
7562
  export const CancelProjectSyncDocument = {
7471
7563
  kind: 'Document',
7472
7564
  definitions: [
@@ -11215,6 +11307,58 @@ export const SetProjectUserEmailDocument = {
11215
11307
  },
11216
11308
  ],
11217
11309
  } as unknown as DocumentNode<SetProjectUserEmailMutation, SetProjectUserEmailMutationVariables>;
11310
+ export const SetProjectUserPasswordDocument = {
11311
+ kind: 'Document',
11312
+ definitions: [
11313
+ {
11314
+ kind: 'OperationDefinition',
11315
+ operation: 'mutation',
11316
+ name: { kind: 'Name', value: 'SetProjectUserPassword' },
11317
+ variableDefinitions: [
11318
+ {
11319
+ kind: 'VariableDefinition',
11320
+ variable: { kind: 'Variable', name: { kind: 'Name', value: 'input' } },
11321
+ type: {
11322
+ kind: 'NonNullType',
11323
+ type: {
11324
+ kind: 'NamedType',
11325
+ name: { kind: 'Name', value: 'SetProjectUserPasswordInput' },
11326
+ },
11327
+ },
11328
+ },
11329
+ ],
11330
+ selectionSet: {
11331
+ kind: 'SelectionSet',
11332
+ selections: [
11333
+ {
11334
+ kind: 'Field',
11335
+ name: { kind: 'Name', value: 'setProjectUserPassword' },
11336
+ arguments: [
11337
+ {
11338
+ kind: 'Argument',
11339
+ name: { kind: 'Name', value: 'input' },
11340
+ value: { kind: 'Variable', name: { kind: 'Name', value: 'input' } },
11341
+ },
11342
+ ],
11343
+ selectionSet: {
11344
+ kind: 'SelectionSet',
11345
+ selections: [
11346
+ { kind: 'Field', name: { kind: 'Name', value: 'id' } },
11347
+ { kind: 'Field', name: { kind: 'Name', value: 'name' } },
11348
+ { kind: 'Field', name: { kind: 'Name', value: 'metadata' } },
11349
+ { kind: 'Field', name: { kind: 'Name', value: 'createdAt' } },
11350
+ { kind: 'Field', name: { kind: 'Name', value: 'updatedAt' } },
11351
+ ],
11352
+ },
11353
+ },
11354
+ ],
11355
+ },
11356
+ },
11357
+ ],
11358
+ } as unknown as DocumentNode<
11359
+ SetProjectUserPasswordMutation,
11360
+ SetProjectUserPasswordMutationVariables
11361
+ >;
11218
11362
  export const UpdateUserDocument = {
11219
11363
  kind: 'Document',
11220
11364
  definitions: [
@@ -271,6 +271,7 @@ export type ResolversTypes = ResolversObject<{
271
271
  CreateUserSessionInput: Types.CreateUserSessionInput;
272
272
  CreateWebhookSubscriptionMutationInput: Types.CreateWebhookSubscriptionMutationInput;
273
273
  CredentialExportData: ResolverTypeWrapper<Types.CredentialExportData>;
274
+ CredentialKind: Types.CredentialKind;
274
275
  Date: ResolverTypeWrapper<Types.Scalars['Date']['output']>;
275
276
  DeleteApiKeyInput: Types.DeleteApiKeyInput;
276
277
  DeleteMyAccountsInput: Types.DeleteMyAccountsInput;
@@ -281,6 +282,9 @@ export type ResolversTypes = ResolversObject<{
281
282
  EmailVerificationProofType: Types.EmailVerificationProofType;
282
283
  ExchangeApiKeyInput: Types.ExchangeApiKeyInput;
283
284
  ExchangeApiKeyResult: ResolverTypeWrapper<Types.ExchangeApiKeyResult>;
285
+ ExportProjectUserCredentialsInput: Types.ExportProjectUserCredentialsInput;
286
+ ExportedPasswordCredential: ResolverTypeWrapper<Types.ExportedPasswordCredential>;
287
+ ExportedProjectUserCredential: ResolverTypeWrapper<Types.ExportedProjectUserCredential>;
284
288
  GenerateMyMfaRecoveryCodesInput: Types.GenerateMyMfaRecoveryCodesInput;
285
289
  GetUserAuthenticationMethodsInput: Types.GetUserAuthenticationMethodsInput;
286
290
  GetUserSessionsInput: Types.GetUserSessionsInput;
@@ -385,7 +389,9 @@ export type ResolversTypes = ResolversObject<{
385
389
  ProjectTag: ResolverTypeWrapper<Types.ProjectTag>;
386
390
  ProjectUser: ResolverTypeWrapper<Types.ProjectUser>;
387
391
  ProjectUserApiKey: ResolverTypeWrapper<Types.ProjectUserApiKey>;
392
+ ProjectUserCredentialExport: ResolverTypeWrapper<Types.ProjectUserCredentialExport>;
388
393
  ProjectUserGroup: ResolverTypeWrapper<Types.ProjectUserGroup>;
394
+ ProjectUserPasswordCredential: ResolverTypeWrapper<Types.ProjectUserPasswordCredential>;
389
395
  ProjectUserPermission: ResolverTypeWrapper<Types.ProjectUserPermission>;
390
396
  Query: ResolverTypeWrapper<Record<PropertyKey, never>>;
391
397
  QueryAccountProjectApiKeysInput: Types.QueryAccountProjectApiKeysInput;
@@ -502,6 +508,7 @@ export type ResolversTypes = ResolversObject<{
502
508
  SetMyNotificationPreferenceInput: Types.SetMyNotificationPreferenceInput;
503
509
  SetMyPrimaryMfaDeviceInput: Types.SetMyPrimaryMfaDeviceInput;
504
510
  SetProjectUserEmailInput: Types.SetProjectUserEmailInput;
511
+ SetProjectUserPasswordInput: Types.SetProjectUserPasswordInput;
505
512
  SigningKey: ResolverTypeWrapper<Types.SigningKey>;
506
513
  SortOrder: Types.SortOrder;
507
514
  StartProjectExportInput: Types.StartProjectExportInput;
@@ -692,6 +699,9 @@ export type ResolversParentTypes = ResolversObject<{
692
699
  EmailVerificationProofInput: Types.EmailVerificationProofInput;
693
700
  ExchangeApiKeyInput: Types.ExchangeApiKeyInput;
694
701
  ExchangeApiKeyResult: Types.ExchangeApiKeyResult;
702
+ ExportProjectUserCredentialsInput: Types.ExportProjectUserCredentialsInput;
703
+ ExportedPasswordCredential: Types.ExportedPasswordCredential;
704
+ ExportedProjectUserCredential: Types.ExportedProjectUserCredential;
695
705
  GenerateMyMfaRecoveryCodesInput: Types.GenerateMyMfaRecoveryCodesInput;
696
706
  GetUserAuthenticationMethodsInput: Types.GetUserAuthenticationMethodsInput;
697
707
  GetUserSessionsInput: Types.GetUserSessionsInput;
@@ -772,7 +782,9 @@ export type ResolversParentTypes = ResolversObject<{
772
782
  ProjectTag: Types.ProjectTag;
773
783
  ProjectUser: Types.ProjectUser;
774
784
  ProjectUserApiKey: Types.ProjectUserApiKey;
785
+ ProjectUserCredentialExport: Types.ProjectUserCredentialExport;
775
786
  ProjectUserGroup: Types.ProjectUserGroup;
787
+ ProjectUserPasswordCredential: Types.ProjectUserPasswordCredential;
776
788
  ProjectUserPermission: Types.ProjectUserPermission;
777
789
  Query: Record<PropertyKey, never>;
778
790
  QueryAccountProjectApiKeysInput: Types.QueryAccountProjectApiKeysInput;
@@ -885,6 +897,7 @@ export type ResolversParentTypes = ResolversObject<{
885
897
  SetMyNotificationPreferenceInput: Types.SetMyNotificationPreferenceInput;
886
898
  SetMyPrimaryMfaDeviceInput: Types.SetMyPrimaryMfaDeviceInput;
887
899
  SetProjectUserEmailInput: Types.SetProjectUserEmailInput;
900
+ SetProjectUserPasswordInput: Types.SetProjectUserPasswordInput;
888
901
  SigningKey: Types.SigningKey;
889
902
  StartProjectExportInput: Types.StartProjectExportInput;
890
903
  String: Types.Scalars['String']['output'];
@@ -1325,6 +1338,28 @@ export type ExchangeApiKeyResultResolvers<
1325
1338
  expiresIn?: Resolver<ResolversTypes['Int'], ParentType, ContextType>;
1326
1339
  }>;
1327
1340
 
1341
+ export type ExportedPasswordCredentialResolvers<
1342
+ ContextType = any,
1343
+ ParentType extends ResolversParentTypes['ExportedPasswordCredential'] =
1344
+ ResolversParentTypes['ExportedPasswordCredential'],
1345
+ > = ResolversObject<{
1346
+ algorithm?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
1347
+ hash?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
1348
+ }>;
1349
+
1350
+ export type ExportedProjectUserCredentialResolvers<
1351
+ ContextType = any,
1352
+ ParentType extends ResolversParentTypes['ExportedProjectUserCredential'] =
1353
+ ResolversParentTypes['ExportedProjectUserCredential'],
1354
+ > = ResolversObject<{
1355
+ password?: Resolver<
1356
+ Types.Maybe<ResolversTypes['ExportedPasswordCredential']>,
1357
+ ParentType,
1358
+ ContextType
1359
+ >;
1360
+ userId?: Resolver<ResolversTypes['ID'], ParentType, ContextType>;
1361
+ }>;
1362
+
1328
1363
  export type GroupResolvers<
1329
1364
  ContextType = any,
1330
1365
  ParentType extends ResolversParentTypes['Group'] = ResolversParentTypes['Group'],
@@ -1968,6 +2003,12 @@ export type MutationResolvers<
1968
2003
  ContextType,
1969
2004
  RequireFields<Types.MutationSetProjectUserEmailArgs, 'input'>
1970
2005
  >;
2006
+ setProjectUserPassword?: Resolver<
2007
+ ResolversTypes['User'],
2008
+ ParentType,
2009
+ ContextType,
2010
+ RequireFields<Types.MutationSetProjectUserPasswordArgs, 'input'>
2011
+ >;
1971
2012
  setupMfa?: Resolver<ResolversTypes['MfaSetupResponse'], ParentType, ContextType>;
1972
2013
  startProjectExport?: Resolver<
1973
2014
  ResolversTypes['ProjectSyncJob'],
@@ -2846,6 +2887,14 @@ export type ProjectUserApiKeyResolvers<
2846
2887
  __isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
2847
2888
  }>;
2848
2889
 
2890
+ export type ProjectUserCredentialExportResolvers<
2891
+ ContextType = any,
2892
+ ParentType extends ResolversParentTypes['ProjectUserCredentialExport'] =
2893
+ ResolversParentTypes['ProjectUserCredentialExport'],
2894
+ > = ResolversObject<{
2895
+ users?: Resolver<Array<ResolversTypes['ExportedProjectUserCredential']>, ParentType, ContextType>;
2896
+ }>;
2897
+
2849
2898
  export type ProjectUserGroupResolvers<
2850
2899
  ContextType = any,
2851
2900
  ParentType extends ResolversParentTypes['ProjectUserGroup'] =
@@ -2861,6 +2910,16 @@ export type ProjectUserGroupResolvers<
2861
2910
  __isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
2862
2911
  }>;
2863
2912
 
2913
+ export type ProjectUserPasswordCredentialResolvers<
2914
+ ContextType = any,
2915
+ ParentType extends ResolversParentTypes['ProjectUserPasswordCredential'] =
2916
+ ResolversParentTypes['ProjectUserPasswordCredential'],
2917
+ > = ResolversObject<{
2918
+ fingerprint?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
2919
+ source?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
2920
+ updatedAt?: Resolver<ResolversTypes['Date'], ParentType, ContextType>;
2921
+ }>;
2922
+
2864
2923
  export type ProjectUserPermissionResolvers<
2865
2924
  ContextType = any,
2866
2925
  ParentType extends ResolversParentTypes['ProjectUserPermission'] =
@@ -2888,6 +2947,12 @@ export type QueryResolvers<
2888
2947
  RequireFields<Types.QueryApiKeysArgs, 'scope'>
2889
2948
  >;
2890
2949
  authSignupPolicy?: Resolver<ResolversTypes['AuthSignupPolicy'], ParentType, ContextType>;
2950
+ exportProjectUserCredentials?: Resolver<
2951
+ ResolversTypes['ProjectUserCredentialExport'],
2952
+ ParentType,
2953
+ ContextType,
2954
+ RequireFields<Types.QueryExportProjectUserCredentialsArgs, 'input'>
2955
+ >;
2891
2956
  groups?: Resolver<
2892
2957
  ResolversTypes['GroupPage'],
2893
2958
  ParentType,
@@ -3445,6 +3510,11 @@ export type UserResolvers<
3445
3510
  id?: Resolver<ResolversTypes['ID'], ParentType, ContextType>;
3446
3511
  metadata?: Resolver<ResolversTypes['JSON'], ParentType, ContextType>;
3447
3512
  name?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
3513
+ passwordCredential?: Resolver<
3514
+ Types.Maybe<ResolversTypes['ProjectUserPasswordCredential']>,
3515
+ ParentType,
3516
+ ContextType
3517
+ >;
3448
3518
  permissionCount?: Resolver<ResolversTypes['Int'], ParentType, ContextType>;
3449
3519
  pictureUrl?: Resolver<Types.Maybe<ResolversTypes['String']>, ParentType, ContextType>;
3450
3520
  primaryTag?: Resolver<Types.Maybe<ResolversTypes['Tag']>, ParentType, ContextType>;
@@ -3766,6 +3836,8 @@ export type Resolvers<ContextType = any> = ResolversObject<{
3766
3836
  CredentialExportData?: CredentialExportDataResolvers<ContextType>;
3767
3837
  Date?: GraphQLScalarType;
3768
3838
  ExchangeApiKeyResult?: ExchangeApiKeyResultResolvers<ContextType>;
3839
+ ExportedPasswordCredential?: ExportedPasswordCredentialResolvers<ContextType>;
3840
+ ExportedProjectUserCredential?: ExportedProjectUserCredentialResolvers<ContextType>;
3769
3841
  Group?: GroupResolvers<ContextType>;
3770
3842
  GroupPage?: GroupPageResolvers<ContextType>;
3771
3843
  GroupPermission?: GroupPermissionResolvers<ContextType>;
@@ -3824,7 +3896,9 @@ export type Resolvers<ContextType = any> = ResolversObject<{
3824
3896
  ProjectTag?: ProjectTagResolvers<ContextType>;
3825
3897
  ProjectUser?: ProjectUserResolvers<ContextType>;
3826
3898
  ProjectUserApiKey?: ProjectUserApiKeyResolvers<ContextType>;
3899
+ ProjectUserCredentialExport?: ProjectUserCredentialExportResolvers<ContextType>;
3827
3900
  ProjectUserGroup?: ProjectUserGroupResolvers<ContextType>;
3901
+ ProjectUserPasswordCredential?: ProjectUserPasswordCredentialResolvers<ContextType>;
3828
3902
  ProjectUserPermission?: ProjectUserPermissionResolvers<ContextType>;
3829
3903
  Query?: QueryResolvers<ContextType>;
3830
3904
  RefreshSessionResponse?: RefreshSessionResponseResolvers<ContextType>;
@@ -808,6 +808,15 @@ export type CredentialExportData = {
808
808
  updatedAt: Scalars['Date']['output'];
809
809
  };
810
810
 
811
+ /**
812
+ * A kind of credential that can be exported. Extensible: a later kind is added here,
813
+ * not as a second export.
814
+ */
815
+ export enum CredentialKind {
816
+ /** The project password credential (bcrypt hash). */
817
+ Password = 'PASSWORD',
818
+ }
819
+
811
820
  export type DeleteApiKeyInput = {
812
821
  hardDelete?: InputMaybe<Scalars['Boolean']['input']>;
813
822
  id: Scalars['ID']['input'];
@@ -853,6 +862,31 @@ export type ExchangeApiKeyResult = {
853
862
  expiresIn: Scalars['Int']['output'];
854
863
  };
855
864
 
865
+ export type ExportProjectUserCredentialsInput = {
866
+ /** Credential kinds to export. Defaults to `[PASSWORD]`. */
867
+ kinds?: InputMaybe<Array<CredentialKind>>;
868
+ /** Project scope the members belong to. */
869
+ scope: Scope;
870
+ /** Project members to export. Every id must be a member of the project, otherwise the whole call fails with not found (no partial export). */
871
+ userIds: Array<Scalars['ID']['input']>;
872
+ };
873
+
874
+ /** A stored password hash in CDM shape. Secret-equivalent: handle like a credential. */
875
+ export type ExportedPasswordCredential = {
876
+ __typename?: 'ExportedPasswordCredential';
877
+ /** Hash algorithm, for example `bcrypt`. */
878
+ algorithm: Scalars['String']['output'];
879
+ /** The stored hash string. */
880
+ hash: Scalars['String']['output'];
881
+ };
882
+
883
+ export type ExportedProjectUserCredential = {
884
+ __typename?: 'ExportedProjectUserCredential';
885
+ /** `null` when the member has no project password credential. */
886
+ password?: Maybe<ExportedPasswordCredential>;
887
+ userId: Scalars['ID']['output'];
888
+ };
889
+
856
890
  export type GenerateMyMfaRecoveryCodesInput = {
857
891
  factorId?: InputMaybe<Scalars['ID']['input']>;
858
892
  };
@@ -1295,6 +1329,14 @@ export type Mutation = {
1295
1329
  * `project.user_email_changed` carries ids only, never the address.
1296
1330
  */
1297
1331
  setProjectUserEmail: User;
1332
+ /**
1333
+ * Set or replace a project member's password. Project scopes only: the operator supplies the
1334
+ * password, and the password policy is enforced server-side. Writes the project credential with
1335
+ * source `admin-set` (a later CDM import never overwrites it) and revokes the user's project-app grants.
1336
+ * The password is never returned, logged or carried by any event; `project.user_password_set`
1337
+ * carries ids and the credential source only.
1338
+ */
1339
+ setProjectUserPassword: User;
1298
1340
  setupMfa: MfaSetupResponse;
1299
1341
  /**
1300
1342
  * Enqueue an asynchronous CDM **export** job for the given project. The worker
@@ -1661,6 +1703,10 @@ export type MutationSetProjectUserEmailArgs = {
1661
1703
  input: SetProjectUserEmailInput;
1662
1704
  };
1663
1705
 
1706
+ export type MutationSetProjectUserPasswordArgs = {
1707
+ input: SetProjectUserPasswordInput;
1708
+ };
1709
+
1664
1710
  export type MutationStartProjectExportArgs = {
1665
1711
  id: Scalars['ID']['input'];
1666
1712
  input: StartProjectExportInput;
@@ -2534,6 +2580,12 @@ export type ProjectUserApiKey = Auditable & {
2534
2580
  userId: Scalars['ID']['output'];
2535
2581
  };
2536
2582
 
2583
+ /** Result of `exportProjectUserCredentials`. This is the one place a credential hash leaves Grant. */
2584
+ export type ProjectUserCredentialExport = {
2585
+ __typename?: 'ProjectUserCredentialExport';
2586
+ users: Array<ExportedProjectUserCredential>;
2587
+ };
2588
+
2537
2589
  export type ProjectUserGroup = Auditable & {
2538
2590
  __typename?: 'ProjectUserGroup';
2539
2591
  createdAt: Scalars['Date']['output'];
@@ -2545,6 +2597,20 @@ export type ProjectUserGroup = Auditable & {
2545
2597
  userId: Scalars['ID']['output'];
2546
2598
  };
2547
2599
 
2600
+ /** Non-secret facts about a user's project password credential. Never contains the hash or the password. */
2601
+ export type ProjectUserPasswordCredential = {
2602
+ __typename?: 'ProjectUserPasswordCredential';
2603
+ /**
2604
+ * Lowercase hex SHA-256 of the stored hash string. A change detector, not a secret: it lets a
2605
+ * caller see that the password changed without learning anything usable from it.
2606
+ */
2607
+ fingerprint: Scalars['String']['output'];
2608
+ /** How the credential was written (for example `reset`, `cdm-import` or `admin-set`). A plain string: the schema has no credential-source enum. */
2609
+ source: Scalars['String']['output'];
2610
+ /** When the stored credential last changed. */
2611
+ updatedAt: Scalars['Date']['output'];
2612
+ };
2613
+
2548
2614
  export type ProjectUserPermission = Auditable & {
2549
2615
  __typename?: 'ProjectUserPermission';
2550
2616
  createdAt: Scalars['Date']['output'];
@@ -2562,6 +2628,14 @@ export type Query = {
2562
2628
  apiKeys: ApiKeyPage;
2563
2629
  /** Public signup policy. No authentication required. */
2564
2630
  authSignupPolicy: AuthSignupPolicy;
2631
+ /**
2632
+ * Export project members' password hashes in CDM shape, for a downstream identity mirror.
2633
+ * This is the one audited exception to "APIs expose only whether a password exists": it
2634
+ * requires `ProjectUser:ExportCredentials` (held by no standard group), every call is audited,
2635
+ * and it returns hashes. Any non-member in `userIds` fails the whole call; members without a
2636
+ * project credential are listed with `password: null`.
2637
+ */
2638
+ exportProjectUserCredentials: ProjectUserCredentialExport;
2565
2639
  groups: GroupPage;
2566
2640
  invitation?: Maybe<OrganizationInvitation>;
2567
2641
  /** Evaluate authorization for the current session or API key token. */
@@ -2630,6 +2704,10 @@ export type QueryApiKeysArgs = {
2630
2704
  sort?: InputMaybe<ApiKeySortInput>;
2631
2705
  };
2632
2706
 
2707
+ export type QueryExportProjectUserCredentialsArgs = {
2708
+ input: ExportProjectUserCredentialsInput;
2709
+ };
2710
+
2633
2711
  export type QueryGroupsArgs = {
2634
2712
  ids?: InputMaybe<Array<Scalars['ID']['input']>>;
2635
2713
  limit?: InputMaybe<Scalars['Int']['input']>;
@@ -3565,6 +3643,16 @@ export type SetProjectUserEmailInput = {
3565
3643
  userId: Scalars['ID']['input'];
3566
3644
  };
3567
3645
 
3646
+ export type SetProjectUserPasswordInput = {
3647
+ /**
3648
+ * The new password, chosen by the operator. Project-scope only. The password policy is enforced
3649
+ * server-side; the value is never returned, logged or carried by any event.
3650
+ */
3651
+ password: Scalars['String']['input'];
3652
+ scope: Scope;
3653
+ userId: Scalars['ID']['input'];
3654
+ };
3655
+
3568
3656
  /**
3569
3657
  * Signing key for a scope (e.g. project). Used for RS256 API key tokens; public key is exposed in JWKS.
3570
3658
  * Only project scopes (accountProject, organizationProject) have manageable keys; system key is internal.
@@ -4135,6 +4223,13 @@ export type User = Auditable & {
4135
4223
  id: Scalars['ID']['output'];
4136
4224
  metadata: Scalars['JSON']['output'];
4137
4225
  name: Scalars['String']['output'];
4226
+ /**
4227
+ * Non-secret facts about the user's password credential in the request's project scope. `null` outside a
4228
+ * project scope, when the user has no project credential there, or when the caller lacks
4229
+ * `ProjectUser:ExportCredentials` (never an error). `fingerprint` is the lowercase hex SHA-256 of the
4230
+ * stored hash string: a change detector, not a secret. The password and the hash are never returned here.
4231
+ */
4232
+ passwordCredential?: Maybe<ProjectUserPasswordCredential>;
4138
4233
  permissionCount: Scalars['Int']['output'];
4139
4234
  pictureUrl?: Maybe<Scalars['String']['output']>;
4140
4235
  primaryTag?: Maybe<Tag>;
@@ -0,0 +1,11 @@
1
+ query ExportProjectUserCredentials($input: ExportProjectUserCredentialsInput!) {
2
+ exportProjectUserCredentials(input: $input) {
3
+ users {
4
+ userId
5
+ password {
6
+ algorithm
7
+ hash
8
+ }
9
+ }
10
+ }
11
+ }
@@ -0,0 +1,9 @@
1
+ mutation SetProjectUserPassword($input: SetProjectUserPasswordInput!) {
2
+ setProjectUserPassword(input: $input) {
3
+ id
4
+ name
5
+ metadata
6
+ createdAt
7
+ updatedAt
8
+ }
9
+ }
@@ -0,0 +1,8 @@
1
+ input ExportProjectUserCredentialsInput {
2
+ "Project scope the members belong to."
3
+ scope: Scope!
4
+ "Project members to export. Every id must be a member of the project, otherwise the whole call fails with not found (no partial export)."
5
+ userIds: [ID!]!
6
+ "Credential kinds to export. Defaults to `[PASSWORD]`."
7
+ kinds: [CredentialKind!] = [PASSWORD]
8
+ }
@@ -0,0 +1,12 @@
1
+ extend type Query {
2
+ """
3
+ Export project members' password hashes in CDM shape, for a downstream identity mirror.
4
+ This is the one audited exception to "APIs expose only whether a password exists": it
5
+ requires `ProjectUser:ExportCredentials` (held by no standard group), every call is audited,
6
+ and it returns hashes. Any non-member in `userIds` fails the whole call; members without a
7
+ project credential are listed with `password: null`.
8
+ """
9
+ exportProjectUserCredentials(
10
+ input: ExportProjectUserCredentialsInput!
11
+ ): ProjectUserCredentialExport!
12
+ }
@@ -0,0 +1,8 @@
1
+ """
2
+ A kind of credential that can be exported. Extensible: a later kind is added here,
3
+ not as a second export.
4
+ """
5
+ enum CredentialKind {
6
+ "The project password credential (bcrypt hash)."
7
+ PASSWORD
8
+ }
@@ -0,0 +1,22 @@
1
+ """
2
+ Result of `exportProjectUserCredentials`. This is the one place a credential hash leaves Grant.
3
+ """
4
+ type ProjectUserCredentialExport {
5
+ users: [ExportedProjectUserCredential!]!
6
+ }
7
+
8
+ type ExportedProjectUserCredential {
9
+ userId: ID!
10
+ "`null` when the member has no project password credential."
11
+ password: ExportedPasswordCredential
12
+ }
13
+
14
+ """
15
+ A stored password hash in CDM shape. Secret-equivalent: handle like a credential.
16
+ """
17
+ type ExportedPasswordCredential {
18
+ "Hash algorithm, for example `bcrypt`."
19
+ algorithm: String!
20
+ "The stored hash string."
21
+ hash: String!
22
+ }
@@ -0,0 +1,14 @@
1
+ """
2
+ Non-secret facts about a user's project password credential. Never contains the hash or the password.
3
+ """
4
+ type ProjectUserPasswordCredential {
5
+ "How the credential was written (for example `reset`, `cdm-import` or `admin-set`). A plain string: the schema has no credential-source enum."
6
+ source: String!
7
+ "When the stored credential last changed."
8
+ updatedAt: Date!
9
+ """
10
+ Lowercase hex SHA-256 of the stored hash string. A change detector, not a secret: it lets a
11
+ caller see that the password changed without learning anything usable from it.
12
+ """
13
+ fingerprint: String!
14
+ }
@@ -0,0 +1,9 @@
1
+ input SetProjectUserPasswordInput {
2
+ userId: ID!
3
+ """
4
+ The new password, chosen by the operator. Project-scope only. The password policy is enforced
5
+ server-side; the value is never returned, logged or carried by any event.
6
+ """
7
+ password: String!
8
+ scope: Scope!
9
+ }
@@ -0,0 +1,10 @@
1
+ extend type Mutation {
2
+ """
3
+ Set or replace a project member's password. Project scopes only: the operator supplies the
4
+ password, and the password policy is enforced server-side. Writes the project credential with
5
+ source `admin-set` (a later CDM import never overwrites it) and revokes the user's project-app grants.
6
+ The password is never returned, logged or carried by any event; `project.user_password_set`
7
+ carries ids and the credential source only.
8
+ """
9
+ setProjectUserPassword(input: SetProjectUserPasswordInput!): User!
10
+ }
@@ -14,6 +14,13 @@ type User implements Auditable {
14
14
  tagCount: Int!
15
15
  authenticationMethods: [UserAuthenticationMethod!]
16
16
  accounts: [Account!]
17
+ """
18
+ Non-secret facts about the user's password credential in the request's project scope. `null` outside a
19
+ project scope, when the user has no project credential there, or when the caller lacks
20
+ `ProjectUser:ExportCredentials` (never an error). `fingerprint` is the lowercase hex SHA-256 of the
21
+ stored hash string: a change detector, not a secret. The password and the hash are never returned here.
22
+ """
23
+ passwordCredential: ProjectUserPasswordCredential
17
24
  createdAt: Date!
18
25
  updatedAt: Date!
19
26
  deletedAt: Date
@@ -52,10 +52,11 @@ describe('operation documents', () => {
52
52
  // 136 after closed-install: AuthSignupPolicyDocument (publicSignupEnabled, bootstrapOpen).
53
53
  // 137 after CDM imports by reference: RequestProjectSyncUploadUrlDocument.
54
54
  // 138 after operator-set-email: SetProjectUserEmailDocument.
55
+ // 140 after operator-set-password: SetProjectUserPasswordDocument, ExportProjectUserCredentialsDocument.
55
56
  // Updating this number is the point of the pin, not a nuisance: every change
56
57
  // to it should be a line in a commit message.
57
58
  it('finds every document under src/operations', () => {
58
- expect(files).toHaveLength(138);
59
+ expect(files).toHaveLength(140);
59
60
  });
60
61
 
61
62
  it.each(files.map((f) => [relative(process.cwd(), f), f]))('%s validates', (_label, file) => {
@@ -68,7 +69,7 @@ describe('graph reachability', () => {
68
69
  const schema = buildMergedSchema();
69
70
 
70
71
  /**
71
- * 175 of 382 declared types cannot be reached from Query or Mutation. They are
72
+ * 175 of 389 declared types cannot be reached from Query or Mutation. They are
72
73
  * not dead: most are consumed as generated TypeScript by apps/api, and the
73
74
  * *SearchableField enums are runtime search configuration. But every one of them
74
75
  * ships in the schema `makeExecutableSchema` serves.
@@ -81,6 +82,12 @@ describe('graph reachability', () => {
81
82
  * Was 181 of 388 before pass 5 slice 4 removed six declarations that were
82
83
  * unreferenced in SDL and in all TypeScript. The reachable count did not change
83
84
  * (207 before and after), which is what makes those six safe to have deleted.
85
+ *
86
+ * operator-set-password declared seven types (382 -> 389 declared). While
87
+ * `passwordCredential` sat on `ProjectUser`, which no query or mutation returns,
88
+ * `ProjectUserPasswordCredential` was unreachable and the pin was 176. It now sits on
89
+ * `User`, so the type is reachable and the pin is back to 175; the other six new
90
+ * types are reachable through the new mutation and query.
84
91
  */
85
92
  const UNREACHABLE_FROM_ROOT = 175;
86
93