@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.
Files changed (39) hide show
  1. package/package.descriptor.mjs +290 -0
  2. package/package.json +29 -0
  3. package/src/client/composables/useDefaultLoginView.js +935 -0
  4. package/src/client/composables/useDefaultSignOutView.js +113 -0
  5. package/src/client/index.js +19 -0
  6. package/src/client/lib/returnToPath.js +20 -0
  7. package/src/client/lib/surfaceLinkTarget.js +19 -0
  8. package/src/client/providers/AuthWebClientProvider.js +72 -0
  9. package/src/client/runtime/authGuardRuntime.js +499 -0
  10. package/src/client/runtime/authHttpClient.js +19 -0
  11. package/src/client/runtime/inject.js +43 -0
  12. package/src/client/runtime/tokens.js +7 -0
  13. package/src/client/runtime/useLoginView.js +7 -0
  14. package/src/client/runtime/useSignOut.js +121 -0
  15. package/src/client/views/AuthProfileMenuLinkItem.vue +83 -0
  16. package/src/client/views/AuthProfileWidget.vue +100 -0
  17. package/src/client/views/DefaultLoginView.vue +291 -0
  18. package/src/client/views/DefaultSignOutView.vue +58 -0
  19. package/src/server/constants/authActionIds.js +15 -0
  20. package/src/server/controllers/AuthController.js +183 -0
  21. package/src/server/providers/AuthRouteServiceProvider.js +31 -0
  22. package/src/server/providers/AuthWebServiceProvider.js +23 -0
  23. package/src/server/routes/authRoutes.js +244 -0
  24. package/src/server/services/AuthWebService.js +126 -0
  25. package/templates/src/pages/auth/login.vue +17 -0
  26. package/templates/src/pages/auth/signout.vue +17 -0
  27. package/templates/src/runtime/authGuardRuntime.js +7 -0
  28. package/templates/src/runtime/authHttpClient.js +1 -0
  29. package/templates/src/runtime/useSignOut.js +1 -0
  30. package/templates/src/views/auth/LoginView.vue +7 -0
  31. package/templates/src/views/auth/SignOutView.vue +7 -0
  32. package/test/authGuardRuntime.test.js +361 -0
  33. package/test/clientBoot.test.js +16 -0
  34. package/test/clientSurface.test.js +89 -0
  35. package/test/index.test.js +21 -0
  36. package/test/logoutFallback.test.js +50 -0
  37. package/test/providerRuntime.test.js +100 -0
  38. package/test/returnToPath.test.js +72 -0
  39. 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 };