mirror of
https://github.com/sismics/docs.git
synced 2025-12-17 19:51:39 +00:00
#84: TOTP key generation and validation code checking on login
This commit is contained in:
@@ -1,3 +1,3 @@
|
||||
api.current_version=${project.version}
|
||||
api.min_version=1.0
|
||||
db.version=8
|
||||
db.version=9
|
||||
@@ -255,6 +255,7 @@ public class UserResource extends BaseResource {
|
||||
public Response login(
|
||||
@FormParam("username") String username,
|
||||
@FormParam("password") String password,
|
||||
@FormParam("code") String validationCodeStr,
|
||||
@FormParam("remember") boolean longLasted) {
|
||||
// Validate the input data
|
||||
username = StringUtils.strip(username);
|
||||
@@ -262,10 +263,25 @@ public class UserResource extends BaseResource {
|
||||
|
||||
// Get the user
|
||||
UserDao userDao = new UserDao();
|
||||
String userId = userDao.authenticate(username, password);
|
||||
if (userId == null) {
|
||||
User user = userDao.authenticate(username, password);
|
||||
if (user == null) {
|
||||
throw new ForbiddenClientException();
|
||||
}
|
||||
|
||||
// Two factor authentication
|
||||
if (user.getTotpKey() != null) {
|
||||
// If TOTP is enabled, ask a validation code
|
||||
if (Strings.isNullOrEmpty(validationCodeStr)) {
|
||||
throw new ClientException("ValidationCodeRequired", "An OTP validation code is required");
|
||||
}
|
||||
|
||||
// Check the validation code
|
||||
int validationCode = ValidationUtil.validateInteger(validationCodeStr, "code");
|
||||
GoogleAuthenticator googleAuthenticator = new GoogleAuthenticator();
|
||||
if (!googleAuthenticator.authorize(user.getTotpKey(), validationCode)) {
|
||||
throw new ForbiddenClientException();
|
||||
}
|
||||
}
|
||||
|
||||
// Get the remote IP
|
||||
String ip = request.getHeader("x-forwarded-for");
|
||||
@@ -275,15 +291,15 @@ public class UserResource extends BaseResource {
|
||||
|
||||
// Create a new session token
|
||||
AuthenticationTokenDao authenticationTokenDao = new AuthenticationTokenDao();
|
||||
AuthenticationToken authenticationToken = new AuthenticationToken();
|
||||
authenticationToken.setUserId(userId);
|
||||
authenticationToken.setLongLasted(longLasted);
|
||||
authenticationToken.setIp(ip);
|
||||
authenticationToken.setUserAgent(StringUtils.abbreviate(request.getHeader("user-agent"), 1000));
|
||||
AuthenticationToken authenticationToken = new AuthenticationToken()
|
||||
.setUserId(user.getId())
|
||||
.setLongLasted(longLasted)
|
||||
.setIp(ip)
|
||||
.setUserAgent(StringUtils.abbreviate(request.getHeader("user-agent"), 1000));
|
||||
String token = authenticationTokenDao.create(authenticationToken);
|
||||
|
||||
// Cleanup old session tokens
|
||||
authenticationTokenDao.deleteOldSessionToken(userId);
|
||||
authenticationTokenDao.deleteOldSessionToken(user.getId());
|
||||
|
||||
JsonObjectBuilder response = Json.createObjectBuilder();
|
||||
int maxAge = longLasted ? TokenBasedSecurityFilter.TOKEN_LONG_LIFETIME : -1;
|
||||
@@ -648,18 +664,18 @@ public class UserResource extends BaseResource {
|
||||
throw new ForbiddenClientException();
|
||||
}
|
||||
|
||||
// Create a new TOTP key and scratch codes
|
||||
// Create a new TOTP key
|
||||
GoogleAuthenticator gAuth = new GoogleAuthenticator();
|
||||
final GoogleAuthenticatorKey key = gAuth.createCredentials();
|
||||
|
||||
JsonArrayBuilder scratchCodes = Json.createArrayBuilder();
|
||||
for (int scratchCode : key.getScratchCodes()) {
|
||||
scratchCodes.add(scratchCode);
|
||||
}
|
||||
// Save it
|
||||
UserDao userDao = new UserDao();
|
||||
User user = userDao.getActiveByUsername(principal.getName());
|
||||
user.setTotpKey(key.getKey());
|
||||
user = userDao.update(user, principal.getId());
|
||||
|
||||
JsonObjectBuilder response = Json.createObjectBuilder()
|
||||
.add("secret", key.getKey())
|
||||
.add("scratch_codes", scratchCodes);
|
||||
.add("secret", key.getKey());
|
||||
return Response.ok().entity(response.build()).build();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
api.current_version=${project.version}
|
||||
api.min_version=1.0
|
||||
db.version=8
|
||||
db.version=9
|
||||
@@ -1,3 +1,3 @@
|
||||
api.current_version=${project.version}
|
||||
api.min_version=1.0
|
||||
db.version=8
|
||||
db.version=9
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.sismics.docs.rest;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
|
||||
import javax.json.JsonArray;
|
||||
@@ -13,6 +14,7 @@ import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.sismics.util.filter.TokenBasedSecurityFilter;
|
||||
import com.sismics.util.totp.GoogleAuthenticator;
|
||||
|
||||
/**
|
||||
* Exhaustive test of the user resource.
|
||||
@@ -299,5 +301,27 @@ public class TestUserResource extends BaseJerseyTest {
|
||||
.post(Entity.form(new Form()), JsonObject.class);
|
||||
String secret = json.getString("secret");
|
||||
Assert.assertNotNull(secret);
|
||||
|
||||
// Try to login with totp1 without a validation code
|
||||
Response response = target().path("/user/login").request()
|
||||
.post(Entity.form(new Form()
|
||||
.param("username", "totp1")
|
||||
.param("password", "12345678")
|
||||
.param("remember", "false")));
|
||||
Assert.assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
|
||||
json = response.readEntity(JsonObject.class);
|
||||
Assert.assertEquals("ValidationCodeRequired", json.getString("type"));
|
||||
|
||||
// Generate a OTP
|
||||
GoogleAuthenticator googleAuthenticator = new GoogleAuthenticator();
|
||||
int validationCode = googleAuthenticator.calculateCode(secret, new Date().getTime() / 30000);
|
||||
|
||||
// Login with totp1 with a validation code
|
||||
json = target().path("/user/login").request()
|
||||
.post(Entity.form(new Form()
|
||||
.param("username", "totp1")
|
||||
.param("password", "12345678")
|
||||
.param("code", Integer.toString(validationCode))
|
||||
.param("remember", "false")), JsonObject.class);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user