Implemented password hashing. Added and improved tests.
This commit is contained in:
@ -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(
|
||||
|
Reference in New Issue
Block a user