Fixes #19.
This commit is contained in:
parent
38f16606e8
commit
8e05dab331
3 changed files with 31 additions and 71 deletions
19
src/auth.nim
19
src/auth.nim
|
|
@ -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)
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue