WIP: tests, REST API support (auth).
This commit is contained in:
120
src/main/nim/strawbosspkg/server.nim
Normal file
120
src/main/nim/strawbosspkg/server.nim
Normal file
@ -0,0 +1,120 @@
|
||||
import asyncdispatch, bcrypt, jester, json, jwt, osproc, sequtils, tempfile, times
|
||||
|
||||
import ./configuration, ./core, private/util
|
||||
|
||||
settings:
|
||||
port = Port(8180)
|
||||
|
||||
type Worker = object
|
||||
process*: Process
|
||||
workingDir*: string
|
||||
|
||||
type
|
||||
Session = object
|
||||
user*: UserRef
|
||||
issuedAt*, expires*: TimeInfo
|
||||
|
||||
const ISO_TIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ss"
|
||||
const BCRYPT_ROUNDS = 16
|
||||
|
||||
proc makeJsonResp(status: HttpCode, details: string = ""): string =
|
||||
result = $(%* {
|
||||
"statusCode": status.int,
|
||||
"status": $status,
|
||||
"details": details
|
||||
})
|
||||
|
||||
proc newSession(user: UserRef): Session =
|
||||
result = Session(
|
||||
user: user,
|
||||
issuedAt: getGMTime(getTime()),
|
||||
expires: daysForward(7))
|
||||
|
||||
proc toJWT(session: Session): JWT =
|
||||
result = 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) }))
|
||||
|
||||
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
|
||||
let jwt = toJWT(request.headers["Authentication"])
|
||||
var secret = cfg.authSecret
|
||||
if not jwt.verify(secret): raiseEx "Unable to verify auth token."
|
||||
jwt.verifyTimeClaims()
|
||||
|
||||
# Find the user record (if authenticated)
|
||||
let username = jwt.claims["sub"].node.str
|
||||
let users = cfg.users.filterIt(it.name == username)
|
||||
if users.len != 1: raiseEx "Could not find session user."
|
||||
|
||||
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))
|
||||
|
||||
template requireAuth() =
|
||||
var session {.inject.}: Session
|
||||
try: session = extractSession(givenCfg, request)
|
||||
except: resp(Http401, makeJsonResp(Http401), "application/json")
|
||||
|
||||
proc spawnWorker(req: RunRequest): Worker =
|
||||
let dir = mkdtemp()
|
||||
var args = @["run", req.projectName, req.stepName, "-r", req.buildRef, "-w", dir]
|
||||
if req.forceRebuild: args.add("-f")
|
||||
result = Worker(
|
||||
process: startProcess("strawboss", ".", args, loadEnv(), {poUsePath}),
|
||||
workingDir: dir)
|
||||
|
||||
proc start*(givenCfg: StrawBossConfig): void =
|
||||
|
||||
var workers: seq[Worker] = @[]
|
||||
|
||||
routes:
|
||||
get "/api/ping":
|
||||
resp($(%*"pong"), "application/json")
|
||||
|
||||
get "/api/auth-token":
|
||||
resp(Http501, makeJsonResp(Http501), "application/json")
|
||||
|
||||
get "/api/projects":
|
||||
requireAuth()
|
||||
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))
|
||||
|
||||
post "/api/project/@projectName/@stepName/run/@buildRef?":
|
||||
workers.add(spawnWorker(RunRequest(
|
||||
projectName: @"projectName",
|
||||
stepName: @"stepName",
|
||||
buildRef: if @"buildRef" != "": @"buildRef" else: nil,
|
||||
forceRebuild: false))) # TODO support this with optional query params
|
||||
|
||||
runForever()
|
Reference in New Issue
Block a user