diff --git a/dictation_client/src/App.tsx b/dictation_client/src/App.tsx index 9f35aca..1d47893 100644 --- a/dictation_client/src/App.tsx +++ b/dictation_client/src/App.tsx @@ -1,8 +1,6 @@ import AppRouter from "AppRouter"; import { BrowserRouter } from "react-router-dom"; -import { PublicClientApplication } from "@azure/msal-browser"; -import { MsalProvider, useMsal } from "@azure/msal-react"; -import { msalConfig } from "common/msalConfig"; +import { useMsal } from "@azure/msal-react"; import { useEffect, useLayoutEffect } from "react"; import { useDispatch, useSelector } from "react-redux"; import globalAxios, { AxiosError, AxiosResponse } from "axios"; @@ -19,7 +17,6 @@ const App = (): JSX.Element => { const { instance } = useMsal(); // eslint-disable-next-line @typescript-eslint/no-unused-vars const [t, i18n] = useTranslation(); - const pca = new PublicClientApplication(msalConfig); useEffect(() => { const id = globalAxios.interceptors.response.use( (response: AxiosResponse) => response, @@ -70,11 +67,9 @@ const App = (): JSX.Element => { dispatch(closeSnackbar()); }} /> - - - - - + + + ); }; diff --git a/dictation_client/src/common/token.ts b/dictation_client/src/common/token.ts index ac7c3e9..88e42d3 100644 --- a/dictation_client/src/common/token.ts +++ b/dictation_client/src/common/token.ts @@ -76,3 +76,16 @@ export const getIdTokenFromLocalStorage = ( } return null; }; + +// JWTが有効期限切れかどうかを判定する +export const isTokenExpired = (token: string | null): boolean => { + if (token == null) { + return true; + } + const tokenObject = JSON.parse(atob(token.split(".")[1])); + if (isToken(tokenObject)) { + const now = Math.floor(Date.now() / 1000); + return tokenObject.exp < now; + } + return true; +}; diff --git a/dictation_client/src/main.tsx b/dictation_client/src/main.tsx index 1aac073..924f95b 100644 --- a/dictation_client/src/main.tsx +++ b/dictation_client/src/main.tsx @@ -3,18 +3,25 @@ import React from "react"; import { createRoot } from "react-dom/client"; import { I18nextProvider } from "react-i18next"; import { Provider } from "react-redux"; +import { PublicClientApplication } from "@azure/msal-browser"; +import { msalConfig } from "common/msalConfig"; +import { MsalProvider } from "@azure/msal-react"; import App from "./App"; import * as serviceWorker from "./serviceWorker"; import i18n from "./i18n"; +const pca = new PublicClientApplication(msalConfig); + const container = document.getElementById("root"); if (container) { const root = createRoot(container); root.render( - - + + + + ); diff --git a/dictation_client/src/pages/AuthPage/index.tsx b/dictation_client/src/pages/AuthPage/index.tsx index e8d81f9..c891d0f 100644 --- a/dictation_client/src/pages/AuthPage/index.tsx +++ b/dictation_client/src/pages/AuthPage/index.tsx @@ -10,14 +10,6 @@ import { import React, { useEffect } from "react"; import { useDispatch, useSelector } from "react-redux"; import { useNavigate } from "react-router-dom"; -import { - clearToken, - isAdminUser, - isApproveTier, - isStandardUser, - loadAccessToken, -} from "features/auth"; -import { TIERS } from "components/auth/constants"; const AuthPage: React.FC = (): JSX.Element => { const { instance } = useMsal(); @@ -34,38 +26,7 @@ const AuthPage: React.FC = (): JSX.Element => { (async () => { try { - // ログイン済みの場合、ログイン後の遷移先を決定する - if (loadAccessToken()) { - // 第一~第四階層の管理者はライセンス画面へ遷移 - if ( - isApproveTier([ - TIERS.TIER1, - TIERS.TIER2, - TIERS.TIER3, - TIERS.TIER4, - ]) && - isAdminUser() - ) { - navigate("/license"); - return; - } - // 第五階層の管理者はユーザー画面へ遷移 - if (isApproveTier([TIERS.TIER5]) && isAdminUser()) { - navigate("/user"); - return; - } - // 一般ユーザーはdictationPageへ遷移 - if (isStandardUser()) { - navigate("/dictations"); - return; - } - // それ以外は認証エラー画面へ遷移 - instance.logoutRedirect({ - postLogoutRedirectUri: "/AuthError", - }); - clearToken(); - return; - } + // idTokenが有効セットされているかを確認する const loginResult = await instance.handleRedirectPromise(); if (loginResult && loginResult.account) { const { homeAccountId, idTokenClaims } = loginResult.account; diff --git a/dictation_client/src/pages/ErrorPage/index.tsx b/dictation_client/src/pages/ErrorPage/index.tsx index 4592ea0..6d82f50 100644 --- a/dictation_client/src/pages/ErrorPage/index.tsx +++ b/dictation_client/src/pages/ErrorPage/index.tsx @@ -1,8 +1,10 @@ import React from "react"; +import { Link } from "react-router-dom"; export const AuthErrorPage = (): JSX.Element => (
-

ログインに失敗しました

+

login failed


+ return to TopPage
); diff --git a/dictation_client/src/pages/LoginPage/index.tsx b/dictation_client/src/pages/LoginPage/index.tsx index c335bd3..8727452 100644 --- a/dictation_client/src/pages/LoginPage/index.tsx +++ b/dictation_client/src/pages/LoginPage/index.tsx @@ -1,6 +1,6 @@ import { useMsal } from "@azure/msal-react"; import { AppDispatch } from "app/store"; -import { isIdToken } from "common/token"; +import { isIdToken, isTokenExpired } from "common/token"; import { clearToken, isAdminUser, @@ -52,10 +52,10 @@ const LoginPage: React.FC = (): JSX.Element => { instance.logoutRedirect({ postLogoutRedirectUri: "/AuthError", }); - clearToken(); - }, [instance, navigate]); + dispatch(clearToken()); + }, [instance, navigate, dispatch]); - const tokenSet = useCallback( + const tokenSetAndNavigate = useCallback( async (idToken: string) => { // ログイン処理呼び出し const { meta, payload } = await dispatch(loginAsync({ idToken })); @@ -96,31 +96,32 @@ const LoginPage: React.FC = (): JSX.Element => { useEffect(() => { // idTokenStringがあるか⇒認証中 - // accessTokenがある場合⇒ログイン済み - // どちらもなければ直打ち + // accessTokenがある場合⇒ログイン済みなのにブラウザバックでログイン画面に戻ってきた場合 + // どちらもなければURL直打ち (async () => { - if (loadAccessToken()) { - navigateToLoginedPage(); - return; + // ローカルストレージにidTokenがある場合は取得する + let idTokenString: string | null = null; + if (localStorageKeyforIdToken !== null) { + idTokenString = localStorage.getItem(localStorageKeyforIdToken); } - // AADB2Cのログイン画面とLoginPageを経由していない場合はトップページに遷移する - if (!localStorageKeyforIdToken) { - navigate("/"); - return; - } - const idTokenString = localStorage.getItem(localStorageKeyforIdToken); - + // idTokenがない(=正常なログインプロセス中でない)場合は有効なアクセストークンを所持しているか確認し、 + // 有効であればログイン画面に遷移 or 無効であればトップページに遷移 if (idTokenString === null) { - navigate("/"); + const token = loadAccessToken(); + // アクセストークンがない or 有効期限切れ場合はトップページに遷移 + if (isTokenExpired(token)) { + navigate("/"); + } else { + // 有効なアクセストークンがある場合はログイン画面に遷移 + navigateToLoginedPage(); + } return; } - if (idTokenString) { - const idTokenObject = JSON.parse(idTokenString); - if (isIdToken(idTokenObject)) { - await tokenSet(idTokenObject.secret); - } + const idTokenObject = JSON.parse(idTokenString); + if (isIdToken(idTokenObject)) { + await tokenSetAndNavigate(idTokenObject.secret); } })(); // 画面描画後のみ実行するため引数を設定しない diff --git a/dictation_function/src/entity/user.entity.ts b/dictation_function/src/entity/user.entity.ts index 78d2c7d..10032d4 100644 --- a/dictation_function/src/entity/user.entity.ts +++ b/dictation_function/src/entity/user.entity.ts @@ -40,9 +40,6 @@ export class User { @Column({ default: true }) auto_renew: boolean; - @Column({ default: true }) - license_alert: boolean; - @Column({ default: true }) notification: boolean; diff --git a/dictation_function/src/test/common/utility.ts b/dictation_function/src/test/common/utility.ts index 512d975..f675ed7 100644 --- a/dictation_function/src/test/common/utility.ts +++ b/dictation_function/src/test/common/utility.ts @@ -48,7 +48,6 @@ export const makeTestUser = async ( accepted_dpa_version: d?.accepted_dpa_version ?? "1.0", email_verified: d?.email_verified ?? true, auto_renew: d?.auto_renew ?? true, - license_alert: d?.license_alert ?? true, notification: d?.notification ?? true, encryption: d?.encryption ?? true, encryption_password: d?.encryption_password, @@ -117,7 +116,6 @@ export const makeTestAccount = async ( accepted_dpa_version: d?.accepted_dpa_version ?? "1.0", email_verified: d?.email_verified ?? true, auto_renew: d?.auto_renew ?? true, - license_alert: d?.license_alert ?? true, notification: d?.notification ?? true, encryption: d?.encryption ?? true, encryption_password: d?.encryption_password ?? "password",