@jskit-ai/auth-web 0.1.4
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/package.descriptor.mjs +290 -0
- package/package.json +29 -0
- package/src/client/composables/useDefaultLoginView.js +935 -0
- package/src/client/composables/useDefaultSignOutView.js +113 -0
- package/src/client/index.js +19 -0
- package/src/client/lib/returnToPath.js +20 -0
- package/src/client/lib/surfaceLinkTarget.js +19 -0
- package/src/client/providers/AuthWebClientProvider.js +72 -0
- package/src/client/runtime/authGuardRuntime.js +499 -0
- package/src/client/runtime/authHttpClient.js +19 -0
- package/src/client/runtime/inject.js +43 -0
- package/src/client/runtime/tokens.js +7 -0
- package/src/client/runtime/useLoginView.js +7 -0
- package/src/client/runtime/useSignOut.js +121 -0
- package/src/client/views/AuthProfileMenuLinkItem.vue +83 -0
- package/src/client/views/AuthProfileWidget.vue +100 -0
- package/src/client/views/DefaultLoginView.vue +291 -0
- package/src/client/views/DefaultSignOutView.vue +58 -0
- package/src/server/constants/authActionIds.js +15 -0
- package/src/server/controllers/AuthController.js +183 -0
- package/src/server/providers/AuthRouteServiceProvider.js +31 -0
- package/src/server/providers/AuthWebServiceProvider.js +23 -0
- package/src/server/routes/authRoutes.js +244 -0
- package/src/server/services/AuthWebService.js +126 -0
- package/templates/src/pages/auth/login.vue +17 -0
- package/templates/src/pages/auth/signout.vue +17 -0
- package/templates/src/runtime/authGuardRuntime.js +7 -0
- package/templates/src/runtime/authHttpClient.js +1 -0
- package/templates/src/runtime/useSignOut.js +1 -0
- package/templates/src/views/auth/LoginView.vue +7 -0
- package/templates/src/views/auth/SignOutView.vue +7 -0
- package/test/authGuardRuntime.test.js +361 -0
- package/test/clientBoot.test.js +16 -0
- package/test/clientSurface.test.js +89 -0
- package/test/index.test.js +21 -0
- package/test/logoutFallback.test.js +50 -0
- package/test/providerRuntime.test.js +100 -0
- package/test/returnToPath.test.js +72 -0
- package/test/surfaceLinkTarget.test.js +80 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { computed, onMounted, ref } from "vue";
|
|
2
|
+
import { useShellWebErrorRuntime } from "@jskit-ai/shell-web/client/error";
|
|
3
|
+
import {
|
|
4
|
+
useWebPlacementContext,
|
|
5
|
+
resolveSurfaceNavigationTargetFromPlacementContext
|
|
6
|
+
} from "@jskit-ai/shell-web/client/placement";
|
|
7
|
+
import { performSignOutRequest } from "../runtime/useSignOut.js";
|
|
8
|
+
import { useAuthGuardRuntime } from "../runtime/inject.js";
|
|
9
|
+
import {
|
|
10
|
+
normalizeAuthReturnToPath,
|
|
11
|
+
resolveAllowedReturnToOriginsFromPlacementContext
|
|
12
|
+
} from "../lib/returnToPath.js";
|
|
13
|
+
|
|
14
|
+
function readReturnToPathFromLocation({ allowedOrigins = [] } = {}) {
|
|
15
|
+
if (typeof window !== "object" || !window.location) {
|
|
16
|
+
return "/";
|
|
17
|
+
}
|
|
18
|
+
const params = new URLSearchParams(window.location.search || "");
|
|
19
|
+
return normalizeAuthReturnToPath(params.get("returnTo"), "/", {
|
|
20
|
+
allowedOrigins
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function navigateToPath(path, { replace = true } = {}) {
|
|
25
|
+
if (typeof window !== "object" || !window.location) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
if (replace) {
|
|
29
|
+
window.location.replace(path);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
window.location.assign(path);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function useDefaultSignOutView() {
|
|
36
|
+
const authGuardRuntime = useAuthGuardRuntime({
|
|
37
|
+
required: true
|
|
38
|
+
});
|
|
39
|
+
const { context: placementContext } = useWebPlacementContext();
|
|
40
|
+
const errorRuntime = useShellWebErrorRuntime();
|
|
41
|
+
const status = ref("pending");
|
|
42
|
+
const errorMessage = ref("");
|
|
43
|
+
const returnToPath = ref("/");
|
|
44
|
+
const allowedReturnToOrigins = computed(() =>
|
|
45
|
+
resolveAllowedReturnToOriginsFromPlacementContext(placementContext.value)
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
function setErrorMessage(message, dedupeKey = "") {
|
|
49
|
+
const normalizedMessage = String(message || "").trim();
|
|
50
|
+
errorMessage.value = normalizedMessage;
|
|
51
|
+
if (!normalizedMessage) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
errorRuntime.report({
|
|
56
|
+
source: "auth-web.default-signout-view",
|
|
57
|
+
message: normalizedMessage,
|
|
58
|
+
severity: "error",
|
|
59
|
+
channel: "banner",
|
|
60
|
+
dedupeKey: dedupeKey || `auth-web.default-signout-view:error:${normalizedMessage}`,
|
|
61
|
+
dedupeWindowMs: 3000
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const loginRoute = computed(() => {
|
|
66
|
+
const loginTarget = resolveSurfaceNavigationTargetFromPlacementContext(placementContext.value, {
|
|
67
|
+
path: "/auth/login",
|
|
68
|
+
surfaceId: "auth"
|
|
69
|
+
});
|
|
70
|
+
const params = new URLSearchParams({
|
|
71
|
+
returnTo: returnToPath.value
|
|
72
|
+
});
|
|
73
|
+
return `${loginTarget.href}?${params.toString()}`;
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
function goToLogin() {
|
|
77
|
+
navigateToPath(loginRoute.value, { replace: false });
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function executeSignOut() {
|
|
81
|
+
status.value = "pending";
|
|
82
|
+
setErrorMessage("");
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
await performSignOutRequest({
|
|
86
|
+
authGuardRuntime
|
|
87
|
+
});
|
|
88
|
+
status.value = "success";
|
|
89
|
+
navigateToPath(loginRoute.value, { replace: true });
|
|
90
|
+
} catch (error) {
|
|
91
|
+
status.value = "error";
|
|
92
|
+
setErrorMessage(String(error?.message || "Sign out failed."));
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function retrySignOut() {
|
|
97
|
+
void executeSignOut();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
onMounted(() => {
|
|
101
|
+
returnToPath.value = readReturnToPathFromLocation({
|
|
102
|
+
allowedOrigins: allowedReturnToOrigins.value
|
|
103
|
+
});
|
|
104
|
+
void executeSignOut();
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
status,
|
|
109
|
+
errorMessage,
|
|
110
|
+
retrySignOut,
|
|
111
|
+
goToLogin
|
|
112
|
+
};
|
|
113
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { AuthWebClientProvider } from "./providers/AuthWebClientProvider.js";
|
|
2
|
+
import DefaultLoginView from "./views/DefaultLoginView.vue";
|
|
3
|
+
import DefaultSignOutView from "./views/DefaultSignOutView.vue";
|
|
4
|
+
|
|
5
|
+
export { AuthWebClientProvider } from "./providers/AuthWebClientProvider.js";
|
|
6
|
+
export { default as DefaultLoginView } from "./views/DefaultLoginView.vue";
|
|
7
|
+
export { default as DefaultSignOutView } from "./views/DefaultSignOutView.vue";
|
|
8
|
+
export { default as AuthProfileWidget } from "./views/AuthProfileWidget.vue";
|
|
9
|
+
export { default as AuthProfileMenuLinkItem } from "./views/AuthProfileMenuLinkItem.vue";
|
|
10
|
+
|
|
11
|
+
const routeComponents = Object.freeze({
|
|
12
|
+
"auth-login": DefaultLoginView,
|
|
13
|
+
"auth-signout": DefaultSignOutView,
|
|
14
|
+
"auth-default-login": DefaultLoginView
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
const clientProviders = Object.freeze([AuthWebClientProvider]);
|
|
18
|
+
|
|
19
|
+
export { routeComponents, clientProviders };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import {
|
|
2
|
+
normalizeReturnToPath as normalizeSharedReturnToPath,
|
|
3
|
+
resolveAllowedOriginsFromPlacementContext
|
|
4
|
+
} from "@jskit-ai/kernel/shared/support";
|
|
5
|
+
|
|
6
|
+
const AUTH_LOOP_PATHNAMES = Object.freeze(["/auth/login", "/auth/signout"]);
|
|
7
|
+
|
|
8
|
+
function resolveAllowedReturnToOriginsFromPlacementContext(contextValue = null) {
|
|
9
|
+
return resolveAllowedOriginsFromPlacementContext(contextValue);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function normalizeAuthReturnToPath(value, fallback = "/", { allowedOrigins = [] } = {}) {
|
|
13
|
+
return normalizeSharedReturnToPath(value, {
|
|
14
|
+
fallback,
|
|
15
|
+
allowedOrigins,
|
|
16
|
+
blockedPathnames: AUTH_LOOP_PATHNAMES
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export { normalizeAuthReturnToPath, resolveAllowedReturnToOriginsFromPlacementContext };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { resolveShellLinkPath } from "@jskit-ai/shell-web/client/navigation/linkResolver";
|
|
2
|
+
|
|
3
|
+
function resolveSurfaceLinkTarget({
|
|
4
|
+
context = null,
|
|
5
|
+
surface = "",
|
|
6
|
+
explicitTo = "",
|
|
7
|
+
workspaceSuffix = "",
|
|
8
|
+
nonWorkspaceSuffix = ""
|
|
9
|
+
} = {}) {
|
|
10
|
+
const fallbackPath = String(nonWorkspaceSuffix || "").trim() || String(workspaceSuffix || "").trim() || "/";
|
|
11
|
+
return resolveShellLinkPath({
|
|
12
|
+
context,
|
|
13
|
+
surface,
|
|
14
|
+
explicitTo,
|
|
15
|
+
relativePath: fallbackPath
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export { resolveSurfaceLinkTarget };
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import DefaultLoginView from "../views/DefaultLoginView.vue";
|
|
2
|
+
import AuthProfileWidget from "../views/AuthProfileWidget.vue";
|
|
3
|
+
import AuthProfileMenuLinkItem from "../views/AuthProfileMenuLinkItem.vue";
|
|
4
|
+
import { CLIENT_MODULE_VUE_APP_TOKEN } from "@jskit-ai/kernel/client/moduleBootstrap";
|
|
5
|
+
import { createAuthGuardRuntime } from "../runtime/authGuardRuntime.js";
|
|
6
|
+
import { useLoginView } from "../runtime/useLoginView.js";
|
|
7
|
+
import {
|
|
8
|
+
WEB_PLACEMENT_RUNTIME_CLIENT_TOKEN,
|
|
9
|
+
resolveSurfaceNavigationTargetFromPlacementContext
|
|
10
|
+
} from "@jskit-ai/shell-web/client/placement";
|
|
11
|
+
import {
|
|
12
|
+
AUTH_GUARD_RUNTIME_CLIENT_TOKEN,
|
|
13
|
+
AUTH_GUARD_RUNTIME_INJECTION_KEY
|
|
14
|
+
} from "../runtime/tokens.js";
|
|
15
|
+
|
|
16
|
+
const AUTH_WEB_PROFILE_WIDGET_COMPONENT_TOKEN = "auth.web.profile.widget";
|
|
17
|
+
const AUTH_WEB_PROFILE_MENU_LINK_ITEM_COMPONENT_TOKEN = "auth.web.profile.menu.link-item";
|
|
18
|
+
const REALTIME_SOCKET_CLIENT_TOKEN = "runtime.realtime.client.socket";
|
|
19
|
+
|
|
20
|
+
class AuthWebClientProvider {
|
|
21
|
+
static id = "auth.web.client";
|
|
22
|
+
|
|
23
|
+
register(app) {
|
|
24
|
+
if (!app || typeof app.singleton !== "function") {
|
|
25
|
+
throw new Error("AuthWebClientProvider requires application singleton().");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
app.singleton("auth.login.component", () => DefaultLoginView);
|
|
29
|
+
app.singleton("auth.login.useLoginView", () => useLoginView);
|
|
30
|
+
app.singleton(AUTH_WEB_PROFILE_WIDGET_COMPONENT_TOKEN, () => AuthProfileWidget);
|
|
31
|
+
app.singleton(AUTH_WEB_PROFILE_MENU_LINK_ITEM_COMPONENT_TOKEN, () => AuthProfileMenuLinkItem);
|
|
32
|
+
app.singleton(AUTH_GUARD_RUNTIME_CLIENT_TOKEN, () => {
|
|
33
|
+
if (!app.has(WEB_PLACEMENT_RUNTIME_CLIENT_TOKEN)) {
|
|
34
|
+
throw new Error("AuthWebClientProvider requires shell-web placement runtime.");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const placementRuntime = app.make(WEB_PLACEMENT_RUNTIME_CLIENT_TOKEN);
|
|
38
|
+
const realtimeSocket = app.has(REALTIME_SOCKET_CLIENT_TOKEN) ? app.make(REALTIME_SOCKET_CLIENT_TOKEN) : null;
|
|
39
|
+
const loginRouteTarget = resolveSurfaceNavigationTargetFromPlacementContext(placementRuntime.getContext(), {
|
|
40
|
+
path: "/auth/login",
|
|
41
|
+
surfaceId: "auth"
|
|
42
|
+
});
|
|
43
|
+
return createAuthGuardRuntime({
|
|
44
|
+
loginRoute: loginRouteTarget.href,
|
|
45
|
+
placementRuntime,
|
|
46
|
+
realtimeSocket
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async boot(app) {
|
|
52
|
+
if (!app || typeof app.make !== "function") {
|
|
53
|
+
throw new Error("AuthWebClientProvider requires application make().");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const authGuardRuntime = app.make(AUTH_GUARD_RUNTIME_CLIENT_TOKEN);
|
|
57
|
+
await authGuardRuntime.initialize();
|
|
58
|
+
|
|
59
|
+
if (!app.has(CLIENT_MODULE_VUE_APP_TOKEN)) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const vueApp = app.make(CLIENT_MODULE_VUE_APP_TOKEN);
|
|
64
|
+
if (!vueApp || typeof vueApp.provide !== "function") {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
vueApp.provide(AUTH_GUARD_RUNTIME_INJECTION_KEY, authGuardRuntime);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export { AuthWebClientProvider };
|