@merkaly/nuxt 0.11.3 → 0.11.5

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/README.md CHANGED
@@ -35,18 +35,11 @@ On boot the module restores an existing session (user and token) and then runs t
35
35
  | store | optional | required | `merkaly:tenant` resolves the store from the host (public); a signed-in buyer is restored too; protected pages (account, checkout) ask for login. |
36
36
  | www | optional | not used | No hooks; anonymous or signed-in. |
37
37
 
38
- ### Session lifetime and Auth0 setup for a new app
38
+ ### Session lifetime
39
39
 
40
- The SPA keeps the session with **refresh tokens** (`useRefreshTokens`, cached in `localStorage`, login with `offline_access`). The access token for the API lasts **2 hours** (`token_lifetime_for_web` of `https://api.merkaly.io`); before each API call the module asks Auth0 for the current token, which is cached and renewed with the refresh token once it expires. When the refresh token is gone or expired, protected surfaces (`requiresAuth: true`) go back to login instead of calling the API without a token.
40
+ The SPA keeps the session with **refresh tokens**: the client uses `useRefreshTokens` with `localStorage` cache and logs in with `audience` and `scope: openid profile email offline_access`. Before each API call the module asks Auth0 for the current access token, which is cached and renewed with the refresh token once it expires (2 hours for `https://api.merkaly.io`). When the refresh token is gone or expired, protected surfaces (`requiresAuth: true`) go back to login instead of calling the API without a token. Account linking (`linkWithConnection(connection)`) uses a separate Auth0 client, the account linking client (named `account.merkaly.io` in Auth0, no app behind it), with an in-memory cache, created only on the first call (no extra `checkSession` on page load). It opens a login popup for that connection and sends the popup's raw ID token to `POST /identities` as `{ idToken }`; the api verifies the token (signature, issuer, audience = the account linking client, expiry) and links the identity from its `sub` into the logged-in user. Requires an api with that contract (older clients that sent `{ provider, userId }` get `400`).
41
41
 
42
- Every new Nuxt app needs this in Auth0 (done for admin, dashboard, store and www on 2026-11-04):
43
-
44
- 1. **Application** (type SPA): allowed callback (`/auth`), logout and web origin URLs for each environment.
45
- 2. **Refresh Token Rotation: on**, **Refresh Token Expiration: on**, with idle lifetime `2592000` (30 days) and maximum lifetime `7776000` (90 days). Never non-rotating or non-expiring.
46
- 3. **API `https://api.merkaly.io`**: "Allow Offline Access" stays **on**; without it Auth0 issues no refresh tokens and every session ends after 2 hours.
47
- 4. **Organizations**: apps that work inside a store (admin) require an organization at login; staff-only apps are filtered by the "Enforce surface access" post-login Action (role `MK_*`).
48
-
49
- These settings can be read and changed through the Management API (`merkaly.us.auth0.com`, the canonical domain, never the custom `auth.merkaly.io`).
42
+ The Auth0 side (tenant, API, app settings, refresh token rotation and lifetimes, organizations, and the checklist for a new app) is documented in the auth.merkaly.io repo: [`docs/tenant-setup.md`](https://github.com/merkaly-io/auth.merkaly.io/blob/master/docs/tenant-setup.md).
50
43
 
51
44
  ## SEO: robots and sitemap
52
45
 
package/dist/module.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "compatibility": {
5
5
  "nuxt": ">=3.14.0"
6
6
  },
7
- "version": "0.11.3",
7
+ "version": "0.11.5",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "1.0.2",
10
10
  "unbuild": "3.6.1"
@@ -1,6 +1,7 @@
1
+ import { Auth0Client } from '@auth0/auth0-spa-js';
1
2
  declare const _default: import("#app").Plugin<{
2
- auth0: import("@auth0/auth0-spa-js").Auth0Client;
3
+ auth0: Auth0Client;
3
4
  }> & import("#app").ObjectPlugin<{
4
- auth0: import("@auth0/auth0-spa-js").Auth0Client;
5
+ auth0: Auth0Client;
5
6
  }>;
6
7
  export default _default;
@@ -1,4 +1,4 @@
1
- import { createAuth0Client } from "@auth0/auth0-spa-js";
1
+ import { Auth0Client, createAuth0Client } from "@auth0/auth0-spa-js";
2
2
  import { defineNuxtPlugin, useRuntimeConfig, useNuxtApp, useState } from "#imports";
3
3
  import { navigateTo } from "#app";
4
4
  import { defu } from "defu";
@@ -47,6 +47,9 @@ export default defineNuxtPlugin(async ({ callHook, hook }) => {
47
47
  return auth0.loginWithRedirect({ appState: { target: location.pathname + location.search } });
48
48
  }
49
49
  }
50
+ if (!user.value && $config.merkaly.auth0.requiresAuth) {
51
+ return auth0.loginWithRedirect({ appState: { target: location.pathname + location.search } });
52
+ }
50
53
  return initTenantContext();
51
54
  };
