Use HMAC for ident hashes and adds dedicated activateEmail page.

This commit is contained in:
Dominik Picheta 2018-05-21 15:00:24 +01:00
commit 595b0ea086
5 changed files with 106 additions and 17 deletions

View file

@ -15,6 +15,7 @@ skipExt = @["nim"]
requires "nim >= 0.14.0"
requires "jester#64295c8"
requires "bcrypt#head"
requires "hmac#9c61ebe2fd134cf97"
requires "recaptcha 1.0.2"
requires "sass"

View file

@ -1,6 +1,6 @@
import random, md5
import bcrypt
import bcrypt, hmac
proc randomSalt(): string =
result = ""
@ -47,12 +47,44 @@ proc makePassword*(password, salt: string, comparingTo = ""): string =
let bcryptSalt = if comparingTo != "": comparingTo else: genSalt(8)
result = hash(getMD5(salt & getMD5(password)), bcryptSalt)
proc makeIdentHash*(user, password: string, epoch: int64, secret: string,
comparingTo = ""): string =
proc makeIdentHash*(user, password: string, epoch: int64,
secret: string): string =
## Creates a hash verifying the identity of a user. Used for password reset
## links and email activation links.
## The ``epoch`` determines the creation time of this hash, it will be checked
## during verification to ensure the hash hasn't expired.
## The ``secret`` is the 'salt' field in the ``person`` table.
let bcryptSalt = if comparingTo != "": comparingTo else: genSalt(8)
result = hash(user & password & $epoch & secret, bcryptSalt)
result = hmac_sha256(secret, user & password & $epoch).toHex()
when isMainModule:
block:
let ident = makeIdentHash("test", "pass", 1526908753, "randomtext")
let ident2 = makeIdentHash("test", "pass", 1526908753, "randomtext")
doAssert ident == ident2
let invalid = makeIdentHash("test", "pass", 1526908754, "randomtext")
doAssert ident != invalid
block:
let ident = makeIdentHash(
"test",
"$2a$08$bY85AhoD1e9u0IsD9sM7Ee6kFSLeXRLxJ6rMgfb1wDnU9liaymoTG",
1526908753,
"*B2a] IL\"~sh)q-GBd/i$^>.TL]PR~>1IX>Fp-:M3pCm^cFD\um"
)
let ident2 = makeIdentHash(
"test",
"$2a$08$bY85AhoD1e9u0IsD9sM7Ee6kFSLeXRLxJ6rMgfb1wDnU9liaymoTG",
1526908753,
"*B2a] IL\"~sh)q-GBd/i$^>.TL]PR~>1IX>Fp-:M3pCm^cFD\um"
)
doAssert ident == ident2
let invalid = makeIdentHash(
"test",
"$2a$08$bY85AhoD1e9u0IsD9sM7Ee6kFSLeXRLxJ6rMgfb1wDnU9liaymoTG",
1526908754,
"*B2a] IL\"~sh)q-GBd/i$^>.TL]PR~>1IX>Fp-:M3pCm^cFD\um"
)
doAssert ident != invalid

View file

@ -206,7 +206,7 @@ proc verifyIdentHash(
var row = getRow(db, query, name)
if row[0] == "":
raise newForumError("User doesn't exist.", @["nick"])
let newIdent = makeIdentHash(name, row[0], epoch, row[1], ident)
let newIdent = makeIdentHash(name, row[0], epoch, row[1])
# Check that it hasn't expired.
let diff = getTime() - epoch.fromUnix()
if diff.hours > 2:
@ -1250,7 +1250,7 @@ routes:
except ForumError as exc:
resp Http400, $(%exc.data),"application/json"
get "/activateEmail":
post "/activateEmail":
createTFD()
cond(@"nick" != "")
cond(@"epoch" != "")
@ -1267,9 +1267,9 @@ routes:
""",
$Rank.Moderated, @"nick"
)
redirect(uri("/activateEmail/success"))
resp Http200, "{}", "application/json"
except ForumError as exc:
redirect(uri("/activateEmail/failure/" & encodeUrl(exc.data.message)))
resp Http400, $(%exc.data),"application/json"
get "/t/@id":
cond "id" in request.params

View file

@ -0,0 +1,58 @@
when defined(js):
import sugar, httpcore, options, json
import dom except Event
include karax/prelude
import karax / [kajax, kdom]
import error, replybox, threadlist, post
import karaxutils
type
ActivateEmail* = ref object
loading: bool
status: HttpCode
error: Option[PostError]
newPassword: kstring
proc newActivateEmail*(): ActivateEmail =
ActivateEmail(
status: Http200,
newPassword: ""
)
proc onPassChange(e: Event, n: VNode, state: ActivateEmail) =
state.newPassword = n.value
proc onPost(httpStatus: int, response: kstring, state: ActivateEmail) =
postFinished:
navigateTo(makeUri("/activateEmail/success"))
proc onSetClick(
ev: Event, n: VNode,
state: ActivateEmail
) =
state.loading = true
state.error = none[PostError]()
let uri = makeUri("activateEmail", search = $kdom.window.location.search)
ajaxPost(uri, @[], "",
(s: int, r: kstring) => onPost(s, r, state))
proc render*(state: ActivateEmail): VNode =
result = buildHtml():
section(class="container grid-xl"):
tdiv(id="activateemail"):
tdiv(class="title"):
p(): text "Activate Email"
tdiv(class="content"):
button(class=class(
{"loading": state.loading},
"btn btn-primary"
),
onClick=(ev: Event, n: VNode) =>
(onSetClick(ev, n, state))):
text "Activate"
if state.error.isSome():
p(class="text-error"):
text state.error.get().message

View file

@ -5,7 +5,7 @@ include karax/prelude
import jester/patterns
import threadlist, postlist, header, profile, newthread, error, about
import resetpassword
import resetpassword, activateemail
import karaxutils
type
@ -15,6 +15,7 @@ type
newThread: NewThread
about: About
resetPassword: ResetPassword
activateEmail: ActivateEmail
proc copyLocation(loc: Location): Location =
# TODO: It sucks that I had to do this. We need a nice way to deep copy in JS.
@ -35,7 +36,8 @@ proc newState(): State =
profile: newProfileState(),
newThread: newNewThread(),
about: newAbout(),
resetPassword: newResetPassword()
resetPassword: newResetPassword(),
activateEmail: newActivateEmail()
)
var state = newState()
@ -104,13 +106,9 @@ proc render(): VNode =
)
)
),
r("/activateEmail/failure/@msg",
r("/activateEmail",
(params: Params) => (
renderMessage(
"Email activation failed",
decodeUrl(params["msg"]),
"fa-exclamation"
)
render(state.activateEmail)
)
),
r("/resetPassword/success",