Use HMAC for ident hashes and adds dedicated activateEmail page.
This commit is contained in:
parent
6ed489ed5b
commit
595b0ea086
5 changed files with 106 additions and 17 deletions
|
|
@ -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"
|
||||
|
||||
|
|
|
|||
42
src/auth.nim
42
src/auth.nim
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
58
src/frontend/activateemail.nim
Normal file
58
src/frontend/activateemail.nim
Normal 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
|
||||
|
|
@ -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",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue