diff --git a/api/src/controllers/user.controller.ts b/api/src/controllers/user.controller.ts index 671e977f3b..df59e869c4 100644 --- a/api/src/controllers/user.controller.ts +++ b/api/src/controllers/user.controller.ts @@ -309,6 +309,19 @@ export class UserController { return await this.userService.isUserConfirmationTokenValid(dto); } + @Post('is-reset-token-valid') + @ApiOperation({ + summary: 'Verifies token is valid', + operationId: 'isUserResetTokenValid', + }) + @ApiOkResponse({ type: SuccessDTO }) + @UseGuards(OptionalAuthGuard, PermissionGuard) + async isUserResetTokenValid( + @Body() dto: ConfirmationRequest, + ): Promise { + return await this.userService.isUserResetTokenValid(dto); + } + @Post('advocate-from-token') @ApiOperation({ summary: 'Get advocate user from confirmation token', diff --git a/api/src/services/auth.service.ts b/api/src/services/auth.service.ts index 6df1c400d2..0089741d1a 100644 --- a/api/src/services/auth.service.ts +++ b/api/src/services/auth.service.ts @@ -1,7 +1,6 @@ import { BadRequestException, Injectable, - NotFoundException, UnauthorizedException, } from '@nestjs/common'; import { CookieOptions, Response } from 'express'; @@ -328,17 +327,23 @@ export class AuthService { }); if (!user) { - throw new NotFoundException( + console.error( `user resetToken: ${dto.token} was requested but not found`, ); + throw new UnauthorizedException('tokenMissing'); } - const token: IdDTO = verify(dto.token, process.env.APP_SECRET) as IdDTO; - + let token: IdDTO = { id: undefined }; + try { + token = verify(dto.token, process.env.APP_SECRET) as IdDTO; + } catch (e) { + throw new UnauthorizedException('tokenExpired'); + } if (token.id !== user.id) { - throw new UnauthorizedException( + console.error( `resetToken ${dto.token} does not match user ${user.id}'s reset token (${user.resetToken})`, ); + throw new UnauthorizedException('tokenExpired'); } await this.snapshotCreateService.createUserSnapshot(user.id); await this.prisma.userAccounts.update({ diff --git a/api/src/services/user.service.ts b/api/src/services/user.service.ts index cc51f0b0a0..ef7aa7aafe 100644 --- a/api/src/services/user.service.ts +++ b/api/src/services/user.service.ts @@ -663,6 +663,52 @@ export class UserService { } } + /* + checks to see if the reset token is valid + */ + async isUserResetTokenValid(dto: ConfirmationRequest): Promise { + try { + const token = verify(dto.token, process.env.APP_SECRET) as IdDTO; + + const storedUser = await this.prisma.userAccounts.findUnique({ + where: { + id: token.id, + }, + }); + this.checkUserAndToken(storedUser.id, dto.token, storedUser.resetToken); + + return { + success: true, + } as SuccessDTO; + } catch (_) { + try { + const storedUser = await this.prisma.userAccounts.findFirst({ + where: { + resetToken: dto.token, + }, + }); + this.checkUserAndToken(storedUser.id, dto.token, storedUser.resetToken); + } catch (e) { + console.error('isUserResetTokenValid error = ', e); + } + } + } + + checkUserAndToken( + userId: string, + incomingToken: string, + storedToken: string, + ): void { + if (!userId) { + throw new NotFoundException( + `user reset token ${incomingToken} was requested but not found`, + ); + } + if (!incomingToken || !storedToken || incomingToken !== storedToken) { + throw new BadRequestException('tokenMissing'); + } + } + /* Returns an advocate user's basic info by validating their confirmation token. Used to pre-populate the account completion form on the public site. diff --git a/api/test/unit/services/auth.service.spec.ts b/api/test/unit/services/auth.service.spec.ts index d36b2fea43..ccab1ce7e7 100644 --- a/api/test/unit/services/auth.service.spec.ts +++ b/api/test/unit/services/auth.service.spec.ts @@ -1062,9 +1062,7 @@ describe('Testing auth service', () => { }, response as unknown as Response, ), - ).rejects.toThrowError( - `resetToken ${token} does not match user ${secondId}'s reset token (${secondToken})`, - ); + ).rejects.toThrowError(`tokenExpired`); expect(prisma.userAccounts.update).not.toHaveBeenCalled(); }); diff --git a/infra/tofu_importable_modules/bloom_deployment/ecs_api_service.tf b/infra/tofu_importable_modules/bloom_deployment/ecs_api_service.tf index 5c0ed8997b..e281274cae 100644 --- a/infra/tofu_importable_modules/bloom_deployment/ecs_api_service.tf +++ b/infra/tofu_importable_modules/bloom_deployment/ecs_api_service.tf @@ -14,6 +14,7 @@ locals { S3_PRIVATE_BUCKET = aws_s3_bucket.private.id S3_PUBLIC_BUCKET = aws_s3_bucket.public.id OTEL_EXPORTER_OTLP_ENDPOINT = "http://127.0.0.1:4317" + USE_WINSTON = "TRUE" } } resource "aws_ecs_task_definition" "bloom_api" { diff --git a/shared-helpers/src/locales/ar.json b/shared-helpers/src/locales/ar.json index 66ed762af5..30a824fa82 100644 --- a/shared-helpers/src/locales/ar.json +++ b/shared-helpers/src/locales/ar.json @@ -609,8 +609,8 @@ "authentication.forgotPassword.enterNewLoginPassword": "الرجاء إدخال كلمة المرور الجديدة لتسجيل الدخول", "authentication.forgotPassword.errors.emailNotFound": "البريد الإلكتروني غير موجود. يرجى التأكد من أن بريدك الإلكتروني لديه حساب معنا وأنه تم تأكيده.", "authentication.forgotPassword.errors.passwordTooWeak": "كلمة المرور ضعيفة جدًا. يجب ألا تقل عن ١٢ حرفًا، وأن تتضمن على الأقل حرفًا صغيرًا واحدًا، وحرفًا كبيرًا واحدًا، ورقمًا واحدًا، ورمزًا خاصًا واحدًا (#?!@$%^&*-).", - "authentication.forgotPassword.errors.tokenExpired": "انتهت صلاحية رمز إعادة تعيين كلمة المرور. الرجاء طلب واحدة جديدة.", - "authentication.forgotPassword.errors.tokenMissing": "لم يتم العثور على الرمز. الرجاء طلب واحدة جديدة.", + "authentication.forgotPassword.errors.tokenExpired": "لقد انتهت صلاحية رابط إعادة تعيين كلمة المرور. يرجى طلب رابط جديد.", + "authentication.forgotPassword.errors.tokenMissing": "رابط إعادة تعيين كلمة المرور غير صالح. يرجى طلب رابط جديد.", "authentication.forgotPassword.message": "إذا كان هناك حساب تم إنشاؤه باستخدام هذا البريد الإلكتروني ، فستتلقى بريدًا إلكترونيًا به ارتباط لإعادة تعيين كلمة المرور الخاصة بك.", "authentication.forgotPassword.passwordConfirmation": "تأكيد كلمة المرور", "authentication.forgotPassword.sendEmail": "أدخل بريدك الإلكتروني للحصول على رابط إعادة تعيين كلمة المرور", diff --git a/shared-helpers/src/locales/bn.json b/shared-helpers/src/locales/bn.json index 537f13bd54..cb48d04ac6 100644 --- a/shared-helpers/src/locales/bn.json +++ b/shared-helpers/src/locales/bn.json @@ -609,8 +609,8 @@ "authentication.forgotPassword.enterNewLoginPassword": "অনুগ্রহ করে নতুন লগইন পাসওয়ার্ড লিখুন।", "authentication.forgotPassword.errors.emailNotFound": "ইমেল পাওয়া যায়নি। অনুগ্রহ করে নিশ্চিত করুন যে আপনার ইমেলের আমাদের সাথে একটি অ্যাকাউন্ট আছে এবং নিশ্চিত করা হয়েছে।", "authentication.forgotPassword.errors.passwordTooWeak": "পাসওয়ার্ডটি খুবই দুর্বল। কমপক্ষে ১২টি অক্ষরের হতে হবে এবং কমপক্ষে একটি ছোট হাতের অক্ষর, একটি বড় হাতের অক্ষর, একটি সংখ্যা এবং একটি বিশেষ অক্ষর (#?!@$%^&*-) অন্তর্ভুক্ত থাকতে হবে।", - "authentication.forgotPassword.errors.tokenExpired": "পাসওয়ার্ড টোকেনের মেয়াদ শেষ হয়ে গেছে। অনুগ্রহ করে নতুনের জন্য অনুরোধ করুন।", - "authentication.forgotPassword.errors.tokenMissing": "টোকেন পাওয়া যায়নি। অনুগ্রহ করে নতুনের জন্য অনুরোধ করুন।", + "authentication.forgotPassword.errors.tokenExpired": "পাসওয়ার্ড রিসেট করার লিঙ্কের মেয়াদ শেষ হয়ে গেছে। অনুগ্রহ করে নতুন একটি লিঙ্কের জন্য অনুরোধ করুন।", + "authentication.forgotPassword.errors.tokenMissing": "পাসওয়ার্ড রিসেট করার লিঙ্কটি ত্রুটিপূর্ণ। অনুগ্রহ করে নতুন একটি লিঙ্ক অনুরোধ করুন।", "authentication.forgotPassword.message": "যদি সেই ইমেল দিয়ে একটি অ্যাকাউন্ট তৈরি করা হয়, তাহলে আপনি আপনার পাসওয়ার্ড রিসেট করার জন্য একটি লিঙ্ক সহ একটি ইমেল পাবেন।", "authentication.forgotPassword.passwordConfirmation": "পাসওয়ার্ড নিশ্চিতকরণ", "authentication.forgotPassword.sendEmail": "পাসওয়ার্ড রিসেট লিঙ্ক পেতে আপনার ইমেল ঠিকানা লিখুন", diff --git a/shared-helpers/src/locales/es.json b/shared-helpers/src/locales/es.json index 1c3702cefc..33acf84a85 100644 --- a/shared-helpers/src/locales/es.json +++ b/shared-helpers/src/locales/es.json @@ -609,8 +609,8 @@ "authentication.forgotPassword.enterNewLoginPassword": "Por favor ingrese una nueva contraseña de inicio de sesión", "authentication.forgotPassword.errors.emailNotFound": "El correo electrónico no fue encontrado. Por favor, revise que si ha creado una cuenta con nosotros con ese correo electrónico y haya sido confirmado confirmado.", "authentication.forgotPassword.errors.passwordTooWeak": "La contraseña es demasiado débil. Debe tener al menos 12 caracteres e incluir al menos una letra minúscula, una letra mayúscula, un número y un carácter especial (#?!@$%^&*-).", - "authentication.forgotPassword.errors.tokenExpired": "El token de restablecimiento de contraseña caducó. Por favor, solicite uno nuevo.", - "authentication.forgotPassword.errors.tokenMissing": "El token no fue encontrado. Por favor, solicite uno nuevo.", + "authentication.forgotPassword.errors.tokenExpired": "El enlace para restablecer la contraseña ha caducado. Por favor, solicite uno nuevo.", + "authentication.forgotPassword.errors.tokenMissing": "El enlace para restablecer la contraseña no es válido. Por favor, solicite uno nuevo.", "authentication.forgotPassword.message": "Si hay una cuenta creada con ese correo electrónico, recibirás un correo electrónico con un enlace para restablecer tu contraseña. El enlace de reinicio es válido por 1 hora.", "authentication.forgotPassword.passwordConfirmation": "Confirmación de contraseña", "authentication.forgotPassword.sendEmail": "Ingrese su correo electrónico para obtener un enlace de restablecimiento de contraseña", diff --git a/shared-helpers/src/locales/fa.json b/shared-helpers/src/locales/fa.json index 6882d5d976..1a25e8ea7b 100644 --- a/shared-helpers/src/locales/fa.json +++ b/shared-helpers/src/locales/fa.json @@ -606,8 +606,8 @@ "authentication.forgotPassword.enterNewLoginPassword": "لطفا رمز عبور جدید ورود را وارد کنید", "authentication.forgotPassword.errors.emailNotFound": "ایمیل پیدا نشد. لطفاً مطمئن شوید که ایمیل شما دارای حساب کاربری در ما است و تأیید شده است.", "authentication.forgotPassword.errors.passwordTooWeak": "رمز عبور خیلی ضعیف است. باید حداقل ۱۲ کاراکتر داشته باشد و شامل حداقل یک حرف کوچک، یک حرف بزرگ، یک عدد و یک کاراکتر ویژه (#?!@$%^&*-) باشد.", - "authentication.forgotPassword.errors.tokenExpired": "رمز عبور جدید منقضی شده است. لطفا رمز عبور جدید درخواست کنید.", - "authentication.forgotPassword.errors.tokenMissing": "توکن پیدا نشد. لطفا درخواست توکن جدید بدهید.", + "authentication.forgotPassword.errors.tokenExpired": "لینک بازیابی رمز عبور منقضی شده است. لطفا درخواست رمز جدید دهید.", + "authentication.forgotPassword.errors.tokenMissing": "لینک بازیابی رمز عبور ناقص است. لطفا درخواست رمز جدید دهید.", "authentication.forgotPassword.message": "اگر با آن ایمیل حساب کاربری ساخته شده باشد، ایمیلی حاوی لینکی برای تنظیم مجدد رمز عبور دریافت خواهید کرد. لینک تنظیم مجدد به مدت ۱ ساعت معتبر است.", "authentication.forgotPassword.passwordConfirmation": "تأیید رمز عبور", "authentication.forgotPassword.sendEmail": "ایمیل خود را وارد کنید تا لینک بازنشانی رمز عبور را دریافت کنید", diff --git a/shared-helpers/src/locales/general.json b/shared-helpers/src/locales/general.json index 93e03ee571..27fe0f4b6f 100644 --- a/shared-helpers/src/locales/general.json +++ b/shared-helpers/src/locales/general.json @@ -605,8 +605,8 @@ "authentication.forgotPassword.enterNewLoginPassword": "Please enter new login password", "authentication.forgotPassword.errors.emailNotFound": "Email not found. Please make sure your email has an account with us and is confirmed.", "authentication.forgotPassword.errors.passwordTooWeak": "Password is too weak. Must be at least 12 characters and include at least one lowercase letter, one uppercase letter, one number, and one special character (#?!@$%^&*-).", - "authentication.forgotPassword.errors.tokenExpired": "Reset password token expired. Please request for a new one.", - "authentication.forgotPassword.errors.tokenMissing": "Token not found. Please request for a new one.", + "authentication.forgotPassword.errors.tokenExpired": "Reset password link expired. Please request a new one.", + "authentication.forgotPassword.errors.tokenMissing": "Reset password link is malformed. Please request a new one.", "authentication.forgotPassword.message": "If there is an account made with that email, you'll receive an email with a link to reset your password. The reset link is valid for 1 hour.", "authentication.forgotPassword.passwordConfirmation": "Password confirmation", "authentication.forgotPassword.sendEmail": "Enter your email to get a password reset link", diff --git a/shared-helpers/src/locales/hy.json b/shared-helpers/src/locales/hy.json index 339947211f..c3e6762c23 100644 --- a/shared-helpers/src/locales/hy.json +++ b/shared-helpers/src/locales/hy.json @@ -606,8 +606,8 @@ "authentication.forgotPassword.enterNewLoginPassword": "Խնդրում ենք մուտքագրել նոր մուտքի գաղտնաբառը", "authentication.forgotPassword.errors.emailNotFound": "Էլ․ հասցեն չի գտնվել։ Համոզվեք, որ ձեր էլ․ հասցեն մեզ մոտ հաշիվ ունի և հաստատված է։", "authentication.forgotPassword.errors.passwordTooWeak": "Գաղտնաբառը չափազանց թույլ է։ Պետք է լինի առնվազն 12 նիշ և պարունակի առնվազն մեկ փոքրատառ, մեկ մեծատառ, մեկ թիվ և մեկ հատուկ նիշ (#?!@$%^&*-):", - "authentication.forgotPassword.errors.tokenExpired": "Գաղտնաբառի վերակայման տոկենի ժամկետը լրացել է։ Խնդրում ենք նորը խնդրել։", - "authentication.forgotPassword.errors.tokenMissing": "Թոքենը չի գտնվել։ Խնդրում ենք նորը խնդրել։", + "authentication.forgotPassword.errors.tokenExpired": "Գաղտնաբառի վերականգնման հղումը ժամկետանց է։ Խնդրում ենք նորը խնդրել։", + "authentication.forgotPassword.errors.tokenMissing": "Գաղտնաբառի վերակայման հղումը սխալ է։ Խնդրում ենք նորը խնդրել։", "authentication.forgotPassword.message": "Եթե այդ էլ․ հասցեով հաշիվ է ստեղծվել, դուք կստանաք էլ․ նամակ՝ ձեր գաղտնաբառը վերականգնելու հղումով։ Վերականգնման հղումը վավեր է 1 ժամ։", "authentication.forgotPassword.passwordConfirmation": "Գաղտնաբառի հաստատում", "authentication.forgotPassword.sendEmail": "Մուտքագրեք ձեր էլ․ հասցեն՝ գաղտնաբառի վերականգնման հղումը ստանալու համար", diff --git a/shared-helpers/src/locales/ko.json b/shared-helpers/src/locales/ko.json index a7dd78056a..76c650b104 100644 --- a/shared-helpers/src/locales/ko.json +++ b/shared-helpers/src/locales/ko.json @@ -606,8 +606,8 @@ "authentication.forgotPassword.enterNewLoginPassword": "새 로그인 비밀번호를 입력하세요.", "authentication.forgotPassword.errors.emailNotFound": "이메일을 찾을 수 없습니다. 귀하의 이메일 주소가 당사에 등록된 계정이며 인증되었는지 확인해 주십시오.", "authentication.forgotPassword.errors.passwordTooWeak": "비밀번호가 너무 취약합니다. 비밀번호는 최소 12자 이상이어야 하며, 소문자, 대문자, 숫자, 특수 문자(#?!@$%^&*-)를 각각 하나씩 포함해야 합니다.", - "authentication.forgotPassword.errors.tokenExpired": "비밀번호 재설정 토큰이 만료되었습니다. 새 토큰을 요청해 주세요.", - "authentication.forgotPassword.errors.tokenMissing": "토큰을 찾을 수 없습니다. 새 토큰을 요청해 주세요.", + "authentication.forgotPassword.errors.tokenExpired": "비밀번호 재설정 링크가 만료되었습니다. 새 링크를 요청해 주세요.", + "authentication.forgotPassword.errors.tokenMissing": "비밀번호 재설정 링크 형식이 올바르지 않습니다. 새 링크를 요청해 주세요.", "authentication.forgotPassword.message": "해당 이메일 주소로 계정이 있는 경우, 비밀번호 재설정 링크가 포함된 이메일을 받게 됩니다. 재설정 링크는 1시간 동안 유효합니다.", "authentication.forgotPassword.passwordConfirmation": "비밀번호 확인", "authentication.forgotPassword.sendEmail": "이메일 주소를 입력하시면 비밀번호 재설정 링크를 받으실 수 있습니다.", diff --git a/shared-helpers/src/locales/tl.json b/shared-helpers/src/locales/tl.json index 6d5fcadba2..1cb80040e3 100644 --- a/shared-helpers/src/locales/tl.json +++ b/shared-helpers/src/locales/tl.json @@ -609,8 +609,8 @@ "authentication.forgotPassword.enterNewLoginPassword": "Mangyaring magpasok ng bagong password sa pag-login", "authentication.forgotPassword.errors.emailNotFound": "Hindi nahanap ang email. Pakitiyak na ang iyong email ay may account sa amin at nakumpirma.", "authentication.forgotPassword.errors.passwordTooWeak": "Masyadong mahina ang password. Dapat ay hindi bababa sa 12 character at may kasamang hindi bababa sa isang maliit na titik, isang malaking titik, isang numero, at isang espesyal na character (#?!@$%^&*-).", - "authentication.forgotPassword.errors.tokenExpired": "Nag-expire na ang token ng pag-reset ng password. Humiling ng bago.", - "authentication.forgotPassword.errors.tokenMissing": "Hindi nahanap ang token. Humiling ng bago.", + "authentication.forgotPassword.errors.tokenExpired": "Nag-expire ang link sa pag-reset ng password. Mangyaring humiling ng bago.", + "authentication.forgotPassword.errors.tokenMissing": "Mali ang pagkakabuo ng link sa pag-reset ng password. Mangyaring humiling ng bago.", "authentication.forgotPassword.message": "Kung may account na ginawa gamit ang email na iyon, makakatanggap ka ng email na may link para i-reset ang iyong password. Ang link sa pag-reset ay may bisa sa loob ng 1 oras.", "authentication.forgotPassword.passwordConfirmation": "Pagkumpirma ng password", "authentication.forgotPassword.sendEmail": "Ilagay ang iyong email para makakuha ng link sa pag-reset ng password", diff --git a/shared-helpers/src/locales/vi.json b/shared-helpers/src/locales/vi.json index c5760bc94b..f60e3f4157 100644 --- a/shared-helpers/src/locales/vi.json +++ b/shared-helpers/src/locales/vi.json @@ -609,8 +609,8 @@ "authentication.forgotPassword.enterNewLoginPassword": "Vui lòng nhập mật khẩu đăng nhập mới", "authentication.forgotPassword.errors.emailNotFound": "Không tìm thấy email. Vui lòng đảm bảo email của quý vị có tài khoản với chúng tôi và đã được xác nhận.", "authentication.forgotPassword.errors.passwordTooWeak": "Mật khẩu quá yếu. Phải có ít nhất 12 ký tự và bao gồm ít nhất một chữ cái viết thường, một chữ cái viết hoa, một số và một ký tự đặc biệt (#?!@$%^&*-).", - "authentication.forgotPassword.errors.tokenExpired": "Mã thông báo đặt lại mật khẩu đã hết hạn. Vui lòng yêu cầu mã mới.", - "authentication.forgotPassword.errors.tokenMissing": "Không tìm thấy mã thông báo. Vui lòng yêu cầu mã mới.", + "authentication.forgotPassword.errors.tokenExpired": "Liên kết đặt lại mật khẩu đã hết hạn. Vui lòng yêu cầu một liên kết mới.", + "authentication.forgotPassword.errors.tokenMissing": "Liên kết đặt lại mật khẩu không hợp lệ. Vui lòng yêu cầu một liên kết mới.", "authentication.forgotPassword.message": "Nếu có tài khoản được tạo bằng email đó, bạn sẽ nhận được email có liên kết để đặt lại mật khẩu của mình. Liên kết đặt lại có hiệu lực trong 1 giờ.", "authentication.forgotPassword.passwordConfirmation": "Xác nhận mật khẩu", "authentication.forgotPassword.sendEmail": "Nhập email của bạn để nhận liên kết đặt lại mật khẩu", diff --git a/shared-helpers/src/locales/zh.json b/shared-helpers/src/locales/zh.json index 7cc4ff963a..a1be3139ad 100644 --- a/shared-helpers/src/locales/zh.json +++ b/shared-helpers/src/locales/zh.json @@ -609,8 +609,8 @@ "authentication.forgotPassword.enterNewLoginPassword": "请输入新的登录密码", "authentication.forgotPassword.errors.emailNotFound": "找不到電子郵件。請確保您以此電子郵件向我們註冊帳戶並確認完畢。", "authentication.forgotPassword.errors.passwordTooWeak": "密碼強度太弱。必須至少 12 個字符,並且至少包含 1 個小寫字母、1 個大寫字母、1 個數字和 1 個特殊字符 (#?!@$%^&*-)。", - "authentication.forgotPassword.errors.tokenExpired": "重設密碼權杖已到期。請要求新的權杖。", - "authentication.forgotPassword.errors.tokenMissing": "找不到權杖。請要求新的權杖。", + "authentication.forgotPassword.errors.tokenExpired": "密碼重置連結已過期。請重新申請。", + "authentication.forgotPassword.errors.tokenMissing": "密碼重置連結格式有誤。請重新申請。", "authentication.forgotPassword.message": "如果使用该电子邮件创建了帐户,您将收到一封包含重置密码链接的电子邮件。 重置链接有效期为1小时。", "authentication.forgotPassword.passwordConfirmation": "密码确认", "authentication.forgotPassword.sendEmail": "輸入您的電子郵件地址以獲取密碼重設鏈接", diff --git a/shared-helpers/src/types/backend-swagger.ts b/shared-helpers/src/types/backend-swagger.ts index 7341f19f63..a6eb569640 100644 --- a/shared-helpers/src/types/backend-swagger.ts +++ b/shared-helpers/src/types/backend-swagger.ts @@ -2286,6 +2286,28 @@ export class UserService { axios(configs, resolve, reject) }) } + /** + * Verifies token is valid + */ + isUserResetTokenValid( + params: { + /** requestBody */ + body?: ConfirmationRequest + } = {} as any, + options: IRequestOptions = {} + ): Promise { + return new Promise((resolve, reject) => { + let url = basePath + "/user/is-reset-token-valid" + + const configs: IRequestConfig = getConfigs("post", "application/json", url, options) + + let data = params.body + + configs.data = data + + axios(configs, resolve, reject) + }) + } /** * Get advocate user from confirmation token */ @@ -8419,6 +8441,9 @@ export interface PublicAppsViewResponse { applicationsCount: PublicAppsCount } +/** StreamableFile */ +export interface StreamableFile {} + /** ApplicationSelectionOptionCreate */ export interface ApplicationSelectionOptionCreate { /** */ diff --git a/sites/partners/__tests__/pages/reset-password.test.tsx b/sites/partners/__tests__/pages/reset-password.test.tsx index 4b1704e55e..f7a51a5fab 100644 --- a/sites/partners/__tests__/pages/reset-password.test.tsx +++ b/sites/partners/__tests__/pages/reset-password.test.tsx @@ -54,9 +54,11 @@ describe("reset-password", () => { ) ).not.toBeInTheDocument() expect( - queryByText("Reset password token expired. Please request for a new one.") + queryByText("Reset password link expired. Please request a new one.") + ).not.toBeInTheDocument() + expect( + queryByText("Reset password link is malformed. Please request a new one.") ).not.toBeInTheDocument() - expect(queryByText("Token not found. Please request for a new one.")).not.toBeInTheDocument() }) it("should show only password input error", async () => { @@ -193,11 +195,11 @@ describe("reset-password", () => { }, expiredToken: { code: "tokenExpired", - message: "Reset password token expired. Please request for a new one.", + message: "Reset password link expired. Please request a new one.", }, missingToken: { code: "tokenMissing", - message: "Token not found. Please request for a new one.", + message: "Reset password link is malformed. Please request a new one.", }, } diff --git a/sites/partners/src/pages/reset-password.tsx b/sites/partners/src/pages/reset-password.tsx index 1ae71a9d62..0e5a5b3257 100644 --- a/sites/partners/src/pages/reset-password.tsx +++ b/sites/partners/src/pages/reset-password.tsx @@ -34,7 +34,7 @@ const ResetPassword = () => { } catch (err) { setIsLoading(false) const { status, data } = err.response || {} - if (status === 400) { + if (status === 400 || status === 401) { setRequestError(`${t(`authentication.forgotPassword.errors.${data.message}`)}`) } else { console.error(err) diff --git a/sites/public/__tests__/pages/reset-password.test.tsx b/sites/public/__tests__/pages/reset-password.test.tsx index c88e513891..12bf5db64f 100644 --- a/sites/public/__tests__/pages/reset-password.test.tsx +++ b/sites/public/__tests__/pages/reset-password.test.tsx @@ -92,15 +92,10 @@ describe("Public Reset Password Page", () => { value: "Password is too weak. Must be at least 12 characters and include at least one lowercase letter, one uppercase letter, one number, and one special character (#?!@$%^&*-).", }, - { - title: "Expired Token", - message: "tokenExpired", - value: "Reset password token expired. Please request for a new one.", - }, { title: "Missing Token", message: "tokenMissing", - value: "Token not found. Please request for a new one.", + value: "Reset password link is malformed. Please request a new one.", }, ].map((entry) => Object.assign(entry, { @@ -121,6 +116,14 @@ describe("Public Reset Password Page", () => { message: message, }) ) + }), + rest.put("http://localhost/api/adapter/user/is-reset-token-valid", (_req, res, ctx) => { + return res( + ctx.status(200), + ctx.json({ + message: message, + }) + ) }) ) render() @@ -137,12 +140,48 @@ describe("Public Reset Password Page", () => { expect(await screen.findByText(value)).toBeInTheDocument() }) + it("should show Expired Token error", async () => { + const message = "tokenExpired" + const value = "Reset password link expired. Please request a new one." + + server.use( + rest.put("http://localhost/api/adapter/auth/update-password", (_req, res, ctx) => { + return res( + ctx.status(400), + ctx.json({ + message: message, + }) + ) + }), + rest.put("http://localhost/api/adapter/user/is-reset-token-valid", (_req, res, ctx) => { + return res( + ctx.status(200), + ctx.json({ + message: message, + }) + ) + }) + ) + render() + + const passwordInput = screen.getByLabelText(/^password$/i, { selector: "input" }) + const confirmPasswordInput = screen.getByLabelText(/password confirmation/i, { + selector: "input", + }) + + await userEvent.type(passwordInput, "Password") + await userEvent.type(confirmPasswordInput, "Password") + await userEvent.click(screen.getByRole("button", { name: /change password/i })) + + expect(await screen.findAllByText(value)) + }) + it("should show generic error message", async () => { // Hide the console.error statment in the component submit handler jest.spyOn(console, "error").mockImplementation() server.use( rest.put("http://localhost/api/adapter/auth/update-password", (_req, res, ctx) => { - return res(ctx.status(401)) + return res(ctx.status(500)) }) ) render() diff --git a/sites/public/src/pages/reset-password.tsx b/sites/public/src/pages/reset-password.tsx index 0d0a1350c6..a451219654 100644 --- a/sites/public/src/pages/reset-password.tsx +++ b/sites/public/src/pages/reset-password.tsx @@ -19,7 +19,7 @@ import FormsLayout from "../layouts/forms" const ResetPassword = () => { const router = useRouter() const { token } = router.query - const { resetPassword } = useContext(AuthContext) + const { resetPassword, userService } = useContext(AuthContext) const { addToast } = useContext(MessageContext) /* Form Handler */ // This is causing a linting issue with unbound-method, see open issue as of 10/21/2020: @@ -40,6 +40,34 @@ const ResetPassword = () => { }) }, []) + useEffect(() => { + if (token) { + userService + .isUserResetTokenValid({ body: { token: token as string } }) + .then((res) => { + if (!res) { + addToast( + `${t("authentication.forgotPassword.errors.tokenExpired", { + contactEmail: t("resources.contactEmail"), + })}`, + { variant: "warn" } + ) + void router.push("/forgot-password") + } + }) + .catch(() => { + addToast( + `${t("authentication.forgotPassword.errors.tokenExpired", { + contactEmail: t("resources.contactEmail"), + })}`, + { variant: "warn" } + ) + void router.push("/forgot-password") + }) + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [token]) + const onSubmit = async (data: { password: string; passwordConfirmation: string }) => { setLoading(true) const { password, passwordConfirmation } = data @@ -58,16 +86,20 @@ const ResetPassword = () => { } catch (err) { setLoading(false) const { status, data } = err.response || {} - if (status === 400) { - setRequestError(`${t(`authentication.forgotPassword.errors.${data.message}`)}`) + if (status === 400 || status === 401) { + addToast(`${t(`authentication.forgotPassword.errors.${data.message}`)}`, { + variant: "warn", + }) } else { console.error(err) - setRequestError( + addToast( `${t("account.settings.alerts.genericError", { contactEmail: t("resources.contactEmail"), - })}` + })}`, + { variant: "warn" } ) } + await router.push("/forgot-password") } }