diff --git a/dictation_client/src/api/api.ts b/dictation_client/src/api/api.ts index 2b5689d..b77b7b4 100644 --- a/dictation_client/src/api/api.ts +++ b/dictation_client/src/api/api.ts @@ -447,6 +447,12 @@ export interface CreateAccountRequest { * @memberof CreateAccountRequest */ 'acceptedEulaVersion': string; + /** + * 同意済みプライバシーポリシーのバージョン + * @type {string} + * @memberof CreateAccountRequest + */ + 'acceptedPrivacyNoticeVersion': string; /** * 同意済み利用規約のバージョン(DPA) * @type {string} @@ -2000,6 +2006,12 @@ export interface UpdateAcceptedVersionRequest { * @memberof UpdateAcceptedVersionRequest */ 'acceptedEULAVersion': string; + /** + * 更新バージョン(PrivacyNotice) + * @type {string} + * @memberof UpdateAcceptedVersionRequest + */ + 'acceptedPrivacyNoticeVersion': string; /** * 更新バージョン(DPA) * @type {string} diff --git a/dictation_client/src/features/terms/constants.ts b/dictation_client/src/features/terms/constants.ts index dd78e43..e9cbd2f 100644 --- a/dictation_client/src/features/terms/constants.ts +++ b/dictation_client/src/features/terms/constants.ts @@ -5,4 +5,5 @@ export const TERMS_DOCUMENT_TYPE = { DPA: "DPA", EULA: "EULA", + PRIVACY_NOTICE: "PrivacyNotice", } as const; diff --git a/dictation_client/src/features/terms/operations.ts b/dictation_client/src/features/terms/operations.ts index 876bb19..f9a1a0d 100644 --- a/dictation_client/src/features/terms/operations.ts +++ b/dictation_client/src/features/terms/operations.ts @@ -110,6 +110,7 @@ export const updateAcceptedVersionAsync = createAsyncThunk< updateAccceptVersions: { acceptedVerDPA: string; acceptedVerEULA: string; + acceptedVerPrivacyNotice: string; }; }, { @@ -140,6 +141,8 @@ export const updateAcceptedVersionAsync = createAsyncThunk< { idToken, acceptedEULAVersion: updateAccceptVersions.acceptedVerEULA, + acceptedPrivacyNoticeVersion: + updateAccceptVersions.acceptedVerPrivacyNotice, acceptedDPAVersion: !(TIERS.TIER5 === tier.toString()) ? updateAccceptVersions.acceptedVerDPA : undefined, diff --git a/dictation_client/src/features/terms/selectors.ts b/dictation_client/src/features/terms/selectors.ts index 5cd00f7..75f45b9 100644 --- a/dictation_client/src/features/terms/selectors.ts +++ b/dictation_client/src/features/terms/selectors.ts @@ -14,7 +14,11 @@ export const selectTermVersions = (state: RootState) => { (termInfo) => termInfo.documentType === TERMS_DOCUMENT_TYPE.EULA )?.version || ""; - return { acceptedVerDPA, acceptedVerEULA }; + const acceptedVerPrivacyNotice = + termsInfo.find( + (termInfo) => termInfo.documentType === TERMS_DOCUMENT_TYPE.PRIVACY_NOTICE + )?.version || ""; + return { acceptedVerDPA, acceptedVerEULA, acceptedVerPrivacyNotice }; }; export const selectTier = (state: RootState) => state.terms.domain.tier; diff --git a/dictation_client/src/pages/SignupPage/signupConfirm.tsx b/dictation_client/src/pages/SignupPage/signupConfirm.tsx index 698e1b6..b4e6a26 100644 --- a/dictation_client/src/pages/SignupPage/signupConfirm.tsx +++ b/dictation_client/src/pages/SignupPage/signupConfirm.tsx @@ -40,6 +40,7 @@ const SignupConfirm: React.FC = (): JSX.Element => { adminMail, adminPassword, acceptedEulaVersion, + acceptedPrivacyNoticeVersion: "", acceptedDpaVersion: "", token: "", }) diff --git a/dictation_client/src/pages/TermsPage/index.tsx b/dictation_client/src/pages/TermsPage/index.tsx index cb45b5d..777a115 100644 --- a/dictation_client/src/pages/TermsPage/index.tsx +++ b/dictation_client/src/pages/TermsPage/index.tsx @@ -29,9 +29,12 @@ const TermsPage: React.FC = (): JSX.Element => { const tier = useSelector(selectTier); const [isCheckedEula, setIsCheckedEula] = useState(false); + const [isCheckedPrivacyNotice, setIsCheckedPrivacyNotice] = useState(false); const [isCheckedDpa, setIsCheckedDpa] = useState(false); const [isClickedEulaLink, setIsClickedEulaLink] = useState(false); + const [isClickedPrivacyNoticeLink, setIsClickedPrivacyNoticeLink] = + useState(false); const [isClickedDpaLink, setIsClickedDpaLink] = useState(false); // 画面起動時 @@ -52,9 +55,9 @@ const TermsPage: React.FC = (): JSX.Element => { // ボタン押下可否判定ロジック const canClickButton = () => { if (isTier5()) { - return isCheckedEula; + return isCheckedEula && isCheckedPrivacyNotice; } - return isCheckedEula && isCheckedDpa; + return isCheckedEula && isCheckedPrivacyNotice && isCheckedDpa; }; // ボタン押下時処理 @@ -62,7 +65,8 @@ const TermsPage: React.FC = (): JSX.Element => { if ( localStorageKeyforIdToken && updateAccceptVersions.acceptedVerDPA !== "" && - updateAccceptVersions.acceptedVerEULA !== "" + updateAccceptVersions.acceptedVerEULA !== "" && + updateAccceptVersions.acceptedVerPrivacyNotice !== "" ) { const { meta } = await dispatch( updateAcceptedVersionAsync({ @@ -132,7 +136,42 @@ const TermsPage: React.FC = (): JSX.Element => {

- {/* 第五階層以外の場合はEulaのリンクをあわせて表示する */} +
+

