@grantjs/schema 1.16.1 → 1.18.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.16.1",
3
+ "version": "1.18.0",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -83,6 +83,17 @@ const INTEGRATIONS_OWNERS: EventCatalogEntry = {
83
83
  audienceRule: { primitives: ['owners'], excludeActor: true },
84
84
  };
85
85
 
86
+ /**
87
+ * An event that exists for webhook consumers only: delivered transactionally to
88
+ * subscriptions, never turned into a notification. The empty audience is what
89
+ * keeps the notification generator from creating rows for it.
90
+ */
91
+ const webhookOnly = (category: EventCategory): EventCatalogEntry => ({
92
+ category,
93
+ deliveryClass: 'transactional',
94
+ audienceRule: { primitives: [] },
95
+ });
96
+
86
97
  export const EVENT_TYPES = [
87
98
  // IAM mutations (Phase 1 slice)
88
99
  'role.created',
@@ -135,6 +146,17 @@ export const EVENT_TYPES = [
135
146
  // CDM / project sync job summaries (entity events suppressed during import)
136
147
  'project_sync.completed',
137
148
  'project_sync.failed',
149
+ // Webhook-only lifecycle events (no notification): users, project users, API keys, tags
150
+ 'user.created',
151
+ 'user.updated',
152
+ 'user.deleted',
153
+ 'project.user_updated',
154
+ 'api_key.deleted',
155
+ 'tag.created',
156
+ 'tag.updated',
157
+ 'tag.deleted',
158
+ 'tag.linked',
159
+ 'tag.unlinked',
138
160
  ] as const;
139
161
 
140
162
  export type EventType = (typeof EVENT_TYPES)[number];
@@ -200,6 +222,16 @@ export const EVENT_CATALOG: Readonly<Record<EventType, EventCatalogEntry>> = {
200
222
  'user.password_changed': SECURITY_SUBJECT,
201
223
  'project_sync.completed': INTEGRATIONS_OWNERS,
202
224
  'project_sync.failed': INTEGRATIONS_OWNERS,
225
+ 'user.created': webhookOnly('membership'),
226
+ 'user.updated': webhookOnly('membership'),
227
+ 'user.deleted': webhookOnly('membership'),
228
+ 'project.user_updated': webhookOnly('membership'),
229
+ 'api_key.deleted': webhookOnly('security'),
230
+ 'tag.created': webhookOnly('iam'),
231
+ 'tag.updated': webhookOnly('iam'),
232
+ 'tag.deleted': webhookOnly('iam'),
233
+ 'tag.linked': webhookOnly('iam'),
234
+ 'tag.unlinked': webhookOnly('iam'),
203
235
  };
204
236
 
205
237
  export function getEventCatalogEntry(type: EventType): EventCatalogEntry {
@@ -33,6 +33,13 @@ export interface DomainEventInput {
33
33
  aggregate?: EventAggregateRef;
34
34
  data: EventData;
35
35
  occurredAt?: Date;
36
+ /**
37
+ * Projects this event is relevant to beyond the one named by its scope. The
38
+ * publisher adds the projects an entity-bound aggregate is linked to; a caller
39
+ * passes it only to capture the links before a write that destroys them (a
40
+ * delete). Never part of the external envelope.
41
+ */
42
+ projectIds?: string[];
36
43
  }
37
44
 
38
45
  /**
@@ -51,6 +58,11 @@ export interface DomainEvent {
51
58
  aggregate?: EventAggregateRef;
52
59
  data: EventData;
53
60
  occurredAt: Date;
61
+ /**
62
+ * Projects the event is relevant to in addition to the project its scope names
63
+ * (webhook fan-out, ADR 0012). Internal: not carried by the webhook envelope.
64
+ */
65
+ projectIds?: string[];
54
66
  }
55
67
 
56
68
  /**
@@ -68,6 +80,14 @@ export interface CloudEventEnvelope {
68
80
  grantsequence: string;
69
81
  grantscope: { tenant: string; id: string };
70
82
  grantactor?: { userId: string } | null;
83
+ /**
84
+ * The user the event is about, when it has one (`user.role_revoked`,
85
+ * `project.user_removed`, `organization.invitation_sent`, ...). `null` when
86
+ * the event has no subject user. Needed because the internal `before`
87
+ * snapshot is dropped from external `data`, so revoke/remove events would
88
+ * otherwise not say whose access changed.
89
+ */
90
+ grantsubjectuser?: { userId: string } | null;
71
91
  grantcategory: EventCategory;
72
92
  data: EventData;
73
93
  }
@@ -583,6 +583,14 @@ export type MyUserDataExportQuery = {
583
583
  lastUsedAt: Date | null;
584
584
  createdAt: Date;
585
585
  }>;
586
+ credentials: Array<{
587
+ scope: string;
588
+ projectId: string | null;
589
+ source: string;
590
+ lastUsedAt: Date | null;
591
+ createdAt: Date;
592
+ updatedAt: Date;
593
+ }>;
586
594
  sessions: Array<{
587
595
  userAgent: string | null;
588
596
  ipAddress: string | null;
@@ -4520,6 +4528,21 @@ export const MyUserDataExportDocument = {
4520
4528
  ],
4521
4529
  },
4522
4530
  },
4531
+ {
4532
+ kind: 'Field',
4533
+ name: { kind: 'Name', value: 'credentials' },
4534
+ selectionSet: {
4535
+ kind: 'SelectionSet',
4536
+ selections: [
4537
+ { kind: 'Field', name: { kind: 'Name', value: 'scope' } },
4538
+ { kind: 'Field', name: { kind: 'Name', value: 'projectId' } },
4539
+ { kind: 'Field', name: { kind: 'Name', value: 'source' } },
4540
+ { kind: 'Field', name: { kind: 'Name', value: 'lastUsedAt' } },
4541
+ { kind: 'Field', name: { kind: 'Name', value: 'createdAt' } },
4542
+ { kind: 'Field', name: { kind: 'Name', value: 'updatedAt' } },
4543
+ ],
4544
+ },
4545
+ },
4523
4546
  {
4524
4547
  kind: 'Field',
4525
4548
  name: { kind: 'Name', value: 'sessions' },
@@ -270,6 +270,7 @@ export type ResolversTypes = ResolversObject<{
270
270
  CreateUserInput: Types.CreateUserInput;
271
271
  CreateUserSessionInput: Types.CreateUserSessionInput;
272
272
  CreateWebhookSubscriptionMutationInput: Types.CreateWebhookSubscriptionMutationInput;
273
+ CredentialExportData: ResolverTypeWrapper<Types.CredentialExportData>;
273
274
  Date: ResolverTypeWrapper<Types.Scalars['Date']['output']>;
274
275
  DeleteApiKeyInput: Types.DeleteApiKeyInput;
275
276
  DeleteMyAccountsInput: Types.DeleteMyAccountsInput;
@@ -680,6 +681,7 @@ export type ResolversParentTypes = ResolversObject<{
680
681
  CreateUserInput: Types.CreateUserInput;
681
682
  CreateUserSessionInput: Types.CreateUserSessionInput;
682
683
  CreateWebhookSubscriptionMutationInput: Types.CreateWebhookSubscriptionMutationInput;
684
+ CredentialExportData: Types.CredentialExportData;
683
685
  Date: Types.Scalars['Date']['output'];
684
686
  DeleteApiKeyInput: Types.DeleteApiKeyInput;
685
687
  DeleteMyAccountsInput: Types.DeleteMyAccountsInput;
@@ -1295,6 +1297,19 @@ export type CreateProjectAppResultResolvers<
1295
1297
  signUpRoleId?: Resolver<Types.Maybe<ResolversTypes['ID']>, ParentType, ContextType>;
1296
1298
  }>;
1297
1299
 
1300
+ export type CredentialExportDataResolvers<
1301
+ ContextType = any,
1302
+ ParentType extends ResolversParentTypes['CredentialExportData'] =
1303
+ ResolversParentTypes['CredentialExportData'],
1304
+ > = ResolversObject<{
1305
+ createdAt?: Resolver<ResolversTypes['Date'], ParentType, ContextType>;
1306
+ lastUsedAt?: Resolver<Types.Maybe<ResolversTypes['Date']>, ParentType, ContextType>;
1307
+ projectId?: Resolver<Types.Maybe<ResolversTypes['ID']>, ParentType, ContextType>;
1308
+ scope?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
1309
+ source?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
1310
+ updatedAt?: Resolver<ResolversTypes['Date'], ParentType, ContextType>;
1311
+ }>;
1312
+
1298
1313
  export interface DateScalarConfig extends GraphQLScalarTypeConfig<ResolversTypes['Date'], any> {
1299
1314
  name: 'Date';
1300
1315
  }
@@ -3469,6 +3484,7 @@ export type UserDataExportResolvers<
3469
3484
  ParentType,
3470
3485
  ContextType
3471
3486
  >;
3487
+ credentials?: Resolver<Array<ResolversTypes['CredentialExportData']>, ParentType, ContextType>;
3472
3488
  exportedAt?: Resolver<ResolversTypes['Date'], ParentType, ContextType>;
3473
3489
  organizationMemberships?: Resolver<
3474
3490
  Array<ResolversTypes['OrganizationMembershipExportData']>,
@@ -3736,6 +3752,7 @@ export type Resolvers<ContextType = any> = ResolversObject<{
3736
3752
  CreateApiKeyResult?: CreateApiKeyResultResolvers<ContextType>;
3737
3753
  CreateMySecondaryAccountResult?: CreateMySecondaryAccountResultResolvers<ContextType>;
3738
3754
  CreateProjectAppResult?: CreateProjectAppResultResolvers<ContextType>;
3755
+ CredentialExportData?: CredentialExportDataResolvers<ContextType>;
3739
3756
  Date?: GraphQLScalarType;
3740
3757
  ExchangeApiKeyResult?: ExchangeApiKeyResultResolvers<ContextType>;
3741
3758
  Group?: GroupResolvers<ContextType>;
@@ -448,8 +448,8 @@ export enum CdmIfMissing {
448
448
  export type CdmKeyResolverInput = {
449
449
  /**
450
450
  * `id` references an existing Grant user id. `email` resolves or creates a
451
- * global email authentication method, passwordless unless the user carries a
452
- * `password`. Other values are treated as CDM-local external keys for project
451
+ * global email authentication method, passwordless on the platform; a `password`
452
+ * becomes a credential of the importing project only. Other values are treated as CDM-local external keys for project
453
453
  * import/export.
454
454
  */
455
455
  findBy?: InputMaybe<CdmFindBy>;
@@ -489,8 +489,8 @@ export enum CdmPasswordAlgorithm {
489
489
  }
490
490
 
491
491
  /**
492
- * A password hash the importer already holds, to be stored as the user's email
493
- * password credential (ADR 0010). Never the plaintext password.
492
+ * A password hash the importer already holds, to be stored as the importing project's
493
+ * password credential (ADR 0011). Never the plaintext password.
494
494
  */
495
495
  export type CdmPasswordInput = {
496
496
  algorithm: CdmPasswordAlgorithm;
@@ -677,7 +677,7 @@ export type CreatePermissionInput = {
677
677
  export type CreateProjectAppInput = {
678
678
  /** Allow new users to sign up when authenticating via this app. Default true. */
679
679
  allowSignUp?: InputMaybe<Scalars['Boolean']['input']>;
680
- /** Auth providers enabled for this app (e.g. github, email). Empty/null = all configured providers. */
680
+ /** Auth providers enabled for this app: `email` (magic link), `github`, `google` and `password` (email and password, checked against the project's own credential). Empty/null = every provider except `password`, which is opt-in and is on only when named here. */
681
681
  enabledProviders?: InputMaybe<Array<Scalars['String']['input']>>;
682
682
  name?: InputMaybe<Scalars['String']['input']>;
683
683
  primaryTagId?: InputMaybe<Scalars['ID']['input']>;
@@ -699,7 +699,7 @@ export type CreateProjectAppResult = {
699
699
  /** Shown only once at creation. Null for public clients. */
700
700
  clientSecret?: Maybe<Scalars['String']['output']>;
701
701
  createdAt: Scalars['Date']['output'];
702
- /** Auth providers enabled for this app (e.g. github, email). Empty/null = all configured providers. */
702
+ /** Auth providers enabled for this app: `email` (magic link), `github`, `google` and `password` (email and password, checked against the project's own credential). Empty/null = every provider except `password`, which is opt-in and is on only when named here. */
703
703
  enabledProviders?: Maybe<Array<Scalars['String']['output']>>;
704
704
  id: Scalars['ID']['output'];
705
705
  name?: Maybe<Scalars['String']['output']>;
@@ -787,6 +787,22 @@ export type CreateWebhookSubscriptionMutationInput = {
787
787
  url: Scalars['String']['input'];
788
788
  };
789
789
 
790
+ /**
791
+ * A password credential the user holds, per scope. Existence, scope, source and dates only:
792
+ * the hash is never exported.
793
+ */
794
+ export type CredentialExportData = {
795
+ __typename?: 'CredentialExportData';
796
+ createdAt: Scalars['Date']['output'];
797
+ lastUsedAt?: Maybe<Scalars['Date']['output']>;
798
+ /** Set for a project credential */
799
+ projectId?: Maybe<Scalars['ID']['output']>;
800
+ /** platform (the Grant console) or project (that project's apps) */
801
+ scope: Scalars['String']['output'];
802
+ source: Scalars['String']['output'];
803
+ updatedAt: Scalars['Date']['output'];
804
+ };
805
+
790
806
  export type DeleteApiKeyInput = {
791
807
  hardDelete?: InputMaybe<Scalars['Boolean']['input']>;
792
808
  id: Scalars['ID']['input'];
@@ -2185,7 +2201,7 @@ export type ProjectApp = Auditable & {
2185
2201
  clientId: Scalars['String']['output'];
2186
2202
  createdAt: Scalars['Date']['output'];
2187
2203
  deletedAt?: Maybe<Scalars['Date']['output']>;
2188
- /** Auth providers enabled for this app (e.g. github, email). Empty/null = all configured providers. */
2204
+ /** Auth providers enabled for this app: `email` (magic link), `github`, `google` and `password` (email and password, checked against the project's own credential). Empty/null = every provider except `password`, which is opt-in and is on only when named here. */
2189
2205
  enabledProviders?: Maybe<Array<Scalars['String']['output']>>;
2190
2206
  id: Scalars['ID']['output'];
2191
2207
  name?: Maybe<Scalars['String']['output']>;
@@ -3617,20 +3633,24 @@ export type SyncProjectResult = {
3617
3633
  groupTagsLinked: Scalars['Int']['output'];
3618
3634
  groupsCreated: Scalars['Int']['output'];
3619
3635
  importId?: Maybe<Scalars['String']['output']>;
3620
- /** Users whose `password` hash was stored as their email credential for the first time. */
3636
+ /**
3637
+ * Users whose `password` hash was stored as the importing project's credential for the
3638
+ * first time.
3639
+ */
3621
3640
  passwordsImported: Scalars['Int']['output'];
3622
3641
  /**
3623
- * Users whose `password` hash was not stored because the credential or the identity is
3624
- * not the import's to write: a password set in Grant, an address the owner has verified,
3625
- * or an identity that already existed and was not created by an earlier import of this
3626
- * project.
3642
+ * Users whose project credential was not written because the person owns it: it was set or
3643
+ * reset in Grant, not imported.
3627
3644
  */
3628
3645
  passwordsSkippedExisting: Scalars['Int']['output'];
3629
- /** Users whose imported credential already equalled the hash in the document. */
3646
+ /**
3647
+ * Users whose project credential, stored by an earlier import of this project, already
3648
+ * equalled the hash in the document.
3649
+ */
3630
3650
  passwordsUnchanged: Scalars['Int']['output'];
3631
3651
  /**
3632
- * Users whose imported credential was replaced because the document carried a different
3633
- * hash for it.
3652
+ * Users whose project credential was replaced because an earlier import of this project had
3653
+ * stored a different hash.
3634
3654
  */
3635
3655
  passwordsUpdated: Scalars['Int']['output'];
3636
3656
  permissionsCreated: Scalars['Int']['output'];
@@ -3830,7 +3850,7 @@ export type UpdatePermissionTagInput = {
3830
3850
  export type UpdateProjectAppInput = {
3831
3851
  /** Allow new users to sign up when authenticating via this app. */
3832
3852
  allowSignUp?: InputMaybe<Scalars['Boolean']['input']>;
3833
- /** Auth providers enabled for this app (e.g. github, email). Empty/null = all configured providers. */
3853
+ /** Auth providers enabled for this app: `email` (magic link), `github`, `google` and `password` (email and password, checked against the project's own credential). Empty/null = every provider except `password`, which is opt-in and is on only when named here. */
3834
3854
  enabledProviders?: InputMaybe<Array<Scalars['String']['input']>>;
3835
3855
  /** Display name for the app. */
3836
3856
  name?: InputMaybe<Scalars['String']['input']>;
@@ -4137,19 +4157,22 @@ export type UserCdmInput = {
4137
4157
  metadata?: InputMaybe<Scalars['JSON']['input']>;
4138
4158
  name: Scalars['String']['input'];
4139
4159
  /**
4140
- * Existing password credential to import for this user. Only valid with
4141
- * `key.findBy: email`, because the credential lives on the email authentication
4142
- * method; any other resolver is a validation error.
4160
+ * Existing password credential to import for this user as a credential of the importing
4161
+ * project only. Only valid with `key.findBy: email`, because the person is resolved by
4162
+ * address; any other resolver is a validation error.
4163
+ *
4164
+ * The person must be a member of the project after the import: assigned in the same
4165
+ * document (`roles`, `groups` or `permissions`) or already a member. A password for anyone
4166
+ * else is a validation error raised when the import is applied, which rolls the import back
4167
+ * and names the position in `users`.
4143
4168
  *
4144
- * Ownership is forward-only. An imported credential is marked as imported and stays
4145
- * owned by the importing system while the email is unverified: a later import of this
4146
- * project with a different hash replaces it, an identical one changes nothing. Once the
4147
- * owner verifies the address, or the password is set, changed or reset in Grant, Grant owns
4148
- * it and imports never touch it again. A credential is written only for an identity the
4149
- * import creates, or one an earlier import of this project created whose email is still
4150
- * unverified; any other existing identity, including one found through GitHub or Google,
4151
- * is left untouched. An absent `password` never removes a
4152
- * stored credential. See `SyncProjectResult` for the counters.
4169
+ * Ownership is forward-only. An imported credential is marked as imported and stays owned
4170
+ * by the importing system until the person resets or changes it: a later import of this
4171
+ * project with a different hash replaces it, an identical one changes nothing. Once it is
4172
+ * set, changed or reset in Grant, Grant owns it and imports never touch it again. Verified
4173
+ * and GitHub or Google linked identities are not excluded: the credential opens this
4174
+ * project's apps only. An absent `password` never removes a stored credential. See
4175
+ * `SyncProjectResult` for the counters.
4153
4176
  */
4154
4177
  password?: InputMaybe<CdmPasswordInput>;
4155
4178
  /** Direct user → permission grants (document keys or catalog `resourceSlug:action` refs). */
@@ -4166,6 +4189,7 @@ export type UserDataExport = {
4166
4189
  __typename?: 'UserDataExport';
4167
4190
  accounts: Array<AccountExportData>;
4168
4191
  authenticationMethods: Array<AuthenticationMethodExportData>;
4192
+ credentials: Array<CredentialExportData>;
4169
4193
  exportedAt: Scalars['Date']['output'];
4170
4194
  organizationMemberships: Array<OrganizationMembershipExportData>;
4171
4195
  projectMemberships: Array<ProjectMembershipExportData>;
package/src/index.ts CHANGED
@@ -13,5 +13,6 @@ export {
13
13
  type ProjectConsentInfoUser,
14
14
  type ProjectConsentRedirectResult,
15
15
  type ProjectOAuthThemeMode,
16
+ type ProjectPasswordSignInResult,
16
17
  } from './project-oauth.types';
17
18
  export * from './webhooks/webhook.types';
@@ -21,6 +21,14 @@ query MyUserDataExport {
21
21
  lastUsedAt
22
22
  createdAt
23
23
  }
24
+ credentials {
25
+ scope
26
+ projectId
27
+ source
28
+ lastUsedAt
29
+ createdAt
30
+ updatedAt
31
+ }
24
32
  sessions {
25
33
  userAgent
26
34
  ipAddress
@@ -62,3 +62,10 @@ export interface ProjectConsentInfo {
62
62
  export interface ProjectConsentRedirectResult {
63
63
  redirectUrl: string;
64
64
  }
65
+
66
+ /**
67
+ * Response from POST /api/auth/project/password: the consent page to continue to, or a
68
+ * pending MFA step when the platform requires MFA at sign-in (ADR 0011 slice 4).
69
+ */
70
+ export type ProjectPasswordSignInResult =
71
+ { status: 'authenticated'; redirectUrl: string } | { status: 'mfa_required'; mfaToken: string };
@@ -10,7 +10,7 @@ input CreateProjectAppInput {
10
10
  """
11
11
  scopes: [String!]
12
12
  """
13
- Auth providers enabled for this app (e.g. github, email). Empty/null = all configured providers.
13
+ Auth providers enabled for this app: `email` (magic link), `github`, `google` and `password` (email and password, checked against the project's own credential). Empty/null = every provider except `password`, which is opt-in and is on only when named here.
14
14
  """
15
15
  enabledProviders: [String!]
16
16
  """
@@ -25,7 +25,7 @@ input UpdateProjectAppInput {
25
25
  """
26
26
  scopes: [String!]
27
27
  """
28
- Auth providers enabled for this app (e.g. github, email). Empty/null = all configured providers.
28
+ Auth providers enabled for this app: `email` (magic link), `github`, `google` and `password` (email and password, checked against the project's own credential). Empty/null = every provider except `password`, which is opt-in and is on only when named here.
29
29
  """
30
30
  enabledProviders: [String!]
31
31
  """
@@ -8,7 +8,7 @@ type CreateProjectAppResult {
8
8
  name: String
9
9
  redirectUris: [String!]!
10
10
  """
11
- Auth providers enabled for this app (e.g. github, email). Empty/null = all configured providers.
11
+ Auth providers enabled for this app: `email` (magic link), `github`, `google` and `password` (email and password, checked against the project's own credential). Empty/null = every provider except `password`, which is opt-in and is on only when named here.
12
12
  """
13
13
  enabledProviders: [String!]
14
14
  """
@@ -19,7 +19,7 @@ type ProjectApp implements Auditable {
19
19
  redirectUris: [String!]!
20
20
  scopes: [String!]
21
21
  """
22
- Auth providers enabled for this app (e.g. github, email). Empty/null = all configured providers.
22
+ Auth providers enabled for this app: `email` (magic link), `github`, `google` and `password` (email and password, checked against the project's own credential). Empty/null = every provider except `password`, which is opt-in and is on only when named here.
23
23
  """
24
24
  enabledProviders: [String!]
25
25
  """
@@ -47,8 +47,8 @@ input CdmKeyResolverInput {
47
47
  value: String!
48
48
  """
49
49
  `id` references an existing Grant user id. `email` resolves or creates a
50
- global email authentication method, passwordless unless the user carries a
51
- `password`. Other values are treated as CDM-local external keys for project
50
+ global email authentication method, passwordless on the platform; a `password`
51
+ becomes a credential of the importing project only. Other values are treated as CDM-local external keys for project
52
52
  import/export.
53
53
  """
54
54
  findBy: CdmFindBy
@@ -138,8 +138,8 @@ input UserApiKeyCdmInput {
138
138
  }
139
139
 
140
140
  """
141
- A password hash the importer already holds, to be stored as the user's email
142
- password credential (ADR 0010). Never the plaintext password.
141
+ A password hash the importer already holds, to be stored as the importing project's
142
+ password credential (ADR 0011). Never the plaintext password.
143
143
  """
144
144
  input CdmPasswordInput {
145
145
  algorithm: CdmPasswordAlgorithm!
@@ -173,19 +173,22 @@ input UserCdmInput {
173
173
  primaryTag: String
174
174
  apiKeys: [UserApiKeyCdmInput!]
175
175
  """
176
- Existing password credential to import for this user. Only valid with
177
- `key.findBy: email`, because the credential lives on the email authentication
178
- method; any other resolver is a validation error.
179
-
180
- Ownership is forward-only. An imported credential is marked as imported and stays
181
- owned by the importing system while the email is unverified: a later import of this
182
- project with a different hash replaces it, an identical one changes nothing. Once the
183
- owner verifies the address, or the password is set, changed or reset in Grant, Grant owns
184
- it and imports never touch it again. A credential is written only for an identity the
185
- import creates, or one an earlier import of this project created whose email is still
186
- unverified; any other existing identity, including one found through GitHub or Google,
187
- is left untouched. An absent `password` never removes a
188
- stored credential. See `SyncProjectResult` for the counters.
176
+ Existing password credential to import for this user as a credential of the importing
177
+ project only. Only valid with `key.findBy: email`, because the person is resolved by
178
+ address; any other resolver is a validation error.
179
+
180
+ The person must be a member of the project after the import: assigned in the same
181
+ document (`roles`, `groups` or `permissions`) or already a member. A password for anyone
182
+ else is a validation error raised when the import is applied, which rolls the import back
183
+ and names the position in `users`.
184
+
185
+ Ownership is forward-only. An imported credential is marked as imported and stays owned
186
+ by the importing system until the person resets or changes it: a later import of this
187
+ project with a different hash replaces it, an identical one changes nothing. Once it is
188
+ set, changed or reset in Grant, Grant owns it and imports never touch it again. Verified
189
+ and GitHub or Google linked identities are not excluded: the credential opens this
190
+ project's apps only. An absent `password` never removes a stored credential. See
191
+ `SyncProjectResult` for the counters.
189
192
  """
190
193
  password: CdmPasswordInput
191
194
  """
@@ -17,23 +17,23 @@ type SyncProjectResult {
17
17
  userRolesAssigned: Int!
18
18
  projectUserApiKeysCreated: Int!
19
19
  """
20
- Users whose `password` hash was stored as their email credential for the first time.
20
+ Users whose `password` hash was stored as the importing project's credential for the
21
+ first time.
21
22
  """
22
23
  passwordsImported: Int!
23
24
  """
24
- Users whose imported credential was replaced because the document carried a different
25
- hash for it.
25
+ Users whose project credential was replaced because an earlier import of this project had
26
+ stored a different hash.
26
27
  """
27
28
  passwordsUpdated: Int!
28
29
  """
29
- Users whose imported credential already equalled the hash in the document.
30
+ Users whose project credential, stored by an earlier import of this project, already
31
+ equalled the hash in the document.
30
32
  """
31
33
  passwordsUnchanged: Int!
32
34
  """
33
- Users whose `password` hash was not stored because the credential or the identity is
34
- not the import's to write: a password set in Grant, an address the owner has verified,
35
- or an identity that already existed and was not created by an earlier import of this
36
- project.
35
+ Users whose project credential was not written because the person owns it: it was set or
36
+ reset in Grant, not imported.
37
37
  """
38
38
  passwordsSkippedExisting: Int!
39
39
  resourcesCreated: Int!
@@ -2,6 +2,7 @@ type UserDataExport {
2
2
  user: UserExportData!
3
3
  accounts: [AccountExportData!]!
4
4
  authenticationMethods: [AuthenticationMethodExportData!]!
5
+ credentials: [CredentialExportData!]!
5
6
  sessions: [SessionExportData!]!
6
7
  organizationMemberships: [OrganizationMembershipExportData!]!
7
8
  projectMemberships: [ProjectMembershipExportData!]!
@@ -32,6 +33,21 @@ type AuthenticationMethodExportData {
32
33
  createdAt: Date!
33
34
  }
34
35
 
36
+ """
37
+ A password credential the user holds, per scope. Existence, scope, source and dates only:
38
+ the hash is never exported.
39
+ """
40
+ type CredentialExportData {
41
+ "platform (the Grant console) or project (that project's apps)"
42
+ scope: String!
43
+ "Set for a project credential"
44
+ projectId: ID
45
+ source: String!
46
+ lastUsedAt: Date
47
+ createdAt: Date!
48
+ updatedAt: Date!
49
+ }
50
+
35
51
  type SessionExportData {
36
52
  userAgent: String
37
53
  ipAddress: String