Added user login and account creation

This commit is contained in:
Gnarwhal 2021-02-05 04:59:17 -05:00
parent 9ba8a99e82
commit 5a1dd33dfe
Signed by: Gnarwhal
GPG key ID: 0989A73D8C421174
19 changed files with 1276 additions and 874 deletions

View file

@ -1,5 +1,6 @@
package achievements;
import achievements.misc.Password;
import achievements.services.DbConnectionService;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

View file

@ -1,6 +1,7 @@
package achievements.controllers;
import achievements.data.Achievements;
import achievements.data.User;
import achievements.data.Games;
import achievements.data.InternalError;
import achievements.services.DbService;
@ -12,6 +13,7 @@ import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import static org.springframework.web.bind.annotation.RequestMethod.GET;
import static org.springframework.web.bind.annotation.RequestMethod.POST;
@RestController
public class Controller {
@ -62,4 +64,47 @@ public class Controller {
return new ResponseEntity("{}", HttpStatus.INTERNAL_SERVER_ERROR);
}
}
/**
* Acceptable codes
* 0 => Success
* 1 => Email already registered
*
* -1 => Unknown error
*/
@RequestMapping(value = "/create_user", method = POST, consumes = "application/json", produces = "application/json")
public ResponseEntity createUser(@RequestBody User user) {
var status = db.createUser(user);
if (status == 0) {
return ResponseEntity.ok("{ \"key\": \"aoeuhtns\" }");
//var sessionKey = db.generateSessionKey(user);
} else {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("{ \"code\": " + status + " }");
}
}
/**
* DO NOT RETURN CODE DIRECTLY!
*
* User should only ever recieve -1, 0, or 1. The specific authentication error should be hidden.
*
* Acceptable codes
* 0 => Success
* 1 => Unregistered email address
* 2 => Incorrect password
*
* -1 => Unknown error
*/
@RequestMapping(value = "/login", method = POST, consumes = "application/json", produces = "application/json")
public ResponseEntity login(@RequestBody User user) {
var status = db.login(user);
if (status == 0) {
return ResponseEntity.ok("{ \"key\": \"aoeuhtns\" }");
} else if (status > 0) {
// Hardcoded 1 response code
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("{ \"code\": 1 }");
} else {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("{ \"code\": " + status + " }");
}
}
}

View file

@ -0,0 +1,43 @@
package achievements.data;
import com.fasterxml.jackson.annotation.JsonProperty;
public class User {
@JsonProperty("email")
public String email;
@JsonProperty("username")
public String username;
@JsonProperty("password")
public String password;
public User(String email, String username, String password) {
this.email = email;
this.username = username;
this.password = password;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}

View file

@ -0,0 +1,95 @@
package achievements.misc;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Random;
public class Password {
private static final Random RANDOM = new SecureRandom();
public final String salt;
public final String hash;
private Password(String salt, String hash) {
this.salt = salt;
this.hash = hash;
}
public static Password generate(String password) {
// Generate the salt
var salt = new byte[16]; // 128 bits
RANDOM.nextBytes(salt);
return new Password(
encode(salt),
encode(hash(salt, password.getBytes()))
);
}
public static boolean validate(String salt, String password, String hash) {
System.out.println(salt + ", " + password);
var srcHash = hash(decode(salt), password.getBytes());
var targetHash = decode(hash);
for (int i = 0; i < srcHash.length; ++i) {
if (srcHash[i] != targetHash[i]) {
return false;
}
}
return true;
}
private static byte[] hash(byte[] salt, byte[] password) {
try {
var concat = new byte[salt.length + password.length];
int i = 0;
for (; i < salt.length; ++i) {
concat[i] = salt[i];
}
for (int j = 0; j < password.length; ++j) {
concat[i + j] = password[j];
}
var md = MessageDigest.getInstance("SHA-256");
return md.digest(concat);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return null;
}
private static String encode(byte[] bytes) {
var chars = new char[bytes.length << 1];
for (int i = 0; i < bytes.length; ++i) {
chars[(i << 1) ] = toHex(bytes[i] >> 0);
chars[(i << 1) + 1] = toHex(bytes[i] >> 4);
}
return new String(chars);
}
private static byte[] decode(String data) {
var decoded = new byte[data.length() >> 1];
for (int i = 0; i < data.length(); i += 2) {
int currentByte =
(fromHex(data.charAt(i )) ) |
(fromHex(data.charAt(i + 1)) << 4);
decoded[i >> 1] = (byte) (currentByte & 0xFF);
}
return decoded;
}
private static char toHex(int halfByte) {
halfByte = halfByte & 0xF;
if (0 <= halfByte && halfByte <= 9 ) return (char) (halfByte + '0' );
if (10 <= halfByte && halfByte <= 15) return (char) (halfByte + 'a' - 10);
return '0';
}
private static int fromHex(char c) {
if ('0' <= c && c <= '9') return c - '0';
if ('A' <= c && c <= 'F') return c - 'A' + 10;
if ('a' <= c && c <= 'f') return c - 'a' + 10;
return 0;
}
}

View file

@ -2,6 +2,8 @@ package achievements.services;
import achievements.data.Achievements;
import achievements.data.Games;
import achievements.data.User;
import achievements.misc.Password;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@ -84,4 +86,49 @@ public class DbService {
return null;
}
}
public int createUser(User user) {
try {
var statement = db.prepareCall("{? = call CreateUser(?, ?, ?, ?)}");
statement.registerOutParameter(1, Types.INTEGER);
statement.setString(2, user.getEmail());
statement.setString(3, user.getUsername());
var password = Password.generate(user.getPassword());
statement.setString(4, password.salt);
statement.setString(5, password.hash);
statement.execute();
var code = statement.getInt(1);
statement.close();
return code;
} catch (SQLException e) {
e.printStackTrace();
}
return -1;
}
public int login(User user) {
try {
var statement = db.prepareStatement("SELECT Salt, Password FROM [dbo].[User] WHERE Email = ?");
statement.setString(1, user.email);
var result = statement.executeQuery();
if (result.next()) {
var salt = result.getString("Salt");
var hash = result.getString("Password");
if (Password.validate(salt, user.getPassword(), hash)) {
return 0;
} else {
return 2;
}
} else {
return 1;
}
} catch (SQLException e) {
e.printStackTrace();
}
return -1;
}
}