[server]: add one click verify button for verification email (#5654)

OG (ott.html):

<img width="701" height="590" alt="image"
src="https://github.com/user-attachments/assets/80b926d1-c65f-44a8-9de4-7b591258bf3c"
/>



New (ott_mobile.html):

<img width="642" height="811" alt="image"
src="https://github.com/user-attachments/assets/aa18a778-1161-4b4e-ad82-cf472da06ff7"
/>
This commit is contained in:
Neeraj
2025-08-21 12:44:24 +05:30
committed by GitHub
4 changed files with 241 additions and 7 deletions

View File

@@ -1,7 +1,8 @@
package ente
const (
OTTTemplate = "ott.html"
OTTTemplate = "ott.html"
OTTMobileTemplate = "ott_mobile.html"
ChangeEmailOTTTemplate = "ott_change_email.html"
EmailChangedTemplate = "email_changed.html"
@@ -31,6 +32,8 @@ type SendOTTRequest struct {
Email string `json:"email"`
Client string `json:"client"`
Purpose string `json:"purpose"`
// Mobile indicates whether the request is coming from a mobile client`
Mobile bool `json:"mobile"`
}
// EmailVerificationRequest represents an email verification request

View File

@@ -0,0 +1,226 @@
<!DOCTYPE html>
<html>
<meta content="text/html; charset=utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, minimum-scale=1"
/>
<style>
body {
background-color: #f0f1f3;
font-family: "Helvetica Neue", "Segoe UI", Helvetica, sans-serif;
font-size: 16px;
line-height: 27px;
margin: 0;
color: #444;
}
pre {
background: #f4f4f4f4;
padding: 2px;
}
table {
width: 100%;
border: 1px solid #ddd;
}
table td {
border-color: #ddd;
padding: 5px;
}
.wrap {
background-color: #fff;
padding: 30px;
max-width: auto;
margin: 0 auto;
border-radius: 5px;
}
.button {
background: #0055d4;
border-radius: 3px;
text-decoration: none !important;
color: #fff !important;
font-weight: bold;
padding: 10px 30px;
display: inline-block;
}
.button:hover {
background: #111;
}
.footer {
text-align: center;
font-size: 12px;
color: #888;
}
.footer a {
color: #888;
margin-right: 5px;
}
.gutter {
padding: 10px;
}
img {
max-width: 100%;
height: auto;
}
a {
color: #0055d4;
}
a:hover {
color: #111;
}
.footer-icons {
padding: 4px !important;
width: 24px !important;
}
</style>
<body>
<div class="gutter" style="padding: 4px">&nbsp;</div>
<div
class="wrap"
style="
background-color: rgb(255, 255, 255);
padding: 30px;
max-width: 525px;
margin: 0 auto;
border-radius: 5px;
font-size: 16px;
"
>
<h2 style="text-align: center; margin-bottom: 16px">
Verify your email address
</h2>
<hr style="margin: 32px 0; border: none; border-top: 1px solid #ddd" />
<div>
<p style="text-align: center">
Click on the button to verify your email.
</p>
<center style="margin: 24px 0">
<a
href="https://verify.ente.io/?ott={{.VerificationCode}}"
style="
background-color: #00b33c;
border-radius: 3px;
text-decoration: none !important;
color: #fff !important;
font-weight: bold;
padding: 8px 30px;
display: inline-block;
text-align: center;
"
target="_blank"
>
Verify email
</a>
</center>
<div
style="
text-align: center;
font-size: 14px;
color: #666;
margin-top: 32px;
margin-bottom: 32px;
"
>
<p> or enter the code manually </p>
<center>
<div
style="
background-color: #f6f6f6;
border-radius: 6px;
color: #787878;
display: inline-block;
letter-spacing: 3px;
padding: 8px 14px;
text-align: center;
font-size: 16px;
font-weight: bold;
"
target="_blank"
>
{{.VerificationCode}}
</div>
</center>
</div>
</div>
<hr
style="
margin-top: 32px;
margin-bottom: 32px;
border: none;
border-top: 1px solid #ddd;
"
/>
<p style="text-align: center; font-size: 14px; color: #666">
If you need help, just hit the reply button!
</p>
</div>
<br />
<div
class="footer"
style="text-align: center; font-size: 12px; color: rgb(136, 136, 136)"
>
<div>
<a href="https://ente.io" target="_blank"
><img
src="https://email-assets.ente.io/ente-green.png"
style="width: 100px; padding: 24px"
title="Ente"
alt="Ente"
/></a>
</div>
<div>
<a href="https://fosstodon.org/@ente" target="_blank"
><img
src="https://email-assets.ente.io/mastodon-icon.png"
class="footer-icons"
style="width: 24px; padding: 4px"
title="Mastodon"
alt="Mastodon"
/></a>
<a href="https://twitter.com/enteio" target="_blank"
><img
src="https://email-assets.ente.io/twitter-icon.png"
class="footer-icons"
style="width: 24px; padding: 4px"
title="Twitter"
alt="Twitter"
/></a>
<a href="https://discord.ente.io" target="_blank"
><img
src="https://email-assets.ente.io/discord-icon.png"
class="footer-icons"
style="width: 24px; padding: 4px"
title="Discord"
alt="Discord"
/></a>
<a href="https://github.com/ente-io" target="_blank"
><img
src="https://email-assets.ente.io/github-icon.png"
class="footer-icons"
style="width: 24px; padding: 4px"
title="GitHub"
alt="GitHub"
/></a>
</div>
<p>
Ente Technologies, Inc.
<br />
1111B S Governors Ave 6032 Dover, DE 19904
</p>
<br />
</div>
</body>
</html>

View File

@@ -40,7 +40,7 @@ func (h *UserHandler) SendOTT(c *gin.Context) {
handler.Error(c, stacktrace.Propagate(ente.ErrBadRequest, "Email id is missing"))
return
}
err := h.UserController.SendEmailOTT(c, email, request.Purpose)
err := h.UserController.SendEmailOTT(c, email, request.Purpose, request.Mobile)
if err != nil {
handler.Error(c, stacktrace.Propagate(err, ""))
return

View File

@@ -82,7 +82,7 @@ func hardcodedOTTForEmail(hardCodedOTT HardCodedOTT, email string) string {
}
// SendEmailOTT generates and sends an OTT to the provided email address
func (c *UserController) SendEmailOTT(context *gin.Context, email string, purpose string) error {
func (c *UserController) SendEmailOTT(context *gin.Context, email string, purpose string, mobile bool) error {
if err := c.validateSendOTT(context, email, purpose); err != nil {
return err
}
@@ -105,7 +105,8 @@ func (c *UserController) SendEmailOTT(context *gin.Context, email string, purpos
return stacktrace.Propagate(err, "")
}
// check if user has already requested for more than 10 codes in last 10mins
otts, _ := c.UserAuthRepo.GetValidOTTs(emailHash, auth.GetApp(context))
app := auth.GetApp(context)
otts, _ := c.UserAuthRepo.GetValidOTTs(emailHash, app)
if len(otts) >= OTTActiveCodeLimit {
msg := "Too many ott requests in a short duration"
go c.DiscordController.NotifyPotentialAbuse(msg)
@@ -119,7 +120,7 @@ func (c *UserController) SendEmailOTT(context *gin.Context, email string, purpos
return stacktrace.Propagate(err, "")
}
log.Info("Added ott for " + emailHash + ": " + ott)
err = emailOTT(email, ott, purpose)
err = emailOTT(app, email, ott, purpose, mobile)
if err != nil {
return stacktrace.Propagate(err, "")
}
@@ -383,12 +384,16 @@ func (c *UserController) TerminateSession(userID int64, token string) error {
return stacktrace.Propagate(c.UserAuthRepo.RemoveToken(userID, token), "")
}
func emailOTT(to string, ott string, purpose string) error {
func emailOTT(app ente.App, to string, ott string, purpose string, mobile bool) error {
var templateName string
if purpose == ente.ChangeEmailOTTPurpose {
templateName = ente.ChangeEmailOTTTemplate
} else {
templateName = ente.OTTTemplate
if mobile && app == ente.Photos {
templateName = ente.OTTMobileTemplate
} else {
templateName = ente.OTTTemplate
}
}
subject := fmt.Sprintf("Verification code: %s", ott)
err := emailUtil.SendTemplatedEmail([]string{to}, "Ente", "verify@ente.io",