Merge branch 'develop' into main
This commit is contained in:
commit
dd00b2fe9b
@ -2,7 +2,6 @@ import { Route, Routes } from "react-router-dom";
|
||||
import TopPage from "pages/TopPage";
|
||||
import AuthPage from "pages/AuthPage";
|
||||
import LoginPage from "pages/LoginPage";
|
||||
import SamplePage from "pages/SamplePage";
|
||||
import { AuthErrorPage } from "pages/ErrorPage";
|
||||
import { NotFoundPage } from "pages/ErrorPage/notFound";
|
||||
import { RouteAuthGuard } from "components/auth/routeAuthGuard";
|
||||
@ -53,11 +52,6 @@ const AppRouter: React.FC = () => (
|
||||
path="/license"
|
||||
element={<RouteAuthGuard component={<LicensePage />} />}
|
||||
/>
|
||||
<Route
|
||||
path="/xxx"
|
||||
element={<RouteAuthGuard component={<SamplePage />} />}
|
||||
/>
|
||||
{/* XXX ヘッダーの挙動確認のため仮のページを作成 */}
|
||||
<Route
|
||||
path="/account"
|
||||
element={<RouteAuthGuard component={<AccountPage />} />}
|
||||
|
||||
@ -39,6 +39,12 @@ export const UNAUTHORIZED_TO_CONTINUE_ERROR_CODES = [
|
||||
"E010501",
|
||||
];
|
||||
|
||||
/**
|
||||
* ローカルストレージに残すキー類
|
||||
* @const {string[]}
|
||||
*/
|
||||
export const KEYS_TO_PRESERVE = ["accessToken", "refreshToken", "displayInfo"];
|
||||
|
||||
/**
|
||||
* アクセストークンを更新する基準の秒数
|
||||
* @const {number}
|
||||
|
||||
@ -46,6 +46,7 @@ export const DelegationBar: React.FC = (): JSX.Element => {
|
||||
alt="Exit"
|
||||
title="Exit"
|
||||
onClick={onClickExit}
|
||||
data-tag="exit-delegation"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -7,7 +7,6 @@ export const HEADER_MENUS_LICENSE = "License";
|
||||
export const HEADER_MENUS_DICTATIONS = "Dictations";
|
||||
export const HEADER_MENUS_WORKFLOW = "Workflow";
|
||||
export const HEADER_MENUS_PARTNER = "Partners";
|
||||
export const HEADER_MENUS_XXX = "XXX"; // XXX 仮のタブ
|
||||
|
||||
export const HEADER_MENUS: {
|
||||
key: HeaderMenus;
|
||||
@ -44,7 +43,6 @@ export const HEADER_MENUS: {
|
||||
label: getTranslationID("common.label.headerPartners"),
|
||||
path: "/partners",
|
||||
},
|
||||
{ key: HEADER_MENUS_XXX, label: "xxx", path: "/xxx" }, // XXX 仮のタブ
|
||||
];
|
||||
|
||||
export const HEADER_NAME = getTranslationID("common.label.headerName");
|
||||
|
||||
@ -87,6 +87,7 @@ const LoginedHeader: React.FC<HeaderProps> = (props: HeaderProps) => {
|
||||
? styles.isActive
|
||||
: ""
|
||||
}
|
||||
data-tag={`menu-${x.key}`}
|
||||
>
|
||||
{t(x.label)}
|
||||
</a>
|
||||
@ -101,6 +102,7 @@ const LoginedHeader: React.FC<HeaderProps> = (props: HeaderProps) => {
|
||||
<span
|
||||
className={styles.accountSignout}
|
||||
onClick={onSignoutButton}
|
||||
data-tag="logout"
|
||||
style={{ pointerEvents: isDelegation ? "none" : "auto" }}
|
||||
>
|
||||
<img src={logout} alt="" className={styles.accountIcon} />
|
||||
|
||||
@ -8,8 +8,7 @@ export type HeaderMenus =
|
||||
| "License"
|
||||
| "Dictations"
|
||||
| "Workflow"
|
||||
| "Partners"
|
||||
| "XXX";
|
||||
| "Partners";
|
||||
|
||||
// ログイン後に遷移しうるパス
|
||||
export type LoginedPaths =
|
||||
@ -18,5 +17,4 @@ export type LoginedPaths =
|
||||
| "/license"
|
||||
| "/dictations"
|
||||
| "/workflow"
|
||||
| "/partners"
|
||||
| "/xxx";
|
||||
| "/partners";
|
||||
|
||||
@ -20,7 +20,6 @@ export const isLoginPaths = (d: string): d is LoginedPaths => {
|
||||
case "/dictations":
|
||||
case "/workflow":
|
||||
case "/partners":
|
||||
case "/xxx":
|
||||
return true;
|
||||
default: {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
|
||||
@ -39,22 +39,34 @@ const Snackbar: React.FC<SnackbarProps> = (props) => {
|
||||
const isShow = isOpen ? styles.isShow : "";
|
||||
|
||||
return (
|
||||
<div className={`${styles.snackbar} ${isAlert} ${isShow}`}>
|
||||
<div
|
||||
className={`${styles.snackbar} ${isAlert} ${isShow}`}
|
||||
data-tag="snackbar"
|
||||
>
|
||||
{level === "error" ? (
|
||||
<img src={reportWhite} className={styles.snackbarIcon} alt="check" />
|
||||
<img
|
||||
src={reportWhite}
|
||||
className={styles.snackbarIcon}
|
||||
alt="check"
|
||||
data-tag="snackbar-error"
|
||||
/>
|
||||
) : (
|
||||
<img
|
||||
src={checkCircleWhite}
|
||||
className={styles.snackbarIcon}
|
||||
alt="report"
|
||||
data-tag="snackbar-report"
|
||||
/>
|
||||
)}
|
||||
<p className={styles.txNormal}>{message}</p>
|
||||
<p className={styles.txNormal} data-tag="snackbar-text">
|
||||
{message}
|
||||
</p>
|
||||
{level === "error" && (
|
||||
<button
|
||||
style={{ marginLeft: "auto" }}
|
||||
type="button"
|
||||
onClick={onCloseSnackbar}
|
||||
data-tag="close-snackbar"
|
||||
>
|
||||
<img
|
||||
src={closeWhite}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { Dealer } from "api/api";
|
||||
import { Dealer, User } from "api/api";
|
||||
import { RootState } from "app/store";
|
||||
|
||||
export const selectAccountInfo = (state: RootState) =>
|
||||
@ -10,7 +10,8 @@ export const selectDealers = (state: RootState) => {
|
||||
const { country } = state.account.domain.getAccountInfo.account;
|
||||
return dealers.filter((x: Dealer) => x.country === country);
|
||||
};
|
||||
export const selectUsers = (state: RootState) => state.account.domain.users;
|
||||
export const selectUsers = (state: RootState) =>
|
||||
state.account.domain.users.filter((x: User) => x.emailVerified);
|
||||
export const selectIsLoading = (state: RootState) =>
|
||||
state.account.apps.isLoading;
|
||||
export const selectUpdateAccountInfo = (state: RootState) =>
|
||||
|
||||
@ -81,7 +81,9 @@ export const isAdminUser = (): boolean => {
|
||||
if (!token) {
|
||||
return false;
|
||||
}
|
||||
return token.role.includes(ADMIN_ROLES.ADMIN);
|
||||
// token.roleを" "で分割して配列にする
|
||||
const role = token.role.split(" ");
|
||||
return role.includes(ADMIN_ROLES.ADMIN);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -95,7 +97,9 @@ export const isStandardUser = (): boolean => {
|
||||
if (!token) {
|
||||
return false;
|
||||
}
|
||||
return token.role.includes(ADMIN_ROLES.STANDARD);
|
||||
// token.roleを" "で分割して配列にする
|
||||
const role = token.role.split(" ");
|
||||
return role.includes(ADMIN_ROLES.STANDARD);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -108,7 +112,9 @@ export const isAuthorUser = (): boolean => {
|
||||
if (!token) {
|
||||
return false;
|
||||
}
|
||||
return token.role.includes(USER_ROLES.AUTHOR);
|
||||
// token.roleを" "で分割して配列にする
|
||||
const role = token.role.split(" ");
|
||||
return role.includes(USER_ROLES.AUTHOR);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -132,5 +138,8 @@ export const isTypistUser = (): boolean => {
|
||||
if (!token) {
|
||||
return false;
|
||||
}
|
||||
return token.role.includes(USER_ROLES.TYPIST);
|
||||
// token.roleを" "で分割して配列にする
|
||||
const role = token.role.split(" ");
|
||||
// roleの中に"typist"が含まれているかどうかを返す
|
||||
return role.includes(USER_ROLES.TYPIST);
|
||||
};
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { createAsyncThunk } from "@reduxjs/toolkit";
|
||||
import type { RootState } from "app/store";
|
||||
import { getAccessToken, setToken } from "features/auth";
|
||||
import { setToken } from "features/auth";
|
||||
import { KEYS_TO_PRESERVE } from "components/auth/constants";
|
||||
import {
|
||||
AuthApi,
|
||||
UsersApi,
|
||||
@ -42,7 +43,18 @@ export const loginAsync = createAsyncThunk<
|
||||
refreshToken: data.refreshToken,
|
||||
})
|
||||
);
|
||||
// ローカルストレージに残すキー
|
||||
const keysToPreserve = KEYS_TO_PRESERVE;
|
||||
|
||||
// すべてのローカルストレージキーを取得
|
||||
const allKeys = Object.keys(localStorage);
|
||||
|
||||
// 特定のキーを除外して削除
|
||||
allKeys.forEach((key) => {
|
||||
if (!keysToPreserve.includes(key)) {
|
||||
localStorage.removeItem(key);
|
||||
}
|
||||
});
|
||||
return data;
|
||||
} catch (e) {
|
||||
// e ⇒ errorObjectに変換"
|
||||
|
||||
@ -56,7 +56,11 @@ export const DeleteAccountPopup: React.FC<DeleteAccountPopupProps> = (
|
||||
<div className={styles.modalBox}>
|
||||
<p className={styles.modalTitle}>
|
||||
{t(getTranslationID("deleteAccountPopup.label.title"))}
|
||||
<button type="button" onClick={closePopup}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={closePopup}
|
||||
data-tag="close-delegate-popup"
|
||||
>
|
||||
<img src={close} className={styles.modalTitleIcon} alt="close" />
|
||||
</button>
|
||||
</p>
|
||||
@ -92,6 +96,7 @@ export const DeleteAccountPopup: React.FC<DeleteAccountPopupProps> = (
|
||||
)}
|
||||
className={`${styles.formDelete} ${styles.marginBtm1} ${styles.isActive}`}
|
||||
onClick={onDeleteAccount}
|
||||
data-tag="delete-account"
|
||||
/>
|
||||
<p>
|
||||
<input
|
||||
@ -102,6 +107,7 @@ export const DeleteAccountPopup: React.FC<DeleteAccountPopupProps> = (
|
||||
)}
|
||||
className={`${styles.formButtonTx} ${styles.marginBtm1}`}
|
||||
onClick={closePopup}
|
||||
data-tag="cancel-delete-account"
|
||||
/>
|
||||
</p>
|
||||
</dd>
|
||||
|
||||
@ -364,6 +364,7 @@ const AccountPage: React.FC = (): JSX.Element => {
|
||||
}
|
||||
`}
|
||||
onClick={onSaveChangesButton}
|
||||
data-tag="savechanges-account"
|
||||
/>
|
||||
<img
|
||||
style={{ display: isLoading ? "inline" : "none" }}
|
||||
@ -377,7 +378,11 @@ const AccountPage: React.FC = (): JSX.Element => {
|
||||
<ul className={styles.linkBottom}>
|
||||
<li>
|
||||
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */}
|
||||
<a className={styles.linkTx} onClick={onDeleteAccountOpen}>
|
||||
<a
|
||||
className={styles.linkTx}
|
||||
onClick={onDeleteAccountOpen}
|
||||
data-tag="open-delete-account-popup"
|
||||
>
|
||||
{t(getTranslationID("accountPage.label.deleteAccount"))}
|
||||
</a>
|
||||
</li>
|
||||
|
||||
@ -1,7 +1,14 @@
|
||||
import { useMsal } from "@azure/msal-react";
|
||||
import { AppDispatch } from "app/store";
|
||||
import { isIdToken } from "common/token";
|
||||
import { loadAccessToken, loadRefreshToken } from "features/auth";
|
||||
import {
|
||||
clearToken,
|
||||
isAdminUser,
|
||||
isApproveTier,
|
||||
isStandardUser,
|
||||
loadAccessToken,
|
||||
loadRefreshToken,
|
||||
} from "features/auth";
|
||||
import { loginAsync, selectLocalStorageKeyforIdToken } from "features/login";
|
||||
import React, { useCallback, useEffect } from "react";
|
||||
import Footer from "components/footer";
|
||||
@ -10,6 +17,7 @@ import { useTranslation } from "react-i18next";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { isErrorObject } from "common/errors";
|
||||
import { TIERS } from "components/auth/constants";
|
||||
|
||||
const LoginPage: React.FC = (): JSX.Element => {
|
||||
const { instance } = useMsal();
|
||||
@ -51,7 +59,29 @@ const LoginPage: React.FC = (): JSX.Element => {
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
navigate("/xxx");
|
||||
// 第一~第四階層の管理者はライセンス画面へ遷移
|
||||
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();
|
||||
}
|
||||
},
|
||||
[dispatch, i18n.language, instance, navigate]
|
||||
|
||||
@ -1,39 +0,0 @@
|
||||
import { useMsal } from "@azure/msal-react";
|
||||
import { AppDispatch } from "app/store";
|
||||
import { UpdateTokenTimer } from "components/auth/updateTokenTimer";
|
||||
import Footer from "components/footer";
|
||||
import Header from "components/header";
|
||||
import { clearToken } from "features/auth";
|
||||
import { clearUserInfo } from "features/login";
|
||||
|
||||
import React from "react";
|
||||
import { useDispatch } from "react-redux";
|
||||
import styles from "styles/app.module.scss";
|
||||
|
||||
const SamplePage: React.FC = (): JSX.Element => {
|
||||
const { instance } = useMsal();
|
||||
const dispatch: AppDispatch = useDispatch();
|
||||
|
||||
return (
|
||||
<div className={styles.wrap}>
|
||||
<Header />
|
||||
<UpdateTokenTimer />
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
className={styles.buttonText}
|
||||
onClick={() => {
|
||||
instance.logoutRedirect({ postLogoutRedirectUri: "/" });
|
||||
dispatch(clearToken());
|
||||
dispatch(clearUserInfo());
|
||||
}}
|
||||
>
|
||||
sign out
|
||||
</button>
|
||||
</div>
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SamplePage;
|
||||
@ -109,6 +109,7 @@ const TermsPage: React.FC = (): JSX.Element => {
|
||||
target="_blank"
|
||||
className={styles.linkTx}
|
||||
onClick={() => setIsClickedEulaLink(true)}
|
||||
data-tag="open-eula"
|
||||
>
|
||||
{t(getTranslationID("termsPage.label.linkOfEula"))}
|
||||
</a>
|
||||
@ -123,6 +124,7 @@ const TermsPage: React.FC = (): JSX.Element => {
|
||||
value=""
|
||||
onChange={(e) => setIsCheckedEula(e.target.checked)}
|
||||
disabled={!isClickedEulaLink}
|
||||
data-tag="accept-eula"
|
||||
/>
|
||||
{t(
|
||||
getTranslationID("termsPage.label.checkBoxForConsent")
|
||||
@ -140,6 +142,7 @@ const TermsPage: React.FC = (): JSX.Element => {
|
||||
target="_blank"
|
||||
className={styles.linkTx}
|
||||
onClick={() => setIsClickedDpaLink(true)}
|
||||
data-tag="open-dpa"
|
||||
>
|
||||
{t(getTranslationID("termsPage.label.linkOfDpa"))}
|
||||
</a>
|
||||
@ -154,6 +157,7 @@ const TermsPage: React.FC = (): JSX.Element => {
|
||||
value=""
|
||||
onChange={(e) => setIsCheckedDpa(e.target.checked)}
|
||||
disabled={!isClickedDpaLink}
|
||||
data-tag="accept-dpa"
|
||||
/>
|
||||
{t(
|
||||
getTranslationID("termsPage.label.checkBoxForConsent")
|
||||
@ -172,6 +176,7 @@ const TermsPage: React.FC = (): JSX.Element => {
|
||||
canClickButton() ? styles.isActive : ""
|
||||
}`}
|
||||
onClick={onAcceptTermsOfUse}
|
||||
data-tag="accept-terms"
|
||||
/>
|
||||
</p>
|
||||
</dd>
|
||||
|
||||
@ -86,6 +86,7 @@ const TopPage: React.FC = (): JSX.Element => {
|
||||
};
|
||||
instance.loginRedirect(loginRequest);
|
||||
}}
|
||||
data-tag="signin"
|
||||
>
|
||||
{t(getTranslationID("topPage.label.signInButton"))}
|
||||
<img
|
||||
|
||||
574
dictation_function/package-lock.json
generated
574
dictation_function/package-lock.json
generated
@ -8,6 +8,8 @@
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"@azure/functions": "^4.0.0",
|
||||
"@azure/identity": "^3.1.3",
|
||||
"@microsoft/microsoft-graph-client": "^3.0.5",
|
||||
"@sendgrid/mail": "^7.7.0",
|
||||
"dotenv": "^16.0.3",
|
||||
"mysql2": "^2.3.3",
|
||||
@ -38,6 +40,123 @@
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@azure/abort-controller": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-1.1.0.tgz",
|
||||
"integrity": "sha512-TrRLIoSQVzfAJX9H1JeFjzAoDGcoK1IYX1UImfceTZpsyYfWr09Ss1aHW1y5TrrR3iq6RZLBwJ3E24uwPhwahw==",
|
||||
"dependencies": {
|
||||
"tslib": "^2.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@azure/core-auth": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.5.0.tgz",
|
||||
"integrity": "sha512-udzoBuYG1VBoHVohDTrvKjyzel34zt77Bhp7dQntVGGD0ehVq48owENbBG8fIgkHRNUBQH5k1r0hpoMu5L8+kw==",
|
||||
"dependencies": {
|
||||
"@azure/abort-controller": "^1.0.0",
|
||||
"@azure/core-util": "^1.1.0",
|
||||
"tslib": "^2.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@azure/core-client": {
|
||||
"version": "1.7.3",
|
||||
"resolved": "https://registry.npmjs.org/@azure/core-client/-/core-client-1.7.3.tgz",
|
||||
"integrity": "sha512-kleJ1iUTxcO32Y06dH9Pfi9K4U+Tlb111WXEnbt7R/ne+NLRwppZiTGJuTD5VVoxTMK5NTbEtm5t2vcdNCFe2g==",
|
||||
"dependencies": {
|
||||
"@azure/abort-controller": "^1.0.0",
|
||||
"@azure/core-auth": "^1.4.0",
|
||||
"@azure/core-rest-pipeline": "^1.9.1",
|
||||
"@azure/core-tracing": "^1.0.0",
|
||||
"@azure/core-util": "^1.0.0",
|
||||
"@azure/logger": "^1.0.0",
|
||||
"tslib": "^2.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@azure/core-rest-pipeline": {
|
||||
"version": "1.12.2",
|
||||
"resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.12.2.tgz",
|
||||
"integrity": "sha512-wLLJQdL4v1yoqYtEtjKNjf8pJ/G/BqVomAWxcKOR1KbZJyCEnCv04yks7Y1NhJ3JzxbDs307W67uX0JzklFdCg==",
|
||||
"dependencies": {
|
||||
"@azure/abort-controller": "^1.0.0",
|
||||
"@azure/core-auth": "^1.4.0",
|
||||
"@azure/core-tracing": "^1.0.1",
|
||||
"@azure/core-util": "^1.3.0",
|
||||
"@azure/logger": "^1.0.0",
|
||||
"form-data": "^4.0.0",
|
||||
"http-proxy-agent": "^5.0.0",
|
||||
"https-proxy-agent": "^5.0.0",
|
||||
"tslib": "^2.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@azure/core-rest-pipeline/node_modules/@tootallnate/once": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz",
|
||||
"integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==",
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@azure/core-rest-pipeline/node_modules/form-data": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/@azure/core-rest-pipeline/node_modules/http-proxy-agent": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz",
|
||||
"integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==",
|
||||
"dependencies": {
|
||||
"@tootallnate/once": "2",
|
||||
"agent-base": "6",
|
||||
"debug": "4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/@azure/core-tracing": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.0.1.tgz",
|
||||
"integrity": "sha512-I5CGMoLtX+pI17ZdiFJZgxMJApsK6jjfm85hpgp3oazCdq5Wxgh4wMr7ge/TTWW1B5WBuvIOI1fMU/FrOAMKrw==",
|
||||
"dependencies": {
|
||||
"tslib": "^2.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@azure/core-util": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.6.1.tgz",
|
||||
"integrity": "sha512-h5taHeySlsV9qxuK64KZxy4iln1BtMYlNt5jbuEFN3UFSAd1EwKg/Gjl5a6tZ/W8t6li3xPnutOx7zbDyXnPmQ==",
|
||||
"dependencies": {
|
||||
"@azure/abort-controller": "^1.0.0",
|
||||
"tslib": "^2.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@azure/functions": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@azure/functions/-/functions-4.0.1.tgz",
|
||||
@ -50,6 +169,94 @@
|
||||
"node": ">=18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@azure/identity": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@azure/identity/-/identity-3.1.3.tgz",
|
||||
"integrity": "sha512-y0jFjSfHsVPwXSwi3KaSPtOZtJZqhiqAhWUXfFYBUd/+twUBovZRXspBwLrF5rJe0r5NyvmScpQjL+TYDTQVvw==",
|
||||
"deprecated": "Please upgrade to the latest version of this package to get necessary fixes",
|
||||
"dependencies": {
|
||||
"@azure/abort-controller": "^1.0.0",
|
||||
"@azure/core-auth": "^1.3.0",
|
||||
"@azure/core-client": "^1.4.0",
|
||||
"@azure/core-rest-pipeline": "^1.1.0",
|
||||
"@azure/core-tracing": "^1.0.0",
|
||||
"@azure/core-util": "^1.0.0",
|
||||
"@azure/logger": "^1.0.0",
|
||||
"@azure/msal-browser": "^2.32.2",
|
||||
"@azure/msal-common": "^9.0.2",
|
||||
"@azure/msal-node": "^1.14.6",
|
||||
"events": "^3.0.0",
|
||||
"jws": "^4.0.0",
|
||||
"open": "^8.0.0",
|
||||
"stoppable": "^1.1.0",
|
||||
"tslib": "^2.2.0",
|
||||
"uuid": "^8.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@azure/logger": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@azure/logger/-/logger-1.0.4.tgz",
|
||||
"integrity": "sha512-ustrPY8MryhloQj7OWGe+HrYx+aoiOxzbXTtgblbV3xwCqpzUK36phH3XNHQKj3EPonyFUuDTfR3qFhTEAuZEg==",
|
||||
"dependencies": {
|
||||
"tslib": "^2.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@azure/msal-browser": {
|
||||
"version": "2.38.3",
|
||||
"resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-2.38.3.tgz",
|
||||
"integrity": "sha512-2WuLFnWWPR1IdvhhysT18cBbkXx1z0YIchVss5AwVA95g7CU5CpT3d+5BcgVGNXDXbUU7/5p0xYHV99V5z8C/A==",
|
||||
"deprecated": "A newer major version of this library is available. Please upgrade to the latest available version.",
|
||||
"dependencies": {
|
||||
"@azure/msal-common": "13.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@azure/msal-browser/node_modules/@azure/msal-common": {
|
||||
"version": "13.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-13.3.1.tgz",
|
||||
"integrity": "sha512-Lrk1ozoAtaP/cp53May3v6HtcFSVxdFrg2Pa/1xu5oIvsIwhxW6zSPibKefCOVgd5osgykMi5jjcZHv8XkzZEQ==",
|
||||
"engines": {
|
||||
"node": ">=0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@azure/msal-common": {
|
||||
"version": "9.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-9.1.1.tgz",
|
||||
"integrity": "sha512-we9xR8lvu47fF0h+J8KyXoRy9+G/fPzm3QEa2TrdR3jaVS3LKAyE2qyMuUkNdbVkvzl8Zr9f7l+IUSP22HeqXw==",
|
||||
"engines": {
|
||||
"node": ">=0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@azure/msal-node": {
|
||||
"version": "1.18.4",
|
||||
"resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-1.18.4.tgz",
|
||||
"integrity": "sha512-Kc/dRvhZ9Q4+1FSfsTFDME/v6+R2Y1fuMty/TfwqE5p9GTPw08BPbKgeWinE8JRHRp+LemjQbUZsn4Q4l6Lszg==",
|
||||
"deprecated": "A newer major version of this library is available. Please upgrade to the latest available version.",
|
||||
"dependencies": {
|
||||
"@azure/msal-common": "13.3.1",
|
||||
"jsonwebtoken": "^9.0.0",
|
||||
"uuid": "^8.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "10 || 12 || 14 || 16 || 18"
|
||||
}
|
||||
},
|
||||
"node_modules/@azure/msal-node/node_modules/@azure/msal-common": {
|
||||
"version": "13.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-13.3.1.tgz",
|
||||
"integrity": "sha512-Lrk1ozoAtaP/cp53May3v6HtcFSVxdFrg2Pa/1xu5oIvsIwhxW6zSPibKefCOVgd5osgykMi5jjcZHv8XkzZEQ==",
|
||||
"engines": {
|
||||
"node": ">=0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame": {
|
||||
"version": "7.22.13",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz",
|
||||
@ -704,6 +911,13 @@
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/@ioredis/commands": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.2.0.tgz",
|
||||
"integrity": "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==",
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@isaacs/cliui": {
|
||||
"version": "8.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
|
||||
@ -1364,6 +1578,32 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@microsoft/microsoft-graph-client": {
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@microsoft/microsoft-graph-client/-/microsoft-graph-client-3.0.5.tgz",
|
||||
"integrity": "sha512-xQADFNLUhE78RzYadFZtOmy/5wBZenSZhVK193m40MTDC5hl1aYMQO1QOJApnKga8WcvMCDCU10taRhuXTOz5w==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"tslib": "^2.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@azure/identity": {
|
||||
"optional": true
|
||||
},
|
||||
"@azure/msal-browser": {
|
||||
"optional": true
|
||||
},
|
||||
"buffer": {
|
||||
"optional": true
|
||||
},
|
||||
"stream-browserify": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@npmcli/fs": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-1.1.1.tgz",
|
||||
@ -1780,7 +2020,6 @@
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
|
||||
"integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
|
||||
"devOptional": true,
|
||||
"dependencies": {
|
||||
"debug": "4"
|
||||
},
|
||||
@ -1907,8 +2146,7 @@
|
||||
"node_modules/asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||
"dev": true
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "0.26.1",
|
||||
@ -2499,6 +2737,11 @@
|
||||
"ieee754": "^1.2.1"
|
||||
}
|
||||
},
|
||||
"node_modules/buffer-equal-constant-time": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
|
||||
"integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="
|
||||
},
|
||||
"node_modules/buffer-from": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
||||
@ -2954,6 +3197,16 @@
|
||||
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/cluster-key-slot": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz",
|
||||
"integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/co": {
|
||||
"version": "4.6.0",
|
||||
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
|
||||
@ -2999,7 +3252,6 @@
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
},
|
||||
@ -3109,11 +3361,18 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/define-lazy-prop": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz",
|
||||
"integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
@ -3173,6 +3432,14 @@
|
||||
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/ecdsa-sig-formatter": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
|
||||
"integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
|
||||
"dependencies": {
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.4.576",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.576.tgz",
|
||||
@ -3263,6 +3530,14 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/events": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
|
||||
"integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==",
|
||||
"engines": {
|
||||
"node": ">=0.8.x"
|
||||
}
|
||||
},
|
||||
"node_modules/execa": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
|
||||
@ -3753,7 +4028,6 @@
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
|
||||
"integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
|
||||
"devOptional": true,
|
||||
"dependencies": {
|
||||
"agent-base": "6",
|
||||
"debug": "4"
|
||||
@ -3870,6 +4144,31 @@
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
||||
},
|
||||
"node_modules/ioredis": {
|
||||
"version": "5.3.2",
|
||||
"resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.3.2.tgz",
|
||||
"integrity": "sha512-1DKMMzlIHM02eBBVOFQ1+AolGjs6+xEcM4PDL7NqOS6szq7H9jSaEkIUH6/a5Hl241LzW6JLSiAbNvTQjUupUA==",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@ioredis/commands": "^1.1.1",
|
||||
"cluster-key-slot": "^1.1.0",
|
||||
"debug": "^4.3.4",
|
||||
"denque": "^2.1.0",
|
||||
"lodash.defaults": "^4.2.0",
|
||||
"lodash.isarguments": "^3.1.0",
|
||||
"redis-errors": "^1.2.0",
|
||||
"redis-parser": "^3.0.0",
|
||||
"standard-as-callback": "^2.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.22.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/ioredis"
|
||||
}
|
||||
},
|
||||
"node_modules/ip": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz",
|
||||
@ -3895,6 +4194,20 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/is-docker": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz",
|
||||
"integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==",
|
||||
"bin": {
|
||||
"is-docker": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/is-fullwidth-code-point": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
||||
@ -3945,6 +4258,17 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/is-wsl": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
|
||||
"integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==",
|
||||
"dependencies": {
|
||||
"is-docker": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/isexe": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||
@ -4747,6 +5071,90 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/jsonwebtoken": {
|
||||
"version": "9.0.2",
|
||||
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
|
||||
"integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==",
|
||||
"dependencies": {
|
||||
"jws": "^3.2.2",
|
||||
"lodash.includes": "^4.3.0",
|
||||
"lodash.isboolean": "^3.0.3",
|
||||
"lodash.isinteger": "^4.0.4",
|
||||
"lodash.isnumber": "^3.0.3",
|
||||
"lodash.isplainobject": "^4.0.6",
|
||||
"lodash.isstring": "^4.0.1",
|
||||
"lodash.once": "^4.0.0",
|
||||
"ms": "^2.1.1",
|
||||
"semver": "^7.5.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12",
|
||||
"npm": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/jsonwebtoken/node_modules/jwa": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
|
||||
"integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==",
|
||||
"dependencies": {
|
||||
"buffer-equal-constant-time": "1.0.1",
|
||||
"ecdsa-sig-formatter": "1.0.11",
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/jsonwebtoken/node_modules/jws": {
|
||||
"version": "3.2.2",
|
||||
"resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
|
||||
"integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
|
||||
"dependencies": {
|
||||
"jwa": "^1.4.1",
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/jsonwebtoken/node_modules/lru-cache": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
|
||||
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
|
||||
"dependencies": {
|
||||
"yallist": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/jsonwebtoken/node_modules/semver": {
|
||||
"version": "7.5.4",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
|
||||
"integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
|
||||
"dependencies": {
|
||||
"lru-cache": "^6.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/jwa": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz",
|
||||
"integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==",
|
||||
"dependencies": {
|
||||
"buffer-equal-constant-time": "1.0.1",
|
||||
"ecdsa-sig-formatter": "1.0.11",
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/jws": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz",
|
||||
"integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==",
|
||||
"dependencies": {
|
||||
"jwa": "^2.0.0",
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/kleur": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz",
|
||||
@ -4783,12 +5191,61 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/lodash.defaults": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
|
||||
"integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==",
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/lodash.includes": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
|
||||
"integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w=="
|
||||
},
|
||||
"node_modules/lodash.isarguments": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz",
|
||||
"integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==",
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/lodash.isboolean": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
|
||||
"integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg=="
|
||||
},
|
||||
"node_modules/lodash.isinteger": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
|
||||
"integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA=="
|
||||
},
|
||||
"node_modules/lodash.isnumber": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
|
||||
"integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw=="
|
||||
},
|
||||
"node_modules/lodash.isplainobject": {
|
||||
"version": "4.0.6",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
|
||||
"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA=="
|
||||
},
|
||||
"node_modules/lodash.isstring": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
|
||||
"integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw=="
|
||||
},
|
||||
"node_modules/lodash.memoize": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
|
||||
"integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/lodash.once": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
|
||||
"integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="
|
||||
},
|
||||
"node_modules/long": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz",
|
||||
@ -4958,7 +5415,6 @@
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
@ -4967,7 +5423,6 @@
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"mime-db": "1.52.0"
|
||||
},
|
||||
@ -5601,6 +6056,22 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/open": {
|
||||
"version": "8.4.2",
|
||||
"resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz",
|
||||
"integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==",
|
||||
"dependencies": {
|
||||
"define-lazy-prop": "^2.0.0",
|
||||
"is-docker": "^2.1.1",
|
||||
"is-wsl": "^2.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/p-limit": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
|
||||
@ -5896,6 +6367,66 @@
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/redis": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/redis/-/redis-3.1.2.tgz",
|
||||
"integrity": "sha512-grn5KoZLr/qrRQVwoSkmzdbw6pwF+/rwODtrOr6vuBRiR/f3rjSTGupbF90Zpqm2oenix8Do6RV7pYEkGwlKkw==",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"denque": "^1.5.0",
|
||||
"redis-commands": "^1.7.0",
|
||||
"redis-errors": "^1.2.0",
|
||||
"redis-parser": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/node-redis"
|
||||
}
|
||||
},
|
||||
"node_modules/redis-commands": {
|
||||
"version": "1.7.0",
|
||||
"resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.7.0.tgz",
|
||||
"integrity": "sha512-nJWqw3bTFy21hX/CPKHth6sfhZbdiHP6bTawSgQBlKOVRG7EZkfHbbHwQJnrE4vsQf0CMNE+3gJ4Fmm16vdVlQ==",
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/redis-errors": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz",
|
||||
"integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/redis-parser": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz",
|
||||
"integrity": "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"redis-errors": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/redis/node_modules/denque": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/denque/-/denque-1.5.1.tgz",
|
||||
"integrity": "sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw==",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/reflect-metadata": {
|
||||
"version": "0.1.13",
|
||||
"resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz",
|
||||
@ -6123,8 +6654,13 @@
|
||||
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
|
||||
"integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-validator-identifier": "^7.22.20",
|
||||
"chalk": "^2.4.2",
|
||||
"js-tokens": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/smart-buffer": {
|
||||
@ -6262,6 +6798,22 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/standard-as-callback": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz",
|
||||
"integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==",
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/stoppable": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/stoppable/-/stoppable-1.1.0.tgz",
|
||||
"integrity": "sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==",
|
||||
"engines": {
|
||||
"node": ">=4",
|
||||
"npm": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/string_decoder": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
|
||||
@ -6388,8 +6940,8 @@
|
||||
"dependencies": {
|
||||
"ansi-regex": "^5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
"peerDependencies": {
|
||||
"@babel/core": "^7.0.0-0"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-ansi-cjs/node_modules/ansi-regex": {
|
||||
|
||||
@ -13,6 +13,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@azure/functions": "^4.0.0",
|
||||
"@azure/identity": "^3.1.3",
|
||||
"@microsoft/microsoft-graph-client": "^3.0.5",
|
||||
"@sendgrid/mail": "^7.7.0",
|
||||
"dotenv": "^16.0.3",
|
||||
"mysql2": "^2.3.3",
|
||||
|
||||
74
dictation_function/src/adb2c/adb2c.service.ts
Normal file
74
dictation_function/src/adb2c/adb2c.service.ts
Normal file
@ -0,0 +1,74 @@
|
||||
import { ClientSecretCredential } from "@azure/identity";
|
||||
import { Client } from "@microsoft/microsoft-graph-client";
|
||||
import { TokenCredentialAuthenticationProvider } from "@microsoft/microsoft-graph-client/authProviders/azureTokenCredentials";
|
||||
import { AdB2cResponse, AdB2cUser } from "./types/types";
|
||||
import { error } from "console";
|
||||
|
||||
export class Adb2cTooManyRequestsError extends Error {}
|
||||
|
||||
export class AdB2cService {
|
||||
private graphClient: Client;
|
||||
|
||||
constructor() {
|
||||
// ADB2Cへの認証情報
|
||||
if (
|
||||
!process.env.ADB2C_TENANT_ID ||
|
||||
!process.env.ADB2C_CLIENT_ID ||
|
||||
!process.env.ADB2C_CLIENT_SECRET
|
||||
) {
|
||||
throw error;
|
||||
}
|
||||
const credential = new ClientSecretCredential(
|
||||
process.env.ADB2C_TENANT_ID,
|
||||
process.env.ADB2C_CLIENT_ID,
|
||||
process.env.ADB2C_CLIENT_SECRET
|
||||
);
|
||||
const authProvider = new TokenCredentialAuthenticationProvider(credential, {
|
||||
scopes: ["https://graph.microsoft.com/.default"],
|
||||
});
|
||||
|
||||
this.graphClient = Client.initWithMiddleware({ authProvider });
|
||||
}
|
||||
|
||||
/**
|
||||
* Azure AD B2Cからユーザ情報を取得する
|
||||
* @param externalIds 外部ユーザーID
|
||||
* @returns ユーザ情報
|
||||
*/
|
||||
async getUsers(externalIds: string[]): Promise<AdB2cUser[]> {
|
||||
const chunkExternalIds = splitArrayInChunksOfFifteen(externalIds);
|
||||
|
||||
try {
|
||||
const b2cUsers: AdB2cUser[] = [];
|
||||
for (let index = 0; index < chunkExternalIds.length; index++) {
|
||||
const element = chunkExternalIds[index];
|
||||
const res: AdB2cResponse = await this.graphClient
|
||||
.api(`users/`)
|
||||
.select(["id", "displayName", "identities"])
|
||||
.filter(`id in (${element.map((y) => `'${y}'`).join(",")})`)
|
||||
.get();
|
||||
|
||||
b2cUsers.push(...res.value);
|
||||
}
|
||||
|
||||
return b2cUsers;
|
||||
} catch (e) {
|
||||
const { statusCode } = e;
|
||||
if (statusCode === 429) {
|
||||
throw new Adb2cTooManyRequestsError();
|
||||
}
|
||||
|
||||
throw e;
|
||||
} finally {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const splitArrayInChunksOfFifteen = (arr: string[]): string[][] => {
|
||||
const result: string[][] = [];
|
||||
const chunkSize = 15; // SDKの制限数
|
||||
for (let i = 0; i < arr.length; i += chunkSize) {
|
||||
result.push(arr.slice(i, i + chunkSize));
|
||||
}
|
||||
return result;
|
||||
};
|
||||
15
dictation_function/src/adb2c/types/types.ts
Normal file
15
dictation_function/src/adb2c/types/types.ts
Normal file
@ -0,0 +1,15 @@
|
||||
export type AdB2cResponse = {
|
||||
'@odata.context': string;
|
||||
value: AdB2cUser[];
|
||||
};
|
||||
export type AdB2cUser = {
|
||||
id: string;
|
||||
displayName: string;
|
||||
identities?: UserIdentity[];
|
||||
};
|
||||
|
||||
export type UserIdentity = {
|
||||
signInType: string;
|
||||
issuer: string;
|
||||
issuerAssignedId: string;
|
||||
};
|
||||
1
dictation_function/src/common/cache/constants.ts
vendored
Normal file
1
dictation_function/src/common/cache/constants.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
export const ADB2C_PREFIX = "adb2c-external-id:"
|
||||
19
dictation_function/src/common/cache/index.ts
vendored
Normal file
19
dictation_function/src/common/cache/index.ts
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
import { ADB2C_PREFIX } from './constants';
|
||||
|
||||
/**
|
||||
* ADB2Cのユーザー格納用のキーを生成する
|
||||
* @param externalId 外部ユーザーID
|
||||
* @returns キャッシュのキー
|
||||
*/
|
||||
export const makeADB2CKey = (externalId: string): string => {
|
||||
return `${ADB2C_PREFIX}${externalId}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* ADB2Cのユーザー格納用のキーから外部ユーザーIDを取得する
|
||||
* @param key キャッシュのキー
|
||||
* @returns 外部ユーザーID
|
||||
*/
|
||||
export const restoreAdB2cID = (key: string): string => {
|
||||
return key.replace(ADB2C_PREFIX, '');
|
||||
}
|
||||
7
dictation_function/src/common/getEnv/getEnv.ts
Normal file
7
dictation_function/src/common/getEnv/getEnv.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export const getMailFrom = (): string => {
|
||||
const from = process.env.MAIL_FROM;
|
||||
if (typeof from === "string") {
|
||||
return from;
|
||||
}
|
||||
throw new Error("MAIL_FROM not found");
|
||||
};
|
||||
@ -1,71 +0,0 @@
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { DataSource } from "typeorm";
|
||||
import { User } from "../../entity/user.entity";
|
||||
import { Account } from "../../entity/account.entity";
|
||||
import { ADMIN_ROLES, USER_ROLES } from "../../constants";
|
||||
|
||||
type InitialTestDBState = {
|
||||
tier1Accounts: { account: Account; users: User[] }[];
|
||||
tier2Accounts: { account: Account; users: User[] }[];
|
||||
tier3Accounts: { account: Account; users: User[] }[];
|
||||
tier4Accounts: { account: Account; users: User[] }[];
|
||||
tier5Accounts: { account: Account; users: User[] }[];
|
||||
};
|
||||
|
||||
// 上書きされたら困る項目を除外したAccount型
|
||||
type OverrideAccount = Omit<
|
||||
Account,
|
||||
"id" | "primary_admin_user_id" | "secondary_admin_user_id" | "user"
|
||||
>;
|
||||
|
||||
// 上書きされたら困る項目を除外したUser型
|
||||
type OverrideUser = Omit<
|
||||
User,
|
||||
"id" | "account" | "license" | "userGroupMembers"
|
||||
>;
|
||||
|
||||
type AccountDefault = { [K in keyof OverrideAccount]?: OverrideAccount[K] };
|
||||
type UserDefault = { [K in keyof OverrideUser]?: OverrideUser[K] };
|
||||
|
||||
/**
|
||||
* テスト ユーティリティ: 指定したプロパティを上書きしたユーザーを作成する
|
||||
* @param dataSource データソース
|
||||
* @param defaultUserValue User型と同等かつoptionalなプロパティを持つ上書き箇所指定用オブジェクト
|
||||
* @returns 作成したユーザー
|
||||
*/
|
||||
export const makeTestUser = async (
|
||||
datasource: DataSource,
|
||||
defaultUserValue?: UserDefault
|
||||
): Promise<User> => {
|
||||
const d = defaultUserValue;
|
||||
const { identifiers } = await datasource.getRepository(User).insert({
|
||||
account_id: d?.account_id ?? -1,
|
||||
external_id: d?.external_id ?? uuidv4(),
|
||||
role: d?.role ?? `${ADMIN_ROLES.STANDARD} ${USER_ROLES.NONE}`,
|
||||
author_id: d?.author_id,
|
||||
accepted_eula_version: d?.accepted_eula_version ?? "1.0",
|
||||
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,
|
||||
prompt: d?.prompt ?? true,
|
||||
created_by: d?.created_by ?? "test_runner",
|
||||
created_at: d?.created_at ?? new Date(),
|
||||
updated_by: d?.updated_by ?? "updater",
|
||||
updated_at: d?.updated_at ?? new Date(),
|
||||
});
|
||||
const result = identifiers.pop() as User;
|
||||
|
||||
const user = await datasource.getRepository(User).findOne({
|
||||
where: {
|
||||
id: result.id,
|
||||
},
|
||||
});
|
||||
if (!user) {
|
||||
throw new Error("Unexpected null");
|
||||
}
|
||||
return user;
|
||||
};
|
||||
70
dictation_function/src/common/types/types.ts
Normal file
70
dictation_function/src/common/types/types.ts
Normal file
@ -0,0 +1,70 @@
|
||||
import {
|
||||
LICENSE_EXPIRATION_DAYS,
|
||||
LICENSE_EXPIRATION_THRESHOLD_DAYS,
|
||||
TRIAL_LICENSE_EXPIRATION_DAYS,
|
||||
} from "../../constants";
|
||||
|
||||
// ライセンス算出用に、その日の始まりの時刻(0:00:00.000)の日付を取得する
|
||||
export class DateWithZeroTime extends Date {
|
||||
constructor(...args: any[]) {
|
||||
if (args.length === 0) {
|
||||
super(); // 引数がない場合、現在の日付で初期化
|
||||
} else {
|
||||
super(...(args as [string])); // 引数がある場合、引数をそのままDateクラスのコンストラクタに渡す
|
||||
}
|
||||
this.setHours(0, 0, 0, 0); // 時分秒を"0:00:00.000"に固定
|
||||
}
|
||||
}
|
||||
|
||||
// ライセンス算出用に、その日の終わりの時刻(23:59:59.999)の日付を取得する
|
||||
export class DateWithDayEndTime extends Date {
|
||||
constructor(...args: any[]) {
|
||||
if (args.length === 0) {
|
||||
super(); // 引数がない場合、現在の日付で初期化
|
||||
} else {
|
||||
super(...(args as [string])); // 引数がある場合、引数をそのままDateクラスのコンストラクタに渡す
|
||||
}
|
||||
this.setHours(23, 59, 59, 999); // 時分秒を"23:59:59.999"に固定
|
||||
}
|
||||
}
|
||||
|
||||
// ライセンスの算出用に、閾値となる時刻(23:59:59.999)の日付を取得する
|
||||
export class ExpirationThresholdDate extends Date {
|
||||
constructor(...args: any[]) {
|
||||
if (args.length === 0) {
|
||||
super(); // 引数がない場合、現在の日付で初期化
|
||||
} else {
|
||||
super(...(args as [string])); // 引数がある場合、引数をそのままDateクラスのコンストラクタに渡す
|
||||
}
|
||||
this.setDate(this.getDate() + LICENSE_EXPIRATION_THRESHOLD_DAYS);
|
||||
this.setHours(23, 59, 59, 999); // 時分秒を"23:59:59.999"に固定
|
||||
}
|
||||
}
|
||||
|
||||
// 新規トライアルライセンス発行時の有効期限算出用に、30日後の日付を取得する
|
||||
export class NewTrialLicenseExpirationDate extends Date {
|
||||
constructor(...args: any[]) {
|
||||
if (args.length === 0) {
|
||||
super(); // 引数がない場合、現在の日付で初期化
|
||||
} else {
|
||||
super(...(args as [string])); // 引数がある場合、引数をそのままDateクラスのコンストラクタに渡す
|
||||
}
|
||||
this.setDate(this.getDate() + TRIAL_LICENSE_EXPIRATION_DAYS);
|
||||
this.setHours(23, 59, 59); // 時分秒を"23:59:59"に固定
|
||||
this.setMilliseconds(0);
|
||||
}
|
||||
}
|
||||
|
||||
// 新規ライセンス割り当て時の有効期限算出用に、365日後の日付を取得する
|
||||
export class NewAllocatedLicenseExpirationDate extends Date {
|
||||
constructor(...args: any[]) {
|
||||
if (args.length === 0) {
|
||||
super(); // 引数がない場合、現在の日付で初期化
|
||||
} else {
|
||||
super(...(args as [string])); // 引数がある場合、引数をそのままDateクラスのコンストラクタに渡す
|
||||
}
|
||||
this.setDate(this.getDate() + LICENSE_EXPIRATION_DAYS);
|
||||
this.setHours(23, 59, 59); // 時分秒を"23:59:59"に固定
|
||||
this.setMilliseconds(0);
|
||||
}
|
||||
}
|
||||
@ -6,7 +6,8 @@ import {
|
||||
PrimaryGeneratedColumn,
|
||||
CreateDateColumn,
|
||||
UpdateDateColumn,
|
||||
OneToMany,
|
||||
OneToOne,
|
||||
JoinColumn,
|
||||
} from "typeorm";
|
||||
|
||||
@Entity({ name: "accounts" })
|
||||
@ -65,6 +66,11 @@ export class Account {
|
||||
}) // defaultはSQLite用設定値.本番用は別途migrationで設定
|
||||
updated_at: Date;
|
||||
|
||||
@OneToMany(() => User, (user) => user.id)
|
||||
user: User[] | null;
|
||||
@OneToOne(() => User, (user) => user.id)
|
||||
@JoinColumn({ name: "primary_admin_user_id" })
|
||||
primaryAdminUser: User | null;
|
||||
|
||||
@OneToOne(() => User, (user) => user.id)
|
||||
@JoinColumn({ name: "secondary_admin_user_id" })
|
||||
secondaryAdminUser: User | null;
|
||||
}
|
||||
|
||||
63
dictation_function/src/entity/license.entity.ts
Normal file
63
dictation_function/src/entity/license.entity.ts
Normal file
@ -0,0 +1,63 @@
|
||||
import {
|
||||
Entity,
|
||||
Column,
|
||||
PrimaryGeneratedColumn,
|
||||
CreateDateColumn,
|
||||
UpdateDateColumn,
|
||||
JoinColumn,
|
||||
OneToOne,
|
||||
} from "typeorm";
|
||||
import { bigintTransformer } from "../common/entity";
|
||||
import { User } from "./user.entity";
|
||||
|
||||
@Entity({ name: "licenses" })
|
||||
export class License {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@Column({ nullable: true, type: "datetime" })
|
||||
expiry_date: Date | null;
|
||||
|
||||
@Column()
|
||||
account_id: number;
|
||||
|
||||
@Column()
|
||||
type: string;
|
||||
|
||||
@Column()
|
||||
status: string;
|
||||
|
||||
@Column({ nullable: true, type: "bigint", transformer: bigintTransformer })
|
||||
allocated_user_id: number | null;
|
||||
|
||||
@Column({ nullable: true, type: "bigint", transformer: bigintTransformer })
|
||||
order_id: number | null;
|
||||
|
||||
@Column({ nullable: true, type: "datetime" })
|
||||
deleted_at: Date | null;
|
||||
|
||||
@Column({ nullable: true, type: "bigint", transformer: bigintTransformer })
|
||||
delete_order_id: number | null;
|
||||
|
||||
@Column({ nullable: true, type: "datetime" })
|
||||
created_by: string | null;
|
||||
|
||||
@CreateDateColumn({
|
||||
default: () => "datetime('now', 'localtime')",
|
||||
type: "datetime",
|
||||
})
|
||||
created_at: Date;
|
||||
|
||||
@Column({ nullable: true, type: "datetime" })
|
||||
updated_by: string | null;
|
||||
|
||||
@UpdateDateColumn({
|
||||
default: () => "datetime('now', 'localtime')",
|
||||
type: "datetime",
|
||||
})
|
||||
updated_at: Date;
|
||||
|
||||
@OneToOne(() => User, (user) => user.license)
|
||||
@JoinColumn({ name: "allocated_user_id" })
|
||||
user: User | null;
|
||||
}
|
||||
@ -4,7 +4,9 @@ import {
|
||||
PrimaryGeneratedColumn,
|
||||
CreateDateColumn,
|
||||
UpdateDateColumn,
|
||||
OneToOne,
|
||||
} from "typeorm";
|
||||
import { License } from "./license.entity";
|
||||
|
||||
@Entity({ name: "users" })
|
||||
export class User {
|
||||
@ -70,4 +72,7 @@ export class User {
|
||||
type: "datetime",
|
||||
}) // defaultはSQLite用設定値.本番用は別途migrationで設定
|
||||
updated_at: Date;
|
||||
|
||||
@OneToOne(() => License, (license) => license.user)
|
||||
license: License | null;
|
||||
}
|
||||
|
||||
347
dictation_function/src/functions/licenseAlert.ts
Normal file
347
dictation_function/src/functions/licenseAlert.ts
Normal file
@ -0,0 +1,347 @@
|
||||
import { app, InvocationContext, Timer } from "@azure/functions";
|
||||
import { Between, DataSource, In, IsNull, MoreThan, Not } from "typeorm";
|
||||
import { User } from "../entity/user.entity";
|
||||
import { Account } from "../entity/account.entity";
|
||||
import {
|
||||
ADB2C_SIGN_IN_TYPE,
|
||||
LICENSE_ALLOCATED_STATUS,
|
||||
TIERS,
|
||||
} from "../constants";
|
||||
import * as dotenv from "dotenv";
|
||||
import { License } from "../entity/license.entity";
|
||||
import {
|
||||
DateWithDayEndTime,
|
||||
DateWithZeroTime,
|
||||
ExpirationThresholdDate,
|
||||
} from "../common/types/types";
|
||||
import { getMailFrom } from "../common/getEnv/getEnv";
|
||||
import { AdB2cService } from "../adb2c/adb2c.service";
|
||||
import { SendGridService } from "../sendgrid/sendgrid.service";
|
||||
|
||||
export async function licenseAlertProcessing(
|
||||
context: InvocationContext,
|
||||
datasource: DataSource,
|
||||
sendgrid: SendGridService,
|
||||
adb2c: AdB2cService
|
||||
) {
|
||||
context.log("[IN]licenseAlertProcessing");
|
||||
const mailFrom = getMailFrom();
|
||||
const accountRepository = datasource.getRepository(Account);
|
||||
|
||||
// 第五のアカウントを取得
|
||||
const accounts = await accountRepository.find({
|
||||
where: {
|
||||
tier: TIERS.TIER5,
|
||||
},
|
||||
relations: {
|
||||
primaryAdminUser: true,
|
||||
secondaryAdminUser: true,
|
||||
},
|
||||
});
|
||||
|
||||
const licenseRepository = datasource.getRepository(License);
|
||||
const currentDate = new DateWithZeroTime();
|
||||
const expiringSoonDate = new ExpirationThresholdDate(currentDate.getTime());
|
||||
const currentDateWithZeroTime = new DateWithZeroTime();
|
||||
const currentDateWithDayEndTime = new DateWithDayEndTime();
|
||||
const sendTargetAccounts = [] as accountInfo[];
|
||||
|
||||
const counts = async () => {
|
||||
for (const account of accounts) {
|
||||
// 有効期限がしきい値より未来または未設定で、割り当て可能なライセンス数の取得を行う
|
||||
const allocatableLicenseWithMargin = await licenseRepository.count({
|
||||
where: [
|
||||
{
|
||||
account_id: account.id,
|
||||
status: In([
|
||||
LICENSE_ALLOCATED_STATUS.UNALLOCATED,
|
||||
LICENSE_ALLOCATED_STATUS.REUSABLE,
|
||||
]),
|
||||
expiry_date: MoreThan(expiringSoonDate),
|
||||
},
|
||||
{
|
||||
account_id: account.id,
|
||||
status: In([
|
||||
LICENSE_ALLOCATED_STATUS.UNALLOCATED,
|
||||
LICENSE_ALLOCATED_STATUS.REUSABLE,
|
||||
]),
|
||||
expiry_date: IsNull(),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
// 有効期限が現在日付からしきい値以内のライセンス数を取得する
|
||||
const expiringSoonLicense = await licenseRepository.count({
|
||||
where: {
|
||||
account_id: account.id,
|
||||
expiry_date: Between(currentDate, expiringSoonDate),
|
||||
status: Not(LICENSE_ALLOCATED_STATUS.DELETED),
|
||||
},
|
||||
});
|
||||
// shortage算出
|
||||
let shortage = allocatableLicenseWithMargin - expiringSoonLicense;
|
||||
shortage = shortage >= 0 ? 0 : Math.abs(shortage);
|
||||
|
||||
// AutoRenewが未チェックかつ、有効期限当日のライセンスが割り当てられているユーザー数を取得
|
||||
const userCount = await licenseRepository.count({
|
||||
where: [
|
||||
{
|
||||
account_id: account.id,
|
||||
expiry_date: Between(
|
||||
currentDateWithZeroTime,
|
||||
currentDateWithDayEndTime
|
||||
),
|
||||
status: LICENSE_ALLOCATED_STATUS.ALLOCATED,
|
||||
user: {
|
||||
auto_renew: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
relations: {
|
||||
user: true,
|
||||
},
|
||||
});
|
||||
|
||||
// 上で取得したshortageとユーザー数のどちらかが1以上ならプライマリ、セカンダリ管理者、親企業名を保持する
|
||||
// (shortageとユーザー数のどちらかが1以上 = アラートメールを送る必要がある)
|
||||
let primaryAdminExternalId: string | undefined;
|
||||
let secondaryAdminExternalId: string | undefined;
|
||||
let parentCompanyName: string | undefined;
|
||||
if (shortage !== 0 || userCount !== 0) {
|
||||
primaryAdminExternalId = account.primaryAdminUser
|
||||
? account.primaryAdminUser.external_id
|
||||
: undefined;
|
||||
secondaryAdminExternalId = account.secondaryAdminUser
|
||||
? account.secondaryAdminUser.external_id
|
||||
: undefined;
|
||||
// 第五のアカウントを取得
|
||||
// strictNullChecks対応
|
||||
if (account.parent_account_id) {
|
||||
const parent = await accountRepository.findOne({
|
||||
where: {
|
||||
id: account.parent_account_id,
|
||||
},
|
||||
});
|
||||
parentCompanyName = parent?.company_name;
|
||||
}
|
||||
} else {
|
||||
primaryAdminExternalId = undefined;
|
||||
secondaryAdminExternalId = undefined;
|
||||
parentCompanyName = undefined;
|
||||
}
|
||||
sendTargetAccounts.push({
|
||||
accountId: account.id,
|
||||
companyName: account.company_name,
|
||||
parentCompanyName: parentCompanyName,
|
||||
shortage: shortage,
|
||||
userCountOfLicenseExpiringSoon: userCount,
|
||||
primaryAdminExternalId: primaryAdminExternalId,
|
||||
secondaryAdminExternalId: secondaryAdminExternalId,
|
||||
primaryAdminEmail: undefined,
|
||||
secondaryAdminEmail: undefined,
|
||||
});
|
||||
}
|
||||
};
|
||||
await counts();
|
||||
|
||||
// ADB2Cからユーザーを取得する用の外部ID配列を作成
|
||||
const externalIds = [] as string[];
|
||||
sendTargetAccounts.map((x) => {
|
||||
if (x.primaryAdminExternalId) {
|
||||
externalIds.push(x.primaryAdminExternalId);
|
||||
}
|
||||
if (x.secondaryAdminExternalId) {
|
||||
externalIds.push(x.secondaryAdminExternalId);
|
||||
}
|
||||
});
|
||||
const adb2cUsers = await adb2c.getUsers(externalIds);
|
||||
|
||||
// ADB2Cから取得したメールアドレスをRDBから取得した情報にマージ
|
||||
sendTargetAccounts.map((info) => {
|
||||
const primaryAdminUser = adb2cUsers.find(
|
||||
(adb2c) => info.primaryAdminExternalId === adb2c.id
|
||||
);
|
||||
if (primaryAdminUser) {
|
||||
const primaryAdminMail = primaryAdminUser.identities?.find(
|
||||
(identity) => identity.signInType === ADB2C_SIGN_IN_TYPE.EMAILADDRESS
|
||||
)?.issuerAssignedId;
|
||||
if (primaryAdminMail) {
|
||||
info.primaryAdminEmail = primaryAdminMail;
|
||||
}
|
||||
|
||||
const secondaryAdminUser = adb2cUsers.find(
|
||||
(adb2c) => info.secondaryAdminExternalId === adb2c.id
|
||||
);
|
||||
if (secondaryAdminUser) {
|
||||
const secondaryAdminMail = secondaryAdminUser.identities?.find(
|
||||
(identity) => identity.signInType === ADB2C_SIGN_IN_TYPE.EMAILADDRESS
|
||||
)?.issuerAssignedId;
|
||||
if (secondaryAdminMail) {
|
||||
info.secondaryAdminEmail = secondaryAdminMail;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const sendMail = async () => {
|
||||
for (const targetAccount of sendTargetAccounts) {
|
||||
// プライマリ管理者が入っているかチェック
|
||||
// 入っていない場合は、アラートメールを送信する必要が無いため、何も処理をせず次のループへ
|
||||
if (targetAccount.primaryAdminExternalId) {
|
||||
// メール送信
|
||||
// strictNullChecks対応
|
||||
if (targetAccount.primaryAdminEmail) {
|
||||
// ライセンス不足メール
|
||||
if (targetAccount.shortage !== 0) {
|
||||
const { subject, text, html } =
|
||||
await sendgrid.createMailContentOfLicenseShortage();
|
||||
// メールを送信
|
||||
try {
|
||||
await sendgrid.sendMail(
|
||||
targetAccount.primaryAdminEmail,
|
||||
mailFrom,
|
||||
subject,
|
||||
text,
|
||||
html
|
||||
);
|
||||
context.log(
|
||||
`Shortage mail send success. mail to :${targetAccount.primaryAdminEmail}`
|
||||
);
|
||||
} catch {
|
||||
context.log(
|
||||
`Shortage mail send failed. mail to :${targetAccount.primaryAdminEmail}`
|
||||
);
|
||||
}
|
||||
|
||||
// セカンダリ管理者が存在する場合、セカンダリ管理者にも送信
|
||||
if (targetAccount.secondaryAdminEmail) {
|
||||
// ライセンス不足メール
|
||||
if (targetAccount.shortage !== 0) {
|
||||
const { subject, text, html } =
|
||||
await sendgrid.createMailContentOfLicenseShortage();
|
||||
// メールを送信
|
||||
try {
|
||||
await sendgrid.sendMail(
|
||||
targetAccount.secondaryAdminEmail,
|
||||
mailFrom,
|
||||
subject,
|
||||
text,
|
||||
html
|
||||
);
|
||||
context.log(
|
||||
`Shortage mail send success. mail to :${targetAccount.secondaryAdminEmail}`
|
||||
);
|
||||
} catch {
|
||||
context.log(
|
||||
`Shortage mail send failed. mail to :${targetAccount.secondaryAdminEmail}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ライセンス失効警告メール
|
||||
if (targetAccount.userCountOfLicenseExpiringSoon !== 0) {
|
||||
const { subject, text, html } =
|
||||
await sendgrid.createMailContentOfLicenseExpiringSoon();
|
||||
// メールを送信
|
||||
try {
|
||||
await sendgrid.sendMail(
|
||||
targetAccount.primaryAdminEmail,
|
||||
mailFrom,
|
||||
subject,
|
||||
text,
|
||||
html
|
||||
);
|
||||
context.log(
|
||||
`Expiring soon mail send success. mail to :${targetAccount.primaryAdminEmail}`
|
||||
);
|
||||
} catch {
|
||||
context.log(
|
||||
`Expiring soon mail send failed. mail to :${targetAccount.primaryAdminEmail}`
|
||||
);
|
||||
}
|
||||
|
||||
// セカンダリ管理者が存在する場合、セカンダリ管理者にも送信
|
||||
if (targetAccount.secondaryAdminEmail) {
|
||||
// ライセンス不足メール
|
||||
if (targetAccount.shortage !== 0) {
|
||||
const { subject, text, html } =
|
||||
await sendgrid.createMailContentOfLicenseExpiringSoon();
|
||||
// メールを送信
|
||||
try {
|
||||
await sendgrid.sendMail(
|
||||
targetAccount.secondaryAdminEmail,
|
||||
mailFrom,
|
||||
subject,
|
||||
text,
|
||||
html
|
||||
);
|
||||
context.log(
|
||||
`Expiring soon mail send success. mail to :${targetAccount.secondaryAdminEmail}`
|
||||
);
|
||||
} catch {
|
||||
context.log(
|
||||
`Expiring soon mail send failed. mail to :${targetAccount.secondaryAdminEmail}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
await sendMail();
|
||||
|
||||
context.log("[OUT]licenseAlertProcessing");
|
||||
}
|
||||
|
||||
export async function licenseAlert(
|
||||
myTimer: Timer,
|
||||
context: InvocationContext
|
||||
): Promise<void> {
|
||||
context.log("[IN]licenseAlert");
|
||||
|
||||
dotenv.config({ path: ".env" });
|
||||
dotenv.config({ path: ".env.local", override: true });
|
||||
const datasource = new DataSource({
|
||||
type: "mysql",
|
||||
host: process.env.DB_HOST,
|
||||
port: Number(process.env.DB_PORT),
|
||||
username: process.env.DB_USERNAME,
|
||||
password: process.env.DB_PASSWORD,
|
||||
database: process.env.DB_NAME,
|
||||
entities: [User, Account, License],
|
||||
});
|
||||
await datasource.initialize();
|
||||
|
||||
const adb2c = new AdB2cService();
|
||||
const sendgrid = new SendGridService();
|
||||
try {
|
||||
await licenseAlertProcessing(context, datasource, sendgrid, adb2c);
|
||||
} catch (e) {
|
||||
context.log("licenseAlertProcessing failed");
|
||||
context.error(e);
|
||||
} finally {
|
||||
await datasource.destroy();
|
||||
context.log("[OUT]licenseAlert");
|
||||
}
|
||||
}
|
||||
|
||||
app.timer("licenseAlert", {
|
||||
schedule: "0 */1 * * * *",
|
||||
handler: licenseAlert,
|
||||
});
|
||||
|
||||
class accountInfo {
|
||||
accountId: number;
|
||||
companyName: string;
|
||||
parentCompanyName: string | undefined;
|
||||
shortage: number;
|
||||
userCountOfLicenseExpiringSoon: number;
|
||||
primaryAdminExternalId: string | undefined;
|
||||
secondaryAdminExternalId: string | undefined;
|
||||
primaryAdminEmail: string | undefined;
|
||||
secondaryAdminEmail: string | undefined;
|
||||
}
|
||||
@ -1,70 +0,0 @@
|
||||
import { app, InvocationContext, Timer } from "@azure/functions";
|
||||
import { DataSource } from "typeorm";
|
||||
import { User } from "../entity/user.entity";
|
||||
import { SendGridService } from "../sendgrid/sendgrid.service";
|
||||
import * as dotenv from "dotenv";
|
||||
|
||||
// タイマートリガー処理のサンプルです
|
||||
// TODO:開発が進んだら削除すること
|
||||
export async function timerTriggerExample(
|
||||
myTimer: Timer,
|
||||
context: InvocationContext
|
||||
): Promise<void> {
|
||||
context.log("Timer function processed request.");
|
||||
|
||||
dotenv.config({ path: ".env" });
|
||||
const datasource = new DataSource({
|
||||
type: "mysql",
|
||||
host: process.env.DB_HOST,
|
||||
port: Number(process.env.DB_PORT),
|
||||
username: process.env.DB_USERNAME,
|
||||
password: process.env.DB_PASSWORD,
|
||||
database: process.env.DB_NAME,
|
||||
entities: [User],
|
||||
});
|
||||
|
||||
try {
|
||||
await datasource.initialize();
|
||||
const userRepository = datasource.getRepository(User); // Userエンティティに対応するリポジトリを取得
|
||||
|
||||
// ユーザーを検索
|
||||
const users = await userRepository.find();
|
||||
console.log(users);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
} finally {
|
||||
await datasource.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
// test実行確認用サンプル
|
||||
// TODO:開発が進んだら削除すること
|
||||
export async function testExample(datasource: DataSource): Promise<User[]> {
|
||||
let users: User[];
|
||||
const userRepository = datasource.getRepository(User); // Userエンティティに対応するリポジトリを取得
|
||||
|
||||
// ユーザーを検索
|
||||
users = await userRepository.find();
|
||||
return users;
|
||||
}
|
||||
|
||||
// test実行確認用サンプル
|
||||
// TODO:開発が進んだら削除すること
|
||||
export async function testSendgridExample(): Promise<string> {
|
||||
const sendgrid = new SendGridService();
|
||||
|
||||
// メールを送信
|
||||
await sendgrid.sendMail(
|
||||
"oura.a89@gmail.com",
|
||||
process.env.MAIL_FROM,
|
||||
"testMail",
|
||||
"test!",
|
||||
"html"
|
||||
);
|
||||
return "sucsess";
|
||||
}
|
||||
|
||||
app.timer("timerTriggerExample", {
|
||||
schedule: "0 */1 * * * *",
|
||||
handler: timerTriggerExample,
|
||||
});
|
||||
@ -1,25 +1,48 @@
|
||||
import sendgrid from "@sendgrid/mail";
|
||||
import { error } from "console";
|
||||
|
||||
export class SendGridService {
|
||||
constructor() {
|
||||
if (!process.env.SENDGRID_API_KEY) {
|
||||
throw error;
|
||||
}
|
||||
sendgrid.setApiKey(process.env.SENDGRID_API_KEY);
|
||||
}
|
||||
/**
|
||||
* メールコンテンツを作成する
|
||||
* メールコンテンツを作成する(ライセンス不足)
|
||||
* @param accountId 認証対象のユーザーが所属するアカウントのID
|
||||
* @param userId 認証対象のユーザーのID
|
||||
* @param email 認証対象のユーザーのメールアドレス
|
||||
* @returns メールのサブジェクトとコンテンツ
|
||||
*/
|
||||
async createMailContent(
|
||||
accountId: number,
|
||||
userId: number,
|
||||
email: string
|
||||
): Promise<{ subject: string; text: string; html: string }> {
|
||||
async createMailContentOfLicenseShortage(): Promise<{
|
||||
subject: string;
|
||||
text: string;
|
||||
html: string;
|
||||
}> {
|
||||
return {
|
||||
subject: "Verify your new account",
|
||||
text: `The verification URL.`,
|
||||
html: `<p>The verification URL.<p>`,
|
||||
subject: "ライセンス在庫不足通知",
|
||||
text: `ライセンス在庫不足通知:本文`,
|
||||
html: `<p>ライセンス在庫不足通知:本文</p>`,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* メールコンテンツを作成する(ライセンス不足)
|
||||
* @param accountId 認証対象のユーザーが所属するアカウントのID
|
||||
* @param userId 認証対象のユーザーのID
|
||||
* @param email 認証対象のユーザーのメールアドレス
|
||||
* @returns メールのサブジェクトとコンテンツ
|
||||
*/
|
||||
async createMailContentOfLicenseExpiringSoon(): Promise<{
|
||||
subject: string;
|
||||
text: string;
|
||||
html: string;
|
||||
}> {
|
||||
return {
|
||||
subject: "ライセンス失効警告 ",
|
||||
text: `ライセンス失効警告:本文`,
|
||||
html: `<p>ライセンス失効警告:本文</p>`,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
198
dictation_function/src/test/common/utility.ts
Normal file
198
dictation_function/src/test/common/utility.ts
Normal file
@ -0,0 +1,198 @@
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { DataSource } from "typeorm";
|
||||
import { User } from "../../entity/user.entity";
|
||||
import { Account } from "../../entity/account.entity";
|
||||
import { ADMIN_ROLES, USER_ROLES } from "../../constants";
|
||||
import { License } from "../../entity/license.entity";
|
||||
|
||||
type InitialTestDBState = {
|
||||
tier1Accounts: { account: Account; users: User[] }[];
|
||||
tier2Accounts: { account: Account; users: User[] }[];
|
||||
tier3Accounts: { account: Account; users: User[] }[];
|
||||
tier4Accounts: { account: Account; users: User[] }[];
|
||||
tier5Accounts: { account: Account; users: User[] }[];
|
||||
};
|
||||
|
||||
// 上書きされたら困る項目を除外したAccount型
|
||||
type OverrideAccount = Omit<
|
||||
Account,
|
||||
"id" | "primary_admin_user_id" | "secondary_admin_user_id" | "user"
|
||||
>;
|
||||
|
||||
// 上書きされたら困る項目を除外したUser型
|
||||
type OverrideUser = Omit<
|
||||
User,
|
||||
"id" | "account" | "license" | "userGroupMembers"
|
||||
>;
|
||||
|
||||
type AccountDefault = { [K in keyof OverrideAccount]?: OverrideAccount[K] };
|
||||
type UserDefault = { [K in keyof OverrideUser]?: OverrideUser[K] };
|
||||
|
||||
/**
|
||||
* テスト ユーティリティ: 指定したプロパティを上書きしたユーザーを作成する
|
||||
* @param dataSource データソース
|
||||
* @param defaultUserValue User型と同等かつoptionalなプロパティを持つ上書き箇所指定用オブジェクト
|
||||
* @returns 作成したユーザー
|
||||
*/
|
||||
export const makeTestUser = async (
|
||||
datasource: DataSource,
|
||||
defaultUserValue?: UserDefault
|
||||
): Promise<User> => {
|
||||
const d = defaultUserValue;
|
||||
const { identifiers } = await datasource.getRepository(User).insert({
|
||||
account_id: d?.account_id ?? -1,
|
||||
external_id: d?.external_id ?? uuidv4(),
|
||||
role: d?.role ?? `${ADMIN_ROLES.STANDARD} ${USER_ROLES.NONE}`,
|
||||
author_id: d?.author_id,
|
||||
accepted_eula_version: d?.accepted_eula_version ?? "1.0",
|
||||
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,
|
||||
prompt: d?.prompt ?? true,
|
||||
created_by: d?.created_by ?? "test_runner",
|
||||
created_at: d?.created_at ?? new Date(),
|
||||
updated_by: d?.updated_by ?? "updater",
|
||||
updated_at: d?.updated_at ?? new Date(),
|
||||
});
|
||||
const result = identifiers.pop() as User;
|
||||
|
||||
const user = await datasource.getRepository(User).findOne({
|
||||
where: {
|
||||
id: result.id,
|
||||
},
|
||||
});
|
||||
if (!user) {
|
||||
throw new Error("Unexpected null");
|
||||
}
|
||||
return user;
|
||||
};
|
||||
|
||||
/**
|
||||
* テスト ユーティリティ: 指定したプロパティを上書きしたアカウントとその管理者ユーザーを作成する
|
||||
* @param dataSource データソース
|
||||
* @param defaultUserValue Account型と同等かつoptionalなプロパティを持つ上書き箇所指定用オブジェクト
|
||||
* @param defaultAdminUserValue User型と同等かつoptionalなプロパティを持つ上書き箇所指定用オブジェクト(account_id等の所属関係が破壊される上書きは無視する)
|
||||
* @returns 作成したアカウント
|
||||
*/
|
||||
export const makeTestAccount = async (
|
||||
datasource: DataSource,
|
||||
defaultAccountValue?: AccountDefault,
|
||||
defaultAdminUserValue?: UserDefault,
|
||||
isPrimaryAdminNotExist?: boolean,
|
||||
isSecondaryAdminNotExist?: boolean
|
||||
): Promise<{ account: Account; admin: User }> => {
|
||||
let accountId: number;
|
||||
let userId: number;
|
||||
{
|
||||
const d = defaultAccountValue;
|
||||
const { identifiers } = await datasource.getRepository(Account).insert({
|
||||
tier: d?.tier ?? 1,
|
||||
parent_account_id: d?.parent_account_id ?? undefined,
|
||||
country: d?.country ?? "US",
|
||||
delegation_permission: d?.delegation_permission ?? false,
|
||||
locked: d?.locked ?? false,
|
||||
company_name: d?.company_name ?? "test inc.",
|
||||
verified: d?.verified ?? true,
|
||||
deleted_at: d?.deleted_at ?? "",
|
||||
created_by: d?.created_by ?? "test_runner",
|
||||
created_at: d?.created_at ?? new Date(),
|
||||
updated_by: d?.updated_by ?? "updater",
|
||||
updated_at: d?.updated_at ?? new Date(),
|
||||
});
|
||||
const result = identifiers.pop() as Account;
|
||||
accountId = result.id;
|
||||
}
|
||||
{
|
||||
const d = defaultAdminUserValue;
|
||||
const { identifiers } = await datasource.getRepository(User).insert({
|
||||
external_id: d?.external_id ?? uuidv4(),
|
||||
account_id: accountId,
|
||||
role: d?.role ?? "admin none",
|
||||
author_id: d?.author_id ?? undefined,
|
||||
accepted_eula_version: d?.accepted_eula_version ?? "1.0",
|
||||
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",
|
||||
prompt: d?.prompt ?? true,
|
||||
deleted_at: d?.deleted_at ?? "",
|
||||
created_by: d?.created_by ?? "test_runner",
|
||||
created_at: d?.created_at ?? new Date(),
|
||||
updated_by: d?.updated_by ?? "updater",
|
||||
updated_at: d?.updated_at ?? new Date(),
|
||||
});
|
||||
|
||||
const result = identifiers.pop() as User;
|
||||
userId = result.id;
|
||||
}
|
||||
|
||||
// Accountの管理者を設定する
|
||||
let secondaryAdminUserId: number | null = null;
|
||||
if (isPrimaryAdminNotExist && !isSecondaryAdminNotExist) {
|
||||
secondaryAdminUserId = userId;
|
||||
}
|
||||
await datasource.getRepository(Account).update(
|
||||
{ id: accountId },
|
||||
{
|
||||
primary_admin_user_id: isPrimaryAdminNotExist ? null : userId,
|
||||
secondary_admin_user_id: secondaryAdminUserId,
|
||||
}
|
||||
);
|
||||
|
||||
const account = await datasource.getRepository(Account).findOne({
|
||||
where: {
|
||||
id: accountId,
|
||||
},
|
||||
});
|
||||
|
||||
const admin = await datasource.getRepository(User).findOne({
|
||||
where: {
|
||||
id: userId,
|
||||
},
|
||||
});
|
||||
if (!account || !admin) {
|
||||
throw new Error("Unexpected null");
|
||||
}
|
||||
|
||||
return {
|
||||
account: account,
|
||||
admin: admin,
|
||||
};
|
||||
};
|
||||
|
||||
export const createLicense = async (
|
||||
datasource: DataSource,
|
||||
licenseId: number,
|
||||
expiry_date: Date | null,
|
||||
accountId: number,
|
||||
type: string,
|
||||
status: string,
|
||||
allocated_user_id: number | null,
|
||||
order_id: number | null,
|
||||
deleted_at: Date | null,
|
||||
delete_order_id: number | null
|
||||
): Promise<void> => {
|
||||
const { identifiers } = await datasource.getRepository(License).insert({
|
||||
id: licenseId,
|
||||
expiry_date: expiry_date,
|
||||
account_id: accountId,
|
||||
type: type,
|
||||
status: status,
|
||||
allocated_user_id: allocated_user_id,
|
||||
order_id: order_id,
|
||||
deleted_at: deleted_at,
|
||||
delete_order_id: delete_order_id,
|
||||
created_by: "test_runner",
|
||||
created_at: new Date(),
|
||||
updated_by: "updater",
|
||||
updated_at: new Date(),
|
||||
});
|
||||
identifiers.pop() as License;
|
||||
};
|
||||
339
dictation_function/src/test/licenseAlert.spec.ts
Normal file
339
dictation_function/src/test/licenseAlert.spec.ts
Normal file
@ -0,0 +1,339 @@
|
||||
import { DataSource } from "typeorm";
|
||||
import { licenseAlertProcessing } from "../functions/licenseAlert";
|
||||
import { makeTestAccount, createLicense } from "./common/utility";
|
||||
import * as dotenv from "dotenv";
|
||||
import {
|
||||
DateWithDayEndTime,
|
||||
DateWithZeroTime,
|
||||
ExpirationThresholdDate,
|
||||
NewTrialLicenseExpirationDate,
|
||||
} from "../common/types/types";
|
||||
import { AdB2cUser } from "../adb2c/types/types";
|
||||
import { ADB2C_SIGN_IN_TYPE } from "../constants";
|
||||
import { SendGridService } from "../sendgrid/sendgrid.service";
|
||||
import { AdB2cService } from "../adb2c/adb2c.service";
|
||||
import { InvocationContext } from "@azure/functions";
|
||||
|
||||
describe("licenseAlert", () => {
|
||||
dotenv.config({ path: ".env" });
|
||||
dotenv.config({ path: ".env.local", override: true });
|
||||
let source: DataSource | null = null;
|
||||
beforeEach(async () => {
|
||||
source = new DataSource({
|
||||
type: "sqlite",
|
||||
database: ":memory:",
|
||||
logging: false,
|
||||
entities: [__dirname + "/../../**/*.entity{.ts,.js}"],
|
||||
synchronize: true, // trueにすると自動的にmigrationが行われるため注意
|
||||
});
|
||||
return source.initialize();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
if (!source) return;
|
||||
await source.destroy();
|
||||
source = null;
|
||||
});
|
||||
|
||||
it("ライセンス在庫不足メールが送信され、ライセンス失効警告メールが送信されないこと", async () => {
|
||||
if (!source) fail();
|
||||
const context = new InvocationContext();
|
||||
const sendgridMock = new SendGridServiceMock() as SendGridService;
|
||||
const adb2cMock = new AdB2cServiceMock() as AdB2cService;
|
||||
// 呼び出し回数でテスト成否を判定
|
||||
const spyShortage = jest.spyOn(
|
||||
sendgridMock,
|
||||
"createMailContentOfLicenseShortage"
|
||||
);
|
||||
const spyExpirySoon = jest.spyOn(
|
||||
sendgridMock,
|
||||
"createMailContentOfLicenseExpiringSoon"
|
||||
);
|
||||
const spySend = jest.spyOn(sendgridMock, "sendMail");
|
||||
|
||||
const currentDate = new DateWithZeroTime();
|
||||
const expiringSoonDate = new ExpirationThresholdDate(currentDate.getTime());
|
||||
const { account, admin } = await makeTestAccount(
|
||||
source,
|
||||
{ tier: 5 },
|
||||
{ external_id: "external_id1" }
|
||||
);
|
||||
await createLicense(
|
||||
source,
|
||||
1,
|
||||
expiringSoonDate,
|
||||
account.id,
|
||||
"STANDARD",
|
||||
"Allocated",
|
||||
admin.id,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
);
|
||||
|
||||
await licenseAlertProcessing(context, source, sendgridMock, adb2cMock);
|
||||
expect(spyShortage.mock.calls).toHaveLength(1);
|
||||
expect(spyExpirySoon.mock.calls).toHaveLength(0);
|
||||
expect(spySend.mock.calls).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("ライセンス在庫不足メール、ライセンス失効警告メールが送信されること", async () => {
|
||||
if (!source) fail();
|
||||
const context = new InvocationContext();
|
||||
const sendgridMock = new SendGridServiceMock() as SendGridService;
|
||||
const adb2cMock = new AdB2cServiceMock() as AdB2cService;
|
||||
|
||||
// 呼び出し回数でテスト成否を判定
|
||||
const spyShortage = jest.spyOn(
|
||||
sendgridMock,
|
||||
"createMailContentOfLicenseShortage"
|
||||
);
|
||||
const spyExpirySoon = jest.spyOn(
|
||||
sendgridMock,
|
||||
"createMailContentOfLicenseExpiringSoon"
|
||||
);
|
||||
const spySend = jest.spyOn(sendgridMock, "sendMail");
|
||||
|
||||
const currentDate = new DateWithZeroTime();
|
||||
const expiringSoonDate = new DateWithDayEndTime(currentDate.getTime());
|
||||
const { account, admin } = await makeTestAccount(
|
||||
source,
|
||||
{ tier: 5 },
|
||||
{ external_id: "external_id2", auto_renew: false }
|
||||
);
|
||||
await createLicense(
|
||||
source,
|
||||
1,
|
||||
expiringSoonDate,
|
||||
account.id,
|
||||
"STANDARD",
|
||||
"Allocated",
|
||||
admin.id,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
);
|
||||
|
||||
await licenseAlertProcessing(context, source, sendgridMock, adb2cMock);
|
||||
expect(spyShortage.mock.calls).toHaveLength(1);
|
||||
expect(spyExpirySoon.mock.calls).toHaveLength(1);
|
||||
expect(spySend.mock.calls).toHaveLength(2);
|
||||
});
|
||||
|
||||
it("在庫があるため、ライセンス在庫不足メールが送信されないこと", async () => {
|
||||
if (!source) fail();
|
||||
const context = new InvocationContext();
|
||||
const sendgridMock = new SendGridServiceMock() as SendGridService;
|
||||
const adb2cMock = new AdB2cServiceMock() as AdB2cService;
|
||||
|
||||
// 呼び出し回数でテスト成否を判定
|
||||
const spyShortage = jest.spyOn(
|
||||
sendgridMock,
|
||||
"createMailContentOfLicenseShortage"
|
||||
);
|
||||
const spyExpirySoon = jest.spyOn(
|
||||
sendgridMock,
|
||||
"createMailContentOfLicenseExpiringSoon"
|
||||
);
|
||||
const spySend = jest.spyOn(sendgridMock, "sendMail");
|
||||
|
||||
const currentDate = new DateWithZeroTime();
|
||||
const expiringSoonDate = new ExpirationThresholdDate(currentDate.getTime());
|
||||
const expiryDate = new NewTrialLicenseExpirationDate(currentDate.getTime());
|
||||
const { account, admin } = await makeTestAccount(
|
||||
source,
|
||||
{ tier: 5 },
|
||||
{ external_id: "external_id3" }
|
||||
);
|
||||
await createLicense(
|
||||
source,
|
||||
1,
|
||||
expiringSoonDate,
|
||||
account.id,
|
||||
"STANDARD",
|
||||
"Allocated",
|
||||
admin.id,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
);
|
||||
await createLicense(
|
||||
source,
|
||||
2,
|
||||
expiryDate,
|
||||
account.id,
|
||||
"STANDARD",
|
||||
"Unallocated",
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
);
|
||||
|
||||
await licenseAlertProcessing(context, source, sendgridMock, adb2cMock);
|
||||
expect(spyShortage.mock.calls).toHaveLength(0);
|
||||
expect(spyExpirySoon.mock.calls).toHaveLength(0);
|
||||
expect(spySend.mock.calls).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("AutoRenewがtureのため、ライセンス失効警告メールが送信されないこと", async () => {
|
||||
if (!source) fail();
|
||||
const context = new InvocationContext();
|
||||
const sendgridMock = new SendGridServiceMock() as SendGridService;
|
||||
const adb2cMock = new AdB2cServiceMock() as AdB2cService;
|
||||
|
||||
// 呼び出し回数でテスト成否を判定
|
||||
const spyShortage = jest.spyOn(
|
||||
sendgridMock,
|
||||
"createMailContentOfLicenseShortage"
|
||||
);
|
||||
const spyExpirySoon = jest.spyOn(
|
||||
sendgridMock,
|
||||
"createMailContentOfLicenseExpiringSoon"
|
||||
);
|
||||
const spySend = jest.spyOn(sendgridMock, "sendMail");
|
||||
|
||||
const currentDate = new DateWithZeroTime();
|
||||
const expiringSoonDate = new DateWithDayEndTime(currentDate.getTime());
|
||||
const { account, admin } = await makeTestAccount(
|
||||
source,
|
||||
{ tier: 5 },
|
||||
{ external_id: "external_id4", auto_renew: true }
|
||||
);
|
||||
await createLicense(
|
||||
source,
|
||||
1,
|
||||
expiringSoonDate,
|
||||
account.id,
|
||||
"STANDARD",
|
||||
"Allocated",
|
||||
admin.id,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
);
|
||||
|
||||
await licenseAlertProcessing(context, source, sendgridMock, adb2cMock);
|
||||
expect(spyShortage.mock.calls).toHaveLength(1);
|
||||
expect(spyExpirySoon.mock.calls).toHaveLength(0);
|
||||
expect(spySend.mock.calls).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
// テスト用sendgrid
|
||||
export class SendGridServiceMock {
|
||||
/**
|
||||
* メールコンテンツを作成する(ライセンス不足)
|
||||
* @param accountId 認証対象のユーザーが所属するアカウントのID
|
||||
* @param userId 認証対象のユーザーのID
|
||||
* @param email 認証対象のユーザーのメールアドレス
|
||||
* @returns メールのサブジェクトとコンテンツ
|
||||
*/
|
||||
async createMailContentOfLicenseShortage(): Promise<{
|
||||
subject: string;
|
||||
text: string;
|
||||
html: string;
|
||||
}> {
|
||||
return {
|
||||
subject: "ライセンス在庫不足通知",
|
||||
text: `ライセンス在庫不足通知:本文`,
|
||||
html: `<p>ライセンス在庫不足通知:本文</p>`,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* メールコンテンツを作成する(ライセンス不足)
|
||||
* @param accountId 認証対象のユーザーが所属するアカウントのID
|
||||
* @param userId 認証対象のユーザーのID
|
||||
* @param email 認証対象のユーザーのメールアドレス
|
||||
* @returns メールのサブジェクトとコンテンツ
|
||||
*/
|
||||
async createMailContentOfLicenseExpiringSoon(): Promise<{
|
||||
subject: string;
|
||||
text: string;
|
||||
html: string;
|
||||
}> {
|
||||
return {
|
||||
subject: "ライセンス失効警告",
|
||||
text: `ライセンス失効警告:本文`,
|
||||
html: `<p>ライセンス失効警告:本文</p>`,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* メールを送信する
|
||||
* @param to
|
||||
* @param from
|
||||
* @param subject
|
||||
* @param text
|
||||
* @param html
|
||||
* @returns mail
|
||||
*/
|
||||
async sendMail(
|
||||
to: string,
|
||||
from: string,
|
||||
subject: string,
|
||||
text: string,
|
||||
html: string
|
||||
): Promise<void> {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// テスト用adb2c
|
||||
export class AdB2cServiceMock {
|
||||
/**
|
||||
* Azure AD B2Cからユーザ情報を取得する
|
||||
* @param externalIds 外部ユーザーID
|
||||
* @returns ユーザ情報
|
||||
*/
|
||||
async getUsers(externalIds: string[]): Promise<AdB2cUser[]> {
|
||||
const AdB2cMockUsers: AdB2cUser[] = [
|
||||
{
|
||||
id: "external_id1",
|
||||
displayName: "test1",
|
||||
identities: [
|
||||
{
|
||||
signInType: ADB2C_SIGN_IN_TYPE.EMAILADDRESS,
|
||||
issuer: "issuer",
|
||||
issuerAssignedId: "test1@mail.com",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "external_id2",
|
||||
displayName: "test2",
|
||||
identities: [
|
||||
{
|
||||
signInType: ADB2C_SIGN_IN_TYPE.EMAILADDRESS,
|
||||
issuer: "issuer",
|
||||
issuerAssignedId: "test2@mail.com",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "external_id3",
|
||||
displayName: "test3",
|
||||
identities: [
|
||||
{
|
||||
signInType: ADB2C_SIGN_IN_TYPE.EMAILADDRESS,
|
||||
issuer: "issuer",
|
||||
issuerAssignedId: "test3@mail.com",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "external_id4",
|
||||
displayName: "test4",
|
||||
identities: [
|
||||
{
|
||||
signInType: ADB2C_SIGN_IN_TYPE.EMAILADDRESS,
|
||||
issuer: "issuer",
|
||||
issuerAssignedId: "test4@mail.com",
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
return AdB2cMockUsers;
|
||||
}
|
||||
}
|
||||
@ -1,42 +0,0 @@
|
||||
import { DataSource } from "typeorm";
|
||||
import {
|
||||
testExample,
|
||||
testSendgridExample,
|
||||
} from "../functions/timerTriggerExample";
|
||||
import { makeTestUser } from "../common/test/utility";
|
||||
import * as dotenv from "dotenv";
|
||||
|
||||
describe("timerTriggerExample", () => {
|
||||
dotenv.config({ path: ".env.local", override: true });
|
||||
let source: DataSource | null = null;
|
||||
beforeEach(async () => {
|
||||
source = new DataSource({
|
||||
type: "sqlite",
|
||||
database: ":memory:",
|
||||
logging: false,
|
||||
entities: [__dirname + "/../../**/*.entity{.ts,.js}"],
|
||||
synchronize: true, // trueにすると自動的にmigrationが行われるため注意
|
||||
});
|
||||
return source.initialize();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
if (!source) return;
|
||||
await source.destroy();
|
||||
source = null;
|
||||
});
|
||||
|
||||
it("sample test(DB)", async () => {
|
||||
const count = 5;
|
||||
for (let i = 0; i < count; i++) {
|
||||
await makeTestUser(source);
|
||||
}
|
||||
|
||||
const result = await testExample(source);
|
||||
expect(result.length).toEqual(count);
|
||||
});
|
||||
|
||||
it("sample test(sendgrid)", async () => {
|
||||
await testSendgridExample();
|
||||
});
|
||||
});
|
||||
@ -8,6 +8,7 @@
|
||||
"strict": false,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"esModuleInterop": true
|
||||
"esModuleInterop": true,
|
||||
"strictNullChecks": true,
|
||||
}
|
||||
}
|
||||
@ -1 +1,3 @@
|
||||
export const ADB2C_PREFIX = 'adb2c-external-id:';
|
||||
|
||||
export const IDTOKEN_PREFIX = 'id-token:';
|
||||
|
||||
11
dictation_server/src/common/cache/index.ts
vendored
11
dictation_server/src/common/cache/index.ts
vendored
@ -1,4 +1,4 @@
|
||||
import { ADB2C_PREFIX } from './constants';
|
||||
import { ADB2C_PREFIX, IDTOKEN_PREFIX } from './constants';
|
||||
|
||||
/**
|
||||
* ADB2Cのユーザー格納用のキーを生成する
|
||||
@ -17,3 +17,12 @@ export const makeADB2CKey = (externalId: string): string => {
|
||||
export const restoreAdB2cID = (key: string): string => {
|
||||
return key.replace(ADB2C_PREFIX, '');
|
||||
};
|
||||
|
||||
/**
|
||||
* ADB2CのIDトークン格納用のキーを生成する
|
||||
* @param idToken IDトークン
|
||||
* @returns キャッシュのキー
|
||||
*/
|
||||
export const makeIDTokenKey = (idToken: string): string => {
|
||||
return `${IDTOKEN_PREFIX}${idToken}`;
|
||||
};
|
||||
|
||||
@ -38,6 +38,8 @@ import { TermsService } from '../../features/terms/terms.service';
|
||||
import { TermsRepositoryModule } from '../../repositories/terms/terms.repository.module';
|
||||
import { TermsModule } from '../../features/terms/terms.module';
|
||||
import { CacheModule } from '@nestjs/common';
|
||||
import { RedisModule } from '../../gateways/redis/redis.module';
|
||||
import { RedisService } from '../../gateways/redis/redis.service';
|
||||
|
||||
export const makeTestingModule = async (
|
||||
datasource: DataSource,
|
||||
@ -77,6 +79,7 @@ export const makeTestingModule = async (
|
||||
SortCriteriaRepositoryModule,
|
||||
WorktypesRepositoryModule,
|
||||
TermsRepositoryModule,
|
||||
RedisModule,
|
||||
CacheModule.register({ isGlobal: true }),
|
||||
],
|
||||
providers: [
|
||||
@ -90,6 +93,7 @@ export const makeTestingModule = async (
|
||||
TemplatesService,
|
||||
WorkflowsService,
|
||||
TermsService,
|
||||
RedisService,
|
||||
],
|
||||
})
|
||||
.useMocker(async (token) => {
|
||||
|
||||
@ -33,6 +33,8 @@ import { RoleGuard } from '../../common/guards/role/roleguards';
|
||||
import { ADMIN_ROLES, TIERS } from '../../constants';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import { AccessToken, RefreshToken } from '../../common/token';
|
||||
import { makeIDTokenKey } from '../../common/cache';
|
||||
import { RedisService } from '../../gateways/redis/redis.service';
|
||||
|
||||
@ApiTags('auth')
|
||||
@Controller('auth')
|
||||
@ -41,6 +43,7 @@ export class AuthController {
|
||||
// TODO「タスク 1828: IDトークンを一度しか使えないようにする」で使用する予定
|
||||
// private readonly redisService: RedisService,
|
||||
private readonly authService: AuthService,
|
||||
private readonly redisService: RedisService,
|
||||
) {}
|
||||
|
||||
@Post('token')
|
||||
@ -77,6 +80,18 @@ export class AuthController {
|
||||
|
||||
const context = makeContext(uuidv4());
|
||||
|
||||
const key = makeIDTokenKey(body.idToken);
|
||||
const isTokenExists = await this.redisService.get<boolean>(key);
|
||||
if (!isTokenExists) {
|
||||
// IDトークンがキャッシュに存在しない場合(idTokenの有効期限をADB2Cの有効期限と合わせる(300秒))
|
||||
await this.redisService.set(key, true, 300);
|
||||
} else {
|
||||
// IDトークンがキャッシュに存在する場合エラー
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000106'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
// 同意済み利用規約バージョンが最新かチェック
|
||||
const isAcceptedLatestVersion =
|
||||
await this.authService.isAcceptedLatestVersion(context, idToken);
|
||||
|
||||
@ -4,15 +4,10 @@ import { AdB2cModule } from '../../gateways/adb2c/adb2c.module';
|
||||
import { UsersRepositoryModule } from '../../repositories/users/users.repository.module';
|
||||
import { AuthController } from './auth.controller';
|
||||
import { AuthService } from './auth.service';
|
||||
import { TermsRepositoryModule } from '../../repositories/terms/terms.repository.module';
|
||||
import { RedisService } from '../../gateways/redis/redis.service';
|
||||
@Module({
|
||||
imports: [
|
||||
ConfigModule,
|
||||
AdB2cModule,
|
||||
UsersRepositoryModule,
|
||||
TermsRepositoryModule,
|
||||
],
|
||||
imports: [ConfigModule, AdB2cModule, UsersRepositoryModule],
|
||||
controllers: [AuthController],
|
||||
providers: [AuthService],
|
||||
providers: [AuthService, RedisService],
|
||||
})
|
||||
export class AuthModule {}
|
||||
|
||||
@ -886,11 +886,12 @@ export class AccountsRepositoryService {
|
||||
where: {
|
||||
id: primaryAdminUserId,
|
||||
account_id: myAccountId,
|
||||
email_verified: true,
|
||||
},
|
||||
});
|
||||
if (!primaryAdminUser) {
|
||||
throw new AdminUserNotFoundError(
|
||||
`Primary admin user is not found. id: ${primaryAdminUserId}, account_id: ${myAccountId}`,
|
||||
`Primary admin user is not found or email not verified. id: ${primaryAdminUserId}, account_id: ${myAccountId}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -901,11 +902,12 @@ export class AccountsRepositoryService {
|
||||
where: {
|
||||
id: secondryAdminUserId,
|
||||
account_id: myAccountId,
|
||||
email_verified: true,
|
||||
},
|
||||
});
|
||||
if (!secondryAdminUser) {
|
||||
throw new AdminUserNotFoundError(
|
||||
`Secondry admin user is not found. id: ${secondryAdminUserId}, account_id: ${myAccountId}`,
|
||||
`Secondary admin user is not found or email not verified. id: ${secondryAdminUserId}, account_id: ${myAccountId}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user