@nside/wefa 0.3.0 → 0.4.0

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 (156) hide show
  1. package/README.md +46 -3
  2. package/dist/LegalConsent-9nOroDoA.cjs +1 -0
  3. package/dist/LegalConsent-CrPVZOxx.js +151 -0
  4. package/dist/LegalDocument-CVJiGmPJ.cjs +109 -0
  5. package/dist/{LegalDocument-BhoEpJ2O.js → LegalDocument-DwVhwjIf.js} +236 -215
  6. package/dist/{LoginView-kH440cCh.js → LoginView-DUPa_PsC.js} +3 -3
  7. package/dist/{LoginView-IIkXXw3R.cjs → LoginView-Dihs8n_X.cjs} +1 -1
  8. package/dist/{LogoutView-DGqh4bP7.js → LogoutView-DAh7MrFi.js} +3 -3
  9. package/dist/{LogoutView-B90MA-_Q.cjs → LogoutView-Fl3nfeJ0.cjs} +1 -1
  10. package/dist/{apiClient-DJdAL3tN.cjs → apiClient-BUS5ZclN.cjs} +1 -1
  11. package/dist/{apiClient-D-kcx_S1.js → apiClient-BbJl566D.js} +1 -1
  12. package/dist/axios-CZvsFspN.js +1887 -0
  13. package/dist/axios-DMqeKDaq.cjs +6 -0
  14. package/dist/containers.cjs +590 -5
  15. package/dist/containers.d.ts +39 -0
  16. package/dist/containers.js +3803 -977
  17. package/dist/{index-Coos428-.js → index--_rUTrqU.js} +308 -282
  18. package/dist/{index-B4vneBZh.cjs → index-B4oFnh1T.cjs} +6 -6
  19. package/dist/index-BHSxFTgZ.js +49 -0
  20. package/dist/{index-BSfhC_wu.cjs → index-BaA_oL1s.cjs} +1 -1
  21. package/dist/{index-CJmnkrIs.cjs → index-Becfy0pF.cjs} +1 -1
  22. package/dist/{index-Dj5oTSEE.js → index-C09d0pI4.js} +15 -15
  23. package/dist/{index-BXrnPbjr.cjs → index-CbQWytWd.cjs} +4 -4
  24. package/dist/{index-DmVIgb18.js → index-CgAb-gZi.js} +11 -11
  25. package/dist/{index-B53YL3vD.cjs → index-DFOQKDki.cjs} +2 -2
  26. package/dist/index-DFSkcsx-.cjs +943 -0
  27. package/dist/{index-CEz0St1t.js → index-DQFN7qxo.js} +7 -7
  28. package/dist/index-DRozw3P8.js +167 -0
  29. package/dist/index-DfCQoHSf.cjs +146 -0
  30. package/dist/index-DkuJMEY1.js +6731 -0
  31. package/dist/{index-bRjoenrr.js → index-Dv6jyKbT.js} +12 -12
  32. package/dist/{index-Bl3JVLei.cjs → index-EDm9-cRY.cjs} +1 -1
  33. package/dist/index-IGN7_cyg.cjs +2 -0
  34. package/dist/{index-DGvdYnh3.js → index-lFl6UsTa.js} +7 -7
  35. package/dist/index-lQmq7gxp.cjs +54 -0
  36. package/dist/{index-FS8xE7Mo.js → index-xUb0UC07.js} +5 -5
  37. package/dist/lib-C3DWunRS.js +26376 -0
  38. package/dist/lib-COvHzA2Y.cjs +2104 -0
  39. package/dist/lib.cjs +1 -1
  40. package/dist/lib.d.ts +160 -7
  41. package/dist/lib.js +33 -30
  42. package/dist/libRoutes-B-H3e9wZ.js +22 -0
  43. package/dist/libRoutes-Cl3TklhN.cjs +1 -0
  44. package/dist/network.cjs +1 -1
  45. package/dist/network.d.ts +19 -0
  46. package/dist/network.js +3 -3
  47. package/dist/router.cjs +1 -1
  48. package/dist/router.d.ts +26 -4
  49. package/dist/router.js +10 -10
  50. package/package.json +54 -47
  51. package/src/assets/main.css +2 -2
  52. package/src/components/AutoroutedBreadcrumb/AutoroutedBreadcrumb.mdx +8 -8
  53. package/src/components/AutoroutedBreadcrumb/AutoroutedBreadcrumb.spec.ts +86 -45
  54. package/src/components/AutoroutedBreadcrumb/AutoroutedBreadcrumb.vue +29 -21
  55. package/src/components/AvatarComponent/AvatarComponent.mdx +63 -0
  56. package/src/components/AvatarComponent/AvatarComponent.stories.ts +98 -0
  57. package/src/components/AvatarComponent/AvatarComponent.vue +115 -0
  58. package/src/components/GanttChartComponent/GanttChartComponent.mdx +143 -0
  59. package/src/components/GanttChartComponent/GanttChartComponent.spec.ts +257 -0
  60. package/src/components/GanttChartComponent/GanttChartComponent.stories.ts +253 -0
  61. package/src/components/GanttChartComponent/GanttChartComponent.vue +220 -0
  62. package/src/components/GanttChartComponent/GanttChartGrid.vue +66 -0
  63. package/src/components/GanttChartComponent/GanttChartHeaderGrid.vue +167 -0
  64. package/src/components/GanttChartComponent/GanttChartHeaderLabel.vue +23 -0
  65. package/src/components/GanttChartComponent/GanttChartLinksOverlay.vue +105 -0
  66. package/src/components/GanttChartComponent/GanttChartRowGrid.vue +288 -0
  67. package/src/components/GanttChartComponent/GanttChartRowLabel.vue +32 -0
  68. package/src/components/GanttChartComponent/composables/useGanttLinks.ts +212 -0
  69. package/src/components/GanttChartComponent/composables/useGanttSizing.ts +42 -0
  70. package/src/components/GanttChartComponent/ganttChartLayout.ts +211 -0
  71. package/src/components/GanttChartComponent/ganttChartTypes.ts +24 -0
  72. package/src/components/GanttChartComponent/index.ts +1 -0
  73. package/src/components/NetworkButton/ApiMutationButton.vue +7 -5
  74. package/src/components/NetworkButton/ApiQueryButton.vue +6 -4
  75. package/src/components/PlotlyComponent/PlotlyComponent.stories.ts +74 -45
  76. package/src/containers/BareContainer/BareContainer.mdx +1 -1
  77. package/src/containers/LayoutContainer/LayoutContainer.mdx +128 -0
  78. package/src/containers/LayoutContainer/LayoutContainer.spec.ts +151 -0
  79. package/src/containers/LayoutContainer/LayoutContainer.stories.ts +292 -0
  80. package/src/containers/LayoutContainer/LayoutContainer.vue +53 -0
  81. package/src/containers/LayoutContainer/MobileNavigationComponent/MobileNavigationComponent.spec.ts +139 -0
  82. package/src/containers/LayoutContainer/MobileNavigationComponent/MobileNavigationComponent.vue +63 -0
  83. package/src/containers/LayoutContainer/SideNavigationComponent/BottomComponent/BottomComponent.spec.ts +39 -0
  84. package/src/containers/LayoutContainer/SideNavigationComponent/BottomComponent/BottomComponent.vue +9 -0
  85. package/src/containers/LayoutContainer/SideNavigationComponent/MainComponent/MainComponent.spec.ts +175 -0
  86. package/src/containers/LayoutContainer/SideNavigationComponent/MainComponent/MainComponent.vue +163 -0
  87. package/src/containers/LayoutContainer/SideNavigationComponent/MainComponent/NavigationLinkComponent.spec.ts +105 -0
  88. package/src/containers/LayoutContainer/SideNavigationComponent/MainComponent/NavigationLinkComponent.vue +45 -0
  89. package/src/containers/LayoutContainer/SideNavigationComponent/SideNavigationComponent.spec.ts +78 -0
  90. package/src/containers/LayoutContainer/SideNavigationComponent/SideNavigationComponent.vue +29 -0
  91. package/src/containers/LayoutContainer/SideNavigationComponent/TopComponent/TopComponent.spec.ts +60 -0
  92. package/src/containers/LayoutContainer/SideNavigationComponent/TopComponent/TopComponent.vue +56 -0
  93. package/src/containers/LayoutContainer/UserMenuTriggerComponent/UserMenuTriggerComponent.spec.ts +96 -0
  94. package/src/containers/LayoutContainer/UserMenuTriggerComponent/UserMenuTriggerComponent.vue +80 -0
  95. package/src/containers/LayoutContainer/index.ts +1 -0
  96. package/src/containers/NavbarContainer/NavbarContainer.mdx +1 -1
  97. package/src/containers/RoutedTabsComponent/RoutedTabsComponent.mdx +3 -3
  98. package/src/containers/SideMenuContainer/SideMenuContainer.mdx +1 -1
  99. package/src/containers/helpers.ts +6 -3
  100. package/src/containers/index.ts +2 -0
  101. package/src/containers/storybook/PlaceholderView.vue +1 -1
  102. package/src/containers/storybook/PrimeComponents.stories.ts +17 -0
  103. package/src/containers/storybook/PrimeComponentsShowcase.vue +587 -0
  104. package/src/containers/storybook/overview.mdx +36 -36
  105. package/src/demo/App.vue +7 -0
  106. package/src/{demo.ts → demo/main.ts} +8 -9
  107. package/src/demo/router.ts +65 -19
  108. package/src/demo/views/PlaygroundView.vue +86 -0
  109. package/src/demo/views/ShowcaseView.vue +41 -0
  110. package/src/lib.ts +3 -1
  111. package/src/locales/Translation.mdx +2 -2
  112. package/src/locales/en/avatar.json +3 -0
  113. package/src/locales/en/gantt_chart.json +6 -0
  114. package/src/locales/en/navigation.json +3 -1
  115. package/src/locales/index.ts +0 -4
  116. package/src/plugins/legalConsent/views/__tests__/LegalConsent.test.ts +12 -7
  117. package/src/router/guards.ts +4 -4
  118. package/src/router/libRoutes.ts +6 -2
  119. package/src/router/router.mdx +107 -66
  120. package/src/router/types.ts +24 -3
  121. package/src/stores/__tests__/backend/jwt.test.ts +4 -4
  122. package/src/stores/__tests__/backend/oauth.test.ts +104 -0
  123. package/src/stores/__tests__/backend/token.test.ts +4 -4
  124. package/src/stores/authentication.mdx +138 -0
  125. package/src/stores/backend/common.ts +89 -0
  126. package/src/stores/backend/constants.ts +22 -0
  127. package/src/stores/backend/schemes/jwt.ts +208 -0
  128. package/src/stores/backend/schemes/oauth.ts +142 -0
  129. package/src/stores/backend/schemes/token.ts +122 -0
  130. package/src/stores/backend/types.ts +96 -0
  131. package/src/stores/backend.ts +21 -427
  132. package/src/stores/index.ts +6 -0
  133. package/src/theme/index.ts +2 -0
  134. package/src/theme/nside.ts +157 -0
  135. package/src/utils/color.spec.ts +24 -0
  136. package/src/utils/color.ts +100 -0
  137. package/src/utils/translations.ts +0 -4
  138. package/dist/LegalConsent-CEcXZml6.cjs +0 -1
  139. package/dist/LegalConsent-Dzq3fdnt.js +0 -277
  140. package/dist/LegalDocument-CS3MnOcV.cjs +0 -109
  141. package/dist/axios-ClRPr3Xn.js +0 -1777
  142. package/dist/axios-Dcidtc2l.cjs +0 -6
  143. package/dist/index-Bc699sOR.js +0 -4997
  144. package/dist/index-CL_OJMNr.cjs +0 -55
  145. package/dist/index-CTNsucOq.cjs +0 -147
  146. package/dist/index-CwLAV8WF.js +0 -210
  147. package/dist/index-FrfvunRp.cjs +0 -146
  148. package/dist/lib-BBJH9d11.cjs +0 -2792
  149. package/dist/lib-Y8FPgwH4.js +0 -20886
  150. package/dist/libRoutes-BsneoQ4G.js +0 -18
  151. package/dist/libRoutes-BzeZrIaK.cjs +0 -1
  152. package/src/demo/DemoApp.vue +0 -13
  153. package/src/demo/ShowcaseView.vue +0 -39
  154. package/src/demo/demo.css +0 -15
  155. /package/src/demo/{DemoContent.vue → views/DemoContent.vue} +0 -0
  156. /package/src/demo/{DemoView.vue → views/DemoView.vue} +0 -0
