Skip to content

Commit

Permalink
Fix password encoding persistence
Browse files Browse the repository at this point in the history
  • Loading branch information
jultty committed Jan 24, 2024
1 parent 99ee8b7 commit 4ebc9d5
Show file tree
Hide file tree
Showing 16 changed files with 102 additions and 21 deletions.
11 changes: 10 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# mirante-spring

Mirante is a data-oriented educational system that aims to minimize the loss of relevant data that could bring us more insight into how we learn.
Mirante is a data-oriented educational system that aims to minimize the loss of relevant data that could bring more insight into how we learn.

This repository contains an implementation using Java and the Spring Boot Framework.

Expand Down Expand Up @@ -56,5 +56,14 @@ To see all available options:
gradle tasks
```

Once the server is running, for development environments an [H2 database console](https://www.h2database.com/html/tutorial.html) is available on `localhost:<port>/h2-console`.

The following options allow access to the H2 console:

- **Driver class:** `org.h2.Driver`
- **JDBC URL:** `jdbc:h2:mem:mirante`
- **User Name:** `dev`
- **Password:** Empty

If you have [Nix](https://nixos.org/manual/nix/stable/introduction) available on your system and flake support enabled, you can use the flake file to setup a development environment with JDK 21 and Gradle using `nix develop`.

3 changes: 2 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ repositories {
dependencies {
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
implementation("org.springframework.boot:spring-boot-starter-data-rest")
implementation("org.springframework.security:spring-security-crypto:6.2.1")
testImplementation("org.springframework.boot:spring-boot-starter-test")
implementation("org.springframework.security:spring-security-crypto:6.2.1")
implementation("org.bouncycastle:bcprov-jdk15on:1.64")
runtimeOnly("com.h2database:h2")
}

Expand Down
10 changes: 10 additions & 0 deletions docs/v0.1.2/relatorio.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,16 @@ A classe utilizada cuida da geração de um _salt_ para tornar a senha armazenad

Durante a implementação, um erro inicial enfrentado após o _commit_ `0b41f38` foi que as senhas eram gravadas no banco como valores nulos.

Ao investigar a raiz do problema, foram indetificadas algumas novas informações:

- O uso da classe `Argon2PaswordEncoder` requer a inserção manual da dependência `org.bouncycastle:bcprov-jdk15on:1.64`
- A [especificação do JPA][#4] exige que exista um construtor padrão (sem argumentos) em classes que definem uma entidade

A segunda informação levou à compreensão de que o ORM não estava chamando o construtor onde a senha era codificada, mas sim utilizando o construtor padrão.

Como solução, o controlador do _endpoint_ `account` passou a serializar os dados da requisição em um objeto de uma nova classe intermediária, criada com acesso restrito somente ao pacote `account`, e então instanciar um objeto da classe `Account` utilizando o construtor correto, que codifica a senha recebida, e então a passa para o repositório JPA para ser persistida no banco.

[#1]: https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html
[#2]: https://docs.spring.io/spring-security/reference/features/authentication/password-storage.html#authentication-password-storage-argon2
[#3]: https://docs.spring.io/spring-security/site/docs/6.2.1/api/org/springframework/security/crypto/argon2/Argon2PasswordEncoder.html#encode(java.lang.CharSequence)
[#4]: https://openjpa.apache.org/builds/1.2.3/apache-openjpa/docs/jpa_overview_pc.html#:~:text=The%20JPA%20specification%20requires%20that,include%20a%20no%2Darg%20constructor.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
10 changes: 10 additions & 0 deletions src/hurl/v0.1.2/account_single.hurl
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# client can create an account
POST http://localhost:8888/account
{
"registration": "jc000001",
"name": " Tania Wolfgramm",
"email": "tania@mirante.dev",
"password": "xyz6060"
}

HTTP 201
23 changes: 19 additions & 4 deletions src/main/java/mirante/api/account/Account.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Transient;

import org.springframework.security.crypto.argon2.Argon2PasswordEncoder;

@Entity
public class Account {
Expand All @@ -12,18 +15,30 @@ public class Account {
private String email;
private String password;

public Account() {}
@Transient
private Argon2PasswordEncoder encoder =
Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8();

public Account(String registration, String name, String email, String password) {
this.registration = registration;
this.name = name;
this.email = email;
this.password = SecUtils.encoder.encode(password);
this.password = encoder.encode(password);
}

Account() {}

public Boolean checkPassword(String password) {
if (encoder.matches(password, this.password)) {
return true;
} else {
return false;
}
}

public void changePassword(String old_password, String new_password) {
if (SecUtils.encoder.matches(old_password, this.password)) {
this.password = SecUtils.encoder.encode(new_password);
if (encoder.matches(old_password, this.password)) {
this.password = encoder.encode(new_password);
}
}

Expand Down
10 changes: 9 additions & 1 deletion src/main/java/mirante/api/account/AccountController.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,15 @@ List<Account> all() {

@PostMapping("/account")
@ResponseStatus(HttpStatus.CREATED)
Account newAccount(@RequestBody Account newAccount) {
Account newAccount(@RequestBody AccountRequest request) {

Account newAccount = new Account(
request.registration,
request.name,
request.email,
request.password
);

return repository.save(newAccount);
}

Expand Down
17 changes: 17 additions & 0 deletions src/main/java/mirante/api/account/AccountRequest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package mirante.api.account;

class AccountRequest {

String registration;
String name;
String email;
String password;

AccountRequest(String registration, String name, String email, String password) {
this.registration = registration;
this.name = name;
this.email = email;
this.password = password;
}
}

8 changes: 0 additions & 8 deletions src/main/java/mirante/api/account/SecUtils.java

This file was deleted.

19 changes: 19 additions & 0 deletions src/test/java/mirante/api/account/AccountTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package mirante.api.account;

import mirante.api.account.Account;

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class AccountTest {

@Test
void encodedPasswordCanBeDecoded() {
String password = "xyz0000";
Account account = new Account("JC000000", "Jane Doe", "jane@doe.com", password);
assertTrue(account.checkPassword(password));
}

}
12 changes: 6 additions & 6 deletions src/web/account.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,16 @@
</head>
<body>
<form action="http://localhost:8888/account" method="POST">
<label for="name">Name:</label><br>
<input type="text" id="name" name="name"><br>
<label for="registration">Prontuário:</label><br>
<input type="text" id="registration" name="registration"><br>

<label for="username">Username:</label><br>
<input type="text" id="username" name="username"><br>
<label for="name">Nome:</label><br>
<input type="text" id="name" name="name"><br>

<label for="email">Email:</label><br>
<input type="email" id="email" name="email"><br>

<label for="password">Password:</label><br>
<label for="password">Senha:</label><br>
<input type="password" id="password" name="password"><br>

<input type="hidden" name="data" id="data">
Expand Down Expand Up @@ -47,8 +47,8 @@
xhr.setRequestHeader('Content-Type', 'application/json');

const data = {
registration: form.elements.registration.value,
name: form.elements.name.value,
username: form.elements.username.value,
email: form.elements.email.value,
password: form.elements.password.value,
};
Expand Down

0 comments on commit 4ebc9d5

Please sign in to comment.