@astrale-domains/shell 0.1.6 → 0.1.8
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/README.md +73 -173
- package/dist/domain.d.ts +499 -31
- package/dist/domain.js +99 -78
- package/dist/functions/application/index.js +2 -1
- package/dist/functions/application/legacy/index.js +1 -0
- package/dist/functions/application/legacy/list-entrypoints.js +8 -0
- package/dist/functions/authority/capabilities.js +1 -0
- package/dist/functions/authority/composition.js +47 -0
- package/dist/functions/authority/index.js +2 -0
- package/dist/functions/authority/roles.js +113 -0
- package/dist/functions/directory/disconnect-directory.js +35 -0
- package/dist/functions/directory/index.js +1 -0
- package/dist/functions/group/assign-member.js +7 -0
- package/dist/functions/group/change-group-membership.js +101 -20
- package/dist/functions/group/create-group.js +23 -0
- package/dist/functions/group/delete-group.js +33 -0
- package/dist/functions/group/deprovision-group.js +27 -0
- package/dist/functions/group/group-errors.js +43 -0
- package/dist/functions/group/group-membership-errors.js +3 -1
- package/dist/functions/group/group.js +51 -0
- package/dist/functions/group/index.js +8 -2
- package/dist/functions/group/legacy/index.js +1 -0
- package/dist/functions/group/legacy/membership.js +64 -0
- package/dist/functions/group/membership-laws.js +37 -0
- package/dist/functions/group/provision-group.js +33 -0
- package/dist/functions/group/unassign-member.js +7 -0
- package/dist/functions/group/update-group.js +55 -0
- package/dist/functions/invitation/index.js +2 -0
- package/dist/functions/invitation/invitation-errors.js +13 -0
- package/dist/functions/invitation/invitation-methods.js +41 -0
- package/dist/functions/invitation/send-invitation.js +131 -0
- package/dist/functions/user/account.js +35 -0
- package/dist/functions/user/create-user.js +45 -0
- package/dist/functions/user/deactivate-user.js +19 -0
- package/dist/functions/user/delete-user.js +34 -0
- package/dist/functions/user/deprovision-user.js +21 -0
- package/dist/functions/user/index.js +15 -1
- package/dist/functions/user/invite-errors.js +18 -0
- package/dist/functions/user/invite-user.js +42 -34
- package/dist/functions/user/legacy/claimed-account.js +11 -0
- package/dist/functions/user/legacy/index.js +4 -0
- package/dist/functions/user/legacy/invite.js +20 -0
- package/dist/functions/user/legacy/migrate-accounts.js +98 -0
- package/dist/functions/user/legacy/record-profile.js +19 -0
- package/dist/functions/user/protect-administrators.js +16 -0
- package/dist/functions/user/provision-user.js +70 -0
- package/dist/functions/user/reactivate-user.js +19 -0
- package/dist/functions/user/read-account.js +12 -0
- package/dist/functions/user/request-invitation.js +17 -0
- package/dist/functions/user/update-profile.js +22 -0
- package/dist/functions/user/update-user.js +68 -0
- package/dist/functions/user/user-errors.js +69 -0
- package/dist/functions/user/write-account.js +57 -0
- package/dist/functions/view-access/index.js +2 -1
- package/dist/functions/view-access/{assign-views.js → legacy/assign-views.js} +11 -7
- package/dist/functions/view-access/legacy/index.js +1 -0
- package/dist/functions/view-access/{view-access-errors.js → legacy/view-access-errors.js} +5 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +3 -1
- package/dist/integrations/user-invitation/invite-user-request.d.ts +6 -0
- package/dist/legacy/index.js +1 -0
- package/dist/legacy/release.js +33 -0
- package/dist/mutations/composition/grant-role.js +13 -0
- package/dist/mutations/composition/index.js +1 -0
- package/dist/mutations/group/index.js +1 -1
- package/dist/mutations/group/legacy/index.js +1 -0
- package/dist/mutations/group/legacy/membership-class.js +7 -0
- package/dist/mutations/group/set-group-membership.js +60 -19
- package/dist/mutations/identity/create-profile.js +37 -0
- package/dist/mutations/identity/delete-identity.js +28 -0
- package/dist/mutations/identity/index.js +4 -0
- package/dist/mutations/identity/write-account.js +109 -0
- package/dist/mutations/identity/write-group.js +72 -0
- package/dist/mutations/invitation/index.js +1 -0
- package/dist/mutations/invitation/write-invitation.js +79 -0
- package/dist/mutations/user/legacy/index.js +1 -0
- package/dist/mutations/user/{record-user-profile.js → legacy/record-user-profile.js} +5 -2
- package/dist/providers/admin-enrollment/create-admin-enrollment-provider.js +6 -3
- package/dist/providers/admin-enrollment/legacy/index.js +1 -0
- package/dist/providers/admin-enrollment/legacy/send-without-profile.js +16 -0
- package/dist/queries/application/legacy/index.js +1 -0
- package/dist/queries/application/legacy/read-installed-entrypoints.js +47 -0
- package/dist/queries/composition/index.js +2 -0
- package/dist/queries/composition/read-extensions.js +34 -0
- package/dist/queries/composition/read-role-nodes.js +76 -0
- package/dist/queries/group/index.js +2 -1
- package/dist/queries/group/legacy/index.js +3 -0
- package/dist/queries/group/legacy/read-core-groups.js +46 -0
- package/dist/queries/group/legacy/read-member-of-group.js +24 -0
- package/dist/queries/group/legacy/read-members-of-group.js +27 -0
- package/dist/queries/group/read-group-members.js +73 -0
- package/dist/queries/group/read-group-membership-endpoints.js +11 -0
- package/dist/queries/group/read-group-membership.js +6 -21
- package/dist/queries/identity/index.js +1 -0
- package/dist/queries/identity/read-identities.js +29 -0
- package/dist/queries/invitation/index.js +1 -0
- package/dist/queries/invitation/read-invitation.js +50 -0
- package/dist/queries/profile/index.js +1 -0
- package/dist/queries/profile/read-profile.js +15 -0
- package/dist/queries/role/index.js +1 -0
- package/dist/queries/role/read-role-node.js +21 -0
- package/dist/queries/user/legacy/index.js +1 -0
- package/dist/queries/user/legacy/read-user-profile.js +27 -0
- package/dist/runtime.d.ts +20147 -466
- package/dist/runtime.js +34 -7
- package/dist/schema/index.d.ts +7 -4
- package/dist/schema/index.js +8 -4
- package/dist/schema/legacy/index.js +1 -0
- package/dist/schema/legacy/schema.js +32 -0
- package/dist/schema/modules/application/legacy/entrypoint-entry.js +11 -0
- package/dist/schema/modules/application/legacy/functions/index.js +1 -0
- package/dist/schema/modules/application/legacy/functions/list-entrypoints.js +22 -0
- package/dist/schema/modules/application/legacy/index.js +2 -0
- package/dist/schema/modules/directory/functions/disconnect-directory.d.ts +13 -0
- package/dist/schema/modules/directory/functions/disconnect-directory.js +15 -0
- package/dist/schema/modules/directory/functions/index.d.ts +1 -0
- package/dist/schema/modules/directory/functions/index.js +1 -0
- package/dist/schema/modules/directory/index.d.ts +1 -0
- package/dist/schema/modules/directory/index.js +1 -0
- package/dist/schema/modules/group/classes/group-profile-link.d.ts +34 -0
- package/dist/schema/modules/group/classes/group-profile-link.js +21 -0
- package/dist/schema/modules/group/classes/group-profile.d.ts +15 -0
- package/dist/schema/modules/group/classes/group-profile.js +20 -0
- package/dist/schema/modules/group/classes/group.d.ts +87 -1
- package/dist/schema/modules/group/classes/group.js +38 -7
- package/dist/schema/modules/group/classes/index.d.ts +2 -1
- package/dist/schema/modules/group/classes/index.js +3 -1
- package/dist/schema/modules/group/functions/assign-member.js +17 -0
- package/dist/schema/modules/group/functions/assign-members.js +17 -0
- package/dist/schema/modules/group/functions/create-group.js +20 -0
- package/dist/schema/modules/group/functions/delete-group.js +12 -0
- package/dist/schema/modules/group/functions/deprovision-group.js +14 -0
- package/dist/schema/modules/group/functions/index.js +9 -2
- package/dist/schema/modules/group/functions/provision-group.js +14 -0
- package/dist/schema/modules/group/functions/unassign-member.js +17 -0
- package/dist/schema/modules/group/functions/unassign-members.js +17 -0
- package/dist/schema/modules/group/functions/update-group.js +20 -0
- package/dist/schema/modules/group/index.d.ts +3 -3
- package/dist/schema/modules/group/index.js +4 -3
- package/dist/schema/modules/group/legacy/admission.d.ts +9 -0
- package/dist/schema/modules/group/legacy/admission.js +9 -0
- package/dist/schema/modules/group/legacy/classes/index.d.ts +1 -0
- package/dist/schema/modules/group/legacy/classes/index.js +1 -0
- package/dist/schema/modules/group/{classes → legacy/classes}/member-of-group.d.ts +10 -1
- package/dist/schema/modules/group/legacy/classes/member-of-group.js +32 -0
- package/dist/schema/modules/group/legacy/core-groups.d.ts +22 -0
- package/dist/schema/modules/group/legacy/core-groups.js +28 -0
- package/dist/schema/modules/group/legacy/index.d.ts +5 -0
- package/dist/schema/modules/group/legacy/index.js +6 -0
- package/dist/schema/modules/group/legacy/policies/belongs-to-group.d.ts +5 -0
- package/dist/schema/modules/group/legacy/policies/belongs-to-group.js +9 -0
- package/dist/schema/modules/group/legacy/policies/index.d.ts +2 -0
- package/dist/schema/modules/group/legacy/policies/index.js +2 -0
- package/dist/schema/modules/group/legacy/policies/owns-membership-group.d.ts +6 -0
- package/dist/schema/modules/group/legacy/policies/owns-membership-group.js +12 -0
- package/dist/schema/modules/group/legacy/relaxations.d.ts +21 -0
- package/dist/schema/modules/group/legacy/relaxations.js +27 -0
- package/dist/schema/modules/group/policies/describes-group-of-subject.d.ts +2 -0
- package/dist/schema/modules/group/policies/describes-group-of-subject.js +7 -0
- package/dist/schema/modules/group/policies/index.d.ts +2 -1
- package/dist/schema/modules/group/policies/index.js +2 -1
- package/dist/schema/modules/group/policies/is-group-profile-source.d.ts +2 -0
- package/dist/schema/modules/group/policies/is-group-profile-source.js +6 -0
- package/dist/schema/modules/group/types/group-membership-input.js +8 -0
- package/dist/schema/modules/group/types/group.js +10 -0
- package/dist/schema/modules/group/types/index.js +2 -1
- package/dist/schema/modules/invitation/classes/index.js +2 -0
- package/dist/schema/modules/invitation/classes/invitation-user.js +17 -0
- package/dist/schema/modules/invitation/classes/invitation.js +32 -0
- package/dist/schema/modules/invitation/functions/accept.js +16 -0
- package/dist/schema/modules/invitation/functions/cancel.js +12 -0
- package/dist/schema/modules/invitation/functions/index.js +4 -0
- package/dist/schema/modules/invitation/functions/resend.js +12 -0
- package/dist/schema/modules/invitation/functions/send.js +20 -0
- package/dist/schema/modules/invitation/index.js +2 -0
- package/dist/schema/modules/invitation/types/index.js +1 -0
- package/dist/schema/modules/invitation/types/invitation-status.js +5 -0
- package/dist/schema/modules/namespace/classes/alias.d.ts +2 -1
- package/dist/schema/modules/namespace/classes/alias.js +11 -2
- package/dist/schema/modules/namespace/functions/link.js +3 -2
- package/dist/schema/modules/namespace/functions/rename.js +3 -2
- package/dist/schema/modules/namespace/functions/unlink.js +3 -2
- package/dist/schema/modules/role/classes/index.d.ts +1 -0
- package/dist/schema/modules/role/classes/index.js +1 -0
- package/dist/schema/modules/role/classes/role.d.ts +16 -0
- package/dist/schema/modules/role/classes/role.js +19 -0
- package/dist/schema/modules/role/core/index.d.ts +1 -0
- package/dist/schema/modules/role/core/index.js +1 -0
- package/dist/schema/modules/role/core/roles.d.ts +39 -0
- package/dist/schema/modules/role/core/roles.js +31 -0
- package/dist/schema/modules/role/index.d.ts +2 -0
- package/dist/schema/modules/role/index.js +2 -0
- package/dist/schema/modules/user/classes/index.d.ts +2 -0
- package/dist/schema/modules/user/classes/index.js +3 -0
- package/dist/schema/modules/user/classes/user-profile-link.d.ts +35 -0
- package/dist/schema/modules/user/classes/user-profile-link.js +22 -0
- package/dist/schema/modules/user/classes/user-profile.d.ts +41 -0
- package/dist/schema/modules/user/classes/user-profile.js +30 -0
- package/dist/schema/modules/user/classes/user.d.ts +133 -8
- package/dist/schema/modules/user/classes/user.js +46 -15
- package/dist/schema/modules/user/functions/create-user.js +21 -0
- package/dist/schema/modules/user/functions/deactivate-user.js +12 -0
- package/dist/schema/modules/user/functions/delete-user.js +12 -0
- package/dist/schema/modules/user/functions/deprovision-user.js +14 -0
- package/dist/schema/modules/user/functions/index.js +8 -1
- package/dist/schema/modules/user/functions/invite-user.js +23 -4
- package/dist/schema/modules/user/functions/provision-user.js +25 -0
- package/dist/schema/modules/user/functions/reactivate-user.js +12 -0
- package/dist/schema/modules/user/functions/update-profile.js +16 -0
- package/dist/schema/modules/user/functions/update-user.js +22 -0
- package/dist/schema/modules/user/index.d.ts +2 -1
- package/dist/schema/modules/user/index.js +2 -1
- package/dist/schema/modules/user/legacy/account-properties.js +24 -0
- package/dist/schema/modules/user/legacy/functions/index.js +2 -0
- package/dist/schema/modules/user/legacy/functions/migrate-accounts.js +23 -0
- package/dist/schema/modules/user/legacy/functions/record-profile.js +18 -0
- package/dist/schema/modules/user/legacy/index.js +5 -0
- package/dist/schema/modules/user/legacy/invite.js +15 -0
- package/dist/schema/modules/user/legacy/relaxations.js +37 -0
- package/dist/schema/modules/user/policies/describes-subject.d.ts +2 -0
- package/dist/schema/modules/user/policies/describes-subject.js +7 -0
- package/dist/schema/modules/user/policies/index.d.ts +2 -0
- package/dist/schema/modules/user/policies/index.js +2 -0
- package/dist/schema/modules/user/policies/is-profile-source.d.ts +2 -0
- package/dist/schema/modules/user/policies/is-profile-source.js +6 -0
- package/dist/schema/modules/user/types/account.d.ts +12 -0
- package/dist/schema/modules/user/types/account.js +12 -0
- package/dist/schema/modules/user/types/index.d.ts +1 -1
- package/dist/schema/modules/user/types/index.js +1 -1
- package/dist/schema/modules/view-access/classes/view-access.d.ts +7 -11
- package/dist/schema/modules/view-access/classes/view-access.js +17 -12
- package/dist/schema/modules/view-access/index.d.ts +1 -2
- package/dist/schema/modules/view-access/index.js +1 -2
- package/dist/schema/modules/view-access/legacy/functions/assign-views.js +22 -0
- package/dist/schema/modules/view-access/legacy/functions/index.js +1 -0
- package/dist/schema/modules/view-access/legacy/index.js +2 -0
- package/dist/schema/modules/view-access/legacy/view-assignment.js +24 -0
- package/dist/schema/modules/view-access/policies/holds-view-access.d.ts +2 -0
- package/dist/schema/modules/view-access/policies/holds-view-access.js +5 -0
- package/dist/schema/modules/view-access/policies/index.d.ts +1 -0
- package/dist/schema/modules/view-access/policies/index.js +1 -0
- package/dist/schema/policies/has-role.d.ts +6 -0
- package/dist/schema/policies/has-role.js +9 -0
- package/dist/schema/policies/index.d.ts +1 -0
- package/dist/schema/policies/index.js +1 -4
- package/dist/schema/policies/legacy/administers-shell-class.js +21 -0
- package/dist/schema/policies/legacy/index.js +4 -0
- package/dist/schema/policies/legacy/is-self.js +9 -0
- package/dist/schema/policies/legacy/owns-class.js +12 -0
- package/dist/schema/policies/legacy/uses-function.js +9 -0
- package/dist/schema/schema.d.ts +1937 -119
- package/dist/schema/schema.js +35 -15
- package/dist/schema/types/attributes.js +15 -0
- package/dist/schema/types/index.js +1 -0
- package/dist/utils/attributes/attributes.js +12 -0
- package/dist/utils/attributes/index.js +1 -0
- package/dist/utils/index.js +3 -0
- package/dist/utils/same-json/index.js +1 -0
- package/dist/utils/same-json/same-json.js +9 -0
- package/package.json +3 -3
- package/dist/functions/application/list-entrypoints.js +0 -6
- package/dist/functions/group/assign-member-to-group.js +0 -4
- package/dist/functions/group/unassign-member-from-group.js +0 -4
- package/dist/functions/user/record-profile.js +0 -18
- package/dist/mutations/user/index.js +0 -1
- package/dist/queries/application/index.js +0 -1
- package/dist/queries/application/read-installed-entrypoints.js +0 -28
- package/dist/queries/user/index.js +0 -1
- package/dist/queries/user/read-user-profile.js +0 -9
- package/dist/schema/modules/application/functions/index.d.ts +0 -1
- package/dist/schema/modules/application/functions/index.js +0 -1
- package/dist/schema/modules/application/functions/list-entrypoints.d.ts +0 -15
- package/dist/schema/modules/application/functions/list-entrypoints.js +0 -16
- package/dist/schema/modules/application/index.d.ts +0 -2
- package/dist/schema/modules/application/index.js +0 -2
- package/dist/schema/modules/application/types/entrypoint-entry.d.ts +0 -8
- package/dist/schema/modules/application/types/entrypoint-entry.js +0 -7
- package/dist/schema/modules/application/types/index.d.ts +0 -1
- package/dist/schema/modules/application/types/index.js +0 -1
- package/dist/schema/modules/group/classes/member-of-group.js +0 -12
- package/dist/schema/modules/group/core/administrators-group.d.ts +0 -1
- package/dist/schema/modules/group/core/administrators-group.js +0 -6
- package/dist/schema/modules/group/core/applications-group.d.ts +0 -1
- package/dist/schema/modules/group/core/applications-group.js +0 -6
- package/dist/schema/modules/group/core/index.d.ts +0 -3
- package/dist/schema/modules/group/core/index.js +0 -3
- package/dist/schema/modules/group/core/members-group.d.ts +0 -1
- package/dist/schema/modules/group/core/members-group.js +0 -6
- package/dist/schema/modules/group/functions/assign-member-to-group.js +0 -11
- package/dist/schema/modules/group/functions/unassign-member-from-group.js +0 -11
- package/dist/schema/modules/group/policies/belongs-to-group.d.ts +0 -1
- package/dist/schema/modules/group/policies/belongs-to-group.js +0 -5
- package/dist/schema/modules/user/functions/record-profile.js +0 -14
- package/dist/schema/modules/user/types/user-profile.d.ts +0 -7
- package/dist/schema/modules/user/types/user-profile.js +0 -7
- package/dist/schema/modules/view-access/core/index.d.ts +0 -1
- package/dist/schema/modules/view-access/core/index.js +0 -1
- package/dist/schema/modules/view-access/core/view-only-group.d.ts +0 -5
- package/dist/schema/modules/view-access/core/view-only-group.js +0 -10
- package/dist/schema/modules/view-access/functions/assign-views.d.ts +0 -16
- package/dist/schema/modules/view-access/functions/assign-views.js +0 -17
- package/dist/schema/modules/view-access/functions/index.d.ts +0 -1
- package/dist/schema/modules/view-access/functions/index.js +0 -1
- package/dist/schema/modules/view-access/types/index.js +0 -1
- package/dist/schema/modules/view-access/types/view-assignment.js +0 -16
- package/dist/schema/policies/administers-shell-class.js +0 -11
- package/dist/schema/policies/is-self.js +0 -5
- package/dist/schema/policies/owns-class.js +0 -7
- package/dist/schema/policies/uses-function.js +0 -5
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { defineAction } from '@astrale-os/sdk/action';
|
|
2
|
+
import { readMembers } from '../authority/index.js';
|
|
3
|
+
import { deleteIdentity } from '../../mutations/identity/index.js';
|
|
4
|
+
import { readProfile } from '../../queries/profile/index.js';
|
|
5
|
+
import { protectAdministrators } from './protect-administrators.js';
|
|
6
|
+
import { readAccount } from './read-account.js';
|
|
7
|
+
import { userErrors } from './user-errors.js';
|
|
8
|
+
/**
|
|
9
|
+
* Delete a deactivated account (U6). Its memberships and Views leave with it; each Domain's own
|
|
10
|
+
* account Edge decides its business record's fate, `detach` keeping it without an account. Deleting
|
|
11
|
+
* the account frees its login (U8).
|
|
12
|
+
*/
|
|
13
|
+
export const remove = defineAction()('User.delete', { errors: userErrors }, async (context) => {
|
|
14
|
+
const { error, self } = context;
|
|
15
|
+
const account = await readAccount(context, self);
|
|
16
|
+
if (account === undefined)
|
|
17
|
+
throw error('USER_NOT_FOUND', {});
|
|
18
|
+
const refusal = await protectAdministrators(context, self, { keepsLastAdministrator: true });
|
|
19
|
+
if (refusal !== undefined)
|
|
20
|
+
throw error(refusal, {});
|
|
21
|
+
if (!account.frozen)
|
|
22
|
+
throw error('USER_ACTIVE', {});
|
|
23
|
+
const members = await readMembers(context, self);
|
|
24
|
+
const profile = await context.query(readProfile, { kind: 'User', node: self });
|
|
25
|
+
const result = await context.mutate(deleteIdentity, {
|
|
26
|
+
kind: 'User',
|
|
27
|
+
node: self,
|
|
28
|
+
members,
|
|
29
|
+
...(profile === undefined ? {} : { profile }),
|
|
30
|
+
});
|
|
31
|
+
if (!result.ok)
|
|
32
|
+
throw error('ACCOUNT_CHANGED', {});
|
|
33
|
+
return {};
|
|
34
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { defineAction } from '@astrale-os/sdk/action';
|
|
2
|
+
import { findAccount } from './read-account.js';
|
|
3
|
+
import { userErrors } from './user-errors.js';
|
|
4
|
+
import { writeAccount } from './write-account.js';
|
|
5
|
+
/**
|
|
6
|
+
* The directory deleted the account (`dsync.user.deleted`): it is frozen and kept with its
|
|
7
|
+
* history. An account the directory no longer has is left as it is.
|
|
8
|
+
*/
|
|
9
|
+
export const deprovision = defineAction()('User.deprovision', { errors: userErrors }, async (context) => {
|
|
10
|
+
const { input, error } = context;
|
|
11
|
+
const account = await findAccount(context, {
|
|
12
|
+
directory: input.directory,
|
|
13
|
+
externalId: input.externalId,
|
|
14
|
+
});
|
|
15
|
+
if (account === undefined)
|
|
16
|
+
return {};
|
|
17
|
+
const written = await writeAccount(context, account, { frozen: true });
|
|
18
|
+
if (written === 'DUPLICATE_KEY' || written === 'CHANGED')
|
|
19
|
+
throw error('ACCOUNT_CHANGED', {});
|
|
20
|
+
return {};
|
|
21
|
+
});
|
|
@@ -1,2 +1,16 @@
|
|
|
1
|
+
export { create } from './create-user.js';
|
|
1
2
|
export { invite } from './invite-user.js';
|
|
2
|
-
export {
|
|
3
|
+
export { update } from './update-user.js';
|
|
4
|
+
export { updateProfile } from './update-profile.js';
|
|
5
|
+
export { deactivate } from './deactivate-user.js';
|
|
6
|
+
export { reactivate } from './reactivate-user.js';
|
|
7
|
+
export { remove } from './delete-user.js';
|
|
8
|
+
export { provision } from './provision-user.js';
|
|
9
|
+
export { deprovision } from './deprovision-user.js';
|
|
10
|
+
export { toAccount } from './account.js';
|
|
11
|
+
export { userErrors } from './user-errors.js';
|
|
12
|
+
export { inviteErrors } from './invite-errors.js';
|
|
13
|
+
export { readAccount } from './read-account.js';
|
|
14
|
+
export { writeAccount } from './write-account.js';
|
|
15
|
+
// Seams L-SHELL-5, L-SHELL-14: the legacy handlers the Runtime registers.
|
|
16
|
+
export { legacyMigrateAccounts, legacyRecordProfile } from './legacy/index.js';
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export const inviteErrors = {
|
|
3
|
+
INVALID_INVITATION_REFERENCE: {
|
|
4
|
+
family: 'VALIDATION_ERROR',
|
|
5
|
+
message: 'User must be an exact canonical @NodeId path.',
|
|
6
|
+
details: z.strictObject({}),
|
|
7
|
+
},
|
|
8
|
+
INVITATION_CONFLICT: {
|
|
9
|
+
family: 'CONFLICT',
|
|
10
|
+
message: 'The invitation conflicts with an existing request.',
|
|
11
|
+
details: z.strictObject({ requestId: z.string() }),
|
|
12
|
+
},
|
|
13
|
+
INVITATION_REJECTED: {
|
|
14
|
+
family: 'VALIDATION_ERROR',
|
|
15
|
+
message: 'Admin rejected the invitation request.',
|
|
16
|
+
details: z.strictObject({ requestId: z.string() }),
|
|
17
|
+
},
|
|
18
|
+
};
|
|
@@ -1,39 +1,47 @@
|
|
|
1
1
|
import { defineWorkflow } from '@astrale-os/sdk/workflow';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { authority, readExtensionTargets } from '../authority/index.js';
|
|
3
|
+
import { grantRole } from '../../mutations/composition/index.js';
|
|
4
4
|
import { canonicalNodeId } from '../../utils/canonical-node-path/index.js';
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
},
|
|
22
|
-
},
|
|
23
|
-
}, async (context) => {
|
|
5
|
+
import { inviteErrors } from './invite-errors.js';
|
|
6
|
+
// Seam L-SHELL-6: the `{ user, email }` form of callers older than profiles.
|
|
7
|
+
import { legacyInvite, legacyIsInvitation } from './legacy/index.js';
|
|
8
|
+
import { readAccount } from './read-account.js';
|
|
9
|
+
import { requestInvitation } from './request-invitation.js';
|
|
10
|
+
import { userErrors } from './user-errors.js';
|
|
11
|
+
const errors = { ...inviteErrors, ...userErrors };
|
|
12
|
+
/**
|
|
13
|
+
* Grant the account its profile, `member` by default, then have Admin send the invitation to the
|
|
14
|
+
* account's own email. A deactivated account is not invited; only an admin grants `admin`.
|
|
15
|
+
*
|
|
16
|
+
* @deprecated The invitation before durable invitations (L-SHELL-18). Replaced by
|
|
17
|
+
* `Invitation.send` (`functions/invitation/send-invitation.ts`). Removal: Shell major (delivery
|
|
18
|
+
* step 5), once Admin (step 2) and 1Pact (step 4) call `Invitation.send`.
|
|
19
|
+
*/
|
|
20
|
+
export const invite = defineWorkflow()('User.invite', { errors }, async (context) => {
|
|
24
21
|
const user = canonicalNodeId(context.input.user, () => context.error('INVALID_INVITATION_REFERENCE', {}));
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
22
|
+
if (legacyIsInvitation(context.input))
|
|
23
|
+
return legacyInvite(context, user);
|
|
24
|
+
const profile = context.input.profile ?? 'member';
|
|
25
|
+
const role = `workspace.${profile}`;
|
|
26
|
+
const account = await context.step.run('read-account', () => readAccount(context, user));
|
|
27
|
+
if (account === undefined)
|
|
28
|
+
throw context.error('USER_NOT_FOUND', {});
|
|
29
|
+
if (account.frozen)
|
|
30
|
+
throw context.error('USER_SUSPENDED', {});
|
|
31
|
+
if (account.email === undefined)
|
|
32
|
+
throw context.error('USER_EMAIL_INVALID', {});
|
|
33
|
+
const email = account.email;
|
|
34
|
+
if (profile === 'admin') {
|
|
35
|
+
const admitted = await context.step.run('check-profile', () => authority(context).callerHolds('workspace.admin'));
|
|
36
|
+
if (!admitted)
|
|
37
|
+
throw context.error('PROFILE_NOT_HELD', {});
|
|
38
38
|
}
|
|
39
|
+
await context.step.run('grant-profile', async () => {
|
|
40
|
+
const held = await readExtensionTargets(context, user);
|
|
41
|
+
const node = await authority(context).roleNode(role);
|
|
42
|
+
if (node !== undefined && held.has(node))
|
|
43
|
+
return;
|
|
44
|
+
await context.mutate(grantRole, { holder: user, role: context.domain.core.nodes[role].ref });
|
|
45
|
+
});
|
|
46
|
+
return requestInvitation(context, { user, email, profile });
|
|
39
47
|
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { readIdentities } from '../../../queries/identity/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* @deprecated The account that claimed an email before accounts had `email` (L-SHELL-13): Shell
|
|
4
|
+
* neither creates nor provides a second account for it (U1), and does not trust the claim enough to
|
|
5
|
+
* return that account; `legacyMigrateAccounts` makes it the email's account. Replaced by the
|
|
6
|
+
* `email` key alone. Removal: Shell major (delivery step 5) after 1Pact step 4.
|
|
7
|
+
*/
|
|
8
|
+
export async function legacyClaimedAccount({ query }, email) {
|
|
9
|
+
const [claimed] = await query(readIdentities, { kind: 'User', where: { claimedEmail: email } });
|
|
10
|
+
return claimed?.id;
|
|
11
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { requestInvitation } from '../request-invitation.js';
|
|
2
|
+
/**
|
|
3
|
+
* @deprecated Whether an invitation names its email, the form before profiles (L-SHELL-6).
|
|
4
|
+
* Replaced by `User.invite({ user, profile })`. Removal: Shell major (delivery step 5) after 1Pact step 4.
|
|
5
|
+
*/
|
|
6
|
+
export function legacyIsInvitation(input) {
|
|
7
|
+
return input.email !== undefined;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* @deprecated The `{ user, email }` invitation of callers older than profiles (L-SHELL-6): Admin
|
|
11
|
+
* sends it to the email the caller gives and assigns the Core group itself; Shell grants nothing.
|
|
12
|
+
* Replaced by `User.invite({ user, profile })`. Removal: Shell major (delivery step 5) after 1Pact
|
|
13
|
+
* step 4.
|
|
14
|
+
*/
|
|
15
|
+
export function legacyInvite(context, user) {
|
|
16
|
+
const email = context.input.email;
|
|
17
|
+
if (email === undefined)
|
|
18
|
+
throw new TypeError('A legacy invitation names its email.');
|
|
19
|
+
return requestInvitation(context, { user, email });
|
|
20
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { defineAction } from '@astrale-os/sdk/action';
|
|
2
|
+
import { toGroup } from '../../group/index.js';
|
|
3
|
+
import { createProfile, groupProfileValues, newProfileValues } from '../../../mutations/identity/index.js';
|
|
4
|
+
import { readIdentities } from '../../../queries/identity/index.js';
|
|
5
|
+
import { readProfile } from '../../../queries/profile/index.js';
|
|
6
|
+
import { nodeReference, normalizeEmail, toAccount } from '../account.js';
|
|
7
|
+
import { readAccount } from '../read-account.js';
|
|
8
|
+
import { writeAccount } from '../write-account.js';
|
|
9
|
+
/**
|
|
10
|
+
* @deprecated (L-SHELL-14) The profile name the migration gives an account that has neither a name
|
|
11
|
+
* nor an email yet. Replaced by nothing: accounts are born with a name or an email. Removal: with
|
|
12
|
+
* `legacyMigrateAccounts`.
|
|
13
|
+
*/
|
|
14
|
+
export const LEGACY_UNNAMED_ACCOUNT = 'Compte sans nom';
|
|
15
|
+
/**
|
|
16
|
+
* @deprecated Write `email` and `name` on the accounts created before them, from what
|
|
17
|
+
* each recorded about itself, and give every account and group stored before profiles its
|
|
18
|
+
* profile (L-SHELL-14). Idempotent: an account that has them is left as it is,
|
|
19
|
+
* and an email another account holds is reported, never taken. Replaced by nothing: accounts are
|
|
20
|
+
* born with them. Removal: Shell major (delivery step 5) after 1Pact step 4, once every instance
|
|
21
|
+
* has run it.
|
|
22
|
+
*
|
|
23
|
+
* A claimed email was recorded by the account itself at sign-in: an administrator runs this only
|
|
24
|
+
* where the GUI recorded the email its identity provider verified.
|
|
25
|
+
*/
|
|
26
|
+
export const legacyMigrateAccounts = defineAction()('legacyMigrateAccounts', async (context) => {
|
|
27
|
+
const properties = context.domain.classes.User.properties;
|
|
28
|
+
const nodes = await context.query(readIdentities, { kind: 'User', where: {} });
|
|
29
|
+
const accounts = nodes.map((node) => ({
|
|
30
|
+
account: toAccount(context.domain, node),
|
|
31
|
+
props: node.props,
|
|
32
|
+
}));
|
|
33
|
+
const taken = new Map();
|
|
34
|
+
for (const { account } of accounts)
|
|
35
|
+
if (account.email !== undefined)
|
|
36
|
+
taken.set(account.email, account.id);
|
|
37
|
+
let migrated = 0;
|
|
38
|
+
const conflicts = [];
|
|
39
|
+
for (const { account, props } of accounts) {
|
|
40
|
+
const claimedEmail = props[properties.claimedEmail.key];
|
|
41
|
+
const claimedName = props[properties.claimedName.key];
|
|
42
|
+
const email = account.email === undefined && typeof claimedEmail === 'string'
|
|
43
|
+
? normalizeEmail(claimedEmail)
|
|
44
|
+
: undefined;
|
|
45
|
+
if (email !== undefined && taken.has(email) && taken.get(email) !== account.id) {
|
|
46
|
+
conflicts.push({ user: nodeReference(account.id), email });
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
const name = account.name === undefined && typeof claimedName === 'string'
|
|
50
|
+
? claimedName.slice(0, 100)
|
|
51
|
+
: undefined;
|
|
52
|
+
const written = await writeAccount(context, account, {
|
|
53
|
+
...(email === undefined ? {} : { email }),
|
|
54
|
+
...(name === undefined ? {} : { name }),
|
|
55
|
+
});
|
|
56
|
+
if (written === 'written') {
|
|
57
|
+
migrated += 1;
|
|
58
|
+
if (email !== undefined)
|
|
59
|
+
taken.set(email, account.id);
|
|
60
|
+
}
|
|
61
|
+
else if (written !== 'unchanged' && email !== undefined) {
|
|
62
|
+
conflicts.push({ user: nodeReference(account.id), email });
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
// Every account and group stored before profiles gets its profile, from its values now.
|
|
66
|
+
let profiles = 0;
|
|
67
|
+
for (const { account } of accounts) {
|
|
68
|
+
if ((await context.query(readProfile, { kind: 'User', node: account.id })) !== undefined)
|
|
69
|
+
continue;
|
|
70
|
+
const current = (await readAccount(context, account.id)) ?? account;
|
|
71
|
+
// An account with no name nor email, such as the owner `instance create` makes, still gets
|
|
72
|
+
// one: named for now, renamed with the account once it has a name.
|
|
73
|
+
const values = newProfileValues(current) ?? { name: LEGACY_UNNAMED_ACCOUNT };
|
|
74
|
+
const result = await context.mutate(createProfile, {
|
|
75
|
+
kind: 'User',
|
|
76
|
+
owner: account.id,
|
|
77
|
+
values,
|
|
78
|
+
});
|
|
79
|
+
if (result.ok)
|
|
80
|
+
profiles += 1;
|
|
81
|
+
}
|
|
82
|
+
const groups = await context.query(readIdentities, { kind: 'Group', where: {} });
|
|
83
|
+
for (const node of groups) {
|
|
84
|
+
const group = toGroup(context.domain, node);
|
|
85
|
+
if (group.name === undefined)
|
|
86
|
+
continue;
|
|
87
|
+
if ((await context.query(readProfile, { kind: 'Group', node: group.id })) !== undefined)
|
|
88
|
+
continue;
|
|
89
|
+
const result = await context.mutate(createProfile, {
|
|
90
|
+
kind: 'Group',
|
|
91
|
+
owner: group.id,
|
|
92
|
+
values: groupProfileValues(group),
|
|
93
|
+
});
|
|
94
|
+
if (result.ok)
|
|
95
|
+
profiles += 1;
|
|
96
|
+
}
|
|
97
|
+
return { migrated, conflicts, profiles };
|
|
98
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { defineAction } from '@astrale-os/sdk/action';
|
|
2
|
+
import { legacyRecordUserProfile } from '../../../mutations/user/legacy/index.js';
|
|
3
|
+
import { legacyReadUserProfile } from '../../../queries/user/legacy/index.js';
|
|
4
|
+
/**
|
|
5
|
+
* @deprecated The email and display name the GUI records at each sign-in (L-SHELL-5): one read and,
|
|
6
|
+
* only when something changed, one write, with Shell's own authority; the email in lower case.
|
|
7
|
+
* Replaced by `User.updateProfile`. Removal: Shell major (delivery step 5) after 1Pact step 4.
|
|
8
|
+
*/
|
|
9
|
+
export const legacyRecordProfile = defineAction()('User.recordProfile', async ({ input, self, query, mutate }) => {
|
|
10
|
+
const asked = {
|
|
11
|
+
...(input.email === undefined ? {} : { email: input.email.trim().toLowerCase() }),
|
|
12
|
+
...(input.displayName === undefined ? {} : { displayName: input.displayName }),
|
|
13
|
+
};
|
|
14
|
+
const current = await query(legacyReadUserProfile, { user: self });
|
|
15
|
+
const set = Object.fromEntries(Object.entries(asked).filter(([key, value]) => current[key] !== value));
|
|
16
|
+
if (Object.keys(set).length > 0)
|
|
17
|
+
await mutate(legacyRecordUserProfile, { user: self, set });
|
|
18
|
+
return {};
|
|
19
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { authority } from '../authority/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* U4 and U5 for a change that takes an account away from its holder: only an admin changes an
|
|
4
|
+
* account that holds an administration Role, and the last active admin stays. Answers the refusal.
|
|
5
|
+
*/
|
|
6
|
+
export async function protectAdministrators(capabilities, user, options) {
|
|
7
|
+
const authorities = authority(capabilities);
|
|
8
|
+
if (!(await authorities.holdsAdministration(user)))
|
|
9
|
+
return undefined;
|
|
10
|
+
if (!(await authorities.callerHolds('workspace.admin')))
|
|
11
|
+
return 'USER_ADMINISTRATOR_PROTECTED';
|
|
12
|
+
if (!options.keepsLastAdministrator)
|
|
13
|
+
return undefined;
|
|
14
|
+
const active = await authorities.activeAdministrators();
|
|
15
|
+
return active.includes(user) && active.length <= 1 ? 'USER_LAST_ADMINISTRATOR' : undefined;
|
|
16
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { defineAction } from '@astrale-os/sdk/action';
|
|
2
|
+
import { createAccount } from '../../mutations/identity/index.js';
|
|
3
|
+
import { nodeReference, normalizeEmail } from './account.js';
|
|
4
|
+
// Seam L-SHELL-13: an email an account claimed before `email` existed is taken (U1).
|
|
5
|
+
import { legacyClaimedAccount } from './legacy/index.js';
|
|
6
|
+
import { findAccount } from './read-account.js';
|
|
7
|
+
import { userErrors } from './user-errors.js';
|
|
8
|
+
import { writeAccount } from './write-account.js';
|
|
9
|
+
/** A2: an account a directory adopts, local, or of the same directory and frozen. */
|
|
10
|
+
const adoptable = (account, directory) => account.directory === undefined || (account.directory === directory && account.frozen);
|
|
11
|
+
/**
|
|
12
|
+
* Write the account a directory provides (`dsync.user.created`, `dsync.user.updated`). The account
|
|
13
|
+
* of its directory id is updated; else the account of its email is adopted (A2); else one is
|
|
14
|
+
* created. Replaying an event writes nothing (A3); a conflict refuses it whole and names the
|
|
15
|
+
* account it conflicts with (A4).
|
|
16
|
+
*/
|
|
17
|
+
export const provision = defineAction()('User.provision', { errors: userErrors }, async (context) => {
|
|
18
|
+
const { input, error } = context;
|
|
19
|
+
const email = normalizeEmail(input.email);
|
|
20
|
+
if (email === undefined)
|
|
21
|
+
throw error('USER_EMAIL_INVALID', {});
|
|
22
|
+
const { directory, externalId } = input;
|
|
23
|
+
const provided = {
|
|
24
|
+
email,
|
|
25
|
+
name: input.name,
|
|
26
|
+
givenName: input.givenName ?? null,
|
|
27
|
+
familyName: input.familyName ?? null,
|
|
28
|
+
frozen: !input.active,
|
|
29
|
+
};
|
|
30
|
+
const [byDirectory, byEmail] = await Promise.all([
|
|
31
|
+
findAccount(context, { directory, externalId }),
|
|
32
|
+
findAccount(context, { email }),
|
|
33
|
+
]);
|
|
34
|
+
const conflict = (account) => error('DIRECTORY_CONFLICT', { user: nodeReference(account.id) });
|
|
35
|
+
let target = byDirectory;
|
|
36
|
+
let adopted = false;
|
|
37
|
+
if (byDirectory !== undefined) {
|
|
38
|
+
if (byEmail !== undefined && byEmail.id !== byDirectory.id)
|
|
39
|
+
throw conflict(byEmail);
|
|
40
|
+
}
|
|
41
|
+
else if (byEmail !== undefined) {
|
|
42
|
+
if (!adoptable(byEmail, directory))
|
|
43
|
+
throw conflict(byEmail);
|
|
44
|
+
target = byEmail;
|
|
45
|
+
adopted = true;
|
|
46
|
+
}
|
|
47
|
+
if (target === undefined) {
|
|
48
|
+
const claimed = await legacyClaimedAccount(context, email);
|
|
49
|
+
if (claimed !== undefined)
|
|
50
|
+
throw error('DIRECTORY_CONFLICT', { user: nodeReference(claimed) });
|
|
51
|
+
const result = await context.mutate(createAccount, {
|
|
52
|
+
values: {
|
|
53
|
+
email,
|
|
54
|
+
name: input.name,
|
|
55
|
+
...(input.givenName === undefined ? {} : { givenName: input.givenName }),
|
|
56
|
+
...(input.familyName === undefined ? {} : { familyName: input.familyName }),
|
|
57
|
+
directory,
|
|
58
|
+
externalId,
|
|
59
|
+
frozen: !input.active,
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
if (!result.ok)
|
|
63
|
+
throw error('ACCOUNT_CHANGED', {});
|
|
64
|
+
return { user: nodeReference(result.user), created: true, adopted: false };
|
|
65
|
+
}
|
|
66
|
+
const written = await writeAccount(context, target, { ...provided, directory, externalId });
|
|
67
|
+
if (written === 'DUPLICATE_KEY' || written === 'CHANGED')
|
|
68
|
+
throw error('ACCOUNT_CHANGED', {});
|
|
69
|
+
return { user: nodeReference(target.id), created: false, adopted };
|
|
70
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { defineAction } from '@astrale-os/sdk/action';
|
|
2
|
+
import { protectAdministrators } from './protect-administrators.js';
|
|
3
|
+
import { readAccount } from './read-account.js';
|
|
4
|
+
import { userErrors } from './user-errors.js';
|
|
5
|
+
import { writeAccount } from './write-account.js';
|
|
6
|
+
/** `frozen: false`: the account may sign in again (U4). */
|
|
7
|
+
export const reactivate = defineAction()('User.reactivate', { errors: userErrors }, async (context) => {
|
|
8
|
+
const { error, self } = context;
|
|
9
|
+
const account = await readAccount(context, self);
|
|
10
|
+
if (account === undefined)
|
|
11
|
+
throw error('USER_NOT_FOUND', {});
|
|
12
|
+
const refusal = await protectAdministrators(context, self, { keepsLastAdministrator: false });
|
|
13
|
+
if (refusal !== undefined)
|
|
14
|
+
throw error(refusal, {});
|
|
15
|
+
const written = await writeAccount(context, account, { frozen: false });
|
|
16
|
+
if (written === 'CHANGED' || written === 'DUPLICATE_KEY')
|
|
17
|
+
throw error('ACCOUNT_CHANGED', {});
|
|
18
|
+
return {};
|
|
19
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { readIdentities } from '../../queries/identity/index.js';
|
|
2
|
+
import { toAccount } from './account.js';
|
|
3
|
+
/** The account at one Node, or undefined when there is none Shell reads. */
|
|
4
|
+
export async function readAccount({ query, domain }, user) {
|
|
5
|
+
const [node] = await query(readIdentities, { kind: 'User', ids: [user] });
|
|
6
|
+
return node === undefined ? undefined : toAccount(domain, node);
|
|
7
|
+
}
|
|
8
|
+
/** The account holding these values: an email, or a directory and its id there. */
|
|
9
|
+
export async function findAccount({ query, domain }, where) {
|
|
10
|
+
const [node] = await query(readIdentities, { kind: 'User', where });
|
|
11
|
+
return node === undefined ? undefined : toAccount(domain, node);
|
|
12
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { InvitationRequestFailure } from '../../integrations/user-invitation/index.js';
|
|
2
|
+
/** Ask Admin to send the invitation, once per invocation key. */
|
|
3
|
+
export async function requestInvitation(context, request) {
|
|
4
|
+
const requestId = context.execution.idempotencyKey ?? crypto.randomUUID();
|
|
5
|
+
try {
|
|
6
|
+
return await context.step.run('request-invitation', () => context.integrations.enrollment.invite({
|
|
7
|
+
...request,
|
|
8
|
+
requestId,
|
|
9
|
+
requestedBy: context.caller.identity,
|
|
10
|
+
}));
|
|
11
|
+
}
|
|
12
|
+
catch (cause) {
|
|
13
|
+
if (cause instanceof InvitationRequestFailure)
|
|
14
|
+
throw context.error(cause.code, { requestId });
|
|
15
|
+
throw cause;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { defineAction } from '@astrale-os/sdk/action';
|
|
2
|
+
import { isProvided } from './account.js';
|
|
3
|
+
import { readAccount } from './read-account.js';
|
|
4
|
+
import { userErrors } from './user-errors.js';
|
|
5
|
+
import { writeAccount } from './write-account.js';
|
|
6
|
+
/** The holder changes its own name or icon; a provided account's name is its directory's (U3). */
|
|
7
|
+
export const updateProfile = defineAction()('User.updateProfile', { errors: userErrors }, async (context) => {
|
|
8
|
+
const { input, error, self } = context;
|
|
9
|
+
const account = await readAccount(context, self);
|
|
10
|
+
if (account === undefined)
|
|
11
|
+
throw error('USER_NOT_FOUND', {});
|
|
12
|
+
if (isProvided(account) && input.name !== undefined && input.name !== account.name) {
|
|
13
|
+
throw error('USER_DIRECTORY_MANAGED', {});
|
|
14
|
+
}
|
|
15
|
+
const written = await writeAccount(context, account, {
|
|
16
|
+
...(input.name === undefined ? {} : { name: input.name }),
|
|
17
|
+
...(input.icon === undefined ? {} : { icon: input.icon }),
|
|
18
|
+
});
|
|
19
|
+
if (written === 'CHANGED' || written === 'DUPLICATE_KEY')
|
|
20
|
+
throw error('ACCOUNT_CHANGED', {});
|
|
21
|
+
return {};
|
|
22
|
+
});
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { defineAction } from '@astrale-os/sdk/action';
|
|
2
|
+
import { authority } from '../authority/index.js';
|
|
3
|
+
import { MAX_ATTRIBUTES, boundedAttributes } from '../../utils/index.js';
|
|
4
|
+
import { isProvided, nodeReference, normalizeEmail } from './account.js';
|
|
5
|
+
import { findAccount, readAccount } from './read-account.js';
|
|
6
|
+
import { userErrors } from './user-errors.js';
|
|
7
|
+
import { writeAccount } from './write-account.js';
|
|
8
|
+
/**
|
|
9
|
+
* Change an account's name, email, icon or attributes. Attributes are the instance's own, never
|
|
10
|
+
* the directory's (AM11). A provided account's name and email come from its
|
|
11
|
+
* directory (U3); an administrator's account changes only by an admin (U4); an email designates at
|
|
12
|
+
* most one account (U1).
|
|
13
|
+
*/
|
|
14
|
+
export const update = defineAction()('User.update', { errors: userErrors }, async (context) => {
|
|
15
|
+
const { input, error, self } = context;
|
|
16
|
+
const account = await readAccount(context, self);
|
|
17
|
+
if (account === undefined)
|
|
18
|
+
throw error('USER_NOT_FOUND', {});
|
|
19
|
+
const attributes = input.attributes === undefined || input.attributes === null
|
|
20
|
+
? input.attributes
|
|
21
|
+
: boundedAttributes(input.attributes);
|
|
22
|
+
if (input.attributes !== undefined && input.attributes !== null && attributes === undefined) {
|
|
23
|
+
throw error('USER_ATTRIBUTES_TOO_MANY', { max: MAX_ATTRIBUTES });
|
|
24
|
+
}
|
|
25
|
+
let email;
|
|
26
|
+
if (input.email !== undefined) {
|
|
27
|
+
email = normalizeEmail(input.email);
|
|
28
|
+
if (email === undefined)
|
|
29
|
+
throw error('USER_EMAIL_INVALID', {});
|
|
30
|
+
}
|
|
31
|
+
const directoryOwned = {
|
|
32
|
+
name: input.name,
|
|
33
|
+
email,
|
|
34
|
+
givenName: input.givenName,
|
|
35
|
+
familyName: input.familyName,
|
|
36
|
+
};
|
|
37
|
+
if (isProvided(account) &&
|
|
38
|
+
Object.entries(directoryOwned).some(([key, value]) => value !== undefined && value !== account[key])) {
|
|
39
|
+
throw error('USER_DIRECTORY_MANAGED', {});
|
|
40
|
+
}
|
|
41
|
+
const authorities = authority(context);
|
|
42
|
+
if ((await authorities.holdsAdministration(account.id)) &&
|
|
43
|
+
!(await authorities.callerHolds('workspace.admin'))) {
|
|
44
|
+
throw error('USER_ADMINISTRATOR_PROTECTED', {});
|
|
45
|
+
}
|
|
46
|
+
if (email !== undefined && email !== account.email) {
|
|
47
|
+
const holder = await findAccount(context, { email });
|
|
48
|
+
if (holder !== undefined)
|
|
49
|
+
throw error('USER_EMAIL_TAKEN', { user: nodeReference(holder.id) });
|
|
50
|
+
}
|
|
51
|
+
const written = await writeAccount(context, account, {
|
|
52
|
+
...(input.name === undefined ? {} : { name: input.name }),
|
|
53
|
+
...(email === undefined ? {} : { email }),
|
|
54
|
+
...(input.givenName === undefined ? {} : { givenName: input.givenName }),
|
|
55
|
+
...(input.familyName === undefined ? {} : { familyName: input.familyName }),
|
|
56
|
+
...(input.icon === undefined ? {} : { icon: input.icon }),
|
|
57
|
+
...(attributes === undefined ? {} : { attributes }),
|
|
58
|
+
});
|
|
59
|
+
if (written === 'DUPLICATE_KEY') {
|
|
60
|
+
const holder = email === undefined ? undefined : await findAccount(context, { email });
|
|
61
|
+
throw error('USER_EMAIL_TAKEN', {
|
|
62
|
+
user: holder === undefined ? '' : nodeReference(holder.id),
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
if (written === 'CHANGED')
|
|
66
|
+
throw error('ACCOUNT_CHANGED', {});
|
|
67
|
+
return {};
|
|
68
|
+
});
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
const none = z.strictObject({});
|
|
3
|
+
const account = z.strictObject({ user: z.string() });
|
|
4
|
+
/**
|
|
5
|
+
* The refusals of the account methods. The SDK declares no FORBIDDEN business family, so a law
|
|
6
|
+
* that forbids a change for the state of the account, its group or its caller answers CONFLICT.
|
|
7
|
+
*/
|
|
8
|
+
export const userErrors = {
|
|
9
|
+
USER_NOT_FOUND: {
|
|
10
|
+
family: 'NOT_FOUND',
|
|
11
|
+
message: 'No account is at this path.',
|
|
12
|
+
details: none,
|
|
13
|
+
},
|
|
14
|
+
USER_EMAIL_INVALID: {
|
|
15
|
+
family: 'VALIDATION_ERROR',
|
|
16
|
+
message: 'The email is not a valid address, or the account has none.',
|
|
17
|
+
details: none,
|
|
18
|
+
},
|
|
19
|
+
USER_ATTRIBUTES_TOO_MANY: {
|
|
20
|
+
family: 'VALIDATION_ERROR',
|
|
21
|
+
message: 'An account carries at most 64 attributes.',
|
|
22
|
+
details: z.strictObject({ max: z.number() }),
|
|
23
|
+
},
|
|
24
|
+
USER_EMAIL_TAKEN: {
|
|
25
|
+
family: 'CONFLICT',
|
|
26
|
+
message: 'Another account already has this email.',
|
|
27
|
+
details: account,
|
|
28
|
+
},
|
|
29
|
+
USER_DIRECTORY_MANAGED: {
|
|
30
|
+
family: 'CONFLICT',
|
|
31
|
+
message: "This account is managed by your organization's directory. Change it in the directory.",
|
|
32
|
+
details: none,
|
|
33
|
+
},
|
|
34
|
+
USER_ADMINISTRATOR_PROTECTED: {
|
|
35
|
+
family: 'CONFLICT',
|
|
36
|
+
message: "Only an administrator may change an administrator's account.",
|
|
37
|
+
details: none,
|
|
38
|
+
},
|
|
39
|
+
USER_LAST_ADMINISTRATOR: {
|
|
40
|
+
family: 'CONFLICT',
|
|
41
|
+
message: 'This account is the last active administrator of the workspace.',
|
|
42
|
+
details: none,
|
|
43
|
+
},
|
|
44
|
+
USER_ACTIVE: {
|
|
45
|
+
family: 'CONFLICT',
|
|
46
|
+
message: 'Deactivate this account before deleting it.',
|
|
47
|
+
details: none,
|
|
48
|
+
},
|
|
49
|
+
USER_SUSPENDED: {
|
|
50
|
+
family: 'CONFLICT',
|
|
51
|
+
message: 'This account is deactivated; reactivate it before inviting it.',
|
|
52
|
+
details: none,
|
|
53
|
+
},
|
|
54
|
+
PROFILE_NOT_HELD: {
|
|
55
|
+
family: 'CONFLICT',
|
|
56
|
+
message: 'Only an administrator may grant the admin profile.',
|
|
57
|
+
details: none,
|
|
58
|
+
},
|
|
59
|
+
DIRECTORY_CONFLICT: {
|
|
60
|
+
family: 'CONFLICT',
|
|
61
|
+
message: 'The directory event conflicts with an existing account; nothing changed.',
|
|
62
|
+
details: account,
|
|
63
|
+
},
|
|
64
|
+
ACCOUNT_CHANGED: {
|
|
65
|
+
family: 'CONFLICT',
|
|
66
|
+
message: 'The account changed concurrently; retry the operation.',
|
|
67
|
+
details: none,
|
|
68
|
+
},
|
|
69
|
+
};
|