diff --git a/signup-service/src/main/java/io/mosip/signup/SignUpServiceApplication.java b/signup-service/src/main/java/io/mosip/signup/SignUpServiceApplication.java index 2bdf1986..4ec15691 100644 --- a/signup-service/src/main/java/io/mosip/signup/SignUpServiceApplication.java +++ b/signup-service/src/main/java/io/mosip/signup/SignUpServiceApplication.java @@ -1,5 +1,7 @@ package io.mosip.signup; +import io.mosip.esignet.core.config.RedisCacheConfig; +import io.mosip.esignet.core.config.SimpleCacheConfig; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cache.annotation.EnableCaching; @@ -10,7 +12,8 @@ @SpringBootApplication(scanBasePackages = "io.mosip.signup.*," + "io.mosip.esignet.core.config.RedisCacheConfig," + "io.mosip.esignet.core.config.SimpleCacheConfig,"+ - "${mosip.auth.adapter.impl.basepackage}") + "${mosip.auth.adapter.impl.basepackage}", + scanBasePackageClasses = {SimpleCacheConfig.class, RedisCacheConfig.class}) public class SignUpServiceApplication { public static void main(String[] args) { diff --git a/signup-service/src/main/java/io/mosip/signup/dto/GenerateChallengeRequest.java b/signup-service/src/main/java/io/mosip/signup/dto/GenerateChallengeRequest.java index 7fa20ca8..62e179d8 100644 --- a/signup-service/src/main/java/io/mosip/signup/dto/GenerateChallengeRequest.java +++ b/signup-service/src/main/java/io/mosip/signup/dto/GenerateChallengeRequest.java @@ -15,5 +15,6 @@ public class GenerateChallengeRequest { private String locale; private boolean regenerate; + @io.mosip.signup.validator.Purpose private Purpose purpose; } diff --git a/signup-service/src/main/java/io/mosip/signup/dto/Identity.java b/signup-service/src/main/java/io/mosip/signup/dto/Identity.java index 2ae16610..eb501fb2 100644 --- a/signup-service/src/main/java/io/mosip/signup/dto/Identity.java +++ b/signup-service/src/main/java/io/mosip/signup/dto/Identity.java @@ -33,4 +33,10 @@ public class Identity implements Serializable { @JsonInclude(JsonInclude.Include.NON_NULL) private List selectedHandles; + + @JsonInclude(JsonInclude.Include.NON_NULL) + private Boolean phoneVerified; + + @JsonInclude(JsonInclude.Include.NON_NULL) + private Long updatedAt; } diff --git a/signup-service/src/main/java/io/mosip/signup/services/CacheUtilService.java b/signup-service/src/main/java/io/mosip/signup/services/CacheUtilService.java index e4f1c08e..e26bb831 100644 --- a/signup-service/src/main/java/io/mosip/signup/services/CacheUtilService.java +++ b/signup-service/src/main/java/io/mosip/signup/services/CacheUtilService.java @@ -16,6 +16,7 @@ @Slf4j @Service public class CacheUtilService { + @Autowired CacheManager cacheManager; @@ -40,9 +41,9 @@ public RegistrationTransaction setRegisteredTransaction(String transactionId, return registrationTransaction; } - @Cacheable(value = SignUpConstants.BLOCKED_IDENTIFIER, key = "#identifierHash") - public String blockIdentifier(String identifierHash) { - return identifierHash; + @Cacheable(value = SignUpConstants.BLOCKED_IDENTIFIER, key = "#key") + public String blockIdentifier(String key, String value) { + return value; } @Cacheable(value = SignUpConstants.KEYSTORE, key = "#key") @@ -71,8 +72,7 @@ public RegistrationTransaction getRegisteredTransaction(String transactionId) { public boolean isIdentifierBlocked(String identifier) { String identifierHash = IdentityProviderUtil.generateB64EncodedHash(IdentityProviderUtil.ALGO_SHA3_256, identifier.toLowerCase(Locale.ROOT)); - String value = cacheManager.getCache(SignUpConstants.BLOCKED_IDENTIFIER).get(identifierHash, String.class); - return value == null ? false : true; + return cacheManager.getCache(SignUpConstants.BLOCKED_IDENTIFIER).get(identifierHash, String.class) != null; } public String getSecretKey(String keyAlias) { diff --git a/signup-service/src/main/java/io/mosip/signup/services/RegistrationService.java b/signup-service/src/main/java/io/mosip/signup/services/RegistrationService.java index 4688cec7..40099678 100644 --- a/signup-service/src/main/java/io/mosip/signup/services/RegistrationService.java +++ b/signup-service/src/main/java/io/mosip/signup/services/RegistrationService.java @@ -31,6 +31,8 @@ import java.nio.charset.StandardCharsets; import java.security.NoSuchAlgorithmException; +import java.time.LocalDateTime; +import java.time.ZoneOffset; import java.util.*; import java.util.stream.Collectors; @@ -157,8 +159,8 @@ public GenerateChallengeResponse generateChallenge(GenerateChallengeRequest gene cacheUtilService.setChallengeGeneratedTransaction(transactionId, transaction); //Resend attempts exhausted, block the identifier for configured time. - if(transaction.getChallengeRetryAttempts() > resendAttempts + 1) - cacheUtilService.blockIdentifier(transaction.getIdentifier()); + if(transaction.getChallengeRetryAttempts() > resendAttempts) + cacheUtilService.blockIdentifier(transaction.getIdentifier(), "blocked"); notificationHelper.sendSMSNotificationAsync(generateChallengeRequest.getIdentifier(), transaction.getLocale(), SEND_OTP_SMS_NOTIFICATION_TEMPLATE_KEY, new HashMap<>(){{put("{challenge}", challenge);}}) @@ -274,6 +276,7 @@ public RegistrationStatusResponse updatePassword(ResetPasswordRequest resetPassw Password password = generateSaltedHash(resetPasswordRequest.getPassword(), transactionId); identity.setPassword(password); + identity.setUpdatedAt(LocalDateTime.now(ZoneOffset.UTC).toEpochSecond(ZoneOffset.UTC)); IdentityRequest identityRequest = new IdentityRequest(); identityRequest.setRegistrationId(transaction.getApplicationId()); @@ -426,6 +429,8 @@ private void saveIdentityData(RegisterRequest registerRequest, String transactio identity.setFullName(userInfoMap.getFullName()); identity.setIDSchemaVersion(idSchemaVersion); identity.setRegistrationType("L1"); + identity.setPhoneVerified(true); + identity.setUpdatedAt(LocalDateTime.now(ZoneOffset.UTC).toEpochSecond(ZoneOffset.UTC)); String uin = getUniqueIdentifier(transactionId); identity.setUIN(uin); diff --git a/signup-service/src/main/java/io/mosip/signup/util/ErrorConstants.java b/signup-service/src/main/java/io/mosip/signup/util/ErrorConstants.java index debb1c06..90167d5c 100644 --- a/signup-service/src/main/java/io/mosip/signup/util/ErrorConstants.java +++ b/signup-service/src/main/java/io/mosip/signup/util/ErrorConstants.java @@ -8,6 +8,7 @@ public class ErrorConstants { public static final String INVALID_PASSWORD = "invalid_password"; public static final String INVALID_USERINFO = "invalid_username"; public static final String INVALID_CONSENT = "invalid_consent"; + public static final String INVALID_PURPOSE ="invalid_purpose"; public static final String IDENTIFIER_MISMATCH = "identifier_mismatch"; public static final String CONSENT_REQUIRED = "consent_required"; public static final String INVALID_TRANSACTION="invalid_transaction"; diff --git a/signup-service/src/main/java/io/mosip/signup/util/SignUpConstants.java b/signup-service/src/main/java/io/mosip/signup/util/SignUpConstants.java index e004350f..0e98a2f8 100644 --- a/signup-service/src/main/java/io/mosip/signup/util/SignUpConstants.java +++ b/signup-service/src/main/java/io/mosip/signup/util/SignUpConstants.java @@ -4,7 +4,7 @@ public class SignUpConstants { public static final String CHALLENGE_GENERATED = "challenge_generated"; public static final String CHALLENGE_VERIFIED = "challenge_verified"; - public static final String REGISTERED_CACHE = "registered"; + public static final String REGISTERED_CACHE = "status_check"; public static final String BLOCKED_IDENTIFIER = "blocked_identifier"; public static final String KEYSTORE = "keystore"; public static final String KEY_ALIAS = "key_alias"; diff --git a/signup-service/src/main/java/io/mosip/signup/validator/Purpose.java b/signup-service/src/main/java/io/mosip/signup/validator/Purpose.java new file mode 100644 index 00000000..80bfe316 --- /dev/null +++ b/signup-service/src/main/java/io/mosip/signup/validator/Purpose.java @@ -0,0 +1,23 @@ +package io.mosip.signup.validator; + +import io.mosip.signup.util.ErrorConstants; + +import javax.validation.Constraint; +import javax.validation.Payload; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.TYPE_USE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +@Target({FIELD, TYPE_USE}) +@Retention(RUNTIME) +@Constraint(validatedBy = PurposeValidator.class) +@Documented +public @interface Purpose { + String message() default ErrorConstants.INVALID_PURPOSE; + Class[] groups() default {}; + Class[] payload() default {}; +} diff --git a/signup-service/src/main/java/io/mosip/signup/validator/PurposeValidator.java b/signup-service/src/main/java/io/mosip/signup/validator/PurposeValidator.java new file mode 100644 index 00000000..496587f0 --- /dev/null +++ b/signup-service/src/main/java/io/mosip/signup/validator/PurposeValidator.java @@ -0,0 +1,14 @@ +package io.mosip.signup.validator; + +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; +import io.mosip.signup.util.Purpose; + +public class PurposeValidator implements ConstraintValidator { + @Override + public boolean isValid(Purpose value, ConstraintValidatorContext context) { + if(value == null) + return false; + return value.equals(Purpose.REGISTRATION) || value.equals(Purpose.RESET_PASSWORD); + } +} diff --git a/signup-service/src/main/resources/application-default.properties b/signup-service/src/main/resources/application-default.properties index 8a44e091..41262f2d 100644 --- a/signup-service/src/main/resources/application-default.properties +++ b/signup-service/src/main/resources/application-default.properties @@ -14,9 +14,10 @@ mosip.signup.fullname.pattern=^[\\u1780-\\u17FF\\u19E0-\\u19FF\\u1A00-\\u1A9F\\u ## Time given to generate and verify the challenge in seconds. ## Default resend delay is 60 seconds, with 3 attempts, so 60*3=180 seconds. -## Adding 10 seconds buffer to default 180 seconds = 190 seconds. -## so 190 seconds is the Generate and verify cookie max age. -mosip.signup.unauthenticated.txn.timeout=190 +## Adding 60 seconds for the default generate challenge 180+60=240 +## Adding 10 seconds buffer to default 240 seconds = 250 seconds. +## so 250 seconds is the Generate and verify cookie max age. +mosip.signup.unauthenticated.txn.timeout=250 mosip.signup.challenge.resend-attempt=3 mosip.signup.challenge.resend-delay=60 diff --git a/signup-service/src/test/java/io/mosip/signup/controllers/RegistrationControllerTest.java b/signup-service/src/test/java/io/mosip/signup/controllers/RegistrationControllerTest.java index ed7641c9..761b7f17 100644 --- a/signup-service/src/test/java/io/mosip/signup/controllers/RegistrationControllerTest.java +++ b/signup-service/src/test/java/io/mosip/signup/controllers/RegistrationControllerTest.java @@ -519,6 +519,23 @@ public void doGenerateChallenge_withRegistrationPurpose_thenPass() throws Except .andExpect(jsonPath("$.errors").isEmpty()); } + @Test + public void doGenerateChallenge_withNullPurpose_returnErrorResponse() throws Exception { + String status = "SUCCESSFUL"; + GenerateChallengeResponse generateChallengeResponse = new GenerateChallengeResponse(status); + generateChallengeRequest.setPurpose(null); + when(registrationService.generateChallenge(generateChallengeRequest, "")) + .thenReturn(generateChallengeResponse); + + mockMvc.perform(post("/registration/generate-challenge") + .content(objectMapper.writeValueAsString(wrapper)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.response").isEmpty()) + .andExpect(jsonPath("$.errors").isNotEmpty()) + .andExpect(jsonPath("$.errors[0].errorCode").value(ErrorConstants.INVALID_PURPOSE)); + } + @Test public void doGenerateChallenge_withResetPasswordPurpose_thenPass() throws Exception { String status = "SUCCESSFUL"; diff --git a/signup-ui/public/locales/en.json b/signup-ui/public/locales/en.json index 2e285463..ef8bc50f 100644 --- a/signup-ui/public/locales/en.json +++ b/signup-ui/public/locales/en.json @@ -62,7 +62,8 @@ "page_under_construction_detail": "Our experts are working hard to make this page available. Meanwhile, we request you to please visit after some time.", "something_went_wrong": "Something went wrong!", "something_went_wrong_detail": "Our experts are working hard to make things working again.", - "attempts_left": "{attemptLeft, plural, =0 {0 of {totalAttempt} attempts left. Please try again after {attemptRetryAfter} minutes.} other {{attemptLeft} of {totalAttempt} attempts left}}", + "attempts_left": "{attemptLeft, plural, =0 {0 of {totalAttempt} attempts left} other {{attemptLeft} of {totalAttempt} attempts left}}", + "attempts_left_and_retry": "{attemptLeft, plural, =0 {0 of {totalAttempt} attempts left. Please try again after {attemptRetryAfter} minutes.} other {{attemptLeft} of {totalAttempt} attempts left}}", "captcha_token_validation": "Please verify that you are a human.", "username_validation": "Enter a valid username", "username_lead_zero_validation": "Number cannot start with zero. Enter a valid mobile number.", @@ -83,7 +84,7 @@ "powered_by": "Powered by" }, "error_response": { - "invalid_transaction": "Invalid Transaction or Transaction Expired", + "invalid_transaction": "The transaction has timed out. Please try again.", "invalid_otp_channel": "Invalid OTP Channel Provided", "invalid_captcha": "Invalid captcha found.", "send_otp_failed": "Send OTP failed", @@ -128,6 +129,8 @@ "fetch_identity_failed": "Fetch Identifier Failed", "challenge_format_and_type_mismatch": "Challenge format and type mismatch", "knowledgebase_mismatch": "Invalid number or name. Please enter a registered mobile number and full name.", + "identifier_blocked": "Identifier blocked for 5 minutes", + "unsupported_purpose": "Invalid Request", "IDR-IDC-001": "Missing Input Parameter", "IDR-IDC-002": "Invalid Input Parameter", "IDR-IDC-003": "Invalid Request", diff --git a/signup-ui/public/locales/km.json b/signup-ui/public/locales/km.json index efac5a54..b1bc3b3a 100644 --- a/signup-ui/public/locales/km.json +++ b/signup-ui/public/locales/km.json @@ -27,7 +27,7 @@ "full_name_tooltip": "ជាអតិបរមា 30 តួអក្សរត្រូវបានអនុញ្ញាត និងមិនគួរមានលេខ ឬតួអក្សរពិសេសណាមួយឡើយ លើកលែងតែដកឃ្លា។", "password": "ពាក្យសម្ងាត់", "password_placeholder": "បញ្ចូលពាក្យសម្ងាត់", - "password_rules": "ប្រើតួអក្សរ 8 ឬច្រើនជាមួយតួអក្សរពិសេស និងយ៉ាងហោចណាស់តួលេខមួយយ៉ាងតិច។", + "password_rules": "ប្រើតួអក្សរ លាយជាមួយលេខ ឱ្យបានចំនួន8តួឬច្រើនជាងនេះ", "confirm_password": "បញ្ជាក់ពាក្យសម្ងាត់", "confirm_password_placeholder": "បញ្ចូលពាក្យសម្ងាត់", "terms_and_condition": "ខ្ញុំយល់ព្រមតាមលក្ខខណ្ឌ និងគោលការណ៍ឯកជនភាពរបស់ប្រទេសកម្ពុជា ដើម្បីរក្សាទុក និងដំណើរការព័ត៌មានរបស់ខ្ញុំតាមតម្រូវការ។", @@ -62,7 +62,8 @@ "page_under_construction_detail": "អ្នកជំនាញកំពុងធ្វើការដើម្បីឱ្យទំព័រនេះអាចប្រើប្រាស់បាន។ សូមចូលម្ដងទៀតនៅពេលក្រោយ។", "something_went_wrong": "មានអ្វីមួយខុសប្រក្រតី!", "something_went_wrong_detail": "អ្នកជំនាញកំពុងធ្វើការដើម្បីឱ្យអ្វីៗដំណើរការឡើងវិញ។", - "attempts_left": "{attemptLeft, plural, =0 {ការព្យាយាមនៅសល់ 0 នៃ {totalAttempt}។ សូមព្យាយាមម្តងទៀតនៅ {attemptRetryAfter} នាទីបន្ទាប់។} other {ការព្យាយាមនៅសល់ {attemptLeft} នៃ {totalAttempt}}}", + "attempts_left": "{attemptLeft, plural, =0 {ការព្យាយាមនៅសល់ 0 នៃ {totalAttempt}} other {ការព្យាយាមនៅសល់ {attemptLeft} នៃ {totalAttempt}}}", + "attempts_left_and_retry": "{attemptLeft, plural, =0 {ការព្យាយាមនៅសល់ 0 នៃ {totalAttempt}។ សូមព្យាយាមម្តងទៀតនៅ {attemptRetryAfter} នាទីបន្ទាប់។} other {ការព្យាយាមនៅសល់ {attemptLeft} នៃ {totalAttempt}}}", "captcha_token_validation": "សូមបញ្ជាក់ថាអ្នកជាមនុស្ស", "username_validation": "សូមបញ្ចូលឈ្មោះអ្នកប្រើប្រាស់ត្រឹមត្រូវ", "username_lead_zero_validation": "លេខមិនអាចចាប់ផ្តើមដោយលេខសូន្យបានទេ។បញ្ចូលលេខទូរស័ព្ទដែលត្រឹមត្រូវ។", @@ -83,7 +84,7 @@ "powered_by": "ដំណើរការដោយ" }, "error_response": { - "invalid_transaction": "ប្រតិបត្តិការមិនត្រឹមត្រូវ ឬប្រតិបត្តិការផុតកំណត់", + "invalid_transaction": "ប្រតិបត្តិការបានផុតកំណត់។ សូមព្យាយាមម្ដងទៀត។", "invalid_otp_channel": "ឆានែលលេខសម្ងាត់ដែលបានផ្ដល់មិនត្រឹមត្រូវ", "invalid_captcha": "បានរកឃើញ captcha មិនត្រឹមត្រូវ", "send_otp_failed": "ផ្ញើលេខសម្ងាត់បរាជ័យ។ លេខសម្គាល់បុគ្គលមិនពិត/មិនស្គាល់", @@ -128,6 +129,8 @@ "fetch_identity_failed": "ទាញ​យក​លេខ​សម្គាល់​បាន​បរាជ័យ", "challenge_format_and_type_mismatch": "លេខទូរស័ព្ទ ឬឈ្មោះមិនត្រឹមត្រូវ។ សូមបញ្ចូលលេខទូរស័ព្ទនិងឈ្មោះដែលបានចុះឈ្មោះ។", "knowledgebase_mismatch": "លេខទូរស័ព្ទ ឬឈ្មោះមិនត្រឹមត្រូវ។ សូមបញ្ចូលលេខទូរស័ព្ទនិងឈ្មោះដែលបានចុះឈ្មោះ។", + "identifier_blocked": "លេខសម្គាល់ត្រូវបានបិទចោលរយៈពេល5នាទី", + "unsupported_purpose": "សំណើមិនត្រឹមត្រូវ", "IDR-IDC-001": "រកមិនឃើញតម្លៃដែលបានបញ្ចូល", "IDR-IDC-002": "តម្លៃដែលបានបញ្ចូលមិនត្រឹមត្រូវ", "IDR-IDC-003": "សំណើរមិនត្រឹមត្រូវ", diff --git a/signup-ui/src/components/resend-attempt.tsx b/signup-ui/src/components/resend-attempt.tsx index a428f452..cef25ff8 100644 --- a/signup-ui/src/components/resend-attempt.tsx +++ b/signup-ui/src/components/resend-attempt.tsx @@ -4,22 +4,24 @@ interface ResendAttemptProps { currentAttempts: number; totalAttempts: number; attemptRetryAfter?: number; + showRetry?: boolean; } export const ResendAttempt = ({ currentAttempts, totalAttempts, - attemptRetryAfter = 5, + attemptRetryAfter = 300, + showRetry = false, }: ResendAttemptProps) => { const { t } = useTranslation(); return ( <> {currentAttempts < totalAttempts && (
- {t("attempts_left", { + {t(showRetry ? "attempts_left_and_retry" : "attempts_left", { attemptLeft: currentAttempts, totalAttempt: totalAttempts, - attemptRetryAfter: attemptRetryAfter, + attemptRetryAfter: attemptRetryAfter / 60, })}
)} diff --git a/signup-ui/src/pages/ResetPasswordPage/Otp/Otp.tsx b/signup-ui/src/pages/ResetPasswordPage/Otp/Otp.tsx index d3639b7f..59b9c120 100644 --- a/signup-ui/src/pages/ResetPasswordPage/Otp/Otp.tsx +++ b/signup-ui/src/pages/ResetPasswordPage/Otp/Otp.tsx @@ -30,9 +30,9 @@ import { Error, GenerateChallengeRequestDto, ResetPasswordForm, + ResetPasswordPossibleInvalid, SettingsDto, VerifyChallengeRequestDto, - ResetPasswordPossibleInvalid, } from "~typings/types"; import { resetPasswordFormDefaultValues } from "../ResetPasswordPage"; @@ -129,15 +129,10 @@ export const Otp = ({ methods, settings }: OtpProps) => { }; return generateChallengeMutation.mutate(generateChallengeRequestDto, { - onSuccess: ({ errors }) => { + onSuccess: ({ response, errors }) => { pinInputRef.current?.clear(); setValue("otp", "", { shouldValidate: true }); - setResendAttempts((resendAttempt) => resendAttempt - 1); - restartResendOtpTotalSecs( - getTimeoutTime(settings.response.configs["resend.delay"]) - ); - if (errors && errors.length > 0) { if (errors[0].errorCode === "invalid_transaction") { setCriticalError(errors[0]); @@ -145,6 +140,13 @@ export const Otp = ({ methods, settings }: OtpProps) => { setChallengeVerificationError(errors[0]); } } + + if (errors.length === 0 && response?.status === "SUCCESS") { + setResendAttempts((resendAttempt) => resendAttempt - 1); + restartResendOtpTotalSecs( + getTimeoutTime(settings.response.configs["resend.delay"]) + ); + } }, }); } @@ -204,9 +206,10 @@ export const Otp = ({ methods, settings }: OtpProps) => { onSuccess: ({ errors }) => { if (errors.length > 0) { if ( - ["invalid_transaction", ...ResetPasswordPossibleInvalid].includes( - errors[0].errorCode - ) + [ + "invalid_transaction", + ...ResetPasswordPossibleInvalid, + ].includes(errors[0].errorCode) ) { setCriticalError(errors[0]); } else { @@ -349,9 +352,10 @@ export const Otp = ({ methods, settings }: OtpProps) => { currentAttempts={resendAttempts} totalAttempts={settings.response.configs["resend.attempts"]} attemptRetryAfter={settings.response.configs["otp.blocked"]} + showRetry={resendAttempts === 0 && resendOtpTotalSecs === 0} /> )} - {resendAttempts === 0 && ( + {resendAttempts === 0 && resendOtpTotalSecs === 0 && (