@@ -0,0 +1,22 @@
1
+ /**
2
+ * A constant variable used as the key for storing and retrieving the authentication token
3
+ * in the browser's localStorage. This key is utilized to maintain user session or
4
+ * authentication state across browser sessions by securely saving the token locally.
5
+ */
6
+ export const localStorageKey = 'authenticationToken'
7
+
8
+ /**
9
+ * Constants used as keys for storing and retrieving JWT authentication tokens
10
+ * in the browser's localStorage. These keys are utilized to maintain user session or
11
+ * authentication state across browser sessions by securely saving the tokens locally.
12
+ */
13
+ export const jwtAccessTokenKey = 'jwtAccessToken'
14
+ export const jwtRefreshTokenKey = 'jwtRefreshToken'
15
+
16
+ export const tokenLoginEndpoint = '/api/token-auth/'
17
+ export const jwtLoginEndpoint = '/api/token/'
18
+ export const jwtRefreshEndpoint = '/api/token/refresh/'
19
+
20
+ export const oauthLoginEndpoint = '/proxy/api/auth/login'
21
+ export const oauthLogoutEndpoint = '/proxy/api/auth/logout'
22
+ export const oauthSessionEndpoint = '/proxy/api/auth/session'
@@ -0,0 +1,208 @@
1
+ import { ref, watch, type Ref } from 'vue'
2
+ import type { AxiosError, AxiosResponse, InternalAxiosRequestConfig } from 'axios'
3
+ import axiosInstance from '@/network/axios.ts'
4
+ import { createCommonAuthFunctions } from '../common.ts'
5
+ import {
6
+ jwtAccessTokenKey,
7
+ jwtLoginEndpoint,
8
+ jwtRefreshEndpoint,
9
+ jwtRefreshTokenKey,
10
+ } from '../constants.ts'
11
+ import type { BackendStore, BackendStoreOptions, Credentials } from '../types.ts'
12
+
13
+ /**
14
+ * Configures and sets up a JWT-based authentication backend store for managing API authentication.
15
+ *
16
+ * The method initializes authentication states, such as access and refresh tokens, login/logout handlers, and
17
+ * authentication interceptors on the axios instance for requests and responses.
18
+ * It also provides helper functions for handling authentication processes, like login, logout,
19
+ * and post-login/logout callbacks. Additionally, it enables the setting of route guards to
20
+ * respond to authentication status changes in a Vue.js application.
21
+ *
22
+ * This implementation is designed to work with a backend offering JWT mechanism.
23
+ * @param backendStoreOptions
24
+ * @returns An object containing authentication state, API client instance, and helper functions:
25
+ * - `axiosInstance`: Pre-configured axios instance with authentication support
26
+ * - `authenticated`: Reactive flag representing the authentication status
27
+ * - `login`: Function to authenticate users using credentials
28
+ * - `logout`: Function to deauthenticate users and clear authentication tokens
29
+ * - `setPostLogin`: Method to set a callback function invoked after successful login
30
+ * - `setPostLogout`: Method to set a callback function invoked after logout
31
+ * - `setupAuthRouteGuard`: Function to set up route guard for managing authentication-driven route reevaluations
32
+ */
33
+ export function jwtAuthenticationBackendStoreSetup(
34
+ backendStoreOptions: BackendStoreOptions
35
+ ): BackendStore {
36
+ /**
37
+ * A reactive variable that indicates whether a user is authenticated or not.
38
+ *
39
+ * The value is `false` by default, representing an unauthenticated state.
40
+ * This variable can be updated dynamically to reflect the authentication status
41
+ * of the user within the application.
42
+ */
43
+ const authenticated = ref(false)
44
+ const _accessToken: Ref<string | null> = ref(null)
45
+ const _refreshToken: Ref<string | null> = ref(null)
46
+ const _postLogout: Ref<() => void> = ref(() => {})
47
+ const _postLogin: Ref<() => void> = ref(() => {})
48
+ const _accessTokenFromLocalStorage = localStorage.getItem(jwtAccessTokenKey)
49
+ const _refreshTokenFromLocalStorage = localStorage.getItem(jwtRefreshTokenKey)
50
+ const loginEndpoint = backendStoreOptions.endpoints?.jwt?.loginEndpoint ?? jwtLoginEndpoint
51
+ const refreshUrl = backendStoreOptions.endpoints?.jwt?.refreshEndpoint ?? jwtRefreshEndpoint
52
+
53
+ // Create common authentication functions
54
+ const commonAuth = createCommonAuthFunctions(authenticated, _postLogin, _postLogout)
55
+
56
+ if (_accessTokenFromLocalStorage && _refreshTokenFromLocalStorage) {
57
+ _accessToken.value = _accessTokenFromLocalStorage
58
+ _refreshToken.value = _refreshTokenFromLocalStorage
59
+ authenticated.value = true
60
+ }
61
+
62
+ watch(_accessToken, () => {
63
+ localStorage.setItem(jwtAccessTokenKey, _accessToken.value ?? '')
64
+ })
65
+
66
+ watch(_refreshToken, () => {
67
+ localStorage.setItem(jwtRefreshTokenKey, _refreshToken.value ?? '')
68
+ })
69
+
70
+ axiosInstance.defaults.withCredentials = true
71
+ axiosInstance.defaults.headers.common['Content-Type'] = 'application/json'
72
+
73
+ axiosInstance.interceptors.request.use(
74
+ async (config) => {
75
+ if (authenticated.value && _accessToken.value) {
76
+ config.headers['Authorization'] = `Bearer ${_accessToken.value}`
77
+ }
78
+ return config
79
+ },
80
+ (error) => Promise.reject(error)
81
+ )
82
+
83
+ /**
84
+ * Checks if the error is related to token authentication issues
85
+ * @param error - The Axios error to check for token-related issues
86
+ * @param accessToken
87
+ * @param refreshToken
88
+ * @returns True if the error is related to token authentication, false otherwise
89
+ */
90
+ function defaultIsTokenError(
91
+ error: AxiosError,
92
+ accessToken: string | null,
93
+ refreshToken: string | null
94
+ ): boolean {
95
+ return !!(
96
+ error.response &&
97
+ [401, 403].includes(error.response.status) &&
98
+ ((error.response?.data as { code?: string })?.code === 'token_not_valid' ||
99
+ !accessToken ||
100
+ !refreshToken)
101
+ )
102
+ }
103
+
104
+ const isTokenError =
105
+ backendStoreOptions.endpoints?.jwt?.isTokenError ??
106
+ ((error: AxiosError, context: { accessToken: string | null; refreshToken: string | null }) =>
107
+ defaultIsTokenError(error, context.accessToken, context.refreshToken))
108
+
109
+ /**
110
+ * Attempts to refresh the JWT token and retry the original request
111
+ * @param originalRequest - The original Axios request configuration to retry after token refresh
112
+ * @returns A promise that resolves to the Axios response from the retried request
113
+ */
114
+ async function handleTokenRefresh(
115
+ originalRequest: InternalAxiosRequestConfig
116
+ ): Promise<AxiosResponse> {
117
+ const response = await axiosInstance.post(refreshUrl, {
118
+ refresh: _refreshToken.value,
119
+ })
120
+
121
+ if (response.status === 200) {
122
+ // Update the tokens
123
+ _accessToken.value = response.data.access
124
+ if (response.data.refresh) {
125
+ _refreshToken.value = response.data.refresh
126
+ }
127
+
128
+ // Retry the original request with the new token
129
+ originalRequest.headers['Authorization'] = `Bearer ${_accessToken.value}`
130
+ return axiosInstance(originalRequest)
131
+ }
132
+ throw new Error('Token refresh failed')
133
+ }
134
+
135
+ axiosInstance.interceptors.response.use(
136
+ (response) => response,
137
+ async (error) => {
138
+ if (
139
+ !isTokenError(error, {
140
+ accessToken: _accessToken.value,
141
+ refreshToken: _refreshToken.value,
142
+ })
143
+ ) {
144
+ return Promise.reject(error)
145
+ }
146
+
147
+ const originalRequest = error.config
148
+ const isRefreshRequest = originalRequest.url === refreshUrl
149
+
150
+ // Prevent infinite loops
151
+ if (!isRefreshRequest && _refreshToken.value) {
152
+ try {
153
+ return await handleTokenRefresh(originalRequest)
154
+ } catch (refreshError) {
155
+ logout()
156
+ return Promise.reject(refreshError)
157
+ }
158
+ } else {
159
+ logout()
160
+ }
161
+
162
+ return Promise.reject(error)
163
+ }
164
+ )
165
+
166
+ /**
167
+ * Authenticates a user by sending their credentials to the server.
168
+ * @param credentials - The user's login credentials, typically including a username and password.
169
+ * @returns A promise that resolves to the Axios response object returned from the authentication request.
170
+ */
171
+ async function login(credentials: Credentials): Promise<AxiosResponse> {
172
+ const response = await axiosInstance.post(loginEndpoint, credentials)
173
+ if (response.status === 200) {
174
+ authenticated.value = true
175
+ _accessToken.value = response.data.access
176
+ _refreshToken.value = response.data.refresh
177
+ }
178
+ _postLogin.value()
179
+ return response
180
+ }
181
+
182
+ /**
183
+ * Logs the user out of the application by performing the following actions:
184
+ * - Removes the user's authentication tokens from localStorage.
185
+ * - Updates the application's authenticated state to false.
186
+ * - Resets the stored authentication tokens to null.
187
+ * - Executes any additional post-logout logic defined in the application.
188
+ */
189
+ function logout(): void {
190
+ localStorage.removeItem(jwtAccessTokenKey)
191
+ localStorage.removeItem(jwtRefreshTokenKey)
192
+ authenticated.value = false
193
+ _accessToken.value = null
194
+ _refreshToken.value = null
195
+ _postLogout.value()
196
+ }
197
+
198
+ return {
199
+ axiosInstance,
200
+ authenticated,
201
+ login,
202
+ logout,
203
+ setPostLogin: commonAuth.setPostLogin,
204
+ setPostLogout: commonAuth.setPostLogout,
205
+ setupAuthRouteGuard: commonAuth.setupAuthRouteGuard,
206
+ unsetAuthRouteGuard: commonAuth.unsetAuthRouteGuard,
207
+ }
208
+ }
@@ -0,0 +1,142 @@
1
+ import { ref, type Ref } from 'vue'
2
+ import type { AxiosResponse } from 'axios'
3
+ import axiosInstance from '@/network/axios.ts'
4
+ import { createCommonAuthFunctions } from '../common.ts'
5
+ import { oauthLoginEndpoint, oauthLogoutEndpoint, oauthSessionEndpoint } from '../constants.ts'
6
+ import type { BackendStore, BackendStoreOptions } from '../types.ts'
7
+
8
+ /**
9
+ * Response shape returned by the Backend-for-Frontend (BFF) session endpoint.
10
+ */
11
+ interface OAuthSessionStatus {
12
+ session: boolean
13
+ }
14
+
15
+ /**
16
+ * Response shape returned by the Backend-for-Frontend (BFF) login endpoint.
17
+ */
18
+ interface OAuthLoginResponse {
19
+ redirect?: string
20
+ }
21
+
22
+ /**
23
+ * Configures and sets up an OAuth session-based authentication backend store.
24
+ *
25
+ * This implementation expects a Backend-for-Frontend (BFF) to manage OAuth login/logout flows and session cookies.
26
+ * It checks session state on initialization, initiates login when no session exists, and
27
+ * ensures axios requests include credentials.
28
+ *
29
+ * Consuming apps should call `useBackendStore` once at app startup, or ensure it is created
30
+ * before route guards run, so session state is resolved early.
31
+ * @param backendStoreOptions - Store configuration, including OAuth endpoint overrides and redirect handler.
32
+ * @returns An object containing authentication state, API client instance, and helper functions:
33
+ */
34
+ export function oauthAuthenticationBackendStoreSetup(
35
+ backendStoreOptions: BackendStoreOptions
36
+ ): BackendStore {
37
+ const authenticated = ref(false)
38
+ const _postLogout: Ref<() => void> = ref(() => {})
39
+ const _postLogin: Ref<() => void> = ref(() => {})
40
+
41
+ const commonAuth = createCommonAuthFunctions(authenticated, _postLogin, _postLogout)
42
+
43
+ const oauthOverrides = backendStoreOptions.endpoints?.oauth
44
+ const oauthEndpoints = {
45
+ login: oauthOverrides?.loginEndpoint ?? oauthLoginEndpoint,
46
+ logout: oauthOverrides?.logoutEndpoint ?? oauthLogoutEndpoint,
47
+ session: oauthOverrides?.sessionEndpoint ?? oauthSessionEndpoint,
48
+ }
49
+ const redirectHandler =
50
+ oauthOverrides?.redirectHandler ??
51
+ ((url: string) => {
52
+ if (typeof window === 'undefined') {
53
+ return
54
+ }
55
+ window.location.assign(url)
56
+ })
57
+
58
+ axiosInstance.defaults.withCredentials = true
59
+ axiosInstance.defaults.headers.common['Content-Type'] = 'application/json'
60
+
61
+ initializeAuthenticationStore().catch((error) => {
62
+ console.debug(error)
63
+ })
64
+
65
+ /**
66
+ * Initializes the OAuth store by checking session state and triggering login if no session exists.
67
+ * This is invoked once during store creation to prime `authenticated` early in app startup.
68
+ */
69
+ async function initializeAuthenticationStore(): Promise<void> {
70
+ const sessionStatus = await checkSessionStatus()
71
+ authenticated.value = sessionStatus.session
72
+
73
+ if (!sessionStatus.session) {
74
+ await initiateOauthSession()
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Calls the Backend-for-Frontend (BFF) session endpoint to determine whether a valid session exists.
80
+ * On failure, it forces an unauthenticated state and returns `{ session: false }`.
81
+ * @returns Session status payload from the Backend-for-Frontend (BFF).
82
+ */
83
+ async function checkSessionStatus(): Promise<OAuthSessionStatus> {
84
+ return axiosInstance
85
+ .get(oauthEndpoints.session, { withCredentials: true })
86
+ .then((response) => response.data as OAuthSessionStatus)
87
+ .catch((error) => {
88
+ console.debug(error)
89
+ authenticated.value = false
90
+ return { session: false }
91
+ })
92
+ }
93
+
94
+ /**
95
+ * Initiates the OAuth login flow via the Backend-for-Frontend (BFF) login endpoint.
96
+ * Expects a JSON payload containing a `redirect` URL and navigates to it.
97
+ * Runs the post-login callback after a redirect URL is received.
98
+ * @returns Axios response from the login endpoint for chaining/testing.
99
+ */
100
+ async function initiateOauthSession(): Promise<AxiosResponse> {
101
+ const response = await axiosInstance.get(oauthEndpoints.login, { withCredentials: true })
102
+ const redirect = (response.data as OAuthLoginResponse | undefined)?.redirect
103
+ if (redirect) {
104
+ redirectHandler(redirect)
105
+ _postLogin.value()
106
+ } else {
107
+ authenticated.value = false
108
+ }
109
+ return response
110
+ }
111
+
112
+ /**
113
+ * Starts the OAuth login flow. Credentials are ignored because the Backend-for-Frontend (BFF) handles login.
114
+ * @returns Axios response from the login endpoint for chaining/testing.
115
+ */
116
+ async function login(): Promise<AxiosResponse> {
117
+ return await initiateOauthSession()
118
+ }
119
+
120
+ /**
121
+ * Logs out via the Backend-for-Frontend (BFF) logout endpoint and clears local authentication state.
122
+ * Post-logout callbacks are executed after the request completes (success or failure).
123
+ */
124
+ function logout(): void {
125
+ authenticated.value = false
126
+ axiosInstance
127
+ .get(oauthEndpoints.logout, { withCredentials: true })
128
+ .catch((error) => console.debug(error))
129
+ .finally(() => _postLogout.value())
130
+ }
131
+
132
+ return {
133
+ axiosInstance,
134
+ authenticated,
135
+ login,
136
+ logout,
137
+ setPostLogin: commonAuth.setPostLogin,
138
+ setPostLogout: commonAuth.setPostLogout,
139
+ setupAuthRouteGuard: commonAuth.setupAuthRouteGuard,
140
+ unsetAuthRouteGuard: commonAuth.unsetAuthRouteGuard,
141
+ }
142
+ }
@@ -0,0 +1,122 @@
1
+ import { ref, watch, type Ref } from 'vue'
2
+ import type { AxiosResponse } from 'axios'
3
+ import axiosInstance from '@/network/axios.ts'
4
+ import { createCommonAuthFunctions } from '../common.ts'
5
+ import { localStorageKey, tokenLoginEndpoint } from '../constants.ts'
6
+ import type { BackendStore, BackendStoreOptions, Credentials } from '../types.ts'
7
+
8
+ /**
9
+ * Configures and sets up a token-based authentication backend store for managing API authentication.
10
+ *
11
+ * The method initializes authentication states, such as tokens, login/logout handlers, and
12
+ * authentication interceptors on the axiosInstance for requests and responses.
13
+ * It also provides helper functions for handling authentication processes, like login, logout,
14
+ * and post-login/logout callbacks. Additionally, it enables the setting of route guards to
15
+ * respond to authentication status changes in a Vue.js application.
16
+ * @param backendStoreOptions
17
+ * @returns An object containing authentication state, API client instance, and helper functions:
18
+ * - `axiosInstance`: Pre-configured axios instance with authentication support
19
+ * - `authenticated`: Reactive flag representing the authentication status
20
+ * - `login`: Function to authenticate users using credentials
21
+ * - `logout`: Function to deauthenticate users and clear authentication tokens
22
+ * - `setPostLogin`: Method to set a callback function invoked after successful login
23
+ * - `setPostLogout`: Method to set a callback function invoked after logout
24
+ * - `setupAuthRouteGuard`: Function to set up route guard for managing authentication-driven route reevaluations
25
+ */
26
+ export function tokenAuthenticationBackendStoreSetup(
27
+ backendStoreOptions: BackendStoreOptions
28
+ ): BackendStore {
29
+ /**
30
+ * A reactive variable that indicates whether a user is authenticated or not.
31
+ *
32
+ * The value is `false` by default, representing an unauthenticated state.
33
+ * This variable can be updated dynamically to reflect the authentication status
34
+ * of the user within the application.
35
+ */
36
+ const authenticated = ref(false)
37
+ const _token: Ref<string | null> = ref(null)
38
+ const _postLogout: Ref<() => void> = ref(() => {})
39
+ const _postLogin: Ref<() => void> = ref(() => {})
40
+ const _fromLocalStorage = localStorage.getItem(localStorageKey)
41
+ const loginEndpoint = backendStoreOptions.endpoints?.token?.loginEndpoint ?? tokenLoginEndpoint
42
+
43
+ // Create common authentication functions
44
+ const commonAuth = createCommonAuthFunctions(authenticated, _postLogin, _postLogout)
45
+
46
+ if (_fromLocalStorage) {
47
+ _token.value = _fromLocalStorage
48
+ authenticated.value = true
49
+ }
50
+
51
+ watch(_token, () => {
52
+ localStorage.setItem(localStorageKey, _token.value ?? '')
53
+ })
54
+
55
+ axiosInstance.defaults.withCredentials = true
56
+ axiosInstance.defaults.headers.common['Content-Type'] = 'application/json'
57
+
58
+ axiosInstance.interceptors.request.use(
59
+ async (config) => {
60
+ if (authenticated.value && _token.value) {
61
+ config.headers['Authorization'] = `Token ${_token.value}`
62
+ }
63
+ return config
64
+ },
65
+ (error) => Promise.reject(error)
66
+ )
67
+
68
+ axiosInstance.interceptors.response.use(
69
+ (response) => response,
70
+ async (error) => {
71
+ if (error.response.status === 403) {
72
+ const data = error.response.data
73
+ if (data?.detail === 'Authentication credentials were not provided.') {
74
+ logout()
75
+ }
76
+ return Promise.reject(error)
77
+ } else {
78
+ return Promise.reject(error)
79
+ }
80
+ }
81
+ )
82
+
83
+ /**
84
+ * Authenticates a user by sending their credentials to the server.
85
+ * @param credentials - The user's login credentials, typically including a username and password.
86
+ * @returns A promise that resolves to the Axios response object returned from the authentication request.
87
+ */
88
+ async function login(credentials: Credentials): Promise<AxiosResponse> {
89
+ const response = await axiosInstance.post(loginEndpoint, credentials)
90
+ if (response.status === 200) {
91
+ authenticated.value = true
92
+ _token.value = response.data.token
93
+ }
94
+ _postLogin.value()
95
+ return response
96
+ }
97
+
98
+ /**
99
+ * Logs the user out of the application by performing the following actions:
100
+ * - Removes the user's authentication token from localStorage.
101
+ * - Updates the application's authenticated state to false.
102
+ * - Resets the stored authentication token to null.
103
+ * - Executes any additional post-logout logic defined in the application.
104
+ */
105
+ function logout(): void {
106
+ localStorage.removeItem(localStorageKey)
107
+ authenticated.value = false
108
+ _token.value = null
109
+ _postLogout.value()
110
+ }
111
+
112
+ return {
113
+ axiosInstance,
114
+ authenticated,
115
+ login,
116
+ logout,
117
+ setPostLogin: commonAuth.setPostLogin,
118
+ setPostLogout: commonAuth.setPostLogout,
119
+ setupAuthRouteGuard: commonAuth.setupAuthRouteGuard,
120
+ unsetAuthRouteGuard: commonAuth.unsetAuthRouteGuard,
121
+ }
122
+ }
@@ -0,0 +1,96 @@
1
+ import type { Ref } from 'vue'
2
+ import type { AxiosError, AxiosInstance, AxiosResponse } from 'axios'
3
+ import type { Router } from 'vue-router'
4
+
5
+ /**
6
+ * Represents the types of authentication mechanisms that can be used.
7
+ *
8
+ * This type includes the following options:
9
+ * - 'TOKEN': Authentication is performed using a token-based system.
10
+ * - 'JWT': Authentication is performed using JSON Web Tokens.
11
+ * - 'SESSION': Authentication is managed using session-based mechanisms (reserved, not yet implemented).
12
+ * - 'OAUTH': Authentication is managed by a Backend-for-Frontend (BFF) using OAuth session cookies.
13
+ *
14
+ * Note: Future enhancement could include more flexible procedures registration
15
+ * (postLogin, postLogout) to allow different actors to register their procedures
16
+ * on some collection.
17
+ */
18
+ export type AuthenticationType = 'TOKEN' | 'JWT' | 'SESSION' | 'OAUTH'
19
+
20
+ /**
21
+ * Represents a set of credentials consisting of a username and password.
22
+ * Typically used for authentication purposes.
23
+ */
24
+ export interface Credentials {
25
+ username: string
26
+ password: string
27
+ }
28
+
29
+ /**
30
+ * Endpoint configuration for OAuth session authentication.
31
+ *
32
+ * `redirectHandler` can be used by consuming apps to control the redirect side effect
33
+ * (e.g. for tests, SPA-specific navigation, or analytics hooks).
34
+ */
35
+ export interface OAuthEndpoints {
36
+ loginEndpoint?: string
37
+ logoutEndpoint?: string
38
+ sessionEndpoint?: string
39
+ redirectHandler?: (url: string) => void
40
+ }
41
+
42
+ /**
43
+ * Endpoint configuration for token authentication.
44
+ */
45
+ export interface TokenEndpoints {
46
+ loginEndpoint?: string
47
+ }
48
+
49
+ /**
50
+ * Endpoint configuration for JWT authentication.
51
+ */
52
+ export interface JwtEndpoints {
53
+ loginEndpoint?: string
54
+ refreshEndpoint?: string
55
+ /**
56
+ * Optional hook to detect token-related errors (e.g., SimpleJWT or custom backend behavior).
57
+ * When omitted, a default SimpleJWT-compatible check is used.
58
+ */
59
+ isTokenError?: (
60
+ error: AxiosError,
61
+ context: { accessToken: string | null; refreshToken: string | null }
62
+ ) => boolean
63
+ }
64
+
65
+ /**
66
+ * Optional per-scheme endpoint overrides for authentication flows.
67
+ */
68
+ export interface AuthenticationEndpoints {
69
+ token?: TokenEndpoints
70
+ jwt?: JwtEndpoints
71
+ oauth?: OAuthEndpoints
72
+ }
73
+
74
+ /**
75
+ * Contract for the backend store returned by `useBackendStore`.
76
+ * Provides authentication state, auth actions, and optional router guard helpers.
77
+ */
78
+ export interface BackendStore {
79
+ authenticated: Ref<boolean>
80
+ axiosInstance: AxiosInstance
81
+ login: (credentials: Credentials) => Promise<AxiosResponse>
82
+ logout: () => void
83
+ setPostLogin: (fn: () => void) => void
84
+ setPostLogout: (fn: () => void) => void
85
+ setupAuthRouteGuard: (router: Router) => void
86
+ unsetAuthRouteGuard: () => void
87
+ }
88
+
89
+ /**
90
+ * Configuration passed to `useBackendStore`/`defineBackendStore`.
91
+ * Controls the authentication scheme and any OAuth endpoint overrides.
92
+ */
93
+ export interface BackendStoreOptions {
94
+ authenticationType: AuthenticationType
95
+ endpoints?: AuthenticationEndpoints
96
+ }