From 7e5d08e623d60d260894d0eafc42008d53a0c63a Mon Sep 17 00:00:00 2001 From: Rakov Date: Thu, 28 Sep 2023 14:18:32 +0300 Subject: [PATCH] fix(#699): auth token --- public/silent-refresh.html | 41 ++++++++++++++++--- src/features/AuthStore/hooks/useAuth.tsx | 28 +++++++++++-- src/features/SystemSettings/hooks.tsx | 1 + src/helpers/cookie/index.tsx | 2 +- src/requests/getCredentials.tsx | 51 ++++++++++++++++++++++++ 5 files changed, 114 insertions(+), 9 deletions(-) create mode 100644 src/requests/getCredentials.tsx diff --git a/public/silent-refresh.html b/public/silent-refresh.html index 852cc138..56cfb515 100644 --- a/public/silent-refresh.html +++ b/public/silent-refresh.html @@ -1,15 +1,46 @@ + + - + - + + \ No newline at end of file diff --git a/src/features/AuthStore/hooks/useAuth.tsx b/src/features/AuthStore/hooks/useAuth.tsx index 115a0a28..41ec48e9 100644 --- a/src/features/AuthStore/hooks/useAuth.tsx +++ b/src/features/AuthStore/hooks/useAuth.tsx @@ -14,7 +14,7 @@ import isString from 'lodash/isString' import isBoolean from 'lodash/isBoolean' import includes from 'lodash/includes' -import { PAGES } from 'config' +import { PAGES, isIOS } from 'config' import { addLanguageUrlParam, @@ -24,6 +24,7 @@ import { setCookie, removeCookie, isMatchPage, + getDomain, } from 'helpers' import { @@ -65,6 +66,14 @@ export const useAuth = () => { }) } + const parseJwt = (value: string) => { + const base64Url = value.split('.')[1] + const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/') + const jsonPayload = decodeURIComponent(window.atob(base64).split('').map((c) => `%${(`00${c.charCodeAt(0).toString(16)}`).slice(-2)}`).join('')) + + return JSON.parse(jsonPayload) + } + const login = useCallback(async () => { userManager.signinRedirect({ extraQueryParams: { lang } }) }, [lang]) @@ -79,6 +88,7 @@ export const useAuth = () => { removeToken() if (key !== 'saveToken') { removeCookie('access_token') + removeCookie('refresh_token') } // eslint-disable-next-line react-hooks/exhaustive-deps }, [lang]) @@ -158,12 +168,21 @@ export const useAuth = () => { } } - const signinRedirectCallback = useCallback(() => { + const saveRefreshToken = (value: string) => { + const ref = parseJwt(value) + const expires = `expires=${new Date((ref.exp * 1000)).toUTCString()}` + document.cookie = `refresh_token=${value};${expires};path=/;domain=${getDomain()};secure;SameSite=None` + } + + const signinRedirectCallback = useCallback(async (refreshToken: string | null) => { setPage(history.location.pathname) userManager.signinRedirectCallback() .then((loadedUser) => { storeUser(loadedUser) + + if (isIOS && refreshToken) saveRefreshToken(refreshToken) + queryParamStorage.clear() if (page.includes(PAGES.useraccount)) { history.push(PAGES.home) @@ -187,10 +206,13 @@ export const useAuth = () => { const searchToken = urlSearch.get('access_token') const searchRefToken = urlSearch.get('id_token') const searchExp = urlSearch.get('expires_in') + const refreshToken = urlSearch.get('refresh_token') const isRedirectedBackFromAuthProvider = Boolean(searchToken && searchRefToken && searchExp) - isRedirectedBackFromAuthProvider ? signinRedirectCallback() : checkUser() + isRedirectedBackFromAuthProvider + ? signinRedirectCallback(refreshToken) + : checkUser() // eslint-disable-next-line react-hooks/exhaustive-deps }, [ checkUser, diff --git a/src/features/SystemSettings/hooks.tsx b/src/features/SystemSettings/hooks.tsx index 8ab7df2e..fb200f3e 100644 --- a/src/features/SystemSettings/hooks.tsx +++ b/src/features/SystemSettings/hooks.tsx @@ -38,6 +38,7 @@ export const useSystemSettings = () => { setSelectedApi(api.value) removeToken() removeCookie('access_token') + removeCookie('refresh_token') window.location.reload() } diff --git a/src/helpers/cookie/index.tsx b/src/helpers/cookie/index.tsx index cbad2d33..87b22f22 100644 --- a/src/helpers/cookie/index.tsx +++ b/src/helpers/cookie/index.tsx @@ -29,7 +29,7 @@ export const checkCookie = (name: string) => { return token[0] } -const getDomain = () => ( +export const getDomain = () => ( process.env.NODE_ENV === 'development' ? 'localhost' : '.insports.tv' diff --git a/src/requests/getCredentials.tsx b/src/requests/getCredentials.tsx new file mode 100644 index 00000000..dce5eb7e --- /dev/null +++ b/src/requests/getCredentials.tsx @@ -0,0 +1,51 @@ +import { AUTH_SERVICE } from 'config' +import { ClientIds } from 'config/clients/types' + +export type TokenFailedResponse = { + error?: { + code: number, + message: string, + }, + ok: false, +} + +type TokenResponse = { + access_token: string, + id_token: string, + refresh_token: string, +} + +type TokenProps = { + client_id: ClientIds, + email?: 'string', + grant_type?: 'password' | 'refresh_token', + id_token?: 'string', + password?: 'string', + refresh_token: string, +} + +export const getCredentials = async ({ + client_id, + grant_type = 'refresh_token', + refresh_token, +}: TokenProps): Promise => { + const url = new URL(`${AUTH_SERVICE}/token`) + + const credetials = await fetch(url, { + body: JSON.stringify({ + client_id, + grant_type, + refresh_token, + }), + headers: { + 'Content-Type': 'application/json', + }, + method: 'POST', + }) + + const body: TokenResponse | TokenFailedResponse = await credetials.json() + + if ('ok' in body) return Promise.reject(body.error) + + return Promise.resolve(body) +}