Implemented password hashing. Added and improved tests.

This commit is contained in:
Jonathan Bernard
2017-03-24 01:04:39 -05:00
parent b5a70f6de0
commit 52b7d2f48b
12 changed files with 113 additions and 56 deletions

View File

@ -1,4 +1,5 @@
import asyncdispatch, bcrypt, jester, json, jwt, osproc, sequtils, tempfile, times
import asyncdispatch, bcrypt, jester, json, jwt, osproc, sequtils, tempfile,
times, unittest
import ./configuration, ./core, private/util
@ -12,10 +13,9 @@ type Worker = object
type
Session = object
user*: UserRef
issuedAt*, expires*: TimeInfo
issuedAt*, expires*: Time
const ISO_TIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ss"
const BCRYPT_ROUNDS = 16
proc makeJsonResp(status: HttpCode, details: string = ""): string =
result = $(%* {
@ -24,28 +24,34 @@ proc makeJsonResp(status: HttpCode, details: string = ""): string =
"details": details
})
proc newSession(user: UserRef): Session =
proc newSession*(user: UserRef): Session =
result = Session(
user: user,
issuedAt: getGMTime(getTime()),
expires: daysForward(7))
issuedAt: getTime(),
expires: daysForward(7).toTime())
proc toJWT(session: Session): JWT =
result = JWT(
proc toJWT*(cfg: StrawBossConfig, session: Session): string =
# result = toJWT(%* {
# "header": {
# "alg": "HS256",
# "typ": "JWT" },
# "claims": {
# "sub": session.user.name,
# "iat": session.issuedAt.toSeconds().int,
# "exp": session.expires.toSeconds().int } })
var jwt = JWT(
header: JOSEHeader(alg: HS256, typ: "jwt"),
claims: toClaims(%*{
"sub": session.user.name,
"iss": session.issuedAt.format(ISO_TIME_FORMAT),
"exp": session.expires.format(ISO_TIME_FORMAT) }))
"iat": session.issuedAt.toSeconds().int,
"exp": session.expires.toSeconds().int }))
proc extractSession(cfg: StrawBossConfig, request: Request): Session =
jwt.sign(cfg.authSecret)
result = $jwt
# Find the auth header
if not request.headers.hasKey("Authentication"):
raiseEx "No auth token."
# Read and verify the JWT token
let jwt = toJWT(request.headers["Authentication"])
proc fromJWT*(cfg: StrawBossConfig, strTok: string): Session =
let jwt = toJWT(strTok)
var secret = cfg.authSecret
if not jwt.verify(secret): raiseEx "Unable to verify auth token."
jwt.verifyTimeClaims()
@ -57,13 +63,17 @@ proc extractSession(cfg: StrawBossConfig, request: Request): Session =
result = Session(
user: users[0],
issuedAt: parse(jwt.claims["iat"].node.str, ISO_TIME_FORMAT),
expires: parse(jwt.claims["exp"].node.str, ISO_TIME_FORMAT))
issuedAt: fromSeconds(jwt.claims["iat"].node.num),
expires: fromSeconds(jwt.claims["exp"].node.num))
template requireAuth() =
var session {.inject.}: Session
try: session = extractSession(givenCfg, request)
except: resp(Http401, makeJsonResp(Http401), "application/json")
proc extractSession(cfg: StrawBossConfig, request: Request): Session =
# Find the auth header
if not request.headers.hasKey("Authentication"):
raiseEx "No auth token."
# Read and verify the JWT token
result = fromJWT(cfg, request.headers["Authentication"])
proc spawnWorker(req: RunRequest): Worker =
let dir = mkdtemp()
@ -73,6 +83,32 @@ proc spawnWorker(req: RunRequest): Worker =
process: startProcess("strawboss", ".", args, loadEnv(), {poUsePath}),
workingDir: dir)
proc hashPwd*(pwd: string, cost: int8): string =
let salt = genSalt(cost)
result = hash(pwd, salt)
proc validatePwd*(u: UserRef, givenPwd: string): bool =
let salt = u.hashedPwd[0..28] # TODO: magic numbers
result = compare(u.hashedPwd, hash(givenPwd, salt))
proc makeAuthToken*(cfg: StrawBossConfig, uname, pwd: string): string =
if uname == nil or pwd == nil:
raiseEx "fields 'username' and 'password' required"
# find the user record
let users = cfg.users.filterIt(it.name == uname)
if users.len != 1: raiseEx "invalid username or password"
let user = users[0]
if not validatePwd(user, pwd): raiseEx "invalid username or password"
result = toJWT(cfg, newSession(user))
template requireAuth() =
var session {.inject.}: Session
try: session = extractSession(givenCfg, request)
except: resp(Http401, makeJsonResp(Http401), "application/json")
proc start*(givenCfg: StrawBossConfig): void =
var workers: seq[Worker] = @[]
@ -89,26 +125,9 @@ proc start*(givenCfg: StrawBossConfig): void =
resp($(%(givenCfg.projects)), "application/json")
get "/api/auth-token":
var username, pwd: string
try:
username = @"username"
pwd = @"password"
except: resp(Http401, makeJsonResp(Http401, "fields 'username' and 'password' required"))
let users = givenCfg.users.filterIt(it.name == username)
if users.len != 1:
resp(Http401, makeJsonResp(Http401, "invalid username or password"))
let user = users[0]
# generate salt
let salt = genSalt(BCRYPT_ROUNDS)
# bcrypt
let hashedPwd = hash(pwd, salt)
stdout.writeLine "Hashed pwd is " & $hashedPwd
resp(Http501, makeJsonResp(Http501))
let authToken = makeAuthToken(givenCfg, @"username", @"password")
except: resp(Http401, makeJsonResp(Http401, getCurrentExceptionMsg()))
post "/api/project/@projectName/@stepName/run/@buildRef?":
workers.add(spawnWorker(RunRequest(