@ -21,17 +21,15 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j ;
import org.springframework.beans.factory.annotation.Value ;
import org.springframework.context.ApplicationEventPublisher ;
import org.springframework.http.HttpHeaders ;
import org.springframework.http.HttpStatus ;
import org.springframework.http.ResponseEntity ;
import org.springframework.security.access.prepost.PreAuthorize ;
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.RequestMapping ;
import org.springframework.web.bind.annotation.RequestMethod ;
import org.springframework.web.bind.annotation.RequestParam ;
import org.springframework.web.bind.annotation.ResponseBody ;
import org.springframework.web.bind.annotation.ResponseStatus ;
import org.springframework.web.bind.annotation.RestController ;
import org.thingsboard.rule.engine.api.MailService ;
import org.thingsboard.server.cache.limits.RateLimitService ;
@ -48,6 +46,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.UserPasswordPolicy ;
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.service.security.auth.rest.RestAuthenticationDetails ;
import org.thingsboard.server.service.security.model.ActivateUserRequest ;
@ -59,9 +58,6 @@ import org.thingsboard.server.service.security.model.UserPrincipal;
import org.thingsboard.server.service.security.model.token.JwtTokenFactory ;
import org.thingsboard.server.service.security.system.SystemSecurityService ;
import java.net.URI ;
import java.net.URISyntaxException ;
@RestController
@TbCoreComponent
@RequestMapping ( "/api" )
@ -75,6 +71,7 @@ public class AuthController extends BaseController {
private final JwtTokenFactory tokenFactory ;
private final MailService mailService ;
private final SystemSecurityService systemSecurityService ;
private final SecuritySettingsService securitySettingsService ;
private final RateLimitService rateLimitService ;
private final ApplicationEventPublisher eventPublisher ;
@ -82,9 +79,8 @@ public class AuthController extends BaseController {
@ApiOperation ( value = "Get current User (getUser)" ,
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')" )
@RequestMapping ( value = "/auth/user" , method = RequestMethod . GET )
public @ResponseBody
User getUser ( ) throws ThingsboardException {
@GetMapping ( value = "/auth/user" )
public User getUser ( ) throws ThingsboardException {
SecurityUser securityUser = getCurrentUser ( ) ;
return userService . findUserById ( securityUser . getTenantId ( ) , securityUser . getId ( ) ) ;
}
@ -92,8 +88,7 @@ public class AuthController extends BaseController {
@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. " )
@PreAuthorize ( "hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')" )
@RequestMapping ( value = "/auth/logout" , method = RequestMethod . POST )
@ResponseStatus ( value = HttpStatus . OK )
@PostMapping ( value = "/auth/logout" )
public void logout ( HttpServletRequest request ) throws ThingsboardException {
logLogoutAction ( request ) ;
}
@ -101,8 +96,7 @@ public class AuthController extends BaseController {
@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." )
@PreAuthorize ( "hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')" )
@RequestMapping ( value = "/auth/changePassword" , method = RequestMethod . POST )
@ResponseStatus ( value = HttpStatus . OK )
@PostMapping ( value = "/auth/changePassword" )
public JwtPair changePassword ( @Parameter ( description = "Change Password Request" )
@RequestBody ChangePasswordRequest changePasswordRequest ) throws ThingsboardException {
String currentPassword = changePasswordRequest . getCurrentPassword ( ) ;
@ -125,46 +119,34 @@ public class AuthController extends BaseController {
@ApiOperation ( value = "Get the current User password policy (getUserPasswordPolicy)" ,
notes = "API call to get the password policy for the password validation form(s)." )
@RequestMapping ( value = "/noauth/userPasswordPolicy" , method = RequestMethod . GET )
@ResponseBody
@GetMapping ( value = "/noauth/userPasswordPolicy" )
public UserPasswordPolicy getUserPasswordPolicy ( ) throws ThingsboardException {
SecuritySettings securitySettings =
checkNotNull ( systemSecurityService . getSecuritySettings ( ) ) ;
SecuritySettings securitySettings = checkNotNull ( securitySettingsService . getSecuritySettings ( ) ) ;
return securitySettings . getPasswordPolicy ( ) ;
}
@ApiOperation ( value = "Check Activate User Token (checkActivateToken)" ,
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 not valid, returns '409 Conflict'." )
@RequestMapping ( value = "/noauth/activate" , params = { "activateToken" } , method = RequestMethod . GET )
public ResponseEntity < String > checkActivateToken (
"If token is not valid, returns '409 Conflict'. " +
"If token is expired, redirects to error page." )
@GetMapping ( value = "/noauth/activate" , params = { "activateToken" } )
public ResponseEntity < ? > checkActivateToken (
@Parameter ( description = "The activate token string." )
@RequestParam ( value = "activateToken" ) String activateToken ) {
HttpHeaders headers = new HttpHeaders ( ) ;
HttpStatus responseStatus ;
UserCredentials userCredentials = userService . findUserCredentialsByActivateToken ( TenantId . SYS_TENANT_ID , activateToken ) ;
if ( userCredentials ! = null ) {
String createURI = "/login/createPassword" ;
try {
URI location = new URI ( createURI + "?activateToken=" + activateToken ) ;
headers . setLocation ( location ) ;
responseStatus = HttpStatus . SEE_OTHER ;
} catch ( URISyntaxException e ) {
log . error ( "Unable to create URI with address [{}]" , createURI ) ;
responseStatus = HttpStatus . BAD_REQUEST ;
}
} else {
responseStatus = HttpStatus . CONFLICT ;
if ( userCredentials = = null ) {
return response ( HttpStatus . CONFLICT ) ;
} else if ( userCredentials . isActivationTokenExpired ( ) ) {
return redirectTo ( "/activationLinkExpired" ) ;
}
return new ResponseEntity < > ( headers , responseStatus ) ;
return redirectTo ( "/login/createPassword?activateToken=" + activateToken ) ;
}
@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. " +
"Always return '200 OK' status for security purposes." )
@RequestMapping ( value = "/noauth/resetPasswordByEmail" , method = RequestMethod . POST )
@ResponseStatus ( value = HttpStatus . OK )
@PostMapping ( value = "/noauth/resetPasswordByEmail" )
public void requestResetPasswordByEmail (
@Parameter ( description = "The JSON object representing the reset password email request." )
@RequestBody ResetPasswordEmailRequest resetPasswordByEmailRequest ,
@ -177,7 +159,7 @@ public class AuthController extends BaseController {
String resetUrl = String . format ( "%s/api/noauth/resetPassword?resetToken=%s" , baseUrl ,
userCredentials . getResetToken ( ) ) ;
mailService . sendResetPasswordEmailAsync ( resetUrl , email ) ;
mailService . sendResetPasswordEmailAsync ( resetUrl , userCredentials . getResetTokenTtl ( ) , email ) ;
} catch ( Exception e ) {
log . warn ( "Error occurred: {}" , e . getMessage ( ) ) ;
}
@ -186,32 +168,22 @@ public class AuthController extends BaseController {
@ApiOperation ( value = "Check password reset token (checkResetToken)" ,
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 not valid, returns '409 Conflict'." )
@RequestMapping ( value = "/noauth/resetPassword" , params = { "resetToken" } , method = RequestMethod . GET )
public ResponseEntity < String > checkResetToken (
"If token is not valid, returns '409 Conflict'. " +
"If token is expired, redirects to error page." )
@GetMapping ( value = "/noauth/resetPassword" , params = { "resetToken" } )
public ResponseEntity < ? > checkResetToken (
@Parameter ( description = "The reset token string." )
@RequestParam ( value = "resetToken" ) String resetToken ) {
HttpHeaders headers = new HttpHeaders ( ) ;
HttpStatus responseStatus ;
String resetURI = "/login/resetPassword" ;
UserCredentials userCredentials = userService . findUserCredentialsByResetToken ( TenantId . SYS_TENANT_ID , resetToken ) ;
if ( userCredentials ! = null ) {
if ( ! rateLimitService . checkRateLimit ( LimitedApi . PASSWORD_RESET , userCredentials . getUserId ( ) , defaultLimitsConfiguration ) ) {
return ResponseEntity . status ( HttpStatus . TOO_MANY_REQUESTS ) . build ( ) ;
}
try {
URI location = new URI ( resetURI + "?resetToken=" + resetToken ) ;
headers . setLocation ( location ) ;
responseStatus = HttpStatus . SEE_OTHER ;
} catch ( URISyntaxException e ) {
log . error ( "Unable to create URI with address [{}]" , resetURI ) ;
responseStatus = HttpStatus . BAD_REQUEST ;
}
} else {
responseStatus = HttpStatus . CONFLICT ;
if ( userCredentials = = null ) {
return response ( HttpStatus . CONFLICT ) ;
} else if ( userCredentials . isResetTokenExpired ( ) ) {
return redirectTo ( "/passwordResetLinkExpired" ) ;
}
if ( ! rateLimitService . checkRateLimit ( LimitedApi . PASSWORD_RESET , userCredentials . getUserId ( ) , defaultLimitsConfiguration ) ) {
return response ( HttpStatus . TOO_MANY_REQUESTS ) ;
}
return new ResponseEntity < > ( headers , responseStatus ) ;
return redirectTo ( "/login/resetPassword?resetToken=" + resetToken ) ;
}
@ApiOperation ( value = "Activate User" ,
@ -220,15 +192,12 @@ public class AuthController extends BaseController {
"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. " +
"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'." )
@RequestMapping ( value = "/noauth/activate" , method = RequestMethod . POST )
@ResponseStatus ( value = HttpStatus . OK )
@ResponseBody
public JwtPair activateUser (
@Parameter ( description = "Activate user request." )
@RequestBody ActivateUserRequest activateRequest ,
@RequestParam ( required = false , defaultValue = "true" ) boolean sendActivationMail ,
HttpServletRequest request ) throws ThingsboardException {
"If token is not valid, returns '400 Bad Request'." )
@PostMapping ( value = "/noauth/activate" )
public JwtPair activateUser ( @Parameter ( description = "Activate user request." )
@RequestBody ActivateUserRequest activateRequest ,
@RequestParam ( required = false , defaultValue = "true" ) boolean sendActivationMail ,
HttpServletRequest request ) {
String activateToken = activateRequest . getActivateToken ( ) ;
String password = activateRequest . getPassword ( ) ;
systemSecurityService . validatePassword ( password , null ) ;
@ -258,18 +227,18 @@ public class AuthController extends BaseController {
@ApiOperation ( value = "Reset password (resetPassword)" ,
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 not valid, returns '404 Bad Request'." )
@RequestMapping ( value = "/noauth/resetPassword" , method = RequestMethod . POST )
@ResponseStatus ( value = HttpStatus . OK )
@ResponseBody
public JwtPair resetPassword (
@Parameter ( description = "Reset password request." )
@RequestBody ResetPasswordRequest resetPasswordRequest ,
HttpServletRequest request ) throws ThingsboardException {
"If token is not valid, returns '400 Bad Request'." )
@PostMapping ( value = "/noauth/resetPassword" )
public JwtPair resetPassword ( @Parameter ( description = "Reset password request." )
@RequestBody ResetPasswordRequest resetPasswordRequest ,
HttpServletRequest request ) throws ThingsboardException {
String resetToken = resetPasswordRequest . getResetToken ( ) ;
String password = resetPasswordRequest . getPassword ( ) ;
UserCredentials userCredentials = userService . findUserCredentialsByResetToken ( TenantId . SYS_TENANT_ID , resetToken ) ;
if ( userCredentials ! = null ) {
if ( userCredentials . isResetTokenExpired ( ) ) {
throw new ThingsboardException ( "Password reset token expired" , ThingsboardErrorCode . BAD_REQUEST_PARAMS ) ;
}
systemSecurityService . validatePassword ( password , userCredentials ) ;
if ( passwordEncoder . matches ( password , userCredentials . getPassword ( ) ) ) {
throw new ThingsboardException ( "New password should be different from existing!" , ThingsboardErrorCode . BAD_REQUEST_PARAMS ) ;
@ -277,6 +246,7 @@ public class AuthController extends BaseController {
String encodedPassword = passwordEncoder . encode ( password ) ;
userCredentials . setPassword ( encodedPassword ) ;
userCredentials . setResetToken ( null ) ;
userCredentials . setResetTokenExpTime ( null ) ;
userCredentials = userService . replaceUserCredentials ( TenantId . SYS_TENANT_ID , userCredentials ) ;
User user = userService . findUserById ( TenantId . SYS_TENANT_ID , userCredentials . getUserId ( ) ) ;
UserPrincipal principal = new UserPrincipal ( UserPrincipal . Type . USER_NAME , user . getEmail ( ) ) ;