98 lines
2.5 KiB
Nim
98 lines
2.5 KiB
Nim
import asynchttpserver, asyncdispatch, json, strtabs, base64, strutils, strformat, options
|
|
import ../lib/dns, state
|
|
|
|
const headers = {"Content-type": "text/plain; charset=utf-8"}
|
|
const authHeader = "authorization"
|
|
|
|
type
|
|
NewRecordReq = object
|
|
fqdn: string
|
|
value: string
|
|
|
|
type
|
|
Auth = tuple
|
|
name: string
|
|
password: string
|
|
|
|
proc handleAuth(req: Request, config: AppConfig): Option[Auth] =
|
|
if not req.headers.hasKey(authHeader):
|
|
return none(Auth)
|
|
|
|
let token = req.headers[authHeader].split(" ")[1]
|
|
let credentials = decode(token).split(":")
|
|
|
|
let user = credentials[0]
|
|
let pw = credentials[1]
|
|
|
|
if not config.users.hasKey(user) or config.users[user] != pw:
|
|
return none(Auth)
|
|
|
|
return some((name: user, password: pw))
|
|
|
|
proc forbidden(req: Request): Future[void] =
|
|
return req.respond(Http401, "forbidden", headers.newHttpHeaders())
|
|
|
|
proc ok(req: Request): Future[void] =
|
|
return req.respond(Http200, "ok", headers.newHttpHeaders())
|
|
|
|
proc notFound(req: Request): Future[void] =
|
|
return req.respond(Http404, "not found", headers.newHttpHeaders())
|
|
|
|
proc present(req: Request, auth: Auth, base: string): Future[void] {.async.} =
|
|
let record = to(parseJson(req.body), NewRecordReq)
|
|
let name = trimName(record.fqdn) & "." & auth.name & "." & base
|
|
|
|
echo &"Adding TXT record for {name} with value {record.value}"
|
|
|
|
addRecord(
|
|
records,
|
|
(name: name.toLowerAscii(), dtype: DnsType.TXT),
|
|
record.value
|
|
)
|
|
|
|
await ok(req)
|
|
|
|
proc cleanup(req: Request, auth: Auth, base: string): Future[void] {.async.} =
|
|
let record = to(parseJson(req.body), NewRecordReq)
|
|
let name = trimName(record.fqdn.toLowerAscii()) & "." & auth.name & "." & base
|
|
|
|
echo &"Deleting TXT record for {name} with value {record.value}"
|
|
|
|
delRecord(
|
|
records,
|
|
(name: name, dtype: DnsType.TXT),
|
|
record.value
|
|
)
|
|
|
|
await ok(req)
|
|
|
|
proc serveApi*(config: AppConfig) {.async.} =
|
|
let http = newAsyncHttpServer()
|
|
|
|
proc cb(req: Request) {.async.} =
|
|
let auth = handleAuth(req, config)
|
|
|
|
if auth.isNone():
|
|
await forbidden(req)
|
|
return
|
|
|
|
let user = auth.unsafeGet()
|
|
|
|
if req.url.path == "/present":
|
|
await present(req, user, config.base)
|
|
elif req.url.path == "/cleanup":
|
|
await cleanup(req, user, config.base)
|
|
else:
|
|
await notFound(req)
|
|
|
|
http.listen(config.apiPort)
|
|
echo &"API listening on port {config.apiPort.int}"
|
|
|
|
while true:
|
|
if http.shouldAcceptRequest():
|
|
try:
|
|
await http.acceptRequest(cb)
|
|
except:
|
|
continue
|
|
else:
|
|
await sleepAsync(500) |