# Mindful Web Marko Wrapper for IdentityX

## Installation

1. Include `@mindful-web/marko-web-identity-x` as a project/website dependency.

2. Include IdentityX tenant configuration within your site
```js
// your-site/config/identity-x.js
const IdentityX = require('@mindful-web/marko-web-identity-x/config');

const config = new IdentityX({
  appId: '<MY-APPLICATION-ID>',
});
module.exports = config;
```

3. Create an IdentityX router to load the IdentityX middleware.
```js
// your-site/server/routes/identity-x.js
const IdentityX = require('@mindful-web/marko-web-identity-x');
const config = require('../../config/identity-x');
const authenticate = require('../templates/user/authenticate');
const login = require('../templates/user/login');
const logout = require('../templates/user/logout');
const register = require('../templates/user/register');
const profile = require('../templates/user/profile');

module.exports = (app) => {
  IdentityX(app, config);

  app.get(config.getEndpointFor('authenticate'), (req, res) => {
    res.marko(authenticate);
  });

  app.get(config.getEndpointFor('login'), (req, res) => {
    res.marko(login);
  });

  app.get(config.getEndpointFor('logout'), (req, res) => {
    res.marko(logout);
  });

  app.get(config.getEndpointFor('register'), (req, res) => {
    res.marko(register);
  });

  app.get(config.getEndpointFor('profile'), (req, res) => {
    res.marko(profile);
  });
};
```

4. Include the IdentityX router **before all other routes!**
```js
// your-site/server/routes/index.js
const IdentityX = require('./identity-x');

module.exports = (app) => {
  IdentityX(app);
  // ...
};
```

5. Create `login`, `logout`, `authenticate`, `register` and `profile` templates. These templates must include the relevant `<marko-web-identity-x-form-...>` component.
```marko
<marko-web-default-page-layout>
  <@page>
    <marko-web-identity-x-form-authenticate />
  </@page>
</marko-web-default-page-layout>
```

5. Include the Browser plugin.
```js
// your-site/browser/index.js
import IdentityX from '@mindful-web/marko-web-identity-x/browser';

IdentityX(Browser);
// ...

export default Browser;
```

## Usage

Include the `<marko-web-identity-x-form-authenticate>` component in the template where users land after authenticating (/user/authenticate).

Include the `<marko-web-identity-x-form-login>` component to display the login form.

Include the `<marko-web-identity-x-form-register>` component to display the register form.

Include the `<marko-web-identity-x-form-logout>` component to display the logout form.

Include the `<marko-web-identity-x-form-profile>` component to display the user profile form.

Include the `<marko-web-identity-x-context>` component where you'd like access to IdentityX context.
```marko
<!-- your-site/server/templates/some-page.marko -->
<marko-web-identity-x-context|{ user, hasUser }|>
  <if(hasUser)>
    <h1>Hello ${user.givenName}!</h1>
  </if>
</marko-web-identity-x-context>
```

Include the `<marko-web-identity-x-access>` component where you'd like to ensure access levels are met:
```marko
<!-- your-site/server/templates/content/index.marko -->
$ const { isRequired, accessLevels } = getAsObject(content, 'userRegistration');
<marko-web-identity-x-access|context|
  enabled=isRequired
  required-access-level-ids=accessLevels
>
  $ const {
    canAccess,
    isLoggedIn,
    requiresAccessLevel,
    hasRequiredAccessLevel,
    messages,
  } = context;
  <if(!canAccess)>
    <if(isLoggedIn && !hasRequiredAccessLevel)>
      $!{messages.loggedInNoAccess}
    </if>
    <else-if(!isLoggedIn && requiresAccessLevel)>
      $!{messages.loggedOutNoAccess}
    </else-if>
    <else-if(!isLoggedIn)>
      <h5>You must be logged-in to access this content.</h5>
      <cms-browser-component name="IdentitySignInForm" />
    </else-if>
  </if>
  <else>
    <p>This is secret content only some can see!</p>
  </else>
</marko-web-identity-x-access>
```

## Customization

You can change the default IdentityX Vue components by passing them to the component loader in your site's browser config:
```diff
import IdentityX from '@mindful-web/marko-web-identity-x/browser';
+ import MyLoginComponent from './my-login-component.vue';

-IdentityX(Browser);
+IdentityX(Browser, {
+  CustomLoginComponent: MyLoginComponent,
+});
```

## Cookie lifetimes

All cookie durations in this package are expressed in **milliseconds**, because that is the unit
express's `res.cookie` `maxAge` option expects. Env vars and config keys that carry a duration are
suffixed `_MS` / `Ms` so the unit is unambiguous at the call site.

| Variable | Applies to | Default | Notes |
| - | - | - | - |
| `IDX_CONTENT_ACCESS_MAXAGE_MS` | `__idx_form_<surveyId>_<contentId>` (content access) | `86400000` (1 day) | |
| `IDX_CONTENT_DOWNLOAD_MAXAGE_MS` | `__idx_form_content-download_<contentId>` | `86400000` (1 day) | |
| `IDX_CONTENT_ACCESS_MAXAGE` | — | — | **Deprecated** alias of the `_MS` var above. |
| `IDX_CONTENT_DOWNLOAD_MAXAGE` | — | — | **Deprecated** alias of the `_MS` var above. |

The unsuffixed names are still honored, but avoid them in new deployments: `MAXAGE` reads as the HTTP
`Max-Age` directive, which is in *seconds*, so a value of `86400` intended as "one day" silently
produced an 86-second cookie. Non-numeric or non-positive values are ignored with a warning and the
default applies, rather than sending `NaN` into `res.cookie` (which throws on every affected request).

