@ -18,6 +18,8 @@ package org.thingsboard.server.controller;
import com.fasterxml.jackson.databind.JsonNode ;
import com.fasterxml.jackson.databind.ObjectMapper ;
import com.fasterxml.jackson.databind.node.ObjectNode ;
import io.swagger.annotations.ApiOperation ;
import io.swagger.annotations.ApiParam ;
import lombok.RequiredArgsConstructor ;
import lombok.extern.slf4j.Slf4j ;
import org.springframework.context.ApplicationEventPublisher ;
@ -48,8 +50,13 @@ import org.thingsboard.server.common.data.security.model.SecuritySettings;
import org.thingsboard.server.common.data.security.model.UserPasswordPolicy ;
import org.thingsboard.server.dao.audit.AuditLogService ;
import org.thingsboard.server.queue.util.TbCoreComponent ;
import org.thingsboard.server.service.security.model.ActivateUserRequest ;
import org.thingsboard.server.service.security.model.ChangePasswordRequest ;
import org.thingsboard.server.service.security.model.ResetPasswordEmailRequest ;
import org.thingsboard.server.service.security.model.ResetPasswordRequest ;
import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRepository ;
import org.thingsboard.server.service.security.auth.rest.RestAuthenticationDetails ;
import org.thingsboard.server.service.security.model.JwtTokenPair ;
import org.thingsboard.server.service.security.model.SecurityUser ;
import org.thingsboard.server.service.security.model.UserPrincipal ;
import org.thingsboard.server.service.security.model.token.JwtTokenFactory ;
@ -74,9 +81,13 @@ public class AuthController extends BaseController {
private final AuditLogService auditLogService ;
private final ApplicationEventPublisher eventPublisher ;
@ApiOperation ( value = "Get current User (getUser)" ,
notes = "Get the information about the User which credentials are used to perform this REST API call." )
@PreAuthorize ( "isAuthenticated()" )
@RequestMapping ( value = "/auth/user" , method = RequestMethod . GET )
public @ResponseBody User getUser ( ) throws ThingsboardException {
public @ResponseBody
User getUser ( ) throws ThingsboardException {
try {
SecurityUser securityUser = getCurrentUser ( ) ;
return userService . findUserById ( securityUser . getTenantId ( ) , securityUser . getId ( ) ) ;
@ -85,6 +96,8 @@ 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 ( "isAuthenticated()" )
@RequestMapping ( value = "/auth/logout" , method = RequestMethod . POST )
@ResponseStatus ( value = HttpStatus . OK )
@ -92,13 +105,17 @@ public class AuthController extends BaseController {
logLogoutAction ( request ) ;
}
@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 ( "isAuthenticated()" )
@RequestMapping ( value = "/auth/changePassword" , method = RequestMethod . POST )
@ResponseStatus ( value = HttpStatus . OK )
public ObjectNode changePassword ( @RequestBody JsonNode changePasswordRequest ) throws ThingsboardException {
public ObjectNode changePassword (
@ApiParam ( value = "Change Password Request" )
@RequestBody ChangePasswordRequest changePasswordRequest ) throws ThingsboardException {
try {
String currentPassword = changePasswordRequest . get ( "currentPassword" ) . asText ( ) ;
String newPassword = changePasswordRequest . get ( "newPassword" ) . asText ( ) ;
String currentPassword = changePasswordRequest . getCurrentPassword ( ) ;
String newPassword = changePasswordRequest . getNewPassword ( ) ;
SecurityUser securityUser = getCurrentUser ( ) ;
UserCredentials userCredentials = userService . findUserCredentialsByUserId ( TenantId . SYS_TENANT_ID , securityUser . getId ( ) ) ;
if ( ! passwordEncoder . matches ( currentPassword , userCredentials . getPassword ( ) ) ) {
@ -123,6 +140,8 @@ 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
public UserPasswordPolicy getUserPasswordPolicy ( ) throws ThingsboardException {
@ -135,8 +154,13 @@ public class AuthController extends BaseController {
}
}
@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 (
@ApiParam ( value = "The activate token string." )
@RequestParam ( value = "activateToken" ) String activateToken ) {
HttpHeaders headers = new HttpHeaders ( ) ;
HttpStatus responseStatus ;
@ -157,13 +181,17 @@ public class AuthController extends BaseController {
return new ResponseEntity < > ( headers , responseStatus ) ;
}
@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 )
public void requestResetPasswordByEmail (
@RequestBody JsonNode resetPasswordByEmailRequest ,
@ApiParam ( value = "The JSON object representing the reset password email request." )
@RequestBody ResetPasswordEmailRequest resetPasswordByEmailRequest ,
HttpServletRequest request ) throws ThingsboardException {
try {
String email = resetPasswordByEmailRequest . get ( "email" ) . asText ( ) ;
String email = resetPasswordByEmailRequest . getEmail ( ) ;
UserCredentials userCredentials = userService . requestPasswordReset ( TenantId . SYS_TENANT_ID , email ) ;
User user = userService . findUserById ( TenantId . SYS_TENANT_ID , userCredentials . getUserId ( ) ) ;
String baseUrl = systemSecurityService . getBaseUrl ( user . getTenantId ( ) , user . getCustomerId ( ) , request ) ;
@ -176,8 +204,13 @@ 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 (
@ApiParam ( value = "The reset token string." )
@RequestParam ( value = "resetToken" ) String resetToken ) {
HttpHeaders headers = new HttpHeaders ( ) ;
HttpStatus responseStatus ;
@ -198,16 +231,24 @@ public class AuthController extends BaseController {
return new ResponseEntity < > ( headers , responseStatus ) ;
}
@ApiOperation ( value = "Activate User" ,
notes = "Checks the activation token and updates corresponding user password in the database. " +
"Now the user may start using his password to login. " +
"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 JsonNode activateUser (
@RequestBody JsonNode activateRequest ,
public JwtTokenPair activateUser (
@ApiParam ( value = "Activate user request." )
@RequestBody ActivateUserRequest activateRequest ,
@RequestParam ( required = false , defaultValue = "true" ) boolean sendActivationMail ,
HttpServletRequest request ) throws ThingsboardException {
try {
String activateToken = activateRequest . get ( "activateToken" ) . asText ( ) ;
String password = activateRequest . get ( "password" ) . asText ( ) ;
String activateToken = activateRequest . getActivateToken ( ) ;
String password = activateRequest . getPassword ( ) ;
systemSecurityService . validatePassword ( TenantId . SYS_TENANT_ID , password , null ) ;
String encodedPassword = passwordEncoder . encode ( password ) ;
UserCredentials credentials = userService . activateUserCredentials ( TenantId . SYS_TENANT_ID , activateToken , encodedPassword ) ;
@ -232,25 +273,26 @@ public class AuthController extends BaseController {
JwtToken accessToken = tokenFactory . createAccessJwtToken ( securityUser ) ;
JwtToken refreshToken = refreshTokenRepository . requestRefreshToken ( securityUser ) ;
ObjectMapper objectMapper = new ObjectMapper ( ) ;
ObjectNode tokenObject = objectMapper . createObjectNode ( ) ;
tokenObject . put ( "token" , accessToken . getToken ( ) ) ;
tokenObject . put ( "refreshToken" , refreshToken . getToken ( ) ) ;
return tokenObject ;
return new JwtTokenPair ( accessToken . getToken ( ) , refreshToken . getToken ( ) ) ;
} catch ( Exception e ) {
throw handleException ( e ) ;
}
}
@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 JsonNode resetPassword (
@RequestBody JsonNode resetPasswordRequest ,
public JwtTokenPair resetPassword (
@ApiParam ( value = "Reset password request." )
@RequestBody ResetPasswordRequest resetPasswordRequest ,
HttpServletRequest request ) throws ThingsboardException {
try {
String resetToken = resetPasswordRequest . get ( "resetToken" ) . asText ( ) ;
String password = resetPasswordRequest . get ( "password" ) . asText ( ) ;
String resetToken = resetPasswordRequest . getResetToken ( ) ;
String password = resetPasswordRequest . getPassword ( ) ;
UserCredentials userCredentials = userService . findUserCredentialsByResetToken ( TenantId . SYS_TENANT_ID , resetToken ) ;
if ( userCredentials ! = null ) {
systemSecurityService . validatePassword ( TenantId . SYS_TENANT_ID , password , userCredentials ) ;
@ -273,11 +315,7 @@ public class AuthController extends BaseController {
JwtToken accessToken = tokenFactory . createAccessJwtToken ( securityUser ) ;
JwtToken refreshToken = refreshTokenRepository . requestRefreshToken ( securityUser ) ;
ObjectMapper objectMapper = new ObjectMapper ( ) ;
ObjectNode tokenObject = objectMapper . createObjectNode ( ) ;
tokenObject . put ( "token" , accessToken . getToken ( ) ) ;
tokenObject . put ( "refreshToken" , refreshToken . getToken ( ) ) ;
return tokenObject ;
return new JwtTokenPair ( accessToken . getToken ( ) , refreshToken . getToken ( ) ) ;
} else {
throw new ThingsboardException ( "Invalid reset token!" , ThingsboardErrorCode . BAD_REQUEST_PARAMS ) ;
}