@mindful-web/marko-web-identity-x 1.96.3 → 1.105.1
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/CLAUDE.md +48 -1
- package/README.md +37 -0
- package/components/access.marko.js +2 -2
- package/components/comment-stream.marko.js +2 -2
- package/components/context.marko.js +2 -2
- package/components/form-access.marko.js +2 -2
- package/components/form-authenticate.marko.js +2 -2
- package/components/form-change-email.marko.js +2 -2
- package/components/form-login.marko.js +2 -2
- package/components/form-logout.marko.js +2 -2
- package/components/form-profile.marko.js +2 -2
- package/components/form-register.marko.js +2 -2
- package/components/google-init.marko.js +2 -2
- package/components/google-sign-in-button.marko.js +2 -2
- package/components/identify.marko.js +2 -2
- package/components/non-auth-identify.marko.js +2 -2
- package/components/progressive-profile-form.marko.js +2 -2
- package/components/subscribe.marko.js +2 -2
- package/integrations/identity-bridge-sync/index.js +77 -0
- package/package.json +2 -2
- package/routes/authenticate.js +3 -1
- package/test/integrations/identity-bridge-sync.spec.js +162 -0
- package/test/routes/authenticate.spec.js +67 -0
package/CLAUDE.md
CHANGED
|
@@ -5,7 +5,8 @@ Web sites. Owns the whole authenticated-user surface: login / register / authent
|
|
|
5
5
|
/ profile / logout, content access + download gating, comments, and the server-side IdentityX API
|
|
6
6
|
client. Add-ons layer on top: `marko-web-omeda-identity-x` (wraps this and adds Omeda hooks —
|
|
7
7
|
how most sites install it), `marko-web-auth0-identity-x`, `marko-web-identity-x-mailchimp`. Google
|
|
8
|
-
Sign-In (One-Tap + button) is **built in** here (see below).
|
|
8
|
+
Sign-In (One-Tap + button) is **built in** here (see below). The opt-in Identity Bridge sync
|
|
9
|
+
(`integrations/identity-bridge-sync`, see below) reports member events to Mindful Identity Bridge.
|
|
9
10
|
|
|
10
11
|
## Config model — the `IdentityXConfiguration` instance (`config.js`)
|
|
11
12
|
|
|
@@ -698,6 +699,52 @@ Submitted **answer values are never tracked** anywhere — `fieldKeys` and the m
|
|
|
698
699
|
still exclude `custom-boolean` — no longer for the renderer bug, but because the gate forces
|
|
699
700
|
required columns and a required checkbox can only submit `true` (see the boot-check table).
|
|
700
701
|
|
|
702
|
+
## Identity Bridge sync (`integrations/identity-bridge-sync`) — added 2026-10
|
|
703
|
+
|
|
704
|
+
Opt-in hook installer that reports member events to Mindful Identity Bridge's `POST /:tenantKey/sync`
|
|
705
|
+
(`apis/identity-bridge` in mindful-reporting, tracked in parameter1/mindful-reporting#304). Identity
|
|
706
|
+
Bridge decides which third-party providers act on each event (eMagazines today). **This package holds
|
|
707
|
+
no provider logic, config or keys** — it replaced mindful-web#363, which ran eMagazines from here.
|
|
708
|
+
|
|
709
|
+
```js
|
|
710
|
+
const installIdentityBridgeSync = require('@mindful-web/marko-web-identity-x/integrations/identity-bridge-sync');
|
|
711
|
+
installIdentityBridgeSync(idxConfig, { websiteId: process.env.SITE_ID });
|
|
712
|
+
```
|
|
713
|
+
|
|
714
|
+
| Hook | Event sent |
|
|
715
|
+
|---|---|
|
|
716
|
+
| `onAuthenticationSuccess` | `{ event: 'authenticated', websiteId, user, loginSource }` |
|
|
717
|
+
| `onUserProfileUpdate` | `{ event: 'profile-updated', websiteId, user }` |
|
|
718
|
+
| `onChangeEmailSuccess` | `{ event: 'email-changed', websiteId, user /* new email */, oldEmail }` |
|
|
719
|
+
|
|
720
|
+
`user` is `{ id, email, givenName, familyName, street, city, regionCode, postalCode }` from the
|
|
721
|
+
IdentityX app user.
|
|
722
|
+
|
|
723
|
+
- **Send values as they are.** The server normalizes `null`/`''`/whitespace to unset and lowercases
|
|
724
|
+
emails. Don't clean them here.
|
|
725
|
+
- **Don't filter here.** Every login is sent, `change-email` included (`change-email-confirm.js`
|
|
726
|
+
fires `onAuthenticationSuccess` right after `onChangeEmailSuccess`). The server skips those logins
|
|
727
|
+
and applies the website's `loginSources`. Adding a filter here would split one rule across two repos.
|
|
728
|
+
- **All three hooks use `shouldAwait: false`.** The server runs synchronously and returns 502 when a
|
|
729
|
+
provider call fails, and the client throws on any non-2xx. So failures reach `onHookError` without
|
|
730
|
+
blocking the IdentityX request.
|
|
731
|
+
- **The client comes from the request.** The hooks call `req.mindful.identityBridge.sync()`, which
|
|
732
|
+
the `@mindful-web/mindful` service middleware sets when given `identityBridge`
|
|
733
|
+
(`packages/mindful/identity-bridge.js`). Each hook throws a clear error when the client is missing.
|
|
734
|
+
⚠️ The site must install that middleware **before** its IdentityX routes:
|
|
735
|
+
|
|
736
|
+
```js
|
|
737
|
+
identityBridge: {
|
|
738
|
+
apiKey: process.env.MINDFUL_IDENTITY_BRIDGE_API_KEY,
|
|
739
|
+
tenantKey: 'allured', // the Mindful tenant key, NOT the site's TENANT_KEY (e.g. `indm_multi`)
|
|
740
|
+
},
|
|
741
|
+
```
|
|
742
|
+
|
|
743
|
+
- `websiteId` is the CMS website id (`SITE_ID`). The server returns 400 if that id doesn't belong to
|
|
744
|
+
the tenant. A website with no providers configured gets `{ data: [] }`.
|
|
745
|
+
- Specs: `test/integrations/identity-bridge-sync.spec.js` (hooks) and `packages/mindful/test/` (client
|
|
746
|
+
+ middleware).
|
|
747
|
+
|
|
701
748
|
## Fleet / rollout
|
|
702
749
|
|
|
703
750
|
Not tracked in-repo. The Google-auth architecture change + fleet rollout plan lives outside the repo
|
package/README.md
CHANGED
|
@@ -264,3 +264,40 @@ The Google account id (JWT `sub`) is persisted as a supplementary idx external i
|
|
|
264
264
|
`{ provider: 'google', tenant: 'oauth', type: 'account' }`; email stays the primary key.
|
|
265
265
|
Non-fatal linkage errors report through the config's `onHookError`.
|
|
266
266
|
| `identity-x-comment-stream-errored` | `{ label, message: '...', ...additionalEventData }`
|
|
267
|
+
|
|
268
|
+
## Identity Bridge sync
|
|
269
|
+
|
|
270
|
+
Opt-in, and **off until installed.** It reports logins, profile updates and email changes to Mindful
|
|
271
|
+
Identity Bridge, which passes them on to the third-party providers configured for the website (e.g.
|
|
272
|
+
eMagazines). Every decision is made server side. This package sends the events and nothing else.
|
|
273
|
+
|
|
274
|
+
1. Give the Mindful service middleware an Identity Bridge client. Install it **before** the IdentityX
|
|
275
|
+
routes, because the hooks read `req.mindful.identityBridge`:
|
|
276
|
+
|
|
277
|
+
```js
|
|
278
|
+
const mindfulService = require('@mindful-web/mindful/marko-web/middleware/service');
|
|
279
|
+
|
|
280
|
+
app.use(mindfulService({
|
|
281
|
+
namespace: '<tenant>/<site>',
|
|
282
|
+
identityBridge: {
|
|
283
|
+
apiKey: process.env.MINDFUL_IDENTITY_BRIDGE_API_KEY,
|
|
284
|
+
tenantKey: 'allured', // the Mindful tenant key, not the site's TENANT_KEY
|
|
285
|
+
},
|
|
286
|
+
}));
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
`identityBridge` also takes `timeout` (ms, default 15000) and `url` (default
|
|
290
|
+
`https://apis.internal.mindfulcms.com/identity-bridge`). Without it, `req.mindful.identityBridge`
|
|
291
|
+
is `null`.
|
|
292
|
+
|
|
293
|
+
2. Install the hooks on the IdentityX config:
|
|
294
|
+
|
|
295
|
+
```js
|
|
296
|
+
const installIdentityBridgeSync = require('@mindful-web/marko-web-identity-x/integrations/identity-bridge-sync');
|
|
297
|
+
|
|
298
|
+
installIdentityBridgeSync(idxConfig, { websiteId: process.env.SITE_ID });
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
The `onAuthenticationSuccess`, `onUserProfileUpdate` and `onChangeEmailSuccess` hooks are not
|
|
302
|
+
awaited. A failed sync, including a missing client on the request, is reported through the config's
|
|
303
|
+
`onHookError`.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"use strict";
|
|
3
3
|
|
|
4
4
|
var marko_template = module.exports = require("marko/dist/html").t(__filename),
|
|
5
|
-
marko_componentType = "/@mindful-web/marko-web-identity-x$1.
|
|
5
|
+
marko_componentType = "/@mindful-web/marko-web-identity-x$1.105.1/components/access.marko",
|
|
6
6
|
marko_component = require("./access.marko"),
|
|
7
7
|
marko_renderer = require("marko/dist/runtime/components/renderer"),
|
|
8
8
|
module_objectPath_module = require("@mindful-web/object-path"),
|
|
@@ -53,7 +53,7 @@ marko_template._ = marko_renderer(render, {
|
|
|
53
53
|
}, marko_component);
|
|
54
54
|
|
|
55
55
|
marko_template.meta = {
|
|
56
|
-
id: "/@mindful-web/marko-web-identity-x$1.
|
|
56
|
+
id: "/@mindful-web/marko-web-identity-x$1.105.1/components/access.marko",
|
|
57
57
|
component: "./access.marko",
|
|
58
58
|
tags: [
|
|
59
59
|
"@mindful-web/marko-core/components/resolve.marko"
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"use strict";
|
|
3
3
|
|
|
4
4
|
var marko_template = module.exports = require("marko/dist/html").t(__filename),
|
|
5
|
-
marko_componentType = "/@mindful-web/marko-web-identity-x$1.
|
|
5
|
+
marko_componentType = "/@mindful-web/marko-web-identity-x$1.105.1/components/comment-stream.marko",
|
|
6
6
|
marko_component = require("./comment-stream.marko"),
|
|
7
7
|
marko_renderer = require("marko/dist/runtime/components/renderer"),
|
|
8
8
|
module_objectPath_module = require("@mindful-web/object-path"),
|
|
@@ -91,7 +91,7 @@ marko_template._ = marko_renderer(render, {
|
|
|
91
91
|
}, marko_component);
|
|
92
92
|
|
|
93
93
|
marko_template.meta = {
|
|
94
|
-
id: "/@mindful-web/marko-web-identity-x$1.
|
|
94
|
+
id: "/@mindful-web/marko-web-identity-x$1.105.1/components/comment-stream.marko",
|
|
95
95
|
component: "./comment-stream.marko",
|
|
96
96
|
tags: [
|
|
97
97
|
"@mindful-web/marko-web/components/browser-component.marko",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"use strict";
|
|
3
3
|
|
|
4
4
|
var marko_template = module.exports = require("marko/dist/html").t(__filename),
|
|
5
|
-
marko_componentType = "/@mindful-web/marko-web-identity-x$1.
|
|
5
|
+
marko_componentType = "/@mindful-web/marko-web-identity-x$1.105.1/components/context.marko",
|
|
6
6
|
marko_component = require("./context.marko"),
|
|
7
7
|
marko_renderer = require("marko/dist/runtime/components/renderer"),
|
|
8
8
|
marko_dynamicTag = require("marko/dist/runtime/helpers/dynamic-tag"),
|
|
@@ -49,7 +49,7 @@ marko_template._ = marko_renderer(render, {
|
|
|
49
49
|
}, marko_component);
|
|
50
50
|
|
|
51
51
|
marko_template.meta = {
|
|
52
|
-
id: "/@mindful-web/marko-web-identity-x$1.
|
|
52
|
+
id: "/@mindful-web/marko-web-identity-x$1.105.1/components/context.marko",
|
|
53
53
|
component: "./context.marko",
|
|
54
54
|
tags: [
|
|
55
55
|
"@mindful-web/marko-core/components/resolve.marko"
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"use strict";
|
|
3
3
|
|
|
4
4
|
var marko_template = module.exports = require("marko/dist/html").t(__filename),
|
|
5
|
-
marko_componentType = "/@mindful-web/marko-web-identity-x$1.
|
|
5
|
+
marko_componentType = "/@mindful-web/marko-web-identity-x$1.105.1/components/form-access.marko",
|
|
6
6
|
marko_component = require("./form-access.marko"),
|
|
7
7
|
marko_renderer = require("marko/dist/runtime/components/renderer"),
|
|
8
8
|
module_defaultValue = require("@mindful-web/marko-core/utils/default-value"),
|
|
@@ -179,7 +179,7 @@ marko_template._ = marko_renderer(render, {
|
|
|
179
179
|
}, marko_component);
|
|
180
180
|
|
|
181
181
|
marko_template.meta = {
|
|
182
|
-
id: "/@mindful-web/marko-web-identity-x$1.
|
|
182
|
+
id: "/@mindful-web/marko-web-identity-x$1.105.1/components/form-access.marko",
|
|
183
183
|
component: "./form-access.marko",
|
|
184
184
|
tags: [
|
|
185
185
|
"@mindful-web/marko-web/components/browser-component.marko",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"use strict";
|
|
3
3
|
|
|
4
4
|
var marko_template = module.exports = require("marko/dist/html").t(__filename),
|
|
5
|
-
marko_componentType = "/@mindful-web/marko-web-identity-x$1.
|
|
5
|
+
marko_componentType = "/@mindful-web/marko-web-identity-x$1.105.1/components/form-authenticate.marko",
|
|
6
6
|
marko_component = require("./form-authenticate.marko"),
|
|
7
7
|
marko_renderer = require("marko/dist/runtime/components/renderer"),
|
|
8
8
|
module_objectPath_module = require("@mindful-web/object-path"),
|
|
@@ -81,7 +81,7 @@ marko_template._ = marko_renderer(render, {
|
|
|
81
81
|
}, marko_component);
|
|
82
82
|
|
|
83
83
|
marko_template.meta = {
|
|
84
|
-
id: "/@mindful-web/marko-web-identity-x$1.
|
|
84
|
+
id: "/@mindful-web/marko-web-identity-x$1.105.1/components/form-authenticate.marko",
|
|
85
85
|
component: "./form-authenticate.marko",
|
|
86
86
|
tags: [
|
|
87
87
|
"@mindful-web/marko-web/components/browser-component.marko",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"use strict";
|
|
3
3
|
|
|
4
4
|
var marko_template = module.exports = require("marko/dist/html").t(__filename),
|
|
5
|
-
marko_componentType = "/@mindful-web/marko-web-identity-x$1.
|
|
5
|
+
marko_componentType = "/@mindful-web/marko-web-identity-x$1.105.1/components/form-change-email.marko",
|
|
6
6
|
marko_component = require("./form-change-email.marko"),
|
|
7
7
|
marko_renderer = require("marko/dist/runtime/components/renderer"),
|
|
8
8
|
module_objectPath_module = require("@mindful-web/object-path"),
|
|
@@ -59,7 +59,7 @@ marko_template._ = marko_renderer(render, {
|
|
|
59
59
|
}, marko_component);
|
|
60
60
|
|
|
61
61
|
marko_template.meta = {
|
|
62
|
-
id: "/@mindful-web/marko-web-identity-x$1.
|
|
62
|
+
id: "/@mindful-web/marko-web-identity-x$1.105.1/components/form-change-email.marko",
|
|
63
63
|
component: "./form-change-email.marko",
|
|
64
64
|
tags: [
|
|
65
65
|
"@mindful-web/marko-web/components/browser-component.marko",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"use strict";
|
|
3
3
|
|
|
4
4
|
var marko_template = module.exports = require("marko/dist/html").t(__filename),
|
|
5
|
-
marko_componentType = "/@mindful-web/marko-web-identity-x$1.
|
|
5
|
+
marko_componentType = "/@mindful-web/marko-web-identity-x$1.105.1/components/form-login.marko",
|
|
6
6
|
marko_component = require("./form-login.marko"),
|
|
7
7
|
marko_renderer = require("marko/dist/runtime/components/renderer"),
|
|
8
8
|
module_objectPath_module = require("@mindful-web/object-path"),
|
|
@@ -92,7 +92,7 @@ marko_template._ = marko_renderer(render, {
|
|
|
92
92
|
}, marko_component);
|
|
93
93
|
|
|
94
94
|
marko_template.meta = {
|
|
95
|
-
id: "/@mindful-web/marko-web-identity-x$1.
|
|
95
|
+
id: "/@mindful-web/marko-web-identity-x$1.105.1/components/form-login.marko",
|
|
96
96
|
component: "./form-login.marko",
|
|
97
97
|
tags: [
|
|
98
98
|
"@mindful-web/marko-web/components/browser-component.marko",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"use strict";
|
|
3
3
|
|
|
4
4
|
var marko_template = module.exports = require("marko/dist/html").t(__filename),
|
|
5
|
-
marko_componentType = "/@mindful-web/marko-web-identity-x$1.
|
|
5
|
+
marko_componentType = "/@mindful-web/marko-web-identity-x$1.105.1/components/form-logout.marko",
|
|
6
6
|
marko_renderer = require("marko/dist/runtime/components/renderer"),
|
|
7
7
|
module_defaultValue = require("@mindful-web/marko-core/utils/default-value"),
|
|
8
8
|
defaultValue = module_defaultValue.default || module_defaultValue,
|
|
@@ -29,7 +29,7 @@ marko_template._ = marko_renderer(render, {
|
|
|
29
29
|
});
|
|
30
30
|
|
|
31
31
|
marko_template.meta = {
|
|
32
|
-
id: "/@mindful-web/marko-web-identity-x$1.
|
|
32
|
+
id: "/@mindful-web/marko-web-identity-x$1.105.1/components/form-logout.marko",
|
|
33
33
|
tags: [
|
|
34
34
|
"@mindful-web/marko-web/components/browser-component.marko"
|
|
35
35
|
]
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"use strict";
|
|
3
3
|
|
|
4
4
|
var marko_template = module.exports = require("marko/dist/html").t(__filename),
|
|
5
|
-
marko_componentType = "/@mindful-web/marko-web-identity-x$1.
|
|
5
|
+
marko_componentType = "/@mindful-web/marko-web-identity-x$1.105.1/components/form-profile.marko",
|
|
6
6
|
marko_component = require("./form-profile.marko"),
|
|
7
7
|
marko_renderer = require("marko/dist/runtime/components/renderer"),
|
|
8
8
|
module_objectPath_module = require("@mindful-web/object-path"),
|
|
@@ -84,7 +84,7 @@ marko_template._ = marko_renderer(render, {
|
|
|
84
84
|
}, marko_component);
|
|
85
85
|
|
|
86
86
|
marko_template.meta = {
|
|
87
|
-
id: "/@mindful-web/marko-web-identity-x$1.
|
|
87
|
+
id: "/@mindful-web/marko-web-identity-x$1.105.1/components/form-profile.marko",
|
|
88
88
|
component: "./form-profile.marko",
|
|
89
89
|
tags: [
|
|
90
90
|
"@mindful-web/marko-web/components/browser-component.marko",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"use strict";
|
|
3
3
|
|
|
4
4
|
var marko_template = module.exports = require("marko/dist/html").t(__filename),
|
|
5
|
-
marko_componentType = "/@mindful-web/marko-web-identity-x$1.
|
|
5
|
+
marko_componentType = "/@mindful-web/marko-web-identity-x$1.105.1/components/form-register.marko",
|
|
6
6
|
marko_renderer = require("marko/dist/runtime/components/renderer"),
|
|
7
7
|
marko_assign = require("marko/dist/runtime/helpers/assign"),
|
|
8
8
|
marko_web_identity_x_form_login_template = require("./form-login.marko"),
|
|
@@ -25,7 +25,7 @@ marko_template._ = marko_renderer(render, {
|
|
|
25
25
|
});
|
|
26
26
|
|
|
27
27
|
marko_template.meta = {
|
|
28
|
-
id: "/@mindful-web/marko-web-identity-x$1.
|
|
28
|
+
id: "/@mindful-web/marko-web-identity-x$1.105.1/components/form-register.marko",
|
|
29
29
|
tags: [
|
|
30
30
|
"./form-login.marko"
|
|
31
31
|
]
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"use strict";
|
|
3
3
|
|
|
4
4
|
var marko_template = module.exports = require("marko/dist/html").t(__filename),
|
|
5
|
-
marko_componentType = "/@mindful-web/marko-web-identity-x$1.
|
|
5
|
+
marko_componentType = "/@mindful-web/marko-web-identity-x$1.105.1/components/google-init.marko",
|
|
6
6
|
marko_renderer = require("marko/dist/runtime/components/renderer"),
|
|
7
7
|
marko_attr = require("marko/dist/runtime/html/helpers/attr"),
|
|
8
8
|
marko_str = require("marko/dist/runtime/helpers/to-string");
|
|
@@ -239,5 +239,5 @@ marko_template._ = marko_renderer(render, {
|
|
|
239
239
|
});
|
|
240
240
|
|
|
241
241
|
marko_template.meta = {
|
|
242
|
-
id: "/@mindful-web/marko-web-identity-x$1.
|
|
242
|
+
id: "/@mindful-web/marko-web-identity-x$1.105.1/components/google-init.marko"
|
|
243
243
|
};
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"use strict";
|
|
3
3
|
|
|
4
4
|
var marko_template = module.exports = require("marko/dist/html").t(__filename),
|
|
5
|
-
marko_componentType = "/@mindful-web/marko-web-identity-x$1.
|
|
5
|
+
marko_componentType = "/@mindful-web/marko-web-identity-x$1.105.1/components/google-sign-in-button.marko",
|
|
6
6
|
marko_renderer = require("marko/dist/runtime/components/renderer"),
|
|
7
7
|
helpers_escape_xml = require("marko/dist/runtime/html/helpers/escape-xml"),
|
|
8
8
|
marko_escapeXml = helpers_escape_xml.x,
|
|
@@ -40,5 +40,5 @@ marko_template._ = marko_renderer(render, {
|
|
|
40
40
|
});
|
|
41
41
|
|
|
42
42
|
marko_template.meta = {
|
|
43
|
-
id: "/@mindful-web/marko-web-identity-x$1.
|
|
43
|
+
id: "/@mindful-web/marko-web-identity-x$1.105.1/components/google-sign-in-button.marko"
|
|
44
44
|
};
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"use strict";
|
|
3
3
|
|
|
4
4
|
var marko_template = module.exports = require("marko/dist/html").t(__filename),
|
|
5
|
-
marko_componentType = "/@mindful-web/marko-web-identity-x$1.
|
|
5
|
+
marko_componentType = "/@mindful-web/marko-web-identity-x$1.105.1/components/identify.marko",
|
|
6
6
|
marko_component = require("./identify.marko"),
|
|
7
7
|
marko_renderer = require("marko/dist/runtime/components/renderer"),
|
|
8
8
|
module_objectPath_module = require("@mindful-web/object-path"),
|
|
@@ -50,7 +50,7 @@ marko_template._ = marko_renderer(render, {
|
|
|
50
50
|
}, marko_component);
|
|
51
51
|
|
|
52
52
|
marko_template.meta = {
|
|
53
|
-
id: "/@mindful-web/marko-web-identity-x$1.
|
|
53
|
+
id: "/@mindful-web/marko-web-identity-x$1.105.1/components/identify.marko",
|
|
54
54
|
component: "./identify.marko",
|
|
55
55
|
tags: [
|
|
56
56
|
"@mindful-web/marko-web-gtm/components/push.marko",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"use strict";
|
|
3
3
|
|
|
4
4
|
var marko_template = module.exports = require("marko/dist/html").t(__filename),
|
|
5
|
-
marko_componentType = "/@mindful-web/marko-web-identity-x$1.
|
|
5
|
+
marko_componentType = "/@mindful-web/marko-web-identity-x$1.105.1/components/non-auth-identify.marko",
|
|
6
6
|
marko_component = require("./non-auth-identify.marko"),
|
|
7
7
|
marko_renderer = require("marko/dist/runtime/components/renderer"),
|
|
8
8
|
marko_dynamicTag = require("marko/dist/runtime/helpers/dynamic-tag"),
|
|
@@ -45,7 +45,7 @@ marko_template._ = marko_renderer(render, {
|
|
|
45
45
|
}, marko_component);
|
|
46
46
|
|
|
47
47
|
marko_template.meta = {
|
|
48
|
-
id: "/@mindful-web/marko-web-identity-x$1.
|
|
48
|
+
id: "/@mindful-web/marko-web-identity-x$1.105.1/components/non-auth-identify.marko",
|
|
49
49
|
component: "./non-auth-identify.marko",
|
|
50
50
|
tags: [
|
|
51
51
|
"@mindful-web/marko-core/components/resolve.marko"
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"use strict";
|
|
3
3
|
|
|
4
4
|
var marko_template = module.exports = require("marko/dist/html").t(__filename),
|
|
5
|
-
marko_componentType = "/@mindful-web/marko-web-identity-x$1.
|
|
5
|
+
marko_componentType = "/@mindful-web/marko-web-identity-x$1.105.1/components/progressive-profile-form.marko",
|
|
6
6
|
marko_component = require("./progressive-profile-form.marko"),
|
|
7
7
|
marko_renderer = require("marko/dist/runtime/components/renderer"),
|
|
8
8
|
module_defaultValue = require("@mindful-web/marko-core/utils/default-value"),
|
|
@@ -137,7 +137,7 @@ marko_template._ = marko_renderer(render, {
|
|
|
137
137
|
}, marko_component);
|
|
138
138
|
|
|
139
139
|
marko_template.meta = {
|
|
140
|
-
id: "/@mindful-web/marko-web-identity-x$1.
|
|
140
|
+
id: "/@mindful-web/marko-web-identity-x$1.105.1/components/progressive-profile-form.marko",
|
|
141
141
|
component: "./progressive-profile-form.marko",
|
|
142
142
|
tags: [
|
|
143
143
|
"@mindful-web/marko-web/components/browser-component.marko",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"use strict";
|
|
3
3
|
|
|
4
4
|
var marko_template = module.exports = require("marko/dist/html").t(__filename),
|
|
5
|
-
marko_componentType = "/@mindful-web/marko-web-identity-x$1.
|
|
5
|
+
marko_componentType = "/@mindful-web/marko-web-identity-x$1.105.1/components/subscribe.marko",
|
|
6
6
|
marko_component = require("./subscribe.marko"),
|
|
7
7
|
marko_renderer = require("marko/dist/runtime/components/renderer"),
|
|
8
8
|
module_objectPath_module = require("@mindful-web/object-path"),
|
|
@@ -73,7 +73,7 @@ marko_template._ = marko_renderer(render, {
|
|
|
73
73
|
}, marko_component);
|
|
74
74
|
|
|
75
75
|
marko_template.meta = {
|
|
76
|
-
id: "/@mindful-web/marko-web-identity-x$1.
|
|
76
|
+
id: "/@mindful-web/marko-web-identity-x$1.105.1/components/subscribe.marko",
|
|
77
77
|
component: "./subscribe.marko",
|
|
78
78
|
tags: [
|
|
79
79
|
"@mindful-web/marko-web/components/browser-component.marko",
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Maps an IdentityX app user to the Identity Bridge `user`. Values are sent as they are: the server
|
|
3
|
+
* normalizes `null`/empty fields and lowercases the email.
|
|
4
|
+
*
|
|
5
|
+
* @param {object} user The IdentityX app user
|
|
6
|
+
*/
|
|
7
|
+
const toSyncUser = (user) => ({
|
|
8
|
+
id: user.id,
|
|
9
|
+
email: user.email,
|
|
10
|
+
givenName: user.givenName,
|
|
11
|
+
familyName: user.familyName,
|
|
12
|
+
street: user.street,
|
|
13
|
+
city: user.city,
|
|
14
|
+
regionCode: user.regionCode,
|
|
15
|
+
postalCode: user.postalCode,
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @param {import('express').Request} req
|
|
20
|
+
*/
|
|
21
|
+
const getClient = (req) => {
|
|
22
|
+
const client = req && req.mindful && req.mindful.identityBridge;
|
|
23
|
+
if (!client) throw new Error('No Identity Bridge client on the request. Pass `identityBridge` to the Mindful service middleware.');
|
|
24
|
+
return client;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Reports IdentityX member events to Mindful Identity Bridge (`POST /:tenantKey/sync`), which
|
|
29
|
+
* decides which third-party providers act on them. Nothing provider-specific lives here: login
|
|
30
|
+
* source filtering, `change-email` logins and subscription checks are all handled server side.
|
|
31
|
+
*
|
|
32
|
+
* Requires the `@mindful-web/mindful` service middleware, configured with `identityBridge`, to run
|
|
33
|
+
* before the IdentityX routes so `req.mindful.identityBridge` is set.
|
|
34
|
+
*
|
|
35
|
+
* None of the hooks are awaited, so a slow or failed sync never blocks the IdentityX request.
|
|
36
|
+
* Failures are reported through the config's `onHookError`.
|
|
37
|
+
*
|
|
38
|
+
* @param {import("../../config")} idxConfig
|
|
39
|
+
* @param {object} options
|
|
40
|
+
* @param {string} options.websiteId The site's CMS website ID (`SITE_ID`)
|
|
41
|
+
*/
|
|
42
|
+
module.exports = (idxConfig, { websiteId } = {}) => {
|
|
43
|
+
if (!idxConfig || !idxConfig.addHook) throw new Error('Unable to install the Identity Bridge sync hooks.');
|
|
44
|
+
if (!websiteId) throw new Error('A websiteId is required to install the Identity Bridge sync hooks.');
|
|
45
|
+
|
|
46
|
+
idxConfig.addHook({
|
|
47
|
+
name: 'onAuthenticationSuccess',
|
|
48
|
+
shouldAwait: false,
|
|
49
|
+
fn: async ({ req, user, loginSource }) => getClient(req).sync({
|
|
50
|
+
event: 'authenticated',
|
|
51
|
+
websiteId,
|
|
52
|
+
loginSource,
|
|
53
|
+
user: toSyncUser(user),
|
|
54
|
+
}),
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
idxConfig.addHook({
|
|
58
|
+
name: 'onUserProfileUpdate',
|
|
59
|
+
shouldAwait: false,
|
|
60
|
+
fn: async ({ req, user }) => getClient(req).sync({
|
|
61
|
+
event: 'profile-updated',
|
|
62
|
+
websiteId,
|
|
63
|
+
user: toSyncUser(user),
|
|
64
|
+
}),
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
idxConfig.addHook({
|
|
68
|
+
name: 'onChangeEmailSuccess',
|
|
69
|
+
shouldAwait: false,
|
|
70
|
+
fn: async ({ req, user, oldEmail }) => getClient(req).sync({
|
|
71
|
+
event: 'email-changed',
|
|
72
|
+
websiteId,
|
|
73
|
+
oldEmail,
|
|
74
|
+
user: toSyncUser(user),
|
|
75
|
+
}),
|
|
76
|
+
});
|
|
77
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mindful-web/marko-web-identity-x",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.105.1",
|
|
4
4
|
"description": "Marko Wrapper for IdentityX",
|
|
5
5
|
"repository": "https://github.com/parameter1/mindful-web/tree/main/packages/marko-web-identity-x",
|
|
6
6
|
"author": "Josh Worden <josh@parameter1.com>",
|
|
@@ -43,5 +43,5 @@
|
|
|
43
43
|
"chai": "^4.3.7",
|
|
44
44
|
"mocha": "^6.2.3"
|
|
45
45
|
},
|
|
46
|
-
"gitHead": "
|
|
46
|
+
"gitHead": "e6054405dc7918b784fd8cb8b8b0042dd251f248"
|
|
47
47
|
}
|
package/routes/authenticate.js
CHANGED
|
@@ -40,7 +40,9 @@ module.exports = asyncRoute(async (req, res) => {
|
|
|
40
40
|
const { token: authToken, user, loginSource } = data.loginAppUser;
|
|
41
41
|
const additionalEventData = {
|
|
42
42
|
...getAsObject(body, 'additionalEventData'),
|
|
43
|
-
|
|
43
|
+
// The login's own event data (displayOverlay, newsletterSignupType, …), stored on the login
|
|
44
|
+
// token by sendLoginLink. Was read from a misspelled path, so hooks never received it.
|
|
45
|
+
...getAsObject(data, 'loginAppUser.additionalContext'),
|
|
44
46
|
};
|
|
45
47
|
|
|
46
48
|
// call authentication hooks
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
const { describe, it } = require('mocha');
|
|
2
|
+
const { expect } = require('chai');
|
|
3
|
+
const install = require('../../integrations/identity-bridge-sync');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Installs the hooks against a stub config and returns them by name.
|
|
7
|
+
*/
|
|
8
|
+
const installHooks = (options = { websiteId: 'website-1' }) => {
|
|
9
|
+
const hooks = [];
|
|
10
|
+
install({ addHook: (hook) => hooks.push(hook) }, options);
|
|
11
|
+
return hooks.reduce((o, hook) => ({ ...o, [hook.name]: hook }), {});
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* A request carrying a stubbed Identity Bridge client that records every event.
|
|
16
|
+
*/
|
|
17
|
+
const buildReq = () => {
|
|
18
|
+
const events = [];
|
|
19
|
+
const identityBridge = {
|
|
20
|
+
sync: async (event) => {
|
|
21
|
+
events.push(event);
|
|
22
|
+
return [];
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
return { req: { mindful: { identityBridge } }, events };
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const rejectionOf = async (promise) => {
|
|
29
|
+
try {
|
|
30
|
+
await promise;
|
|
31
|
+
} catch (e) {
|
|
32
|
+
return e;
|
|
33
|
+
}
|
|
34
|
+
return null;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const user = {
|
|
38
|
+
id: 'user-1',
|
|
39
|
+
email: 'jane@example.com',
|
|
40
|
+
givenName: 'Jane',
|
|
41
|
+
familyName: null,
|
|
42
|
+
street: '',
|
|
43
|
+
city: 'Chicago',
|
|
44
|
+
regionCode: 'US-IL',
|
|
45
|
+
postalCode: '60601',
|
|
46
|
+
verified: true,
|
|
47
|
+
customAttributes: { foo: 'bar' },
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// Sent as IdentityX has it, `null` and `''` included; the server normalizes them.
|
|
51
|
+
const syncUser = {
|
|
52
|
+
id: 'user-1',
|
|
53
|
+
email: 'jane@example.com',
|
|
54
|
+
givenName: 'Jane',
|
|
55
|
+
familyName: null,
|
|
56
|
+
street: '',
|
|
57
|
+
city: 'Chicago',
|
|
58
|
+
regionCode: 'US-IL',
|
|
59
|
+
postalCode: '60601',
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
describe('integrations/identity-bridge-sync', () => {
|
|
63
|
+
describe('install', () => {
|
|
64
|
+
it('registers non-awaited login, profile update and email change hooks', () => {
|
|
65
|
+
const hooks = [];
|
|
66
|
+
install({ addHook: (hook) => hooks.push(hook) }, { websiteId: 'website-1' });
|
|
67
|
+
expect(hooks.map(({ name }) => name)).to.deep.equal([
|
|
68
|
+
'onAuthenticationSuccess',
|
|
69
|
+
'onUserProfileUpdate',
|
|
70
|
+
'onChangeEmailSuccess',
|
|
71
|
+
]);
|
|
72
|
+
hooks.forEach((hook) => expect(hook.shouldAwait).to.equal(false));
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('throws without an IdentityX config', () => {
|
|
76
|
+
expect(() => install()).to.throw('Unable to install the Identity Bridge sync hooks.');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('throws without a website id', () => {
|
|
80
|
+
expect(() => installHooks({})).to.throw('A websiteId is required to install the Identity Bridge sync hooks.');
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
describe('onAuthenticationSuccess', () => {
|
|
85
|
+
it('sends an authenticated event with the login source', async () => {
|
|
86
|
+
const { req, events } = buildReq();
|
|
87
|
+
await installHooks().onAuthenticationSuccess.fn({ req, user, loginSource: 'contentGate' });
|
|
88
|
+
expect(events).to.deep.equal([{
|
|
89
|
+
event: 'authenticated',
|
|
90
|
+
websiteId: 'website-1',
|
|
91
|
+
loginSource: 'contentGate',
|
|
92
|
+
user: syncUser,
|
|
93
|
+
}]);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('sends every login source, including change-email, for the server to filter', async () => {
|
|
97
|
+
const { req, events } = buildReq();
|
|
98
|
+
await installHooks().onAuthenticationSuccess.fn({ req, user, loginSource: 'change-email' });
|
|
99
|
+
expect(events).to.have.lengthOf(1);
|
|
100
|
+
expect(events[0].loginSource).to.equal('change-email');
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
describe('onUserProfileUpdate', () => {
|
|
105
|
+
it('sends a profile-updated event', async () => {
|
|
106
|
+
const { req, events } = buildReq();
|
|
107
|
+
await installHooks().onUserProfileUpdate.fn({ req, user, actionSource: 'profile' });
|
|
108
|
+
expect(events).to.deep.equal([{
|
|
109
|
+
event: 'profile-updated',
|
|
110
|
+
websiteId: 'website-1',
|
|
111
|
+
user: syncUser,
|
|
112
|
+
}]);
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
describe('onChangeEmailSuccess', () => {
|
|
117
|
+
it('sends an email-changed event with the new email on the user', async () => {
|
|
118
|
+
const { req, events } = buildReq();
|
|
119
|
+
await installHooks().onChangeEmailSuccess.fn({
|
|
120
|
+
req,
|
|
121
|
+
user: { ...user, email: 'jane.new@example.com' },
|
|
122
|
+
oldEmail: 'jane@example.com',
|
|
123
|
+
});
|
|
124
|
+
expect(events).to.deep.equal([{
|
|
125
|
+
event: 'email-changed',
|
|
126
|
+
websiteId: 'website-1',
|
|
127
|
+
oldEmail: 'jane@example.com',
|
|
128
|
+
user: { ...syncUser, email: 'jane.new@example.com' },
|
|
129
|
+
}]);
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
describe('without a client on the request', () => {
|
|
134
|
+
['onAuthenticationSuccess', 'onUserProfileUpdate', 'onChangeEmailSuccess'].forEach((name) => {
|
|
135
|
+
it(`${name} throws a clear error`, async () => {
|
|
136
|
+
const hooks = installHooks();
|
|
137
|
+
const reqs = [{}, { mindful: {} }, { mindful: { identityBridge: null } }];
|
|
138
|
+
const errors = await Promise.all(reqs.map((req) => rejectionOf(hooks[name].fn({
|
|
139
|
+
req,
|
|
140
|
+
user,
|
|
141
|
+
oldEmail: 'jane@example.com',
|
|
142
|
+
}))));
|
|
143
|
+
errors.forEach((error) => {
|
|
144
|
+
expect(error).to.be.an('error');
|
|
145
|
+
expect(error.message).to.equal('No Identity Bridge client on the request. Pass `identityBridge` to the Mindful service middleware.');
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('passes on a failed sync so onHookError reports it', async () => {
|
|
152
|
+
const req = {
|
|
153
|
+
mindful: {
|
|
154
|
+
identityBridge: {
|
|
155
|
+
sync: async () => { throw new Error('Identity Bridge sync failed (502): emagazines'); },
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
const error = await rejectionOf(installHooks().onUserProfileUpdate.fn({ req, user }));
|
|
160
|
+
expect(error.message).to.equal('Identity Bridge sync failed (502): emagazines');
|
|
161
|
+
});
|
|
162
|
+
});
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
const { describe, it } = require('mocha');
|
|
2
|
+
const { expect } = require('chai');
|
|
3
|
+
const route = require('../../routes/authenticate');
|
|
4
|
+
|
|
5
|
+
const run = async ({ additionalContext, bodyEventData } = {}) => {
|
|
6
|
+
const hookArgs = [];
|
|
7
|
+
let json;
|
|
8
|
+
const identityX = {
|
|
9
|
+
client: {
|
|
10
|
+
mutate: async () => ({
|
|
11
|
+
data: {
|
|
12
|
+
loginAppUser: {
|
|
13
|
+
token: { id: 'tok', value: 'tok-value' },
|
|
14
|
+
user: { id: 'user-1' },
|
|
15
|
+
loginSource: 'content_meter_login',
|
|
16
|
+
additionalContext,
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
}),
|
|
20
|
+
},
|
|
21
|
+
config: {
|
|
22
|
+
hooks: {
|
|
23
|
+
onAuthenticationSuccess: [{ fn: (args) => hookArgs.push(args), shouldAwait: true }],
|
|
24
|
+
},
|
|
25
|
+
get: () => undefined,
|
|
26
|
+
getAppId: () => 'app-1',
|
|
27
|
+
},
|
|
28
|
+
getOrgUserApiToken: () => 'org-token',
|
|
29
|
+
setIdentityCookie: () => {},
|
|
30
|
+
generateEntityId: async () => 'entity-1',
|
|
31
|
+
findUserById: async (id) => ({ id }),
|
|
32
|
+
};
|
|
33
|
+
const req = { identityX, body: { token: 'login-token', additionalEventData: bodyEventData } };
|
|
34
|
+
const res = { cookie: () => {}, json: (value) => { json = value; } };
|
|
35
|
+
let error;
|
|
36
|
+
await route(req, res, (e) => { error = e; });
|
|
37
|
+
if (error) throw error;
|
|
38
|
+
return { hookArgs, json };
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
describe('routes/authenticate', () => {
|
|
42
|
+
it('passes the login token context to onAuthenticationSuccess', async () => {
|
|
43
|
+
// The login's own event data is what lets the hook attribute the Verify Email behavior to
|
|
44
|
+
// the same source as Log In (e.g. a metered-gate overlay). It was read from a misspelled
|
|
45
|
+
// path, so the hook never received it.
|
|
46
|
+
const { hookArgs, json } = await run({
|
|
47
|
+
additionalContext: { displayOverlay: true, actionSource: 'content_meter_login', source: 'content_meter_login' },
|
|
48
|
+
});
|
|
49
|
+
expect(hookArgs).to.have.length(1);
|
|
50
|
+
expect(hookArgs[0].loginSource).to.equal('content_meter_login');
|
|
51
|
+
expect(hookArgs[0].additionalEventData).to.include({ displayOverlay: true, actionSource: 'content_meter_login' });
|
|
52
|
+
expect(json.additionalEventData).to.include({ displayOverlay: true });
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('lets the token context win over event data posted by the authenticate page', async () => {
|
|
56
|
+
const { hookArgs } = await run({
|
|
57
|
+
additionalContext: { newsletterSignupType: 'pushdown' },
|
|
58
|
+
bodyEventData: { newsletterSignupType: 'footer', fromPage: true },
|
|
59
|
+
});
|
|
60
|
+
expect(hookArgs[0].additionalEventData).to.include({ newsletterSignupType: 'pushdown', fromPage: true });
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('tolerates a token with no stored context', async () => {
|
|
64
|
+
const { hookArgs } = await run({ bodyEventData: { fromPage: true } });
|
|
65
|
+
expect(hookArgs[0].additionalEventData).to.deep.equal({ fromPage: true });
|
|
66
|
+
});
|
|
67
|
+
});
|