@mindful-web/marko-web-identity-x 1.76.7 → 1.77.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.
- package/CLAUDE.md +139 -0
- package/README.md +48 -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 +2 -0
- package/components/form-authenticate.marko.js +14 -2
- package/components/form-change-email.marko.js +2 -2
- package/components/form-login.marko +2 -0
- package/components/form-login.marko.js +14 -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 +73 -0
- package/components/google-init.marko.js +95 -0
- package/components/google-sign-in-button.marko +24 -0
- package/components/google-sign-in-button.marko.js +44 -0
- package/components/identify.marko.js +2 -2
- package/components/marko.json +9 -0
- package/components/non-auth-identify.marko.js +2 -2
- package/components/subscribe.marko.js +2 -2
- package/config.js +17 -0
- package/package.json +3 -2
- package/routes/google.js +206 -0
- package/routes/index.js +2 -0
- package/routes/logout.js +5 -0
package/CLAUDE.md
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# marko-web-identity-x — Claude Code Context
|
|
2
|
+
|
|
3
|
+
The Marko/Express wrapper for **IdentityX** (Parameter1's identity + access platform) on Mindful
|
|
4
|
+
Web sites. Owns the whole authenticated-user surface: login / register / authenticate (email-link)
|
|
5
|
+
/ profile / logout, content access + download gating, comments, and the server-side IdentityX API
|
|
6
|
+
client. Add-ons layer on top: `marko-web-omeda-identity-x` (wraps this and adds Omeda hooks —
|
|
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).
|
|
9
|
+
|
|
10
|
+
## Config model — the `IdentityXConfiguration` instance (`config.js`)
|
|
11
|
+
|
|
12
|
+
Unlike most Mindful packages, config is **not** read from `site.getAsObject(...)` — it's an
|
|
13
|
+
instance of `IdentityXConfiguration` (`new IdentityX({...})`), passed to the installer and injected
|
|
14
|
+
onto every request as `req.identityX.config`. Read in templates via
|
|
15
|
+
`req.identityX.config.get(...)` / `.getAsObject(...)` / typed getters.
|
|
16
|
+
|
|
17
|
+
```js
|
|
18
|
+
const IdentityX = require('@mindful-web/marko-web-identity-x/config');
|
|
19
|
+
const config = new IdentityX({
|
|
20
|
+
appId: '<APP-ID>', // required
|
|
21
|
+
apiToken: process.env.IDENTITYX_API_TOKEN, // required for write ops
|
|
22
|
+
requiredServerFields: ['organization', 'countryCode'],
|
|
23
|
+
requiredClientFields: [...], // gate access until present
|
|
24
|
+
hiddenFields: [...], defaultCountryCode, booleanQuestionsLabel, gtmUserFields,
|
|
25
|
+
onHookError: newrelic.noticeError.bind(newrelic), // hook + google-auth error reporter
|
|
26
|
+
googleAuth: { clientId, missingFieldsBehavior, autoPrompt, signInButtonEnabled }, // opt-in; see below
|
|
27
|
+
});
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Arbitrary extra keys pass through `...rest` into `options` and are reachable via `config.get(path)`.
|
|
31
|
+
`endpointTypes` (`authenticate|changeEmail|login|logout|register|profile`) map to `/user/<type>`
|
|
32
|
+
routes via `getEndpointFor(type)` (override with an `endpoints` config key).
|
|
33
|
+
|
|
34
|
+
## Wiring (`index.js`)
|
|
35
|
+
|
|
36
|
+
`IdentityX(app, config, { templates })`:
|
|
37
|
+
1. `app.use(middleware(config))` — injects the service (below).
|
|
38
|
+
2. `app.use('/__idx', routes)` — the JSON API router.
|
|
39
|
+
3. For each `endpointType` with a supplied `template`, registers `GET <endpoint>` to render it
|
|
40
|
+
(the `authenticate` endpoint redirects to profile when a token is already present).
|
|
41
|
+
|
|
42
|
+
Most sites don't call this directly — they call `omedaIdentityX(app, { idxConfig, idxRouteTemplates, … })`,
|
|
43
|
+
which adds Omeda integration hooks to `idxConfig` and then calls `identityX(app, idxConfig, …)`
|
|
44
|
+
internally. So **`googleAuth` goes on the `idxConfig` instance**, not the omeda params and not site config.
|
|
45
|
+
|
|
46
|
+
## Middleware (`middleware.js`)
|
|
47
|
+
|
|
48
|
+
Injects a per-request `IdentityX` **service** instance as `req.identityX` (+ `res.locals.identityX`),
|
|
49
|
+
reads the token cookie, and sets the identity cookie for logged-in users. `IdentityXRequest.identityX`
|
|
50
|
+
is the type every route/template relies on.
|
|
51
|
+
|
|
52
|
+
## Routes — `/__idx/*` (`routes/index.js`)
|
|
53
|
+
|
|
54
|
+
JSON API behind the Vue components: `authenticate`, `login`, `login-fields`, `logout`, `profile`,
|
|
55
|
+
`access`, `download`, `change-email/{initiate,confirm}`, `comment(s)`/`comment-count`/`flag`,
|
|
56
|
+
`countries`/`regions`, and **`google`** (see below). `logout.js` removes the context cookie, calls
|
|
57
|
+
`onLogout` (tokenless) or `logoutAppUser`, and **clears the GIS `g_state` cookie** unconditionally.
|
|
58
|
+
|
|
59
|
+
## Components / tags (`components/marko.json`)
|
|
60
|
+
|
|
61
|
+
Marko wrappers (`<marko-web-identity-x-*>`) that render a matching **browser Vue component**
|
|
62
|
+
(`browser/*.vue`, registered in `browser/index.js` as `IdentityX*`, overridable via
|
|
63
|
+
`Custom*Component`): `form-login`, `form-authenticate`, `form-register`, `form-profile`,
|
|
64
|
+
`form-change-email`, `form-logout`, `form-access`, `access`, `comment-stream`, `identify` /
|
|
65
|
+
`non-auth-identify`, `context` (a `|{ user, application, fields, isEnabled }|` provider), and the
|
|
66
|
+
Google tags. Vue components emit an EventBus stream bridged to `dataLayer` + p1events (see
|
|
67
|
+
`browser/index.js`).
|
|
68
|
+
|
|
69
|
+
## Service (`service.js`)
|
|
70
|
+
|
|
71
|
+
Server-side IdentityX GraphQL client (Apollo, idx-compat). Key methods: `loadActiveContext`,
|
|
72
|
+
`loadAppUserByEmail` / `findUserById` / `findUserByExternalId`, `createAppUser`,
|
|
73
|
+
`impersonateAppUser`, `logoutAppUser`, `addExternalUserId`, `setUnverifiedAppUserData`,
|
|
74
|
+
`generateEntityId`, `sendLoginLink`, `checkContentAccess`, cookie helpers (`setToken`,
|
|
75
|
+
`setIdentityCookie`, `getIdentity`). Org-scoped writes use `getOrgUserApiToken()`.
|
|
76
|
+
|
|
77
|
+
## Hooks (`hooks.js`)
|
|
78
|
+
|
|
79
|
+
`onLoadActiveContext`, `onLoginLinkSent`, `onAuthenticationSuccess`, `onLogout`,
|
|
80
|
+
`onChangeEmailLinkSent`, `onChangeEmailSuccess`, `onUserProfileUpdate`. Registered via
|
|
81
|
+
`config.addHook({ name, fn, shouldAwait })` and invoked with `utils/call-hooks-for`. This is the
|
|
82
|
+
seam Omeda uses for rapid-identify + subscription sync. **Note:** `onLogout` currently fires only
|
|
83
|
+
in the tokenless logout branch — a "fire `onLogout` always" change would let the `g_state` clear
|
|
84
|
+
become a hook rather than inline route code.
|
|
85
|
+
|
|
86
|
+
## Google Sign-In (One-Tap + button) — folded in 2026-07
|
|
87
|
+
|
|
88
|
+
Verifies a Google Identity Services (GIS) credential server-side and drops the user into the
|
|
89
|
+
standard IdentityX session, bypassing the email-link step. Handles the One-Tap prompt **and** the
|
|
90
|
+
rendered "Sign in with Google" button; both post to `POST /__idx/google`. **Dormant until
|
|
91
|
+
`googleAuth.clientId` is set** — no client id ⇒ nothing renders and the route 400s. Previously a
|
|
92
|
+
standalone `marko-web-google-identity-x` package; folded in here because it was ~all ceremony gated
|
|
93
|
+
on a client id and already depended on idx internals.
|
|
94
|
+
|
|
95
|
+
**Config** (on the IdentityX config, above): `clientId` (master gate — per-deploy env),
|
|
96
|
+
`missingFieldsBehavior` (`profile-gate` default | `immediate` | `relaxed`; `relaxed` is a doc-only
|
|
97
|
+
alias of `immediate` — only `profile-gate` is special-cased), `autoPrompt` (site-wide floating
|
|
98
|
+
overlay), `signInButtonEnabled` (button; default true).
|
|
99
|
+
|
|
100
|
+
**Route — `routes/google.js` (`POST /__idx/google`).** Verify the JWT →
|
|
101
|
+
`loadAppUserByEmail`/`createAppUser` (**email is the primary key**) → link the Google `sub` as a
|
|
102
|
+
supplementary external id under `{ provider:'google', tenant:'oauth', type:'account' }` (idempotent;
|
|
103
|
+
conflict-guarded; non-fatal via `config.get('onHookError')`) → `impersonateAppUser({ method:'GOOGLE_ONE_TAP', verify:true })`
|
|
104
|
+
→ backfill given/family name only if absent → fire `onLoginLinkSent` **then** (re-fetch)
|
|
105
|
+
`onAuthenticationSuccess`, same order/hooks as email-link so Omeda rapid-identify + subscription
|
|
106
|
+
sync run identically. `profile-gate` computes `requiresUserInput` by checking required fields are
|
|
107
|
+
non-empty (deliberately not `forceProfileReVerification`). Responds `{ ok, requiresUserInput,
|
|
108
|
+
redirectTo, user, entity, … }`; the client redirects to `/user/profile` when input is required.
|
|
109
|
+
|
|
110
|
+
**Tags** (`components/marko.json`, config read from `req.identityX.config`):
|
|
111
|
+
- `<marko-web-identity-x-google-init>` — loads the GSI client + defines `window.handleGoogleOneTapCredential`;
|
|
112
|
+
shows the One-Tap prompt when `autoPrompt`. The button depends on this being on the page.
|
|
113
|
+
- `<marko-web-identity-x-google-sign-in-button client-id= redirect-to= label=>` — the button; self-hides
|
|
114
|
+
when unconfigured or `signInButtonEnabled` is false.
|
|
115
|
+
|
|
116
|
+
**Auto-rendering.** Both tags render inside `form-login.marko` + `form-authenticate.marko`, so the
|
|
117
|
+
login/authenticate pages get the button with **no per-site edit**. For the site-wide floating prompt
|
|
118
|
+
(`autoPrompt: true`), a site additionally drops `<marko-web-identity-x-google-init/>` once in its
|
|
119
|
+
document layout, gated on `!req.identityX.token`. The button tag can also be placed in a content
|
|
120
|
+
meter / gate.
|
|
121
|
+
|
|
122
|
+
**CSS.** `.google-sign-in` styles ship in `marko-web-theme-monorail/scss/components/_identity-x.scss`
|
|
123
|
+
(with the rest of the auth CSS) — no separate import.
|
|
124
|
+
|
|
125
|
+
**Logout.** `routes/logout.js` clears the GIS `g_state` cookie so a later sign-in with a *different*
|
|
126
|
+
Google account isn't blocked by stale auto-select state (harmless no-op when Google Sign-In is unused).
|
|
127
|
+
|
|
128
|
+
**Deliberate decisions / limitations.** `loginSource: 'google-one-tap'` and
|
|
129
|
+
`method: 'GOOGLE_ONE_TAP'` are emitted for BOTH surfaces (don't distinguish button vs prompt) —
|
|
130
|
+
kept for analytics/enum continuity. One success p1event (`Identity / Authenticate`, label
|
|
131
|
+
`Google Sign In` for the button via GIS `select_by` `btn*`, else `Google One Tap`), fired client-side
|
|
132
|
+
after `setIdentity`. `google-auth-library` is now a base-idx dependency (every site pulls it,
|
|
133
|
+
server-only) — accepted because the feature is gated on the client id. The Google OAuth app must
|
|
134
|
+
list each site origin as an authorized JavaScript origin or the surfaces silently no-show.
|
|
135
|
+
|
|
136
|
+
## Fleet / rollout
|
|
137
|
+
|
|
138
|
+
Not tracked in-repo. The Google-auth architecture change + fleet rollout plan lives outside the repo
|
|
139
|
+
at `../GOOGLE-AUTH-ROLLOUT.md` (mindful working dir).
|
package/README.md
CHANGED
|
@@ -197,4 +197,52 @@ Each component will emit an event when an error is encountered and include the e
|
|
|
197
197
|
| `identity-x-profile-errored` | `{ label, message: '...', ...additionalEventData }`
|
|
198
198
|
| `identity-x-comment-post-errored` | `{ label, message: '...', ...additionalEventData }`
|
|
199
199
|
| `identity-x-comment-report-errored` | `{ label, message: '...', ...additionalEventData }`
|
|
200
|
+
|
|
201
|
+
## Google Sign-In (One-Tap + button)
|
|
202
|
+
|
|
203
|
+
Built in. Verifies a Google Identity Services (GIS) credential server-side and drops the user
|
|
204
|
+
into the standard IdentityX session, bypassing the email-link step. Handles both the One-Tap
|
|
205
|
+
prompt and the rendered "Sign in with Google" button; both post to `POST /__idx/google`.
|
|
206
|
+
|
|
207
|
+
**Dormant until configured** — with no `clientId`, nothing renders and the route 400s. Enable
|
|
208
|
+
per-site by adding a `googleAuth` block to the IdentityX config:
|
|
209
|
+
|
|
210
|
+
```js
|
|
211
|
+
const config = new IdentityX({
|
|
212
|
+
appId: '<MY-APPLICATION-ID>',
|
|
213
|
+
googleAuth: {
|
|
214
|
+
clientId: process.env.GOOGLE_AUTH_CLIENT_ID || '', // GIS OAuth client id — the master gate
|
|
215
|
+
missingFieldsBehavior: 'profile-gate', // 'profile-gate' | 'immediate' | 'relaxed'
|
|
216
|
+
autoPrompt: false, // floating One-Tap overlay
|
|
217
|
+
signInButtonEnabled: true, // render the button
|
|
218
|
+
},
|
|
219
|
+
});
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
Reading the values from env is optional — set literals here if you'd rather not manage infra
|
|
223
|
+
env vars. The client id's Google OAuth app must list each site origin as an **authorized
|
|
224
|
+
JavaScript origin**, or the prompt/button silently no-shows.
|
|
225
|
+
|
|
226
|
+
`missingFieldsBehavior`: `profile-gate` authenticates immediately but forces profile completion
|
|
227
|
+
before gated content; `immediate`/`relaxed` grant access with fields left blank (`relaxed` is a
|
|
228
|
+
doc-only alias of `immediate`).
|
|
229
|
+
|
|
230
|
+
**Rendering.** The button + GIS init are rendered automatically inside the `form-login` and
|
|
231
|
+
`form-authenticate` templates — no per-site placement, and both self-hide when unconfigured. For
|
|
232
|
+
the **site-wide floating One-Tap prompt** (`autoPrompt: true`), also drop
|
|
233
|
+
`<marko-web-identity-x-google-init/>` once in your document layout, gated on an unauthenticated
|
|
234
|
+
user, e.g. `<if(!req.identityX.token)> <marko-web-identity-x-google-init/> </if>`. The button tag
|
|
235
|
+
(`<marko-web-identity-x-google-sign-in-button redirect-to=... />`) can also be placed in a content meter
|
|
236
|
+
or gate if desired.
|
|
237
|
+
|
|
238
|
+
**CSS.** Styles ship with the rest of the auth CSS in `marko-web-theme-monorail`
|
|
239
|
+
(`scss/components/_identity-x.scss`) — no separate import.
|
|
240
|
+
|
|
241
|
+
**Logout.** The idx logout route clears the GIS `g_state` cookie so a later sign-in with a
|
|
242
|
+
*different* Google account isn't blocked by stale auto-select state (harmless no-op when Google
|
|
243
|
+
Sign-In isn't used).
|
|
244
|
+
|
|
245
|
+
The Google account id (JWT `sub`) is persisted as a supplementary idx external id under
|
|
246
|
+
`{ provider: 'google', tenant: 'oauth', type: 'account' }`; email stays the primary key.
|
|
247
|
+
Non-fatal linkage errors report through the config's `onHookError`.
|
|
200
248
|
| `identity-x-comment-stream-errored` | `{ label, message: '...', ...additionalEventData }`
|
|
@@ -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.77.0/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.77.0/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.77.0/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"),
|
|
@@ -85,7 +85,7 @@ marko_template._ = marko_renderer(render, {
|
|
|
85
85
|
}, marko_component);
|
|
86
86
|
|
|
87
87
|
marko_template.meta = {
|
|
88
|
-
id: "/@mindful-web/marko-web-identity-x$1.
|
|
88
|
+
id: "/@mindful-web/marko-web-identity-x$1.77.0/components/comment-stream.marko",
|
|
89
89
|
component: "./comment-stream.marko",
|
|
90
90
|
tags: [
|
|
91
91
|
"@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.77.0/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.77.0/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.77.0/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"),
|
|
@@ -174,7 +174,7 @@ marko_template._ = marko_renderer(render, {
|
|
|
174
174
|
}, marko_component);
|
|
175
175
|
|
|
176
176
|
marko_template.meta = {
|
|
177
|
-
id: "/@mindful-web/marko-web-identity-x$1.
|
|
177
|
+
id: "/@mindful-web/marko-web-identity-x$1.77.0/components/form-access.marko",
|
|
178
178
|
component: "./form-access.marko",
|
|
179
179
|
tags: [
|
|
180
180
|
"@mindful-web/marko-web/components/browser-component.marko",
|
|
@@ -32,5 +32,7 @@ $ const lang = input.lang || defaultValue(site.config.lang, "en");
|
|
|
32
32
|
lang
|
|
33
33
|
};
|
|
34
34
|
<marko-web-browser-component name="IdentityXAuthenticate" props=props />
|
|
35
|
+
<marko-web-identity-x-google-init/>
|
|
36
|
+
<marko-web-identity-x-google-sign-in-button redirect-to=query.redirectTo/>
|
|
35
37
|
</marko-web-identity-x-context>
|
|
36
38
|
</if>
|
|
@@ -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.77.0/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"),
|
|
@@ -13,6 +13,10 @@ var marko_template = module.exports = require("marko/dist/html").t(__filename),
|
|
|
13
13
|
marko_web_browser_component_template = require("@mindful-web/marko-web/components/browser-component.marko"),
|
|
14
14
|
marko_loadTag = require("marko/dist/runtime/helpers/load-tag"),
|
|
15
15
|
marko_web_browser_component_tag = marko_loadTag(marko_web_browser_component_template),
|
|
16
|
+
marko_web_identity_x_google_init_template = require("./google-init.marko"),
|
|
17
|
+
marko_web_identity_x_google_init_tag = marko_loadTag(marko_web_identity_x_google_init_template),
|
|
18
|
+
marko_web_identity_x_google_sign_in_button_template = require("./google-sign-in-button.marko"),
|
|
19
|
+
marko_web_identity_x_google_sign_in_button_tag = marko_loadTag(marko_web_identity_x_google_sign_in_button_template),
|
|
16
20
|
marko_web_identity_x_context_template = require("./context.marko"),
|
|
17
21
|
marko_web_identity_x_context_tag = marko_loadTag(marko_web_identity_x_context_template);
|
|
18
22
|
|
|
@@ -61,6 +65,12 @@ function render(input, out, __component, component, state) {
|
|
|
61
65
|
name: "IdentityXAuthenticate",
|
|
62
66
|
props: props
|
|
63
67
|
}, out, __component, "1");
|
|
68
|
+
|
|
69
|
+
marko_web_identity_x_google_init_tag({}, out, __component, "2");
|
|
70
|
+
|
|
71
|
+
marko_web_identity_x_google_sign_in_button_tag({
|
|
72
|
+
redirectTo: query.redirectTo
|
|
73
|
+
}, out, __component, "3");
|
|
64
74
|
}
|
|
65
75
|
}, out, __component, "0");
|
|
66
76
|
}
|
|
@@ -71,10 +81,12 @@ marko_template._ = marko_renderer(render, {
|
|
|
71
81
|
}, marko_component);
|
|
72
82
|
|
|
73
83
|
marko_template.meta = {
|
|
74
|
-
id: "/@mindful-web/marko-web-identity-x$1.
|
|
84
|
+
id: "/@mindful-web/marko-web-identity-x$1.77.0/components/form-authenticate.marko",
|
|
75
85
|
component: "./form-authenticate.marko",
|
|
76
86
|
tags: [
|
|
77
87
|
"@mindful-web/marko-web/components/browser-component.marko",
|
|
88
|
+
"./google-init.marko",
|
|
89
|
+
"./google-sign-in-button.marko",
|
|
78
90
|
"./context.marko"
|
|
79
91
|
]
|
|
80
92
|
};
|
|
@@ -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.77.0/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.77.0/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",
|
|
@@ -40,6 +40,8 @@ $ const withFields = defaultValue(input.withFields, false);
|
|
|
40
40
|
};
|
|
41
41
|
<if(isEnabled)>
|
|
42
42
|
<marko-web-browser-component name="IdentityXLogin" props=props />
|
|
43
|
+
<marko-web-identity-x-google-init/>
|
|
44
|
+
<marko-web-identity-x-google-sign-in-button redirect-to=input.redirect/>
|
|
43
45
|
</if>
|
|
44
46
|
</marko-web-identity-x-context>
|
|
45
47
|
</if>
|
|
@@ -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.77.0/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"),
|
|
@@ -15,6 +15,10 @@ var marko_template = module.exports = require("marko/dist/html").t(__filename),
|
|
|
15
15
|
marko_web_browser_component_template = require("@mindful-web/marko-web/components/browser-component.marko"),
|
|
16
16
|
marko_loadTag = require("marko/dist/runtime/helpers/load-tag"),
|
|
17
17
|
marko_web_browser_component_tag = marko_loadTag(marko_web_browser_component_template),
|
|
18
|
+
marko_web_identity_x_google_init_template = require("./google-init.marko"),
|
|
19
|
+
marko_web_identity_x_google_init_tag = marko_loadTag(marko_web_identity_x_google_init_template),
|
|
20
|
+
marko_web_identity_x_google_sign_in_button_template = require("./google-sign-in-button.marko"),
|
|
21
|
+
marko_web_identity_x_google_sign_in_button_tag = marko_loadTag(marko_web_identity_x_google_sign_in_button_template),
|
|
18
22
|
marko_web_identity_x_context_template = require("./context.marko"),
|
|
19
23
|
marko_web_identity_x_context_tag = marko_loadTag(marko_web_identity_x_context_template);
|
|
20
24
|
|
|
@@ -71,6 +75,12 @@ function render(input, out, __component, component, state) {
|
|
|
71
75
|
name: "IdentityXLogin",
|
|
72
76
|
props: props
|
|
73
77
|
}, out, __component, "1");
|
|
78
|
+
|
|
79
|
+
marko_web_identity_x_google_init_tag({}, out, __component, "2");
|
|
80
|
+
|
|
81
|
+
marko_web_identity_x_google_sign_in_button_tag({
|
|
82
|
+
redirectTo: input.redirect
|
|
83
|
+
}, out, __component, "3");
|
|
74
84
|
}
|
|
75
85
|
}
|
|
76
86
|
}, out, __component, "0");
|
|
@@ -82,10 +92,12 @@ marko_template._ = marko_renderer(render, {
|
|
|
82
92
|
}, marko_component);
|
|
83
93
|
|
|
84
94
|
marko_template.meta = {
|
|
85
|
-
id: "/@mindful-web/marko-web-identity-x$1.
|
|
95
|
+
id: "/@mindful-web/marko-web-identity-x$1.77.0/components/form-login.marko",
|
|
86
96
|
component: "./form-login.marko",
|
|
87
97
|
tags: [
|
|
88
98
|
"@mindful-web/marko-web/components/browser-component.marko",
|
|
99
|
+
"./google-init.marko",
|
|
100
|
+
"./google-sign-in-button.marko",
|
|
89
101
|
"./context.marko"
|
|
90
102
|
]
|
|
91
103
|
};
|
|
@@ -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.77.0/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.77.0/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.77.0/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.77.0/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.77.0/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.77.0/components/form-register.marko",
|
|
29
29
|
tags: [
|
|
30
30
|
"./form-login.marko"
|
|
31
31
|
]
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
$ const { req } = out.global;
|
|
2
|
+
$ const config = req.identityX && req.identityX.config;
|
|
3
|
+
$ const { clientId, autoPrompt } = config ? config.getAsObject('googleAuth') : {};
|
|
4
|
+
$ const callbackName = 'handleGoogleOneTapCredential';
|
|
5
|
+
$ const initScript = `
|
|
6
|
+
(function() {
|
|
7
|
+
window.${callbackName} = function(response) {
|
|
8
|
+
var redirectTo = window.__googleSignInRedirectTo || '';
|
|
9
|
+
var errorEl = document.getElementById('google-sign-in-error');
|
|
10
|
+
if (errorEl) errorEl.style.display = 'none';
|
|
11
|
+
|
|
12
|
+
// GIS 'select_by' distinguishes the surface: a 'btn*' value means the
|
|
13
|
+
// rendered "Sign in with Google" button, 'user*'/'auto' means the One-Tap
|
|
14
|
+
// prompt. Used to label the p1events authentication event accordingly.
|
|
15
|
+
var eventLabel = String(response.select_by || '').indexOf('btn') === 0
|
|
16
|
+
? 'Google Sign In'
|
|
17
|
+
: 'Google One Tap';
|
|
18
|
+
|
|
19
|
+
fetch('/__idx/google', {
|
|
20
|
+
method: 'POST',
|
|
21
|
+
headers: { 'Content-Type': 'application/json' },
|
|
22
|
+
body: JSON.stringify({ credential: response.credential, redirectTo: redirectTo }),
|
|
23
|
+
})
|
|
24
|
+
.then(function(res) { return res.json(); })
|
|
25
|
+
.then(function(data) {
|
|
26
|
+
if (!data.ok) {
|
|
27
|
+
if (errorEl) {
|
|
28
|
+
errorEl.textContent = data.error || 'Google sign-in failed. Please try again.';
|
|
29
|
+
errorEl.style.display = 'block';
|
|
30
|
+
}
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
if (window.p1events) {
|
|
34
|
+
// De-anonymize the session to the app user, then record the
|
|
35
|
+
// authentication — mirrors the identity-x conversion events.
|
|
36
|
+
window.p1events('setIdentity', data.entity);
|
|
37
|
+
window.p1events('track', {
|
|
38
|
+
category: 'Identity',
|
|
39
|
+
action: 'Authenticate',
|
|
40
|
+
label: eventLabel,
|
|
41
|
+
props: { idxEntity: data.entity, actionSource: 'google-one-tap' },
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
if (data.requiresUserInput) {
|
|
45
|
+
var destination = data.redirectTo || window.location.pathname;
|
|
46
|
+
window.location.href = '/user/profile?returnTo=' + encodeURIComponent(destination);
|
|
47
|
+
} else {
|
|
48
|
+
window.location.href = data.redirectTo || window.location.href;
|
|
49
|
+
}
|
|
50
|
+
})
|
|
51
|
+
.catch(function() {
|
|
52
|
+
if (errorEl) {
|
|
53
|
+
errorEl.textContent = 'An error occurred during Google sign-in. Please try again.';
|
|
54
|
+
errorEl.style.display = 'block';
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
};
|
|
58
|
+
})();
|
|
59
|
+
`;
|
|
60
|
+
|
|
61
|
+
<if(clientId)>
|
|
62
|
+
<div
|
|
63
|
+
id="g_id_onload"
|
|
64
|
+
data-client_id=clientId
|
|
65
|
+
data-callback=callbackName
|
|
66
|
+
data-auto_prompt=(autoPrompt ? 'true' : 'false')
|
|
67
|
+
data-auto_select="false"
|
|
68
|
+
data-cancel_on_tap_outside="true"
|
|
69
|
+
data-itp_support="true"
|
|
70
|
+
/>
|
|
71
|
+
<script src="https://accounts.google.com/gsi/client" async defer />
|
|
72
|
+
<script>$!{initScript}</script>
|
|
73
|
+
</if>
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// Compiled using marko@4.20.2 - DO NOT EDIT
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
var marko_template = module.exports = require("marko/dist/html").t(__filename),
|
|
5
|
+
marko_componentType = "/@mindful-web/marko-web-identity-x$1.77.0/components/google-init.marko",
|
|
6
|
+
marko_renderer = require("marko/dist/runtime/components/renderer"),
|
|
7
|
+
marko_attr = require("marko/dist/runtime/html/helpers/attr"),
|
|
8
|
+
marko_str = require("marko/dist/runtime/helpers/to-string");
|
|
9
|
+
|
|
10
|
+
function render(input, out, __component, component, state) {
|
|
11
|
+
var data = input;
|
|
12
|
+
|
|
13
|
+
const { req } = out.global;
|
|
14
|
+
|
|
15
|
+
const config = req.identityX && req.identityX.config;
|
|
16
|
+
|
|
17
|
+
const { clientId, autoPrompt } = config ? config.getAsObject('googleAuth') : {};
|
|
18
|
+
|
|
19
|
+
const callbackName = 'handleGoogleOneTapCredential';
|
|
20
|
+
|
|
21
|
+
const initScript = `
|
|
22
|
+
(function() {
|
|
23
|
+
window.${callbackName} = function(response) {
|
|
24
|
+
var redirectTo = window.__googleSignInRedirectTo || '';
|
|
25
|
+
var errorEl = document.getElementById('google-sign-in-error');
|
|
26
|
+
if (errorEl) errorEl.style.display = 'none';
|
|
27
|
+
|
|
28
|
+
// GIS 'select_by' distinguishes the surface: a 'btn*' value means the
|
|
29
|
+
// rendered "Sign in with Google" button, 'user*'/'auto' means the One-Tap
|
|
30
|
+
// prompt. Used to label the p1events authentication event accordingly.
|
|
31
|
+
var eventLabel = String(response.select_by || '').indexOf('btn') === 0
|
|
32
|
+
? 'Google Sign In'
|
|
33
|
+
: 'Google One Tap';
|
|
34
|
+
|
|
35
|
+
fetch('/__idx/google', {
|
|
36
|
+
method: 'POST',
|
|
37
|
+
headers: { 'Content-Type': 'application/json' },
|
|
38
|
+
body: JSON.stringify({ credential: response.credential, redirectTo: redirectTo }),
|
|
39
|
+
})
|
|
40
|
+
.then(function(res) { return res.json(); })
|
|
41
|
+
.then(function(data) {
|
|
42
|
+
if (!data.ok) {
|
|
43
|
+
if (errorEl) {
|
|
44
|
+
errorEl.textContent = data.error || 'Google sign-in failed. Please try again.';
|
|
45
|
+
errorEl.style.display = 'block';
|
|
46
|
+
}
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
if (window.p1events) {
|
|
50
|
+
// De-anonymize the session to the app user, then record the
|
|
51
|
+
// authentication — mirrors the identity-x conversion events.
|
|
52
|
+
window.p1events('setIdentity', data.entity);
|
|
53
|
+
window.p1events('track', {
|
|
54
|
+
category: 'Identity',
|
|
55
|
+
action: 'Authenticate',
|
|
56
|
+
label: eventLabel,
|
|
57
|
+
props: { idxEntity: data.entity, actionSource: 'google-one-tap' },
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
if (data.requiresUserInput) {
|
|
61
|
+
var destination = data.redirectTo || window.location.pathname;
|
|
62
|
+
window.location.href = '/user/profile?returnTo=' + encodeURIComponent(destination);
|
|
63
|
+
} else {
|
|
64
|
+
window.location.href = data.redirectTo || window.location.href;
|
|
65
|
+
}
|
|
66
|
+
})
|
|
67
|
+
.catch(function() {
|
|
68
|
+
if (errorEl) {
|
|
69
|
+
errorEl.textContent = 'An error occurred during Google sign-in. Please try again.';
|
|
70
|
+
errorEl.style.display = 'block';
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
};
|
|
74
|
+
})();
|
|
75
|
+
`;
|
|
76
|
+
|
|
77
|
+
if (clientId) {
|
|
78
|
+
out.w("<div id=\"g_id_onload\"" +
|
|
79
|
+
marko_attr("data-client_id", clientId) +
|
|
80
|
+
marko_attr("data-callback", callbackName) +
|
|
81
|
+
marko_attr("data-auto_prompt", autoPrompt ? "true" : "false") +
|
|
82
|
+
" data-auto_select=\"false\" data-cancel_on_tap_outside=\"true\" data-itp_support=\"true\"></div><script src=\"https://accounts.google.com/gsi/client\" async defer></script><script>" +
|
|
83
|
+
marko_str(initScript) +
|
|
84
|
+
"</script>");
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
marko_template._ = marko_renderer(render, {
|
|
89
|
+
d_: true,
|
|
90
|
+
e_: marko_componentType
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
marko_template.meta = {
|
|
94
|
+
id: "/@mindful-web/marko-web-identity-x$1.77.0/components/google-init.marko"
|
|
95
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
$ const { req } = out.global;
|
|
2
|
+
$ const config = req.identityX && req.identityX.config;
|
|
3
|
+
$ const googleAuth = config ? config.getAsObject('googleAuth') : {};
|
|
4
|
+
$ const clientId = input.clientId || googleAuth.clientId || '';
|
|
5
|
+
$ const redirectTo = input.redirectTo || '';
|
|
6
|
+
$ const label = input.label || 'Or sign in with Google';
|
|
7
|
+
$ const signInButtonEnabled = googleAuth.signInButtonEnabled !== false;
|
|
8
|
+
|
|
9
|
+
<if(clientId && signInButtonEnabled)>
|
|
10
|
+
<div class="google-sign-in">
|
|
11
|
+
<p class="google-sign-in__divider">${label}</p>
|
|
12
|
+
<div
|
|
13
|
+
class="g_id_signin"
|
|
14
|
+
data-type="standard"
|
|
15
|
+
data-theme="outline"
|
|
16
|
+
data-text="signin_with"
|
|
17
|
+
data-shape="rectangular"
|
|
18
|
+
data-logo_alignment="left"
|
|
19
|
+
data-width="300"
|
|
20
|
+
/>
|
|
21
|
+
<p id="google-sign-in-error" class="google-sign-in__error" style="display:none;" role="alert" />
|
|
22
|
+
<script>window.__googleSignInRedirectTo = $!{JSON.stringify(redirectTo)};</script>
|
|
23
|
+
</div>
|
|
24
|
+
</if>
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// Compiled using marko@4.20.2 - DO NOT EDIT
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
var marko_template = module.exports = require("marko/dist/html").t(__filename),
|
|
5
|
+
marko_componentType = "/@mindful-web/marko-web-identity-x$1.77.0/components/google-sign-in-button.marko",
|
|
6
|
+
marko_renderer = require("marko/dist/runtime/components/renderer"),
|
|
7
|
+
helpers_escape_xml = require("marko/dist/runtime/html/helpers/escape-xml"),
|
|
8
|
+
marko_escapeXml = helpers_escape_xml.x,
|
|
9
|
+
marko_escapeScript = require("marko/dist/runtime/html/helpers/escape-script-placeholder");
|
|
10
|
+
|
|
11
|
+
function render(input, out, __component, component, state) {
|
|
12
|
+
var data = input;
|
|
13
|
+
|
|
14
|
+
const { req } = out.global;
|
|
15
|
+
|
|
16
|
+
const config = req.identityX && req.identityX.config;
|
|
17
|
+
|
|
18
|
+
const googleAuth = config ? config.getAsObject('googleAuth') : {};
|
|
19
|
+
|
|
20
|
+
const clientId = input.clientId || googleAuth.clientId || '';
|
|
21
|
+
|
|
22
|
+
const redirectTo = input.redirectTo || '';
|
|
23
|
+
|
|
24
|
+
const label = input.label || 'Or sign in with Google';
|
|
25
|
+
|
|
26
|
+
const signInButtonEnabled = googleAuth.signInButtonEnabled !== false;
|
|
27
|
+
|
|
28
|
+
if (clientId && signInButtonEnabled) {
|
|
29
|
+
out.w("<div class=\"google-sign-in\"><p class=\"google-sign-in__divider\">" +
|
|
30
|
+
marko_escapeXml(label) +
|
|
31
|
+
"</p><div class=\"g_id_signin\" data-type=\"standard\" data-theme=\"outline\" data-text=\"signin_with\" data-shape=\"rectangular\" data-logo_alignment=\"left\" data-width=\"300\"></div><p id=\"google-sign-in-error\" class=\"google-sign-in__error\" style=\"display:none;\" role=\"alert\"></p><script>" +
|
|
32
|
+
marko_escapeScript(("window.__googleSignInRedirectTo = " + JSON.stringify(redirectTo)) + ";") +
|
|
33
|
+
"</script></div>");
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
marko_template._ = marko_renderer(render, {
|
|
38
|
+
d_: true,
|
|
39
|
+
e_: marko_componentType
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
marko_template.meta = {
|
|
43
|
+
id: "/@mindful-web/marko-web-identity-x$1.77.0/components/google-sign-in-button.marko"
|
|
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.77.0/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.77.0/components/identify.marko",
|
|
54
54
|
component: "./identify.marko",
|
|
55
55
|
tags: [
|
|
56
56
|
"@mindful-web/marko-web-gtm/components/push.marko",
|
package/components/marko.json
CHANGED
|
@@ -106,5 +106,14 @@
|
|
|
106
106
|
"<marko-web-identity-x-identify>": {
|
|
107
107
|
"template": "./identify.marko",
|
|
108
108
|
"@provider-data": "object"
|
|
109
|
+
},
|
|
110
|
+
"<marko-web-identity-x-google-init>": {
|
|
111
|
+
"template": "./google-init.marko"
|
|
112
|
+
},
|
|
113
|
+
"<marko-web-identity-x-google-sign-in-button>": {
|
|
114
|
+
"template": "./google-sign-in-button.marko",
|
|
115
|
+
"@client-id": "string",
|
|
116
|
+
"@redirect-to": "string",
|
|
117
|
+
"@label": "string"
|
|
109
118
|
}
|
|
110
119
|
}
|
|
@@ -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.77.0/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.77.0/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.77.0/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.77.0/components/subscribe.marko",
|
|
77
77
|
component: "./subscribe.marko",
|
|
78
78
|
tags: [
|
|
79
79
|
"@mindful-web/marko-web/components/browser-component.marko",
|
package/config.js
CHANGED
|
@@ -16,6 +16,13 @@ class IdentityXConfiguration {
|
|
|
16
16
|
* @param {string[]} [options.activeCustomFieldIds] Limit displayed custom fields, if present.
|
|
17
17
|
* @param {string[]} [options.hiddenFields] The fields to hide from the profile.
|
|
18
18
|
* @param {function} [options.onHookError]
|
|
19
|
+
* @param {object} [options.googleAuth] Google Sign-In config. When `clientId` is
|
|
20
|
+
* set, the One-Tap prompt / "Sign in with Google" button render in the auth
|
|
21
|
+
* templates and `POST /__idx/google` accepts credentials. Dormant otherwise.
|
|
22
|
+
* @param {string} [options.googleAuth.clientId] GIS OAuth client id — the master gate.
|
|
23
|
+
* @param {("profile-gate"|"immediate"|"relaxed")} [options.googleAuth.missingFieldsBehavior]
|
|
24
|
+
* @param {boolean} [options.googleAuth.autoPrompt] Show the floating One-Tap overlay.
|
|
25
|
+
* @param {boolean} [options.googleAuth.signInButtonEnabled] Render the button (default true).
|
|
19
26
|
* @param {object} options.rest
|
|
20
27
|
*/
|
|
21
28
|
constructor({
|
|
@@ -31,6 +38,7 @@ class IdentityXConfiguration {
|
|
|
31
38
|
defaultCountryCode,
|
|
32
39
|
booleanQuestionsLabel,
|
|
33
40
|
gtmUserFields = {},
|
|
41
|
+
googleAuth = {},
|
|
34
42
|
onHookError,
|
|
35
43
|
...rest
|
|
36
44
|
} = {}) {
|
|
@@ -49,6 +57,7 @@ class IdentityXConfiguration {
|
|
|
49
57
|
defaultCountryCode,
|
|
50
58
|
booleanQuestionsLabel,
|
|
51
59
|
gtmUserFields,
|
|
60
|
+
googleAuth,
|
|
52
61
|
onHookError: (e) => {
|
|
53
62
|
if (process.env.NODE_ENV === 'development') {
|
|
54
63
|
log('ERROR IN IDENTITY-X HOOK', e);
|
|
@@ -141,6 +150,14 @@ class IdentityXConfiguration {
|
|
|
141
150
|
return this.getAsArray('activeCustomFieldIds');
|
|
142
151
|
}
|
|
143
152
|
|
|
153
|
+
/**
|
|
154
|
+
* The Google Sign-In config block. Empty object when unconfigured, so a missing
|
|
155
|
+
* `clientId` reads as falsy and the feature stays dormant.
|
|
156
|
+
*/
|
|
157
|
+
getGoogleAuth() {
|
|
158
|
+
return this.getAsObject('googleAuth');
|
|
159
|
+
}
|
|
160
|
+
|
|
144
161
|
getAdditionalCustomFieldIds() {
|
|
145
162
|
return this.getAsArray('additionalCustomFieldIds');
|
|
146
163
|
}
|
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.77.0",
|
|
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>",
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
"cookie": "0.3.1",
|
|
25
25
|
"dayjs": "^1.11.7",
|
|
26
26
|
"express": "^4.18.2",
|
|
27
|
+
"google-auth-library": "^9.0.0",
|
|
27
28
|
"graphql-tag": "^2.12.6",
|
|
28
29
|
"i18n-iso-countries": "^4.3.1",
|
|
29
30
|
"jsonwebtoken": "^8.5.1",
|
|
@@ -37,5 +38,5 @@
|
|
|
37
38
|
"publishConfig": {
|
|
38
39
|
"access": "public"
|
|
39
40
|
},
|
|
40
|
-
"gitHead": "
|
|
41
|
+
"gitHead": "c8c0dca117cb9c8f76ae5e8d5786cc99904a0da3"
|
|
41
42
|
}
|
package/routes/google.js
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
const gql = require('graphql-tag');
|
|
2
|
+
const { OAuth2Client } = require('google-auth-library');
|
|
3
|
+
const { asyncRoute } = require('@mindful-web/utils');
|
|
4
|
+
const IMPERSONATE_APP_USER = require('../api/mutations/impersonate-app-user');
|
|
5
|
+
const tokenCookie = require('../utils/token-cookie');
|
|
6
|
+
const contextCookie = require('../utils/context-cookie');
|
|
7
|
+
const callHooksFor = require('../utils/call-hooks-for');
|
|
8
|
+
|
|
9
|
+
const UPDATE_OWN_APP_USER = gql`
|
|
10
|
+
mutation GoogleSignInUpdateUser($input: UpdateOwnAppUserMutationInput!) {
|
|
11
|
+
updateOwnAppUser(input: $input) { id givenName familyName }
|
|
12
|
+
}
|
|
13
|
+
`;
|
|
14
|
+
|
|
15
|
+
const isEmpty = (v) => v == null || v === '';
|
|
16
|
+
|
|
17
|
+
const LOGIN_SOURCE = 'google-one-tap';
|
|
18
|
+
|
|
19
|
+
// Namespace under which the Google account id (the JWT `sub`) is stored as an
|
|
20
|
+
// idx external id, mirroring the Omeda { provider, tenant, type } convention.
|
|
21
|
+
// These values must remain stable — changing them orphans existing links.
|
|
22
|
+
const GOOGLE_NAMESPACE = { provider: 'google', tenant: 'oauth', type: 'account' };
|
|
23
|
+
|
|
24
|
+
// eslint-disable-next-line no-console
|
|
25
|
+
const defaultOnError = (e) => console.error('[identity-x google-auth]', e);
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* `POST /__idx/google` — the Google Sign-In route. Handles both the One-Tap
|
|
29
|
+
* prompt and the "Sign in with Google" button; both surfaces post here.
|
|
30
|
+
*
|
|
31
|
+
* Verifies the Google credential, finds-or-creates the IdentityX app user, links
|
|
32
|
+
* the Google account id (`sub`) as an external id, impersonates the user to mint
|
|
33
|
+
* a session token, and fires the standard `onLoginLinkSent` /
|
|
34
|
+
* `onAuthenticationSuccess` hooks so downstream integrations (e.g. Omeda
|
|
35
|
+
* rapid-identify + subscription sync) run exactly as they do for email-link
|
|
36
|
+
* users.
|
|
37
|
+
*
|
|
38
|
+
* Configuration lives on the IdentityX config under the `googleAuth` key
|
|
39
|
+
* (`{ clientId, missingFieldsBehavior, autoPrompt, signInButtonEnabled }`). The
|
|
40
|
+
* route is dormant until `clientId` is set: no client id ⇒ 400.
|
|
41
|
+
*
|
|
42
|
+
* Non-fatal errors from the supplementary `sub`-linkage are reported through the
|
|
43
|
+
* IdentityX config's `onHookError` (the same reporter sites already wire to
|
|
44
|
+
* newrelic for hook failures), falling back to `console.error`.
|
|
45
|
+
*/
|
|
46
|
+
module.exports = asyncRoute(async (req, res) => {
|
|
47
|
+
/** @type {import('../service')} */
|
|
48
|
+
const { identityX, body } = req;
|
|
49
|
+
const { credential, redirectTo } = body;
|
|
50
|
+
const { config } = identityX;
|
|
51
|
+
const onError = config.get('onHookError') || defaultOnError;
|
|
52
|
+
|
|
53
|
+
const { clientId, missingFieldsBehavior = 'profile-gate' } = config.getAsObject('googleAuth');
|
|
54
|
+
|
|
55
|
+
if (!clientId) {
|
|
56
|
+
res.status(400).json({ ok: false, error: 'Google Sign-In is not configured for this site.' });
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
if (!credential) {
|
|
60
|
+
res.status(400).json({ ok: false, error: 'No Google credential was provided.' });
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Verify the Google JWT and extract user info.
|
|
65
|
+
const oauthClient = new OAuth2Client(clientId);
|
|
66
|
+
const ticket = await oauthClient.verifyIdToken({ idToken: credential, audience: clientId });
|
|
67
|
+
const googlePayload = ticket.getPayload();
|
|
68
|
+
const {
|
|
69
|
+
email,
|
|
70
|
+
given_name: givenName,
|
|
71
|
+
family_name: familyName,
|
|
72
|
+
sub: googleSub,
|
|
73
|
+
} = googlePayload;
|
|
74
|
+
|
|
75
|
+
if (!email) throw new Error('Google credential did not include an email address.');
|
|
76
|
+
|
|
77
|
+
// Find or create the identity-x user.
|
|
78
|
+
let appUser = await identityX.loadAppUserByEmail(email);
|
|
79
|
+
const createdNewUser = !appUser;
|
|
80
|
+
if (!appUser) {
|
|
81
|
+
appUser = await identityX.createAppUser({ email });
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Persist the Google account id (the JWT `sub`) as an idx external id so the
|
|
85
|
+
// user is linked to its Google identity. Email stays the primary key; this is
|
|
86
|
+
// supplementary provenance and must never block sign-in.
|
|
87
|
+
if (googleSub) {
|
|
88
|
+
const alreadyLinked = (appUser.externalIds || []).some((extId) => (
|
|
89
|
+
extId.namespace
|
|
90
|
+
&& extId.namespace.provider === GOOGLE_NAMESPACE.provider
|
|
91
|
+
&& extId.identifier
|
|
92
|
+
&& extId.identifier.value === googleSub
|
|
93
|
+
));
|
|
94
|
+
if (!alreadyLinked) {
|
|
95
|
+
try {
|
|
96
|
+
// If this sub is already attached to a different user (e.g. the Google
|
|
97
|
+
// account's email changed and a new user was created under it), report
|
|
98
|
+
// and skip rather than fanning the sub across two users.
|
|
99
|
+
const existing = await identityX.findUserByExternalId({
|
|
100
|
+
identifier: googleSub,
|
|
101
|
+
namespace: GOOGLE_NAMESPACE,
|
|
102
|
+
type: 'sub',
|
|
103
|
+
});
|
|
104
|
+
if (existing && existing.id !== appUser.id) {
|
|
105
|
+
onError(new Error(
|
|
106
|
+
`Google sub already linked to idx user ${existing.id}, not ${appUser.id}`,
|
|
107
|
+
));
|
|
108
|
+
} else {
|
|
109
|
+
await identityX.addExternalUserId({
|
|
110
|
+
userId: appUser.id,
|
|
111
|
+
identifier: { value: googleSub, type: 'sub' },
|
|
112
|
+
namespace: GOOGLE_NAMESPACE,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
} catch (e) {
|
|
116
|
+
// Supplementary linkage — never block authentication on a failure here.
|
|
117
|
+
onError(e);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Impersonate the user with verify: true — marks them as verified and returns
|
|
123
|
+
// a session token, bypassing the normal email-link flow.
|
|
124
|
+
const { data: impersonateData } = await identityX.client.mutate({
|
|
125
|
+
mutation: IMPERSONATE_APP_USER,
|
|
126
|
+
variables: {
|
|
127
|
+
input: {
|
|
128
|
+
id: appUser.id,
|
|
129
|
+
method: 'GOOGLE_ONE_TAP',
|
|
130
|
+
verify: true,
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
context: { apiToken: identityX.getOrgUserApiToken() },
|
|
134
|
+
});
|
|
135
|
+
const { token: authToken } = impersonateData.impersonateAppUser;
|
|
136
|
+
|
|
137
|
+
tokenCookie.setTo(res, authToken.value);
|
|
138
|
+
contextCookie.setTo(res, { loginSource: LOGIN_SOURCE });
|
|
139
|
+
identityX.setIdentityCookie(appUser.id);
|
|
140
|
+
|
|
141
|
+
// Re-init the Apollo client with the user's own session token so that
|
|
142
|
+
// updateOwnAppUser is authenticated as this user (not as the org admin).
|
|
143
|
+
identityX.setToken(authToken);
|
|
144
|
+
|
|
145
|
+
// Set name fields from Google as verified data if not already present.
|
|
146
|
+
const needsNameUpdate = !appUser.givenName || !appUser.familyName;
|
|
147
|
+
if (needsNameUpdate && (givenName || familyName)) {
|
|
148
|
+
const nameInput = {};
|
|
149
|
+
if (givenName && !appUser.givenName) nameInput.givenName = givenName;
|
|
150
|
+
if (familyName && !appUser.familyName) nameInput.familyName = familyName;
|
|
151
|
+
await identityX.client.mutate({
|
|
152
|
+
mutation: UPDATE_OWN_APP_USER,
|
|
153
|
+
variables: { input: nameInput },
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Load the refreshed user so hooks and field checks have up-to-date data.
|
|
158
|
+
const user = await identityX.findUserById(appUser.id);
|
|
159
|
+
const entity = await identityX.generateEntityId({ userId: appUser.id });
|
|
160
|
+
|
|
161
|
+
// For profile-gate: check whether the user is actually missing required fields
|
|
162
|
+
// rather than using forceProfileReVerification, which isn't cleared by a normal
|
|
163
|
+
// /user/profile submission and would redirect complete users on every sign-in.
|
|
164
|
+
let requiresUserInput = false;
|
|
165
|
+
if (missingFieldsBehavior === 'profile-gate') {
|
|
166
|
+
const requiredFields = [
|
|
167
|
+
...config.getRequiredServerFields(),
|
|
168
|
+
...config.getRequiredClientFields(),
|
|
169
|
+
];
|
|
170
|
+
requiresUserInput = requiredFields.some((key) => isEmpty(user[key]));
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Fire onLoginLinkSent before onAuthenticationSuccess so that hooks wired to
|
|
174
|
+
// it (e.g. Omeda rapid-identify and subscription sync) run for Google users
|
|
175
|
+
// the same way they do for email-link users. This also populates
|
|
176
|
+
// user.externalIds with the Omeda encryptedCustomerId so the subsequent
|
|
177
|
+
// onAuthenticationSuccess hook can set oly_enc_id in the response.
|
|
178
|
+
await callHooksFor(identityX, 'onLoginLinkSent', {
|
|
179
|
+
req,
|
|
180
|
+
source: LOGIN_SOURCE,
|
|
181
|
+
user,
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
// Re-fetch so onAuthenticationSuccess sees the externalIds written by
|
|
185
|
+
// the onLoginLinkSent hook (Omeda rapid-identify).
|
|
186
|
+
const userAfterLinkSent = await identityX.findUserById(appUser.id);
|
|
187
|
+
|
|
188
|
+
await callHooksFor(identityX, 'onAuthenticationSuccess', {
|
|
189
|
+
req,
|
|
190
|
+
res,
|
|
191
|
+
user: userAfterLinkSent,
|
|
192
|
+
authToken,
|
|
193
|
+
loginSource: LOGIN_SOURCE,
|
|
194
|
+
additionalEventData: { createdNewUser, requiresUserInput },
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
res.json({
|
|
198
|
+
ok: true,
|
|
199
|
+
requiresUserInput,
|
|
200
|
+
redirectTo: redirectTo || null,
|
|
201
|
+
applicationId: config.getAppId(),
|
|
202
|
+
user: userAfterLinkSent,
|
|
203
|
+
loginSource: LOGIN_SOURCE,
|
|
204
|
+
entity,
|
|
205
|
+
});
|
|
206
|
+
});
|
package/routes/index.js
CHANGED
|
@@ -11,6 +11,7 @@ const countries = require('./countries');
|
|
|
11
11
|
const createComment = require('./create-comment');
|
|
12
12
|
const download = require('./download');
|
|
13
13
|
const flagComment = require('./flag-comment');
|
|
14
|
+
const google = require('./google');
|
|
14
15
|
const login = require('./login');
|
|
15
16
|
const loginFields = require('./login-fields');
|
|
16
17
|
const logout = require('./logout');
|
|
@@ -31,6 +32,7 @@ router.post('/change-email/initiate', changeEmailInit);
|
|
|
31
32
|
router.post('/comment', createComment);
|
|
32
33
|
router.post('/comment/flag/:id', flagComment);
|
|
33
34
|
router.post('/download', download);
|
|
35
|
+
router.post('/google', google);
|
|
34
36
|
router.post('/login-fields', loginFields);
|
|
35
37
|
router.post('/login', login);
|
|
36
38
|
router.post('/logout', logout);
|
package/routes/logout.js
CHANGED
|
@@ -7,6 +7,11 @@ module.exports = asyncRoute(async (req, res) => {
|
|
|
7
7
|
/** @type {import('../middleware').IdentityXRequest} */
|
|
8
8
|
const { identityX } = req;
|
|
9
9
|
contextCookie.removeFrom(res);
|
|
10
|
+
// Clear the Google Identity Services `g_state` cookie so a subsequent Google
|
|
11
|
+
// sign-in (especially with a *different* account) isn't blocked by stale
|
|
12
|
+
// auto-select state. GIS only loads for unauthenticated users, so nothing else
|
|
13
|
+
// resets this on logout. Harmless no-op for sites not using Google Sign-In.
|
|
14
|
+
res.clearCookie('g_state', { path: '/' });
|
|
10
15
|
const token = tokenCookie.getFrom(req);
|
|
11
16
|
if (!token) {
|
|
12
17
|
await callHooksFor(identityX, 'onLogout', { req, res });
|