Skip to content

Commit 7a825dd

Browse files
committed
Recovery controllers
1 parent aa48261 commit 7a825dd

File tree

12 files changed

+262
-9
lines changed

12 files changed

+262
-9
lines changed

src/main/java/com/github/throyer/common/springboot/configurations/SpringSecurityConfiguration.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,10 @@ protected void configure(HttpSecurity http) throws Exception {
132132
.permitAll()
133133
.antMatchers(POST, "/app/register")
134134
.permitAll()
135+
.antMatchers(GET, "/app/recovery/**")
136+
.permitAll()
137+
.antMatchers(POST, "/app/recovery/**")
138+
.permitAll()
135139
.anyRequest()
136140
.authenticated()
137141
.and()
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package com.github.throyer.common.springboot.controllers.app;
2+
3+
import com.github.throyer.common.springboot.domain.services.user.RecoveryPasswordService;
4+
import com.github.throyer.common.springboot.domain.services.user.dto.Codes;
5+
import com.github.throyer.common.springboot.domain.services.user.dto.RecoveryRequest;
6+
import javax.validation.Valid;
7+
import org.springframework.beans.factory.annotation.Autowired;
8+
import org.springframework.stereotype.Controller;
9+
import org.springframework.ui.Model;
10+
import org.springframework.validation.BindingResult;
11+
import org.springframework.web.bind.annotation.GetMapping;
12+
import org.springframework.web.bind.annotation.PostMapping;
13+
import org.springframework.web.bind.annotation.RequestMapping;
14+
15+
@Controller
16+
@RequestMapping("/app/recovery")
17+
public class RecoveryController {
18+
19+
@Autowired
20+
private RecoveryPasswordService service;
21+
22+
@GetMapping
23+
public String index(Model model) {
24+
model.addAttribute("recovery", new RecoveryRequest());
25+
return "/app/recovery/index";
26+
}
27+
28+
@PostMapping
29+
public String create(
30+
@Valid RecoveryRequest recovery,
31+
BindingResult result,
32+
Model model
33+
) {
34+
return service.recovery(recovery, result, model);
35+
}
36+
37+
@GetMapping("/confirm")
38+
public String codes(Model model) {
39+
model.addAttribute("codes", new Codes());
40+
return "/app/recovery/confirm";
41+
}
42+
43+
@PostMapping("/confirm")
44+
public String confirm(
45+
@Valid Codes codes,
46+
BindingResult result,
47+
Model model
48+
) {
49+
return service.confirm(codes, result, model);
50+
}
51+
}

src/main/java/com/github/throyer/common/springboot/domain/models/entity/Recovery.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ public Recovery(User user, Integer minutesToExpire) {
4444
this.code = code();
4545
}
4646

47+
public Recovery(String email, String password_recovery_code, String code) {
48+
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
49+
}
50+
4751
public Long getId() {
4852
return id;
4953
}

src/main/java/com/github/throyer/common/springboot/domain/services/user/RecoveryPasswordService.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,15 @@
55
import com.github.throyer.common.springboot.domain.repositories.RecoveryRepository;
66
import com.github.throyer.common.springboot.domain.repositories.UserRepository;
77
import com.github.throyer.common.springboot.domain.services.email.MailService;
8+
import com.github.throyer.common.springboot.domain.services.user.dto.Codes;
9+
import com.github.throyer.common.springboot.domain.services.user.dto.RecoveryRequest;
10+
import com.github.throyer.common.springboot.utils.Toasts;
811

912
import org.springframework.beans.factory.annotation.Autowired;
1013
import org.springframework.http.HttpStatus;
1114
import org.springframework.stereotype.Service;
15+
import org.springframework.ui.Model;
16+
import org.springframework.validation.BindingResult;
1217
import org.springframework.web.server.ResponseStatusException;
1318

1419
@Service
@@ -23,6 +28,19 @@ public class RecoveryPasswordService {
2328
@Autowired
2429
private MailService service;
2530

31+
public String recovery(RecoveryRequest recovery, BindingResult result, Model model) {
32+
33+
if (result.hasErrors()) {
34+
Toasts.add(model, result);
35+
model.addAttribute("recovery", recovery);
36+
return "/app/recovery/index";
37+
}
38+
39+
recovery(recovery.getEmail());
40+
41+
return "redirect:/app/recovery/confirm";
42+
}
43+
2644
public void recovery(String email) {
2745
var user = users.findOptionalByEmail(email);
2846

@@ -41,6 +59,21 @@ public void recovery(String email) {
4159
service.send(recoveryEmail);
4260
} catch (Exception exception) { }
4361
}
62+
63+
public String confirm(Codes codes, BindingResult result, Model model) {
64+
if (result.hasErrors()) {
65+
Toasts.add(model, result);
66+
model.addAttribute("recovery", codes);
67+
return "/app/recovery/index";
68+
}
69+
70+
try {
71+
confirm(codes.getEmail(), codes.code());
72+
return "redirect:/app/recovery/update";
73+
} catch (Exception exception) {
74+
return "redirect:/app/recovery/update";
75+
}
76+
}
4477

4578
public void confirm(String email, String code) {
4679
var user = users.findOptionalByEmail(email)
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package com.github.throyer.common.springboot.domain.services.user.dto;
2+
3+
import javax.validation.constraints.Email;
4+
import javax.validation.constraints.NotEmpty;
5+
import javax.validation.constraints.NotNull;
6+
import lombok.Data;
7+
8+
@Data
9+
public class Codes {
10+
11+
private String first;
12+
private String second;
13+
private String third;
14+
private String fourth;
15+
16+
@Email
17+
@NotNull
18+
@NotEmpty
19+
private String email;
20+
21+
public String code() {
22+
return String.format("%s%s%s%s", first, second, third, fourth);
23+
}
24+
}

src/main/java/com/github/throyer/common/springboot/domain/services/user/dto/RecoveryRequest.java

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,13 @@
33
import javax.validation.constraints.Email;
44
import javax.validation.constraints.NotEmpty;
55
import javax.validation.constraints.NotNull;
6+
import lombok.Data;
67

8+
@Data
79
public class RecoveryRequest {
810

911
@Email
1012
@NotNull
1113
@NotEmpty
1214
private String email;
13-
14-
public String getEmail() {
15-
return email;
16-
}
17-
18-
public void setEmail(String email) {
19-
this.email = email;
20-
}
2115
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
.codes {
2+
display: flex;
3+
justify-content: center;
4+
gap: 0.6rem;
5+
}
6+
7+
.codes .code {
8+
width: 35px;
9+
}
10+
11+
.codes .code input {
12+
padding: 0.375rem 0.75rem !important;
13+
background-image: none !important;
14+
}
15+
16+
/* Chrome, Safari, Edge, Opera */
17+
input::-webkit-outer-spin-button,
18+
input::-webkit-inner-spin-button {
19+
-webkit-appearance: none;
20+
margin: 0;
21+
}
22+
23+
/* Firefox */
24+
input[type=number] {
25+
-moz-appearance: textfield;
26+
}

src/main/resources/static/js/codes.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
document.querySelector('input[name=first]')
2+
.addEventListener('paste', (event) => {
3+
4+
/**
5+
* @type {string}
6+
*/
7+
const paste = (event.clipboardData || window.clipboardData).getData('text');
8+
9+
const [first, second, third, fourth] = paste.split('');
10+
11+
document.querySelector('input[name=first]').value = first;
12+
document.querySelector('input[name=second]').value = second;
13+
document.querySelector('input[name=third]').value = third;
14+
document.querySelector('input[name=fourth]').value = fourth;
15+
16+
document.querySelector('button[type=submit]').focus();
17+
})

src/main/resources/templates/app/login/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ <h1 class="h3 mb-3 font-weight-light text-center mt-2">Login</h1>
4848
</div>
4949

5050
<div class="mt-4 d-flex justify-content-center mb-2 text-sm">
51-
<a href="">Forgot your password?</a>
51+
<a th:href="@{/app/recovery}">Forgot your password?</a>
5252
</div>
5353

5454
<div th:if="${param.error}" class="alert alert-danger alert-dismissible fade show mt-2"
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
<layout th:replace="~{app/fragments/layout :: layout(~{::title}, ~{::link}, ~{::main}, ~{::script}, ~{})}">
2+
<title>Register</title>
3+
<link rel="stylesheet" type="text/css" th:href="@{/css/forms.css}">
4+
<link rel="stylesheet" type="text/css" th:href="@{/css/codes.css}">
5+
<main class="container">
6+
<i class="fas fa-user fa-6x mb-2"></i>
7+
<h1>Recovery</h1>
8+
<form autocomplete="off" th:object="${codes}" class="needs-validation" novalidate th:action="@{/app/register}" method="POST">
9+
<div class="row">
10+
<div class="col-md-12 mt-4">
11+
<label for="input_email" class="form-label">E-mail</label>
12+
<input
13+
th:field="*{email}"
14+
type="email"
15+
class="form-control"
16+
id="input_name"
17+
required
18+
>
19+
<div class="invalid-feedback">
20+
Por favor, informe o email.
21+
</div>
22+
</div>
23+
</div>
24+
<label for="input_email" class="form-label mt-4">Recovery code</label>
25+
<div class="codes">
26+
<div class="code">
27+
<input
28+
th:field="*{first}"
29+
type="number"
30+
class="form-control"
31+
required
32+
>
33+
</div>
34+
<div class="code">
35+
<input
36+
th:field="*{second}"
37+
type="number"
38+
class="form-control"
39+
required
40+
>
41+
</div>
42+
<div class="code">
43+
<input
44+
th:field="*{third}"
45+
type="number"
46+
class="form-control"
47+
required
48+
>
49+
</div>
50+
<div class="code">
51+
<input
52+
th:field="*{fourth}"
53+
type="number"
54+
class="form-control"
55+
required
56+
>
57+
</div>
58+
</div>
59+
<div class="row">
60+
<div class="col-md-12 mt-4 d-grid gap-2">
61+
<button class="btn btn-primary" type="submit">Submit</button>
62+
</div>
63+
</div>
64+
</form>
65+
</main>
66+
<script language="javascript" th:src="@{/js/forms.js}"></script>
67+
<script language="javascript" th:src="@{/js/codes.js}"></script>
68+
</layout>
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<layout th:replace="~{app/fragments/layout :: layout(~{::title}, ~{::link}, ~{::main}, ~{::script}, ~{})}">
2+
<title>Register</title>
3+
<link rel="stylesheet" type="text/css" th:href="@{/css/forms.css}">
4+
<main class="container">
5+
<i class="fas fa-user fa-6x mb-2"></i>
6+
<h1>Recovery</h1>
7+
<form th:object="${recovery}" class="needs-validation" novalidate th:action="@{/app/recovery}" method="POST">
8+
<div class="row">
9+
<div class="col-md-12 mt-4">
10+
<label for="input_email" class="form-label">E-mail</label>
11+
<input
12+
th:field="*{email}"
13+
type="email"
14+
class="form-control"
15+
id="input_name"
16+
autocomplete="email"
17+
required
18+
>
19+
<div class="invalid-feedback">
20+
Por favor, informe o email.
21+
</div>
22+
</div>
23+
</div>
24+
<div class="row">
25+
<div class="col-md-12 mt-4 d-grid gap-2">
26+
<button class="btn btn-primary" type="submit">Submit</button>
27+
</div>
28+
</div>
29+
</form>
30+
</main>
31+
<script language="javascript" th:src="@{/js/forms.js}"></script>
32+
</layout>

src/main/resources/templates/app/recovery/update.html

Whitespace-only changes.

0 commit comments

Comments
 (0)