Commit 6687ed02 authored by SN150021's avatar SN150021

短信验证登陆SMS

parent 37fb2889
...@@ -107,33 +107,7 @@ public class AccountController { ...@@ -107,33 +107,7 @@ public class AccountController {
public ResultDto requestOTP(@RequestBody MobileDto mobile) { public ResultDto requestOTP(@RequestBody MobileDto mobile) {
ResultDto resultDto = new ResultDto(); ResultDto resultDto = new ResultDto();
try { try {
accountService.sendSmsOtp(mobile.getMobile()); accountService.sendSmsOtp(mobile);
resultDto.setStatus(0);
} catch (ClientRequestException e) {
logger.error("fail to send sms", e);
dealClientRequestException(resultDto, e);
}
return resultDto;
}
@RequestMapping(path = "/resetAuthCode", method = RequestMethod.POST)
public ResultDto resetAuthCode(@RequestBody MobileDto mobile) {
ResultDto resultDto = new ResultDto();
try {
accountService.sendSmsOtpReset(mobile.getMobile());
resultDto.setStatus(0);
} catch (ClientRequestException e) {
logger.error("fail to send sms", e);
dealClientRequestException(resultDto, e);
}
return resultDto;
}
@RequestMapping(path = "/loginAuthCode", method = RequestMethod.POST)
public ResultDto loginAuthCode(@RequestBody MobileDto mobile) {
ResultDto resultDto = new ResultDto();
try {
accountService.loginAuthCode(mobile.getMobile());
resultDto.setStatus(0); resultDto.setStatus(0);
} catch (ClientRequestException e) { } catch (ClientRequestException e) {
logger.error("fail to send sms", e); logger.error("fail to send sms", e);
......
...@@ -14,7 +14,7 @@ public class Otp { ...@@ -14,7 +14,7 @@ public class Otp {
@Id @Id
private String phone; private String phone;
private String type; //0.登陆 1、 private Integer type; //0.登陆 1.注册 2.找回密码
/** /**
* 发送给用户的短信验证码 * 发送给用户的短信验证码
...@@ -26,6 +26,14 @@ public class Otp { ...@@ -26,6 +26,14 @@ public class Otp {
*/ */
private long createdAt; private long createdAt;
public Integer getType() {
return type;
}
public void setType(Integer type) {
this.type = type;
}
public String getPhone() { public String getPhone() {
return phone; return phone;
} }
......
...@@ -3,12 +3,21 @@ package com.edgec.browserbackend.account.dto; ...@@ -3,12 +3,21 @@ package com.edgec.browserbackend.account.dto;
public class MobileDto { public class MobileDto {
String mobile; String mobile;
String sign; String sign;
Integer type;
String sign_time; String sign_time;
public String getMobile() { public String getMobile() {
return mobile; return mobile;
} }
public Integer getType() {
return type;
}
public void setType(Integer type) {
this.type = type;
}
public void setMobile(String mobile) { public void setMobile(String mobile) {
this.mobile = mobile; this.mobile = mobile;
} }
......
...@@ -6,7 +6,7 @@ import org.springframework.data.mongodb.repository.MongoRepository; ...@@ -6,7 +6,7 @@ import org.springframework.data.mongodb.repository.MongoRepository;
public interface OtpRepository extends MongoRepository<Otp, String> { public interface OtpRepository extends MongoRepository<Otp, String> {
Otp findByPhoneAndCreatedAtGreaterThanEqual(String phone, long timestamp); Otp findByPhoneAndTypeAndCreatedAtGreaterThanEqual(String phone, Integer type, long timestamp);
} }
...@@ -82,7 +82,7 @@ public interface AccountService { ...@@ -82,7 +82,7 @@ public interface AccountService {
Page<UserPayment> getUserPayment(Pageable pageable, String username); Page<UserPayment> getUserPayment(Pageable pageable, String username);
void sendSmsOtp(String phone); void sendSmsOtp(MobileDto mobileDto);
AccountDto getAccountByCellphone(String cellphone); AccountDto getAccountByCellphone(String cellphone);
...@@ -112,7 +112,4 @@ public interface AccountService { ...@@ -112,7 +112,4 @@ public interface AccountService {
boolean setAuthorize(String username, boolean isAgree); boolean setAuthorize(String username, boolean isAgree);
void sendSmsOtpReset(String mobile);
void loginAuthCode(String mobile);
} }
...@@ -20,20 +20,36 @@ public class SmsUtils { ...@@ -20,20 +20,36 @@ public class SmsUtils {
private static final int TIME_OUT = 10 * 60; //10minues private static final int TIME_OUT = 10 * 60; //10minues
public enum SmsTemplateCode { public enum SmsTemplateCode {
LOGINACCOUNT(0, "SMS_472020179"),
NEWACCOUNT("SMS_472080002"), NEWACCOUNT(1,"SMS_472080002"),
RESETACCOUNT("SMS_471765248"); RESETACCOUNT(2,"SMS_471765248"),
; ;
Integer type;
String code; String code;
SmsTemplateCode(String code) { SmsTemplateCode(Integer type, String code) {
this.type =type;
this.code = code; this.code = code;
} }
public Integer getType() {
return type;
}
public String getCode() { public String getCode() {
return code; return code;
} }
public static SmsTemplateCode getByType(Integer type) {
for (SmsTemplateCode smsTemplateCode : SmsTemplateCode.values()) {
if (smsTemplateCode.type.intValue() == type.intValue()) {
return smsTemplateCode;
}
}
return null;
}
} }
/** /**
......
...@@ -12,6 +12,7 @@ import com.edgec.browserbackend.account.service.SmsUtils; ...@@ -12,6 +12,7 @@ import com.edgec.browserbackend.account.service.SmsUtils;
import com.edgec.browserbackend.account.service.SmsUtils.SmsTemplateCode; import com.edgec.browserbackend.account.service.SmsUtils.SmsTemplateCode;
import com.edgec.browserbackend.account.utils.AccountServicePool; import com.edgec.browserbackend.account.utils.AccountServicePool;
import com.edgec.browserbackend.auth.exception.AuthErrorCode; import com.edgec.browserbackend.auth.exception.AuthErrorCode;
import com.edgec.browserbackend.auth.repository.UserRepository;
import com.edgec.browserbackend.auth.service.UserService; import com.edgec.browserbackend.auth.service.UserService;
import com.edgec.browserbackend.browser.ErrorCode.BrowserErrorCode; import com.edgec.browserbackend.browser.ErrorCode.BrowserErrorCode;
import com.edgec.browserbackend.browser.domain.IpSummary; import com.edgec.browserbackend.browser.domain.IpSummary;
...@@ -23,6 +24,7 @@ import com.edgec.browserbackend.browser.service.ShopService; ...@@ -23,6 +24,7 @@ import com.edgec.browserbackend.browser.service.ShopService;
import com.edgec.browserbackend.common.commons.error.ClientRequestException; import com.edgec.browserbackend.common.commons.error.ClientRequestException;
import com.edgec.browserbackend.common.utils.Aes; import com.edgec.browserbackend.common.utils.Aes;
import com.edgec.browserbackend.common.utils.FileUtil; import com.edgec.browserbackend.common.utils.FileUtil;
import javax.annotation.Resource;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
...@@ -106,6 +108,9 @@ public class AccountServiceImpl implements AccountService { ...@@ -106,6 +108,9 @@ public class AccountServiceImpl implements AccountService {
@Autowired @Autowired
private GlobalFieldRepository globalFieldRepository; private GlobalFieldRepository globalFieldRepository;
@Resource
private UserRepository userRepository;
@Override @Override
public List<UserBillList> getUserBills0(String name) { public List<UserBillList> getUserBills0(String name) {
...@@ -433,7 +438,7 @@ public class AccountServiceImpl implements AccountService { ...@@ -433,7 +438,7 @@ public class AccountServiceImpl implements AccountService {
} }
// 2. 校验用户输入的短信验证码是否正确 // 2. 校验用户输入的短信验证码是否正确
Otp otp = otpRepository.findByPhoneAndCreatedAtGreaterThanEqual(user.getUsername(), Instant.now().minusSeconds(600).toEpochMilli()); Otp otp = otpRepository.findByPhoneAndTypeAndCreatedAtGreaterThanEqual(user.getUsername(), 1, Instant.now().minusSeconds(600).toEpochMilli());
if (otp == null) { if (otp == null) {
throw new ClientRequestException(AccountErrorCode.OTPWRONG, AccountErrorCode.OTPWRONG.getReason()); throw new ClientRequestException(AccountErrorCode.OTPWRONG, AccountErrorCode.OTPWRONG.getReason());
} }
...@@ -881,7 +886,7 @@ public class AccountServiceImpl implements AccountService { ...@@ -881,7 +886,7 @@ public class AccountServiceImpl implements AccountService {
@Override @Override
public void resetPasswordWithOtp(UserDto user) { public void resetPasswordWithOtp(UserDto user) {
Otp otp = otpRepository.findByPhoneAndCreatedAtGreaterThanEqual(user.getUsername(), Otp otp = otpRepository.findByPhoneAndTypeAndCreatedAtGreaterThanEqual(user.getUsername(), 2,
Instant.now().minusSeconds(600).toEpochMilli()); Instant.now().minusSeconds(600).toEpochMilli());
if (otp == null) { if (otp == null) {
throw new ClientRequestException(AccountErrorCode.OTPWRONG, AccountErrorCode.OTPWRONG.getReason()); throw new ClientRequestException(AccountErrorCode.OTPWRONG, AccountErrorCode.OTPWRONG.getReason());
...@@ -911,17 +916,34 @@ public class AccountServiceImpl implements AccountService { ...@@ -911,17 +916,34 @@ public class AccountServiceImpl implements AccountService {
} }
@Override @Override
public void sendSmsOtp(String phone) { public void sendSmsOtp(MobileDto mobileDto) {
// 1. 校验注册用户是否已存在 // 1. 校验注册用户是否已存在
Account existing1 = accountRepository.findByName(phone).orElse(null); String phone = mobileDto.getMobile();
Account existing2 = accountRepository.findOneByPhoneNumber(phone); if(mobileDto.getType() == 1) {
if (existing1 != null || existing2 != null) { Account existing1 = accountRepository.findByName(phone).orElse(null);
throw new ClientRequestException(AccountErrorCode.NAMEEXIST, "account already exists: " + phone); Account existing2 = accountRepository.findOneByPhoneNumber(phone);
if (existing1 != null || existing2 != null) {
throw new ClientRequestException(AccountErrorCode.NAMEEXIST,
"account already exists: " + phone);
}
}else {
com.edgec.browserbackend.auth.domain.User user = userRepository.findByUsernameAndEnabled(
phone, true).orElse(null);
if(null == user){
throw new ClientRequestException(AccountErrorCode.NAMENOTEXIST,
"account already exists: " + phone);
}
} }
String code = SmsUtils.sendSmsOTP(phone, SmsTemplateCode.NEWACCOUNT); SmsTemplateCode smsTemplateCode = SmsTemplateCode.getByType(mobileDto.getType());
if(null == smsTemplateCode){
throw new ClientRequestException(AccountErrorCode.OTHERS,
"authCode type error" + phone);
}
String code = SmsUtils.sendSmsOTP(phone, smsTemplateCode);
Otp otp = new Otp(); Otp otp = new Otp();
otp.setPhone(phone); otp.setPhone(phone);
otp.setOtp(code); otp.setOtp(code);
otp.setType(mobileDto.getType());
otp.setCreatedAt(Instant.now().toEpochMilli()); otp.setCreatedAt(Instant.now().toEpochMilli());
otpRepository.save(otp); otpRepository.save(otp);
} }
...@@ -1170,25 +1192,6 @@ public class AccountServiceImpl implements AccountService { ...@@ -1170,25 +1192,6 @@ public class AccountServiceImpl implements AccountService {
} }
} }
@Override
public void sendSmsOtpReset(String phone) {
String code = com.edgec.browserbackend.account.service.SmsUtils.sendSmsOTP(phone, SmsTemplateCode.RESETACCOUNT);
Otp otp = new Otp();
otp.setPhone(phone);
otp.setOtp(code);
otp.setCreatedAt(Instant.now().toEpochMilli());
otpRepository.save(otp);
}
@Override
public void loginAuthCode(String mobile) {
// Otp otp = new Otp();
// otp.setPhone(phone);
// otp.setOtp(code);
// otp.setCreatedAt(Instant.now().toEpochMilli());
//otpRepository.save(otp);
}
private void notifyCustomerRegister(Account contactUs) { private void notifyCustomerRegister(Account contactUs) {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append("Name: " + contactUs.getName() + "<br/>"); sb.append("Name: " + contactUs.getName() + "<br/>");
......
package com.edgec.browserbackend.auth.config; package com.edgec.browserbackend.auth.config;
import com.edgec.browserbackend.account.repository.AccountRepository;
import com.edgec.browserbackend.account.repository.OtpRepository;
import com.edgec.browserbackend.auth.repository.UserRepository;
import com.edgec.browserbackend.auth.service.MongoTokenStore; import com.edgec.browserbackend.auth.service.MongoTokenStore;
import com.edgec.browserbackend.auth.service.security.MongoUserDetailsService; import com.edgec.browserbackend.auth.service.security.MongoUserDetailsService;
import java.util.ArrayList; import java.util.ArrayList;
...@@ -36,7 +39,6 @@ import org.springframework.security.oauth2.provider.token.AuthorizationServerTok ...@@ -36,7 +39,6 @@ import org.springframework.security.oauth2.provider.token.AuthorizationServerTok
@EnableAuthorizationServer @EnableAuthorizationServer
public class OAuth2AuthorizationNewConfig extends AuthorizationServerConfigurerAdapter { public class OAuth2AuthorizationNewConfig extends AuthorizationServerConfigurerAdapter {
private final String NOOP_PASSWORD_ENCODE = "{noop}";
@Autowired @Autowired
private MongoTokenStore mongoTokenStore; private MongoTokenStore mongoTokenStore;
...@@ -54,6 +56,12 @@ public class OAuth2AuthorizationNewConfig extends AuthorizationServerConfigurerA ...@@ -54,6 +56,12 @@ public class OAuth2AuthorizationNewConfig extends AuthorizationServerConfigurerA
@Autowired @Autowired
private OAuthResponseExceptionTranslator oAuthResponseExceptionTranslator; private OAuthResponseExceptionTranslator oAuthResponseExceptionTranslator;
@Autowired
private OtpRepository otpRepository;
@Autowired
private UserRepository repository;
@Override @Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception { public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
...@@ -111,8 +119,7 @@ public class OAuth2AuthorizationNewConfig extends AuthorizationServerConfigurerA ...@@ -111,8 +119,7 @@ public class OAuth2AuthorizationNewConfig extends AuthorizationServerConfigurerA
} }
/** /**
* 这是从spring 的代码中 copy出来的, 默认的几个TokenGranter, 还原封不动加进去. * 覆盖原来的List<TokenGranter>,方便我们添加自定义的授权方式,比如SMSCodeTokenGranter短信验证码授权
* 主要目的是覆盖原来的List<TokenGranter>,方便我们添加自定义的授权方式,比如SMSCodeTokenGranter短信验证码授权
*/ */
private List<TokenGranter> getDefaultTokenGranters(AuthorizationServerEndpointsConfigurer endpoints) { private List<TokenGranter> getDefaultTokenGranters(AuthorizationServerEndpointsConfigurer endpoints) {
AuthorizationServerTokenServices tokenServices = endpoints.getDefaultAuthorizationServerTokenServices(); AuthorizationServerTokenServices tokenServices = endpoints.getDefaultAuthorizationServerTokenServices();
...@@ -131,9 +138,7 @@ public class OAuth2AuthorizationNewConfig extends AuthorizationServerConfigurerA ...@@ -131,9 +138,7 @@ public class OAuth2AuthorizationNewConfig extends AuthorizationServerConfigurerA
tokenGranters.add(new ResourceOwnerPasswordTokenGranter(authenticationManager, tokenGranters.add(new ResourceOwnerPasswordTokenGranter(authenticationManager,
tokenServices, endpoints.getClientDetailsService(), requestFactory)); tokenServices, endpoints.getClientDetailsService(), requestFactory));
} }
// 这里就是我们自己的授权验证 tokenGranters.add(new SMSCodeTokenGranter(tokenServices, endpoints.getClientDetailsService(), requestFactory, otpRepository, repository, "sms"));
tokenGranters.add(new SMSCodeTokenGranter(tokenServices, endpoints.getClientDetailsService(), requestFactory, "sms"));
// 再有其他的验证, 就往下面添加....
return tokenGranters; return tokenGranters;
} }
......
package com.edgec.browserbackend.auth.config; package com.edgec.browserbackend.auth.config;
import com.edgec.browserbackend.account.domain.Account;
import com.edgec.browserbackend.account.domain.Otp;
import com.edgec.browserbackend.account.exception.AccountErrorCode;
import com.edgec.browserbackend.account.repository.AccountRepository;
import com.edgec.browserbackend.account.repository.OtpRepository;
import com.edgec.browserbackend.auth.domain.User; import com.edgec.browserbackend.auth.domain.User;
import com.edgec.browserbackend.auth.domain.mongo.MongoOAuth2AccessToken; import com.edgec.browserbackend.auth.domain.mongo.MongoOAuth2AccessToken;
import com.edgec.browserbackend.auth.repository.UserRepository;
import com.edgec.browserbackend.auth.repository.mongo.MongoOAuth2AccessTokenRepository; import com.edgec.browserbackend.auth.repository.mongo.MongoOAuth2AccessTokenRepository;
import com.edgec.browserbackend.common.commons.error.ClientRequestException;
import java.time.Instant;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import javax.annotation.Resource;
import org.apache.tomcat.util.net.openssl.ciphers.Authentication; import org.apache.tomcat.util.net.openssl.ciphers.Authentication;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.provider.ClientDetails; import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.ClientDetailsService; import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.OAuth2Authentication; import org.springframework.security.oauth2.provider.OAuth2Authentication;
...@@ -17,6 +30,7 @@ import org.springframework.security.oauth2.provider.TokenGranter; ...@@ -17,6 +30,7 @@ import org.springframework.security.oauth2.provider.TokenGranter;
import org.springframework.security.oauth2.provider.TokenRequest; import org.springframework.security.oauth2.provider.TokenRequest;
import org.springframework.security.oauth2.provider.token.AbstractTokenGranter; import org.springframework.security.oauth2.provider.token.AbstractTokenGranter;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices; import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.stereotype.Component;
/** /**
* XXXX * XXXX
...@@ -26,21 +40,44 @@ import org.springframework.security.oauth2.provider.token.AuthorizationServerTok ...@@ -26,21 +40,44 @@ import org.springframework.security.oauth2.provider.token.AuthorizationServerTok
*/ */
public class SMSCodeTokenGranter extends AbstractTokenGranter { public class SMSCodeTokenGranter extends AbstractTokenGranter {
public SMSCodeTokenGranter(AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory, String grantType) { private OtpRepository otpRepository;
private UserRepository repository;
public SMSCodeTokenGranter(
AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService,
OAuth2RequestFactory requestFactory, OtpRepository otpRepository , UserRepository repository,String grantType) {
this( tokenServices, clientDetailsService, requestFactory, grantType);
this.otpRepository = otpRepository;
this.repository = repository;
}
protected SMSCodeTokenGranter(AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService,
OAuth2RequestFactory requestFactory, String grantType) {
super(tokenServices, clientDetailsService, requestFactory, grantType); super(tokenServices, clientDetailsService, requestFactory, grantType);
} }
@Override @Override
protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) { protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {
Map<String, String> parameters = new LinkedHashMap<String, String>(tokenRequest.getRequestParameters()); Map<String, String> parameters = new LinkedHashMap<String, String>(tokenRequest.getRequestParameters());
String userMobileNo = parameters.get("mobile"); //客户端提交的用户名 String userName = parameters.get("username"); //客户端提交的用户名
String smsCode = parameters.get("smscode"); //客户端提交的验证码 String smsCode = parameters.get("smscode"); //客户端提交的验证码
User user = repository.findByUsernameAndEnabled(userName, true).orElse(null);
if (user == null) {
throw new ClientRequestException(AccountErrorCode.NAMENOTEXIST, "Username does not exist: " + userName);
}
/** 下面写自己的验证逻辑 */ // 2. 校验用户输入的短信验证码是否正确
Otp otp = otpRepository.findByPhoneAndTypeAndCreatedAtGreaterThanEqual(userName, 0, Instant.now().minusSeconds(300).toEpochMilli());
if (otp == null) {
throw new ClientRequestException(AccountErrorCode.OTPWRONG, AccountErrorCode.OTPWRONG.getReason());
}
if (!otp.getOtp().equals(smsCode)) {
throw new ClientRequestException(AccountErrorCode.OTPWRONG, AccountErrorCode.OTPWRONG.getReason());
}
User user = new User();
user.setPhone("11111111");
UsernamePasswordAuthenticationToken userAuth = new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities()); UsernamePasswordAuthenticationToken userAuth = new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
((AbstractAuthenticationToken) userAuth).setDetails(parameters); ((AbstractAuthenticationToken) userAuth).setDetails(parameters);
......
...@@ -33,7 +33,7 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter { ...@@ -33,7 +33,7 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override @Override
public void configure(WebSecurity web) throws Exception { public void configure(WebSecurity web) throws Exception {
web.ignoring() web.ignoring()
.antMatchers("/user/authCode", "/user/signUp", "/user/resetAuthCode", .antMatchers("/user/authCode", "/user/signUp",
"/user/forgot**", "/0xadministrator/getconfig**"); "/user/forgot**", "/0xadministrator/getconfig**");
} }
......
...@@ -12,4 +12,6 @@ public interface UserRepository extends CrudRepository<User, String> { ...@@ -12,4 +12,6 @@ public interface UserRepository extends CrudRepository<User, String> {
Optional<User> findByEmail(String email); Optional<User> findByEmail(String email);
Optional<User> findByPhone(String phone); Optional<User> findByPhone(String phone);
Optional<User> findByUsernameAndEnabled(String userName, boolean enabled);
} }
...@@ -62,7 +62,7 @@ class BrowserBackendApplicationTests { ...@@ -62,7 +62,7 @@ class BrowserBackendApplicationTests {
JSONObject param = new JSONObject(); JSONObject param = new JSONObject();
param.put("day", "7"); param.put("day", "7");
param.put("amount", "5"); param.put("amount", "5");
SmsUtils.sendIpSms("18711016574", SmsUtils.SmsTemplateCode.IPWILLEXPIRE_EXPIRE, param); //SmsUtils.sendIpSms("18711016574", SmsUtils.SmsTemplateCode.IPWILLEXPIRE_EXPIRE, param);
} }
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment