diff --git a/maxkey-authentications/maxkey-authentication-captcha/src/main/java/org/maxkey/web/contorller/ImageCaptcha.java b/maxkey-authentications/maxkey-authentication-captcha/src/main/java/org/maxkey/web/contorller/ImageCaptcha.java index 1cef533b8..dc083d975 100644 --- a/maxkey-authentications/maxkey-authentication-captcha/src/main/java/org/maxkey/web/contorller/ImageCaptcha.java +++ b/maxkey-authentications/maxkey-authentication-captcha/src/main/java/org/maxkey/web/contorller/ImageCaptcha.java @@ -17,21 +17,26 @@ package org.maxkey.web.contorller; public class ImageCaptcha { - String id; + String state; String image; - public ImageCaptcha(String id, String image) { + public ImageCaptcha(String state, String image) { super(); - this.id = id; + this.state = state; this.image = image; } - public String getId() { - return id; + + public String getState() { + return state; } - public void setId(String id) { - this.id = id; + + + public void setState(String state) { + this.state = state; } + + public String getImage() { return image; } diff --git a/maxkey-authentications/maxkey-authentication-captcha/src/main/java/org/maxkey/web/contorller/ImageCaptchaEndpoint.java b/maxkey-authentications/maxkey-authentication-captcha/src/main/java/org/maxkey/web/contorller/ImageCaptchaEndpoint.java index d78ddc96a..2aba59ba2 100644 --- a/maxkey-authentications/maxkey-authentication-captcha/src/main/java/org/maxkey/web/contorller/ImageCaptchaEndpoint.java +++ b/maxkey-authentications/maxkey-authentication-captcha/src/main/java/org/maxkey/web/contorller/ImageCaptchaEndpoint.java @@ -88,7 +88,7 @@ public class ImageCaptchaEndpoint { }else { state = authJwtService.genJwt(); } - kaptchaKey = authJwtService.resolveTicket(state); + kaptchaKey = authJwtService.resolveJWTID(state); _logger.trace("kaptchaKey {} , Captcha Text is {}" ,kaptchaKey, kaptchaValue); momentaryService.put("", kaptchaKey, kaptchaValue); diff --git a/maxkey-authentications/maxkey-authentication-core/src/main/java/org/maxkey/authn/AbstractAuthenticationProvider.java b/maxkey-authentications/maxkey-authentication-core/src/main/java/org/maxkey/authn/AbstractAuthenticationProvider.java index a51357447..a5816241e 100644 --- a/maxkey-authentications/maxkey-authentication-core/src/main/java/org/maxkey/authn/AbstractAuthenticationProvider.java +++ b/maxkey-authentications/maxkey-authentication-core/src/main/java/org/maxkey/authn/AbstractAuthenticationProvider.java @@ -30,7 +30,6 @@ import org.maxkey.constants.ConstsStatus; import org.maxkey.entity.UserInfo; import org.maxkey.password.onetimepwd.AbstractOtpAuthn; import org.maxkey.password.onetimepwd.OtpAuthnService; -import org.maxkey.persistence.MomentaryService; import org.maxkey.web.WebConstants; import org.maxkey.web.WebContext; import org.slf4j.Logger; @@ -70,8 +69,6 @@ public abstract class AbstractAuthenticationProvider { protected OnlineTicketService onlineTicketServices; - protected MomentaryService momentaryService; - protected AuthJwtService authJwtService; public static ArrayList grantedAdministratorsAuthoritys = new ArrayList(); diff --git a/maxkey-authentications/maxkey-authentication-core/src/main/java/org/maxkey/authn/jwt/AuthJwtService.java b/maxkey-authentications/maxkey-authentication-core/src/main/java/org/maxkey/authn/jwt/AuthJwtService.java index 19653cdfd..c21c90ea2 100644 --- a/maxkey-authentications/maxkey-authentication-core/src/main/java/org/maxkey/authn/jwt/AuthJwtService.java +++ b/maxkey-authentications/maxkey-authentication-core/src/main/java/org/maxkey/authn/jwt/AuthJwtService.java @@ -19,11 +19,14 @@ package org.maxkey.authn.jwt; import java.text.ParseException; import java.util.Date; + +import org.apache.commons.lang3.StringUtils; import org.joda.time.DateTime; import org.maxkey.authn.SigninPrincipal; import org.maxkey.configuration.AuthJwkConfig; import org.maxkey.crypto.jwt.HMAC512Service; import org.maxkey.entity.UserInfo; +import org.maxkey.persistence.MomentaryService; import org.maxkey.web.WebContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -44,6 +47,8 @@ public class AuthJwtService { AuthJwkConfig authJwkConfig; CongressService congressService; + + MomentaryService momentaryService; public AuthJwtService(AuthJwkConfig authJwkConfig) throws JOSEException { this.authJwkConfig = authJwkConfig; @@ -51,12 +56,16 @@ public class AuthJwtService { this.hmac512Service = new HMAC512Service(authJwkConfig.getSecret()); } - public AuthJwtService(AuthJwkConfig authJwkConfig,CongressService congressService) throws JOSEException { + public AuthJwtService(AuthJwkConfig authJwkConfig,CongressService congressService,MomentaryService momentaryService) throws JOSEException { this.authJwkConfig = authJwkConfig; this.congressService = congressService; + this.momentaryService = momentaryService; + this.hmac512Service = new HMAC512Service(authJwkConfig.getSecret()); + + } /** @@ -156,7 +165,7 @@ public class AuthJwtService { return signedJWT.getJWTClaimsSet(); } - public String resolveTicket(String authToken) throws ParseException { + public String resolveJWTID(String authToken) throws ParseException { JWTClaimsSet claims = resolve(authToken); return claims.getJWTID(); } @@ -177,4 +186,21 @@ public class AuthJwtService { return authJwt; } + public boolean validateCaptcha(String state,String captcha) { + try { + String jwtId = resolveJWTID(state); + if(StringUtils.isNotBlank(jwtId) &&StringUtils.isNotBlank(captcha)) { + Object momentaryCaptcha = momentaryService.get("", jwtId); + _logger.debug("captcha : {}, momentary Captcha : {}" ,captcha, momentaryCaptcha); + if (!StringUtils.isBlank(captcha) && captcha.equals(momentaryCaptcha.toString())) { + momentaryService.remove("", jwtId); + return true; + } + } + } catch (ParseException e) { + _logger.debug("Exception ",e); + } + return false; + } + } diff --git a/maxkey-authentications/maxkey-authentication-core/src/main/java/org/maxkey/authn/provider/MfaAuthenticationProvider.java b/maxkey-authentications/maxkey-authentication-core/src/main/java/org/maxkey/authn/provider/MfaAuthenticationProvider.java index 41a0bf420..5ba49541e 100644 --- a/maxkey-authentications/maxkey-authentication-core/src/main/java/org/maxkey/authn/provider/MfaAuthenticationProvider.java +++ b/maxkey-authentications/maxkey-authentication-core/src/main/java/org/maxkey/authn/provider/MfaAuthenticationProvider.java @@ -26,7 +26,6 @@ import org.maxkey.configuration.ApplicationConfig; import org.maxkey.constants.ConstsLoginType; import org.maxkey.entity.Institutions; import org.maxkey.entity.UserInfo; -import org.maxkey.persistence.MomentaryService; import org.maxkey.web.WebConstants; import org.maxkey.web.WebContext; import org.slf4j.Logger; @@ -59,13 +58,11 @@ public class MfaAuthenticationProvider extends AbstractAuthenticationProvider { AbstractAuthenticationRealm authenticationRealm, ApplicationConfig applicationConfig, OnlineTicketService onlineTicketServices, - AuthJwtService authJwtService, - MomentaryService momentaryService) { + AuthJwtService authJwtService) { this.authenticationRealm = authenticationRealm; this.applicationConfig = applicationConfig; this.onlineTicketServices = onlineTicketServices; this.authJwtService = authJwtService; - this.momentaryService = momentaryService; } @Override diff --git a/maxkey-authentications/maxkey-authentication-core/src/main/java/org/maxkey/authn/provider/NormalAuthenticationProvider.java b/maxkey-authentications/maxkey-authentication-core/src/main/java/org/maxkey/authn/provider/NormalAuthenticationProvider.java index fad4829e0..25eb64c77 100644 --- a/maxkey-authentications/maxkey-authentication-core/src/main/java/org/maxkey/authn/provider/NormalAuthenticationProvider.java +++ b/maxkey-authentications/maxkey-authentication-core/src/main/java/org/maxkey/authn/provider/NormalAuthenticationProvider.java @@ -18,7 +18,6 @@ package org.maxkey.authn.provider; import java.text.ParseException; -import org.apache.commons.lang3.StringUtils; import org.maxkey.authn.AbstractAuthenticationProvider; import org.maxkey.authn.LoginCredential; import org.maxkey.authn.jwt.AuthJwtService; @@ -28,7 +27,6 @@ import org.maxkey.configuration.ApplicationConfig; import org.maxkey.constants.ConstsLoginType; import org.maxkey.entity.Institutions; import org.maxkey.entity.UserInfo; -import org.maxkey.persistence.MomentaryService; import org.maxkey.web.WebConstants; import org.maxkey.web.WebContext; import org.slf4j.Logger; @@ -60,13 +58,11 @@ public class NormalAuthenticationProvider extends AbstractAuthenticationProvider AbstractAuthenticationRealm authenticationRealm, ApplicationConfig applicationConfig, OnlineTicketService onlineTicketServices, - AuthJwtService authJwtService, - MomentaryService momentaryService) { + AuthJwtService authJwtService) { this.authenticationRealm = authenticationRealm; this.applicationConfig = applicationConfig; this.onlineTicketServices = onlineTicketServices; this.authJwtService = authJwtService; - this.momentaryService = momentaryService; } @Override @@ -138,19 +134,8 @@ public class NormalAuthenticationProvider extends AbstractAuthenticationProvider */ protected void captchaValid(String state ,String captcha) throws ParseException { // for basic - String ticket = authJwtService.resolveTicket(state); - if(ticket == null) { + if(!authJwtService.validateCaptcha(state,captcha)) { throw new BadCredentialsException(WebContext.getI18nValue("login.error.captcha")); - } - Object momentaryCaptcha = momentaryService.get("", ticket); - _logger.info("captcha : {} , momentary Captcha : {} " ,captcha, momentaryCaptcha); - if (StringUtils.isBlank(captcha) || !captcha.equals(momentaryCaptcha.toString())) { - _logger.debug("login captcha valid error."); - throw new BadCredentialsException(WebContext.getI18nValue("login.error.captcha")); - } - momentaryService.remove("", ticket); + } } - - - } diff --git a/maxkey-authentications/maxkey-authentication-core/src/main/java/org/maxkey/authn/web/AuthorizationUtils.java b/maxkey-authentications/maxkey-authentication-core/src/main/java/org/maxkey/authn/web/AuthorizationUtils.java index d1d9feb74..0193d3d5f 100644 --- a/maxkey-authentications/maxkey-authentication-core/src/main/java/org/maxkey/authn/web/AuthorizationUtils.java +++ b/maxkey-authentications/maxkey-authentication-core/src/main/java/org/maxkey/authn/web/AuthorizationUtils.java @@ -73,7 +73,7 @@ public class AuthorizationUtils { AuthJwtService authJwtService, OnlineTicketService onlineTicketService) throws ParseException { if(authJwtService.validateJwtToken(authorization)) { - String ticket = authJwtService.resolveTicket(authorization); + String ticket = authJwtService.resolveJWTID(authorization); OnlineTicket onlineTicket = onlineTicketService.get(ticket); if(onlineTicket != null) { setAuthentication(onlineTicket.getAuthentication()); diff --git a/maxkey-authentications/maxkey-authentication-core/src/main/java/org/maxkey/autoconfigure/AuthenticationAutoConfiguration.java b/maxkey-authentications/maxkey-authentication-core/src/main/java/org/maxkey/autoconfigure/AuthenticationAutoConfiguration.java index 5f631543c..358fee8aa 100644 --- a/maxkey-authentications/maxkey-authentication-core/src/main/java/org/maxkey/autoconfigure/AuthenticationAutoConfiguration.java +++ b/maxkey-authentications/maxkey-authentication-core/src/main/java/org/maxkey/autoconfigure/AuthenticationAutoConfiguration.java @@ -87,16 +87,14 @@ public class AuthenticationAutoConfiguration implements InitializingBean { AbstractAuthenticationRealm authenticationRealm, ApplicationConfig applicationConfig, OnlineTicketService onlineTicketServices, - AuthJwtService authJwtService, - MomentaryService momentaryService + AuthJwtService authJwtService ) { _logger.debug("init authentication Provider ."); return new NormalAuthenticationProvider( authenticationRealm, applicationConfig, onlineTicketServices, - authJwtService, - momentaryService + authJwtService ); } @@ -134,6 +132,7 @@ public class AuthenticationAutoConfiguration implements InitializingBean { public AuthJwtService authJwtService( AuthJwkConfig authJwkConfig, RedisConnectionFactory redisConnFactory, + MomentaryService momentaryService, @Value("${maxkey.server.persistence}") int persistence) throws JOSEException { CongressService congressService; if (persistence == ConstsPersistence.REDIS) { @@ -142,7 +141,7 @@ public class AuthenticationAutoConfiguration implements InitializingBean { congressService = new InMemoryCongressService(); } - AuthJwtService authJwtService = new AuthJwtService(authJwkConfig,congressService); + AuthJwtService authJwtService = new AuthJwtService(authJwkConfig,congressService,momentaryService); return authJwtService; } diff --git a/maxkey-authentications/maxkey-authentication-otp/src/main/java/org/maxkey/password/onetimepwd/OtpAuthnService.java b/maxkey-authentications/maxkey-authentication-otp/src/main/java/org/maxkey/password/onetimepwd/OtpAuthnService.java index 5e694d788..2847bdbfe 100644 --- a/maxkey-authentications/maxkey-authentication-otp/src/main/java/org/maxkey/password/onetimepwd/OtpAuthnService.java +++ b/maxkey-authentications/maxkey-authentication-otp/src/main/java/org/maxkey/password/onetimepwd/OtpAuthnService.java @@ -124,6 +124,32 @@ public class OtpAuthnService { } return otpAuthn; } + + public AbstractOtpAuthn getMailOtpAuthn(String instId) { + AbstractOtpAuthn otpAuthn = otpAuthnStore.getIfPresent(instId); + if(otpAuthn == null) { + EmailSenders emailSender = + emailSendersService.findOne("where instid = ? ", new Object[]{instId}, new int[]{Types.VARCHAR}); + + String credentials = PasswordReciprocal.getInstance().decoder(emailSender.getCredentials()); + EmailConfig emailConfig = + new EmailConfig( + emailSender.getAccount(), + credentials, + emailSender.getSmtpHost(), + emailSender.getPort(), + ConstsBoolean.isTrue(emailSender.getSslSwitch()), + emailSender.getSender()); + MailOtpAuthn mailOtpAuthn = new MailOtpAuthn(emailConfig); + mailOtpAuthn.setInterval(60 * 5);//5 minute + if(redisOptTokenStore != null) { + mailOtpAuthn.setOptTokenStore(redisOptTokenStore); + } + otpAuthn = mailOtpAuthn; + } + otpAuthnStore.put(instId, otpAuthn); + return otpAuthn; + } public void setRedisOptTokenStore(RedisOtpTokenStore redisOptTokenStore) { this.redisOptTokenStore = redisOptTokenStore; diff --git a/maxkey-core/src/main/java/org/maxkey/entity/ChangePassword.java b/maxkey-core/src/main/java/org/maxkey/entity/ChangePassword.java index 8ba531d18..81dc8b55e 100644 --- a/maxkey-core/src/main/java/org/maxkey/entity/ChangePassword.java +++ b/maxkey-core/src/main/java/org/maxkey/entity/ChangePassword.java @@ -67,6 +67,11 @@ public class ChangePassword extends JpaBaseEntity{ this.setInstId(userInfo.getInstId()); } + public void clearPassword() { + this.password =""; + this.decipherable = ""; + } + /** * @return the id */ diff --git a/maxkey-core/src/main/java/org/maxkey/persistence/repository/PasswordPolicyRepository.java b/maxkey-core/src/main/java/org/maxkey/persistence/repository/PasswordPolicyRepository.java index d3f9decf4..7bcd8660f 100644 --- a/maxkey-core/src/main/java/org/maxkey/persistence/repository/PasswordPolicyRepository.java +++ b/maxkey-core/src/main/java/org/maxkey/persistence/repository/PasswordPolicyRepository.java @@ -152,6 +152,7 @@ public class PasswordPolicyRepository { public ArrayList getPasswordPolicyRuleList() { + getPasswordPolicy(); return passwordPolicyRuleList; } diff --git a/maxkey-web-frontend/maxkey-web-app/src/app/app.module.ts b/maxkey-web-frontend/maxkey-web-app/src/app/app.module.ts index e90354fc4..b4ca81434 100644 --- a/maxkey-web-frontend/maxkey-web-app/src/app/app.module.ts +++ b/maxkey-web-frontend/maxkey-web-app/src/app/app.module.ts @@ -106,4 +106,4 @@ import { Observable } from 'rxjs'; providers: [...LANG_PROVIDES, ...INTERCEPTOR_PROVIDES, ...I18NSERVICE_PROVIDES, ...APPINIT_PROVIDES], bootstrap: [AppComponent] }) -export class AppModule {} +export class AppModule { } diff --git a/maxkey-web-frontend/maxkey-web-app/src/app/routes/passport/forgot/forgot.component.html b/maxkey-web-frontend/maxkey-web-app/src/app/routes/passport/forgot/forgot.component.html new file mode 100644 index 000000000..9848b2a6d --- /dev/null +++ b/maxkey-web-frontend/maxkey-web-app/src/app/routes/passport/forgot/forgot.component.html @@ -0,0 +1,120 @@ +

{{ 'mxk.forgot.forgot' | i18n }}

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + {{ 'validation.phone-number.required' | i18n }} + + + {{ 'validation.phone-number.wrong-format' | i18n }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {{ 'mxk.password.password' | i18n }} + + + + + + + + + + + + + + {{ 'mxk.password.confirmPassword' | i18n }} + + + + + + + + + + + +
diff --git a/maxkey-web-frontend/maxkey-web-app/src/app/routes/passport/forgot/forgot.component.less b/maxkey-web-frontend/maxkey-web-app/src/app/routes/passport/forgot/forgot.component.less new file mode 100644 index 000000000..aa4329c26 --- /dev/null +++ b/maxkey-web-frontend/maxkey-web-app/src/app/routes/passport/forgot/forgot.component.less @@ -0,0 +1,21 @@ +@import '@delon/theme/index'; + +:host { + display: block; + width: 460px; + margin: 0 auto; + + + ::ng-deep { + .ant-tabs .ant-tabs-bar { + margin-bottom: 24px; + text-align: center; + border-bottom: 0; + } + } + + .login { + float: right; + line-height: @btn-height-lg; + } +} \ No newline at end of file diff --git a/maxkey-web-frontend/maxkey-web-app/src/app/routes/passport/forgot/forgot.component.spec.ts b/maxkey-web-frontend/maxkey-web-app/src/app/routes/passport/forgot/forgot.component.spec.ts new file mode 100644 index 000000000..998fd5d8c --- /dev/null +++ b/maxkey-web-frontend/maxkey-web-app/src/app/routes/passport/forgot/forgot.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ForgotComponent } from './forgot.component'; + +describe('ForgotComponent', () => { + let component: ForgotComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ ForgotComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(ForgotComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/maxkey-web-frontend/maxkey-web-app/src/app/routes/passport/forgot/forgot.component.ts b/maxkey-web-frontend/maxkey-web-app/src/app/routes/passport/forgot/forgot.component.ts new file mode 100644 index 000000000..574f733d4 --- /dev/null +++ b/maxkey-web-frontend/maxkey-web-app/src/app/routes/passport/forgot/forgot.component.ts @@ -0,0 +1,176 @@ +import { Component, OnInit, ChangeDetectorRef } from '@angular/core'; +import { AbstractControl, FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { Router, ActivatedRoute } from '@angular/router'; +import { NzMessageService } from 'ng-zorro-antd/message'; +import { NzStepsModule } from 'ng-zorro-antd/steps'; + +import { ChangePassword } from '../../../entity/ChangePassword'; +import { ForgotPasswordService } from '../../../service/forgot-password.service'; +import { ImageCaptchaService } from '../../../service/image-captcha.service'; +import { PasswordService } from '../../../service/password.service'; + +@Component({ + selector: 'app-forgot', + templateUrl: './forgot.component.html', + styleUrls: ['./forgot.component.less'] +}) +export class ForgotComponent implements OnInit { + form: { + submitting: boolean; + model: ChangePassword; + } = { + submitting: false, + model: new ChangePassword() + }; + + imageCaptcha = ''; + formGroup: FormGroup; + state = ''; + step = 0; + forgotType = 'mobile'; + passwordVisible = false; + loading = false; + count = 0; + interval$: any; + userId = ''; + username = ''; + + constructor( + fb: FormBuilder, + private forgotPasswordService: ForgotPasswordService, + private imageCaptchaService: ImageCaptchaService, + private msg: NzMessageService, + private cdr: ChangeDetectorRef + ) { + this.formGroup = fb.group({ + mobile: [null, [Validators.required, Validators.pattern(/^1\d{10}$/)]], + email: [null, [Validators.required]], + password: [null, [Validators.required]], + captcha: [null, [Validators.required]], + otpCaptcha: [null, [Validators.required]], + confirmPassword: [null, [Validators.required]] + }); + } + + get email(): AbstractControl { + return this.formGroup.get('email')!; + } + get password(): AbstractControl { + return this.formGroup.get('password')!; + } + get confirmPassword(): AbstractControl { + return this.formGroup.get('confirmPassword')!; + } + get mobile(): AbstractControl { + return this.formGroup.get('mobile')!; + } + get captcha(): AbstractControl { + return this.formGroup.get('captcha')!; + } + + get otpCaptcha(): AbstractControl { + return this.formGroup.get('otpCaptcha')!; + } + + ngOnInit(): void { + this.getImageCaptcha(); + } + + getImageCaptcha(): void { + this.imageCaptchaService.captcha({}).subscribe(res => { + this.imageCaptcha = res.data.image; + this.state = res.data.state; + this.cdr.detectChanges(); + }); + } + + //send sms + sendOtpCode(): void { + if (this.forgotType == 'mobile' && this.mobile.invalid) { + this.mobile.markAsDirty({ onlySelf: true }); + this.mobile.updateValueAndValidity({ onlySelf: true }); + return; + } + + if (this.forgotType == 'email' && this.email.invalid) { + this.email.markAsDirty({ onlySelf: true }); + this.email.updateValueAndValidity({ onlySelf: true }); + return; + } + if (this.forgotType == 'mobile') { + this.forgotPasswordService + .produceOtp({ mobile: this.mobile.value, state: this.state, captcha: this.captcha.value }) + .subscribe(res => { + if (res.code !== 0) { + this.msg.success(`发送失败`); + this.getImageCaptcha(); + this.cdr.detectChanges(); + } + this.userId = res.data.userId; + this.username = res.data.username; + //console.log(res.data); + }); + } else if (this.forgotType == 'email') { + this.forgotPasswordService + .produceEmailOtp({ email: this.email.value, state: this.state, captcha: this.captcha.value }) + .subscribe(res => { + if (res.code !== 0) { + this.msg.success(`发送失败`); + this.getImageCaptcha(); + this.cdr.detectChanges(); + } + this.userId = res.data.userId; + this.username = res.data.username; + //console.log(res.data); + }); + } + this.count = 59; + this.interval$ = setInterval(() => { + this.count -= 1; + if (this.count <= 0) { + clearInterval(this.interval$); + } + this.cdr.detectChanges(); + }, 1000); + } + + onNextReset(e: MouseEvent) { + if (this.otpCaptcha.invalid) { + this.otpCaptcha.markAsDirty({ onlySelf: true }); + this.otpCaptcha.updateValueAndValidity({ onlySelf: true }); + return; + } + this.step = 1; + } + + onSubmit(e: MouseEvent) { + this.forgotPasswordService + .setPassWord({ + forgotType: this.forgotType, + userId: this.userId, + username: this.username, + password: this.password.value, + confirmPassword: this.confirmPassword.value, + otpCaptcha: this.otpCaptcha.value, + state: this.state + }) + .subscribe(res => { + if (res.code !== 0) { + this.msg.success(`密码修改失败`); + this.getImageCaptcha(); + this.step = 0; + this.cdr.detectChanges(); + } + this.msg.success(`密码修改成功`); + }); + } + + ngModelChange() { + if (this.forgotType == 'email') { + this.mobile.reset(); + } + if (this.forgotType == 'mobile') { + this.email.reset(); + } + } +} diff --git a/maxkey-web-frontend/maxkey-web-app/src/app/routes/passport/login/login.component.html b/maxkey-web-frontend/maxkey-web-app/src/app/routes/passport/login/login.component.html index 5ebb08a07..08bcf18e8 100644 --- a/maxkey-web-frontend/maxkey-web-app/src/app/routes/passport/login/login.component.html +++ b/maxkey-web-frontend/maxkey-web-app/src/app/routes/passport/login/login.component.html @@ -89,7 +89,7 @@ - {{ 'mxk.login.forgot-password' | i18n }}{{ 'mxk.login.forgot-password' | i18n }} diff --git a/maxkey-web-frontend/maxkey-web-app/src/app/routes/passport/passport-routing.module.ts b/maxkey-web-frontend/maxkey-web-app/src/app/routes/passport/passport-routing.module.ts index c73b21bc6..021ed4c60 100644 --- a/maxkey-web-frontend/maxkey-web-app/src/app/routes/passport/passport-routing.module.ts +++ b/maxkey-web-frontend/maxkey-web-app/src/app/routes/passport/passport-routing.module.ts @@ -3,6 +3,7 @@ import { RouterModule, Routes } from '@angular/router'; import { LayoutPassportComponent } from '../../layout/passport/passport.component'; import { CallbackComponent } from './callback.component'; +import { ForgotComponent } from './forgot/forgot.component'; import { UserLockComponent } from './lock/lock.component'; import { UserLoginComponent } from './login/login.component'; import { UserRegisterResultComponent } from './register-result/register-result.component'; @@ -29,6 +30,11 @@ const routes: Routes = [ component: UserRegisterResultComponent, data: { title: '注册结果', titleI18n: 'app.register.register' } }, + { + path: 'forgot', + component: ForgotComponent, + data: { title: '忘记密码', titleI18n: 'app.forgot.forgot' } + }, { path: 'lock', component: UserLockComponent, diff --git a/maxkey-web-frontend/maxkey-web-app/src/app/routes/passport/passport.module.ts b/maxkey-web-frontend/maxkey-web-app/src/app/routes/passport/passport.module.ts index 9638c2217..16938ee4b 100644 --- a/maxkey-web-frontend/maxkey-web-app/src/app/routes/passport/passport.module.ts +++ b/maxkey-web-frontend/maxkey-web-app/src/app/routes/passport/passport.module.ts @@ -1,7 +1,9 @@ import { NgModule } from '@angular/core'; import { SharedModule } from '@shared'; +import { NzStepsModule } from 'ng-zorro-antd/steps'; import { CallbackComponent } from './callback.component'; +import { ForgotComponent } from './forgot/forgot.component'; import { UserLockComponent } from './lock/lock.component'; import { UserLoginComponent } from './login/login.component'; import { PassportRoutingModule } from './passport-routing.module'; @@ -11,7 +13,7 @@ import { UserRegisterComponent } from './register/register.component'; const COMPONENTS = [UserLoginComponent, UserRegisterResultComponent, UserRegisterComponent, UserLockComponent, CallbackComponent]; @NgModule({ - imports: [SharedModule, PassportRoutingModule], - declarations: [...COMPONENTS] + imports: [SharedModule, PassportRoutingModule, NzStepsModule], + declarations: [...COMPONENTS, ForgotComponent] }) -export class PassportModule {} +export class PassportModule { } diff --git a/maxkey-web-frontend/maxkey-web-app/src/app/service/forgot-password.service.ts b/maxkey-web-frontend/maxkey-web-app/src/app/service/forgot-password.service.ts new file mode 100644 index 000000000..67d71d04c --- /dev/null +++ b/maxkey-web-frontend/maxkey-web-app/src/app/service/forgot-password.service.ts @@ -0,0 +1,20 @@ +import { Injectable, Inject } from '@angular/core'; +import { _HttpClient, User } from '@delon/theme'; +@Injectable({ + providedIn: 'root' +}) +export class ForgotPasswordService { + constructor(private http: _HttpClient) { } + + produceOtp(param: any) { + return this.http.get('/forgotpassword/produceOtp?_allow_anonymous=true', param); + } + + produceEmailOtp(param: any) { + return this.http.get(`/forgotpassword/produceEmailOtp?_allow_anonymous=true`, param); + } + + setPassWord(param: any) { + return this.http.get('/forgotpassword/setpassword?_allow_anonymous=true', param); + } +} diff --git a/maxkey-web-frontend/maxkey-web-app/src/assets/i18n/en-US.json b/maxkey-web-frontend/maxkey-web-app/src/assets/i18n/en-US.json index 72c55b4e4..3b599a7b8 100644 --- a/maxkey-web-frontend/maxkey-web-app/src/assets/i18n/en-US.json +++ b/maxkey-web-frontend/maxkey-web-app/src/assets/i18n/en-US.json @@ -21,6 +21,22 @@ "text.captcha": "Captcha", "text.smscode": "Code" }, + "forgot":{ + "forgot":"Forgot Password", + "step1":"Authentication", + "step2":"Resetting Password", + "type.mobile":"By Mobile", + "type.email":"By Email", + "mobile":"Mobile Phone Number", + "email":"Email", + "captcha":"Picture Captcha", + "sendCaptcha":"Send Captcha", + "password":"New Password", + "confirmPassword":"Confirm Password", + "login":"Back Login", + "next":"Next", + "submit":"Confirm" + }, "menu": { "applist": "Apps", "sessions": "Sessions", @@ -807,6 +823,7 @@ "app.login.text.password": "Password", "app.login.text.captcha": "CAPTCHA", "app.login.text.smscode": "Code", + "app.forgot.forgot":"Forgot Password", "app.register.register": "Register", "app.register.get-verification-code": "Get code", "app.register.sign-in": "Already have an account?", diff --git a/maxkey-web-frontend/maxkey-web-app/src/assets/i18n/zh-CN.json b/maxkey-web-frontend/maxkey-web-app/src/assets/i18n/zh-CN.json index 066644f11..e64f07315 100644 --- a/maxkey-web-frontend/maxkey-web-app/src/assets/i18n/zh-CN.json +++ b/maxkey-web-frontend/maxkey-web-app/src/assets/i18n/zh-CN.json @@ -16,11 +16,27 @@ "signup": "用户注册", "login": "登录", "text.username": "用户名", - "text.mobile": "手机号", + "text.mobile": "手机号码", "text.password": "密码", "text.captcha": "验证码", "text.smscode": "验证码" }, + "forgot":{ + "forgot":"忘记密码", + "step1":"验证身份", + "step2":"设置新密码", + "type.mobile":"手机找回", + "type.email":"邮箱找回", + "mobile":"手机号码", + "email":"邮箱", + "captcha":"图形验证码", + "sendCaptcha":"发送验证码", + "password":"新密码", + "confirmPassword":"确认新密码", + "login":"返回登录", + "next":"下一步", + "submit":"提交" + }, "menu": { "applist": "应用", "sessions": "会话", @@ -805,6 +821,7 @@ "app.login.text.password": "密码", "app.login.text.captcha": "验证码", "app.login.text.smscode": "验证码", + "app.forgot.forgot":"忘记密码", "app.register.register": "注册", "app.register.get-verification-code": "获取验证码", "app.register.sign-in": "使用已有账户登录", diff --git a/maxkey-webs/maxkey-web-maxkey/src/main/java/org/maxkey/web/contorller/ChangePasswodController.java b/maxkey-webs/maxkey-web-maxkey/src/main/java/org/maxkey/web/contorller/ChangePasswodController.java index aefc4db65..782e9010e 100644 --- a/maxkey-webs/maxkey-web-maxkey/src/main/java/org/maxkey/web/contorller/ChangePasswodController.java +++ b/maxkey-webs/maxkey-web-maxkey/src/main/java/org/maxkey/web/contorller/ChangePasswodController.java @@ -1,5 +1,5 @@ /* - * Copyright [2020] [MaxKey of copyright http://www.maxkey.top] + * Copyright [2022] [MaxKey of copyright http://www.maxkey.top] * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,31 +17,21 @@ package org.maxkey.web.contorller; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - import org.maxkey.authn.annotation.CurrentUser; -import org.maxkey.constants.ConstsOperateMessage; import org.maxkey.constants.ConstsPasswordSetType; -import org.maxkey.constants.ConstsTimeInterval; import org.maxkey.entity.ChangePassword; +import org.maxkey.entity.Message; import org.maxkey.entity.UserInfo; -import org.maxkey.persistence.repository.PasswordPolicyValidator; import org.maxkey.persistence.service.UserInfoService; -import org.maxkey.web.WebConstants; -import org.maxkey.web.WebContext; -import org.maxkey.web.message.Message; -import org.maxkey.web.message.MessageType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; -import org.springframework.web.servlet.ModelAndView; @Controller @RequestMapping(value={"/config"}) @@ -52,8 +42,8 @@ public class ChangePasswodController { private UserInfoService userInfoService; @ResponseBody - @RequestMapping(value="/changePassword") - public Message changePasswod( + @RequestMapping(value = { "/changePassword" }, produces = {MediaType.APPLICATION_JSON_VALUE}) + public ResponseEntity changePasswod( @RequestBody ChangePassword changePassword, @CurrentUser UserInfo currentUser) { @@ -62,12 +52,9 @@ public class ChangePasswodController { changePassword.setInstId(currentUser.getInstId()); changePassword.setPasswordSetType(ConstsPasswordSetType.PASSWORD_NORMAL); if(userInfoService.changePassword(changePassword)) { - return new Message(WebContext.getI18nValue(ConstsOperateMessage.UPDATE_SUCCESS),MessageType.success); + return new Message().buildResponse(); }else { - return new Message( - WebContext.getI18nValue(ConstsOperateMessage.UPDATE_ERROR)+"
" - +WebContext.getAttribute(PasswordPolicyValidator.PASSWORD_POLICY_VALIDATE_RESULT), - MessageType.error); + return new Message(Message.ERROR).buildResponse(); } } diff --git a/maxkey-webs/maxkey-web-maxkey/src/main/java/org/maxkey/web/contorller/ForgotPasswordContorller.java b/maxkey-webs/maxkey-web-maxkey/src/main/java/org/maxkey/web/contorller/ForgotPasswordContorller.java index db1c0c5c7..b9fa795ff 100644 --- a/maxkey-webs/maxkey-web-maxkey/src/main/java/org/maxkey/web/contorller/ForgotPasswordContorller.java +++ b/maxkey-webs/maxkey-web-maxkey/src/main/java/org/maxkey/web/contorller/ForgotPasswordContorller.java @@ -1,5 +1,5 @@ /* - * Copyright [2020] [MaxKey of copyright http://www.maxkey.top] + * Copyright [2022] [MaxKey of copyright http://www.maxkey.top] * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,22 +19,26 @@ package org.maxkey.web.contorller; import java.util.regex.Pattern; +import org.apache.commons.lang3.StringUtils; +import org.maxkey.authn.jwt.AuthJwtService; import org.maxkey.configuration.EmailConfig; +import org.maxkey.entity.ChangePassword; +import org.maxkey.entity.Message; import org.maxkey.entity.UserInfo; import org.maxkey.password.onetimepwd.AbstractOtpAuthn; import org.maxkey.password.onetimepwd.OtpAuthnService; -import org.maxkey.persistence.repository.PasswordPolicyValidator; import org.maxkey.persistence.service.UserInfoService; -import org.maxkey.web.WebConstants; -import org.maxkey.web.WebContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.bind.annotation.ResponseBody; @Controller @RequestMapping(value = { "/forgotpassword" }) @@ -45,7 +49,7 @@ public class ForgotPasswordContorller { "^\\s*\\w+(?:\\.{0,1}[\\w-]+)*@[a-zA-Z0-9]+(?:[-.][a-zA-Z0-9]+)*\\.[a-zA-Z]+\\s*$"); Pattern mobileRegex = Pattern.compile( - "^(13[4,5,6,7,8,9]|15[0,8,9,1,7]|188|187)\\\\d{8}$"); + "^[1][3,4,5,7,8][0-9]{9}$"); @Autowired EmailConfig emailConfig; @@ -64,101 +68,107 @@ public class ForgotPasswordContorller { } @Autowired - private UserInfoService userInfoService; - + AuthJwtService authJwtService; + @Autowired - @Qualifier("mailOtpAuthn") - protected AbstractOtpAuthn mailOtpAuthn; + UserInfoService userInfoService; @Autowired @Qualifier("otpAuthnService") OtpAuthnService otpAuthnService; - - @RequestMapping(value = { "/forward" }) - public ModelAndView forwardreg() { - _logger.debug("forgotpassword /forgotpassword/forward."); - return new ModelAndView("forgotpassword/findpwd"); - } - - @RequestMapping(value = { "/emailmobile" }) - public ModelAndView email(@RequestParam String emailMobile,@RequestParam String captcha) { - _logger.debug("forgotpassword /forgotpassword/emailmobile."); - _logger.debug("emailMobile : " + emailMobile); - int forgotType = ForgotType.NOTFOUND; - UserInfo userInfo = null; - if (captcha != null && captcha - .equals(WebContext.getSession().getAttribute( - WebConstants.KAPTCHA_SESSION_KEY).toString())) { - if(mobileRegex.matcher(emailMobile).matches()) { - forgotType = ForgotType.MOBILE; - }else if(emailRegex.matcher(emailMobile).matches()) { - forgotType = ForgotType.EMAIL; - }else { - forgotType = ForgotType.EMAIL; - emailMobile =emailMobile + "@" + emailConfig.getSmtpHost().substring(emailConfig.getSmtpHost().indexOf(".")+1); - } - - userInfo = userInfoService.findByEmailMobile(emailMobile); - - if(null != userInfo) { - if (forgotType == ForgotType.EMAIL ) { - mailOtpAuthn.produce(userInfo); - }else if (forgotType == ForgotType.MOBILE) { - AbstractOtpAuthn smsOtpAuthn = otpAuthnService.getByInstId(userInfo.getInstId()); - smsOtpAuthn.produce(userInfo); - } - } - - }else { - _logger.debug("login captcha valid error."); - forgotType = ForgotType.CAPTCHAERROR; + + + + @ResponseBody + @RequestMapping(value = { "/produceOtp" }, produces = {MediaType.APPLICATION_JSON_VALUE}) + public ResponseEntity produceOtp( + @RequestParam String mobile, + @RequestParam String state, + @RequestParam String captcha) { + _logger.debug("forgotpassword /forgotpassword/produceOtp."); + _logger.debug(" Mobile {}: " ,mobile); + if (!authJwtService.validateCaptcha(state,captcha)) { + _logger.debug("login captcha valid error."); + return new Message(Message.FAIL).buildResponse(); } - ModelAndView modelAndView = new ModelAndView("forgotpassword/resetpwd"); - modelAndView.addObject("userId", userInfo==null ?"":userInfo.getId()); - modelAndView.addObject("username", userInfo==null ?"":userInfo.getUsername()); - modelAndView.addObject("emailMobile", emailMobile); - modelAndView.addObject("forgotType", forgotType); + ChangePassword change = null; + _logger.debug("Mobile Regex matches {}",mobileRegex.matcher(mobile).matches()); + if(StringUtils.isNotBlank(mobile) && mobileRegex.matcher(mobile).matches()) { + UserInfo userInfo = userInfoService.findByEmailMobile(mobile); + if(userInfo != null) { + change = new ChangePassword(userInfo); + change.clearPassword(); + AbstractOtpAuthn smsOtpAuthn = otpAuthnService.getByInstId(userInfo.getInstId()); + smsOtpAuthn.produce(userInfo); + return new Message(change).buildResponse(); + } + } + + return new Message(Message.FAIL).buildResponse(); + } + + @ResponseBody + @RequestMapping(value = { "/produceEmailOtp" }, produces = {MediaType.APPLICATION_JSON_VALUE}) + public ResponseEntity produceEmailOtp( + @RequestParam String email, + @RequestParam String state, + @RequestParam String captcha) { + _logger.debug("forgotpassword /forgotpassword/produceEmailOtp."); + _logger.debug("Email {} : " , email); + if (!authJwtService.validateCaptcha(state,captcha)) { + _logger.debug("login captcha valid error."); + return new Message(Message.FAIL).buildResponse(); + } - return modelAndView; + ChangePassword change = null; + if(StringUtils.isNotBlank(email) && emailRegex.matcher(email).matches()) { + UserInfo userInfo = userInfoService.findByEmailMobile(email); + if(userInfo != null) { + change = new ChangePassword(userInfo); + change.clearPassword(); + AbstractOtpAuthn mailOtpAuthn = otpAuthnService.getMailOtpAuthn(userInfo.getInstId()); + mailOtpAuthn.produce(userInfo); + return new Message(change).buildResponse(); + } + } + return new Message(Message.FAIL).buildResponse(); } @RequestMapping(value = { "/setpassword" }) - public ModelAndView setPassWord( - @RequestParam String userId, - @RequestParam String username, - @RequestParam int forgotType, - @RequestParam String password, - @RequestParam String confirmpassword, - @RequestParam String captcha) { - _logger.debug("forgotPassword /forgotpassword/pwdreseted."); - ModelAndView modelAndView = new ModelAndView("forgotpassword/pwdreseted"); - if (null != password && password.equals(confirmpassword)) { - UserInfo userInfo = new UserInfo(); - userInfo.setId(userId); - userInfo.setUsername(username); - userInfo.setPassword(password); - userInfo.setDecipherable(password); - UserInfo loadedUserInfo = userInfoService.findByUsername(username); - AbstractOtpAuthn smsOtpAuthn = otpAuthnService.getByInstId(loadedUserInfo.getInstId()); - if ((forgotType == ForgotType.EMAIL && mailOtpAuthn.validate(userInfo, captcha)) || - (forgotType == ForgotType.MOBILE && smsOtpAuthn.validate(userInfo, captcha)) - ) { - /** - if(userInfoService.changePassword(userInfo,true)) { - modelAndView.addObject("passwordResetResult", PasswordResetResult.SUCCESS); - }else { - ; - modelAndView.addObject("validate_result", WebContext.getAttribute(PasswordPolicyValidator.PASSWORD_POLICY_VALIDATE_RESULT)); - modelAndView.addObject("passwordResetResult", PasswordResetResult.PASSWORDERROR); - }*/ - } else { - modelAndView.addObject("passwordResetResult", PasswordResetResult.CAPTCHAERROR); - } - } else { - modelAndView.addObject("passwordResetResult", PasswordResetResult.PASSWORDERROR); + public ResponseEntity setPassWord( + @ModelAttribute ChangePassword changePassword, + @RequestParam String forgotType, + @RequestParam String otpCaptcha, + @RequestParam String state) { + _logger.debug("forgotPassword /forgotpassword/setpassword."); + if (StringUtils.isNotBlank(changePassword.getPassword() ) + && changePassword.getPassword().equals(changePassword.getConfirmPassword())) { + UserInfo loadedUserInfo = userInfoService.get(changePassword.getUserId()); + if(loadedUserInfo != null) { + AbstractOtpAuthn smsOtpAuthn = otpAuthnService.getByInstId(loadedUserInfo.getInstId()); + AbstractOtpAuthn mailOtpAuthn = otpAuthnService.getMailOtpAuthn(loadedUserInfo.getInstId()); + if ( + (forgotType.equalsIgnoreCase("email") + && mailOtpAuthn !=null + && mailOtpAuthn.validate(loadedUserInfo, otpCaptcha)) + || + (forgotType.equalsIgnoreCase("mobile") + && smsOtpAuthn !=null + && smsOtpAuthn.validate(loadedUserInfo, otpCaptcha)) + ) { + + if(userInfoService.changePassword(changePassword,true)) { + return new Message(Message.SUCCESS).buildResponse(); + }else { + return new Message(Message.FAIL).buildResponse(); + } + } else { + return new Message(Message.FAIL).buildResponse(); + } + } } - return modelAndView; + return new Message(Message.FAIL).buildResponse(); } } diff --git a/maxkey-webs/maxkey-web-maxkey/src/main/java/org/maxkey/web/contorller/SafeController.java b/maxkey-webs/maxkey-web-maxkey/src/main/java/org/maxkey/web/contorller/SafeController.java index 93696e276..6c0bbab96 100644 --- a/maxkey-webs/maxkey-web-maxkey/src/main/java/org/maxkey/web/contorller/SafeController.java +++ b/maxkey-webs/maxkey-web-maxkey/src/main/java/org/maxkey/web/contorller/SafeController.java @@ -46,9 +46,6 @@ public class SafeController { @Autowired private UserInfoService userInfoService; - - - @RequestMapping(value="/forward/setting") public ModelAndView fowardSetting(@CurrentUser UserInfo currentUser) { ModelAndView modelAndView=new ModelAndView("safe/setting");