This commit is contained in:
Dominik Picheta 2018-05-20 22:53:32 +01:00
commit 8e05dab331
3 changed files with 31 additions and 71 deletions

View file

@ -37,13 +37,15 @@ proc makeSalt*(): string =
newResult.add result[i] newResult.add result[i]
return newResult return newResult
proc makeSessionKey*(): string =
## Creates a random key to be used to authorize a session.
let random = makeSalt()
return bcrypt.hash(random, genSalt(8))
proc makePassword*(password, salt: string, comparingTo = ""): string = proc makePassword*(password, salt: string, comparingTo = ""): string =
## Creates an MD5 hash by combining password and salt. ## Creates an MD5 hash by combining password and salt.
when defined(windows): let bcryptSalt = if comparingTo != "": comparingTo else: genSalt(8)
result = getMD5(salt & getMD5(password)) result = hash(getMD5(salt & getMD5(password)), bcryptSalt)
else:
let bcryptSalt = if comparingTo != "": comparingTo else: genSalt(8)
result = hash(getMD5(salt & getMD5(password)), bcryptSalt)
proc makeIdentHash*(user, password: string, epoch: int64, secret: string, proc makeIdentHash*(user, password: string, epoch: int64, secret: string,
comparingTo = ""): string = comparingTo = ""): string =
@ -52,8 +54,5 @@ proc makeIdentHash*(user, password: string, epoch: int64, secret: string,
## The ``epoch`` determines the creation time of this hash, it will be checked ## The ``epoch`` determines the creation time of this hash, it will be checked
## during verification to ensure the hash hasn't expired. ## during verification to ensure the hash hasn't expired.
## The ``secret`` is the 'salt' field in the ``person`` table. ## The ``secret`` is the 'salt' field in the ``person`` table.
when defined(windows): let bcryptSalt = if comparingTo != "": comparingTo else: genSalt(8)
result = getMD5(user & password & $epoch & secret) result = hash(user & password & $epoch & secret, bcryptSalt)
else:
let bcryptSalt = if comparingTo != "": comparingTo else: genSalt(8)
result = hash(user & password & $epoch & secret, bcryptSalt)

View file

@ -23,45 +23,26 @@ import frontend/[
category, postlist, error, header, post, profile, user, karaxutils category, postlist, error, header, post, profile, user, karaxutils
] ]
when not defined(windows):
import bcrypt # TODO
from htmlgen import tr, th, td, span, input from htmlgen import tr, th, td, span, input
const const
unselectedThread = -1 unselectedThread = -1
transientThread = 0
ThreadsPerPage = 15 ThreadsPerPage = 15
PostsPerPage = 10 PostsPerPage = 10
MaxPagesFromCurrent = 8
noPageNums = ["/login", "/register", "/dologin", "/doregister", "/profile"]
noHomeBtn = ["/", "/login", "/register", "/dologin", "/doregister", "/profile"]
type type
TCrud = enum crCreate, crRead, crUpdate, crDelete TCrud = enum crCreate, crRead, crUpdate, crDelete
TSession = object of RootObj Session = object of RootObj
threadid: int
postid: int
userName, userPass, email: string userName, userPass, email: string
rank: Rank rank: Rank
TPost = tuple[subject, content: string] TPost = tuple[subject, content: string]
TForumData = ref object of TSession TForumData = ref object of Session
req: Request req: Request
userid: string userid: string
actionContent: string
errorMsg, loginErrorMsg: string
invalidField: string
currentPost: TPost ## Only used for reply previews
startTime: float
isThreadsList: bool
pageNum: int
totalPosts: int
search: string
noPagenumumNav: bool
config: Config config: Config
var var
@ -74,17 +55,8 @@ var
proc init(c: TForumData) = proc init(c: TForumData) =
c.userPass = "" c.userPass = ""
c.userName = "" c.userName = ""
c.threadId = unselectedThread
c.postId = -1
c.userid = "" c.userid = ""
c.actionContent = ""
c.errorMsg = ""
c.loginErrorMsg = ""
c.invalidField = ""
c.currentPost = (subject: "", content: "")
c.search = ""
proc loggedIn(c: TForumData): bool = proc loggedIn(c: TForumData): bool =
result = c.userName.len > 0 result = c.userName.len > 0
@ -93,7 +65,7 @@ proc loggedIn(c: TForumData): bool =
proc genThreadUrl(c: TForumData, postId = "", action = "", threadid = "", pageNum = ""): string = proc genThreadUrl(c: TForumData, postId = "", action = "", threadid = "", pageNum = ""): string =
result = "/t/" & (if threadid == "": $c.threadId else: threadid) result = "/t/" & threadid
if pageNum != "": if pageNum != "":
result.add("/" & pageNum) result.add("/" & pageNum)
if action != "": if action != "":
@ -115,10 +87,6 @@ proc getGravatarUrl(email: string, size = 80): string =
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
template `||`(x: untyped): untyped = (if not isNil(x): x else: "") template `||`(x: untyped): untyped = (if not isNil(x): x else: "")
proc setError(c: TForumData, field, msg: string): bool {.inline.} =
c.invalidField = field
c.errorMsg = "Error: " & msg
return false
proc resetPassword( proc resetPassword(
c: TForumData, c: TForumData,
@ -142,22 +110,21 @@ proc resetPassword(
) )
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 key = ?"
c.username = "" c.username = ""
c.userpass = "" c.userpass = ""
exec(db, query, c.req.ip, c.req.cookies["sid"]) exec(db, query, c.req.ip, c.req.cookies["sid"])
proc checkLoggedIn(c: TForumData) = proc checkLoggedIn(c: TForumData) =
if not c.req.cookies.hasKey("sid"): return if not c.req.cookies.hasKey("sid"): return
let pass = c.req.cookies["sid"] let sid = c.req.cookies["sid"]
if execAffectedRows(db, if execAffectedRows(db,
sql("update session set lastModified = DATETIME('now') " & sql("update session set lastModified = DATETIME('now') " &
"where ip = ? and password = ?"), "where ip = ? and key = ?"),
c.req.ip, pass) > 0: c.req.ip, sid) > 0:
c.userpass = pass
c.userid = getValue(db, c.userid = getValue(db,
sql"select userid from session where ip = ? and password = ?", sql"select userid from session where ip = ? and key = ?",
c.req.ip, pass) c.req.ip, sid)
let row = getRow(db, let row = getRow(db,
sql"select name, email, status from person where id = ?", c.userid) sql"select name, email, status from person where id = ?", c.userid)
@ -170,7 +137,7 @@ proc checkLoggedIn(c: TForumData) =
c.userid) c.userid)
else: else:
echo("SID not found in sessions. Assuming logged out.") warn("SID not found in sessions. Assuming logged out.")
proc incrementViews(threadId: int) = proc incrementViews(threadId: int) =
const query = sql"update thread set views = views + 1 where id = ?" const query = sql"update thread set views = views + 1 where id = ?"
@ -181,7 +148,7 @@ proc validateRst(c: TForumData, content: string): bool =
try: try:
discard rstToHtml(content) discard rstToHtml(content)
except EParseError: except EParseError:
result = setError(c, "", getCurrentExceptionMsg()) result = false
proc crud(c: TCrud, table: string, data: varargs[string]): SqlQuery = proc crud(c: TCrud, table: string, data: varargs[string]): SqlQuery =
case c case c
@ -273,10 +240,6 @@ template createTFD() =
new(c) new(c)
init(c) init(c)
c.req = request c.req = request
c.startTime = epochTime()
c.isThreadsList = false
c.pageNum = 1
c.config = config
if request.cookies.len > 0: if request.cookies.len > 0:
checkLoggedIn(c) checkLoggedIn(c)
@ -534,7 +497,7 @@ proc executeNewThread(c: TForumData, subject, msg: string): (int64, int64) =
raise newForumError("Subject already exists", @["subject"]) raise newForumError("Subject already exists", @["subject"])
discard tryExec(db, crud(crCreate, "thread_fts", "id", "name"), discard tryExec(db, crud(crCreate, "thread_fts", "id", "name"),
c.threadID, subject) result[0], subject)
result[1] = executeReply(c, result[0].int, msg, none[int]()) result[1] = executeReply(c, result[0].int, msg, none[int]())
discard tryExec(db, sql"insert into post_fts(post_fts) values('optimize')") discard tryExec(db, sql"insert into post_fts(post_fts) values('optimize')")
discard tryExec(db, sql"insert into post_fts(thread_fts) values('optimize')") discard tryExec(db, sql"insert into post_fts(thread_fts) values('optimize')")
@ -553,12 +516,13 @@ proc executeLogin(c: TForumData, username, password: string): string =
for row in fastRows(db, query, username, username): for row in fastRows(db, query, username, username):
if row[2] == makePassword(password, row[4], row[2]): if row[2] == makePassword(password, row[4], row[2]):
let key = makeSessionKey()
exec( exec(
db, db,
sql"insert into session (ip, password, userid) values (?, ?, ?)", sql"insert into session (ip, key, userid) values (?, ?, ?)",
c.req.ip, row[2], row[0] c.req.ip, key, row[0]
) )
return row[2] return key
raise newForumError("Invalid username or password") raise newForumError("Invalid username or password")
@ -574,9 +538,8 @@ proc validateEmail(email: string, checkDuplicated: bool) =
raise newForumError("Email already exists", @["email"]) raise newForumError("Email already exists", @["email"])
proc executeRegister(c: TForumData, name, pass, antibot, userIp, proc executeRegister(c: TForumData, name, pass, antibot, userIp,
email: string): Future[string] {.async.} = email: string) {.async.} =
## Registers a new user and returns a new session key for that user's ## Registers a new user.
## session if registration was successful. Exceptions are raised otherwise.
# email validation # email validation
validateEmail(email, checkDuplicated=true) validateEmail(email, checkDuplicated=true)
@ -619,8 +582,6 @@ proc executeRegister(c: TForumData, name, pass, antibot, userIp,
VALUES (?, ?, ?, ?, ?, DATETIME('now')) VALUES (?, ?, ?, ?, ?, DATETIME('now'))
""", name, password, email, salt, $EmailUnconfirmed) """, name, password, email, salt, $EmailUnconfirmed)
return password
proc executeLike(c: TForumData, postId: int) = proc executeLike(c: TForumData, postId: int) =
# Verify the post exists and doesn't belong to the current user. # Verify the post exists and doesn't belong to the current user.
const postQuery = sql""" const postQuery = sql"""
@ -986,7 +947,7 @@ routes:
else: else:
"" ""
try: try:
discard await executeRegister( await executeRegister(
c, c,
username, username,
password, password,
@ -1349,7 +1310,6 @@ routes:
get "/threadActivity.xml": get "/threadActivity.xml":
createTFD() createTFD()
c.isThreadsList = true
resp genThreadsRSS(c), "application/atom+xml" resp genThreadsRSS(c), "application/atom+xml"
get "/postActivity.xml": get "/postActivity.xml":
@ -1369,6 +1329,7 @@ routes:
# # if ban == EmailUnconfirmed: # # if ban == EmailUnconfirmed:
# # success = setStatus(c, @"nick", Moderated, "") # # success = setStatus(c, @"nick", Moderated, "")
when false:
post "/search/?@page?": post "/search/?@page?":
cond isFTSAvailable cond isFTSAvailable
createTFD() createTFD()

View file

@ -35,7 +35,7 @@ proc initialiseDb(admin: tuple[username, password, email: string],
const const
userNameType = "varchar(20)" userNameType = "varchar(20)"
passwordType = "varchar(50)" passwordType = "varchar(300)"
emailType = "varchar(254)" # https://stackoverflow.com/a/574698/492186 emailType = "varchar(254)" # https://stackoverflow.com/a/574698/492186
# -- Category # -- Category
@ -141,7 +141,7 @@ proc initialiseDb(admin: tuple[username, password, email: string],
create table session( create table session(
id integer primary key, id integer primary key,
ip inet not null, ip inet not null,
password $# not null, key $# not null,
userid integer not null, userid integer not null,
lastModified timestamp not null default (DATETIME('now')), lastModified timestamp not null default (DATETIME('now')),
foreign key (userid) references person(id) foreign key (userid) references person(id)