WIP Adding session auth and routes.
This commit is contained in:
parent
0976871563
commit
2cfb91aaeb
@ -43,12 +43,11 @@ are:
|
|||||||
* `artifactsRepo`: A string denoting the path to the artifacts repository
|
* `artifactsRepo`: A string denoting the path to the artifacts repository
|
||||||
directory.
|
directory.
|
||||||
|
|
||||||
|
* `authSecret`: Secret key used to sign JWT session tokens.
|
||||||
|
|
||||||
* `users`: the array of user definition objects. Each user object is required
|
* `users`: the array of user definition objects. Each user object is required
|
||||||
to have `username` and `hashedPwd` keys, both string.
|
to have `username` and `hashedPwd` keys, both string.
|
||||||
|
|
||||||
* `tokens`: an array of string, each representing a valid auth token that has
|
|
||||||
been issued to a client.
|
|
||||||
|
|
||||||
* `projects`: an array of project definitions (detailed below).
|
* `projects`: an array of project definitions (detailed below).
|
||||||
|
|
||||||
All are required.
|
All are required.
|
||||||
|
18
api.rst
18
api.rst
@ -1,9 +1,9 @@
|
|||||||
GET /api/ping
|
✓ GET /api/ping
|
||||||
POST /api/auth-token
|
- POST /api/auth-token
|
||||||
GET /api/projects -- return project summaries
|
✓ GET /api/projects -- return project summaries
|
||||||
POST /api/projects -- create a new project
|
- POST /api/projects -- create a new project
|
||||||
GET /api/project/<proj-id> -- return detailed project record (include steps)
|
- GET /api/project/<proj-id> -- return detailed project record (include steps)
|
||||||
GET /api/project/<proj-id>/<step-id> -- return detailed step information (include runs)
|
- GET /api/project/<proj-id>/active -- return detailed information about all currently running runs
|
||||||
POST /api/project/<proj-id>/<step-id>/run/<ref> -- kick off a run
|
- GET /api/project/<proj-id>/<step-id> -- return detailed step information (include runs)
|
||||||
GET /api/project/<proj-id>/<step-id>/run/<ref> -- return detailed run information
|
- POST /api/project/<proj-id>/<step-id>/run/<ref> -- kick off a run
|
||||||
|
- GET /api/project/<proj-id>/<step-id>/run/<ref> -- return detailed run information
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import logging, json, os, nre, sequtils, strtabs, tables
|
import logging, json, os, nre, sequtils, strtabs, tables, times
|
||||||
import private/util
|
import private/util
|
||||||
|
|
||||||
# Types
|
# Types
|
||||||
@ -21,14 +21,23 @@ type
|
|||||||
cfgFilePath*, defaultBranch*, name*, repo*: string
|
cfgFilePath*, defaultBranch*, name*, repo*: string
|
||||||
envVars*: StringTableRef
|
envVars*: StringTableRef
|
||||||
|
|
||||||
StrawBossConfig* = object
|
|
||||||
artifactsRepo*: string
|
|
||||||
projects*: seq[ProjectDef]
|
|
||||||
|
|
||||||
RunRequest* = object
|
RunRequest* = object
|
||||||
projectName*, stepName*, buildRef*, workspaceDir*: string
|
projectName*, stepName*, buildRef*, workspaceDir*: string
|
||||||
forceRebuild*: bool
|
forceRebuild*: bool
|
||||||
|
|
||||||
|
User* = object
|
||||||
|
name*: string
|
||||||
|
hashedPwd*: string
|
||||||
|
|
||||||
|
UserRef* = ref User
|
||||||
|
|
||||||
|
StrawBossConfig* = object
|
||||||
|
artifactsRepo*: string
|
||||||
|
authSecret*: string
|
||||||
|
projects*: seq[ProjectDef]
|
||||||
|
users*: seq[UserRef]
|
||||||
|
|
||||||
|
|
||||||
# internal utils
|
# internal utils
|
||||||
|
|
||||||
let nullNode = newJNull()
|
let nullNode = newJNull()
|
||||||
@ -62,9 +71,18 @@ proc loadStrawBossConfig*(cfgFile: string): StrawBossConfig =
|
|||||||
envVars: envVars,
|
envVars: envVars,
|
||||||
repo: pJson.getOrFail("repo", "project definition").getStr))
|
repo: pJson.getOrFail("repo", "project definition").getStr))
|
||||||
|
|
||||||
|
var users: seq[UserRef] = @[]
|
||||||
|
|
||||||
|
for uJson in jsonCfg.getIfExists("users").getElems:
|
||||||
|
users.add(UserRef(
|
||||||
|
name: uJson.getOrFail("name", "user record").getStr,
|
||||||
|
hashedPwd: uJson.getOrFail("hashedPwd", "user record").getStr))
|
||||||
|
|
||||||
result = StrawBossConfig(
|
result = StrawBossConfig(
|
||||||
artifactsRepo: jsonCfg.getIfExists("artifactsRepo").getStr("artifacts"),
|
artifactsRepo: jsonCfg.getIfExists("artifactsRepo").getStr("artifacts"),
|
||||||
projects: projectDefs)
|
authSecret: jsonCfg.getOrFail("authSecret", "strawboss config").getStr,
|
||||||
|
projects: projectDefs,
|
||||||
|
users: users)
|
||||||
|
|
||||||
proc loadProjectConfig*(cfgFile: string): ProjectCfg =
|
proc loadProjectConfig*(cfgFile: string): ProjectCfg =
|
||||||
if not existsFile(cfgFile):
|
if not existsFile(cfgFile):
|
||||||
@ -120,6 +138,15 @@ proc `%`*(s: BuildStatus): JsonNode =
|
|||||||
"details": s.details
|
"details": s.details
|
||||||
}
|
}
|
||||||
|
|
||||||
|
proc `%`*(p: ProjectDef): JsonNode =
|
||||||
|
result = %* {
|
||||||
|
"name": p.name,
|
||||||
|
"cfgFilePath": p.cfgFilePath,
|
||||||
|
"defaultBranch": p.defaultBranch,
|
||||||
|
"repo": p.repo }
|
||||||
|
|
||||||
|
# TODO: envVars?
|
||||||
|
|
||||||
proc `%`*(req: RunRequest): JsonNode =
|
proc `%`*(req: RunRequest): JsonNode =
|
||||||
result = %* {
|
result = %* {
|
||||||
"projectName": req.projectName,
|
"projectName": req.projectName,
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import asyncdispatch, jester, json, osproc, tempfile
|
import asyncdispatch, jester, json, jwt, osproc, sequtils, tempfile, times
|
||||||
|
|
||||||
import ./configuration, ./core, private/util
|
import ./configuration, ./core, private/util
|
||||||
|
|
||||||
@ -9,6 +9,60 @@ type Worker = object
|
|||||||
process*: Process
|
process*: Process
|
||||||
workingDir*: string
|
workingDir*: string
|
||||||
|
|
||||||
|
type
|
||||||
|
Session = object
|
||||||
|
user*: UserRef
|
||||||
|
issuedAt*, expires*: TimeInfo
|
||||||
|
|
||||||
|
const ISO_TIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ss"
|
||||||
|
|
||||||
|
proc makeJsonResp(status: HttpCode): string =
|
||||||
|
result = $(%* {
|
||||||
|
"statusCode": status.int,
|
||||||
|
"status": $status
|
||||||
|
})
|
||||||
|
|
||||||
|
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 =
|
proc spawnWorker(req: RunRequest): Worker =
|
||||||
let dir = mkdtemp()
|
let dir = mkdtemp()
|
||||||
var args = @["run", req.projectName, req.stepName, "-r", req.buildRef, "-w", dir]
|
var args = @["run", req.projectName, req.stepName, "-r", req.buildRef, "-w", dir]
|
||||||
@ -25,8 +79,13 @@ proc start*(givenCfg: StrawBossConfig): void =
|
|||||||
get "/api/ping":
|
get "/api/ping":
|
||||||
resp $(%*"pong"), "application/json"
|
resp $(%*"pong"), "application/json"
|
||||||
|
|
||||||
|
get "/api/auth-token":
|
||||||
|
resp Http501, makeJsonResp(Http501), "application/json"
|
||||||
|
|
||||||
get "/api/projects":
|
get "/api/projects":
|
||||||
resp $(%*[]), "application/json"
|
requireAuth()
|
||||||
|
#let projectDefs: seq[ProjectDef] = givenCfg.projects.mapIt(it)
|
||||||
|
resp $(%(givenCfg.projects)), "application/json"
|
||||||
|
|
||||||
post "/api/project/@projectName/@stepName/run/@buildRef?":
|
post "/api/project/@projectName/@stepName/run/@buildRef?":
|
||||||
workers.add(spawnWorker(RunRequest(
|
workers.add(spawnWorker(RunRequest(
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"artifactsRepo": "artifacts",
|
"artifactsRepo": "artifacts",
|
||||||
"users": [],
|
"users": [],
|
||||||
"tokens": [],
|
"authSecret": "change me",
|
||||||
"projects": [
|
"projects": [
|
||||||
{ "name": "new-life-intro-band",
|
{ "name": "new-life-intro-band",
|
||||||
"repo": "/home/jdb/projects/new-life-introductory-band" },
|
"repo": "/home/jdb/projects/new-life-introductory-band" },
|
||||||
|
@ -10,4 +10,5 @@ srcDir = "src/main/nim"
|
|||||||
# Dependencies
|
# Dependencies
|
||||||
|
|
||||||
requires @["nim >= 0.16.1", "docopt >= 0.1.0", "tempfile", "jester"]
|
requires @["nim >= 0.16.1", "docopt >= 0.1.0", "tempfile", "jester"]
|
||||||
|
requires "https://github.com/yglukhov/nim-jwt"
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user