@ -26,9 +26,10 @@ import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity ;
import org.springframework.http.ResponseEntity ;
import org.springframework.security.access.prepost.PreAuthorize ;
import org.springframework.security.access.prepost.PreAuthorize ;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder ;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder ;
import org.springframework.web.bind.annotation.GetMapping ;
import org.springframework.web.bind.annotation.PostMapping ;
import org.springframework.web.bind.annotation.RequestBody ;
import org.springframework.web.bind.annotation.RequestBody ;
import org.springframework.web.bind.annotation.RequestMapping ;
import org.springframework.web.bind.annotation.RequestMapping ;
import org.springframework.web.bind.annotation.RequestMethod ;
import org.springframework.web.bind.annotation.RequestParam ;
import org.springframework.web.bind.annotation.RequestParam ;
import org.springframework.web.bind.annotation.ResponseBody ;
import org.springframework.web.bind.annotation.ResponseBody ;
import org.springframework.web.bind.annotation.ResponseStatus ;
import org.springframework.web.bind.annotation.ResponseStatus ;
@ -48,6 +49,7 @@ import org.thingsboard.server.common.data.security.model.JwtPair;
import org.thingsboard.server.common.data.security.model.SecuritySettings ;
import org.thingsboard.server.common.data.security.model.SecuritySettings ;
import org.thingsboard.server.common.data.security.model.UserPasswordPolicy ;
import org.thingsboard.server.common.data.security.model.UserPasswordPolicy ;
import org.thingsboard.server.config.annotations.ApiOperation ;
import org.thingsboard.server.config.annotations.ApiOperation ;
import org.thingsboard.server.dao.settings.SecuritySettingsService ;
import org.thingsboard.server.queue.util.TbCoreComponent ;
import org.thingsboard.server.queue.util.TbCoreComponent ;
import org.thingsboard.server.service.security.auth.rest.RestAuthenticationDetails ;
import org.thingsboard.server.service.security.auth.rest.RestAuthenticationDetails ;
import org.thingsboard.server.service.security.model.ActivateUserRequest ;
import org.thingsboard.server.service.security.model.ActivateUserRequest ;
@ -75,6 +77,7 @@ public class AuthController extends BaseController {
private final JwtTokenFactory tokenFactory ;
private final JwtTokenFactory tokenFactory ;
private final MailService mailService ;
private final MailService mailService ;
private final SystemSecurityService systemSecurityService ;
private final SystemSecurityService systemSecurityService ;
private final SecuritySettingsService securitySettingsService ;
private final RateLimitService rateLimitService ;
private final RateLimitService rateLimitService ;
private final ApplicationEventPublisher eventPublisher ;
private final ApplicationEventPublisher eventPublisher ;
@ -82,7 +85,7 @@ public class AuthController extends BaseController {
@ApiOperation ( value = "Get current User (getUser)" ,
@ApiOperation ( value = "Get current User (getUser)" ,
notes = "Get the information about the User which credentials are used to perform this REST API call." )
notes = "Get the information about the User which credentials are used to perform this REST API call." )
@PreAuthorize ( "hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')" )
@PreAuthorize ( "hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')" )
@Reques tMapping ( value = "/auth/user" , method = RequestMethod . GET )
@Ge tMapping ( value = "/auth/user" )
public @ResponseBody
public @ResponseBody
User getUser ( ) throws ThingsboardException {
User getUser ( ) throws ThingsboardException {
SecurityUser securityUser = getCurrentUser ( ) ;
SecurityUser securityUser = getCurrentUser ( ) ;
@ -92,7 +95,7 @@ public class AuthController extends BaseController {
@ApiOperation ( value = "Logout (logout)" ,
@ApiOperation ( value = "Logout (logout)" ,
notes = "Special API call to record the 'logout' of the user to the Audit Logs. Since platform uses [JWT](https://jwt.io/), the actual logout is the procedure of clearing the [JWT](https://jwt.io/) token on the client side. " )
notes = "Special API call to record the 'logout' of the user to the Audit Logs. Since platform uses [JWT](https://jwt.io/), the actual logout is the procedure of clearing the [JWT](https://jwt.io/) token on the client side. " )
@PreAuthorize ( "hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')" )
@PreAuthorize ( "hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')" )
@Reque stMapping ( value = "/auth/logout" , method = RequestMethod . POST )
@Po stMapping ( value = "/auth/logout" )
@ResponseStatus ( value = HttpStatus . OK )
@ResponseStatus ( value = HttpStatus . OK )
public void logout ( HttpServletRequest request ) throws ThingsboardException {
public void logout ( HttpServletRequest request ) throws ThingsboardException {
logLogoutAction ( request ) ;
logLogoutAction ( request ) ;
@ -101,8 +104,7 @@ public class AuthController extends BaseController {
@ApiOperation ( value = "Change password for current User (changePassword)" ,
@ApiOperation ( value = "Change password for current User (changePassword)" ,
notes = "Change the password for the User which credentials are used to perform this REST API call. Be aware that previously generated [JWT](https://jwt.io/) tokens will be still valid until they expire." )
notes = "Change the password for the User which credentials are used to perform this REST API call. Be aware that previously generated [JWT](https://jwt.io/) tokens will be still valid until they expire." )
@PreAuthorize ( "hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')" )
@PreAuthorize ( "hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')" )
@RequestMapping ( value = "/auth/changePassword" , method = RequestMethod . POST )
@PostMapping ( value = "/auth/changePassword" )
@ResponseStatus ( value = HttpStatus . OK )
public JwtPair changePassword ( @Parameter ( description = "Change Password Request" )
public JwtPair changePassword ( @Parameter ( description = "Change Password Request" )
@RequestBody ChangePasswordRequest changePasswordRequest ) throws ThingsboardException {
@RequestBody ChangePasswordRequest changePasswordRequest ) throws ThingsboardException {
String currentPassword = changePasswordRequest . getCurrentPassword ( ) ;
String currentPassword = changePasswordRequest . getCurrentPassword ( ) ;
@ -125,11 +127,9 @@ public class AuthController extends BaseController {
@ApiOperation ( value = "Get the current User password policy (getUserPasswordPolicy)" ,
@ApiOperation ( value = "Get the current User password policy (getUserPasswordPolicy)" ,
notes = "API call to get the password policy for the password validation form(s)." )
notes = "API call to get the password policy for the password validation form(s)." )
@RequestMapping ( value = "/noauth/userPasswordPolicy" , method = RequestMethod . GET )
@GetMapping ( value = "/noauth/userPasswordPolicy" )
@ResponseBody
public UserPasswordPolicy getUserPasswordPolicy ( ) throws ThingsboardException {
public UserPasswordPolicy getUserPasswordPolicy ( ) throws ThingsboardException {
SecuritySettings securitySettings =
SecuritySettings securitySettings = checkNotNull ( securitySettingsService . getSecuritySettings ( ) ) ;
checkNotNull ( systemSecurityService . getSecuritySettings ( ) ) ;
return securitySettings . getPasswordPolicy ( ) ;
return securitySettings . getPasswordPolicy ( ) ;
}
}
@ -137,14 +137,14 @@ public class AuthController extends BaseController {
notes = "Checks the activation token and forwards user to 'Create Password' page. " +
notes = "Checks the activation token and forwards user to 'Create Password' page. " +
"If token is valid, returns '303 See Other' (redirect) response code with the correct address of 'Create Password' page and same 'activateToken' specified in the URL parameters. " +
"If token is valid, returns '303 See Other' (redirect) response code with the correct address of 'Create Password' page and same 'activateToken' specified in the URL parameters. " +
"If token is not valid, returns '409 Conflict'." )
"If token is not valid, returns '409 Conflict'." )
@Reques tMapping ( value = "/noauth/activate" , params = { "activateToken" } , method = RequestMethod . GET )
@Ge tMapping ( value = "/noauth/activate" , params = { "activateToken" } )
public ResponseEntity < String > checkActivateToken (
public ResponseEntity < String > checkActivateToken (
@Parameter ( description = "The activate token string." )
@Parameter ( description = "The activate token string." )
@RequestParam ( value = "activateToken" ) String activateToken ) {
@RequestParam ( value = "activateToken" ) String activateToken ) {
HttpHeaders headers = new HttpHeaders ( ) ;
HttpHeaders headers = new HttpHeaders ( ) ;
HttpStatus responseStatus ;
HttpStatus responseStatus ;
UserCredentials userCredentials = userService . findUserCredentialsByActivateToken ( TenantId . SYS_TENANT_ID , activateToken ) ;
UserCredentials userCredentials = userService . findUserCredentialsByActivateToken ( TenantId . SYS_TENANT_ID , activateToken ) ;
if ( userCredentials ! = null ) {
if ( userCredentials ! = null & & ! userCredentials . isActivationTokenExpired ( ) ) {
String createURI = "/login/createPassword" ;
String createURI = "/login/createPassword" ;
try {
try {
URI location = new URI ( createURI + "?activateToken=" + activateToken ) ;
URI location = new URI ( createURI + "?activateToken=" + activateToken ) ;
@ -163,8 +163,7 @@ public class AuthController extends BaseController {
@ApiOperation ( value = "Request reset password email (requestResetPasswordByEmail)" ,
@ApiOperation ( value = "Request reset password email (requestResetPasswordByEmail)" ,
notes = "Request to send the reset password email if the user with specified email address is present in the database. " +
notes = "Request to send the reset password email if the user with specified email address is present in the database. " +
"Always return '200 OK' status for security purposes." )
"Always return '200 OK' status for security purposes." )
@RequestMapping ( value = "/noauth/resetPasswordByEmail" , method = RequestMethod . POST )
@PostMapping ( value = "/noauth/resetPasswordByEmail" )
@ResponseStatus ( value = HttpStatus . OK )
public void requestResetPasswordByEmail (
public void requestResetPasswordByEmail (
@Parameter ( description = "The JSON object representing the reset password email request." )
@Parameter ( description = "The JSON object representing the reset password email request." )
@RequestBody ResetPasswordEmailRequest resetPasswordByEmailRequest ,
@RequestBody ResetPasswordEmailRequest resetPasswordByEmailRequest ,
@ -187,7 +186,7 @@ public class AuthController extends BaseController {
notes = "Checks the password reset token and forwards user to 'Reset Password' page. " +
notes = "Checks the password reset token and forwards user to 'Reset Password' page. " +
"If token is valid, returns '303 See Other' (redirect) response code with the correct address of 'Reset Password' page and same 'resetToken' specified in the URL parameters. " +
"If token is valid, returns '303 See Other' (redirect) response code with the correct address of 'Reset Password' page and same 'resetToken' specified in the URL parameters. " +
"If token is not valid, returns '409 Conflict'." )
"If token is not valid, returns '409 Conflict'." )
@Reques tMapping ( value = "/noauth/resetPassword" , params = { "resetToken" } , method = RequestMethod . GET )
@Ge tMapping ( value = "/noauth/resetPassword" , params = { "resetToken" } )
public ResponseEntity < String > checkResetToken (
public ResponseEntity < String > checkResetToken (
@Parameter ( description = "The reset token string." )
@Parameter ( description = "The reset token string." )
@RequestParam ( value = "resetToken" ) String resetToken ) {
@RequestParam ( value = "resetToken" ) String resetToken ) {
@ -196,7 +195,7 @@ public class AuthController extends BaseController {
String resetURI = "/login/resetPassword" ;
String resetURI = "/login/resetPassword" ;
UserCredentials userCredentials = userService . findUserCredentialsByResetToken ( TenantId . SYS_TENANT_ID , resetToken ) ;
UserCredentials userCredentials = userService . findUserCredentialsByResetToken ( TenantId . SYS_TENANT_ID , resetToken ) ;
if ( userCredentials ! = null ) {
if ( userCredentials ! = null & & ! userCredentials . isResetTokenExpired ( ) ) {
if ( ! rateLimitService . checkRateLimit ( LimitedApi . PASSWORD_RESET , userCredentials . getUserId ( ) , defaultLimitsConfiguration ) ) {
if ( ! rateLimitService . checkRateLimit ( LimitedApi . PASSWORD_RESET , userCredentials . getUserId ( ) , defaultLimitsConfiguration ) ) {
return ResponseEntity . status ( HttpStatus . TOO_MANY_REQUESTS ) . build ( ) ;
return ResponseEntity . status ( HttpStatus . TOO_MANY_REQUESTS ) . build ( ) ;
}
}
@ -220,15 +219,12 @@ public class AuthController extends BaseController {
"The response already contains the [JWT](https://jwt.io) activation and refresh tokens, " +
"The response already contains the [JWT](https://jwt.io) activation and refresh tokens, " +
"to simplify the user activation flow and avoid asking user to input password again after activation. " +
"to simplify the user activation flow and avoid asking user to input password again after activation. " +
"If token is valid, returns the object that contains [JWT](https://jwt.io/) access and refresh tokens. " +
"If token is valid, returns the object that contains [JWT](https://jwt.io/) access and refresh tokens. " +
"If token is not valid, returns '404 Bad Request'." )
"If token is not valid, returns '400 Bad Request'." )
@RequestMapping ( value = "/noauth/activate" , method = RequestMethod . POST )
@PostMapping ( value = "/noauth/activate" )
@ResponseStatus ( value = HttpStatus . OK )
public JwtPair activateUser ( @Parameter ( description = "Activate user request." )
@ResponseBody
@RequestBody ActivateUserRequest activateRequest ,
public JwtPair activateUser (
@RequestParam ( required = false , defaultValue = "true" ) boolean sendActivationMail ,
@Parameter ( description = "Activate user request." )
HttpServletRequest request ) {
@RequestBody ActivateUserRequest activateRequest ,
@RequestParam ( required = false , defaultValue = "true" ) boolean sendActivationMail ,
HttpServletRequest request ) throws ThingsboardException {
String activateToken = activateRequest . getActivateToken ( ) ;
String activateToken = activateRequest . getActivateToken ( ) ;
String password = activateRequest . getPassword ( ) ;
String password = activateRequest . getPassword ( ) ;
systemSecurityService . validatePassword ( password , null ) ;
systemSecurityService . validatePassword ( password , null ) ;
@ -258,18 +254,18 @@ public class AuthController extends BaseController {
@ApiOperation ( value = "Reset password (resetPassword)" ,
@ApiOperation ( value = "Reset password (resetPassword)" ,
notes = "Checks the password reset token and updates the password. " +
notes = "Checks the password reset token and updates the password. " +
"If token is valid, returns the object that contains [JWT](https://jwt.io/) access and refresh tokens. " +
"If token is valid, returns the object that contains [JWT](https://jwt.io/) access and refresh tokens. " +
"If token is not valid, returns '404 Bad Request'." )
"If token is not valid, returns '400 Bad Request'." )
@RequestMapping ( value = "/noauth/resetPassword" , method = RequestMethod . POST )
@PostMapping ( value = "/noauth/resetPassword" )
@ResponseStatus ( value = HttpStatus . OK )
public JwtPair resetPassword ( @Parameter ( description = "Reset password request." )
@ResponseBody
@RequestBody ResetPasswordRequest resetPasswordRequest ,
public JwtPair resetPassword (
HttpServletRequest request ) throws ThingsboardException {
@Parameter ( description = "Reset password request." )
@RequestBody ResetPasswordRequest resetPasswordRequest ,
HttpServletRequest request ) throws ThingsboardException {
String resetToken = resetPasswordRequest . getResetToken ( ) ;
String resetToken = resetPasswordRequest . getResetToken ( ) ;
String password = resetPasswordRequest . getPassword ( ) ;
String password = resetPasswordRequest . getPassword ( ) ;
UserCredentials userCredentials = userService . findUserCredentialsByResetToken ( TenantId . SYS_TENANT_ID , resetToken ) ;
UserCredentials userCredentials = userService . findUserCredentialsByResetToken ( TenantId . SYS_TENANT_ID , resetToken ) ;
if ( userCredentials ! = null ) {
if ( userCredentials ! = null ) {
if ( userCredentials . isResetTokenExpired ( ) ) {
throw new ThingsboardException ( "Password reset token expired" , ThingsboardErrorCode . BAD_REQUEST_PARAMS ) ;
}
systemSecurityService . validatePassword ( password , userCredentials ) ;
systemSecurityService . validatePassword ( password , userCredentials ) ;
if ( passwordEncoder . matches ( password , userCredentials . getPassword ( ) ) ) {
if ( passwordEncoder . matches ( password , userCredentials . getPassword ( ) ) ) {
throw new ThingsboardException ( "New password should be different from existing!" , ThingsboardErrorCode . BAD_REQUEST_PARAMS ) ;
throw new ThingsboardException ( "New password should be different from existing!" , ThingsboardErrorCode . BAD_REQUEST_PARAMS ) ;
@ -277,6 +273,7 @@ public class AuthController extends BaseController {
String encodedPassword = passwordEncoder . encode ( password ) ;
String encodedPassword = passwordEncoder . encode ( password ) ;
userCredentials . setPassword ( encodedPassword ) ;
userCredentials . setPassword ( encodedPassword ) ;
userCredentials . setResetToken ( null ) ;
userCredentials . setResetToken ( null ) ;
userCredentials . setResetTokenExpTime ( null ) ;
userCredentials = userService . replaceUserCredentials ( TenantId . SYS_TENANT_ID , userCredentials ) ;
userCredentials = userService . replaceUserCredentials ( TenantId . SYS_TENANT_ID , userCredentials ) ;
User user = userService . findUserById ( TenantId . SYS_TENANT_ID , userCredentials . getUserId ( ) ) ;
User user = userService . findUserById ( TenantId . SYS_TENANT_ID , userCredentials . getUserId ( ) ) ;
UserPrincipal principal = new UserPrincipal ( UserPrincipal . Type . USER_NAME , user . getEmail ( ) ) ;
UserPrincipal principal = new UserPrincipal ( UserPrincipal . Type . USER_NAME , user . getEmail ( ) ) ;