diff --git a/src/buffoonery.nim b/src/buffoonery.nim index 4b2a270..a1aec7d 100644 --- a/src/buffoonery.nim +++ b/src/buffoonery.nim @@ -1,7 +1,5 @@ -# This is just an example to get you started. A typical library package -# exports the main API in this file. Note that you cannot rename this file -# but you can remove it if you wish. - -proc add*(x, y: int): int = - ## Adds two files together. - return x + y +import buffoonery/apierror, + buffoonery/apiutils, + buffoonery/auth, + buffoonery/jsonutils +export apierror, apiutils, auth, jsonutils diff --git a/src/buffoonery/apiutils.nim b/src/buffoonery/apiutils.nim new file mode 100644 index 0000000..27d2ac5 --- /dev/null +++ b/src/buffoonery/apiutils.nim @@ -0,0 +1,96 @@ +import std/json, std/logging, std/strutils, std/sequtils +import jester, namespaced_logging + +import ./apierror + +const CONTENT_TYPE_JSON* = "application/json" + +var logNs {.threadvar.}: LoggingNamespace + +template log(): untyped = + if logNs.isNil: logNs = initLoggingNamespace("buffoonery/apiutils", lvlDebug) + logNs + +## Response Utilities +template halt*( + code: HttpCode, + headers: RawHeaders, + content: string) = + ## Immediately replies with the specified request. This means any further + ## code will not be executed after calling this template in the current + ## route. + bind TCActionSend, newHttpHeaders + result[0] = CallbackAction.TCActionSend + result[1] = code + result[2] = if isSome(result[2]): some(result[2].get & headers) + else: some(headers) + result[3] = content + result.matched = true + break allRoutes + +template sendJsonResp*( + code: HttpCode, + body: string = "", + knownOrigins: seq[string], + headersToSend: RawHeaders) = + ## Immediately send a JSON response and stop processing the request. + let reqOrigin = + if headers(request).hasKey("Origin"): $(headers(request)["Origin"]) + else: "" + + let corsHeaders = + if knownOrigins.contains(reqOrigin): + @{ + "Access-Control-Allow-Origin": reqOrigin, + "Access-Control-Allow-Credentials": "true", + "Access-Control-Allow-Methods": $(reqMethod(request)), + "Access-Control-Allow-Headers": "Authorization,X-CSRF-TOKEN" + } + else: + log().warn "Unrecognized Origin '" & reqOrigin & "', excluding CORS headers." + @{:} + + halt( + code, + cast[RawHeaders](headersToSend) & corsHeaders & @{ + "Content-Type": CONTENT_TYPE_JSON, + "Cache-Control": "no-cache" + }, + body + ) + +proc makeDataBody*(data: JsonNode): string = $(%*{"details":"","data":data }) +proc makeStatusBody*(details: string): string = $(%*{"details":details}) + +template sendErrorResp*(err: ref ApiError, knownOrigins: seq[string]): void = + log().debug err.respMsg & ( if err.msg.len > 0: ": " & err.msg else: "") + if not err.parent.isNil: log().debug " original exception: " & err.parent.msg + sendJsonResp(err.respCode, makeStatusBody(err.respMsg), knownOrigins, @{:}) + +## CORS support +template sendOptionsResp*( + allowedMethods: seq[HttpMethod], + knownOrigins: seq[string]) = + + let reqOrigin = + if headers(request).hasKey("Origin"): $(headers(request)["Origin"]) + else: "" + + let corsHeaders = + if knownOrigins.contains(reqOrigin): + @{ + "Access-Control-Allow-Origin": reqOrigin, + "Access-Control-Allow-Credentials": "true", + "Access-Control-Allow-Methods": allowedMethods.mapIt($it).join(", "), + "Access-Control-Allow-Headers": "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization,X-CSRF-TOKEN" + } + else: + log().warn "Unrecognized Origin '" & reqOrigin & "', excluding CORS headers." + log().debug "Valid origins: " & knownOrigins.join(", ") + @{:} + + halt( + Http200, + corsHeaders, + "" + )