Browse Source

FEATURE: Email verification code lifetime is functional now

master
parent
commit
100fb4b5b2
  1. 13
      pages/register.html
  2. 8
      src/db/verification.go
  3. 19
      src/server/endpoints.go
  4. 6
      src/server/validation.go

13
pages/register.html

@ -45,7 +45,6 @@
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title" id="verificationModalLabel">Email Verification</h5> <h5 class="modal-title" id="verificationModalLabel">Email Verification</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<p>Enter the verification code sent to your email address</p> <p>Enter the verification code sent to your email address</p>
@ -56,7 +55,6 @@
<p class="text-danger"><span id="error-message-modal"></span></p> <p class="text-danger"><span id="error-message-modal"></span></p>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary" id="verifyButton" onclick="verify();">Verify</button> <button type="button" class="btn btn-primary" id="verifyButton" onclick="verify();">Verify</button>
</div> </div>
</div> </div>
@ -85,7 +83,15 @@ async function verify() {
} }
} }
let verificationSent = false;
async function register() { async function register() {
if (verificationSent) {
// No need to resend registration data, reopen verification modal,
showVerificationModal();
return;
}
let emailInput = document.getElementById("input-email"); let emailInput = document.getElementById("input-email");
if (!emailInput.reportValidity()) { if (!emailInput.reportValidity()) {
return; return;
@ -106,9 +112,10 @@ async function register() {
}; };
let response = await postNewUser(postData); let response = await postNewUser(postData);
if (response.ok) { if (response.ok) {
let json = await response.json(); let json = await response.json();
if (json.confirm_email) { if (json.confirm_email) {
verificationSent = true;
// Open email confirmation modal // Open email confirmation modal
showVerificationModal(); showVerificationModal();
} else { } else {

8
src/db/verification.go

@ -1,6 +1,8 @@
package db package db
import "database/sql" import (
"database/sql"
)
type Verification struct { type Verification struct {
ID uint64 `json:"id"` ID uint64 `json:"id"`
@ -55,10 +57,10 @@ func (db *DB) GetVerification(id uint64) (*Verification, error) {
return verification, nil return verification, nil
} }
// Returns the first email verification by email // Returns the last email verification by email
func (db *DB) GetVerificationByEmail(email string) (*Verification, error) { func (db *DB) GetVerificationByEmail(email string) (*Verification, error) {
rows, err := db.Query( rows, err := db.Query(
"SELECT * FROM verifications WHERE email=?", "SELECT * FROM verifications WHERE (email=?) ORDER BY life_seconds DESC",
email, email,
) )
if err != nil { if err != nil {

19
src/server/endpoints.go

@ -108,7 +108,7 @@ func (s *Server) EndpointUserCreate(w http.ResponseWriter, req *http.Request) {
} }
// Send email verification message // Send email verification message
verification, err := GenerateVerificationCode(s.db, user.Email) verification, err := GenerateVerificationCode(s.db, user.Email, 5, uint64(time.Hour.Seconds()))
if err != nil { if err != nil {
logger.Error("[Server][EndpointUserCreate] Failed to generate verification code for %s: %s", user.Email, err) logger.Error("[Server][EndpointUserCreate] Failed to generate verification code for %s: %s", user.Email, err)
http.Error(w, "Failed to generate confirmation code", http.StatusInternalServerError) http.Error(w, "Failed to generate confirmation code", http.StatusInternalServerError)
@ -120,7 +120,7 @@ func (s *Server) EndpointUserCreate(w http.ResponseWriter, req *http.Request) {
email.NewEmail( email.NewEmail(
s.config.Verification.Emailer.User, s.config.Verification.Emailer.User,
"Dela: Email verification", "Dela: Email verification",
fmt.Sprintf("Your email verification code: <b>%s</b>\nPlease, verify your email in %f hours.\nThis email was specified during Dela account creation. Ignore this message if it wasn't you", verification.Code, float32(verification.LifeSeconds)/3600), fmt.Sprintf("<p>Your email verification code is: <b>%s</b></p><p>Please, verify your email in %.1f hours. Your account will be deleted after some time without verified status.</p><p>This email was specified during Dela account creation. Ignore this message if it wasn't you.</p>", verification.Code, float32(verification.LifeSeconds)/3600),
[]string{user.Email}, []string{user.Email},
), ),
) )
@ -130,8 +130,8 @@ func (s *Server) EndpointUserCreate(w http.ResponseWriter, req *http.Request) {
return return
} }
// Autodelete user account if email was not verified in time // Autodelete user account after some more time if email was not verified in time
time.AfterFunc(time.Second*time.Duration(verification.LifeSeconds), func() { time.AfterFunc((time.Second*time.Duration(verification.LifeSeconds))*5, func() {
err = s.db.DeleteUnverifiedUserClean(user.Email) err = s.db.DeleteUnverifiedUserClean(user.Email)
if err != nil { if err != nil {
logger.Error("[Server][EndpointUserCreate] Failed to autodelete unverified user %s: %s", user.Email, err) logger.Error("[Server][EndpointUserCreate] Failed to autodelete unverified user %s: %s", user.Email, err)
@ -173,8 +173,8 @@ func (s *Server) EndpointUserVerify(w http.ResponseWriter, req *http.Request) {
// Retrieve user // Retrieve user
user, err := s.db.GetUser(answer.Email) user, err := s.db.GetUser(answer.Email)
if err != nil { if err != nil {
logger.Error("[Server][EndpointUserVerify] Failed to retrieve information on \"%s\": %s", answer.Email, err) // Most likely already deleted this user's account
http.Error(w, "Failed to get user information", http.StatusInternalServerError) http.Error(w, "Account no longer exists, try registering again", http.StatusInternalServerError)
return return
} }
@ -193,6 +193,13 @@ func (s *Server) EndpointUserVerify(w http.ResponseWriter, req *http.Request) {
return return
} }
// Check for lifetime
if time.Now().Unix() > int64(dbCode.IssuedUnix+dbCode.LifeSeconds) {
// Expired!
http.Error(w, "This code is expired!", http.StatusForbidden)
return
}
// All's good! // All's good!
err = s.db.UserSetEmailConfirmed(user.Email) err = s.db.UserSetEmailConfirmed(user.Email)
if err != nil { if err != nil {

6
src/server/validation.go

@ -127,12 +127,12 @@ func GetLoginFromReq(req *http.Request) string {
/* /*
Generates a new verification code for given email with 8-digit numeric code, Generates a new verification code for given email with 8-digit numeric code,
current issue time and one hour lifetime. current issue time and provided life time.
Inserts newly created email verification into database. Inserts newly created email verification into database.
*/ */
func GenerateVerificationCode(dbase *db.DB, email string) (*db.Verification, error) { func GenerateVerificationCode(dbase *db.DB, email string, length uint, lifeTimeSeconds uint64) (*db.Verification, error) {
verification := db.NewVerification( verification := db.NewVerification(
email, misc.GenerateNumericCode(8), uint64(time.Now().Unix()), uint64(time.Hour.Seconds()), email, misc.GenerateNumericCode(length), uint64(time.Now().Unix()), lifeTimeSeconds,
) )
err := dbase.CreateVerification(*verification) err := dbase.CreateVerification(*verification)

Loading…
Cancel
Save