@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
package/config.js ADDED
@@ -0,0 +1,161 @@
1
+ const { get, getAsArray, getAsObject } = require('@mindful-web/object-path');
2
+ const { isFunction: isFn } = require('@mindful-web/utils');
3
+ const validHooks = require('./hooks');
4
+
5
+ const { log } = console;
6
+
7
+ class IdentityXConfiguration {
8
+ /**
9
+ *
10
+ * @param {object} options
11
+ * @param {string} options.appId The application ID to use.
12
+ * @param {string} [options.apiToken] An API token to use. Only required when doing write ops.
13
+ * @param {string[]} [options.requiredServerFields] Required fields, server enforced.
14
+ * @param {string[]} [options.requiredClientFields] Required fields, client-side only.
15
+ * @param {string[]} [options.requiredCreateFields] Required fields, when creating a new user.
16
+ * @param {string[]} [options.activeCustomFieldIds] Limit displayed custom fields, if present.
17
+ * @param {string[]} [options.hiddenFields] The fields to hide from the profile.
18
+ * @param {function} [options.onHookError]
19
+ * @param {object} options.rest
20
+ */
21
+ constructor({
22
+ appId,
23
+ apiToken,
24
+ requiredServerFields = [],
25
+ requiredClientFields = [],
26
+ requiredCreateFields = [],
27
+ activeCustomFieldIds = [],
28
+ additionalCustomFieldIds = [],
29
+ defaultFieldLabels = {},
30
+ hiddenFields = ['city', 'street', 'addressExtra', 'phoneNumber', 'mobileNumber'],
31
+ defaultCountryCode,
32
+ booleanQuestionsLabel,
33
+ gtmUserFields = {},
34
+ onHookError,
35
+ ...rest
36
+ } = {}) {
37
+ if (!appId) throw new Error('Unable to configure IdentityX: no Application ID was provided.');
38
+ this.appId = appId;
39
+ this.apiToken = apiToken;
40
+ this.options = {
41
+ enableChangeEmail: false,
42
+ requiredServerFields,
43
+ requiredClientFields,
44
+ requiredCreateFields,
45
+ activeCustomFieldIds,
46
+ additionalCustomFieldIds,
47
+ defaultFieldLabels,
48
+ hiddenFields,
49
+ defaultCountryCode,
50
+ booleanQuestionsLabel,
51
+ gtmUserFields,
52
+ onHookError: (e) => {
53
+ if (process.env.NODE_ENV === 'development') {
54
+ log('ERROR IN IDENTITY-X HOOK', e);
55
+ if (e.networkError) log('Network Error', get(e, 'networkError.result.errors.0'));
56
+ }
57
+ if (isFn(onHookError)) onHookError(e);
58
+ },
59
+ ...rest,
60
+ };
61
+
62
+ this.endpointTypes = [
63
+ 'authenticate',
64
+ 'changeEmail',
65
+ 'login',
66
+ 'logout',
67
+ 'register',
68
+ 'profile',
69
+ ];
70
+ this.hooks = validHooks.reduce((o, name) => ({ ...o, [name]: [] }), {});
71
+ }
72
+
73
+ /**
74
+ * Adds a function to the hook queue.
75
+ *
76
+ * @param {object} params
77
+ * @param {string} params.string The hook name to register the function with.
78
+ * @param {function} params.fn The function to call. Can be async/promise.
79
+ * @param {boolean} [params.shouldAwait=false] Whether the function should be awaited.
80
+ */
81
+ addHook({ name, fn, shouldAwait = false } = {}) {
82
+ if (!validHooks.includes(name)) throw new Error(`No hook found for '${name}'`);
83
+ if (!isFn(fn)) throw new Error('The hook `fn` must be a function.');
84
+ this.hooks[name].push({ fn, shouldAwait });
85
+ return this;
86
+ }
87
+
88
+ commentsEnabled() {
89
+ return this.get('comments.enabled', true);
90
+ }
91
+
92
+ getAppId() {
93
+ return this.appId;
94
+ }
95
+
96
+ getApiToken() {
97
+ return this.apiToken;
98
+ }
99
+
100
+ getEndpointFor(type) {
101
+ return this.get(`endpoints.${type}`, `/user/${type}`);
102
+ }
103
+
104
+ getEndpoints() {
105
+ return this.endpointTypes.reduce((o, type) => {
106
+ const endpoint = this.getEndpointFor(type);
107
+ return { ...o, [type]: endpoint };
108
+ }, {});
109
+ }
110
+
111
+ getRequiredServerFields() {
112
+ return this.getAsArray('requiredServerFields');
113
+ }
114
+
115
+ getGTMUserData(user) {
116
+ if (!user) return {};
117
+ const questions = this.getAsObject('gtmUserFields');
118
+ const userData = { id: user.id };
119
+ Object.entries(questions).forEach(([key, value]) => {
120
+ const select = getAsArray(user, 'customSelectFieldAnswers').find(({ id }) => id === value);
121
+ if (select && select.answers.length) userData[key] = select.answers.map(({ label }) => label).join('|');
122
+ const boolean = getAsArray(user, 'customBooleanFieldAnswers').find(({ id }) => id === value);
123
+ if (boolean && boolean.hasAnswered) userData[key] = boolean.answer;
124
+ });
125
+ return userData;
126
+ }
127
+
128
+ getRequiredClientFields() {
129
+ return this.getAsArray('requiredClientFields');
130
+ }
131
+
132
+ getRequiredCreateFields() {
133
+ return this.getAsArray('requiredCreateFields');
134
+ }
135
+
136
+ getHiddenFields() {
137
+ return this.getAsArray('hiddenFields');
138
+ }
139
+
140
+ getActiveCustomFieldIds() {
141
+ return this.getAsArray('activeCustomFieldIds');
142
+ }
143
+
144
+ getAdditionalCustomFieldIds() {
145
+ return this.getAsArray('additionalCustomFieldIds');
146
+ }
147
+
148
+ get(path, def) {
149
+ return get(this.options, path, def);
150
+ }
151
+
152
+ getAsArray(path) {
153
+ return getAsArray(this.options, path);
154
+ }
155
+
156
+ getAsObject(path) {
157
+ return getAsObject(this.options, path);
158
+ }
159
+ }
160
+
161
+ module.exports = IdentityXConfiguration;
package/hooks.js ADDED
@@ -0,0 +1,20 @@
1
+ /**
2
+ * @typedef {(
3
+ * "onAuthenticationSuccess"|
4
+ * "onChangeEmailLinkSent"|
5
+ * "onChangeEmailSuccess"|
6
+ * "onLoadActiveContext"|
7
+ * "onLoginLinkSent"|
8
+ * "onLogout"|
9
+ * "onUserProfileUpdate"
10
+ * )} HookTypeEnum
11
+ */
12
+ module.exports = [
13
+ 'onAuthenticationSuccess',
14
+ 'onChangeEmailLinkSent',
15
+ 'onChangeEmailSuccess',
16
+ 'onLoadActiveContext',
17
+ 'onLoginLinkSent',
18
+ 'onLogout',
19
+ 'onUserProfileUpdate',
20
+ ];
package/index.js ADDED
@@ -0,0 +1,30 @@
1
+ const routes = require('./routes');
2
+ const middleware = require('./middleware');
3
+
4
+ /**
5
+ *
6
+ * @param {object} app The express app instance
7
+ * @param {IdentityXConfiguration} config The IdentityX config
8
+ */
9
+ module.exports = (app, config, {
10
+ templates = {},
11
+ } = {}) => {
12
+ app.use(middleware(config));
13
+ app.use('/__idx', routes);
14
+
15
+ config.endpointTypes.forEach((type) => {
16
+ const endpoint = config.getEndpointFor(type);
17
+ const template = templates[type];
18
+ if (!template) return;
19
+
20
+ app.get(endpoint, (req, res) => {
21
+ const { token } = req.identityX;
22
+ // Redirect to profile if a user token is already present (refresh after link click)
23
+ if (type === 'authenticate' && token) {
24
+ const redirectTo = req.query.redirectTo || config.getEndpointFor('profile');
25
+ res.redirect(302, redirectTo);
26
+ }
27
+ res.marko(template);
28
+ });
29
+ });
30
+ };
package/marko.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "taglib-id": "@mindful-web/marko-web-identity-x",
3
+ "taglib-imports": [
4
+ "./components/marko.json"
5
+ ]
6
+ }
@@ -0,0 +1,31 @@
1
+ const { get, getAsObject } = require('@mindful-web/object-path');
2
+
3
+ const cookieNamePrefix = '__idx_form';
4
+ // default is one day
5
+ const maxAge = process.env.IDX_CONTENT_ACCESS_MAXAGE
6
+ ? Number(process.env.IDX_CONTENT_ACCESS_MAXAGE)
7
+ : (24 * 60 * 60 * 1000);
8
+
9
+ const contentAccessState = ({ res, content }) => {
10
+ // Handle setting of contentAccessState Object
11
+ const { surveyType, surveyId } = getAsObject(content, 'gating');
12
+ const cookieName = `${cookieNamePrefix}_${surveyId}_${content.id}`;
13
+
14
+ const setFormDisplay = ({ req }) => {
15
+ res.locals.contentAccessState.displayForm = !get(req, `cookies.${cookieName}`);
16
+ };
17
+ res.locals.contentAccessState = {
18
+ displayForm: false,
19
+ setFormDisplay,
20
+ };
21
+
22
+ if (surveyType === 'idx') {
23
+ const cookie = { name: cookieName, maxAge };
24
+ res.locals.contentAccessState.formId = surveyId;
25
+ res.locals.contentAccessState.cookie = cookie;
26
+ }
27
+ };
28
+
29
+ module.exports = {
30
+ contentAccessState,
31
+ };
@@ -0,0 +1,27 @@
1
+ const { get } = require('@mindful-web/object-path');
2
+
3
+ const cookieNamePrefix = '__idx_form';
4
+ const maxAge = process.env.IDX_CONTENT_DOWNLOAD_MAXAGE
5
+ ? Number(process.env.IDX_CONTENT_DOWNLOAD_MAXAGE)
6
+ : (24 * 60 * 60 * 1000);
7
+
8
+ const contentDownloadState = ({ res, content }) => {
9
+ // Handle setting of contentDownloadState Object
10
+ const surveyId = 'content-download';
11
+ const cookieName = `${cookieNamePrefix}_${surveyId}_${content.id}`;
12
+
13
+ const setFormDisplay = ({ req }) => {
14
+ res.locals.contentDownloadState.displayForm = !get(req, `cookies.${cookieName}`);
15
+ };
16
+ res.locals.contentDownloadState = {
17
+ displayForm: false,
18
+ setFormDisplay,
19
+ };
20
+ const cookie = { name: cookieName, maxAge };
21
+ res.locals.contentDownloadState.formId = surveyId;
22
+ res.locals.contentDownloadState.cookie = cookie;
23
+ };
24
+
25
+ module.exports = {
26
+ contentDownloadState,
27
+ };
package/middleware.js ADDED
@@ -0,0 +1,32 @@
1
+ const { asyncRoute } = require('@mindful-web/utils');
2
+ const IdentityX = require('./service');
3
+
4
+ /**
5
+ * An Express middleware that injects the IdentityX service.
6
+ *
7
+ * @param {IdentityXConfiguration} config The IdentityX config object
8
+ * @returns {function} The middleware function
9
+ *
10
+ * @typedef IdentityXRequest
11
+ * @prop {IdentityX} identityX The IdentityX service instance
12
+ */
13
+ module.exports = (config) => asyncRoute(async (req, res, next) => {
14
+ const service = new IdentityX({ req, res, config });
15
+ req.identityX = service;
16
+ res.locals.identityX = service;
17
+
18
+ const cookie = service.getIdentity(res);
19
+
20
+ // Don't overwrite an existing cookie
21
+ if (cookie) return next();
22
+
23
+ // Set cookie for logged in users
24
+ if (service.token) {
25
+ const { user } = await service.loadActiveContext();
26
+ if (user && user.id) {
27
+ service.setIdentityCookie(user.id);
28
+ }
29
+ }
30
+
31
+ return next();
32
+ });
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@mindful-web/marko-web-identity-x",
3
+ "version": "1.0.0",
4
+ "description": "Marko Wrapper for IdentityX",
5
+ "repository": "https://github.com/parameter1/mindful-web/tree/main/packages/marko-web-identity-x",
6
+ "author": "Josh Worden <josh@parameter1.com>",
7
+ "license": "MIT",
8
+ "scripts": {
9
+ "lint:fix": "yarn lint --fix",
10
+ "lint": "eslint --ext .js --ext .vue --max-warnings 5 ./",
11
+ "compile": "mindful-web-marko-compile compile",
12
+ "prepublish": "yarn compile --silent",
13
+ "test": "yarn compile --no-clean && yarn lint"
14
+ },
15
+ "dependencies": {
16
+ "@mindful-web/marko-web-gtm": "^1.0.0",
17
+ "@mindful-web/object-path": "^1.0.0",
18
+ "@mindful-web/utils": "^1.0.0",
19
+ "apollo-cache-inmemory": "^1.6.6",
20
+ "apollo-client": "^2.6.10",
21
+ "apollo-link-context": "^1.0.20",
22
+ "apollo-link-http": "^1.5.17",
23
+ "body-parser": "^1.20.1",
24
+ "cookie": "0.3.1",
25
+ "dayjs": "^1.11.7",
26
+ "express": "^4.18.2",
27
+ "graphql-tag": "^2.12.6",
28
+ "jsonwebtoken": "^8.5.1",
29
+ "node-fetch": "^2.6.9",
30
+ "object-path": "^0.11.8"
31
+ },
32
+ "peerDependencies": {
33
+ "@mindful-web/marko-core": "^0.0.0",
34
+ "@mindful-web/marko-web": "^0.0.0"
35
+ },
36
+ "publishConfig": {
37
+ "access": "public"
38
+ },
39
+ "gitHead": "0b77cab713eb5841202bb86c7119949866bc68b5"
40
+ }
@@ -0,0 +1,29 @@
1
+ const gql = require('graphql-tag');
2
+ const { asyncRoute } = require('@mindful-web/utils');
3
+
4
+ const mutation = gql`
5
+ mutation StoreContentAccessSubmission($input: CreateContentAccessSubmissionMutationInput!) {
6
+ createContentAccessSubmission(input:$input) {
7
+ id
8
+ }
9
+ }
10
+ `;
11
+
12
+ module.exports = asyncRoute(async (req, res) => {
13
+ /** @type {import('../middleware').IdentityXRequest} */
14
+ const { body, apollo, identityX } = req;
15
+ const { contentId, payload, cookie } = body;
16
+ const input = {
17
+ contentId,
18
+ payload,
19
+ ipAddress: req.ip,
20
+ };
21
+ const { name: COOKIE_NAME, maxAge } = cookie;
22
+ await apollo.mutate({ mutation, variables: { input } });
23
+ res.cookie(COOKIE_NAME, true, { maxAge, httpOnly: false });
24
+ const entity = await identityX.generateEntityId();
25
+ res.json({
26
+ ok: true,
27
+ entity,
28
+ });
29
+ });
@@ -0,0 +1,58 @@
1
+ const gql = require('graphql-tag');
2
+ const { asyncRoute } = require('@mindful-web/utils');
3
+ const tokenCookie = require('../utils/token-cookie');
4
+ const contextCookie = require('../utils/context-cookie');
5
+ const callHooksFor = require('../utils/call-hooks-for');
6
+ const userFragment = require('../api/fragments/active-user');
7
+
8
+ const loginAppUser = gql`
9
+ mutation LoginAppUser($input: LoginAppUserMutationInput!) {
10
+ loginAppUser(input: $input) {
11
+ token {
12
+ id
13
+ value
14
+ }
15
+ user {
16
+ ...ActiveUserFragment
17
+ }
18
+ loginSource
19
+ }
20
+ }
21
+
22
+ ${userFragment}
23
+ `;
24
+
25
+ module.exports = asyncRoute(async (req, res) => {
26
+ /** @type {import('../middleware').IdentityXRequest} */
27
+ const { identityX, body } = req;
28
+ const { token, additionalEventData } = body;
29
+ if (!token) throw new Error('No login token was provided.');
30
+
31
+ const input = { token };
32
+ const variables = { input };
33
+ const { data = {} } = await identityX.client.mutate({ mutation: loginAppUser, variables });
34
+ const { token: authToken, user, loginSource } = data.loginAppUser;
35
+
36
+ // call authentication hooks
37
+ await callHooksFor(identityX, 'onAuthenticationSuccess', {
38
+ req,
39
+ res,
40
+ user,
41
+ authToken,
42
+ loginSource,
43
+ additionalEventData,
44
+ });
45
+ tokenCookie.setTo(res, authToken.value);
46
+ contextCookie.setTo(res, { loginSource });
47
+ identityX.setIdentityCookie(user.id);
48
+ const entity = await identityX.generateEntityId({ userId: user.id });
49
+ const refreshed = await identityX.findUserById(user.id);
50
+ res.json({
51
+ ok: true,
52
+ applicationId: identityX.config.getAppId(),
53
+ user: refreshed,
54
+ loginSource,
55
+ additionalEventData,
56
+ entity,
57
+ });
58
+ });
@@ -0,0 +1,60 @@
1
+ const gql = require('graphql-tag');
2
+ const jwt = require('jsonwebtoken');
3
+ const { get } = require('@mindful-web/object-path');
4
+ const { asyncRoute } = require('@mindful-web/utils');
5
+ const tokenCookie = require('../utils/token-cookie');
6
+ const contextCookie = require('../utils/context-cookie');
7
+ const callHooksFor = require('../utils/call-hooks-for');
8
+ const userFragment = require('../api/fragments/active-user');
9
+
10
+ const mutation = gql`
11
+ mutation ChangeEmailConfirm($input: ChangeAppUserEmailMutationInput!) {
12
+ changeAppUserEmail(input: $input) {
13
+ token {
14
+ id
15
+ value
16
+ }
17
+ user {
18
+ ...ActiveUserFragment
19
+ }
20
+ }
21
+ }
22
+
23
+ ${userFragment}
24
+ `;
25
+
26
+ module.exports = asyncRoute(async (req, res) => {
27
+ /** @type {import('../middleware').IdentityXRequest} */
28
+ const { identityX, body } = req;
29
+ const { token } = body;
30
+ const loginSource = 'change-email';
31
+ if (!token) throw new Error('No login token was provided.');
32
+
33
+ const input = { token };
34
+ const variables = { input };
35
+ const { data = {} } = await identityX.client.mutate({ mutation, variables });
36
+ const { token: authToken, user } = data.changeAppUserEmail;
37
+
38
+ const decoded = await jwt.decode(token);
39
+ await callHooksFor(identityX, 'onChangeEmailSuccess', {
40
+ user,
41
+ oldEmail: get(decoded, 'data.email'),
42
+ });
43
+
44
+ // call authentication hooks
45
+ await callHooksFor(identityX, 'onAuthenticationSuccess', {
46
+ req,
47
+ res,
48
+ user,
49
+ authToken,
50
+ loginSource,
51
+ });
52
+ tokenCookie.setTo(res, authToken.value);
53
+ contextCookie.setTo(res, { loginSource });
54
+ const entity = await identityX.generateEntityId({ userId: user.id });
55
+ res.json({
56
+ ok: true,
57
+ user,
58
+ entity,
59
+ });
60
+ });
@@ -0,0 +1,16 @@
1
+ const { asyncRoute } = require('@mindful-web/utils');
2
+
3
+ module.exports = asyncRoute(async (req, res) => {
4
+ /** @type {import('../middleware').IdentityXRequest} */
5
+ const { identityX, body } = req;
6
+ const { email } = body;
7
+ const entity = await identityX.generateEntityId();
8
+
9
+ // Send login link.
10
+ await identityX.sendChangeEmailLink({ email });
11
+ await identityX.logoutAppUser();
12
+ return res.json({
13
+ ok: true,
14
+ entity,
15
+ });
16
+ });
@@ -0,0 +1,19 @@
1
+ const gql = require('graphql-tag');
2
+ const { asyncRoute } = require('@mindful-web/utils');
3
+
4
+ const query = gql`
5
+ query CountComments($identifier: String!) {
6
+ commentsForStream(input: { identifier: $identifier }) {
7
+ totalCount
8
+ }
9
+ }
10
+ `;
11
+
12
+ module.exports = asyncRoute(async (req, res) => {
13
+ /** @type {import('../middleware').IdentityXRequest} */
14
+ const { identityX } = req;
15
+ const { identifier } = req.params;
16
+ const variables = { identifier };
17
+ const { data } = await identityX.client.query({ query, variables });
18
+ res.json(data.commentsForStream);
19
+ });
@@ -0,0 +1,43 @@
1
+ const gql = require('graphql-tag');
2
+ const { asyncRoute } = require('@mindful-web/utils');
3
+
4
+ const query = gql`
5
+ query ListComments($input: CommentsForStreamQueryInput!) {
6
+ commentsForStream(input: $input) {
7
+ totalCount
8
+ stream {
9
+ id
10
+ archived
11
+ }
12
+ pageInfo {
13
+ endCursor
14
+ hasNextPage
15
+ }
16
+ edges {
17
+ node {
18
+ id
19
+ body
20
+ approved
21
+ flagged
22
+ createdAt
23
+ user {
24
+ id
25
+ displayName
26
+ }
27
+ }
28
+ }
29
+ }
30
+ }
31
+ `;
32
+
33
+ module.exports = asyncRoute(async (req, res) => {
34
+ const { identifier } = req.params;
35
+ const { after } = req.query;
36
+ /** @type {import('../middleware').IdentityXRequest} */
37
+ const { identityX } = req;
38
+ const limit = after ? 20 : 5;
39
+ const pagination = { limit, after };
40
+ const variables = { input: { identifier, pagination } };
41
+ const { data } = await identityX.client.query({ query, variables });
42
+ res.json(data.commentsForStream);
43
+ });
@@ -0,0 +1,19 @@
1
+ const gql = require('graphql-tag');
2
+ const { asyncRoute } = require('@mindful-web/utils');
3
+
4
+ const localeCountries = gql`
5
+ query LocaleCountries {
6
+ localeCountries {
7
+ id
8
+ name
9
+ flag
10
+ }
11
+ }
12
+ `;
13
+
14
+ module.exports = asyncRoute(async (req, res) => {
15
+ /** @type {import('../middleware').IdentityXRequest} */
16
+ const { identityX } = req;
17
+ const { data } = await identityX.client.query({ query: localeCountries });
18
+ res.json(data.localeCountries);
19
+ });
@@ -0,0 +1,25 @@
1
+ const gql = require('graphql-tag');
2
+ const { asyncRoute } = require('@mindful-web/utils');
3
+
4
+ const mutation = gql`
5
+ mutation CreateComment($input: CreateCommentMutationInput!) {
6
+ createComment(input: $input) {
7
+ id
8
+ }
9
+ }
10
+ `;
11
+
12
+ module.exports = asyncRoute(async (req, res) => {
13
+ /** @type {import('../middleware').IdentityXRequest} */
14
+ const { identityX } = req;
15
+ const { displayName, body, stream } = req.body;
16
+
17
+ const input = { displayName, body, stream };
18
+ const variables = { input };
19
+ await identityX.client.mutate({ mutation, variables });
20
+ const entity = await identityX.generateEntityId();
21
+ res.json({
22
+ ok: true,
23
+ entity,
24
+ });
25
+ });
@@ -0,0 +1,29 @@
1
+ const gql = require('graphql-tag');
2
+ const { asyncRoute } = require('@mindful-web/utils');
3
+
4
+ const mutation = gql`
5
+ mutation StoreContentDownloadSubmission($input: CreateContentDownloadSubmissionMutationInput!) {
6
+ createContentDownloadSubmission(input:$input) {
7
+ id
8
+ }
9
+ }
10
+ `;
11
+
12
+ module.exports = asyncRoute(async (req, res) => {
13
+ /** @type {import('../middleware').IdentityXRequest} */
14
+ const { body, apollo, identityX } = req;
15
+ const { contentId, payload, cookie } = body;
16
+ const input = {
17
+ contentId,
18
+ payload,
19
+ ipAddress: req.ip,
20
+ };
21
+ const { name: COOKIE_NAME, maxAge } = cookie;
22
+ await apollo.mutate({ mutation, variables: { input } });
23
+ res.cookie(COOKIE_NAME, true, { maxAge, httpOnly: false });
24
+ const entity = await identityX.generateEntityId();
25
+ res.json({
26
+ ok: true,
27
+ entity,
28
+ });
29
+ });
@@ -0,0 +1,21 @@
1
+ const gql = require('graphql-tag');
2
+ const { asyncRoute } = require('@mindful-web/utils');
3
+
4
+ const mutation = gql`
5
+ mutation FlagComment($id: String!) {
6
+ setCommentFlagged(input: { id: $id, value: true }) {
7
+ id
8
+ flagged
9
+ }
10
+ }
11
+ `;
12
+
13
+ module.exports = asyncRoute(async (req, res) => {
14
+ /** @type {import('../middleware').IdentityXRequest} */
15
+ const { identityX } = req;
16
+ const { id } = req.params;
17
+ const variables = { id };
18
+ await identityX.client.mutate({ mutation, variables });
19
+ const entity = await identityX.generateEntityId();
20
+ res.json({ ok: true, entity });
21
+ });