@merkaly/nuxt 0.7.11 → 0.8.1
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 +1 -1
- package/dist/runtime/components/app.d.vue.ts +9 -2
- package/dist/runtime/components/app.vue +6 -1
- package/dist/runtime/components/app.vue.d.ts +9 -2
- package/dist/runtime/components/auth/AuthError.d.vue.ts +21 -0
- package/dist/runtime/components/auth/AuthError.vue +33 -0
- package/dist/runtime/components/auth/AuthError.vue.d.ts +21 -0
- package/dist/runtime/composables/useAuth.d.ts +18 -0
- package/dist/runtime/composables/useAuth.js +19 -1
- package/dist/runtime/plugins/api.global.d.ts +1 -1
- package/dist/runtime/plugins/api.global.js +1 -1
- package/dist/runtime/plugins/auth0.client.js +28 -6
- package/dist/runtime/types/nuxt.d.ts +3 -0
- package/package.json +2 -2
package/dist/module.json
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
|
-
declare var __VLS_29: {}, __VLS_31: {
|
|
1
|
+
declare var __VLS_29: {}, __VLS_31: {
|
|
2
|
+
clear: () => undefined;
|
|
3
|
+
error: import("#imports").AuthError;
|
|
4
|
+
logout: () => any;
|
|
5
|
+
retry: () => Promise<void>;
|
|
6
|
+
}, __VLS_40: {};
|
|
2
7
|
type __VLS_Slots = {} & {
|
|
3
8
|
loading?: (props: typeof __VLS_29) => any;
|
|
4
9
|
} & {
|
|
5
|
-
|
|
10
|
+
error?: (props: typeof __VLS_31) => any;
|
|
11
|
+
} & {
|
|
12
|
+
default?: (props: typeof __VLS_40) => any;
|
|
6
13
|
};
|
|
7
14
|
declare const __VLS_base: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
8
15
|
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
@@ -3,10 +3,11 @@ import { useRoute } from "#app";
|
|
|
3
3
|
import { useAuth, watchOnce, addRouteMiddleware, useNuxtApp } from "#imports";
|
|
4
4
|
import AuthMiddleware from "../middleware/auth";
|
|
5
5
|
import { useNavigation } from "../composables/useNavigation";
|
|
6
|
+
import AuthError from "./auth/AuthError.vue";
|
|
6
7
|
import { useColorMode } from "@vueuse/core";
|
|
7
8
|
import { Notivue, NotificationProgress, Notification, pastelTheme } from "notivue";
|
|
8
9
|
const $route = useRoute();
|
|
9
|
-
const { isLoading } = useAuth();
|
|
10
|
+
const { clear, error, isLoading, logout, retry } = useAuth();
|
|
10
11
|
const { hook } = useNuxtApp();
|
|
11
12
|
useColorMode({ attribute: "data-bs-theme" });
|
|
12
13
|
watchOnce(isLoading, () => AuthMiddleware($route, $route));
|
|
@@ -28,6 +29,10 @@ hook("page:finish", () => regenerate());
|
|
|
28
29
|
<BApp>
|
|
29
30
|
<slot v-if="isLoading" name="loading" />
|
|
30
31
|
|
|
32
|
+
<slot v-else-if="error" :clear="clear" :error="error" :logout="logout" :retry="retry" name="error">
|
|
33
|
+
<AuthError :error="error" @logout="logout" @retry="retry" />
|
|
34
|
+
</slot>
|
|
35
|
+
|
|
31
36
|
<slot v-else>
|
|
32
37
|
<NuxtPage />
|
|
33
38
|
</slot>
|
|
@@ -1,8 +1,15 @@
|
|
|
1
|
-
declare var __VLS_29: {}, __VLS_31: {
|
|
1
|
+
declare var __VLS_29: {}, __VLS_31: {
|
|
2
|
+
clear: () => undefined;
|
|
3
|
+
error: import("#imports").AuthError;
|
|
4
|
+
logout: () => any;
|
|
5
|
+
retry: () => Promise<void>;
|
|
6
|
+
}, __VLS_40: {};
|
|
2
7
|
type __VLS_Slots = {} & {
|
|
3
8
|
loading?: (props: typeof __VLS_29) => any;
|
|
4
9
|
} & {
|
|
5
|
-
|
|
10
|
+
error?: (props: typeof __VLS_31) => any;
|
|
11
|
+
} & {
|
|
12
|
+
default?: (props: typeof __VLS_40) => any;
|
|
6
13
|
};
|
|
7
14
|
declare const __VLS_base: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
8
15
|
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { PropType } from 'vue';
|
|
2
|
+
import type { AuthError } from '../../composables/useAuth.js';
|
|
3
|
+
declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
|
|
4
|
+
error: {
|
|
5
|
+
type: PropType<AuthError>;
|
|
6
|
+
required: true;
|
|
7
|
+
};
|
|
8
|
+
}>, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
|
|
9
|
+
retry: () => any;
|
|
10
|
+
logout: () => any;
|
|
11
|
+
}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
|
12
|
+
error: {
|
|
13
|
+
type: PropType<AuthError>;
|
|
14
|
+
required: true;
|
|
15
|
+
};
|
|
16
|
+
}>> & Readonly<{
|
|
17
|
+
onRetry?: (() => any) | undefined;
|
|
18
|
+
onLogout?: (() => any) | undefined;
|
|
19
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
20
|
+
declare const _default: typeof __VLS_export;
|
|
21
|
+
export default _default;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { computed } from "vue";
|
|
3
|
+
const props = defineProps({
|
|
4
|
+
error: { type: Object, required: true }
|
|
5
|
+
});
|
|
6
|
+
const emit = defineEmits(["logout", "retry"]);
|
|
7
|
+
const isDenied = computed(() => [401, 403].includes(props.error.status));
|
|
8
|
+
const title = computed(() => isDenied.value ? "Access denied" : "We could not start your session");
|
|
9
|
+
const description = computed(() => isDenied.value ? "You signed in successfully, but this account is not allowed to access this application." : "You signed in successfully, but something failed while preparing your session.");
|
|
10
|
+
const details = computed(() => [props.error.stage, props.error.status, props.error.url].filter(Boolean).join(" \xB7 "));
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<template>
|
|
14
|
+
<div class="d-flex flex-column justify-content-center align-items-center text-center gap-3 min-vh-100 p-5">
|
|
15
|
+
<h1 class="h3 mb-0" v-text="title" />
|
|
16
|
+
|
|
17
|
+
<p class="text-muted mb-0" v-text="description" />
|
|
18
|
+
|
|
19
|
+
<code class="small" v-text="details" />
|
|
20
|
+
|
|
21
|
+
<p class="small text-danger mb-0" v-text="props.error.message" />
|
|
22
|
+
|
|
23
|
+
<div class="d-flex gap-3">
|
|
24
|
+
<BButton v-if="props.error.stage !== 'callback'" variant="primary" @click="emit('retry')">
|
|
25
|
+
<span v-text="'Retry'" />
|
|
26
|
+
</BButton>
|
|
27
|
+
|
|
28
|
+
<BButton variant="light" @click="emit('logout')">
|
|
29
|
+
<span v-text="'Sign in with another account'" />
|
|
30
|
+
</BButton>
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
33
|
+
</template>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { PropType } from 'vue';
|
|
2
|
+
import type { AuthError } from '../../composables/useAuth.js';
|
|
3
|
+
declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
|
|
4
|
+
error: {
|
|
5
|
+
type: PropType<AuthError>;
|
|
6
|
+
required: true;
|
|
7
|
+
};
|
|
8
|
+
}>, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
|
|
9
|
+
retry: () => any;
|
|
10
|
+
logout: () => any;
|
|
11
|
+
}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
|
12
|
+
error: {
|
|
13
|
+
type: PropType<AuthError>;
|
|
14
|
+
required: true;
|
|
15
|
+
};
|
|
16
|
+
}>> & Readonly<{
|
|
17
|
+
onRetry?: (() => any) | undefined;
|
|
18
|
+
onLogout?: (() => any) | undefined;
|
|
19
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
20
|
+
declare const _default: typeof __VLS_export;
|
|
21
|
+
export default _default;
|
|
@@ -1,7 +1,25 @@
|
|
|
1
1
|
import type { User } from '@auth0/auth0-spa-js';
|
|
2
|
+
export type AuthErrorStage = 'callback' | 'token' | 'session' | 'tenant';
|
|
3
|
+
export interface AuthError {
|
|
4
|
+
message: string;
|
|
5
|
+
stage: AuthErrorStage;
|
|
6
|
+
status?: number;
|
|
7
|
+
url?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface AuthErrorContext {
|
|
10
|
+
clear: () => void;
|
|
11
|
+
error: AuthError;
|
|
12
|
+
logout: () => Promise<void>;
|
|
13
|
+
retry: () => Promise<void>;
|
|
14
|
+
}
|
|
2
15
|
export declare const useAuth: () => {
|
|
16
|
+
clear: () => undefined;
|
|
17
|
+
error: import("vue").Ref<AuthError | undefined, AuthError | undefined>;
|
|
18
|
+
fail: (reason: AuthError) => Promise<void>;
|
|
3
19
|
isAuthenticated: import("vue").ComputedRef<boolean>;
|
|
4
20
|
isLoading: import("vue").Ref<boolean, boolean>;
|
|
21
|
+
logout: () => any;
|
|
22
|
+
retry: () => Promise<void>;
|
|
5
23
|
token: import("vue").Ref<string | null, string | null>;
|
|
6
24
|
user: import("vue").Ref<User | undefined, User | undefined>;
|
|
7
25
|
};
|
|
@@ -1,13 +1,31 @@
|
|
|
1
1
|
import { computed } from "vue";
|
|
2
|
-
import { useState } from "#imports";
|
|
2
|
+
import { useNuxtApp, useState } from "#imports";
|
|
3
3
|
export const useAuth = () => {
|
|
4
|
+
const nuxtApp = useNuxtApp();
|
|
4
5
|
const user = useState("auth:user", () => void 0);
|
|
5
6
|
const token = useState("auth:token", () => null);
|
|
6
7
|
const isLoading = useState("auth:loading", () => true);
|
|
8
|
+
const error = useState("auth:error", () => void 0);
|
|
7
9
|
const isAuthenticated = computed(() => Boolean(user.value?.sub));
|
|
10
|
+
const clear = () => error.value = void 0;
|
|
11
|
+
const logout = () => nuxtApp.$auth0.logout();
|
|
12
|
+
const retry = async () => {
|
|
13
|
+
clear();
|
|
14
|
+
await nuxtApp.callHook("merkaly:auth:retry");
|
|
15
|
+
};
|
|
16
|
+
const fail = async (reason) => {
|
|
17
|
+
error.value = reason;
|
|
18
|
+
console.error(`[Auth] ${reason.stage} failed`, reason);
|
|
19
|
+
await nuxtApp.callHook("merkaly:auth:error", { clear, error: reason, logout, retry });
|
|
20
|
+
};
|
|
8
21
|
return {
|
|
22
|
+
clear,
|
|
23
|
+
error,
|
|
24
|
+
fail,
|
|
9
25
|
isAuthenticated,
|
|
10
26
|
isLoading,
|
|
27
|
+
logout,
|
|
28
|
+
retry,
|
|
11
29
|
token,
|
|
12
30
|
user
|
|
13
31
|
};
|
|
@@ -28,7 +28,7 @@ export interface RefOptions {
|
|
|
28
28
|
export interface HooksOptions<TData = unknown, TMeta = Record<string, unknown>, TParams = object> {
|
|
29
29
|
onBeforeSend?(args: OnBeforeSendArgs): Promise<unknown> | unknown;
|
|
30
30
|
onComplete?(args: OnCompleteArgs): Promise<unknown> | unknown;
|
|
31
|
-
onError?(reason: Error): Promise<unknown> | unknown;
|
|
31
|
+
onError?(reason: Error, args: OnResponseArgs): Promise<unknown> | unknown;
|
|
32
32
|
onFatal?(reason: Error): Promise<unknown> | unknown;
|
|
33
33
|
onResponse?(args: OnResponseArgs): Promise<unknown> | unknown;
|
|
34
34
|
onSuccess?(args: OnSuccessArgs<TData, TMeta, TParams>): Promise<unknown> | unknown;
|
|
@@ -54,7 +54,7 @@ This surface has requiresTenant: true. Possible causes:
|
|
|
54
54
|
},
|
|
55
55
|
onResponseError: async ({ response, request }) => {
|
|
56
56
|
const { _data } = response;
|
|
57
|
-
await options.onError?.(_data);
|
|
57
|
+
await options.onError?.(_data, { response, request });
|
|
58
58
|
if (options.error) options.error.value = _data;
|
|
59
59
|
await options?.onComplete?.({ response, request });
|
|
60
60
|
if (options.loading) options.loading.value = false;
|
|
@@ -3,6 +3,7 @@ import { defineNuxtPlugin, useRuntimeConfig, useNuxtApp } from "#imports";
|
|
|
3
3
|
import { navigateTo } from "#app";
|
|
4
4
|
import { defu } from "defu";
|
|
5
5
|
import { useAuth } from "../composables/useAuth.js";
|
|
6
|
+
const NO_SESSION_ERRORS = ["consent_required", "interaction_required", "invalid_grant", "login_required", "missing_refresh_token"];
|
|
6
7
|
export default defineNuxtPlugin(async ({ callHook, hook }) => {
|
|
7
8
|
const { public: $config } = useRuntimeConfig();
|
|
8
9
|
const callbackPath = URL.canParse($config.merkaly.auth0.callbackUrl) ? new URL($config.merkaly.auth0.callbackUrl).pathname : $config.merkaly.auth0.callbackUrl;
|
|
@@ -12,18 +13,38 @@ export default defineNuxtPlugin(async ({ callHook, hook }) => {
|
|
|
12
13
|
domain: $config.merkaly.auth0.domain,
|
|
13
14
|
useRefreshTokens: true
|
|
14
15
|
});
|
|
15
|
-
const { isLoading, user, token } = useAuth();
|
|
16
|
+
const { isLoading, user, token, fail } = useAuth();
|
|
16
17
|
const self0 = Object.assign(Object.create(Object.getPrototypeOf(auth0)), auth0);
|
|
18
|
+
let tokenError;
|
|
17
19
|
auth0.getUser = () => self0.getUser().then((result) => user.value = result).catch((reason) => console.error("[Auth0] getUser failed", reason));
|
|
18
|
-
auth0.getTokenSilently = (options = {}) => self0.getTokenSilently(defu({ authorizationParams: { audience: $config.merkaly.auth0.audience } }, options)).then((result) =>
|
|
20
|
+
auth0.getTokenSilently = (options = {}) => self0.getTokenSilently(defu({ authorizationParams: { audience: $config.merkaly.auth0.audience } }, options)).then((result) => {
|
|
21
|
+
tokenError = void 0;
|
|
22
|
+
return token.value = result;
|
|
23
|
+
}).catch((err) => {
|
|
24
|
+
tokenError = err;
|
|
25
|
+
console.warn("[Auth0] getTokenSilently failed \u2013 fallback, user logged in?", err);
|
|
26
|
+
});
|
|
27
|
+
const resolveIdentity = () => Promise.allSettled([auth0.getUser(), auth0.getTokenSilently()]);
|
|
19
28
|
const initTenantContext = async () => {
|
|
20
29
|
await callHook("merkaly:tenant", user.value);
|
|
21
30
|
await callHook("merkaly:auth", user.value);
|
|
22
31
|
};
|
|
23
|
-
|
|
24
|
-
|
|
32
|
+
const startSession = async () => {
|
|
33
|
+
if (user.value && !token.value && tokenError && !NO_SESSION_ERRORS.includes(tokenError.error)) {
|
|
34
|
+
return fail({ message: tokenError.error_description || tokenError.message, stage: "token" });
|
|
35
|
+
}
|
|
36
|
+
if (user.value && !token.value && tokenError) {
|
|
37
|
+
user.value = void 0;
|
|
38
|
+
await self0.logout({ openUrl: false });
|
|
39
|
+
}
|
|
40
|
+
return initTenantContext();
|
|
41
|
+
};
|
|
42
|
+
hook("merkaly:auth:retry", () => resolveIdentity().then(startSession));
|
|
43
|
+
auth0.handleRedirectCallback = () => self0.handleRedirectCallback().then(async ({ appState }) => {
|
|
44
|
+
await resolveIdentity();
|
|
45
|
+
await startSession();
|
|
25
46
|
void navigateTo(appState?.target || "/");
|
|
26
|
-
}
|
|
47
|
+
}, (reason) => fail({ message: reason.error_description || reason.message, stage: "callback" }));
|
|
27
48
|
auth0.loginWithRedirect = (options = {}) => self0.loginWithRedirect(defu(options, {
|
|
28
49
|
authorizationParams: {
|
|
29
50
|
audience: $config.merkaly.auth0.audience,
|
|
@@ -61,7 +82,8 @@ export default defineNuxtPlugin(async ({ callHook, hook }) => {
|
|
|
61
82
|
};
|
|
62
83
|
const isAuthCallback = window.location.pathname === callbackPath;
|
|
63
84
|
if (!isAuthCallback) {
|
|
64
|
-
await
|
|
85
|
+
await resolveIdentity();
|
|
86
|
+
hook("app:created", startSession);
|
|
65
87
|
}
|
|
66
88
|
isLoading.value = false;
|
|
67
89
|
return { provide: { auth0 } };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { HookResult } from '@nuxt/schema';
|
|
2
2
|
import type { Auth0Client, User } from '@auth0/auth0-spa-js';
|
|
3
3
|
import type { ApiOptions } from '../plugins/api.global';
|
|
4
|
+
import type { AuthErrorContext } from '../composables/useAuth';
|
|
4
5
|
|
|
5
6
|
declare module '#app' {
|
|
6
7
|
interface NuxtApp {
|
|
@@ -11,6 +12,8 @@ declare module '#app' {
|
|
|
11
12
|
|
|
12
13
|
interface RuntimeNuxtHooks {
|
|
13
14
|
'merkaly:auth': (user?: User) => HookResult;
|
|
15
|
+
'merkaly:auth:error': (context: AuthErrorContext) => HookResult;
|
|
16
|
+
'merkaly:auth:retry': () => HookResult;
|
|
14
17
|
'merkaly:tenant': (user?: User) => HookResult;
|
|
15
18
|
}
|
|
16
19
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@merkaly/nuxt",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/merkaly-io/nuxt.git"
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"@nuxt/devtools": "^3.4.0",
|
|
34
34
|
"@nuxt/eslint": "1.17.0",
|
|
35
35
|
"@nuxt/eslint-config": "^1.17.0",
|
|
36
|
-
"@nuxt/fonts": "1.
|
|
36
|
+
"@nuxt/fonts": "1.2.0",
|
|
37
37
|
"@nuxt/image": "^2.2.0",
|
|
38
38
|
"@nuxt/kit": "^4.6.0",
|
|
39
39
|
"@nuxtjs/i18n": "^10.6.0",
|