@authup/client-web-nuxt 1.0.0-beta.62 → 1.0.0-beta.64

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/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@authup/client-web-nuxt",
3
3
  "configKey": "authup",
4
- "version": "1.0.0-beta.62",
4
+ "version": "1.0.0-beta.64",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.3",
7
7
  "unbuild": "3.6.1"
package/dist/module.mjs CHANGED
@@ -13,14 +13,7 @@ const module$1 = defineNuxtModule({
13
13
  const runtimeDir = fileURLToPath(new URL("./runtime", import.meta.url));
14
14
  nuxt.options.build.transpile.push(runtimeDir);
15
15
  const resolver = createResolver(import.meta.url);
16
- const runtimeOptions = {
17
- apiURL: options.apiURL,
18
- apiURLRuntimeKey: options.apiURLRuntimeKey,
19
- cookieDomain: options.cookieDomain,
20
- cookieDomainRuntimeKey: options.cookieDomainRuntimeKey,
21
- homeRoute: options.homeRoute,
22
- loginRoute: options.loginRoute
23
- };
16
+ const runtimeOptions = { ...options };
24
17
  nuxt.options.runtimeConfig.public = nuxt.options.runtimeConfig.public || {};
25
18
  if (nuxt.options.runtimeConfig.public.authup) {
26
19
  nuxt.options.runtimeConfig.public.authup = merge(
@@ -8,6 +8,27 @@ export declare class RoutingInterceptor {
8
8
  protected loginRoute: string;
9
9
  constructor(nuxtApp: NuxtApp);
10
10
  execute(to: RouteLocationNormalized, from: RouteLocationNormalized): Promise<RouteLocationAsPathGeneric | undefined>;
11
+ /**
12
+ * Resolve a post-login destination, or undefined when the value is not a
13
+ * site-relative path.
14
+ *
15
+ * The base is a fixed dummy origin, so a value that lands anywhere else
16
+ * declared an authority of its own and is refused. That covers the whole
17
+ * family in one comparison — `//host`, `https://host`, the `\`-for-`/`
18
+ * variants a prefix check misses, and non-special schemes, which resolve
19
+ * to the opaque `"null"` origin.
20
+ */
21
+ protected resolveDestination(value: string): URL | undefined;
22
+ protected withoutAuthorizationParams(route: RouteLocationNormalized): RouteLocationAsPathGeneric;
23
+ /**
24
+ * Return to a route the user already had.
25
+ *
26
+ * Passing `fullPath` as `path` looks equivalent and is not: vue-router
27
+ * runs it through `parseURL`, which keeps the path and silently discards
28
+ * the query and hash, so a bounce off `/users?page=2#row-7` used to land
29
+ * on a bare `/users`.
30
+ */
31
+ protected backTo(route: RouteLocationNormalized): RouteLocationAsPathGeneric;
11
32
  protected hasLoggedInCondition(route: RouteLocationNormalized): boolean;
12
33
  protected validateLoggedInCondition(route: RouteLocationNormalized): boolean;
13
34
  protected hasLoggedOutCondition(route: RouteLocationNormalized): boolean;
@@ -8,6 +8,7 @@ import {
8
8
  import { hasOwnProperty, omitRecord } from "@authup/kit";
9
9
  import { BuiltInPolicyType, definePolicyData } from "@authup/access";
10
10
  import { RouteMetaKey } from "../constants.js";
11
+ const DESTINATION_BASE = "http://localhost";
11
12
  export class RoutingInterceptor {
12
13
  store;
13
14
  storeRefs;
@@ -27,6 +28,8 @@ export class RoutingInterceptor {
27
28
  return void 0;
28
29
  }
29
30
  const request = loadAuthorizationRequest();
31
+ const { redirect } = to.query;
32
+ const destination = typeof redirect === "string" ? this.resolveDestination(redirect) : void 0;
30
33
  try {
31
34
  if (request) {
32
35
  const state = typeof to.query.state === "string" ? to.query.state : void 0;
@@ -43,26 +46,22 @@ export class RoutingInterceptor {
43
46
  await this.store.exchangeAuthorizationCode(code);
44
47
  }
45
48
  clearAuthorizationRequest();
46
- if (request?.target) {
47
- const url = new URL(request.target, "http://localhost");
49
+ if (destination) {
50
+ const query = {};
51
+ for (const key of new Set(destination.searchParams.keys())) {
52
+ const values = destination.searchParams.getAll(key);
53
+ query[key] = values.length > 1 ? values : values[0];
54
+ }
48
55
  return {
49
- path: url.pathname,
50
- query: Object.fromEntries(url.searchParams.entries()),
51
- hash: url.hash
56
+ path: destination.pathname,
57
+ query,
58
+ hash: destination.hash
52
59
  };
53
60
  }
54
- return {
55
- path: to.path,
56
- query: omitRecord(to.query, ["code", "state"]),
57
- hash: to.hash
58
- };
61
+ return this.withoutAuthorizationParams(to);
59
62
  } catch {
60
63
  clearAuthorizationRequest();
61
- return {
62
- path: to.path,
63
- query: omitRecord(to.query, ["code", "state"]),
64
- hash: to.hash
65
- };
64
+ return this.withoutAuthorizationParams(to);
66
65
  }
67
66
  }
68
67
  try {
@@ -101,12 +100,59 @@ export class RoutingInterceptor {
101
100
  }
102
101
  if (this.hasLoggedOutCondition(from)) {
103
102
  await this.store.logout();
104
- return { path: from.fullPath };
103
+ return this.backTo(from);
104
+ }
105
+ if (typeof from.query.code === "string") {
106
+ return { path: this.homeRoute };
105
107
  }
106
- return { path: from.fullPath };
108
+ return this.backTo(from);
107
109
  }
108
110
  return void 0;
109
111
  }
112
+ /**
113
+ * Resolve a post-login destination, or undefined when the value is not a
114
+ * site-relative path.
115
+ *
116
+ * The base is a fixed dummy origin, so a value that lands anywhere else
117
+ * declared an authority of its own and is refused. That covers the whole
118
+ * family in one comparison — `//host`, `https://host`, the `\`-for-`/`
119
+ * variants a prefix check misses, and non-special schemes, which resolve
120
+ * to the opaque `"null"` origin.
121
+ */
122
+ resolveDestination(value) {
123
+ if (!value) {
124
+ return void 0;
125
+ }
126
+ let url;
127
+ try {
128
+ url = new URL(value, DESTINATION_BASE);
129
+ } catch {
130
+ return void 0;
131
+ }
132
+ return url.origin === DESTINATION_BASE ? url : void 0;
133
+ }
134
+ withoutAuthorizationParams(route) {
135
+ return {
136
+ path: route.path,
137
+ query: omitRecord(route.query, ["code", "state", "redirect"]),
138
+ hash: route.hash
139
+ };
140
+ }
141
+ /**
142
+ * Return to a route the user already had.
143
+ *
144
+ * Passing `fullPath` as `path` looks equivalent and is not: vue-router
145
+ * runs it through `parseURL`, which keeps the path and silently discards
146
+ * the query and hash, so a bounce off `/users?page=2#row-7` used to land
147
+ * on a bare `/users`.
148
+ */
149
+ backTo(route) {
150
+ return {
151
+ path: route.path,
152
+ query: route.query,
153
+ hash: route.hash
154
+ };
155
+ }
110
156
  hasLoggedInCondition(route) {
111
157
  return route.matched.some(
112
158
  (matched) => !!matched.meta[RouteMetaKey.REQUIRE_LOGGED_IN]
@@ -28,6 +28,13 @@ function buildCookieOptions(runtimeConfig) {
28
28
  }
29
29
  return cookieOptions;
30
30
  }
31
+ function buildCookieName(runtimeConfig) {
32
+ const options = runtimeConfig.public.authup;
33
+ if (!options.cookiePrefix) {
34
+ return (key) => key;
35
+ }
36
+ return (key) => `${options.cookiePrefix}${key}`;
37
+ }
31
38
  function buildApiUrl(runtimeConfig) {
32
39
  const options = runtimeConfig.public.authup;
33
40
  let url;
@@ -62,13 +69,14 @@ export default defineNuxtPlugin({
62
69
  const runtimeConfig = useRuntimeConfig();
63
70
  const baseURL = buildApiUrl(runtimeConfig);
64
71
  const cookieOptions = buildCookieOptions(runtimeConfig);
72
+ const cookieName = buildCookieName(runtimeConfig);
65
73
  install(ctx.vueApp, {
66
74
  pinia: ctx.$pinia,
67
75
  baseURL,
68
76
  cookieSet: (key, value, options) => {
69
77
  const app = tryUseNuxtApp();
70
78
  if (app) {
71
- const cookie = useCookie(key, {
79
+ const cookie = useCookie(cookieName(key), {
72
80
  ...cookieOptions,
73
81
  ...options || {}
74
82
  });
@@ -78,7 +86,7 @@ export default defineNuxtPlugin({
78
86
  cookieUnset: (key, options) => {
79
87
  const app = tryUseNuxtApp();
80
88
  if (app) {
81
- const cookie = useCookie(key, {
89
+ const cookie = useCookie(cookieName(key), {
82
90
  ...cookieOptions,
83
91
  ...options || {}
84
92
  });
@@ -88,7 +96,7 @@ export default defineNuxtPlugin({
88
96
  cookieGet: (key) => {
89
97
  const app = tryUseNuxtApp();
90
98
  if (app) {
91
- const cookie = useCookie(key);
99
+ const cookie = useCookie(cookieName(key));
92
100
  return cookie.value;
93
101
  }
94
102
  return null;
@@ -22,6 +22,21 @@ export type RuntimeOptions = {
22
22
  * (client-side & server-side)
23
23
  */
24
24
  cookieDomainRuntimeKey?: string;
25
+ /**
26
+ * Prefix prepended to every session cookie name
27
+ * (client-side & server-side)
28
+ *
29
+ * The session cookies are written under fixed names (`access_token`,
30
+ * `refresh_token`, ...). Widening `cookieDomain` delivers those names to
31
+ * every host under that domain, so any other authup client there writes
32
+ * the same names and the browser ends up holding two records under one.
33
+ * A prefix keeps them apart.
34
+ *
35
+ * Prepended verbatim, so `flame_` writes `flame_access_token`. Use cookie
36
+ * name characters only: letters, digits, `_`, `-`, `.`, never `:` or a
37
+ * space.
38
+ */
39
+ cookiePrefix?: string;
25
40
  /**
26
41
  * Path of the home route
27
42
  * Default: /
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@authup/client-web-nuxt",
3
3
  "type": "module",
4
- "version": "1.0.0-beta.62",
4
+ "version": "1.0.0-beta.64",
5
5
  "description": "Nuxt module for the authup ecosystem",
6
6
  "license": "Apache-2.0",
7
7
  "repository": {
@@ -24,16 +24,18 @@
24
24
  "build": "nuxt-module-build prepare && nuxt-module-build build",
25
25
  "prepack": "nuxt-module-build prepare && nuxt-module-build build",
26
26
  "dev:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxi prepare playground",
27
- "lint": "eslint ."
27
+ "check:types": "nuxt-module-build prepare && vue-tsc -p tsconfig.test.json",
28
+ "lint": "eslint .",
29
+ "test": "nuxt-module-build prepare && cross-env NODE_ENV=test vitest run --config test/vitest.config.ts"
28
30
  },
29
31
  "engines": {
30
32
  "node": "^22.13.0 || ^23.5.0 || >=24.0.0"
31
33
  },
32
34
  "dependencies": {
33
- "@authup/access": "^1.0.0-beta.62",
34
- "@authup/client-web-kit": "^1.0.0-beta.62",
35
- "@authup/core-http-kit": "^1.0.0-beta.62",
36
- "@authup/kit": "^1.0.0-beta.62",
35
+ "@authup/access": "^1.0.0-beta.64",
36
+ "@authup/client-web-kit": "^1.0.0-beta.64",
37
+ "@authup/core-http-kit": "^1.0.0-beta.64",
38
+ "@authup/kit": "^1.0.0-beta.64",
37
39
  "@nuxt/kit": "^4.0.0",
38
40
  "pathtrace": "^2.2.0",
39
41
  "smob": "^1.6.1"
@@ -41,18 +43,21 @@
41
43
  "devDependencies": {
42
44
  "@nuxt/module-builder": "^1.0.3",
43
45
  "@nuxt/schema": "^4.0.0",
44
- "@pinia/nuxt": "^1.0.1",
46
+ "@pinia/nuxt": "^1.0.2",
47
+ "cross-env": "^10.1.0",
45
48
  "fix-dts-default-cjs-exports": "^1.0.1",
46
49
  "mkdist": "^2.3.0",
47
50
  "nuxt": "^4.5.2",
48
- "pinia": "^4.0.2",
51
+ "pinia": "^4.0.3",
49
52
  "rollup-plugin-dts": "^6.5.1",
50
- "strip-literal": "^3.1.0",
51
- "vue-router": "^5.0.7"
53
+ "strip-literal": "^4.0.0",
54
+ "vitest": "^4.0.14",
55
+ "vue": "^3.5.42",
56
+ "vue-router": "^5.3.0"
52
57
  },
53
58
  "peerDependencies": {
54
59
  "@pinia/nuxt": "*",
55
- "pinia": "^4.0.2"
60
+ "pinia": "^4.0.3"
56
61
  },
57
62
  "keywords": [
58
63
  "auth",