+ {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */} + setIsClickedPrivacyNoticeLink(true)} + data-tag="open-pricacy-notice" + > + {t( + getTranslationID("termsPage.label.linkOfPrivacyNotice") + )} + + {` ${t(getTranslationID("termsPage.label.forOdds"))}`} +

+

+ +

+
+ {/* 第五階層以外の場合はDPAのリンクをあわせて表示する */} {!isTier5() && (

diff --git a/dictation_client/src/translation/de.json b/dictation_client/src/translation/de.json index f7e485d..71b532f 100644 --- a/dictation_client/src/translation/de.json +++ b/dictation_client/src/translation/de.json @@ -532,6 +532,7 @@ "title": "(de)Terms of Use has updated. Please confirm again.", "linkOfEula": "(de)Click here to read the terms of use.", "linkOfDpa": "(de)Click here to read the terms of use.", + "linkOfPrivacyNotice": "(de)Click here to read the terms of use.", "checkBoxForConsent": "(de)Yes, I agree to the terms of use.", "forOdds": "(de)for ODDS.", "button": "(de)Continue", diff --git a/dictation_client/src/translation/en.json b/dictation_client/src/translation/en.json index 6da4f95..cbf9f66 100644 --- a/dictation_client/src/translation/en.json +++ b/dictation_client/src/translation/en.json @@ -532,6 +532,7 @@ "title": "Terms of Use has updated. Please confirm again.", "linkOfEula": "Click here to read the terms of use.", "linkOfDpa": "Click here to read the terms of use.", + "linkOfPrivacyNotice": "Click here to read the terms of use.", "checkBoxForConsent": "Yes, I agree to the terms of use.", "forOdds": "for ODDS.", "button": "Continue", diff --git a/dictation_client/src/translation/es.json b/dictation_client/src/translation/es.json index d6711e3..552edb2 100644 --- a/dictation_client/src/translation/es.json +++ b/dictation_client/src/translation/es.json @@ -532,6 +532,7 @@ "title": "(es)Terms of Use has updated. Please confirm again.", "linkOfEula": "(es)Click here to read the terms of use.", "linkOfDpa": "(es)Click here to read the terms of use.", + "linkOfPrivacyNotice": "(es)Click here to read the terms of use.", "checkBoxForConsent": "(es)Yes, I agree to the terms of use.", "forOdds": "(es)for ODDS.", "button": "(es)Continue", diff --git a/dictation_client/src/translation/fr.json b/dictation_client/src/translation/fr.json index d6735e1..e3bf194 100644 --- a/dictation_client/src/translation/fr.json +++ b/dictation_client/src/translation/fr.json @@ -532,6 +532,7 @@ "title": "(fr)Terms of Use has updated. Please confirm again.", "linkOfEula": "(fr)Click here to read the terms of use.", "linkOfDpa": "(fr)Click here to read the terms of use.", + "linkOfPrivacyNotice": "(fr)Click here to read the terms of use.", "checkBoxForConsent": "(fr)Yes, I agree to the terms of use.", "forOdds": "(fr)for ODDS.", "button": "(fr)Continue", diff --git a/dictation_server/src/api/odms/openapi.json b/dictation_server/src/api/odms/openapi.json index 7667921..069d43c 100644 --- a/dictation_server/src/api/odms/openapi.json +++ b/dictation_server/src/api/odms/openapi.json @@ -3537,6 +3537,10 @@ "type": "string", "description": "同意済み利用規約のバージョン(EULA)" }, + "acceptedPrivacyNoticeVersion": { + "type": "string", + "description": "同意済みプライバシーポリシーのバージョン" + }, "acceptedDpaVersion": { "type": "string", "description": "同意済み利用規約のバージョン(DPA)" @@ -3550,6 +3554,7 @@ "adminMail", "adminPassword", "acceptedEulaVersion", + "acceptedPrivacyNoticeVersion", "acceptedDpaVersion", "token" ] @@ -4292,12 +4297,20 @@ "type": "string", "description": "更新バージョン(EULA)" }, + "acceptedPrivacyNoticeVersion": { + "type": "string", + "description": "更新バージョン(PrivacyNotice)" + }, "acceptedDPAVersion": { "type": "string", "description": "更新バージョン(DPA)" } }, - "required": ["idToken", "acceptedEULAVersion"] + "required": [ + "idToken", + "acceptedEULAVersion", + "acceptedPrivacyNoticeVersion" + ] }, "UpdateAcceptedVersionResponse": { "type": "object", "properties": {} }, "GetMyUserResponse": { diff --git a/dictation_server/src/common/test/utility.ts b/dictation_server/src/common/test/utility.ts index 00b19d7..18dda44 100644 --- a/dictation_server/src/common/test/utility.ts +++ b/dictation_server/src/common/test/utility.ts @@ -182,6 +182,8 @@ export const makeTestAccount = async ( role: d?.role ?? 'admin none', author_id: d?.author_id ?? undefined, accepted_eula_version: d?.accepted_eula_version ?? '1.0', + accepted_privacy_notice_version: + d?.accepted_privacy_notice_version ?? '1.0', accepted_dpa_version: d?.accepted_dpa_version ?? '1.0', email_verified: d?.email_verified ?? true, auto_renew: d?.auto_renew ?? true, diff --git a/dictation_server/src/constants/index.ts b/dictation_server/src/constants/index.ts index 0f8a7eb..439c7ed 100644 --- a/dictation_server/src/constants/index.ts +++ b/dictation_server/src/constants/index.ts @@ -287,6 +287,7 @@ export const MANUAL_RECOVERY_REQUIRED = '[MANUAL_RECOVERY_REQUIRED]'; export const TERM_TYPE = { EULA: 'EULA', DPA: 'DPA', + PRIVACY_NOTICE: 'PrivacyNotice', } as const; /** diff --git a/dictation_server/src/features/accounts/accounts.controller.ts b/dictation_server/src/features/accounts/accounts.controller.ts index 04c8e26..619aa2b 100644 --- a/dictation_server/src/features/accounts/accounts.controller.ts +++ b/dictation_server/src/features/accounts/accounts.controller.ts @@ -118,6 +118,7 @@ export class AccountsController { adminPassword, adminName, acceptedEulaVersion, + acceptedPrivacyNoticeVersion, acceptedDpaVersion, } = body; const role = USER_ROLES.NONE; @@ -134,6 +135,7 @@ export class AccountsController { adminName, role, acceptedEulaVersion, + acceptedPrivacyNoticeVersion, acceptedDpaVersion, ); diff --git a/dictation_server/src/features/accounts/accounts.service.spec.ts b/dictation_server/src/features/accounts/accounts.service.spec.ts index 2656aed..27c3715 100644 --- a/dictation_server/src/features/accounts/accounts.service.spec.ts +++ b/dictation_server/src/features/accounts/accounts.service.spec.ts @@ -112,6 +112,7 @@ describe('createAccount', () => { const username = 'dummy_username'; const role = 'none'; const acceptedEulaVersion = '1.0.0'; + const acceptedPrivacyNoticeVersion = '1.0.0'; const acceptedDpaVersion = '1.0.0'; overrideAdB2cService(service, { @@ -144,6 +145,7 @@ describe('createAccount', () => { username, role, acceptedEulaVersion, + acceptedPrivacyNoticeVersion, acceptedDpaVersion, ); // 作成したアカウントのIDが返ってくるか確認 @@ -161,6 +163,9 @@ describe('createAccount', () => { expect(account?.primary_admin_user_id).toBe(user?.id); expect(account?.secondary_admin_user_id).toBe(null); expect(user?.accepted_eula_version).toBe(acceptedEulaVersion); + expect(user?.accepted_privacy_notice_version).toBe( + acceptedPrivacyNoticeVersion, + ); expect(user?.accepted_dpa_version).toBe(acceptedDpaVersion); expect(user?.account_id).toBe(accountId); expect(user?.role).toBe(role); @@ -195,6 +200,7 @@ describe('createAccount', () => { const username = 'dummy_username'; const role = 'admin none'; const acceptedEulaVersion = '1.0.0'; + const acceptedPrivacyNoticeVersion = '1.0.0'; const acceptedDpaVersion = '1.0.0'; overrideAdB2cService(service, { @@ -216,6 +222,7 @@ describe('createAccount', () => { username, role, acceptedEulaVersion, + acceptedPrivacyNoticeVersion, acceptedDpaVersion, ); } catch (e) { @@ -264,6 +271,7 @@ describe('createAccount', () => { const username = 'dummy_username'; const role = 'admin none'; const acceptedEulaVersion = '1.0.0'; + const acceptedPrivacyNoticeVersion = '1.0.0'; const acceptedDpaVersion = '1.0.0'; overrideAdB2cService(service, { @@ -286,6 +294,7 @@ describe('createAccount', () => { username, role, acceptedEulaVersion, + acceptedPrivacyNoticeVersion, acceptedDpaVersion, ); } catch (e) { @@ -318,6 +327,7 @@ describe('createAccount', () => { const username = 'dummy_username'; const role = 'none'; const acceptedEulaVersion = '1.0.0'; + const acceptedPrivacyNoticeVersion = '1.0.0'; const acceptedDpaVersion = '1.0.0'; overrideAdB2cService(service, { @@ -345,6 +355,7 @@ describe('createAccount', () => { username, role, acceptedEulaVersion, + acceptedPrivacyNoticeVersion, acceptedDpaVersion, ); } catch (e) { @@ -384,6 +395,7 @@ describe('createAccount', () => { const username = 'dummy_username'; const role = 'none'; const acceptedEulaVersion = '1.0.0'; + const acceptedPrivacyNoticeVersion = '1.0.0'; const acceptedDpaVersion = '1.0.0'; overrideAdB2cService(service, { @@ -411,6 +423,7 @@ describe('createAccount', () => { username, role, acceptedEulaVersion, + acceptedPrivacyNoticeVersion, acceptedDpaVersion, ); } catch (e) { @@ -452,6 +465,7 @@ describe('createAccount', () => { const username = 'dummy_username'; const role = 'none'; const acceptedEulaVersion = '1.0.0'; + const acceptedPrivacyNoticeVersion = '1.0.0'; const acceptedDpaVersion = '1.0.0'; overrideAdB2cService(service, { @@ -480,6 +494,7 @@ describe('createAccount', () => { username, role, acceptedEulaVersion, + acceptedPrivacyNoticeVersion, acceptedDpaVersion, ); } catch (e) { @@ -520,6 +535,7 @@ describe('createAccount', () => { const username = 'dummy_username'; const role = 'none'; const acceptedEulaVersion = '1.0.0'; + const acceptedPrivacyNoticeVersion = '1.0.0'; const acceptedDpaVersion = '1.0.0'; overrideAdB2cService(service, { @@ -551,6 +567,7 @@ describe('createAccount', () => { username, role, acceptedEulaVersion, + acceptedPrivacyNoticeVersion, acceptedDpaVersion, ); } catch (e) { @@ -593,6 +610,7 @@ describe('createAccount', () => { const username = 'dummy_username'; const role = 'none'; const acceptedEulaVersion = '1.0.0'; + const acceptedPrivacyNoticeVersion = '1.0.0'; const acceptedDpaVersion = '1.0.0'; overrideAdB2cService(service, { @@ -641,6 +659,7 @@ describe('createAccount', () => { username, role, acceptedEulaVersion, + acceptedPrivacyNoticeVersion, acceptedDpaVersion, ); } catch (e) { @@ -689,6 +708,7 @@ describe('createAccount', () => { const username = 'dummy_username'; const role = 'none'; const acceptedEulaVersion = '1.0.0'; + const acceptedPrivacyNoticeVersion = '1.0.0'; const acceptedDpaVersion = '1.0.0'; overrideAdB2cService(service, { @@ -734,6 +754,7 @@ describe('createAccount', () => { username, role, acceptedEulaVersion, + acceptedPrivacyNoticeVersion, acceptedDpaVersion, ); } catch (e) { diff --git a/dictation_server/src/features/accounts/accounts.service.ts b/dictation_server/src/features/accounts/accounts.service.ts index 49f61f2..668f815 100644 --- a/dictation_server/src/features/accounts/accounts.service.ts +++ b/dictation_server/src/features/accounts/accounts.service.ts @@ -176,6 +176,7 @@ export class AccountsService { username: string, role: string, acceptedEulaVersion: string, + acceptedPrivacyNoticeVersion: string, acceptedDpaVersion: string, ): Promise<{ accountId: number; userId: number; externalUserId: string }> { this.logger.log( @@ -185,6 +186,7 @@ export class AccountsService { `dealerAccountId: ${dealerAccountId}, ` + `role: ${role}, ` + `acceptedEulaVersion: ${acceptedEulaVersion}, ` + + `acceptedPrivacyNoticeVersion: ${acceptedPrivacyNoticeVersion}, ` + `acceptedDpaVersion: ${acceptedDpaVersion} };`, ); try { @@ -233,6 +235,7 @@ export class AccountsService { externalUser.sub, role, acceptedEulaVersion, + acceptedPrivacyNoticeVersion, acceptedDpaVersion, ); account = newAccount; diff --git a/dictation_server/src/features/accounts/types/types.ts b/dictation_server/src/features/accounts/types/types.ts index 8a555d6..276fd0e 100644 --- a/dictation_server/src/features/accounts/types/types.ts +++ b/dictation_server/src/features/accounts/types/types.ts @@ -45,6 +45,8 @@ export class CreateAccountRequest { adminPassword: string; @ApiProperty({ description: '同意済み利用規約のバージョン(EULA)' }) acceptedEulaVersion: string; + @ApiProperty({ description: '同意済みプライバシーポリシーのバージョン' }) + acceptedPrivacyNoticeVersion: string; @ApiProperty({ description: '同意済み利用規約のバージョン(DPA)' }) acceptedDpaVersion: string; @ApiProperty({ description: 'reCAPTCHA Token' }) diff --git a/dictation_server/src/features/auth/auth.service.spec.ts b/dictation_server/src/features/auth/auth.service.spec.ts index 4451cb0..f978889 100644 --- a/dictation_server/src/features/auth/auth.service.spec.ts +++ b/dictation_server/src/features/auth/auth.service.spec.ts @@ -196,6 +196,7 @@ describe('checkIsAcceptedLatestVersion', () => { }; await createTermInfo(source, 'EULA', '1.0'); + await createTermInfo(source, 'PrivacyNotice', '1.0'); await createTermInfo(source, 'DPA', '1.0'); const result = await service.isAcceptedLatestVersion(context, idToken); expect(result).toBe(true); @@ -219,6 +220,7 @@ describe('checkIsAcceptedLatestVersion', () => { }; await createTermInfo(source, 'EULA', '1.0'); + await createTermInfo(source, 'PrivacyNotice', '1.0'); await createTermInfo(source, 'DPA', '1.0'); const result = await service.isAcceptedLatestVersion(context, idToken); expect(result).toBe(true); @@ -242,6 +244,7 @@ describe('checkIsAcceptedLatestVersion', () => { }; await createTermInfo(source, 'EULA', '1.1'); + await createTermInfo(source, 'PrivacyNotice', '1.0'); await createTermInfo(source, 'DPA', '1.0'); const result = await service.isAcceptedLatestVersion(context, idToken); expect(result).toBe(false); @@ -265,6 +268,7 @@ describe('checkIsAcceptedLatestVersion', () => { }; await createTermInfo(source, 'EULA', '1.1'); + await createTermInfo(source, 'PrivacyNotice', '1.0'); await createTermInfo(source, 'DPA', '1.0'); const result = await service.isAcceptedLatestVersion(context, idToken); expect(result).toBe(false); @@ -288,10 +292,35 @@ describe('checkIsAcceptedLatestVersion', () => { }; await createTermInfo(source, 'EULA', '1.0'); + await createTermInfo(source, 'PrivacyNotice', '1.0'); await createTermInfo(source, 'DPA', '1.1'); const result = await service.isAcceptedLatestVersion(context, idToken); expect(result).toBe(false); }); + + it('同意済みプライバシーポリシーが最新でないときにチェックが通らないこと(第一~第四)', async () => { + if (!source) fail(); + const module = await makeTestingModule(source); + if (!module) fail(); + const service = module.get(AuthService); + const { admin } = await makeTestAccount(source, { + tier: 4, + }); + const context = makeContext(uuidv4()); + + const idToken = { + emails: [], + sub: admin.external_id, + exp: 0, + iat: 0, + }; + + await createTermInfo(source, 'EULA', '1.0'); + await createTermInfo(source, 'PrivacyNotice', '1.1'); + await createTermInfo(source, 'DPA', '1.0'); + const result = await service.isAcceptedLatestVersion(context, idToken); + expect(result).toBe(false); + }); }); describe('generateDelegationRefreshToken', () => { diff --git a/dictation_server/src/features/auth/auth.service.ts b/dictation_server/src/features/auth/auth.service.ts index 543ca12..dbcdfc3 100644 --- a/dictation_server/src/features/auth/auth.service.ts +++ b/dictation_server/src/features/auth/auth.service.ts @@ -689,28 +689,38 @@ export class AuthService { const { acceptedEulaVersion, latestEulaVersion, + acceptedPrivacyNoticeVersion, + latestPrivacyNoticeVersion, acceptedDpaVersion, latestDpaVersion, tier, } = await this.usersRepository.getAcceptedAndLatestVersion(idToken.sub); - // 第五階層はEULAのみ判定 + // 第五階層はEULAとPrivacyNoticeのみ判定 if (tier === TIERS.TIER5) { - if (!acceptedEulaVersion) { + if (!acceptedEulaVersion || !acceptedPrivacyNoticeVersion) { return false; } // 最新バージョンに同意済みか判定 const eulaAccepted = acceptedEulaVersion === latestEulaVersion; - return eulaAccepted; + const privacyNoticeAccepted = + acceptedPrivacyNoticeVersion === latestPrivacyNoticeVersion; + return eulaAccepted && privacyNoticeAccepted; } else { - // 第一~第四階層はEULA、DPAを判定 - if (!acceptedEulaVersion || !acceptedDpaVersion) { + // 第一~第四階層はEULA、PrivacyNotice、DPAを判定 + if ( + !acceptedEulaVersion || + !acceptedPrivacyNoticeVersion || + !acceptedDpaVersion + ) { return false; } // 最新バージョンに同意済みか判定 const eulaAccepted = acceptedEulaVersion === latestEulaVersion; + const privacyNoticeAccepted = + acceptedPrivacyNoticeVersion === latestPrivacyNoticeVersion; const dpaAccepted = acceptedDpaVersion === latestDpaVersion; - return eulaAccepted && dpaAccepted; + return eulaAccepted && privacyNoticeAccepted && dpaAccepted; } } catch (e) { this.logger.error(`[${context.getTrackingId()}] error=${e}`); diff --git a/dictation_server/src/features/auth/types/types.ts b/dictation_server/src/features/auth/types/types.ts index 1be9570..c031508 100644 --- a/dictation_server/src/features/auth/types/types.ts +++ b/dictation_server/src/features/auth/types/types.ts @@ -22,8 +22,10 @@ export class AccessTokenRequest {} export type TermsCheckInfo = { tier: number; acceptedEulaVersion?: string; + acceptedPrivacyNoticeVersion?: string; acceptedDpaVersion?: string; latestEulaVersion: string; + latestPrivacyNoticeVersion: string; latestDpaVersion: string; }; diff --git a/dictation_server/src/features/files/test/files.service.mock.ts b/dictation_server/src/features/files/test/files.service.mock.ts index a5b8f06..c69e7bf 100644 --- a/dictation_server/src/features/files/test/files.service.mock.ts +++ b/dictation_server/src/features/files/test/files.service.mock.ts @@ -139,6 +139,7 @@ export const makeDefaultUsersRepositoryMockValue = role: 'none', author_id: '', accepted_eula_version: '1.0', + accepted_privacy_notice_version: '1.0', accepted_dpa_version: '1.0', email_verified: true, deleted_at: null, diff --git a/dictation_server/src/features/tasks/test/tasks.service.mock.ts b/dictation_server/src/features/tasks/test/tasks.service.mock.ts index cf5aa71..5d5d97c 100644 --- a/dictation_server/src/features/tasks/test/tasks.service.mock.ts +++ b/dictation_server/src/features/tasks/test/tasks.service.mock.ts @@ -470,6 +470,7 @@ const defaultTasksRepositoryMockValue: { external_id: 'userId', role: 'typist', accepted_eula_version: '', + accepted_privacy_notice_version: '', accepted_dpa_version: '', email_verified: true, auto_renew: true, diff --git a/dictation_server/src/features/terms/terms.service.spec.ts b/dictation_server/src/features/terms/terms.service.spec.ts index 772e9f5..6ff1176 100644 --- a/dictation_server/src/features/terms/terms.service.spec.ts +++ b/dictation_server/src/features/terms/terms.service.spec.ts @@ -34,6 +34,8 @@ describe('利用規約取得', () => { await createTermInfo(source, 'EULA', 'v1.0'); await createTermInfo(source, 'EULA', 'v1.1'); + await createTermInfo(source, 'PrivacyNotice', 'v1.0'); + await createTermInfo(source, 'PrivacyNotice', 'v1.1'); await createTermInfo(source, 'DPA', 'v1.0'); await createTermInfo(source, 'DPA', 'v1.2'); @@ -42,8 +44,10 @@ describe('利用規約取得', () => { expect(result[0].documentType).toBe('EULA'); expect(result[0].version).toBe('v1.1'); - expect(result[1].documentType).toBe('DPA'); - expect(result[1].version).toBe('v1.2'); + expect(result[1].documentType).toBe('PrivacyNotice'); + expect(result[1].version).toBe('v1.1'); + expect(result[2].documentType).toBe('DPA'); + expect(result[2].version).toBe('v1.2'); }); it('利用規約情報(EULA、DPA両方)が存在しない場合エラーとなる', async () => { @@ -75,6 +79,21 @@ describe('利用規約取得', () => { ); }); + it('利用規約情報(PrivacyNoticeのみ)が存在しない場合エラーとなる', async () => { + if (!source) fail(); + const module = await makeTestingModule(source); + if (!module) fail(); + const service = module.get(TermsService); + await createTermInfo(source, 'PrivacyNotice', 'v1.0'); + const context = makeContext(uuidv4()); + await expect(service.getTermsInfo(context)).rejects.toEqual( + new HttpException( + makeErrorResponse('E009999'), + HttpStatus.INTERNAL_SERVER_ERROR, + ), + ); + }); + it('利用規約情報(DPAのみ)が存在しない場合エラーとなる', async () => { if (!source) fail(); const module = await makeTestingModule(source); diff --git a/dictation_server/src/features/terms/terms.service.ts b/dictation_server/src/features/terms/terms.service.ts index 65137a1..ee52415 100644 --- a/dictation_server/src/features/terms/terms.service.ts +++ b/dictation_server/src/features/terms/terms.service.ts @@ -19,13 +19,17 @@ export class TermsService { `[IN] [${context.getTrackingId()}] ${this.getTermsInfo.name}`, ); try { - const { eulaVersion, dpaVersion } = + const { eulaVersion, privacyNoticeVersion, dpaVersion } = await this.termsRepository.getLatestTermsInfo(); return [ { documentType: TERM_TYPE.EULA, version: eulaVersion, }, + { + documentType: TERM_TYPE.PRIVACY_NOTICE, + version: privacyNoticeVersion, + }, { documentType: TERM_TYPE.DPA, version: dpaVersion, diff --git a/dictation_server/src/features/terms/types/types.ts b/dictation_server/src/features/terms/types/types.ts index 6a45eae..479960e 100644 --- a/dictation_server/src/features/terms/types/types.ts +++ b/dictation_server/src/features/terms/types/types.ts @@ -13,5 +13,6 @@ export class GetTermsInfoResponse { export type TermsVersion = { eulaVersion: string; + privacyNoticeVersion: string; dpaVersion: string; }; diff --git a/dictation_server/src/features/users/types/types.ts b/dictation_server/src/features/users/types/types.ts index 9f48418..d21f72d 100644 --- a/dictation_server/src/features/users/types/types.ts +++ b/dictation_server/src/features/users/types/types.ts @@ -263,6 +263,8 @@ export class UpdateAcceptedVersionRequest { idToken: string; @ApiProperty({ description: '更新バージョン(EULA)' }) acceptedEULAVersion: string; + @ApiProperty({ description: '更新バージョン(PrivacyNotice)' }) + acceptedPrivacyNoticeVersion: string; @ApiProperty({ description: '更新バージョン(DPA)', required: false }) acceptedDPAVersion?: string; } diff --git a/dictation_server/src/features/users/users.controller.ts b/dictation_server/src/features/users/users.controller.ts index a92bbd2..9f88897 100644 --- a/dictation_server/src/features/users/users.controller.ts +++ b/dictation_server/src/features/users/users.controller.ts @@ -627,7 +627,12 @@ export class UsersController { async updateAcceptedVersion( @Body() body: UpdateAcceptedVersionRequest, ): Promise { - const { idToken, acceptedEULAVersion, acceptedDPAVersion } = body; + const { + idToken, + acceptedEULAVersion, + acceptedPrivacyNoticeVersion, + acceptedDPAVersion, + } = body; const context = makeContext(uuidv4()); @@ -650,6 +655,7 @@ export class UsersController { context, verifiedIdToken.sub, acceptedEULAVersion, + acceptedPrivacyNoticeVersion, acceptedDPAVersion, ); return {}; diff --git a/dictation_server/src/features/users/users.service.spec.ts b/dictation_server/src/features/users/users.service.spec.ts index 83894ee..cea6f31 100644 --- a/dictation_server/src/features/users/users.service.spec.ts +++ b/dictation_server/src/features/users/users.service.spec.ts @@ -208,6 +208,7 @@ describe('UsersService.confirmUserAndInitPassword', () => { account_id: 1, role: 'None', accepted_eula_version: 'string', + accepted_privacy_notice_version: 'string', accepted_dpa_version: 'string', email_verified: false, created_by: 'string;', @@ -259,6 +260,7 @@ describe('UsersService.confirmUserAndInitPassword', () => { account_id: 1, role: 'None', accepted_eula_version: 'string', + accepted_privacy_notice_version: 'string', accepted_dpa_version: 'string', email_verified: false, created_by: 'string;', @@ -306,6 +308,7 @@ describe('UsersService.confirmUserAndInitPassword', () => { account_id: 1, role: 'None', accepted_eula_version: 'string', + accepted_privacy_notice_version: 'string', accepted_dpa_version: 'string', email_verified: true, created_by: 'string;', @@ -358,6 +361,7 @@ describe('UsersService.confirmUserAndInitPassword', () => { account_id: 1, role: 'None', accepted_eula_version: 'string', + accepted_privacy_notice_version: 'string', accepted_dpa_version: 'string', email_verified: false, created_by: 'string;', @@ -2617,7 +2621,12 @@ describe('UsersService.updateAcceptedVersion', () => { const context = makeContext(uuidv4()); const service = module.get(UsersService); - await service.updateAcceptedVersion(context, admin.external_id, 'v2.0'); + await service.updateAcceptedVersion( + context, + admin.external_id, + 'v2.0', + 'v2.0', + ); const user = await getUser(source, admin.id); expect(user?.accepted_eula_version).toBe('v2.0'); @@ -2637,6 +2646,7 @@ describe('UsersService.updateAcceptedVersion', () => { context, admin.external_id, 'v2.0', + 'v2.0', 'v3.0', ); const user = await getUser(source, admin.id); @@ -2660,6 +2670,7 @@ describe('UsersService.updateAcceptedVersion', () => { context, admin.external_id, 'v2.0', + 'v2.0', undefined, ), ).rejects.toEqual( diff --git a/dictation_server/src/features/users/users.service.ts b/dictation_server/src/features/users/users.service.ts index cf82fd5..60e3e92 100644 --- a/dictation_server/src/features/users/users.service.ts +++ b/dictation_server/src/features/users/users.service.ts @@ -403,6 +403,7 @@ export class UsersService { role, accepted_dpa_version: null, accepted_eula_version: null, + accepted_privacy_notice_version: null, encryption: false, encryption_password: null, prompt: false, @@ -422,6 +423,7 @@ export class UsersService { prompt: prompt ?? false, accepted_dpa_version: null, accepted_eula_version: null, + accepted_privacy_notice_version: null, }; default: //不正なroleが指定された場合はログを出力してエラーを返す @@ -1044,12 +1046,14 @@ export class UsersService { * @param context * @param idToken * @param eulaVersion + * @param privacyNoticeVersion * @param dpaVersion */ async updateAcceptedVersion( context: Context, externalId: string, eulaVersion: string, + privacyNoticeVersion: string, dpaVersion?: string, ): Promise { this.logger.log( @@ -1058,6 +1062,7 @@ export class UsersService { } | params: { ` + `externalId: ${externalId}, ` + `eulaVersion: ${eulaVersion}, ` + + `privacyNoticeVersion: ${privacyNoticeVersion}, ` + `dpaVersion: ${dpaVersion}, };`, ); @@ -1065,6 +1070,7 @@ export class UsersService { await this.usersRepository.updateAcceptedTermsVersion( externalId, eulaVersion, + privacyNoticeVersion, dpaVersion, ); } catch (e) { diff --git a/dictation_server/src/repositories/accounts/accounts.repository.service.ts b/dictation_server/src/repositories/accounts/accounts.repository.service.ts index 1df7d13..d7762bd 100644 --- a/dictation_server/src/repositories/accounts/accounts.repository.service.ts +++ b/dictation_server/src/repositories/accounts/accounts.repository.service.ts @@ -127,6 +127,7 @@ export class AccountsRepositoryService { adminExternalUserId: string, adminUserRole: string, adminUserAcceptedEulaVersion?: string, + adminUserAcceptedPrivacyNoticeVersion?: string, adminUserAcceptedDpaVersion?: string, ): Promise<{ newAccount: Account; adminUser: User }> { return await this.dataSource.transaction(async (entityManager) => { @@ -148,6 +149,8 @@ export class AccountsRepositoryService { user.external_id = adminExternalUserId; user.role = adminUserRole; user.accepted_eula_version = adminUserAcceptedEulaVersion ?? null; + user.accepted_privacy_notice_version = + adminUserAcceptedPrivacyNoticeVersion ?? null; user.accepted_dpa_version = adminUserAcceptedDpaVersion ?? null; } const usersRepo = entityManager.getRepository(User); diff --git a/dictation_server/src/repositories/terms/terms.repository.service.ts b/dictation_server/src/repositories/terms/terms.repository.service.ts index 7c79f24..7deee0f 100644 --- a/dictation_server/src/repositories/terms/terms.repository.service.ts +++ b/dictation_server/src/repositories/terms/terms.repository.service.ts @@ -24,6 +24,14 @@ export class TermsRepositoryService { id: 'DESC', }, }); + const latestPrivacyNoticeInfo = await termRepo.findOne({ + where: { + document_type: TERM_TYPE.PRIVACY_NOTICE, + }, + order: { + id: 'DESC', + }, + }); const latestDpaInfo = await termRepo.findOne({ where: { document_type: TERM_TYPE.DPA, @@ -33,13 +41,16 @@ export class TermsRepositoryService { }, }); - if (!latestEulaInfo || !latestDpaInfo) { + if (!latestEulaInfo || !latestPrivacyNoticeInfo || !latestDpaInfo) { throw new TermInfoNotFoundError( - `Terms info is not found. latestEulaInfo: ${latestEulaInfo}, latestDpaInfo: ${latestDpaInfo}`, + `Terms info is not found. latestEulaInfo: ${latestEulaInfo}, + latestPrivacyNoticeInfo: ${latestPrivacyNoticeInfo}, + latestDpaInfo: ${latestDpaInfo}`, ); } return { eulaVersion: latestEulaInfo.version, + privacyNoticeVersion: latestEulaInfo.version, dpaVersion: latestDpaInfo.version, }; }); diff --git a/dictation_server/src/repositories/users/entity/user.entity.ts b/dictation_server/src/repositories/users/entity/user.entity.ts index 34d0bca..0f4e57c 100644 --- a/dictation_server/src/repositories/users/entity/user.entity.ts +++ b/dictation_server/src/repositories/users/entity/user.entity.ts @@ -34,6 +34,9 @@ export class User { @Column({ nullable: true, type: 'varchar' }) accepted_eula_version: string | null; + @Column({ nullable: true, type: 'varchar' }) + accepted_privacy_notice_version: string | null; + @Column({ nullable: true, type: 'varchar' }) accepted_dpa_version: string | null; @@ -112,6 +115,9 @@ export class UserArchive { @Column({ nullable: true, type: 'varchar' }) accepted_eula_version: string | null; + @Column({ nullable: true, type: 'varchar' }) + accepted_privacy_notice_version: string | null; + @Column({ nullable: true, type: 'varchar' }) accepted_dpa_version: string | null; diff --git a/dictation_server/src/repositories/users/users.repository.service.ts b/dictation_server/src/repositories/users/users.repository.service.ts index af179e9..7cfec4d 100644 --- a/dictation_server/src/repositories/users/users.repository.service.ts +++ b/dictation_server/src/repositories/users/users.repository.service.ts @@ -360,7 +360,7 @@ export class UsersRepositoryService { }, where: { account_id: accountId }, }); - + return dbUsers; }); } @@ -471,6 +471,14 @@ export class UsersRepositoryService { id: 'DESC', }, }); + const latestPrivacyNoticeInfo = await termRepo.findOne({ + where: { + document_type: TERM_TYPE.PRIVACY_NOTICE, + }, + order: { + id: 'DESC', + }, + }); const latestDpaInfo = await termRepo.findOne({ where: { document_type: TERM_TYPE.DPA, @@ -479,16 +487,18 @@ export class UsersRepositoryService { id: 'DESC', }, }); - - if (!latestEulaInfo || !latestDpaInfo) { + if (!latestEulaInfo || !latestPrivacyNoticeInfo || !latestDpaInfo) { throw new TermInfoNotFoundError(`Terms info is not found.`); } return { tier: user.account.tier, acceptedEulaVersion: user.accepted_eula_version ?? undefined, + acceptedPrivacyNoticeVersion: + user.accepted_privacy_notice_version ?? undefined, acceptedDpaVersion: user.accepted_dpa_version ?? undefined, latestEulaVersion: latestEulaInfo.version, + latestPrivacyNoticeVersion: latestPrivacyNoticeInfo.version, latestDpaVersion: latestDpaInfo.version, }; }); @@ -498,12 +508,14 @@ export class UsersRepositoryService { * 同意済み利用規約のバージョンを更新する * @param externalId * @param eulaVersion + * @param privacyNoticeVersion * @param dpaVersion * @returns update */ async updateAcceptedTermsVersion( externalId: string, eulaVersion: string, + privacyNoticeVersion: string, dpaVersion: string | undefined, ): Promise { await this.dataSource.transaction(async (entityManager) => { @@ -531,6 +543,11 @@ export class UsersRepositoryService { if (!eulaVersion) { throw new UpdateTermsVersionNotSetError(`EULA version param not set.`); } + if (!privacyNoticeVersion) { + throw new UpdateTermsVersionNotSetError( + `PrivacyNotice version param not set.`, + ); + } if (user.account.tier !== TIERS.TIER5 && !dpaVersion) { throw new UpdateTermsVersionNotSetError( `DPA version param not set. User's tier: ${user.account.tier}`, @@ -538,6 +555,8 @@ export class UsersRepositoryService { } user.accepted_eula_version = eulaVersion; + user.accepted_privacy_notice_version = + privacyNoticeVersion ?? user.accepted_privacy_notice_version; user.accepted_dpa_version = dpaVersion ?? user.accepted_dpa_version; await userRepo.update({ id: user.id }, user); });