@authup/client-web-nuxt 1.0.0-beta.61 → 1.0.0-beta.63

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.61",
4
+ "version": "1.0.0-beta.63",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.3",
7
7
  "unbuild": "3.6.1"
@@ -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]
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.61",
4
+ "version": "1.0.0-beta.63",
5
5
  "description": "Nuxt module for the authup ecosystem",
6
6
  "license": "Apache-2.0",
7
7
  "repository": {
@@ -24,16 +24,17 @@
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
+ "lint": "eslint .",
28
+ "test": "nuxt-module-build prepare && cross-env NODE_ENV=test vitest run --config test/vitest.config.ts"
28
29
  },
29
30
  "engines": {
30
31
  "node": "^22.13.0 || ^23.5.0 || >=24.0.0"
31
32
  },
32
33
  "dependencies": {
33
- "@authup/access": "^1.0.0-beta.61",
34
- "@authup/client-web-kit": "^1.0.0-beta.61",
35
- "@authup/core-http-kit": "^1.0.0-beta.61",
36
- "@authup/kit": "^1.0.0-beta.61",
34
+ "@authup/access": "^1.0.0-beta.63",
35
+ "@authup/client-web-kit": "^1.0.0-beta.63",
36
+ "@authup/core-http-kit": "^1.0.0-beta.63",
37
+ "@authup/kit": "^1.0.0-beta.63",
37
38
  "@nuxt/kit": "^4.0.0",
38
39
  "pathtrace": "^2.2.0",
39
40
  "smob": "^1.6.1"
@@ -42,12 +43,15 @@
42
43
  "@nuxt/module-builder": "^1.0.3",
43
44
  "@nuxt/schema": "^4.0.0",
44
45
  "@pinia/nuxt": "^1.0.1",
46
+ "cross-env": "^10.1.0",
45
47
  "fix-dts-default-cjs-exports": "^1.0.1",
46
48
  "mkdist": "^2.3.0",
47
- "nuxt": "^4.5.1",
49
+ "nuxt": "^4.5.2",
48
50
  "pinia": "^4.0.2",
49
51
  "rollup-plugin-dts": "^6.5.1",
50
- "strip-literal": "^3.1.0",
52
+ "strip-literal": "^4.0.0",
53
+ "vitest": "^4.0.14",
54
+ "vue": "^3.5.41",
51
55
  "vue-router": "^5.0.7"
52
56
  },
53
57
  "peerDependencies": {