Implements /resetPassword and refactors email code.
This commit is contained in:
parent
4c0ef19344
commit
249031b9a8
4 changed files with 199 additions and 162 deletions
|
|
@ -52,7 +52,6 @@ proc makeIdentHash*(user, password, epoch, secret: string,
|
||||||
## If ``epoch`` is smaller than the epoch of the user's last login then
|
## If ``epoch`` is smaller than the epoch of the user's last login then
|
||||||
## the link is invalid.
|
## the link is invalid.
|
||||||
## The ``secret`` is the 'salt' field in the ``person`` table.
|
## The ``secret`` is the 'salt' field in the ``person`` table.
|
||||||
echo(user, password, epoch, secret)
|
|
||||||
when defined(windows):
|
when defined(windows):
|
||||||
result = getMD5(user & password & epoch & secret)
|
result = getMD5(user & password & epoch & secret)
|
||||||
else:
|
else:
|
||||||
|
|
|
||||||
134
src/email.nim
Normal file
134
src/email.nim
Normal file
|
|
@ -0,0 +1,134 @@
|
||||||
|
import asyncdispatch, smtp, strutils, times, cgi, tables
|
||||||
|
|
||||||
|
from jester import Request, makeUri
|
||||||
|
|
||||||
|
import utils, auth
|
||||||
|
|
||||||
|
type
|
||||||
|
Mailer* = ref object
|
||||||
|
config: Config
|
||||||
|
lastReset: Time
|
||||||
|
emailsSent: CountTable[string]
|
||||||
|
|
||||||
|
proc newMailer*(config: Config): Mailer =
|
||||||
|
Mailer(
|
||||||
|
config: config,
|
||||||
|
lastReset: getTime(),
|
||||||
|
emailsSent: initCountTable[string]()
|
||||||
|
)
|
||||||
|
|
||||||
|
proc rateCheck(mailer: Mailer, address: string): bool =
|
||||||
|
## Returns true if we've emailed the address too much.
|
||||||
|
let diff = getTime() - mailer.lastReset
|
||||||
|
if diff.hours >= 1:
|
||||||
|
mailer.lastReset = getTime()
|
||||||
|
mailer.emailsSent.clear()
|
||||||
|
|
||||||
|
result = address in mailer.emailsSent and mailer.emailsSent[address] >= 2
|
||||||
|
mailer.emailsSent.inc(address)
|
||||||
|
|
||||||
|
proc sendMail(
|
||||||
|
mailer: Mailer,
|
||||||
|
subject, message, recipient: string,
|
||||||
|
fromAddr = "forum@nim-lang.org",
|
||||||
|
otherHeaders:seq[(string, string)] = @[]
|
||||||
|
) {.async.} =
|
||||||
|
# Ensure we aren't emailing this address too much.
|
||||||
|
if rateCheck(mailer, recipient):
|
||||||
|
let msg = "Too many messages have been sent to this email address recently."
|
||||||
|
raise newForumError(msg)
|
||||||
|
|
||||||
|
if mailer.config.smtpAddress.len == 0:
|
||||||
|
echo("[WARNING] Cannot send mail: no smtp server configured (smtpAddress).")
|
||||||
|
return
|
||||||
|
|
||||||
|
var client = newAsyncSmtp()
|
||||||
|
await client.connect(mailer.config.smtpAddress, Port(mailer.config.smtpPort))
|
||||||
|
if mailer.config.smtpUser.len > 0:
|
||||||
|
await client.auth(mailer.config.smtpUser, mailer.config.smtpPassword)
|
||||||
|
|
||||||
|
let toList = @[recipient]
|
||||||
|
|
||||||
|
var headers = otherHeaders
|
||||||
|
headers.add(("From", fromAddr))
|
||||||
|
|
||||||
|
let encoded = createMessage(subject, message,
|
||||||
|
toList, @[], headers)
|
||||||
|
|
||||||
|
await client.sendMail(fromAddr, toList, $encoded)
|
||||||
|
|
||||||
|
proc sendPassReset(mailer: Mailer, email, user, resetUrl: string) {.async.} =
|
||||||
|
let message = """Hello $1,
|
||||||
|
A password reset has been requested for your account on the $3.
|
||||||
|
|
||||||
|
If you did not make this request, you can safely ignore this email.
|
||||||
|
A password reset request can be made by anyone, and it does not indicate
|
||||||
|
that your account is in any danger of being accessed by someone else.
|
||||||
|
|
||||||
|
If you do actually want to reset your password, visit this link:
|
||||||
|
|
||||||
|
$2
|
||||||
|
|
||||||
|
Thank you for being a part of our community!
|
||||||
|
""" % [user, resetUrl, mailer.config.name]
|
||||||
|
|
||||||
|
let subject = mailer.config.name & " Password Recovery"
|
||||||
|
await sendMail(mailer, subject, message, email)
|
||||||
|
|
||||||
|
proc sendEmailActivation(
|
||||||
|
mailer: Mailer,
|
||||||
|
email, user, activateUrl: string
|
||||||
|
) {.async.} =
|
||||||
|
let message = """Hello $1,
|
||||||
|
You have recently registered an account on the $3.
|
||||||
|
|
||||||
|
As the final step in your registration, we require that you confirm your email
|
||||||
|
via the following link:
|
||||||
|
|
||||||
|
$2
|
||||||
|
|
||||||
|
Thank you for registering and becoming a part of our community!
|
||||||
|
""" % [user, activateUrl, mailer.config.name]
|
||||||
|
let subject = mailer.config.name & " Account Email Confirmation"
|
||||||
|
await sendMail(mailer, subject, message, email)
|
||||||
|
|
||||||
|
type
|
||||||
|
SecureEmailKind* = enum
|
||||||
|
ActivateEmail, ResetPassword
|
||||||
|
|
||||||
|
proc sendSecureEmail*(
|
||||||
|
mailer: Mailer,
|
||||||
|
kind: SecureEmailKind, req: Request,
|
||||||
|
name, password, email, salt: string
|
||||||
|
) {.async.} =
|
||||||
|
let epoch = $int(epochTime())
|
||||||
|
|
||||||
|
let path =
|
||||||
|
case kind
|
||||||
|
of ActivateEmail:
|
||||||
|
"activateEmail"
|
||||||
|
of ResetPassword:
|
||||||
|
"resetPassword"
|
||||||
|
let url = req.makeUri(
|
||||||
|
"/$#?nick=$#&epoch=$#&ident=$#" %
|
||||||
|
[
|
||||||
|
path,
|
||||||
|
encodeUrl(name),
|
||||||
|
encodeUrl(epoch),
|
||||||
|
encodeUrl(makeIdentHash(name, password, epoch, salt))
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
let emailSentFut =
|
||||||
|
case kind
|
||||||
|
of ActivateEmail:
|
||||||
|
sendEmailActivation(mailer, email, name, url)
|
||||||
|
of ResetPassword:
|
||||||
|
sendPassReset(mailer, email, name, url)
|
||||||
|
yield emailSentFut
|
||||||
|
if emailSentFut.failed:
|
||||||
|
echo("[WARNING] Couldn't send email: ", emailSentFut.error.msg)
|
||||||
|
if emailSentFut.error of ForumError:
|
||||||
|
raise emailSentFut.error
|
||||||
|
else:
|
||||||
|
raise newForumError("Couldn't send email", @["email"])
|
||||||
120
src/forum.nim
120
src/forum.nim
|
|
@ -9,13 +9,14 @@
|
||||||
import
|
import
|
||||||
os, strutils, times, md5, strtabs, math, db_sqlite,
|
os, strutils, times, md5, strtabs, math, db_sqlite,
|
||||||
scgi, jester, asyncdispatch, asyncnet, sequtils,
|
scgi, jester, asyncdispatch, asyncnet, sequtils,
|
||||||
parseutils, utils, random, rst, recaptcha, json, re, sugar
|
parseutils, random, rst, recaptcha, json, re, sugar,
|
||||||
|
strformat
|
||||||
import cgi except setCookie
|
import cgi except setCookie
|
||||||
import options
|
import options
|
||||||
|
|
||||||
import sass
|
import sass
|
||||||
|
|
||||||
import auth
|
import auth, email, utils
|
||||||
|
|
||||||
import frontend/threadlist except User
|
import frontend/threadlist except User
|
||||||
import frontend/[
|
import frontend/[
|
||||||
|
|
@ -63,24 +64,12 @@ type
|
||||||
noPagenumumNav: bool
|
noPagenumumNav: bool
|
||||||
config: Config
|
config: Config
|
||||||
|
|
||||||
ForumError = object of Exception
|
|
||||||
data: PostError
|
|
||||||
|
|
||||||
var
|
var
|
||||||
db: DbConn
|
db: DbConn
|
||||||
isFTSAvailable: bool
|
isFTSAvailable: bool
|
||||||
config: Config
|
config: Config
|
||||||
captcha: ReCaptcha
|
captcha: ReCaptcha
|
||||||
|
mailer: Mailer
|
||||||
proc newForumError(message: string,
|
|
||||||
fields: seq[string] = @[]): ref ForumError =
|
|
||||||
new(result)
|
|
||||||
result.msg = message
|
|
||||||
result.data =
|
|
||||||
PostError(
|
|
||||||
errorFields: fields,
|
|
||||||
message: message
|
|
||||||
)
|
|
||||||
|
|
||||||
proc init(c: TForumData) =
|
proc init(c: TForumData) =
|
||||||
c.userPass = ""
|
c.userPass = ""
|
||||||
|
|
@ -131,42 +120,23 @@ proc setError(c: TForumData, field, msg: string): bool {.inline.} =
|
||||||
c.errorMsg = "Error: " & msg
|
c.errorMsg = "Error: " & msg
|
||||||
return false
|
return false
|
||||||
|
|
||||||
proc resetPassword(c: TForumData, nick, antibot, userIp: string): Future[bool] {.async.} =
|
proc resetPassword(
|
||||||
# captcha validation:
|
c: TForumData,
|
||||||
if config.recaptchaSecretKey.len > 0:
|
email: string
|
||||||
var captchaValid: bool = false
|
) {.async.} =
|
||||||
try:
|
|
||||||
captchaValid = await captcha.verify(antibot, userIp)
|
|
||||||
except:
|
|
||||||
echo("[ERROR] Error checking captcha: " & getCurrentExceptionMsg())
|
|
||||||
captchaValid = false
|
|
||||||
|
|
||||||
if not captchaValid:
|
|
||||||
return setError(c, "g-recaptcha-response", "Answer to captcha incorrect!")
|
|
||||||
|
|
||||||
# Gather some extra information to determine ident hash.
|
# Gather some extra information to determine ident hash.
|
||||||
let epoch = $int(epochTime())
|
|
||||||
let row = db.getRow(
|
let row = db.getRow(
|
||||||
sql"select password, salt, email from person where name = ?", nick)
|
sql"select name, password, email, salt from person where email = ?",
|
||||||
|
email
|
||||||
|
)
|
||||||
if row[0] == "":
|
if row[0] == "":
|
||||||
return setError(c, "nick", "Nickname not found")
|
raise newForumError("Email not found", @["email"])
|
||||||
# Generate URL for the email.
|
|
||||||
# TODO: Get rid of the stupid `%` in main.tmpl as it screws up strutils.%
|
|
||||||
let resetUrl = c.req.makeUri(
|
|
||||||
strutils.`%`("/emailResetPassword?nick=$1&epoch=$2&ident=$3",
|
|
||||||
[encodeUrl(nick), encodeUrl(epoch),
|
|
||||||
encodeUrl(makeIdentHash(nick, row[0], epoch, row[1]))]))
|
|
||||||
echo "User's reset URL is: ", resetUrl
|
|
||||||
# Send the email.
|
|
||||||
let emailSentFut = sendPassReset(c.config, row[2], nick, resetUrl)
|
|
||||||
# TODO: This is a workaround for 'var T' not being usable in async procs.
|
|
||||||
while not emailSentFut.finished:
|
|
||||||
poll()
|
|
||||||
if emailSentFut.failed:
|
|
||||||
echo("[WARNING] Couldn't send activation email: ", emailSentFut.error.msg)
|
|
||||||
return setError(c, "email", "Couldn't send activation email")
|
|
||||||
|
|
||||||
return true
|
await sendSecureEmail(
|
||||||
|
mailer,
|
||||||
|
ResetPassword, c.req,
|
||||||
|
row[0], row[1], row[2], row[3]
|
||||||
|
)
|
||||||
|
|
||||||
proc logout(c: TForumData) =
|
proc logout(c: TForumData) =
|
||||||
const query = sql"delete from session where ip = ? and password = ?"
|
const query = sql"delete from session where ip = ? and password = ?"
|
||||||
|
|
@ -279,6 +249,8 @@ proc initialise() =
|
||||||
doAssert config.isDev, "Recaptcha required for production!"
|
doAssert config.isDev, "Recaptcha required for production!"
|
||||||
echo("[WARNING] No recaptcha secret key specified.")
|
echo("[WARNING] No recaptcha secret key specified.")
|
||||||
|
|
||||||
|
mailer = newMailer(config)
|
||||||
|
|
||||||
db = open(connection=config.dbPath, user="", password="",
|
db = open(connection=config.dbPath, user="", password="",
|
||||||
database="nimforum")
|
database="nimforum")
|
||||||
isFTSAvailable = db.getAllRows(sql("SELECT name FROM sqlite_master WHERE " &
|
isFTSAvailable = db.getAllRows(sql("SELECT name FROM sqlite_master WHERE " &
|
||||||
|
|
@ -582,19 +554,6 @@ proc executeLogin(c: TForumData, username, password: string): string =
|
||||||
|
|
||||||
raise newForumError("Invalid username or password")
|
raise newForumError("Invalid username or password")
|
||||||
|
|
||||||
proc sendEmailActivation(c: TForumData, name, password,
|
|
||||||
email, salt: string) {.async.} =
|
|
||||||
let epoch = $int(epochTime())
|
|
||||||
let activateUrl = c.req.makeUri("/activateEmail?nick=$1&epoch=$2&ident=$3" %
|
|
||||||
[encodeUrl(name), encodeUrl(epoch),
|
|
||||||
encodeUrl(makeIdentHash(name, password, epoch, salt))])
|
|
||||||
|
|
||||||
let emailSentFut = sendEmailActivation(c.config, email, name, activateUrl)
|
|
||||||
yield emailSentFut
|
|
||||||
if emailSentFut.failed:
|
|
||||||
echo("[WARNING] Couldn't send activation email: ", emailSentFut.error.msg)
|
|
||||||
raise newForumError("Couldn't send activation email", @["email"])
|
|
||||||
|
|
||||||
proc executeRegister(c: TForumData, name, pass, antibot, userIp,
|
proc executeRegister(c: TForumData, name, pass, antibot, userIp,
|
||||||
email: string): Future[string] {.async.} =
|
email: string): Future[string] {.async.} =
|
||||||
## Registers a new user and returns a new session key for that user's
|
## Registers a new user and returns a new session key for that user's
|
||||||
|
|
@ -632,7 +591,9 @@ proc executeRegister(c: TForumData, name, pass, antibot, userIp,
|
||||||
let password = makePassword(pass, salt)
|
let password = makePassword(pass, salt)
|
||||||
|
|
||||||
# Send activation email.
|
# Send activation email.
|
||||||
await sendEmailActivation(c, name, password, email, salt)
|
await sendSecureEmail(
|
||||||
|
mailer, ActivateEmail, c.req, name, password, email, salt
|
||||||
|
)
|
||||||
|
|
||||||
# Add account to person table
|
# Add account to person table
|
||||||
exec(db, sql"""
|
exec(db, sql"""
|
||||||
|
|
@ -726,7 +687,9 @@ proc updateProfile(
|
||||||
if rank != EmailUnconfirmed:
|
if rank != EmailUnconfirmed:
|
||||||
raise newForumError("Rank needs a change when setting new email.")
|
raise newForumError("Rank needs a change when setting new email.")
|
||||||
|
|
||||||
await sendEmailActivation(c, row[0], row[1], row[2], row[3])
|
await sendSecureEmail(
|
||||||
|
mailer, ActivateEmail, c.req, row[0], row[1], row[2], row[3]
|
||||||
|
)
|
||||||
|
|
||||||
exec(
|
exec(
|
||||||
db,
|
db,
|
||||||
|
|
@ -1193,7 +1156,7 @@ routes:
|
||||||
except ForumError as exc:
|
except ForumError as exc:
|
||||||
resp Http400, $(%exc.data), "application/json"
|
resp Http400, $(%exc.data), "application/json"
|
||||||
|
|
||||||
post re"/deleteUser":
|
post "/deleteUser":
|
||||||
createTFD()
|
createTFD()
|
||||||
if not c.loggedIn():
|
if not c.loggedIn():
|
||||||
let err = PostError(
|
let err = PostError(
|
||||||
|
|
@ -1213,7 +1176,7 @@ routes:
|
||||||
except ForumError as exc:
|
except ForumError as exc:
|
||||||
resp Http400, $(%exc.data), "application/json"
|
resp Http400, $(%exc.data), "application/json"
|
||||||
|
|
||||||
post re"/saveProfile":
|
post "/saveProfile":
|
||||||
createTFD()
|
createTFD()
|
||||||
if not c.loggedIn():
|
if not c.loggedIn():
|
||||||
let err = PostError(
|
let err = PostError(
|
||||||
|
|
@ -1238,6 +1201,25 @@ routes:
|
||||||
let exc = (ref ForumError)(getCurrentException())
|
let exc = (ref ForumError)(getCurrentException())
|
||||||
resp Http400, $(%exc.data), "application/json"
|
resp Http400, $(%exc.data), "application/json"
|
||||||
|
|
||||||
|
post "/resetPassword":
|
||||||
|
createTFD()
|
||||||
|
if not c.loggedIn():
|
||||||
|
let err = PostError(
|
||||||
|
errorFields: @[],
|
||||||
|
message: "Not logged in."
|
||||||
|
)
|
||||||
|
resp Http401, $(%err), "application/json"
|
||||||
|
|
||||||
|
let formData = request.formData
|
||||||
|
cond "email" in formData
|
||||||
|
try:
|
||||||
|
await resetPassword(c, formData["email"].body)
|
||||||
|
resp Http200, "{}", "application/json"
|
||||||
|
except ForumError:
|
||||||
|
let exc = (ref ForumError)(getCurrentException())
|
||||||
|
resp Http400, $(%exc.data), "application/json"
|
||||||
|
|
||||||
|
|
||||||
get "/t/@id":
|
get "/t/@id":
|
||||||
cond "id" in request.params
|
cond "id" in request.params
|
||||||
|
|
||||||
|
|
@ -1342,16 +1324,6 @@ routes:
|
||||||
# else:
|
# else:
|
||||||
# resp genMain(c, "Invalid ident hash", "Nim Forum")
|
# resp genMain(c, "Invalid ident hash", "Nim Forum")
|
||||||
|
|
||||||
post "/doresetpassword":
|
|
||||||
createTFD()
|
|
||||||
echo(request.params)
|
|
||||||
cond(@"nick" != "")
|
|
||||||
|
|
||||||
# if await resetPassword(c, @"nick", @"g-recaptcha-response", request.host):
|
|
||||||
# resp genMain(c, "Email sent!", "Reset Password - Nim Forum")
|
|
||||||
# else:
|
|
||||||
# resp genMain(c, genFormResetPassword(c), "Reset Password - Nim Forum")
|
|
||||||
|
|
||||||
post "/search/?@page?":
|
post "/search/?@page?":
|
||||||
cond isFTSAvailable
|
cond isFTSAvailable
|
||||||
createTFD()
|
createTFD()
|
||||||
|
|
|
||||||
106
src/utils.nim
106
src/utils.nim
|
|
@ -7,7 +7,7 @@ from times import getTime, getGMTime, format
|
||||||
let
|
let
|
||||||
UsernameIdent* = IdentChars # TODO: Double check that everyone follows this.
|
UsernameIdent* = IdentChars # TODO: Double check that everyone follows this.
|
||||||
|
|
||||||
import frontend/karaxutils
|
import frontend/[karaxutils, error]
|
||||||
export parseInt
|
export parseInt
|
||||||
|
|
||||||
proc `%`*[T](opt: Option[T]): JsonNode =
|
proc `%`*[T](opt: Option[T]): JsonNode =
|
||||||
|
|
@ -17,11 +17,11 @@ proc `%`*[T](opt: Option[T]): JsonNode =
|
||||||
|
|
||||||
type
|
type
|
||||||
Config* = object
|
Config* = object
|
||||||
smtpAddress: string
|
smtpAddress*: string
|
||||||
smtpPort: int
|
smtpPort*: int
|
||||||
smtpUser: string
|
smtpUser*: string
|
||||||
smtpPassword: string
|
smtpPassword*: string
|
||||||
mlistAddress: string
|
mlistAddress*: string
|
||||||
recaptchaSecretKey*: string
|
recaptchaSecretKey*: string
|
||||||
recaptchaSiteKey*: string
|
recaptchaSiteKey*: string
|
||||||
isDev*: bool
|
isDev*: bool
|
||||||
|
|
@ -29,6 +29,19 @@ type
|
||||||
hostname*: string
|
hostname*: string
|
||||||
name*: string
|
name*: string
|
||||||
|
|
||||||
|
ForumError* = object of Exception
|
||||||
|
data*: PostError
|
||||||
|
|
||||||
|
proc newForumError*(message: string,
|
||||||
|
fields: seq[string] = @[]): ref ForumError =
|
||||||
|
new(result)
|
||||||
|
result.msg = message
|
||||||
|
result.data =
|
||||||
|
PostError(
|
||||||
|
errorFields: fields,
|
||||||
|
message: message
|
||||||
|
)
|
||||||
|
|
||||||
var docConfig: StringTableRef
|
var docConfig: StringTableRef
|
||||||
|
|
||||||
docConfig = rstgen.defaultConfig()
|
docConfig = rstgen.defaultConfig()
|
||||||
|
|
@ -166,84 +179,3 @@ proc rstToHtml*(content: string): string =
|
||||||
add(result, node, indWidth=0, addNewLines=false)
|
add(result, node, indWidth=0, addNewLines=false)
|
||||||
except:
|
except:
|
||||||
echo("[WARNING] Could not parse rst html.")
|
echo("[WARNING] Could not parse rst html.")
|
||||||
|
|
||||||
proc sendMail(config: Config, subject, message, recipient: string, from_addr = "forum@nim-lang.org", otherHeaders:seq[(string, string)] = @[]) {.async.} =
|
|
||||||
if config.smtpAddress.len == 0:
|
|
||||||
echo("[WARNING] Cannot send mail: no smtp server configured (smtpAddress).")
|
|
||||||
return
|
|
||||||
|
|
||||||
var client = newAsyncSmtp()
|
|
||||||
await client.connect(config.smtpAddress, Port(config.smtpPort))
|
|
||||||
if config.smtpUser.len > 0:
|
|
||||||
await client.auth(config.smtpUser, config.smtpPassword)
|
|
||||||
|
|
||||||
let toList = @[recipient]
|
|
||||||
|
|
||||||
var headers = otherHeaders
|
|
||||||
headers.add(("From", from_addr))
|
|
||||||
|
|
||||||
let encoded = createMessage(subject, message,
|
|
||||||
toList, @[], headers)
|
|
||||||
|
|
||||||
await client.sendMail(from_addr, toList, $encoded)
|
|
||||||
|
|
||||||
proc sendMailToMailingList*(config: Config, username, user_email_addr, subject, message: string, threadUrl: string, thread_id=0, post_id=0, is_reply=false) {.async.} =
|
|
||||||
# send message to a mailing list
|
|
||||||
if config.mlistAddress.len == 0:
|
|
||||||
echo("[WARNING] Cannot send mail: no mlistAddress configured.")
|
|
||||||
return
|
|
||||||
|
|
||||||
let from_addr = "$# <$#>" % [username, user_email_addr]
|
|
||||||
|
|
||||||
let date = getTime().getGMTime().format("ddd, d MMM yyyy HH:mm:ss") & " +0000"
|
|
||||||
var otherHeaders = @[
|
|
||||||
("Date", date),
|
|
||||||
("Resent-From", "forum@nim-lang.org"),
|
|
||||||
("Resent-date", date)
|
|
||||||
]
|
|
||||||
|
|
||||||
if is_reply:
|
|
||||||
let msg_id = "<forum-id-$#-$#@nim-lang.org>" % [$thread_id, $post_id]
|
|
||||||
otherHeaders.add(("Message-ID", msg_id))
|
|
||||||
let references = "<forum-tid-$#@nim-lang.org>" % [$thread_id]
|
|
||||||
otherHeaders.add(("References", references))
|
|
||||||
|
|
||||||
else: # New thread
|
|
||||||
let msg_id = "<forum-tid-$#@nim-lang.org>" % $thread_id
|
|
||||||
otherHeaders.add(("Message-ID", msg_id))
|
|
||||||
|
|
||||||
var processedMsg: string
|
|
||||||
try:
|
|
||||||
processedMsg = rstToHTML(message) & "<hr/><a href=\"" & threadUrl & "\" style=\"font-size:small\">View thread on Nim forum</a>"
|
|
||||||
otherHeaders.add(("Content-Type", "text/html; charset=\"UTF-8\""))
|
|
||||||
except:
|
|
||||||
processedMsg = message
|
|
||||||
|
|
||||||
await sendMail(config, subject, processedMsg, config.mlistAddress, from_addr=from_addr, otherHeaders=otherHeaders)
|
|
||||||
|
|
||||||
proc sendPassReset*(config: Config, email, user, resetUrl: string) {.async.} =
|
|
||||||
let message = """Hello $1,
|
|
||||||
A password reset has been requested for your account on the Nim Forum.
|
|
||||||
|
|
||||||
If you did not make this request, you can safely ignore this email.
|
|
||||||
A password reset request can be made by anyone, and it does not indicate
|
|
||||||
that your account is in any danger of being accessed by someone else.
|
|
||||||
|
|
||||||
If you do actually want to reset your password, visit this link:
|
|
||||||
|
|
||||||
$2
|
|
||||||
|
|
||||||
Thank you for being a part of the Nim community!""" % [user, resetUrl]
|
|
||||||
await sendMail(config, "Nim Forum Password Recovery", message, email)
|
|
||||||
|
|
||||||
proc sendEmailActivation*(config: Config, email, user, activateUrl: string) {.async.} =
|
|
||||||
let message = """Hello $1,
|
|
||||||
You have recently registered an account on the Nim Forum.
|
|
||||||
|
|
||||||
As the final step in your registration, we require that you confirm your email
|
|
||||||
via the following link:
|
|
||||||
|
|
||||||
$2
|
|
||||||
|
|
||||||
Thank you for registering and becoming a part of the Nim community!""" % [user, activateUrl]
|
|
||||||
await sendMail(config, "Nim Forum Account Email Confirmation", message, email)
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue