@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.
- package/README.md +46 -3
- package/dist/LegalConsent-9nOroDoA.cjs +1 -0
- package/dist/LegalConsent-CrPVZOxx.js +151 -0
- package/dist/LegalDocument-CVJiGmPJ.cjs +109 -0
- package/dist/{LegalDocument-BhoEpJ2O.js → LegalDocument-DwVhwjIf.js} +236 -215
- package/dist/{LoginView-kH440cCh.js → LoginView-DUPa_PsC.js} +3 -3
- package/dist/{LoginView-IIkXXw3R.cjs → LoginView-Dihs8n_X.cjs} +1 -1
- package/dist/{LogoutView-DGqh4bP7.js → LogoutView-DAh7MrFi.js} +3 -3
- package/dist/{LogoutView-B90MA-_Q.cjs → LogoutView-Fl3nfeJ0.cjs} +1 -1
- package/dist/{apiClient-DJdAL3tN.cjs → apiClient-BUS5ZclN.cjs} +1 -1
- package/dist/{apiClient-D-kcx_S1.js → apiClient-BbJl566D.js} +1 -1
- package/dist/axios-CZvsFspN.js +1887 -0
- package/dist/axios-DMqeKDaq.cjs +6 -0
- package/dist/containers.cjs +590 -5
- package/dist/containers.d.ts +39 -0
- package/dist/containers.js +3803 -977
- package/dist/{index-Coos428-.js → index--_rUTrqU.js} +308 -282
- package/dist/{index-B4vneBZh.cjs → index-B4oFnh1T.cjs} +6 -6
- package/dist/index-BHSxFTgZ.js +49 -0
- package/dist/{index-BSfhC_wu.cjs → index-BaA_oL1s.cjs} +1 -1
- package/dist/{index-CJmnkrIs.cjs → index-Becfy0pF.cjs} +1 -1
- package/dist/{index-Dj5oTSEE.js → index-C09d0pI4.js} +15 -15
- package/dist/{index-BXrnPbjr.cjs → index-CbQWytWd.cjs} +4 -4
- package/dist/{index-DmVIgb18.js → index-CgAb-gZi.js} +11 -11
- package/dist/{index-B53YL3vD.cjs → index-DFOQKDki.cjs} +2 -2
- package/dist/index-DFSkcsx-.cjs +943 -0
- package/dist/{index-CEz0St1t.js → index-DQFN7qxo.js} +7 -7
- package/dist/index-DRozw3P8.js +167 -0
- package/dist/index-DfCQoHSf.cjs +146 -0
- package/dist/index-DkuJMEY1.js +6731 -0
- package/dist/{index-bRjoenrr.js → index-Dv6jyKbT.js} +12 -12
- package/dist/{index-Bl3JVLei.cjs → index-EDm9-cRY.cjs} +1 -1
- package/dist/index-IGN7_cyg.cjs +2 -0
- package/dist/{index-DGvdYnh3.js → index-lFl6UsTa.js} +7 -7
- package/dist/index-lQmq7gxp.cjs +54 -0
- package/dist/{index-FS8xE7Mo.js → index-xUb0UC07.js} +5 -5
- package/dist/lib-C3DWunRS.js +26376 -0
- package/dist/lib-COvHzA2Y.cjs +2104 -0
- package/dist/lib.cjs +1 -1
- package/dist/lib.d.ts +160 -7
- package/dist/lib.js +33 -30
- package/dist/libRoutes-B-H3e9wZ.js +22 -0
- package/dist/libRoutes-Cl3TklhN.cjs +1 -0
- package/dist/network.cjs +1 -1
- package/dist/network.d.ts +19 -0
- package/dist/network.js +3 -3
- package/dist/router.cjs +1 -1
- package/dist/router.d.ts +26 -4
- package/dist/router.js +10 -10
- package/package.json +54 -47
- package/src/assets/main.css +2 -2
- package/src/components/AutoroutedBreadcrumb/AutoroutedBreadcrumb.mdx +8 -8
- package/src/components/AutoroutedBreadcrumb/AutoroutedBreadcrumb.spec.ts +86 -45
- package/src/components/AutoroutedBreadcrumb/AutoroutedBreadcrumb.vue +29 -21
- package/src/components/AvatarComponent/AvatarComponent.mdx +63 -0
- package/src/components/AvatarComponent/AvatarComponent.stories.ts +98 -0
- package/src/components/AvatarComponent/AvatarComponent.vue +115 -0
- package/src/components/GanttChartComponent/GanttChartComponent.mdx +143 -0
- package/src/components/GanttChartComponent/GanttChartComponent.spec.ts +257 -0
- package/src/components/GanttChartComponent/GanttChartComponent.stories.ts +253 -0
- package/src/components/GanttChartComponent/GanttChartComponent.vue +220 -0
- package/src/components/GanttChartComponent/GanttChartGrid.vue +66 -0
- package/src/components/GanttChartComponent/GanttChartHeaderGrid.vue +167 -0
- package/src/components/GanttChartComponent/GanttChartHeaderLabel.vue +23 -0
- package/src/components/GanttChartComponent/GanttChartLinksOverlay.vue +105 -0
- package/src/components/GanttChartComponent/GanttChartRowGrid.vue +288 -0
- package/src/components/GanttChartComponent/GanttChartRowLabel.vue +32 -0
- package/src/components/GanttChartComponent/composables/useGanttLinks.ts +212 -0
- package/src/components/GanttChartComponent/composables/useGanttSizing.ts +42 -0
- package/src/components/GanttChartComponent/ganttChartLayout.ts +211 -0
- package/src/components/GanttChartComponent/ganttChartTypes.ts +24 -0
- package/src/components/GanttChartComponent/index.ts +1 -0
- package/src/components/NetworkButton/ApiMutationButton.vue +7 -5
- package/src/components/NetworkButton/ApiQueryButton.vue +6 -4
- package/src/components/PlotlyComponent/PlotlyComponent.stories.ts +74 -45
- package/src/containers/BareContainer/BareContainer.mdx +1 -1
- package/src/containers/LayoutContainer/LayoutContainer.mdx +128 -0
- package/src/containers/LayoutContainer/LayoutContainer.spec.ts +151 -0
- package/src/containers/LayoutContainer/LayoutContainer.stories.ts +292 -0
- package/src/containers/LayoutContainer/LayoutContainer.vue +53 -0
- package/src/containers/LayoutContainer/MobileNavigationComponent/MobileNavigationComponent.spec.ts +139 -0
- package/src/containers/LayoutContainer/MobileNavigationComponent/MobileNavigationComponent.vue +63 -0
- package/src/containers/LayoutContainer/SideNavigationComponent/BottomComponent/BottomComponent.spec.ts +39 -0
- package/src/containers/LayoutContainer/SideNavigationComponent/BottomComponent/BottomComponent.vue +9 -0
- package/src/containers/LayoutContainer/SideNavigationComponent/MainComponent/MainComponent.spec.ts +175 -0
- package/src/containers/LayoutContainer/SideNavigationComponent/MainComponent/MainComponent.vue +163 -0
- package/src/containers/LayoutContainer/SideNavigationComponent/MainComponent/NavigationLinkComponent.spec.ts +105 -0
- package/src/containers/LayoutContainer/SideNavigationComponent/MainComponent/NavigationLinkComponent.vue +45 -0
- package/src/containers/LayoutContainer/SideNavigationComponent/SideNavigationComponent.spec.ts +78 -0
- package/src/containers/LayoutContainer/SideNavigationComponent/SideNavigationComponent.vue +29 -0
- package/src/containers/LayoutContainer/SideNavigationComponent/TopComponent/TopComponent.spec.ts +60 -0
- package/src/containers/LayoutContainer/SideNavigationComponent/TopComponent/TopComponent.vue +56 -0
- package/src/containers/LayoutContainer/UserMenuTriggerComponent/UserMenuTriggerComponent.spec.ts +96 -0
- package/src/containers/LayoutContainer/UserMenuTriggerComponent/UserMenuTriggerComponent.vue +80 -0
- package/src/containers/LayoutContainer/index.ts +1 -0
- package/src/containers/NavbarContainer/NavbarContainer.mdx +1 -1
- package/src/containers/RoutedTabsComponent/RoutedTabsComponent.mdx +3 -3
- package/src/containers/SideMenuContainer/SideMenuContainer.mdx +1 -1
- package/src/containers/helpers.ts +6 -3
- package/src/containers/index.ts +2 -0
- package/src/containers/storybook/PlaceholderView.vue +1 -1
- package/src/containers/storybook/PrimeComponents.stories.ts +17 -0
- package/src/containers/storybook/PrimeComponentsShowcase.vue +587 -0
- package/src/containers/storybook/overview.mdx +36 -36
- package/src/demo/App.vue +7 -0
- package/src/{demo.ts → demo/main.ts} +8 -9
- package/src/demo/router.ts +65 -19
- package/src/demo/views/PlaygroundView.vue +86 -0
- package/src/demo/views/ShowcaseView.vue +41 -0
- package/src/lib.ts +3 -1
- package/src/locales/Translation.mdx +2 -2
- package/src/locales/en/avatar.json +3 -0
- package/src/locales/en/gantt_chart.json +6 -0
- package/src/locales/en/navigation.json +3 -1
- package/src/locales/index.ts +0 -4
- package/src/plugins/legalConsent/views/__tests__/LegalConsent.test.ts +12 -7
- package/src/router/guards.ts +4 -4
- package/src/router/libRoutes.ts +6 -2
- package/src/router/router.mdx +107 -66
- package/src/router/types.ts +24 -3
- package/src/stores/__tests__/backend/jwt.test.ts +4 -4
- package/src/stores/__tests__/backend/oauth.test.ts +104 -0
- package/src/stores/__tests__/backend/token.test.ts +4 -4
- package/src/stores/authentication.mdx +138 -0
- package/src/stores/backend/common.ts +89 -0
- package/src/stores/backend/constants.ts +22 -0
- package/src/stores/backend/schemes/jwt.ts +208 -0
- package/src/stores/backend/schemes/oauth.ts +142 -0
- package/src/stores/backend/schemes/token.ts +122 -0
- package/src/stores/backend/types.ts +96 -0
- package/src/stores/backend.ts +21 -427
- package/src/stores/index.ts +6 -0
- package/src/theme/index.ts +2 -0
- package/src/theme/nside.ts +157 -0
- package/src/utils/color.spec.ts +24 -0
- package/src/utils/color.ts +100 -0
- package/src/utils/translations.ts +0 -4
- package/dist/LegalConsent-CEcXZml6.cjs +0 -1
- package/dist/LegalConsent-Dzq3fdnt.js +0 -277
- package/dist/LegalDocument-CS3MnOcV.cjs +0 -109
- package/dist/axios-ClRPr3Xn.js +0 -1777
- package/dist/axios-Dcidtc2l.cjs +0 -6
- package/dist/index-Bc699sOR.js +0 -4997
- package/dist/index-CL_OJMNr.cjs +0 -55
- package/dist/index-CTNsucOq.cjs +0 -147
- package/dist/index-CwLAV8WF.js +0 -210
- package/dist/index-FrfvunRp.cjs +0 -146
- package/dist/lib-BBJH9d11.cjs +0 -2792
- package/dist/lib-Y8FPgwH4.js +0 -20886
- package/dist/libRoutes-BsneoQ4G.js +0 -18
- package/dist/libRoutes-BzeZrIaK.cjs +0 -1
- package/src/demo/DemoApp.vue +0 -13
- package/src/demo/ShowcaseView.vue +0 -39
- package/src/demo/demo.css +0 -15
- /package/src/demo/{DemoContent.vue → views/DemoContent.vue} +0 -0
- /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
|
+
}
|