Merged PR 550: 画面実装(トークンを定期的に更新する仕組み)
## 概要 [Task2910: 画面実装(トークンを定期的に更新する仕組み)](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/2910) - タイマーで定期的に代行操作用のアクセストークンを更新する処理を実装しました。 ## レビューポイント - 通常のアクセストークンのタイマー内で同じタイミングでチェックするように実装していますが分けたほうがいいなどありますでしょうか? ## UIの変更 - [Task2910](https://ndstokyo.sharepoint.com/:f:/r/sites/Piranha/Shared%20Documents/General/OMDS/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88/Task2910?csf=1&web=1&e=g0RdIf) ## 動作確認状況 - ローカルで確認
This commit is contained in:
parent
ef4f22029b
commit
0212c61bbc
@ -1,5 +1,6 @@
|
||||
// トークンの型やtypeGuardの関数を配置するファイル
|
||||
export interface Token {
|
||||
delegateUserId?: string;
|
||||
userId: string;
|
||||
role: string;
|
||||
tier: number;
|
||||
|
||||
@ -33,4 +33,20 @@ export const TIERS = {
|
||||
* 401エラー時にログアウトさせずに処理を継続するエラーコード
|
||||
* @const {string[]}
|
||||
*/
|
||||
export const UNAUTHORIZED_TO_CONTINUE_ERROR_CODES = ["E010209"];
|
||||
export const UNAUTHORIZED_TO_CONTINUE_ERROR_CODES = [
|
||||
"E010209",
|
||||
"E010503",
|
||||
"E10501",
|
||||
];
|
||||
|
||||
/**
|
||||
* アクセストークンを更新する基準の秒数
|
||||
* @const {number}
|
||||
*/
|
||||
export const TOKEN_UPDATE_TIME = 5 * 60;
|
||||
|
||||
/**
|
||||
* アクセストークンの更新チェックを行う間隔(ミリ秒)
|
||||
* @const {number}
|
||||
*/
|
||||
export const TOKEN_UPDATE_INTERVAL_MS = 3 * 60 * 1000;
|
||||
|
||||
@ -2,33 +2,56 @@ import React, { useCallback } from "react";
|
||||
import { AppDispatch } from "app/store";
|
||||
import { decodeToken } from "common/decodeToken";
|
||||
import { useInterval } from "common/useInterval";
|
||||
import { updateTokenAsync, loadAccessToken } from "features/auth";
|
||||
import {
|
||||
updateTokenAsync,
|
||||
loadAccessToken,
|
||||
updateDelegationTokenAsync,
|
||||
} from "features/auth";
|
||||
import { DateTime } from "luxon";
|
||||
import { useDispatch } from "react-redux";
|
||||
// アクセストークンを更新する基準の秒数
|
||||
const TOKEN_UPDATE_TIME = 5 * 60;
|
||||
// アクセストークンの更新チェックを行う間隔(ミリ秒)
|
||||
const TOKEN_UPDATE_INTERVAL_MS = 3 * 60 * 1000;
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { selectDelegationAccessToken } from "features/auth/selectors";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { cleanupDelegateAccount } from "features/partner";
|
||||
import { TOKEN_UPDATE_INTERVAL_MS, TOKEN_UPDATE_TIME } from "./constants";
|
||||
|
||||
export const UpdateTokenTimer = () => {
|
||||
const dispatch: AppDispatch = useDispatch();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const delegattionToken = useSelector(selectDelegationAccessToken);
|
||||
|
||||
// 期限が5分以内であれば更新APIを呼ぶ
|
||||
const updateToken = useCallback(async () => {
|
||||
// localStorageからトークンを取得
|
||||
const jwt = loadAccessToken();
|
||||
// 現在時刻を取得
|
||||
const now = DateTime.local().toSeconds();
|
||||
// selectorに以下の判定処理を移したかったが、初期表示時の値でしか判定できないのでComponent内に置く
|
||||
if (jwt) {
|
||||
const token = decodeToken(jwt);
|
||||
if (token) {
|
||||
const { exp } = token;
|
||||
const now = DateTime.local().toSeconds();
|
||||
if (exp - now <= TOKEN_UPDATE_TIME) {
|
||||
await dispatch(updateTokenAsync());
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [dispatch]);
|
||||
|
||||
// 代行操作トークン更新処理
|
||||
if (delegattionToken) {
|
||||
const token = decodeToken(delegattionToken);
|
||||
if (token) {
|
||||
const { exp } = token;
|
||||
if (exp - now <= TOKEN_UPDATE_TIME) {
|
||||
const { meta } = await dispatch(updateDelegationTokenAsync());
|
||||
if (meta.requestStatus === "rejected") {
|
||||
dispatch(cleanupDelegateAccount());
|
||||
navigate("/partners");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [dispatch, delegattionToken, navigate]);
|
||||
|
||||
useInterval(updateToken, TOKEN_UPDATE_INTERVAL_MS);
|
||||
|
||||
|
||||
@ -9,7 +9,11 @@ import {
|
||||
removeRefreshToken,
|
||||
} from "./utils";
|
||||
import type { AuthState } from "./state";
|
||||
import { getDelegationTokenAsync, updateTokenAsync } from "./operations";
|
||||
import {
|
||||
getDelegationTokenAsync,
|
||||
updateDelegationTokenAsync,
|
||||
updateTokenAsync,
|
||||
} from "./operations";
|
||||
|
||||
const initialState: AuthState = {
|
||||
configuration: initialConfig(),
|
||||
@ -61,6 +65,14 @@ export const authSlice = createSlice({
|
||||
state.delegationAccessToken = accessToken;
|
||||
state.delegationRefreshToken = refreshToken;
|
||||
});
|
||||
builder.addCase(updateDelegationTokenAsync.fulfilled, (state, action) => {
|
||||
const { accessToken } = action.payload;
|
||||
state.delegationAccessToken = accessToken;
|
||||
});
|
||||
builder.addCase(updateDelegationTokenAsync.rejected, (state) => {
|
||||
state.delegationAccessToken = null;
|
||||
state.delegationRefreshToken = null;
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@ -6,10 +6,11 @@ import { ErrorObject, createErrorObject } from "common/errors";
|
||||
import {
|
||||
AccessTokenResponse,
|
||||
AuthApi,
|
||||
DelegationAccessTokenResponse,
|
||||
DelegationTokenResponse,
|
||||
} from "../../api/api";
|
||||
import { Configuration } from "../../api/configuration";
|
||||
import { getAccessToken, loadRefreshToken } from "./utils";
|
||||
import { getAccessToken, getRefreshToken, loadRefreshToken } from "./utils";
|
||||
|
||||
export const updateTokenAsync = createAsyncThunk<
|
||||
AccessTokenResponse,
|
||||
@ -40,6 +41,50 @@ export const updateTokenAsync = createAsyncThunk<
|
||||
}
|
||||
});
|
||||
|
||||
export const updateDelegationTokenAsync = createAsyncThunk<
|
||||
DelegationAccessTokenResponse,
|
||||
void,
|
||||
{
|
||||
// rejectした時の返却値の型
|
||||
rejectValue: {
|
||||
/* Empty Object */
|
||||
};
|
||||
}
|
||||
>("auth/updateDelegationTokenAsync", async (args, thunkApi) => {
|
||||
// apiのConfigurationを取得する
|
||||
const { getState } = thunkApi;
|
||||
const state = getState() as RootState;
|
||||
const { configuration } = state.auth;
|
||||
const refreshToken = getRefreshToken(state.auth);
|
||||
const config = new Configuration(configuration);
|
||||
const authApi = new AuthApi(config);
|
||||
|
||||
try {
|
||||
const { data } = await authApi.delegationAccessToken({
|
||||
headers: { authorization: `Bearer ${refreshToken}` },
|
||||
});
|
||||
|
||||
return data;
|
||||
} catch (e) {
|
||||
const error = createErrorObject(e);
|
||||
|
||||
let errorMessage = getTranslationID("common.message.internalServerError");
|
||||
if (error.code === "E010503") {
|
||||
errorMessage = getTranslationID(
|
||||
"partnerPage.message.delegateCancelError"
|
||||
);
|
||||
}
|
||||
|
||||
thunkApi.dispatch(
|
||||
openSnackbar({
|
||||
level: "error",
|
||||
message: errorMessage,
|
||||
})
|
||||
);
|
||||
return thunkApi.rejectWithValue({});
|
||||
}
|
||||
});
|
||||
|
||||
// パートナーのアカウントを代理操作するトークンを取得する
|
||||
export const getDelegationTokenAsync = createAsyncThunk<
|
||||
// 正常時の戻り値の型
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { createAsyncThunk } from "@reduxjs/toolkit";
|
||||
import type { RootState } from "app/store";
|
||||
import { setToken, getAccessToken } from "features/auth";
|
||||
import { getAccessToken, setToken } from "features/auth";
|
||||
import {
|
||||
AuthApi,
|
||||
UsersApi,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user