## Vue Event emission

This package emits the following events via the EventBus/global Vue root. Each payload will be an
object and will include a `label` field tied to the `eventLabel` prop of the emitting component.
Each payload _may_ include additional data, as relevant.

All components support passing an object of `additionalEventData`, which when present will append
data both to the submission (for backend hook handling) and to the emitted event payload.
### Submission events

| Event name | Event payload | Details |
| - | - | - |
| `identity-x-login-link-sent` | `{ label, ...additionalEventData  }` | Fires when a user submits their email to start the login handshake.
| `identity-x-authenticated` | `{ label, ...additionalEventData, mustReVerifyProfile, isProfileComplete, requiresCustomFieldAnswers }` | Fires when a user has completed the login handshake and is now fully authenticated.
| `identity-x-logout` | `{ label, ...additionalEventData  }` | Fires when a user has logged out successfully.
| `identity-x-profile-updated` | `{ label, ...additionalEventData }` | Fires when a user has submitted an update to their profile/fields.
| `identity-x-comment-post-submitted` | `{ label, ...additionalEventData }` | Fires when a user posts a comment to a comment stream
| `identity-x-comment-report-submitted` | `{ label, ...additionalEventData, id }` | Fires when a user reports a comment on a comment stream
| `identity-x-comment-stream-login-link-sent` | `{ label, ...additionalEventData }` | Fires when a user starts login from a comment stream

### View events

Each component will emit an event when the component is displayed.

| Event name | Event payload | Details |
| - | - | - |
| `identity-x-authenticate-displayed` | `{ label, ...additionalEventData  }`
| `identity-x-login-displayed` | `{ label, ...additionalEventData }`
| `identity-x-logout-displayed` | `{ label, ...additionalEventData  }`
| `identity-x-profile-displayed` | `{ label, ...additionalEventData }`
| `identity-x-comment-stream-displayed` | `{ label, ...additionalEventData }`
| `identity-x-comment-stream-loaded` | `{ label, ...additionalEventData }`
| `identity-x-comment-stream-loaded-more` | `{ label, ...additionalEventData }`

### Error events

Each component will emit an event when an error is encountered and include the error message as the
`message` attribute of the emitted payload.

| Event name | Event payload |
| - | - |
| `identity-x-authenticate-errored` | `{ label, message: '...', ...additionalEventData }`
| `identity-x-login-errored` | `{ label, message: '...', ...additionalEventData }`
| `identity-x-logout-errored` | `{ label, message: '...', ...additionalEventData }`
| `identity-x-profile-errored` | `{ label, message: '...', ...additionalEventData }`
| `identity-x-comment-post-errored` | `{ label, message: '...', ...additionalEventData }`
| `identity-x-comment-report-errored` | `{ label, message: '...', ...additionalEventData }`

## Google Sign-In (One-Tap + button)

Built in. Verifies a Google Identity Services (GIS) credential server-side and drops the user
into the standard IdentityX session, bypassing the email-link step. Handles both the One-Tap
prompt and the rendered "Sign in with Google" button; both post to `POST /__idx/google`.

**Dormant until configured** — with no `clientId`, nothing renders and the route 400s. Enable
per-site by adding a `googleAuth` block to the IdentityX config:

```js
const config = new IdentityX({
  appId: '<MY-APPLICATION-ID>',
  googleAuth: {
    clientId: process.env.GOOGLE_AUTH_CLIENT_ID || '', // GIS OAuth client id — the master gate
    missingFieldsBehavior: 'profile-gate',             // 'profile-gate' | 'immediate' | 'relaxed'
    autoPrompt: false,                                 // floating One-Tap overlay
    signInButtonEnabled: true,                         // render the button
  },
});
```

Reading the values from env is optional — set literals here if you'd rather not manage infra
env vars. The client id's Google OAuth app must list each site origin as an **authorized
JavaScript origin**, or the prompt/button silently no-shows.

`missingFieldsBehavior`: `profile-gate` authenticates immediately but forces profile completion
before gated content; `immediate`/`relaxed` grant access with fields left blank (`relaxed` is a
doc-only alias of `immediate`).

**Rendering.** The button + GIS init are rendered automatically inside the `form-login` and
`form-authenticate` templates — no per-site placement, and both self-hide when unconfigured. For
the **site-wide floating One-Tap prompt** (`autoPrompt: true`), also drop
`<marko-web-identity-x-google-init/>` once in your document layout, gated on an unauthenticated
user, e.g. `<if(!req.identityX.token)> <marko-web-identity-x-google-init/> </if>`. The button tag
(`<marko-web-identity-x-google-sign-in-button redirect-to=... />`) can also be placed in a content meter
or gate if desired.

**CSS.** Styles ship with the rest of the auth CSS in `marko-web-theme-monorail`
(`scss/components/_identity-x.scss`) — no separate import.

**Logout.** The idx logout route clears the GIS `g_state` cookie so a later sign-in with a
*different* Google account isn't blocked by stale auto-select state (harmless no-op when Google
Sign-In isn't used).

The Google account id (JWT `sub`) is persisted as a supplementary idx external id under
`{ provider: 'google', tenant: 'oauth', type: 'account' }`; email stays the primary key.
Non-fatal linkage errors report through the config's `onHookError`.
| `identity-x-comment-stream-errored` | `{ label, message: '...', ...additionalEventData }`
