Skip to content
This repository has been archived by the owner on Jan 31, 2023. It is now read-only.

Commit

Permalink
Merge pull request #41 from Vernite/verification-emails
Browse files Browse the repository at this point in the history
verification emails
  • Loading branch information
adiantek authored Jan 10, 2023
2 parents e856306 + b579b40 commit de03955
Show file tree
Hide file tree
Showing 3 changed files with 171 additions and 4 deletions.
27 changes: 27 additions & 0 deletions src/main/java/dev/vernite/vernite/RateLimitInterceptor.java
Original file line number Diff line number Diff line change
@@ -1,3 +1,30 @@
/*
* BSD 2-Clause License
*
* Copyright (c) 2022, [Aleksandra Serba, Marcin Czerniak, Bartosz Wawrzyniak, Adrian Antkowiak]
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

package dev.vernite.vernite;

import java.util.ArrayDeque;
Expand Down
29 changes: 25 additions & 4 deletions src/main/java/dev/vernite/vernite/user/auth/AuthController.java
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,15 @@
import org.springframework.beans.factory.annotation.Value;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
Expand Down Expand Up @@ -325,6 +327,25 @@ public User edit(@NotNull @Parameter(hidden = true) User loggedUser, @RequestBod
return loggedUser;
}

@GetMapping("/verify/{code}")
public ResponseEntity<Void> verify(@Parameter(hidden = true) User loggedUser, @PathVariable String code) {
if (loggedUser != null) {
return ResponseEntity.status(HttpStatus.FOUND)
.location(URI.create("https://vernite.dev/?path=/dashboard"))
.build();
}
User u = VerificationEmails.pollUser(code);
if (u != null) {
userRepository.save(u);
return ResponseEntity.status(HttpStatus.FOUND)
.location(URI.create("https://vernite.dev/?path=/auth/register/token-success"))
.build();
}
return ResponseEntity.status(HttpStatus.FOUND)
.location(URI.create("https://vernite.dev/?path=/auth/register/token-expired"))
.build();
}

@Operation(summary = "Register account", description = "This method registers a new account. On success returns newly created user.")
@ApiResponse(responseCode = "200", description = "Newly created user.")
@ApiResponse(responseCode = "403", description = "User is already logged or invalid captcha.", content = @Content())
Expand Down Expand Up @@ -382,8 +403,8 @@ public Future<User> register(@Parameter(hidden = true) User loggedUser, @Request
u.setLanguage(req.getLanguage());
u.setDateFormat(req.getDateFormat());
u.setCounterSequence(new CounterSequence());
u = userRepository.save(u);
createSession(request, response, u, false);

String code = VerificationEmails.prepareUser(u);

ClassLoader old = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(cl);
Expand All @@ -392,7 +413,7 @@ public Future<User> register(@Parameter(hidden = true) User loggedUser, @Request
msg.setFrom("[email protected]");
// TODO activation link
msg.setSubject("Dziękujemy za rejestrację");
msg.setText("Cześć, " + req.getName() + "!\nDziękujemy za zarejestrowanie się w naszym serwisie");
msg.setText("Cześć, " + req.getName() + "!\nDziękujemy za zarejestrowanie się w naszym serwisie. Aby dokończyć rejestrację, potwierdź swój adres e-mail:\nhttps://vernite.dev/api/auth/verify/" + code);
javaMailSender.send(msg);
Thread.currentThread().setContextClassLoader(old);
return u;
Expand All @@ -403,7 +424,7 @@ public Future<User> register(@Parameter(hidden = true) User loggedUser, @Request
@ApiResponse(responseCode = "200", description = "User logged out")
@PostMapping("/logout")
public void destroySession(HttpServletRequest req, HttpServletResponse resp,
@Parameter(hidden = true) @CookieValue(AuthController.COOKIE_NAME) String session) {
@Parameter(hidden = true) @CookieValue(value = AuthController.COOKIE_NAME, required = false) String session) {
if (session != null) {
this.userSessionRepository.deleteBySession(session);
Cookie cookie = new Cookie(COOKIE_NAME, null);
Expand Down
119 changes: 119 additions & 0 deletions src/main/java/dev/vernite/vernite/user/auth/VerificationEmails.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/*
* BSD 2-Clause License
*
* Copyright (c) 2022, [Aleksandra Serba, Marcin Czerniak, Bartosz Wawrzyniak, Adrian Antkowiak]
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

package dev.vernite.vernite.user.auth;

import java.util.HashMap;
import java.util.Iterator;
import java.util.concurrent.TimeUnit;

import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import dev.vernite.vernite.common.utils.SecureRandomUtils;
import dev.vernite.vernite.user.User;
import lombok.AllArgsConstructor;
import lombok.Data;

@Component
public class VerificationEmails {
private static final long CODE_MAX_TIME = TimeUnit.HOURS.toMillis(1);
private static final HashMap<String, VerificationEntry> code2user = new HashMap<>();
private static final HashMap<String, VerificationEntry> email2user = new HashMap<>();

/**
* prepares the user to be registered
* @param user the user to be registered
* @return the code that should be sent to the user
*/
public static synchronized String prepareUser(User user) {
long t = System.currentTimeMillis();
String code = SecureRandomUtils.generateSecureRandomString();
VerificationEntry entry = email2user.get(user.getEmail().toLowerCase());
if (entry != null && entry.destroyed) {
entry = null;
}
if (entry != null) {
entry.setTime(t);
entry.setUser(user);
} else {
entry = new VerificationEntry(user, t, false);
}
code2user.put(code, entry);
return code;
}

/**
* returns the user that should be register
* @param code the code that was in e-mail
* @return user or null if code is invalid
*/
public static synchronized User pollUser(String code) {
VerificationEntry entry = code2user.remove(code);
if (entry == null) {
return null;
}
email2user.remove(entry.getUser().getEmail().toLowerCase());
if (entry.isDestroyed()) {
return null;
}
if (System.currentTimeMillis() - entry.getTime() > CODE_MAX_TIME) {
entry.setDestroyed(true);
return null;
}
entry.setDestroyed(true);
return entry.getUser();
}

private static void clear(Iterator<VerificationEntry> it) {
long now = System.currentTimeMillis();
while (it.hasNext()) {
VerificationEntry entry = it.next();
if (entry.isDestroyed() || now - entry.getTime() > CODE_MAX_TIME) {
it.remove();
}
}
}

private static synchronized void clear() {
clear(code2user.values().iterator());
clear(email2user.values().iterator());
}

@Scheduled(cron = "0 * * * * *")
public void clearScheduler() {
clear();
}

@Data
@AllArgsConstructor
private static class VerificationEntry {
private User user;
private long time;
private boolean destroyed;
}
}

0 comments on commit de03955

Please sign in to comment.