Merge pull request #98 from euantorano/master
Adding reCAPTCHA rather than the custom captcha.
This commit is contained in:
commit
238903ca3b
9 changed files with 103 additions and 122 deletions
37
captchas.nim
37
captchas.nim
|
|
@ -1,37 +0,0 @@
|
|||
#
|
||||
#
|
||||
# The Nim Forum
|
||||
# (c) Copyright 2012 Andreas Rumpf, Dominik Picheta
|
||||
# Look at license.txt for more info.
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
import cairo, os, strutils, jester
|
||||
|
||||
proc getCaptchaFilename*(i: int): string {.inline.} =
|
||||
result = "public/captchas/capture_" & $i & ".png"
|
||||
|
||||
proc getCaptchaUrl*(req: Request, i: int): string =
|
||||
result = req.makeUri("/captchas/capture_" & $i & ".png", absolute = false)
|
||||
|
||||
proc createCaptcha*(file, text: string) =
|
||||
var surface = imageSurfaceCreate(FORMAT_ARGB32, int32(10*text.len), int32(10))
|
||||
var cr = create(surface)
|
||||
|
||||
selectFontFace(cr, "serif", FONT_SLANT_NORMAL, FONT_WEIGHT_BOLD)
|
||||
setFontSize(cr, 12.0)
|
||||
|
||||
setSourceRgb(cr, 1.0, 0.5, 0.0)
|
||||
moveTo(cr, 0.0, 10.0)
|
||||
showText(cr, repeat('O', text.len))
|
||||
|
||||
setSourceRgb(cr, 0.0, 0.0, 1.0)
|
||||
moveTo(cr, 0.0, 10.0)
|
||||
showText(cr, text)
|
||||
|
||||
destroy(cr)
|
||||
discard writeToPng(surface, file)
|
||||
destroy(surface)
|
||||
|
||||
when isMainModule:
|
||||
createCaptcha("test.png", "1+33")
|
||||
32
forms.tmpl
32
forms.tmpl
|
|
@ -5,7 +5,7 @@
|
|||
#end template
|
||||
#
|
||||
#
|
||||
#proc genThreadsList(c: var TForumData, count: var int): string =
|
||||
#proc genThreadsList(c: TForumData, count: var int): string =
|
||||
# const queryModAdmin = sql"""select id, name, views, modified from thread
|
||||
# where id in (select thread from post where author in
|
||||
# (select id from person where status not in ('Spammer') or id = ?))
|
||||
|
|
@ -92,7 +92,7 @@
|
|||
#end proc
|
||||
#
|
||||
#
|
||||
#proc genPostPreview(c: var TForumData,
|
||||
#proc genPostPreview(c: TForumData,
|
||||
# title, content, author, date: string): string =
|
||||
# result = ""
|
||||
<a name="preview"></a>
|
||||
|
|
@ -119,7 +119,7 @@
|
|||
#end proc
|
||||
#
|
||||
#
|
||||
#proc genPostsList(c: var TForumData, threadId: string, count: var int): string =
|
||||
#proc genPostsList(c: TForumData, threadId: string, count: var int): string =
|
||||
# let query = sql("""select p.id, u.name, p.header, p.content, p.creation, p.author, u.email from post p,
|
||||
# person u
|
||||
# where u.id = p.author and p.thread = ? and $#
|
||||
|
|
@ -180,7 +180,7 @@
|
|||
#
|
||||
#proc genMarkHelp(): string
|
||||
#end proc
|
||||
#proc genFormPost(c: var TForumData, action: string,
|
||||
#proc genFormPost(c: TForumData, action: string,
|
||||
# topText, title, content: string, isEdit: bool): string =
|
||||
# result = ""
|
||||
<br />
|
||||
|
|
@ -225,7 +225,7 @@
|
|||
#end proc
|
||||
#
|
||||
#
|
||||
#proc genFormRegister(c: var TForumData): string =
|
||||
#proc genFormRegister(c: TForumData): string =
|
||||
# result = ""
|
||||
<div id="talk-head">
|
||||
<div class="info-post">
|
||||
|
|
@ -249,10 +249,12 @@
|
|||
<td>${fieldValid(c, "email", "E-Mail:")}</td>
|
||||
<td>${textWidget(c, "email", reuseText, maxlength=300)}</td>
|
||||
</tr>
|
||||
#if useCaptcha:
|
||||
<tr>
|
||||
<td>${fieldValid(c, "antibot", "What is " & antibot(c) & "?")}</td>
|
||||
<td>${textWidget(c, "antibot", "", maxlength=4)}</td>
|
||||
<td>${fieldValid(c, "g-recaptcha-response", "Captcha:")}</td>
|
||||
<td>${captcha.render(includeNoScript=true)}</td>
|
||||
</tr>
|
||||
#end if
|
||||
</table>
|
||||
#if c.errorMsg != "":
|
||||
<div style="float: left; width: 100%;">
|
||||
|
|
@ -263,7 +265,7 @@
|
|||
</form>
|
||||
#end proc
|
||||
#
|
||||
#proc genFormSetRank(c: var TForumData; ui: TUserInfo): string =
|
||||
#proc genFormSetRank(c: TForumData; ui: TUserInfo): string =
|
||||
# result = ""
|
||||
<form action="${c.req.makeUri("/dosetrank/" & ui.nick, false)}" method="POST">
|
||||
<table border="0">
|
||||
|
|
@ -286,7 +288,7 @@
|
|||
</form>
|
||||
#end proc
|
||||
#
|
||||
#proc genFormLogin(c: var TForumData): string =
|
||||
#proc genFormLogin(c: TForumData): string =
|
||||
# result = ""
|
||||
# if not c.loggedIn:
|
||||
<form action="${c.req.makeUri("/dologin", false)}" method="POST">
|
||||
|
|
@ -305,7 +307,7 @@
|
|||
#end proc
|
||||
#
|
||||
#
|
||||
#proc genListOnline(c: var TForumData, stats: TForumStats): string =
|
||||
#proc genListOnline(c: TForumData, stats: TForumStats): string =
|
||||
# result = ""
|
||||
# var active: seq[string] = @[]
|
||||
# for i in stats.activeUsers:
|
||||
|
|
@ -322,7 +324,7 @@
|
|||
#
|
||||
#
|
||||
#
|
||||
#proc genSearchResults(c: var TForumData,
|
||||
#proc genSearchResults(c: TForumData,
|
||||
# results: iterator: db_sqlite.Row {.closure, tags: [ReadDbEffect].},
|
||||
# count: var int): string =
|
||||
# const threadId = 0
|
||||
|
|
@ -405,7 +407,7 @@
|
|||
#end proc
|
||||
#
|
||||
#
|
||||
#proc genFormResetPassword(c: var TForumData): string =
|
||||
#proc genFormResetPassword(c: TForumData): string =
|
||||
# result = ""
|
||||
<div id="talk-head">
|
||||
<div class="info-post">
|
||||
|
|
@ -421,10 +423,12 @@
|
|||
<td>${fieldValid(c, "nick", "Your nickname:")}</td>
|
||||
<td><input type="text" name="nick" maxlength="20" /></td>
|
||||
</tr>
|
||||
#if useCaptcha:
|
||||
<tr>
|
||||
<td>${fieldValid(c, "antibot", "What is " & antibot(c) & "?")}</td>
|
||||
<td>${textWidget(c, "antibot", "", maxlength=4)}</td>
|
||||
<td>${fieldValid(c, "g-recaptcha-response", "Captcha:")}</td>
|
||||
<td>${captcha.render(includeNoScript=true)}</td>
|
||||
</tr>
|
||||
#end if
|
||||
</table>
|
||||
#if c.errorMsg != "":
|
||||
<div style="float: left; width: 100%;">
|
||||
|
|
|
|||
4
forum.json.example
Normal file
4
forum.json.example
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"recaptchaSecretKey": "",
|
||||
"recaptchaSiteKey": ""
|
||||
}
|
||||
134
forum.nim
134
forum.nim
|
|
@ -8,8 +8,8 @@
|
|||
|
||||
import
|
||||
os, strutils, times, md5, strtabs, cgi, math, db_sqlite,
|
||||
captchas, scgi, jester, asyncdispatch, asyncnet, cache, sequtils,
|
||||
parseutils, utils, random, rst, ranks
|
||||
scgi, jester, asyncdispatch, asyncnet, cache, sequtils,
|
||||
parseutils, utils, random, rst, ranks, recaptcha
|
||||
|
||||
when not defined(windows):
|
||||
import bcrypt # TODO
|
||||
|
|
@ -37,7 +37,7 @@ type
|
|||
|
||||
TPost = tuple[subject, content: string]
|
||||
|
||||
TForumData = object of TSession
|
||||
TForumData = ref object of TSession
|
||||
req: Request
|
||||
userid: string
|
||||
actionContent: string
|
||||
|
|
@ -77,8 +77,10 @@ var
|
|||
db: DbConn
|
||||
isFTSAvailable: bool
|
||||
config: Config
|
||||
useCaptcha: bool
|
||||
captcha: ReCaptcha
|
||||
|
||||
proc init(c: var TForumData) =
|
||||
proc init(c: TForumData) =
|
||||
c.userPass = ""
|
||||
c.userName = ""
|
||||
c.threadId = unselectedThread
|
||||
|
|
@ -141,16 +143,16 @@ proc genThreadUrl(c: TForumData, postId = "", action = "", threadid = "", pageNu
|
|||
result.add("#" & postId)
|
||||
result = c.req.makeUri(result, absolute = false)
|
||||
|
||||
proc formSession(c: var TForumData, nextAction: string): string =
|
||||
proc formSession(c: TForumData, nextAction: string): string =
|
||||
return """<input type="hidden" name="threadid" value="$1" />
|
||||
<input type="hidden" name="postid" value="$2" />""" % [
|
||||
$c.threadId, $c.postid]
|
||||
|
||||
proc urlButton(c: var TForumData, text, url: string): string =
|
||||
proc urlButton(c: TForumData, text, url: string): string =
|
||||
return ("""<a class="url_button" href="$1">$2</a>""") % [
|
||||
url, text]
|
||||
|
||||
proc genButtons(c: var TForumData, btns: seq[TStyledButton]): string =
|
||||
proc genButtons(c: TForumData, btns: seq[TStyledButton]): string =
|
||||
if btns.len == 1:
|
||||
var anchor = ""
|
||||
|
||||
|
|
@ -274,35 +276,16 @@ proc validThreadId(c: TForumData): bool =
|
|||
result = getValue(db, sql"select id from thread where id = ?",
|
||||
$c.threadId).len > 0
|
||||
|
||||
proc antibot(c: var TForumData): string =
|
||||
let a = random(10)+1
|
||||
let b = random(1000)+1
|
||||
let answer = $(a+b)
|
||||
|
||||
exec(db, sql"delete from antibot where ip = ?", c.req.ip)
|
||||
let captchaId = tryInsertID(db,
|
||||
sql"insert into antibot(ip, answer) values (?, ?)", c.req.ip,
|
||||
answer).int mod 10_000
|
||||
let captchaFile = getCaptchaFilename(captchaId)
|
||||
createCaptcha(captchaFile, $a & "+" & $b)
|
||||
result = """<img src="$1" />""" % c.req.getCaptchaUrl(captchaId)
|
||||
|
||||
const
|
||||
SecureChars = {'A'..'Z', 'a'..'z', '0'..'9', '_', '\128'..'\255'}
|
||||
|
||||
proc setError(c: var TForumData, field, msg: string): bool {.inline.} =
|
||||
proc setError(c: TForumData, field, msg: string): bool {.inline.} =
|
||||
c.invalidField = field
|
||||
c.errorMsg = "Error: " & msg
|
||||
return false
|
||||
|
||||
proc isCaptchaCorrect(c: var TForumData, antibot: string): bool =
|
||||
## Determines whether the user typed in the captcha correctly.
|
||||
let correctRes = getValue(db,
|
||||
sql"select answer from antibot where ip = ?", c.req.ip)
|
||||
return antibot == correctRes
|
||||
|
||||
proc register(c: var TForumData, name, pass, antibot,
|
||||
email: string): bool =
|
||||
proc register(c: TForumData, name, pass, antibot, userIp,
|
||||
email: string): Future[bool] {.async.} =
|
||||
# Username validation:
|
||||
if name.len == 0 or not allCharsInSet(name, SecureChars):
|
||||
return setError(c, "name", "Invalid username!")
|
||||
|
|
@ -314,8 +297,16 @@ proc register(c: var TForumData, name, pass, antibot,
|
|||
return setError(c, "new_password", "Invalid password!")
|
||||
|
||||
# captcha validation:
|
||||
if not isCaptchaCorrect(c, antibot):
|
||||
return setError(c, "antibot", "Answer to captcha incorrect!")
|
||||
if useCaptcha:
|
||||
var captchaValid: bool = false
|
||||
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!")
|
||||
|
||||
# email validation
|
||||
if not ('@' in email and '.' in email):
|
||||
|
|
@ -350,10 +341,19 @@ proc register(c: var TForumData, name, pass, antibot,
|
|||
|
||||
return true
|
||||
|
||||
proc resetPassword(c: var TForumData, nick, antibot: string): bool =
|
||||
# Validate captcha
|
||||
if not isCaptchaCorrect(c, antibot):
|
||||
return setError(c, "antibot", "Answer to captcha incorrect!")
|
||||
proc resetPassword(c: TForumData, nick, antibot, userIp: string): Future[bool] {.async.} =
|
||||
# captcha validation:
|
||||
if useCaptcha:
|
||||
var captchaValid: bool = false
|
||||
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.
|
||||
let epoch = $int(epochTime())
|
||||
let row = db.getRow(
|
||||
|
|
@ -378,7 +378,7 @@ proc resetPassword(c: var TForumData, nick, antibot: string): bool =
|
|||
|
||||
return true
|
||||
|
||||
proc logout(c: var TForumData) =
|
||||
proc logout(c: TForumData) =
|
||||
const query = sql"delete from session where ip = ? and password = ?"
|
||||
c.username = ""
|
||||
c.userpass = ""
|
||||
|
|
@ -395,7 +395,7 @@ proc getBanErrorMsg(banValue: string; rank: Rank): string =
|
|||
of Moderated, User, Moderator, Admin:
|
||||
return ""
|
||||
|
||||
proc checkLoggedIn(c: var TForumData) =
|
||||
proc checkLoggedIn(c: TForumData) =
|
||||
if not c.req.cookies.hasKey("sid"): return
|
||||
let pass = c.req.cookies["sid"]
|
||||
if execAffectedRows(db,
|
||||
|
|
@ -425,7 +425,7 @@ proc checkLoggedIn(c: var TForumData) =
|
|||
else:
|
||||
echo("SID not found in sessions. Assuming logged out.")
|
||||
|
||||
proc incrementViews(c: var TForumData) =
|
||||
proc incrementViews(c: TForumData) =
|
||||
const query = sql"update thread set views = views + 1 where id = ?"
|
||||
exec(db, query, $c.threadId)
|
||||
|
||||
|
|
@ -435,7 +435,7 @@ proc isPreview(c: TForumData): bool =
|
|||
proc isDelete(c: TForumData): bool =
|
||||
result = c.req.params.hasKey("delete")
|
||||
|
||||
proc validateRst(c: var TForumData, content: string): bool =
|
||||
proc validateRst(c: TForumData, content: string): bool =
|
||||
result = true
|
||||
try:
|
||||
discard rstToHtml(content)
|
||||
|
|
@ -513,7 +513,7 @@ template writeToDb(c, cr, setPostId: untyped) =
|
|||
if setPostId:
|
||||
c.postId = retID.int
|
||||
|
||||
proc updateThreads(c: var TForumData): int =
|
||||
proc updateThreads(c: TForumData): int =
|
||||
## Removes threads if they have no posts, or changes their modified field
|
||||
## if they still contain posts.
|
||||
const query =
|
||||
|
|
@ -531,7 +531,7 @@ proc updateThreads(c: var TForumData): int =
|
|||
result = -1
|
||||
discard setError(c, "", "database error")
|
||||
|
||||
proc edit(c: var TForumData, postId: int): bool =
|
||||
proc edit(c: TForumData, postId: int): bool =
|
||||
checkLogin(c)
|
||||
if c.isPreview:
|
||||
retrPost(c)
|
||||
|
|
@ -564,8 +564,8 @@ proc edit(c: var TForumData, postId: int): bool =
|
|||
exec(db, crud(crUpdate, "thread", "name"), subject, $c.threadId)
|
||||
result = true
|
||||
|
||||
proc gatherUserInfo(c: var TForumData, nick: string, ui: var TUserInfo): bool
|
||||
proc spamCheck(c: var TForumData, subject, content: string): bool =
|
||||
proc gatherUserInfo(c: TForumData, nick: string, ui: var TUserInfo): bool
|
||||
proc spamCheck(c: TForumData, subject, content: string): bool =
|
||||
# Check current user's info
|
||||
var ui: TUserInfo
|
||||
if gatherUserInfo(c, c.userName, ui):
|
||||
|
|
@ -595,7 +595,7 @@ proc spamCheck(c: var TForumData, subject, content: string): bool =
|
|||
word in contentAlphabet.toLowerAscii():
|
||||
return true
|
||||
|
||||
proc rateLimitCheck(c: var TForumData): bool =
|
||||
proc rateLimitCheck(c: TForumData): bool =
|
||||
const query40 =
|
||||
sql("SELECT count(*) FROM post where author = ? and " &
|
||||
"(strftime('%s', 'now') - strftime('%s', creation)) < 40")
|
||||
|
|
@ -614,7 +614,7 @@ proc rateLimitCheck(c: var TForumData): bool =
|
|||
if last300s > 6: return true
|
||||
return false
|
||||
|
||||
proc makeThreadURL(c: var TForumData): string =
|
||||
proc makeThreadURL(c: TForumData): string =
|
||||
c.req.makeUri("/t/" & $c.threadId)
|
||||
|
||||
template postChecks() {.dirty.} =
|
||||
|
|
@ -624,7 +624,7 @@ template postChecks() {.dirty.} =
|
|||
if rateLimitCheck(c):
|
||||
return setError(c, "subject", "You're posting too fast.")
|
||||
|
||||
proc reply(c: var TForumData): bool =
|
||||
proc reply(c: TForumData): bool =
|
||||
# reply to an existing thread
|
||||
checkLogin(c)
|
||||
retrPost(c)
|
||||
|
|
@ -642,7 +642,7 @@ proc reply(c: var TForumData): bool =
|
|||
threadUrl=c.makeThreadURL())
|
||||
result = true
|
||||
|
||||
proc newThread(c: var TForumData): bool =
|
||||
proc newThread(c: TForumData): bool =
|
||||
# create new conversation thread (permanent or transient)
|
||||
const query = sql"insert into thread(name, views, modified) values (?, 0, DATETIME('now'))"
|
||||
checkLogin(c)
|
||||
|
|
@ -665,7 +665,7 @@ proc newThread(c: var TForumData): bool =
|
|||
threadUrl=c.makeThreadURL())
|
||||
result = true
|
||||
|
||||
proc login(c: var TForumData, name, pass: string): bool =
|
||||
proc login(c: TForumData, name, pass: string): bool =
|
||||
# get form data:
|
||||
const query =
|
||||
sql"select id, name, password, email, salt, status, ban from person where name = ?"
|
||||
|
|
@ -693,7 +693,7 @@ proc login(c: var TForumData, name, pass: string): bool =
|
|||
else:
|
||||
return c.setError("password", "Login failed!")
|
||||
|
||||
proc verifyIdentHash(c: var TForumData, name, epoch, ident: string): bool =
|
||||
proc verifyIdentHash(c: TForumData, name, epoch, ident: string): bool =
|
||||
const query =
|
||||
sql"select password, salt, strftime('%s', lastOnline) from person where name = ?"
|
||||
var row = getRow(db, query, name)
|
||||
|
|
@ -704,13 +704,13 @@ proc verifyIdentHash(c: var TForumData, name, epoch, ident: string): bool =
|
|||
if row[2].parseInt > (epoch.parseInt + 60): return false
|
||||
result = newIdent == ident
|
||||
|
||||
proc deleteAll(c: var TForumData, nick: string): bool =
|
||||
proc deleteAll(c: TForumData, nick: string): bool =
|
||||
const query =
|
||||
sql("delete from post where author = (select id from person where name = ?)")
|
||||
result = tryExec(db, query, nick)
|
||||
result = result and updateThreads(c) >= 0
|
||||
|
||||
proc setStatus(c: var TForumData, nick: string, status: Rank;
|
||||
proc setStatus(c: TForumData, nick: string, status: Rank;
|
||||
reason: string): bool =
|
||||
const query =
|
||||
sql("update person set status = ?, ban = ? where name = ?")
|
||||
|
|
@ -722,13 +722,13 @@ proc setStatus(c: var TForumData, nick: string, status: Rank;
|
|||
if status == Spammer and result:
|
||||
result = deleteAll(c, nick)
|
||||
|
||||
proc setPassword(c: var TForumData, nick, pass: string): bool =
|
||||
proc setPassword(c: TForumData, nick, pass: string): bool =
|
||||
const query =
|
||||
sql("update person set password = ?, salt = ? where name = ?")
|
||||
var salt = makeSalt()
|
||||
result = tryExec(db, query, makePassword(pass, salt), salt, nick)
|
||||
|
||||
proc hasReplyBtn(c: var TForumData): bool =
|
||||
proc hasReplyBtn(c: TForumData): bool =
|
||||
result = c.req.pathInfo != "/donewthread" and c.req.pathInfo != "/doreply"
|
||||
result = result and c.req.params.getOrDefault("action") notin ["reply", "edit"]
|
||||
# If the user is not logged in and there are no page numbers then we shouldn't
|
||||
|
|
@ -737,7 +737,7 @@ proc hasReplyBtn(c: var TForumData): bool =
|
|||
result = result and (pages > 1 or c.loggedIn)
|
||||
return c.threadId >= 0 and result
|
||||
|
||||
proc getStats(c: var TForumData, simple: bool): TForumStats =
|
||||
proc getStats(c: TForumData, simple: bool): TForumStats =
|
||||
const totalUsersQuery =
|
||||
sql"select count(*) from person"
|
||||
result.totalUsers = getValue(db, totalUsersQuery).parseInt
|
||||
|
|
@ -762,7 +762,7 @@ proc getStats(c: var TForumData, simple: bool): TForumStats =
|
|||
result.newestMember = (row[1], row[0].parseInt)
|
||||
newestMemberCreation = row[3].parseInt
|
||||
|
||||
proc genPagenumNav(c: var TForumData, stats: TForumStats): string =
|
||||
proc genPagenumNav(c: TForumData, stats: TForumStats): string =
|
||||
result = ""
|
||||
var
|
||||
firstUrl = ""
|
||||
|
|
@ -835,22 +835,22 @@ proc genPagenumNav(c: var TForumData, stats: TForumStats): string =
|
|||
result.add(nextTag)
|
||||
result.add(lastTag)
|
||||
|
||||
proc gatherTotalPostsByID(c: var TForumData, thrid: int): int =
|
||||
proc gatherTotalPostsByID(c: TForumData, thrid: int): int =
|
||||
## Gets the total post count of a thread.
|
||||
result = getValue(db, sql"select count(*) from post where thread = ?", $thrid).parseInt
|
||||
|
||||
proc gatherTotalPosts(c: var TForumData) =
|
||||
proc gatherTotalPosts(c: TForumData) =
|
||||
if c.totalPosts > 0: return
|
||||
# Gather some data.
|
||||
const totalPostsQuery =
|
||||
sql"select count(*) from post p, person u where u.id = p.author and p.thread = ?"
|
||||
c.totalPosts = getValue(db, totalPostsQuery, $c.threadId).parseInt
|
||||
|
||||
proc getPagesInThread(c: var TForumData): int =
|
||||
proc getPagesInThread(c: TForumData): int =
|
||||
c.gatherTotalPosts() # Get total post count
|
||||
result = ceil(c.totalPosts / PostsPerPage).int-1
|
||||
|
||||
proc getPagesInThreadByID(c: var TForumData, thrid: int): int =
|
||||
proc getPagesInThreadByID(c: TForumData, thrid: int): int =
|
||||
result = ceil(c.gatherTotalPostsByID(thrid) / PostsPerPage).int
|
||||
|
||||
proc getThreadTitle(thrid: int, pageNum: int): string =
|
||||
|
|
@ -858,7 +858,7 @@ proc getThreadTitle(thrid: int, pageNum: int): string =
|
|||
if pageNum notin {0,1}:
|
||||
result.add(" - Page " & $pageNum)
|
||||
|
||||
proc genPagenumLocalNav(c: var TForumData, thrid: int): string =
|
||||
proc genPagenumLocalNav(c: TForumData, thrid: int): string =
|
||||
result = ""
|
||||
const maxPostPages = 6 # Maximum links to pages shown.
|
||||
const hmpp = maxPostPages div 2
|
||||
|
|
@ -878,7 +878,7 @@ proc genPagenumLocalNav(c: var TForumData, thrid: int): string =
|
|||
|
||||
result = htmlgen.span(class = "pages", result)
|
||||
|
||||
proc gatherUserInfo(c: var TForumData, nick: string, ui: var TUserInfo): bool =
|
||||
proc gatherUserInfo(c: TForumData, nick: string, ui: var TUserInfo): bool =
|
||||
ui.nick = nick
|
||||
const getUIDQuery = sql"select id from person where name = ?"
|
||||
var uid = getValue(db, getUIDQuery, nick)
|
||||
|
|
@ -908,7 +908,7 @@ proc gatherUserInfo(c: var TForumData, nick: string, ui: var TUserInfo): bool =
|
|||
include "forms.tmpl"
|
||||
include "main.tmpl"
|
||||
|
||||
proc genProfile(c: var TForumData, ui: TUserInfo): string =
|
||||
proc genProfile(c: TForumData, ui: TUserInfo): string =
|
||||
result = ""
|
||||
|
||||
result.add(htmlgen.`div`(id = "talk-head",
|
||||
|
|
@ -982,6 +982,7 @@ proc prependRe(s: string): string =
|
|||
|
||||
template createTFD() =
|
||||
var c {.inject.}: TForumData
|
||||
new(c)
|
||||
init(c)
|
||||
c.req = request
|
||||
c.startTime = epochTime()
|
||||
|
|
@ -1123,7 +1124,7 @@ routes:
|
|||
|
||||
post "/doregister":
|
||||
createTFD()
|
||||
if c.register(@"name", @"new_password", @"antibot", @"email"):
|
||||
if await c.register(@"name", @"new_password", @"g-recaptcha-response", request.host, @"email"):
|
||||
resp genMain(c, "You are now registered. You must now confirm your" &
|
||||
" email address by clicking the link sent to " & @"email",
|
||||
"Registration successful - Nim Forum")
|
||||
|
|
@ -1292,7 +1293,7 @@ routes:
|
|||
echo(request.params)
|
||||
cond(@"nick" != "")
|
||||
|
||||
if resetPassword(c, @"nick", @"antibot"):
|
||||
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")
|
||||
|
|
@ -1353,6 +1354,11 @@ when isMainModule:
|
|||
isFTSAvailable = db.getAllRows(sql("SELECT name FROM sqlite_master WHERE " &
|
||||
"type='table' AND name='post_fts'")).len == 1
|
||||
config = loadConfig()
|
||||
if len(config.recaptchaSecretKey) > 0 and len(config.recaptchaSiteKey) > 0:
|
||||
useCaptcha = true
|
||||
captcha = initReCaptcha(config.recaptchaSecretKey, config.recaptchaSiteKey)
|
||||
else:
|
||||
useCaptcha = false
|
||||
var http = true
|
||||
if paramCount() > 0:
|
||||
if paramStr(1) == "scgi":
|
||||
|
|
|
|||
|
|
@ -2,3 +2,5 @@
|
|||
# we need the documentation generator of the compiler:
|
||||
path="$lib/packages/docutils"
|
||||
path="$nim"
|
||||
|
||||
-d:ssl
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
#? stdtmpl | standard
|
||||
#proc genMain(c: var TForumData, content: string, title = "Nim Forum",
|
||||
#proc genMain(c: TForumData, content: string, title = "Nim Forum",
|
||||
# additional_headers = "", showRssLinks = false): string =
|
||||
# result = ""
|
||||
# var stats: TForumStats
|
||||
|
|
@ -170,7 +170,7 @@
|
|||
</html>
|
||||
#end proc
|
||||
#
|
||||
#proc genRSSHeaders(c: var TForumData): string =
|
||||
#proc genRSSHeaders(c: TForumData): string =
|
||||
# result = ""
|
||||
<link href="${c.req.makeUri("/threadActivity.xml")}" title="Thread activity"
|
||||
type="application/atom+xml" rel="alternate">
|
||||
|
|
@ -178,7 +178,7 @@
|
|||
type="application/atom+xml" rel="alternate">
|
||||
#end proc
|
||||
#
|
||||
#proc genThreadsRSS(c: var TForumData): string =
|
||||
#proc genThreadsRSS(c: TForumData): string =
|
||||
# result = ""
|
||||
# const query = sql"""SELECT A.id, A.name,
|
||||
# strftime('%Y-%m-%dT%H:%M:%SZ', (A.modified)),
|
||||
|
|
@ -226,7 +226,7 @@ ${xmlEncode(rstToHtml(%postContent))}</content>
|
|||
</feed>
|
||||
#end proc
|
||||
#
|
||||
#proc genPostsRSS(c: var TForumData): string =
|
||||
#proc genPostsRSS(c: TForumData): string =
|
||||
# result = ""
|
||||
# const query = sql"""SELECT A.id, B.name, A.content, A.thread,
|
||||
# A.header, strftime('%Y-%m-%dT%H:%M:%SZ', A.creation),
|
||||
|
|
|
|||
|
|
@ -8,4 +8,4 @@ license = "MIT"
|
|||
bin = "forum"
|
||||
|
||||
[Deps]
|
||||
Requires: "nim >= 0.14.0, cairo#head, jester#head, bcrypt#head"
|
||||
Requires: "nim >= 0.14.0, cairo#head, jester#head, bcrypt#head, recaptcha >= 1.0.0"
|
||||
|
|
|
|||
2
public/captchas/.gitignore
vendored
2
public/captchas/.gitignore
vendored
|
|
@ -1,2 +0,0 @@
|
|||
*
|
||||
!.gitignore
|
||||
|
|
@ -22,6 +22,8 @@ type
|
|||
smtpUser: string
|
||||
smtpPassword: string
|
||||
mlistAddress: string
|
||||
recaptchaSecretKey*: string
|
||||
recaptchaSiteKey*: string
|
||||
|
||||
var docConfig: StringTableRef
|
||||
|
||||
|
|
@ -38,6 +40,8 @@ proc loadConfig*(filename = getCurrentDir() / "forum.json"): Config =
|
|||
result.smtpUser = root{"smtpUser"}.getStr("")
|
||||
result.smtpPassword = root{"smtpPassword"}.getStr("")
|
||||
result.mlistAddress = root{"mlistAddress"}.getStr("")
|
||||
result.recaptchaSecretKey = root{"recaptchaSecretKey"}.getStr("")
|
||||
result.recaptchaSiteKey = root{"recaptchaSiteKey"}.getStr("")
|
||||
except:
|
||||
echo("[WARNING] Couldn't read config file: ", filename)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue