Merged PR 657: ユーザー認証完了通知の実装 [U-113]

## 概要
[Task3322: ユーザー認証完了通知の実装](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3322)

- アカウント認証完了後のパスワード通知メール送信処理を追加しました。
- テストでSendGridのメソッドを`overrideSendgridService`で上書きしている箇所について、個別の送信メソッドは不要なので削除しました。

## レビューポイント
- `overrideSendgridService`の対応は適切でしょうか?

## UIの変更
- なし

## 動作確認状況
- ローカルで確認
This commit is contained in:
makabe.t 2023-12-25 05:59:10 +00:00
parent 9f8ccc436f
commit f6c3f69801
11 changed files with 210 additions and 221 deletions

View File

@ -99,46 +99,6 @@ export const overrideSendgridService = <TService>(
text: string,
html: string,
) => Promise<void>;
sendMailWithU101?: (
context: Context,
customerMail: string,
customerAccountName: string,
) => Promise<void>;
sendMailWithU105?: (
context: Context,
customerMails: string[],
customerAccountName: string,
lisenceCount: number,
poNumber: string,
dealerEmails: string[],
dealerAccountName: string,
) => Promise<void>;
sendMailWithU106?: (
context: Context,
customerMails: string[],
customerAccountName: string,
lisenceCount: number,
poNumber: string,
dealerEmails: string[],
dealerAccountName: string,
) => Promise<void>;
sendMailWithU107?: (
context: Context,
customerMails: string[],
customerAccountName: string,
lisenceCount: number,
poNumber: string,
dealerEmails: string[],
dealerAccountName: string,
) => Promise<void>;
sendMailWithU108?: (
context: Context,
userName: string,
userMail: string,
customerAdminMails: string[],
customerAccountName: string,
dealerAccountName: string,
) => Promise<void>;
},
): void => {
// テストコードでのみ許される強引な方法でprivateメンバ変数の参照を取得
@ -159,73 +119,6 @@ export const overrideSendgridService = <TService>(
writable: true,
});
}
if (overrides.sendMailWithU101) {
Object.defineProperty(obj, obj.sendMailWithU101.name, {
value: overrides.sendMailWithU101,
writable: true,
});
} else {
Object.defineProperty(obj, obj.sendMailWithU101.name, {
value: async () => {
return;
},
writable: true,
});
}
if (overrides.sendMailWithU105) {
Object.defineProperty(obj, obj.sendMailWithU105.name, {
value: overrides.sendMailWithU105,
writable: true,
});
} else {
Object.defineProperty(obj, obj.sendMailWithU105.name, {
value: async () => {
return;
},
writable: true,
});
}
if (overrides.sendMailWithU106) {
Object.defineProperty(obj, obj.sendMailWithU106.name, {
value: overrides.sendMailWithU106,
writable: true,
});
} else {
Object.defineProperty(obj, obj.sendMailWithU106.name, {
value: async () => {
return;
},
writable: true,
});
}
if (overrides.sendMailWithU107) {
Object.defineProperty(obj, obj.sendMailWithU107.name, {
value: overrides.sendMailWithU107,
writable: true,
});
} else {
Object.defineProperty(obj, obj.sendMailWithU107.name, {
value: async () => {
return;
},
writable: true,
});
}
if (overrides.sendMailWithU108) {
Object.defineProperty(obj, obj.sendMailWithU108.name, {
value: overrides.sendMailWithU108,
writable: true,
});
} else {
Object.defineProperty(obj, obj.sendMailWithU108.name, {
value: async () => {
return;
},
writable: true,
});
}
if (overrides.createMailContentFromEmailConfirm) {
Object.defineProperty(obj, obj.createMailContentFromEmailConfirm.name, {
value: overrides.createMailContentFromEmailConfirm,

View File

@ -2273,11 +2273,7 @@ describe('issueLicense', () => {
const module = await makeTestingModule(source);
if (!module) fail();
const service = module.get<AccountsService>(AccountsService);
overrideSendgridService(service, {
sendMailWithU107: async () => {
return;
},
});
overrideSendgridService(service, {});
const now = new Date();
// 親と子アカウントを作成する
@ -2374,11 +2370,7 @@ describe('issueLicense', () => {
const module = await makeTestingModule(source);
if (!module) fail();
const service = module.get<AccountsService>(AccountsService);
overrideSendgridService(service, {
sendMailWithU107: async () => {
return;
},
});
overrideSendgridService(service, {});
const now = new Date();
// 親と子アカウントを作成する
const { id: parentAccountId } = (

View File

@ -652,11 +652,7 @@ describe('ライセンス割り当て', () => {
);
const service = module.get<UsersService>(UsersService);
overrideSendgridService(service, {
sendMailWithU108: async () => {
return;
},
});
overrideSendgridService(service, {});
const expiry_date = new NewAllocatedLicenseExpirationDate();
@ -728,11 +724,7 @@ describe('ライセンス割り当て', () => {
);
const service = module.get<UsersService>(UsersService);
overrideSendgridService(service, {
sendMailWithU108: async () => {
return;
},
});
overrideSendgridService(service, {});
await service.allocateLicense(
makeContext('trackingId', 'requestId'),
@ -810,11 +802,7 @@ describe('ライセンス割り当て', () => {
);
const service = module.get<UsersService>(UsersService);
overrideSendgridService(service, {
sendMailWithU108: async () => {
return;
},
});
overrideSendgridService(service, {});
const expiry_date = new NewAllocatedLicenseExpirationDate();
await service.allocateLicense(
@ -920,11 +908,7 @@ describe('ライセンス割り当て', () => {
);
const service = module.get<UsersService>(UsersService);
overrideSendgridService(service, {
sendMailWithU108: async () => {
return;
},
});
overrideSendgridService(service, {});
await service.allocateLicense(
makeContext('trackingId', 'requestId'),
userId,
@ -989,11 +973,7 @@ describe('ライセンス割り当て', () => {
);
const service = module.get<UsersService>(UsersService);
overrideSendgridService(service, {
sendMailWithU108: async () => {
return;
},
});
overrideSendgridService(service, {});
await service.allocateLicense(
makeContext('trackingId', 'requestId'),
userId,
@ -1058,11 +1038,7 @@ describe('ライセンス割り当て', () => {
);
const service = module.get<UsersService>(UsersService);
overrideSendgridService(service, {
sendMailWithU108: async () => {
return;
},
});
overrideSendgridService(service, {});
await service.allocateLicense(
makeContext('trackingId', 'requestId'),
userId,
@ -1107,11 +1083,7 @@ describe('ライセンス割り当て', () => {
);
const service = module.get<UsersService>(UsersService);
overrideSendgridService(service, {
sendMailWithU108: async () => {
return;
},
});
overrideSendgridService(service, {});
await expect(
service.allocateLicense(
@ -1164,11 +1136,7 @@ describe('ライセンス割り当て', () => {
);
const service = module.get<UsersService>(UsersService);
overrideSendgridService(service, {
sendMailWithU108: async () => {
return;
},
});
overrideSendgridService(service, {});
await expect(
service.allocateLicense(
@ -1246,11 +1214,7 @@ describe('ライセンス割り当て解除', () => {
);
const service = module.get<UsersService>(UsersService);
overrideSendgridService(service, {
sendMailWithU108: async () => {
return;
},
});
overrideSendgridService(service, {});
await service.deallocateLicense(
makeContext('trackingId', 'requestId'),
userId,
@ -1341,11 +1305,7 @@ describe('ライセンス割り当て解除', () => {
);
const service = module.get<UsersService>(UsersService);
overrideSendgridService(service, {
sendMailWithU108: async () => {
return;
},
});
overrideSendgridService(service, {});
await expect(
service.deallocateLicense(makeContext('trackingId', 'requestId'), userId),
).rejects.toEqual(
@ -1402,11 +1362,7 @@ describe('ライセンス注文キャンセル', () => {
);
const service = module.get<LicensesService>(LicensesService);
overrideSendgridService(service, {
sendMailWithU106: async () => {
return;
},
});
overrideSendgridService(service, {});
await service.cancelOrder(
makeContext('trackingId', 'requestId'),
tier2Accounts[0].users[0].external_id,
@ -1442,11 +1398,7 @@ describe('ライセンス注文キャンセル', () => {
);
const service = module.get<LicensesService>(LicensesService);
overrideSendgridService(service, {
sendMailWithU106: async () => {
return;
},
});
overrideSendgridService(service, {});
await expect(
service.cancelOrder(
makeContext('trackingId', 'requestId'),
@ -1478,11 +1430,7 @@ describe('ライセンス注文キャンセル', () => {
);
const service = module.get<LicensesService>(LicensesService);
overrideSendgridService(service, {
sendMailWithU106: async () => {
return;
},
});
overrideSendgridService(service, {});
await expect(
service.cancelOrder(
makeContext('trackingId', 'requestId'),

View File

@ -57,6 +57,7 @@ export type SendGridMockValue = {
| { subject: string; text: string; html: string }
| Error;
sendMail: undefined | Error;
sendMailWithU113: undefined | Error;
};
export type ConfigMockValue = {
@ -296,6 +297,7 @@ export const makeDefaultSendGridlValue = (): SendGridMockValue => {
text: 'test',
html: 'test',
},
sendMailWithU113: undefined,
};
};

View File

@ -94,11 +94,7 @@ describe('UsersService.confirmUser', () => {
});
const service = module.get<UsersService>(UsersService);
overrideSendgridService(service, {
sendMailWithU101: async () => {
return;
},
});
overrideSendgridService(service, {});
// account id:1, user id: 2のトークン
const token =
@ -147,11 +143,7 @@ describe('UsersService.confirmUser', () => {
if (!module) fail();
const token = 'invalid.id.token';
const service = module.get<UsersService>(UsersService);
overrideSendgridService(service, {
sendMailWithU101: async () => {
return;
},
});
overrideSendgridService(service, {});
const context = makeContext(`uuidv4`, 'requestId');
await expect(service.confirmUser(context, token)).rejects.toEqual(
new HttpException(makeErrorResponse('E000101'), HttpStatus.BAD_REQUEST),
@ -186,11 +178,7 @@ describe('UsersService.confirmUser', () => {
email_verified: false,
});
const service = module.get<UsersService>(UsersService);
overrideSendgridService(service, {
sendMailWithU101: async () => {
return;
},
});
overrideSendgridService(service, {});
const token =
'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY2NvdW50SWQiOjEsInVzZXJJZCI6MiwiZW1haWwiOiJ4eHhAeHh4Lnh4eCIsImlhdCI6MTAwMDAwMDAwMCwiZXhwIjo5MDAwMDAwMDAwfQ.26L6BdNg-3TbyKT62PswlJ6RPMkcTtHzlDXW2Uo9XbMPVSrl2ObcuS6EcXjFFN2DEfNTKbqX_zevIWMpHOAdLNgGhk528nLrBrNvPASqtTjvW9muxMXpjUdjRVkmVbOylBHWW3YpWL9JEbJQ7rAzWDfaIdPhMovdaxumnZt_UwnlnrdaVPLACW7tkH_laEcAU507iSiM4mqxxG8FuTs34t6PEdwRuzZAQPN2IOPYNSvGNdJYryPacSeSNZ_z1xeBYXLOLQfOBZzyTReYDOhXdikhrNUbxjgnZQlSXBCVMlZ9PH42bHfp-LJIeJzW0yqnF6oLklvJP-fo8eW0k5iDOw';
const context = makeContext(`uuidv4`, 'requestId');
@ -203,11 +191,7 @@ describe('UsersService.confirmUser', () => {
const module = await makeTestingModule(source);
if (!module) fail();
const service = module.get<UsersService>(UsersService);
overrideSendgridService(service, {
sendMailWithU101: async () => {
return;
},
});
overrideSendgridService(service, {});
const token =
'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY2NvdW50SWQiOjEsInVzZXJJZCI6MiwiZW1haWwiOiJ4eHhAeHh4Lnh4eCIsImlhdCI6MTAwMDAwMDAwMCwiZXhwIjo5MDAwMDAwMDAwfQ.26L6BdNg-3TbyKT62PswlJ6RPMkcTtHzlDXW2Uo9XbMPVSrl2ObcuS6EcXjFFN2DEfNTKbqX_zevIWMpHOAdLNgGhk528nLrBrNvPASqtTjvW9muxMXpjUdjRVkmVbOylBHWW3YpWL9JEbJQ7rAzWDfaIdPhMovdaxumnZt_UwnlnrdaVPLACW7tkH_laEcAU507iSiM4mqxxG8FuTs34t6PEdwRuzZAQPN2IOPYNSvGNdJYryPacSeSNZ_z1xeBYXLOLQfOBZzyTReYDOhXdikhrNUbxjgnZQlSXBCVMlZ9PH42bHfp-LJIeJzW0yqnF6oLklvJP-fo8eW0k5iDOw';
const context = makeContext(`uuidv4`, 'requestId');

View File

@ -515,7 +515,7 @@ export class UsersService {
// ランダムなパスワードを生成する
const ramdomPassword = makePassword();
const { userId, email } = decodedToken;
const { accountId, userId, email } = decodedToken;
try {
// ユーザー情報からAzure AD B2CのIDを特定する
@ -529,21 +529,30 @@ export class UsersService {
);
// ユーザを認証済みにする
await this.usersRepository.updateUserVerified(context, userId);
// TODO [Task2163] ODMS側が正式にメッセージを決めるまで仮のメール内容とする
const subject = 'A temporary password has been issued.';
const text = 'temporary password: ' + ramdomPassword;
const html = `<p>OMDS TOP PAGE URL.<p><a href="${this.appDomain}">${this.appDomain}</a><br>temporary password: ${ramdomPassword}`;
// メールを送信
await this.sendgridService.sendMail(
context,
[email],
[],
this.mailFrom,
subject,
text,
html,
);
// メール送信処理
try {
const { external_id: primaryAdminUserExternalId } =
await this.getPrimaryAdminUser(context, accountId);
const adb2cUser = await this.adB2cService.getUser(
context,
primaryAdminUserExternalId,
);
const { displayName: primaryAdminName } =
getUserNameAndMailAddress(adb2cUser);
await this.sendgridService.sendMailWithU113(
context,
email,
primaryAdminName,
ramdomPassword,
);
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
// メール送信に関する例外はログだけ出して握りつぶす
}
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
if (e instanceof Error) {

View File

@ -19,6 +19,7 @@ import {
USER_NAME,
TYPIST_NAME,
VERIFY_LINK,
TEMPORARY_PASSWORD,
} from '../../templates/constants';
@Injectable()
@ -50,6 +51,8 @@ export class SendGridService {
// U-112のテンプレート差分親アカウントがない場合
private readonly templateU112NoParentHtml: string;
private readonly templateU112NoParentText: string;
private readonly templateU113Html: string;
private readonly templateU113Text: string;
private readonly templateU114Html: string;
private readonly templateU114Text: string;
private readonly templateU115Html: string;
@ -166,7 +169,14 @@ export class SendGridService {
path.resolve(__dirname, `../../templates/template_U_112_no_parent.txt`),
'utf-8',
);
this.templateU113Html = readFileSync(
path.resolve(__dirname, `../../templates/template_U_113.html`),
'utf-8',
);
this.templateU113Text = readFileSync(
path.resolve(__dirname, `../../templates/template_U_113.txt`),
'utf-8',
);
this.templateU114Html = readFileSync(
path.resolve(__dirname, `../../templates/template_U_114.html`),
'utf-8',
@ -776,6 +786,51 @@ export class SendGridService {
}
}
/**
* U-113使
* @param context
* @param userMail
* @param primaryAdminName (primary)
* @param temporaryPassword
* @returns mail with u113
*/
async sendMailWithU113(
context: Context,
userMail: string,
primaryAdminName: string,
temporaryPassword: string,
): Promise<void> {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${this.sendMailWithU113.name}`,
);
try {
const subject = 'Temporary password [U-113]';
// メールの本文を作成する
const html = this.templateU113Html
.replaceAll(PRIMARY_ADMIN_NAME, primaryAdminName)
.replaceAll(TEMPORARY_PASSWORD, temporaryPassword);
const text = this.templateU113Text
.replaceAll(PRIMARY_ADMIN_NAME, primaryAdminName)
.replaceAll(TEMPORARY_PASSWORD, temporaryPassword);
// メールを送信する
this.sendMail(
context,
[userMail],
[],
this.mailFrom,
subject,
text,
html,
);
} finally {
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.sendMailWithU113.name}`,
);
}
}
/**
* U-114使
* @param context

View File

@ -10,3 +10,4 @@ export const VERIFY_LINK = '$VERIFY_LINK$';
export const AUTHOR_NAME = '$AUTHOR_NAME$';
export const FILE_NAME = '$FILE_NAME$';
export const TYPIST_NAME = '$TYPIST_NAME$';
export const TEMPORARY_PASSWORD = '$TEMPORARY_PASSWORD$';

View File

@ -0,0 +1,73 @@
<html>
<head>
<title>Temporary password [U-113]</title>
</head>
<body>
<div>
<h3>&lt;English&gt;</h3>
<p>
Your user registration has been completed. Please login to ODMS Cloud
with the following temporary password. You may continue using your
temporary password; however, we strongly recommend that you change your
password for security reasons. To change your password, click on [Forgot
your password?] link on the ODMS Cloud Sign in screen.
</p>
<p>Temporary password:$TEMPORARY_PASSWORD$</p>
<p>
If you need support regarding ODMS Cloud, please contact
$PRIMARY_ADMIN_NAME$.
</p>
<p>
If you have received this e-mail in error, please delete this e-mail
from your system.<br />
This is an automatically generated e-mail and this mailbox is not
monitored. Please do not reply.
</p>
</div>
<div>
<h3>&lt;Deutsch&gt;</h3>
<p>
Ihre Benutzerregistrierung ist abgeschlossen. Bitte melden Sie sich mit
dem folgenden temporären Passwort bei ODMS Cloud an. Sie können Ihr
temporäres Passwort weiterhin verwenden; Aus Sicherheitsgründen
empfehlen wir Ihnen jedoch dringend, Ihr Passwort zu ändern. Um Ihr
Passwort zu ändern, klicken Sie auf dem ODMS Cloud-Anmeldebildschirm auf
den Link [Kennwort vergessen?].
</p>
<p>Temporäres Passwort:$TEMPORARY_PASSWORD$</p>
<p>
Wenn Sie Unterstützung bezüglich ODMS Cloud benötigen, wenden Sie sich
bitte an $PRIMARY_ADMIN_NAME$.
</p>
<p>
Wenn Sie diese E-Mail fälschlicherweise erhalten haben, löschen Sie
diese E-Mail bitte aus Ihrem System.<br />
Dies ist eine automatisch generierte E-Mail und dieses Postfach wird
nicht überwacht. Bitte nicht antworten.
</p>
</div>
<div>
<h3>&lt;Français&gt;</h3>
<p>
Votre inscription d'utilisateur est terminée. Veuillez vous connecter à
ODMS Cloud avec le mot de passe temporaire suivant. Vous pouvez
continuer à utiliser votre mot de passe temporaire ; cependant, nous
vous recommandons fortement de changer votre mot de passe pour des
raisons de sécurité. Pour modifier votre mot de passe, cliquez sur le
lien [Vous avez oublié votre mot de passe ?] sur l'écran de connexion
ODMS Cloud.
</p>
<p>Temporary password:$TEMPORARY_PASSWORD$</p>
<p>
Si vous avez besoin d'assistance concernant ODMS Cloud, veuillez
contacter $PRIMARY_ADMIN_NAME$.
</p>
<p>
Si vous avez reçu cet e-mail par erreur, veuillez supprimer cet e-mail
de votre système.<br />
Il s'agit d'un e-mail généré automatiquement et cette boîte aux lettres
n'est pas surveillée. Merci de ne pas répondre.
</p>
</div>
</body>
</html>

View File

@ -0,0 +1,32 @@
<English>
Your user registration has been completed. Please login to ODMS Cloud with the following temporary password. You may continue using your temporary password; however, we strongly recommend that you change your password for security reasons. To change your password, click on [Forgot your password?] link on the ODMS Cloud Sign in screen.
Temporary password:$TEMPORARY_PASSWORD$
If you need support regarding ODMS Cloud, please contact $PRIMARY_ADMIN_NAME$.
If you have received this e-mail in error, please delete this e-mail from your system.
This is an automatically generated e-mail and this mailbox is not monitored. Please do not reply.
<Deutsch>
Ihre Benutzerregistrierung ist abgeschlossen. Bitte melden Sie sich mit dem folgenden temporären Passwort bei ODMS Cloud an. Sie können Ihr temporäres Passwort weiterhin verwenden; Aus Sicherheitsgründen empfehlen wir Ihnen jedoch dringend, Ihr Passwort zu ändern. Um Ihr Passwort zu ändern, klicken Sie auf dem ODMS Cloud-Anmeldebildschirm auf den Link [Kennwort vergessen?].
Temporäres Passwort:$TEMPORARY_PASSWORD$
Wenn Sie Unterstützung bezüglich ODMS Cloud benötigen, wenden Sie sich bitte an $PRIMARY_ADMIN_NAME$.
Wenn Sie diese E-Mail fälschlicherweise erhalten haben, löschen Sie diese E-Mail bitte aus Ihrem System.
Dies ist eine automatisch generierte E-Mail und dieses Postfach wird nicht überwacht. Bitte nicht antworten.
<Français>
Votre inscription d'utilisateur est terminée. Veuillez vous connecter à ODMS Cloud avec le mot de passe temporaire suivant. Vous pouvez continuer à utiliser votre mot de passe temporaire ; cependant, nous vous recommandons fortement de changer votre mot de passe pour des raisons de sécurité. Pour modifier votre mot de passe, cliquez sur le lien [Vous avez oublié votre mot de passe ?] sur l'écran de connexion ODMS Cloud.
Temporary password:$TEMPORARY_PASSWORD$
Si vous avez besoin d'assistance concernant ODMS Cloud, veuillez contacter $PRIMARY_ADMIN_NAME$.
Si vous avez reçu cet e-mail par erreur, veuillez supprimer cet e-mail de votre système.
Il s'agit d'un e-mail généré automatiquement et cette boîte aux lettres n'est pas surveillée. Merci de ne pas répondre.

View File

@ -1,4 +1,4 @@
<h3>&lt;English&gt;</h3>
<English>
Your user information has been registered within the ODMS Cloud by your administrator. To complete your user registration, you must verify your email address. Please verify by clicking on the link below. Once the registration is completed, a notification window will appear confirming that your user information has been verified.
@ -9,7 +9,7 @@ If you need support regarding ODMS Cloud, please contact $PRIMARY_ADMIN_NAME$.
If you have received this e-mail in error, please delete this e-mail from your system.
This is an automatically generated e-mail and this mailbox is not monitored. Please do not reply.
<h3>&lt;Deutsch&gt;</h3>
<Deutsch>
Ihre Benutzerinformationen wurden von Ihrem Administrator in der ODMS Cloud registriert. Um Ihre Benutzerregistrierung abzuschließen, müssen Sie Ihre E-Mail-Adresse bestätigen. Bitte überprüfen Sie dies, indem Sie auf den untenstehenden Link klicken. Sobald die Registrierung abgeschlossen ist, erscheint ein Benachrichtigungsfenster, das bestätigt, dass Ihre Benutzerinformationen überprüft wurden.
@ -20,7 +20,7 @@ Wenn Sie Unterstützung bezüglich ODMS Cloud benötigen, wenden Sie sich bitte
Wenn Sie diese E-Mail fälschlicherweise erhalten haben, löschen Sie diese E-Mail bitte aus Ihrem System.
Dies ist eine automatisch generierte E-Mail und dieses Postfach wird nicht überwacht. Bitte nicht antworten.
<h3>&lt;Français&gt;</h3>
<Français>
Vos informations utilisateur ont été enregistrées dans le cloud ODMS par votre administrateur. Pour finaliser votre inscription d'utilisateur, vous devez vérifier votre adresse e-mail. Veuillez vérifier en cliquant sur le lien ci-dessous. Une fois l'inscription terminée, une fenêtre de notification apparaîtra confirmant que vos informations d'utilisateur ont été vérifiées.