diff --git a/src/frontend/auth/oidc-client.js b/src/frontend/auth/oidc-client.js index dce37bbc..e8629b83 100644 --- a/src/frontend/auth/oidc-client.js +++ b/src/frontend/auth/oidc-client.js @@ -30,32 +30,117 @@ Contributors: export default { login() { - window.OidcUserManager.signinRedirect() - .then(() => { - // eslint-disable-next-line no-console - console.log('User logged in'); - }) - .catch(error => { - // eslint-disable-next-line no-console - console.error(error); - }); + try { + // Сохраняем текущий URL для возврата после авторизации + const currentPath = window.location.pathname + window.location.search + window.location.hash; + localStorage.setItem('last-visited-url', currentPath); + + // Перенаправляем на страницу авторизации GitLab + window.OidcUserManager.signinRedirect() + .then(() => { + // eslint-disable-next-line no-console + console.log('Redirecting to login page...'); + }) + .catch(error => { + // eslint-disable-next-line no-console + console.error('Error during login redirect:', error); + }); + } catch (error) { + console.error('Error during login:', error); + } }, logout() { - window.OidcUserManager.signoutRedirect() - .then(() => { - // eslint-disable-next-line no-console - console.log('User logged out'); - }) - .catch(error => { - // eslint-disable-next-line no-console - console.error(error); - }); + try { + // Очищаем токены + localStorage.removeItem('access_token'); + localStorage.removeItem('refresh_token'); + localStorage.removeItem('original-route'); + localStorage.removeItem('last-visited-url'); + + // Выходим из системы + window.OidcUserManager.signoutRedirect() + .then(() => { + // eslint-disable-next-line no-console + console.log('User logged out'); + }) + .catch(error => { + // eslint-disable-next-line no-console + console.error('Error during logout:', error); + // В случае ошибки перенаправляем на главную страницу + window.location = window.origin + '/main'; + }); + } catch (error) { + console.error('Error during logout:', error); + // В случае ошибки перенаправляем на главную страницу + window.location = window.origin + '/main'; + } }, async signinCallback() { - if (window.location.hash) { - await window.OidcUserManager.signinCallback(); - window.location.hash = ''; - } else { + try { + // Обрабатываем callback от OIDC провайдера + if (window.location.hash) { + await window.OidcUserManager.signinCallback(); + window.location.hash = ''; + } + + // Проверяем, авторизован ли пользователь + const user = await window.OidcUserManager.getUser(); + if (!user) { + console.error('User not authenticated after signin callback'); + return; + } + + // Проверяем, есть ли сохраненный маршрут + const originalRoute = window.localStorage.getItem('original-route'); + if (originalRoute) { + try { + const parsedRoute = JSON.parse(originalRoute); + + // Удаляем сохраненный маршрут ТОЛЬКО после успешной авторизации + window.localStorage.removeItem('original-route'); + + // Формируем полный URL с учетом параметров и хэша + let url = parsedRoute.path; + const queryParams = new URLSearchParams(); + + // Добавляем параметры запроса + if (parsedRoute.query) { + Object.entries(parsedRoute.query).forEach(([key, value]) => { + queryParams.append(key, value); + }); + } + + // Добавляем параметры запроса к URL, если они есть + const queryString = queryParams.toString(); + if (queryString) { + url += '?' + queryString; + } + + // Добавляем хэш, если он есть + if (parsedRoute.hash) { + url += parsedRoute.hash; + } + + // Перенаправляем на восстановленный URL + window.location = window.origin + url; + } catch (error) { + console.error('Error processing saved route:', error); + window.localStorage.removeItem('original-route'); + window.location = window.origin + '/main'; + } + } else { + // Проверяем, есть ли сохраненный URL в localStorage + const lastVisitedUrl = window.localStorage.getItem('last-visited-url'); + if (lastVisitedUrl) { + window.localStorage.removeItem('last-visited-url'); + window.location = window.origin + lastVisitedUrl; + } else { + window.location = window.origin + '/main'; + } + } + } catch (error) { + console.error('Error during signin callback:', error); + // НЕ удаляем маршрут при ошибке, чтобы можно было повторить попытку window.location = window.origin + '/main'; } }, diff --git a/src/frontend/components/Layouts/Header.vue b/src/frontend/components/Layouts/Header.vue index 6b971df4..684d10a1 100644 --- a/src/frontend/components/Layouts/Header.vue +++ b/src/frontend/components/Layouts/Header.vue @@ -185,12 +185,20 @@ window.$PAPI.goto(null, entity, id); }, loginout() { - this.user ? oidcClient.logout() : oidcClient.login(); - console.log("login/logout"); - this.user ? oidcClient.logout() : oidcClient.login().then(() => { - window.Vuex.dispatch('setRolesFromToken'); - console.log("call set roles from token"); - }); + if (this.user) { + // Выход пользователя + oidcClient.logout(); + // Очищаем токены + localStorage.removeItem('access_token'); + localStorage.removeItem('refresh_token'); + this.user = null; + window.Vuex.dispatch('clean'); + } else { + // Вход пользователя + oidcClient.login(); + // После успешного входа роли будут установлены автоматически + console.log("Redirecting to login..."); + } } } }; diff --git a/src/frontend/helpers/href.js b/src/frontend/helpers/href.js index d72c2ea3..609e640b 100644 --- a/src/frontend/helpers/href.js +++ b/src/frontend/helpers/href.js @@ -46,13 +46,20 @@ export default { gotoURL(ref) { try { if (uri.isExternalURI(ref)) { - window.open(ref, 'blank_'); + window.open(ref, '_blank'); } else { const url = new URL(ref, window.location); - if (isLocalRoute(url)) - window.Router.push({ path: url.pathname, query: Object.fromEntries(url.searchParams)}); - else - window.open(url, 'blank_'); + if (isLocalRoute(url)) { + const fullPath = url.pathname + (url.search || '') + (url.hash || ''); + window.localStorage.setItem('last-visited-url', fullPath); + window.Router.push({ + path: url.pathname, + query: Object.fromEntries(url.searchParams), + hash: url.hash + }); + } else { + window.open(url, '_blank'); + } } } catch (e) { if (env.isPlugin(Plugins.idea)) { diff --git a/src/frontend/router/index.js b/src/frontend/router/index.js index a6fd4911..caa14236 100644 --- a/src/frontend/router/index.js +++ b/src/frontend/router/index.js @@ -66,13 +66,46 @@ if (!env.isPlugin()) { ? route.query.code : new URLSearchParams(route.hash.substr(1)).get('code'); if (OAuthCode) { + // Диспатчим событие получения OAuth-кода window.Vuex.dispatch('onReceivedOAuthCode', OAuthCode); - const rRoute = cookie.get('return-route'); - return rRoute ? JSON.parse(rRoute) : { - path: '/main', - query: {}, - hash: '' - }; + + // Пытаемся получить сохраненный маршрут из cookie или localStorage + let rRoute = cookie.get('return-route'); + const lsRoute = window.localStorage.getItem('original-route'); + + // Предпочитаем localStorage, если там есть данные + if (lsRoute) { + rRoute = lsRoute; + } + + if (rRoute) { + try { + const parsedRoute = JSON.parse(rRoute); + + // Сохраняем маршрут в localStorage для использования после авторизации + // НЕ удаляем маршрут здесь, чтобы он был доступен после перезагрузки страницы + window.localStorage.setItem('original-route', rRoute); + + // Очищаем cookie, так как данные уже в localStorage + cookie.delete('return-route'); + + // Возвращаем маршрут для редиректа + return parsedRoute; + } catch (e) { + console.error('Error parsing saved route:', e); + return { + path: '/main', + query: {}, + hash: '' + }; + } + } else { + return { + path: '/main', + query: {}, + hash: '' + }; + } } else { return { path: '/sso/error', diff --git a/src/frontend/router/routes.js b/src/frontend/router/routes.js index d68d2224..96c4b1c1 100644 --- a/src/frontend/router/routes.js +++ b/src/frontend/router/routes.js @@ -48,25 +48,40 @@ import SSOError from '@front/components/sso/SSOError'; import oidcClient from '@front/auth/oidc-client'; const middleware = (route) => { + // Проверяем, настроен ли OAuth и не авторизован ли пользователь if (config.oauth !== false && !window.Vuex.state.isOAuthProcess && !window.Vuex.state.access_token) { - cookie.set('return-route', JSON.stringify({ + // Сохраняем полный URL, включая параметры запроса, хэш и параметры маршрута + const fullRoute = { path: route.path, query: route.query, - hash: route.hash - }), 1); - window.location = new URL( - `/oauth/authorize?client_id=${config.oauth.APP_ID}` - + '&redirect_uri=' + new URL(consts.pages.OAUTH_CALLBACK_PAGE, window.location) - + `&response_type=code&state=none&scope=${config.oauth.REQUESTED_SCOPES}` - + '&' + Math.floor(Math.random() * 10000) - , config.gitlab_server - ); + hash: route.hash, + params: route.params + }; + + // Сохраняем в cookie и localStorage для надежности + cookie.set('return-route', JSON.stringify(fullRoute), 1); + window.localStorage.setItem('original-route', JSON.stringify(fullRoute)); + + // Формируем URL для авторизации с добавлением случайного параметра для предотвращения кэширования + const authUrl = new URL( + `/oauth/authorize?client_id=${config.oauth.APP_ID}` + + '&redirect_uri=' + encodeURIComponent(new URL(consts.pages.OAUTH_CALLBACK_PAGE, window.location).toString()) + + `&response_type=code&state=none&scope=${config.oauth.REQUESTED_SCOPES}` + + '&random=' + Math.floor(Math.random() * 10000) + , config.gitlab_server + ); + + // Перенаправляем на страницу авторизации + window.location = authUrl; + return; } + // Проверяем роли пользователя, если включена ролевая модель window.OidcUserManager.getUser().then(user => { if (user) { // eslint-disable-next-line no-console - console.log(user.profile.roles); + console.log('User authenticated:', user.profile.name); + console.log('User roles:', user.profile.roles); } }); diff --git a/src/frontend/storage/gitlab.js b/src/frontend/storage/gitlab.js index 259ca547..0c90d4f6 100644 --- a/src/frontend/storage/gitlab.js +++ b/src/frontend/storage/gitlab.js @@ -141,6 +141,66 @@ export default { actions: { // Action for init store init(context) { + // Восстанавливаем токены из localStorage, если они есть + const savedAccessToken = localStorage.getItem('access_token'); + const savedRefreshToken = localStorage.getItem('refresh_token'); + + if (savedAccessToken) { + context.commit('setAccessToken', savedAccessToken); + } + + if (savedRefreshToken) { + context.commit('setRefreshToken', savedRefreshToken); + // Если есть refresh_token, обновляем access_token + context.dispatch('refreshAccessToken'); + } + + // Проверяем, есть ли сохраненный маршрут + const originalRoute = window.localStorage.getItem('original-route'); + if (originalRoute) { + try { + const parsedRoute = JSON.parse(originalRoute); + // Если есть сохраненный маршрут, проверяем аутентификацию + window.OidcUserManager.getUser().then(user => { + if (user) { + // Если пользователь аутентифицирован, восстанавливаем маршрут + window.localStorage.removeItem('original-route'); + + // Формируем полный URL с учетом параметров и хэша + let url = parsedRoute.path; + const queryParams = new URLSearchParams(); + + // Добавляем параметры запроса + if (parsedRoute.query) { + Object.entries(parsedRoute.query).forEach(([key, value]) => { + queryParams.append(key, value); + }); + } + + // Добавляем параметры запроса к URL, если они есть + const queryString = queryParams.toString(); + if (queryString) { + url += '?' + queryString; + } + + // Добавляем хэш, если он есть + if (parsedRoute.hash) { + url += parsedRoute.hash; + } + + // Перенаправляем на восстановленный URL + if (window.location.pathname !== url) { + window.location = window.origin + url; + } + } + }); + } catch (error) { + console.error('Error processing saved route:', error); + window.localStorage.removeItem('original-route'); + } + } + + // Продолжаем обычную инициализацию context.dispatch('plugins/init'); const errors = { @@ -387,15 +447,25 @@ export default { url: OAuthURL, params: Object.assign({ client_id: config.oauth.APP_ID, + client_secret: config.oauth.CLIENT_SECRET, redirect_uri: (new URL(consts.pages.OAUTH_CALLBACK_PAGE, window.location)).toString() }, params) }) .then((response) => { context.commit('setAccessToken', response.data.access_token); context.commit('setRefreshToken', response.data.refresh_token); + + // Сохраняем токены в localStorage для сохранения между сессиями + localStorage.setItem('access_token', response.data.access_token); + localStorage.setItem('refresh_token', response.data.refresh_token); + // Если expires_in нет, считаем, что токен вечный response.data.expires_in && setTimeout(() => context.dispatch('refreshAccessToken'), (response.data.expires_in - 10) * 1000); - if (OAuthCode) context.dispatch('reloadAll'); + + if (OAuthCode) { + // После успешного получения токена загружаем данные + context.dispatch('reloadAll'); + } }).catch((e) => { context.commit('appendProblems', [{ problem: validatorErrors.title.net, @@ -410,6 +480,8 @@ export default { // Need to call when gitlab takes callback's rout with oauth code onReceivedOAuthCode(context, OAuthCode) { + // Сохраняем код авторизации и запускаем процесс получения токена + console.log('Received OAuth code, requesting access token...'); context.dispatch('refreshAccessToken', OAuthCode); },