@mindful-web/marko-web-identity-x 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. package/.eslintignore +1 -0
  2. package/LICENSE +21 -0
  3. package/README.md +200 -0
  4. package/api/fragment-types.json +1 -0
  5. package/api/fragments/active-user.js +108 -0
  6. package/api/mutations/add-external-user-id.js +24 -0
  7. package/api/mutations/create-app-user.js +11 -0
  8. package/api/mutations/delete-app-user-for-current-application.js +7 -0
  9. package/api/mutations/impersonate-app-user.js +12 -0
  10. package/api/mutations/logout-app-user.js +11 -0
  11. package/api/mutations/send-change-email-link.js +7 -0
  12. package/api/mutations/send-login-link.js +7 -0
  13. package/api/mutations/set-custom-attributes.js +15 -0
  14. package/api/queries/check-content-access.js +19 -0
  15. package/api/queries/get-active-context.js +45 -0
  16. package/api/queries/load-user-by-external-id.js +14 -0
  17. package/api/queries/load-user-by-id.js +11 -0
  18. package/api/queries/load-user.js +11 -0
  19. package/browser/.eslintrc.js +11 -0
  20. package/browser/access.vue +377 -0
  21. package/browser/authenticate.vue +278 -0
  22. package/browser/change-email-confirm.vue +120 -0
  23. package/browser/change-email-init.vue +179 -0
  24. package/browser/comments/create.vue +107 -0
  25. package/browser/comments/post.vue +151 -0
  26. package/browser/comments/stream.vue +319 -0
  27. package/browser/custom-column.vue +314 -0
  28. package/browser/dayjs.js +6 -0
  29. package/browser/download-related.vue +120 -0
  30. package/browser/download.vue +380 -0
  31. package/browser/errors/authentication.js +7 -0
  32. package/browser/errors/feature.js +7 -0
  33. package/browser/errors/form.js +7 -0
  34. package/browser/errors/logout.js +7 -0
  35. package/browser/form/address-block.vue +131 -0
  36. package/browser/form/common/checkbox-group.vue +140 -0
  37. package/browser/form/common/form-group.vue +24 -0
  38. package/browser/form/common/form-label.vue +17 -0
  39. package/browser/form/consent.vue +121 -0
  40. package/browser/form/fields/address-extra.vue +68 -0
  41. package/browser/form/fields/city.vue +68 -0
  42. package/browser/form/fields/comment-body.vue +63 -0
  43. package/browser/form/fields/country.vue +94 -0
  44. package/browser/form/fields/custom-boolean.vue +58 -0
  45. package/browser/form/fields/custom-select-group.vue +25 -0
  46. package/browser/form/fields/custom-select-multiple.vue +97 -0
  47. package/browser/form/fields/custom-select-write-in-inline.vue +39 -0
  48. package/browser/form/fields/custom-select-write-in.vue +66 -0
  49. package/browser/form/fields/custom-select.vue +191 -0
  50. package/browser/form/fields/custom-text.vue +62 -0
  51. package/browser/form/fields/display-name.vue +64 -0
  52. package/browser/form/fields/email.vue +62 -0
  53. package/browser/form/fields/family-name.vue +68 -0
  54. package/browser/form/fields/given-name.vue +68 -0
  55. package/browser/form/fields/organization-title.vue +68 -0
  56. package/browser/form/fields/organization.vue +68 -0
  57. package/browser/form/fields/phone-number.vue +68 -0
  58. package/browser/form/fields/postal-code.vue +68 -0
  59. package/browser/form/fields/receive-email.vue +58 -0
  60. package/browser/form/fields/region.vue +106 -0
  61. package/browser/form/fields/regional-policy.vue +59 -0
  62. package/browser/form/fields/street.vue +68 -0
  63. package/browser/graphql/create-client.js +65 -0
  64. package/browser/graphql/parse.js +1 -0
  65. package/browser/graphql/queries/download-related-content.js +38 -0
  66. package/browser/index.js +110 -0
  67. package/browser/login-fields.vue +214 -0
  68. package/browser/login.vue +331 -0
  69. package/browser/logout.vue +68 -0
  70. package/browser/mixins/global-auto-signup-event-emitter.js +18 -0
  71. package/browser/mixins/global-event-emitter.js +28 -0
  72. package/browser/profile.vue +688 -0
  73. package/browser/service.js +32 -0
  74. package/browser/utils/clean-path.js +1 -0
  75. package/browser/utils/cookies-enabled.js +10 -0
  76. package/browser/utils/get-as-array.js +6 -0
  77. package/browser/utils/get-referring-page.js +7 -0
  78. package/browser/utils/get.js +8 -0
  79. package/browser/utils/post.js +10 -0
  80. package/browser/utils/redirect.js +7 -0
  81. package/browser/utils/region-country-codes.js +1 -0
  82. package/components/access.marko +24 -0
  83. package/components/access.marko.js +61 -0
  84. package/components/comment-stream.marko +31 -0
  85. package/components/comment-stream.marko.js +71 -0
  86. package/components/context.marko +15 -0
  87. package/components/context.marko.js +52 -0
  88. package/components/form-access.marko +60 -0
  89. package/components/form-access.marko.js +111 -0
  90. package/components/form-authenticate.marko +32 -0
  91. package/components/form-authenticate.marko.js +73 -0
  92. package/components/form-change-email.marko +27 -0
  93. package/components/form-change-email.marko.js +68 -0
  94. package/components/form-login.marko +32 -0
  95. package/components/form-login.marko.js +72 -0
  96. package/components/form-logout.marko +7 -0
  97. package/components/form-logout.marko.js +36 -0
  98. package/components/form-profile.marko +38 -0
  99. package/components/form-profile.marko.js +78 -0
  100. package/components/form-register.marko +2 -0
  101. package/components/form-register.marko.js +32 -0
  102. package/components/identify.marko +17 -0
  103. package/components/identify.marko.js +59 -0
  104. package/components/marko.json +90 -0
  105. package/components/non-auth-identify.marko +15 -0
  106. package/components/non-auth-identify.marko.js +53 -0
  107. package/config.js +161 -0
  108. package/hooks.js +20 -0
  109. package/index.js +30 -0
  110. package/marko.json +6 -0
  111. package/middleware/content-access-state.js +31 -0
  112. package/middleware/content-download-state.js +27 -0
  113. package/middleware.js +32 -0
  114. package/package.json +40 -0
  115. package/routes/access.js +29 -0
  116. package/routes/authenticate.js +58 -0
  117. package/routes/change-email-confirm.js +60 -0
  118. package/routes/change-email-init.js +16 -0
  119. package/routes/comment-count.js +19 -0
  120. package/routes/comments.js +43 -0
  121. package/routes/countries.js +19 -0
  122. package/routes/create-comment.js +25 -0
  123. package/routes/download.js +29 -0
  124. package/routes/flag-comment.js +21 -0
  125. package/routes/index.js +40 -0
  126. package/routes/login-fields.js +35 -0
  127. package/routes/login.js +95 -0
  128. package/routes/logout.js +19 -0
  129. package/routes/profile.js +183 -0
  130. package/routes/regions.js +22 -0
  131. package/service.js +483 -0
  132. package/utils/call-hooks-for.js +28 -0
  133. package/utils/context-cookie.js +30 -0
  134. package/utils/create-client.js +48 -0
  135. package/utils/create-identity-builder.js +33 -0
  136. package/utils/get-as-array.js +6 -0
  137. package/utils/json-error-handler.js +41 -0
  138. package/utils/token-cookie.js +30 -0
@@ -0,0 +1,40 @@
1
+ const { Router } = require('express');
2
+ const { json } = require('body-parser');
3
+ const jsonErrorHandler = require('../utils/json-error-handler');
4
+ const access = require('./access');
5
+ const authenticate = require('./authenticate');
6
+ const changeEmailConfirm = require('./change-email-confirm');
7
+ const changeEmailInit = require('./change-email-init');
8
+ const commentCount = require('./comment-count');
9
+ const comments = require('./comments');
10
+ const countries = require('./countries');
11
+ const createComment = require('./create-comment');
12
+ const download = require('./download');
13
+ const flagComment = require('./flag-comment');
14
+ const login = require('./login');
15
+ const loginFields = require('./login-fields');
16
+ const logout = require('./logout');
17
+ const profile = require('./profile');
18
+ const regions = require('./regions');
19
+
20
+ const router = Router();
21
+
22
+ router.use(json());
23
+ router.get('/comment-count/:identifier', commentCount);
24
+ router.get('/comments/:identifier', comments);
25
+ router.get('/countries', countries);
26
+ router.get('/regions', regions);
27
+ router.post('/access', access);
28
+ router.post('/authenticate', authenticate);
29
+ router.post('/change-email/confirm', changeEmailConfirm);
30
+ router.post('/change-email/initiate', changeEmailInit);
31
+ router.post('/comment', createComment);
32
+ router.post('/comment/flag/:id', flagComment);
33
+ router.post('/download', download);
34
+ router.post('/login-fields', loginFields);
35
+ router.post('/login', login);
36
+ router.post('/logout', logout);
37
+ router.post('/profile', profile);
38
+ router.use(jsonErrorHandler());
39
+
40
+ module.exports = router;
@@ -0,0 +1,35 @@
1
+ const gql = require('graphql-tag');
2
+ const { asyncRoute } = require('@mindful-web/utils');
3
+
4
+ const { isArray } = Array;
5
+
6
+ const mutation = gql`
7
+ mutation SetPreLoginFields($input: SetAppUserUnverifiedDataMutationInput!) {
8
+ setAppUserUnverifiedData(input: $input) {
9
+ id
10
+ }
11
+ }
12
+ `;
13
+
14
+ module.exports = asyncRoute(async (req, res) => {
15
+ /** @type {import('../middleware').IdentityXRequest} */
16
+ const { identityX, body } = req;
17
+ const {
18
+ email,
19
+ values,
20
+ } = body;
21
+
22
+ const regionalConsentAnswers = isArray(values.regionalConsentAnswers)
23
+ ? values.regionalConsentAnswers
24
+ : [];
25
+
26
+ const input = {
27
+ ...values,
28
+ regionalConsentAnswers: regionalConsentAnswers
29
+ .map((answer) => ({ policyId: answer.id, given: answer.given })),
30
+ email,
31
+ };
32
+ await identityX.client.mutate({ mutation, variables: { input } });
33
+ const entity = await identityX.generateEntityId();
34
+ return res.json({ ok: true, entity });
35
+ });
@@ -0,0 +1,95 @@
1
+ const gql = require('graphql-tag');
2
+ const { asyncRoute } = require('@mindful-web/utils');
3
+
4
+ const mutation = gql`
5
+ mutation SetPreLoginFields($input: SetAppUserUnverifiedDataMutationInput!) {
6
+ setAppUserUnverifiedData(input: $input) {
7
+ id
8
+ }
9
+ }
10
+ `;
11
+
12
+ const forceProfileReVerificationUser = gql`
13
+ mutation ForceRevalidateAppUser($input: ForceProfileReVerificationAppUserMutationInput!) {
14
+ forceProfileReVerificationAppUser(input: $input) {
15
+ id
16
+ forceProfileReVerification
17
+ }
18
+ }
19
+ `;
20
+
21
+ module.exports = asyncRoute(async (req, res) => {
22
+ /** @type {import('../middleware').IdentityXRequest} */
23
+ const { identityX, body } = req;
24
+ const {
25
+ authUrl,
26
+ email,
27
+ source,
28
+ redirectTo,
29
+ additionalEventData = {},
30
+ } = body;
31
+ let appUser = await identityX.loadAppUserByEmail(email);
32
+
33
+ // Check for required creation fields
34
+ const required = identityX.config.getRequiredCreateFields();
35
+ if (required.length) {
36
+ // If we don't have a user, or the user is missing some of the required fields
37
+ if (!appUser || (appUser && !required.every((key) => appUser[key]))) {
38
+ additionalEventData.createdNewUser = true;
39
+ // And they weren't presented in the login request, require them.
40
+ if (!required.every((key) => body[key])) {
41
+ return res.status(400).json({ ok: false, requiresUserInput: true });
42
+ }
43
+ }
44
+ }
45
+
46
+ if (!appUser || (appUser && !required.every((key) => appUser[key]))) {
47
+ // Create the user.
48
+ appUser = await identityX.createAppUser({ email });
49
+ additionalEventData.createdNewUser = true;
50
+ }
51
+
52
+ if (additionalEventData.forceProfileReVerification) {
53
+ const { id } = appUser;
54
+ await identityX.client.mutate({
55
+ mutation: forceProfileReVerificationUser,
56
+ variables: { input: { id } },
57
+ });
58
+ }
59
+
60
+ // Set the fields as unverified app data and continue login.
61
+ if (required.length && additionalEventData.createdNewUser) {
62
+ const regionalConsentAnswers = Array.isArray(body.regionalConsentAnswers)
63
+ ? body.regionalConsentAnswers
64
+ : [];
65
+ const input = {
66
+ ...(required.reduce((obj, key) => ({ ...obj, [key]: body[key] }), {})),
67
+ regionalConsentAnswers: regionalConsentAnswers
68
+ .map((answer) => ({ policyId: answer.id, given: answer.given })),
69
+ email,
70
+ };
71
+ await identityX.client.mutate({ mutation, variables: { input } });
72
+ }
73
+
74
+ // Refresh the user for verification/new field state
75
+ if (additionalEventData.forceProfileReVerification || additionalEventData.createdNewUser) {
76
+ appUser = await identityX.loadAppUserByEmail(email);
77
+ }
78
+ const entity = await identityX.generateEntityId({ userId: appUser.id });
79
+
80
+ // Send login link.
81
+ await identityX.sendLoginLink({
82
+ appUser,
83
+ authUrl,
84
+ source,
85
+ redirectTo,
86
+ additionalEventData,
87
+ });
88
+ const returnedAppUser = { id: appUser.id, email: appUser.email };
89
+ return res.json({
90
+ ok: true,
91
+ additionalEventData,
92
+ appUser: returnedAppUser,
93
+ entity,
94
+ });
95
+ });
@@ -0,0 +1,19 @@
1
+ const { asyncRoute } = require('@mindful-web/utils');
2
+ const contextCookie = require('../utils/context-cookie');
3
+ const tokenCookie = require('../utils/token-cookie');
4
+ const callHooksFor = require('../utils/call-hooks-for');
5
+
6
+ module.exports = asyncRoute(async (req, res) => {
7
+ /** @type {import('../middleware').IdentityXRequest} */
8
+ const { identityX } = req;
9
+ contextCookie.removeFrom(res);
10
+ const token = tokenCookie.getFrom(req);
11
+ if (!token) {
12
+ await callHooksFor(identityX, 'onLogout', { req, res });
13
+ } else {
14
+ await identityX.logoutAppUser({ token });
15
+ }
16
+ res.json({
17
+ ok: true,
18
+ });
19
+ });
@@ -0,0 +1,183 @@
1
+ const gql = require('graphql-tag');
2
+ const { asyncRoute } = require('@mindful-web/utils');
3
+ const { getAsArray, getAsObject } = require('@mindful-web/object-path');
4
+ const userFragment = require('../api/fragments/active-user');
5
+ const callHooksFor = require('../utils/call-hooks-for');
6
+
7
+ const mutation = gql`
8
+ mutation UpdateUserProfile($input: UpdateOwnAppUserMutationInput!) {
9
+ updateOwnAppUser(input: $input) {
10
+ ...ActiveUserFragment
11
+ }
12
+ }
13
+
14
+ ${userFragment}
15
+ `;
16
+
17
+ const consentAnswers = gql`
18
+ mutation SetAppUserRegionalConsent($input: SetAppUserRegionalConsentMutationInput!) {
19
+ setAppUserRegionalConsent(input: $input) {
20
+ id
21
+ }
22
+ }
23
+ `;
24
+
25
+ const customBooleanFieldsMutation = gql`
26
+ mutation SetAppUserCustomBooleanFields($input: UpdateOwnAppUserCustomBooleanAnswersMutationInput!) {
27
+ updateOwnAppUserCustomBooleanAnswers(input: $input) {
28
+ id
29
+ }
30
+ }
31
+ `;
32
+
33
+ const customSelectFieldsMutation = gql`
34
+ mutation SetAppUserCustomSelectFields($input: UpdateOwnAppUserCustomSelectAnswersMutationInput!) {
35
+ updateOwnAppUserCustomSelectAnswers(input: $input) {
36
+ id
37
+ }
38
+ }
39
+ `;
40
+
41
+ const customTextFieldsMutation = gql`
42
+ mutation SetAppUserCustomTextFields($input: UpdateOwnAppUserCustomTextAnswersMutationInput!) {
43
+ updateOwnAppUserCustomTextAnswers(input: $input) {
44
+ id
45
+ }
46
+ }
47
+ `;
48
+
49
+ module.exports = asyncRoute(async (req, res) => {
50
+ /** @type {import('../middleware').IdentityXRequest} */
51
+ const { identityX, body } = req;
52
+ const {
53
+ givenName,
54
+ familyName,
55
+ organization,
56
+ organizationTitle,
57
+ countryCode,
58
+ regionCode,
59
+ postalCode,
60
+ city,
61
+ street,
62
+ addressExtra,
63
+ mobileNumber,
64
+ phoneNumber,
65
+ receiveEmail,
66
+ regionalConsentAnswers,
67
+ customBooleanFieldAnswers,
68
+ customSelectFieldAnswers,
69
+ customTextFieldAnswers,
70
+ additionalEventData = {},
71
+ } = body;
72
+ const input = {
73
+ givenName,
74
+ familyName,
75
+ organization,
76
+ organizationTitle,
77
+ countryCode,
78
+ regionCode,
79
+ postalCode,
80
+ city,
81
+ street,
82
+ addressExtra,
83
+ mobileNumber,
84
+ phoneNumber,
85
+ receiveEmail,
86
+ };
87
+
88
+ // get identity-x.forms custom-select question ids to append to ActiveCustomQuestionIds
89
+ const customFormFieldIds = Object.values(getAsObject(identityX, 'config.options.forms')).reduce((questions, form) => {
90
+ // loop over the fieldRows
91
+ getAsArray(form, 'fieldRows').forEach((row) => row.forEach((question) => {
92
+ // push the custom-select form being used with those forms to acceptable profile inputs
93
+ // @todo figure out if other types should also make it... custom-boolean specifically.
94
+ if (question.type === 'custom-select') questions.push(question.id);
95
+ }));
96
+ return questions;
97
+ }, []);
98
+
99
+ const customFieldIds = [
100
+ ...new Set([
101
+ ...getAsArray(identityX, 'config.options.activeCustomFieldIds'),
102
+ ...customFormFieldIds,
103
+ ]),
104
+ ];
105
+
106
+ const answers = regionalConsentAnswers
107
+ .map((answer) => ({ policyId: answer.id, given: answer.given }));
108
+
109
+ if (answers.length) {
110
+ await identityX.client.mutate({ mutation: consentAnswers, variables: { input: { answers } } });
111
+ }
112
+
113
+ if (customBooleanFieldAnswers.length) {
114
+ // only update custom questions when there some :)
115
+ const customBooleanFieldsInput = customBooleanFieldAnswers.map((fieldAnswer) => ({
116
+ fieldId: fieldAnswer.field.id,
117
+ // can either be true, false or null. convert null to false.
118
+ // the form submit is effectively answers the question.
119
+ value: Boolean(fieldAnswer.answer),
120
+ })).filter(
121
+ customFieldIds.length > 0
122
+ ? ({ fieldId }) => customFieldIds.includes(fieldId)
123
+ : () => true,
124
+ );
125
+ await identityX.client.mutate({
126
+ mutation: customBooleanFieldsMutation,
127
+ variables: { input: { answers: customBooleanFieldsInput } },
128
+ });
129
+ }
130
+
131
+ if (customSelectFieldAnswers.length) {
132
+ // only update custom questions when there some :)
133
+ const customSelectFieldsInput = customSelectFieldAnswers.map((fieldAnswer) => ({
134
+ fieldId: fieldAnswer.field.id,
135
+ optionIds: fieldAnswer.answers.map(({ id }) => id),
136
+ writeInValues: fieldAnswer.answers.reduce((arr, { id, writeInValue }) => ([
137
+ ...arr,
138
+ ...(writeInValue ? [{ optionId: id, value: writeInValue }] : []),
139
+ ]), []),
140
+ })).filter(
141
+ customFieldIds.length > 0
142
+ ? ({ fieldId }) => customFieldIds.includes(fieldId)
143
+ : () => true,
144
+ );
145
+ await identityX.client.mutate({
146
+ mutation: customSelectFieldsMutation,
147
+ variables: { input: { answers: customSelectFieldsInput } },
148
+ });
149
+ }
150
+
151
+ if (customTextFieldAnswers.length) {
152
+ // only update custom questions when there some :)
153
+ const customTextFieldsInput = customTextFieldAnswers.map((fieldAnswer) => ({
154
+ fieldId: fieldAnswer.field.id,
155
+ // the form submit is effectively answers the question.
156
+ value: fieldAnswer.value,
157
+ })).filter(
158
+ customFieldIds.length > 0
159
+ ? ({ fieldId }) => customFieldIds.includes(fieldId)
160
+ : () => true,
161
+ );
162
+ await identityX.client.mutate({
163
+ mutation: customTextFieldsMutation,
164
+ variables: { input: { answers: customTextFieldsInput } },
165
+ });
166
+ }
167
+
168
+ const { data } = await identityX.client.mutate({ mutation, variables: { input } });
169
+ const { updateOwnAppUser: user } = data;
170
+ await callHooksFor(identityX, 'onUserProfileUpdate', {
171
+ additionalEventData,
172
+ ...(additionalEventData || {}),
173
+ req,
174
+ user,
175
+ });
176
+ const entity = await identityX.generateEntityId({ userId: user.id });
177
+ res.json({
178
+ ok: true,
179
+ user,
180
+ additionalEventData,
181
+ entity,
182
+ });
183
+ });
@@ -0,0 +1,22 @@
1
+ const gql = require('graphql-tag');
2
+ const { asyncRoute } = require('@mindful-web/utils');
3
+
4
+ const localeRegions = gql`
5
+ query localeRegions {
6
+ localeRegions {
7
+ id
8
+ code
9
+ name
10
+ country {
11
+ id
12
+ }
13
+ }
14
+ }
15
+ `;
16
+
17
+ module.exports = asyncRoute(async (req, res) => {
18
+ /** @type {import('../middleware').IdentityXRequest} */
19
+ const { identityX } = req;
20
+ const { data } = await identityX.client.query({ query: localeRegions });
21
+ res.json(data.localeRegions);
22
+ });