52
55
  hook("merkaly:auth:retry", () => resolveIdentity().then(startSession));
@@ -68,27 +71,30 @@ export default defineNuxtPlugin(async ({ callHook, hook }) => {
68
71
  returnTo: URL.canParse($config.merkaly.auth0.logoutUrl) ? $config.merkaly.auth0.logoutUrl : location.origin.concat($config.merkaly.auth0.logoutUrl)
69
72
  }
70
73
  });
71
- const callbackUrl = $config.merkaly.auth0.callbackUrl;
72
- const redirectUri = URL.canParse(callbackUrl) ? callbackUrl : `${location.origin}${callbackUrl}`;
73
- const linkingClient = await createAuth0Client({
74
+ let linkingClient;
75
+ const getLinkingClient = () => linkingClient ??= new Auth0Client({
76
+ authorizationParams: {
77
+ redirect_uri: URL.canParse($config.merkaly.auth0.callbackUrl) ? $config.merkaly.auth0.callbackUrl : location.origin.concat($config.merkaly.auth0.callbackUrl)
78
+ },
74
79
  cacheLocation: "memory",
75
80
  clientId: "AwD3uBHhLhFBbJhwSWXYFh9cZYidNc6L",
76
- domain: $config.merkaly.auth0.domain,
77
- authorizationParams: {
78
- redirect_uri: redirectUri
79
- }
81
+ // account.merkaly.io, the api checks this audience
82
+ domain: $config.merkaly.auth0.domain
80
83
  });
81
84
  auth0.linkWithConnection = async (connection) => {
82
- await linkingClient.loginWithPopup({ authorizationParams: { connection } });
83
- const claims = await linkingClient.getIdTokenClaims();
84
- if (!claims?.sub) {
85
+ const client = getLinkingClient();
86
+ await client.loginWithPopup({ authorizationParams: { connection } });
87
+ const claims = await client.getIdTokenClaims();
88
+ if (!claims?.__raw) {
85
89
  throw new Error("Failed to get ID token for linking");
86
90
  }
87
- const [provider, ...userIdParts] = claims.sub.split("|");
88
- const userId = userIdParts.join("|");
89
- const body = { provider, userId };
90
91
  const { $api } = useNuxtApp();
91
- return await $api("/identities", { body, global: true, method: "POST" });
92
+ return await $api("/identities", {
93
+ // the api verifies it and derives the identity
94
+ body: { idToken: claims.__raw },
95
+ global: true,
96
+ method: "POST"
97
+ });
92
98
  };
93
99
  const isAuthCallback = window.location.pathname === callbackPath;
94
100
  if (isAuthCallback) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@merkaly/nuxt",
3
- "version": "0.11.3",
3
+ "version": "0.11.5",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/merkaly-io/nuxt.git"