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 "nim >= 0.14.0"
|
||||||
requires "jester#64295c8"
|
requires "jester#64295c8"
|
||||||
requires "bcrypt#head"
|
requires "bcrypt#head"
|
||||||
|
requires "hmac#9c61ebe2fd134cf97"
|
||||||
requires "recaptcha 1.0.2"
|
requires "recaptcha 1.0.2"
|
||||||
requires "sass"
|
requires "sass"
|
||||||
|
|
||||||
|
|
|
||||||
42
src/auth.nim
42
src/auth.nim
|
|
@ -1,6 +1,6 @@
|
||||||
import random, md5
|
import random, md5
|
||||||
|
|
||||||
import bcrypt
|
import bcrypt, hmac
|
||||||
|
|
||||||
proc randomSalt(): string =
|
proc randomSalt(): string =
|
||||||
result = ""
|
result = ""
|
||||||
|
|
@ -47,12 +47,44 @@ proc makePassword*(password, salt: string, comparingTo = ""): string =
|
||||||
let bcryptSalt = if comparingTo != "": comparingTo else: genSalt(8)
|
let bcryptSalt = if comparingTo != "": comparingTo else: genSalt(8)
|
||||||
result = hash(getMD5(salt & getMD5(password)), bcryptSalt)
|
result = hash(getMD5(salt & getMD5(password)), bcryptSalt)
|
||||||
|
|
||||||
proc makeIdentHash*(user, password: string, epoch: int64, secret: string,
|
proc makeIdentHash*(user, password: string, epoch: int64,
|
||||||
comparingTo = ""): string =
|
secret: string): string =
|
||||||
## Creates a hash verifying the identity of a user. Used for password reset
|
## Creates a hash verifying the identity of a user. Used for password reset
|
||||||
## links and email activation links.
|
## links and email activation links.
|
||||||
## 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.
|
||||||
let bcryptSalt = if comparingTo != "": comparingTo else: genSalt(8)
|
result = hmac_sha256(secret, user & password & $epoch).toHex()
|
||||||
result = hash(user & password & $epoch & secret, bcryptSalt)
|
|
||||||
|
|
||||||
|
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)
|
var row = getRow(db, query, name)
|
||||||
if row[0] == "":
|
if row[0] == "":
|
||||||
raise newForumError("User doesn't exist.", @["nick"])
|
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.
|
# Check that it hasn't expired.
|
||||||
let diff = getTime() - epoch.fromUnix()
|
let diff = getTime() - epoch.fromUnix()
|
||||||
if diff.hours > 2:
|
if diff.hours > 2:
|
||||||
|
|
@ -1250,7 +1250,7 @@ routes:
|
||||||
except ForumError as exc:
|
except ForumError as exc:
|
||||||
resp Http400, $(%exc.data),"application/json"
|
resp Http400, $(%exc.data),"application/json"
|
||||||
|
|
||||||
get "/activateEmail":
|
post "/activateEmail":
|
||||||
createTFD()
|
createTFD()
|
||||||
cond(@"nick" != "")
|
cond(@"nick" != "")
|
||||||
cond(@"epoch" != "")
|
cond(@"epoch" != "")
|
||||||
|
|
@ -1267,9 +1267,9 @@ routes:
|
||||||
""",
|
""",
|
||||||
$Rank.Moderated, @"nick"
|
$Rank.Moderated, @"nick"
|
||||||
)
|
)
|
||||||
redirect(uri("/activateEmail/success"))
|
resp Http200, "{}", "application/json"
|
||||||
except ForumError as exc:
|
except ForumError as exc:
|
||||||
redirect(uri("/activateEmail/failure/" & encodeUrl(exc.data.message)))
|
resp Http400, $(%exc.data),"application/json"
|
||||||
|
|
||||||
get "/t/@id":
|
get "/t/@id":
|
||||||
cond "id" in request.params
|
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 jester/patterns
|
||||||
|
|
||||||
import threadlist, postlist, header, profile, newthread, error, about
|
import threadlist, postlist, header, profile, newthread, error, about
|
||||||
import resetpassword
|
import resetpassword, activateemail
|
||||||
import karaxutils
|
import karaxutils
|
||||||
|
|
||||||
type
|
type
|
||||||
|
|
@ -15,6 +15,7 @@ type
|
||||||
newThread: NewThread
|
newThread: NewThread
|
||||||
about: About
|
about: About
|
||||||
resetPassword: ResetPassword
|
resetPassword: ResetPassword
|
||||||
|
activateEmail: ActivateEmail
|
||||||
|
|
||||||
proc copyLocation(loc: Location): Location =
|
proc copyLocation(loc: Location): Location =
|
||||||
# TODO: It sucks that I had to do this. We need a nice way to deep copy in JS.
|
# 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(),
|
profile: newProfileState(),
|
||||||
newThread: newNewThread(),
|
newThread: newNewThread(),
|
||||||
about: newAbout(),
|
about: newAbout(),
|
||||||
resetPassword: newResetPassword()
|
resetPassword: newResetPassword(),
|
||||||
|
activateEmail: newActivateEmail()
|
||||||
)
|
)
|
||||||
|
|
||||||
var state = newState()
|
var state = newState()
|
||||||
|
|
@ -104,13 +106,9 @@ proc render(): VNode =
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
r("/activateEmail/failure/@msg",
|
r("/activateEmail",
|
||||||
(params: Params) => (
|
(params: Params) => (
|
||||||
renderMessage(
|
render(state.activateEmail)
|
||||||
"Email activation failed",
|
|
||||||
decodeUrl(params["msg"]),
|
|
||||||
"fa-exclamation"
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
r("/resetPassword/success",
|
r("/resetPassword/success",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue