@astrale-domains/shell 0.1.6 → 0.1.7
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 +477 -31
- package/dist/domain.js +87 -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 +98 -0
- package/dist/functions/directory/disconnect-directory.js +35 -0
- package/dist/functions/directory/index.js +1 -0
- package/dist/functions/group/change-group-membership.js +43 -9
- 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 +2 -0
- 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 +60 -0
- package/dist/functions/group/membership-laws.js +40 -0
- package/dist/functions/group/provision-group.js +33 -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 +81 -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/legacy/index.js +1 -0
- package/dist/mutations/group/legacy/membership-class.js +7 -0
- package/dist/mutations/group/set-group-membership.js +23 -14
- 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 +62 -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 +41 -0
- package/dist/queries/group/index.js +1 -1
- package/dist/queries/group/legacy/index.js +2 -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/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 +25 -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 +13 -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 +18203 -630
- package/dist/runtime.js +32 -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 +21 -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 +73 -1
- package/dist/schema/modules/group/classes/group.js +28 -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/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 +7 -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/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.js +10 -0
- package/dist/schema/modules/group/types/index.js +1 -0
- 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 +30 -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 +37 -0
- package/dist/schema/modules/role/core/roles.js +30 -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 +1849 -118
- 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/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
- /package/dist/functions/group/{assign-member-to-group.js → assign-member.js} +0 -0
- /package/dist/functions/group/{unassign-member-from-group.js → unassign-member.js} +0 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { GROUP_PROFILE_PROPERTIES, groupProfileValues, } from '../../mutations/identity/index.js';
|
|
2
|
+
import { readIdentities } from '../../queries/identity/index.js';
|
|
3
|
+
import { readProfile } from '../../queries/profile/index.js';
|
|
4
|
+
import { toAttributes } from '../../utils/index.js';
|
|
5
|
+
const text = (value) => (typeof value === 'string' ? value : undefined);
|
|
6
|
+
/** One group Node as Shell's laws read it, by the Property keys of the resolved Domain. */
|
|
7
|
+
export function toGroup(domain, node) {
|
|
8
|
+
const properties = domain.classes.Group.properties;
|
|
9
|
+
const value = (name) => node.props[properties[name].key];
|
|
10
|
+
const read = (name) => text(value(name));
|
|
11
|
+
const values = {
|
|
12
|
+
name: read('name'),
|
|
13
|
+
description: read('description'),
|
|
14
|
+
icon: read('icon'),
|
|
15
|
+
directory: read('directory'),
|
|
16
|
+
externalId: read('externalId'),
|
|
17
|
+
attributes: toAttributes(value('attributes')),
|
|
18
|
+
};
|
|
19
|
+
return {
|
|
20
|
+
id: node.id,
|
|
21
|
+
class: node.class,
|
|
22
|
+
...Object.fromEntries(Object.entries(values).filter(([, value]) => value !== undefined)),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
export async function readGroup({ query, domain }, group) {
|
|
26
|
+
const [node] = await query(readIdentities, { kind: 'Group', ids: [group] });
|
|
27
|
+
return node === undefined ? undefined : toGroup(domain, node);
|
|
28
|
+
}
|
|
29
|
+
export async function findGroup({ query, domain }, where) {
|
|
30
|
+
const [node] = await query(readIdentities, { kind: 'Group', where });
|
|
31
|
+
return node === undefined ? undefined : toGroup(domain, node);
|
|
32
|
+
}
|
|
33
|
+
/** A group is provided while it carries its directory (G2). */
|
|
34
|
+
export const isProvidedGroup = (group) => group.directory !== undefined;
|
|
35
|
+
/**
|
|
36
|
+
* The profile a group change writes: its profile Node when the change touches a shared value, or,
|
|
37
|
+
* for a group stored before profiles, the whole profile to create; undefined when nothing shared
|
|
38
|
+
* changes.
|
|
39
|
+
*/
|
|
40
|
+
export async function groupProfileTarget({ query }, group, set, unset) {
|
|
41
|
+
const shares = GROUP_PROFILE_PROPERTIES.some((key) => key in set || unset.includes(key));
|
|
42
|
+
if (!shares)
|
|
43
|
+
return undefined;
|
|
44
|
+
const profile = await query(readProfile, { kind: 'Group', node: group.id });
|
|
45
|
+
if (profile !== undefined)
|
|
46
|
+
return { id: profile };
|
|
47
|
+
const merged = { ...group, ...set };
|
|
48
|
+
for (const key of unset)
|
|
49
|
+
delete merged[key];
|
|
50
|
+
return { create: groupProfileValues(merged) };
|
|
51
|
+
}
|
|
@@ -1,2 +1,8 @@
|
|
|
1
|
-
export {
|
|
2
|
-
export {
|
|
1
|
+
export { create } from './create-group.js';
|
|
2
|
+
export { update } from './update-group.js';
|
|
3
|
+
export { remove } from './delete-group.js';
|
|
4
|
+
export { assignMember } from './assign-member.js';
|
|
5
|
+
export { unassignMember } from './unassign-member.js';
|
|
6
|
+
export { provision } from './provision-group.js';
|
|
7
|
+
export { deprovision } from './deprovision-group.js';
|
|
8
|
+
export { toGroup } from './group.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { legacyChangeForeignMembership, legacyMembershipEdges } from './membership.js';
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { setGroupMembership } from '../../../mutations/group/index.js';
|
|
2
|
+
import { readGroupMembership, readGroupMembershipEndpoints } from '../../../queries/group/index.js';
|
|
3
|
+
import { legacyReadCoreGroups, legacyReadMemberOfGroup } from '../../../queries/group/legacy/index.js';
|
|
4
|
+
/**
|
|
5
|
+
* @deprecated The legacy `member_of_group` Edge a change of a Shell group's members also converges
|
|
6
|
+
* (L-SHELL-8): joining one of the legacy Core groups writes it beside `extends_with`, for the readers
|
|
7
|
+
* that still check it; leaving any group removes it. Replaced by `extends_with` alone. Removal:
|
|
8
|
+
* Shell major (delivery step 5) after 1Pact step 4.
|
|
9
|
+
*/
|
|
10
|
+
export async function legacyMembershipEdges(context, membership, present) {
|
|
11
|
+
const observed = await context.query(legacyReadMemberOfGroup, membership);
|
|
12
|
+
let wanted = false;
|
|
13
|
+
if (present) {
|
|
14
|
+
const legacyGroups = Object.values(await context.query(legacyReadCoreGroups, {}));
|
|
15
|
+
wanted = observed.present || legacyGroups.includes(membership.group);
|
|
16
|
+
}
|
|
17
|
+
return [{ edge: 'legacy', current: observed.present, present: wanted, guard: observed.guard }];
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* @deprecated A Domain that extends Group changes the members of its own groups (L-SHELL-9): Shell
|
|
21
|
+
* writes both legacy Edges as before, but under that Domain's authority, since the release no
|
|
22
|
+
* longer grants Shell any grant authority over foreign Identities (AM5). Shell's group laws
|
|
23
|
+
* (G2, G3) do not apply to another Domain's Identity. Replaced by the Domain's own Identity and
|
|
24
|
+
* Roles (P5, P6). Removal: Shell major (delivery step 5) after 1Pact step 4.
|
|
25
|
+
*/
|
|
26
|
+
export async function legacyChangeForeignMembership(context, membership, present) {
|
|
27
|
+
const caller = context.graph.caller;
|
|
28
|
+
if (caller === undefined)
|
|
29
|
+
throw context.error('GROUP_MEMBERSHIP_ENDPOINTS_UNAVAILABLE', {});
|
|
30
|
+
const observe = async () => {
|
|
31
|
+
const endpoints = await caller.query(readGroupMembershipEndpoints, membership);
|
|
32
|
+
if (!endpoints.visible)
|
|
33
|
+
throw context.error('GROUP_MEMBERSHIP_ENDPOINTS_UNAVAILABLE', {});
|
|
34
|
+
const [authority, legacy] = await Promise.all([
|
|
35
|
+
caller.query(readGroupMembership, membership),
|
|
36
|
+
caller.query(legacyReadMemberOfGroup, membership),
|
|
37
|
+
]);
|
|
38
|
+
const edges = [
|
|
39
|
+
{ edge: 'authority', current: authority.present, present, guard: authority.guard },
|
|
40
|
+
{ edge: 'legacy', current: legacy.present, present, guard: legacy.guard },
|
|
41
|
+
];
|
|
42
|
+
return { endpoints: endpoints.guard, edges };
|
|
43
|
+
};
|
|
44
|
+
const matches = (edges) => edges.every(({ current, present: wanted }) => current === wanted);
|
|
45
|
+
const current = await context.step.run('observe-foreign-membership', observe);
|
|
46
|
+
if (matches(current.edges))
|
|
47
|
+
return {};
|
|
48
|
+
const result = await context.step.run('mutate-foreign-membership', () => caller.mutate(setGroupMembership, { ...membership, ...current }));
|
|
49
|
+
if (result.ok)
|
|
50
|
+
return {};
|
|
51
|
+
if (result.outcomeKnown) {
|
|
52
|
+
if (result.rule === 'cycle')
|
|
53
|
+
throw context.error('GROUP_MEMBERSHIP_CYCLE', {});
|
|
54
|
+
throw context.error('GROUP_MEMBERSHIP_CONFLICT', { cause: result.code });
|
|
55
|
+
}
|
|
56
|
+
const recovered = await context.step.run('recover-foreign-membership', observe);
|
|
57
|
+
if (matches(recovered.edges))
|
|
58
|
+
return {};
|
|
59
|
+
throw context.error('GROUP_MEMBERSHIP_UNCONFIRMED', { cause: result.code, outcomeKnown: false });
|
|
60
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { authority } from '../authority/index.js';
|
|
2
|
+
import { isProvidedGroup } from './group.js';
|
|
3
|
+
/**
|
|
4
|
+
* G2, G3 and acyclicity for one change of a Shell group's members. A provided group changes its
|
|
5
|
+
* members only through its directory, a local one through `group.writer`; a group that holds a
|
|
6
|
+
* Role, directly or through what it extends, only by an admin or its directory; and a member never
|
|
7
|
+
* joins a group it already holds.
|
|
8
|
+
*/
|
|
9
|
+
export async function membershipRefusal(capabilities, group, member, joining) {
|
|
10
|
+
const authorities = authority(capabilities);
|
|
11
|
+
const provided = isProvidedGroup(group);
|
|
12
|
+
if (provided) {
|
|
13
|
+
if (!(await authorities.callerHolds('directory.provisioner'))) {
|
|
14
|
+
return { code: 'GROUP_DIRECTORY_MANAGED' };
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
else if (!(await authorities.callerHolds('group.writer'))) {
|
|
18
|
+
return { code: 'GROUP_NOT_DIRECTORY_MANAGED' };
|
|
19
|
+
}
|
|
20
|
+
if (!provided) {
|
|
21
|
+
const role = await roleProtected(capabilities, group);
|
|
22
|
+
if (role !== undefined)
|
|
23
|
+
return { code: 'GROUP_ROLE_PROTECTED', role };
|
|
24
|
+
}
|
|
25
|
+
if (joining && (await authorities.reaches(group.id, member))) {
|
|
26
|
+
return { code: 'GROUP_MEMBERSHIP_CYCLE' };
|
|
27
|
+
}
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* G3: the Role a group holds, when the caller is no admin and so may not change who holds it,
|
|
32
|
+
* through its members or by deleting it.
|
|
33
|
+
*/
|
|
34
|
+
export async function roleProtected(capabilities, group) {
|
|
35
|
+
const authorities = authority(capabilities);
|
|
36
|
+
const role = await authorities.heldRole(group.id);
|
|
37
|
+
if (role === undefined || (await authorities.callerHolds('workspace.admin')))
|
|
38
|
+
return undefined;
|
|
39
|
+
return role;
|
|
40
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { defineAction } from '@astrale-os/sdk/action';
|
|
2
|
+
import { Path } from '@astrale-os/sdk/graph/path';
|
|
3
|
+
import { createGroup, updateGroup } from '../../mutations/identity/index.js';
|
|
4
|
+
import { groupErrors } from './group-errors.js';
|
|
5
|
+
import { findGroup, groupProfileTarget } from './group.js';
|
|
6
|
+
/**
|
|
7
|
+
* Create or rename the group a directory provides (`dsync.group.created`, `dsync.group.updated`).
|
|
8
|
+
* Replaying an event writes nothing (A3).
|
|
9
|
+
*/
|
|
10
|
+
export const provision = defineAction()('Group.provision', { errors: groupErrors }, async (context) => {
|
|
11
|
+
const { input, error } = context;
|
|
12
|
+
const { directory, externalId, name } = input;
|
|
13
|
+
const existing = await findGroup(context, { directory, externalId });
|
|
14
|
+
if (existing !== undefined) {
|
|
15
|
+
if (existing.name !== name) {
|
|
16
|
+
const profile = await groupProfileTarget(context, existing, { name }, []);
|
|
17
|
+
const result = await context.mutate(updateGroup, {
|
|
18
|
+
group: existing.id,
|
|
19
|
+
set: { name },
|
|
20
|
+
...(profile === undefined ? {} : { profile }),
|
|
21
|
+
});
|
|
22
|
+
if (!result.ok)
|
|
23
|
+
throw error('GROUP_CHANGED', {});
|
|
24
|
+
}
|
|
25
|
+
return { group: Path.id(existing.id).raw, created: false };
|
|
26
|
+
}
|
|
27
|
+
const result = await context.mutate(createGroup, {
|
|
28
|
+
values: { name, directory, externalId },
|
|
29
|
+
});
|
|
30
|
+
if (!result.ok)
|
|
31
|
+
throw error('GROUP_CHANGED', {});
|
|
32
|
+
return { group: Path.id(result.group).raw, created: true };
|
|
33
|
+
});
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { defineAction } from '@astrale-os/sdk/action';
|
|
2
|
+
import { updateGroup } from '../../mutations/identity/index.js';
|
|
3
|
+
import { MAX_ATTRIBUTES, boundedAttributes } from '../../utils/index.js';
|
|
4
|
+
import { sameJson } from '../../utils/index.js';
|
|
5
|
+
import { groupErrors } from './group-errors.js';
|
|
6
|
+
import { groupProfileTarget, isProvidedGroup, readGroup } from './group.js';
|
|
7
|
+
/**
|
|
8
|
+
* Rename, describe, or change the icon or the attributes of a group; a provided group's name is
|
|
9
|
+
* its directory's (G2), its attributes the instance's own (AM11).
|
|
10
|
+
*/
|
|
11
|
+
export const update = defineAction()('Group.update', { errors: groupErrors }, async (context) => {
|
|
12
|
+
const { input, error, self } = context;
|
|
13
|
+
const group = await readGroup(context, self);
|
|
14
|
+
if (group === undefined)
|
|
15
|
+
throw error('GROUP_NOT_FOUND', {});
|
|
16
|
+
if (isProvidedGroup(group) && input.name !== undefined && input.name !== group.name) {
|
|
17
|
+
throw error('GROUP_DIRECTORY_MANAGED', {});
|
|
18
|
+
}
|
|
19
|
+
const set = {};
|
|
20
|
+
const unset = [];
|
|
21
|
+
for (const key of ['name', 'description', 'icon']) {
|
|
22
|
+
const value = input[key];
|
|
23
|
+
if (value === undefined)
|
|
24
|
+
continue;
|
|
25
|
+
if (value === null) {
|
|
26
|
+
if (group[key] !== undefined)
|
|
27
|
+
unset.push(key);
|
|
28
|
+
}
|
|
29
|
+
else if (value !== group[key])
|
|
30
|
+
set[key] = value;
|
|
31
|
+
}
|
|
32
|
+
if (input.attributes === null) {
|
|
33
|
+
if (group.attributes !== undefined)
|
|
34
|
+
unset.push('attributes');
|
|
35
|
+
}
|
|
36
|
+
else if (input.attributes !== undefined) {
|
|
37
|
+
const attributes = boundedAttributes(input.attributes);
|
|
38
|
+
if (attributes === undefined)
|
|
39
|
+
throw error('GROUP_ATTRIBUTES_TOO_MANY', { max: MAX_ATTRIBUTES });
|
|
40
|
+
if (!sameJson(attributes, group.attributes))
|
|
41
|
+
set.attributes = attributes;
|
|
42
|
+
}
|
|
43
|
+
if (Object.keys(set).length === 0 && unset.length === 0)
|
|
44
|
+
return {};
|
|
45
|
+
const profile = await groupProfileTarget(context, group, set, unset);
|
|
46
|
+
const result = await context.mutate(updateGroup, {
|
|
47
|
+
group: self,
|
|
48
|
+
set: set,
|
|
49
|
+
unset,
|
|
50
|
+
...(profile === undefined ? {} : { profile }),
|
|
51
|
+
});
|
|
52
|
+
if (!result.ok)
|
|
53
|
+
throw error('GROUP_CHANGED', {});
|
|
54
|
+
return {};
|
|
55
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export const invitationErrors = {
|
|
3
|
+
INVITATION_NOT_FOUND: {
|
|
4
|
+
family: 'NOT_FOUND',
|
|
5
|
+
message: 'No invitation the caller reads.',
|
|
6
|
+
details: z.strictObject({}),
|
|
7
|
+
},
|
|
8
|
+
INVITATION_NOT_PENDING: {
|
|
9
|
+
family: 'CONFLICT',
|
|
10
|
+
message: 'The invitation is no longer pending: invite the account again.',
|
|
11
|
+
details: z.strictObject({}),
|
|
12
|
+
},
|
|
13
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { defineAction } from '@astrale-os/sdk/action';
|
|
2
|
+
import { readInvitation } from '../../queries/invitation/index.js';
|
|
3
|
+
import { invitationErrors } from './invitation-errors.js';
|
|
4
|
+
import { invitationSignal } from './send-invitation.js';
|
|
5
|
+
/** The pending invitation a method acts on, or its refusal. */
|
|
6
|
+
function pending(invitation, error) {
|
|
7
|
+
if (invitation === undefined)
|
|
8
|
+
throw error('INVITATION_NOT_FOUND', {});
|
|
9
|
+
if (invitation.status !== 'pending')
|
|
10
|
+
throw error('INVITATION_NOT_PENDING', {});
|
|
11
|
+
return invitation;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Send the run of a pending invitation its next event. The Shell signals under its own Domain
|
|
15
|
+
* authority: the Kernel lets only the Domain that owns a run signal it.
|
|
16
|
+
*/
|
|
17
|
+
async function signal(kernel, invitation, value) {
|
|
18
|
+
await kernel.execution.signal(invitation.execution, {
|
|
19
|
+
name: invitationSignal(invitation.round),
|
|
20
|
+
value,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
/** Send the email of a pending invitation again: same invitation, same run. */
|
|
24
|
+
export const resend = defineAction()('Invitation.resend', { errors: invitationErrors }, async (context) => {
|
|
25
|
+
const invitation = pending(await context.query(readInvitation, { invitation: context.self }), context.error);
|
|
26
|
+
await signal(context.kernel, invitation, { kind: 'resend' });
|
|
27
|
+
return {};
|
|
28
|
+
});
|
|
29
|
+
/** Accept a pending invitation at the first sign-in: the run binds the account (U8). */
|
|
30
|
+
export const accept = defineAction()('Invitation.accept', { errors: invitationErrors }, async (context) => {
|
|
31
|
+
const invitation = pending(await context.query(readInvitation, { invitation: context.self }), context.error);
|
|
32
|
+
const { iss, sub } = context.input;
|
|
33
|
+
await signal(context.kernel, invitation, { kind: 'accepted', iss, sub });
|
|
34
|
+
return {};
|
|
35
|
+
});
|
|
36
|
+
/** Cancel a pending invitation: its run marks it cancelled. */
|
|
37
|
+
export const cancel = defineAction()('Invitation.cancel', { errors: invitationErrors }, async (context) => {
|
|
38
|
+
const invitation = pending(await context.query(readInvitation, { invitation: context.self }), context.error);
|
|
39
|
+
await context.kernel.execution.cancel(invitation.execution);
|
|
40
|
+
return {};
|
|
41
|
+
});
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { defineWorkflow } from '@astrale-os/sdk/workflow';
|
|
2
|
+
import { inviteErrors, readAccount, userErrors } from '../user/index.js';
|
|
3
|
+
import { InvitationRequestFailure } from '../../integrations/user-invitation/index.js';
|
|
4
|
+
import { bindAccount, createInvitation, updateInvitation } from '../../mutations/invitation/index.js';
|
|
5
|
+
import { canonicalNodeId } from '../../utils/canonical-node-path/index.js';
|
|
6
|
+
const errors = { ...inviteErrors, ...userErrors };
|
|
7
|
+
/** How long an invitation waits for its first sign-in. */
|
|
8
|
+
export const INVITATION_TTL_MS = 7 * 24 * 3_600_000;
|
|
9
|
+
/** The signal name of one round: a name is delivered once, so each event moves to the next. */
|
|
10
|
+
export const invitationSignal = (round) => `event-${round}`;
|
|
11
|
+
/**
|
|
12
|
+
* Invite an account and keep its invitation until it ends: record it, have Admin send the email,
|
|
13
|
+
* then wait for the first sign-in (`accept`), a resend, a cancel or the expiry. It grants nothing:
|
|
14
|
+
* the account holds what `User.create` or an admin gave it.
|
|
15
|
+
*/
|
|
16
|
+
export const send = defineWorkflow()('Invitation.send', { errors }, async (context) => {
|
|
17
|
+
const { step, execution, awaitSignal, cancelled, error } = context;
|
|
18
|
+
const user = canonicalNodeId(context.input.user, () => error('INVALID_INVITATION_REFERENCE', {}));
|
|
19
|
+
const account = await step.run('read-account', ({ query }) => readAccount({ query, domain: context.domain }, user));
|
|
20
|
+
if (account === undefined)
|
|
21
|
+
throw error('USER_NOT_FOUND', {});
|
|
22
|
+
if (account.frozen)
|
|
23
|
+
throw error('USER_SUSPENDED', {});
|
|
24
|
+
if (account.email === undefined)
|
|
25
|
+
throw error('USER_EMAIL_INVALID', {});
|
|
26
|
+
const email = account.email;
|
|
27
|
+
const startedAt = await step.run('clock', () => Date.now());
|
|
28
|
+
const expiresAt = startedAt + INVITATION_TTL_MS;
|
|
29
|
+
const { invitation } = await step.mutate('record-invitation', createInvitation, {
|
|
30
|
+
user,
|
|
31
|
+
execution: execution.id,
|
|
32
|
+
expiresAt: new Date(expiresAt).toISOString(),
|
|
33
|
+
});
|
|
34
|
+
const requestedBy = execution.initiator.caller ?? execution.initiator.principal;
|
|
35
|
+
const sendEmail = async (round) => {
|
|
36
|
+
try {
|
|
37
|
+
await step.run(`send-email-${round}`, ({ integrations: providers }) => providers.enrollment.invite({
|
|
38
|
+
requestId: `${execution.id}:${round}`,
|
|
39
|
+
user,
|
|
40
|
+
email,
|
|
41
|
+
requestedBy: String(requestedBy),
|
|
42
|
+
}));
|
|
43
|
+
}
|
|
44
|
+
catch (cause) {
|
|
45
|
+
if (cause instanceof InvitationRequestFailure) {
|
|
46
|
+
throw error(cause.code, { requestId: `${execution.id}:${round}` });
|
|
47
|
+
}
|
|
48
|
+
throw cause;
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
try {
|
|
52
|
+
await sendEmail(0);
|
|
53
|
+
for (let round = 0;; round += 1) {
|
|
54
|
+
const now = await step.run(`clock-${round}`, () => Date.now());
|
|
55
|
+
const event = await awaitSignal(invitationSignal(round), {
|
|
56
|
+
timeoutMs: Math.max(expiresAt - now, 1),
|
|
57
|
+
});
|
|
58
|
+
if (event.kind === 'timeout') {
|
|
59
|
+
await step.mutate('expire', updateInvitation, { invitation, status: 'expired' });
|
|
60
|
+
return { status: 'expired' };
|
|
61
|
+
}
|
|
62
|
+
if (event.value.kind === 'accepted') {
|
|
63
|
+
const { iss, sub } = event.value;
|
|
64
|
+
await step.mutate('bind-account', bindAccount, { user, iss, sub });
|
|
65
|
+
await step.mutate('accept', updateInvitation, { invitation, status: 'accepted' });
|
|
66
|
+
return { status: 'accepted' };
|
|
67
|
+
}
|
|
68
|
+
await sendEmail(round + 1);
|
|
69
|
+
await step.mutate(`next-round-${round + 1}`, updateInvitation, {
|
|
70
|
+
invitation,
|
|
71
|
+
round: round + 1,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
catch (cause) {
|
|
76
|
+
if (!cancelled(cause))
|
|
77
|
+
throw cause;
|
|
78
|
+
await step.mutate('cancel', updateInvitation, { invitation, status: 'cancelled' });
|
|
79
|
+
return { status: 'cancelled' };
|
|
80
|
+
}
|
|
81
|
+
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Path } from '@astrale-os/sdk/graph/path';
|
|
2
|
+
import { toAttributes } from '../../utils/index.js';
|
|
3
|
+
const text = (value) => (typeof value === 'string' ? value : undefined);
|
|
4
|
+
/** One account Node as Shell's laws read it, by the Property keys of the resolved Domain. */
|
|
5
|
+
export function toAccount(domain, node) {
|
|
6
|
+
const properties = domain.classes.User.properties;
|
|
7
|
+
const read = (name) => node.props[properties[name].key];
|
|
8
|
+
const values = {
|
|
9
|
+
name: text(read('name')),
|
|
10
|
+
email: text(read('email')),
|
|
11
|
+
givenName: text(read('givenName')),
|
|
12
|
+
familyName: text(read('familyName')),
|
|
13
|
+
icon: text(read('icon')),
|
|
14
|
+
directory: text(read('directory')),
|
|
15
|
+
externalId: text(read('externalId')),
|
|
16
|
+
attributes: toAttributes(read('attributes')),
|
|
17
|
+
frozen: typeof read('frozen') === 'boolean' ? read('frozen') : undefined,
|
|
18
|
+
};
|
|
19
|
+
return {
|
|
20
|
+
id: node.id,
|
|
21
|
+
class: node.class,
|
|
22
|
+
...Object.fromEntries(Object.entries(values).filter(([, value]) => value !== undefined)),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/** An account is provided while it carries its directory (U3). */
|
|
26
|
+
export const isProvided = (account) => account.directory !== undefined;
|
|
27
|
+
/** An email in the form Shell keeps it, lower case; undefined when it is not one. */
|
|
28
|
+
export function normalizeEmail(input) {
|
|
29
|
+
const email = input.trim().toLowerCase();
|
|
30
|
+
return email.length >= 3 && email.length <= 320 && /^[^\s@]+@[^\s@]+$/u.test(email)
|
|
31
|
+
? email
|
|
32
|
+
: undefined;
|
|
33
|
+
}
|
|
34
|
+
/** A Node as a method returns it. */
|
|
35
|
+
export const nodeReference = (id) => Path.id(id).raw;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { defineAction } from '@astrale-os/sdk/action';
|
|
2
|
+
import { createAccount } from '../../mutations/identity/index.js';
|
|
3
|
+
import { MAX_ATTRIBUTES, boundedAttributes } from '../../utils/index.js';
|
|
4
|
+
import { nodeReference, normalizeEmail } from './account.js';
|
|
5
|
+
// Seam L-SHELL-13: an email an account claimed before `email` existed is taken (U1).
|
|
6
|
+
import { legacyClaimedAccount } from './legacy/index.js';
|
|
7
|
+
import { findAccount } from './read-account.js';
|
|
8
|
+
import { userErrors } from './user-errors.js';
|
|
9
|
+
/**
|
|
10
|
+
* Idempotent on the email (U1): the account of the email comes back, `created: false`, whoever
|
|
11
|
+
* created it, invited or provided. A Domain needs no directory to know it (A5).
|
|
12
|
+
*/
|
|
13
|
+
export const create = defineAction()('User.create', { errors: userErrors }, async (context) => {
|
|
14
|
+
const { input, error } = context;
|
|
15
|
+
const email = normalizeEmail(input.email);
|
|
16
|
+
if (email === undefined)
|
|
17
|
+
throw error('USER_EMAIL_INVALID', {});
|
|
18
|
+
const attributes = input.attributes === undefined ? undefined : boundedAttributes(input.attributes);
|
|
19
|
+
if (input.attributes !== undefined && attributes === undefined) {
|
|
20
|
+
throw error('USER_ATTRIBUTES_TOO_MANY', { max: MAX_ATTRIBUTES });
|
|
21
|
+
}
|
|
22
|
+
const existing = await findAccount(context, { email });
|
|
23
|
+
if (existing !== undefined)
|
|
24
|
+
return { user: nodeReference(existing.id), created: false };
|
|
25
|
+
const claimed = await legacyClaimedAccount(context, email);
|
|
26
|
+
if (claimed !== undefined)
|
|
27
|
+
throw error('USER_EMAIL_TAKEN', { user: nodeReference(claimed) });
|
|
28
|
+
const result = await context.mutate(createAccount, {
|
|
29
|
+
values: {
|
|
30
|
+
email,
|
|
31
|
+
name: input.name,
|
|
32
|
+
...(input.givenName === undefined ? {} : { givenName: input.givenName }),
|
|
33
|
+
...(input.familyName === undefined ? {} : { familyName: input.familyName }),
|
|
34
|
+
...(attributes === undefined ? {} : { attributes }),
|
|
35
|
+
frozen: false,
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
if (result.ok)
|
|
39
|
+
return { user: nodeReference(result.user), created: true };
|
|
40
|
+
// Another call created the account of this email meanwhile: it is the one.
|
|
41
|
+
const winner = await findAccount(context, { email });
|
|
42
|
+
if (winner !== undefined)
|
|
43
|
+
return { user: nodeReference(winner.id), created: false };
|
|
44
|
+
throw error('ACCOUNT_CHANGED', {});
|
|
45
|
+
});
|
|
@@ -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: true`: the Kernel refuses the account's next sign-in (U4, U5). */
|
|
7
|
+
export const deactivate = defineAction()('User.deactivate', { 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: true });
|
|
13
|
+
if (refusal !== undefined)
|
|
14
|
+
throw error(refusal, {});
|
|
15
|
+
const written = await writeAccount(context, account, { frozen: true });
|
|
16
|
+
if (written === 'CHANGED' || written === 'DUPLICATE_KEY')
|
|
17
|
+
throw error('ACCOUNT_CHANGED', {});
|
|
18
|
+
return {};
|
|
19
|
+
});
|
|
@@ -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
|
+
};
|