diff --git a/api/personal_measure_api.config.json b/api/personal_measure_api.config.json index 532bb41..53b3c38 100644 --- a/api/personal_measure_api.config.json +++ b/api/personal_measure_api.config.json @@ -3,5 +3,6 @@ "dbConnString":"host=localhost port=5500 dbname=personal_measure user=postgres password=password", "debug":true, "port":8081, - "pwdCost":11 + "pwdCost":11, + "knownOrigins": [ "https://curl.localhost" ] } diff --git a/api/personal_measure_api.config.prod.json b/api/personal_measure_api.config.prod.json index 758520c..9bd246b 100644 --- a/api/personal_measure_api.config.prod.json +++ b/api/personal_measure_api.config.prod.json @@ -1,5 +1,6 @@ { "debug":false, "port":80, - "pwdCost":11 + "pwdCost":11, + "knownOrigins": [ "https://pm.jdb-labs.com" ] } diff --git a/api/src/main/nim/personal_measure_apipkg/api.nim b/api/src/main/nim/personal_measure_apipkg/api.nim index 9dccd3d..55737a2 100644 --- a/api/src/main/nim/personal_measure_apipkg/api.nim +++ b/api/src/main/nim/personal_measure_apipkg/api.nim @@ -32,15 +32,18 @@ template halt(code: HttpCode, result.matched = true break allRoutes -template jsonResp(code: HttpCode, details: string = "", headersToSend: RawHeaders = @{:} ) = +template jsonResp(code: HttpCode, body: string = "", headersToSend: RawHeaders = @{:} ) = + + let reqOrigin = + if request.headers.hasKey("Origin"): $(request.headers["Origin"]) + else: "" - let reqOrigin = $(request.headers["Origin"]) let corsHeaders = if ctx.cfg.knownOrigins.contains(reqOrigin): @{ "Access-Control-Allow-Origin": reqOrigin, "Access-Control-Allow-Credentials": "true", - "Access-Control-Allow-Methods": $(request.reqMeth) + "Access-Control-Allow-Methods": $(request.reqMethod) } else: @{:} @@ -50,17 +53,25 @@ template jsonResp(code: HttpCode, details: string = "", headersToSend: RawHeader "Content-Type": JSON, "Cache-Control": "no-cache" }, + body + ) + +template jsonResp(body: string) = jsonResp(Http200, body) + +template statusResp(code: HttpCode, details: string = "", headersToSend: RawHeaders = @{:} ) = + jsonResp( + code, $(%* { "statusCode": code.int, "status": $code, "details": details - }) - ) + }), + headersToSend) -template json500Resp(ex: ref Exception, details: string = ""): void = +template execptionResp(ex: ref Exception, details: string = ""): void = when not defined(release): debug ex.getStackTrace() error details & ":\n" & ex.msg - jsonResp(Http500) + statusResp(Http500) # internal JSON parsing utils proc getIfExists(n: JsonNode, key: string): JsonNode = @@ -187,10 +198,10 @@ template checkAuth(requiresAdmin = false) = try: session = extractSession(ctx, request) except: debug "Auth failed: " & getCurrentExceptionMsg() - jsonResp(Http401, "Unauthorized", @{"WWW-Authenticate": "Bearer"}) + statusResp(Http401, "Unauthorized", @{"WWW-Authenticate": "Bearer"}) if requiresAdmin and not session.user.isAdmin: - jsonResp(Http401, "Unauthorized", @{"WWW-Authenticate": "Bearer"}) + statusResp(Http401, "Unauthorized", @{"WWW-Authenticate": "Bearer"}) proc start*(ctx: PMApiContext): void = @@ -205,7 +216,7 @@ proc start*(ctx: PMApiContext): void = routes: get "/version": - resp($(%("personal_measure_api v" & PM_API_VERSION)), JSON) + jsonResp($(%("personal_measure_api v" & PM_API_VERSION))) post "/auth-token": @@ -214,9 +225,9 @@ proc start*(ctx: PMApiContext): void = let email = jsonBody.getOrFail("email").getStr let pwd = jsonBody.getOrFail("password").getStr let authToken = makeAuthToken(ctx, email, pwd) - resp($(%authToken), JSON) - except JsonParsingError: jsonResp(Http400, getCurrentExceptionMsg()) - except: jsonResp(Http401, getCurrentExceptionMsg()) + jsonResp($(%authToken)) + except JsonParsingError: statusResp(Http400, getCurrentExceptionMsg()) + except: statusResp(Http401, getCurrentExceptionMsg()) post "/change-pwd": checkAuth() @@ -229,15 +240,15 @@ proc start*(ctx: PMApiContext): void = let newHash = hashWithSalt(jsonBody.getOrFail("newPassword").getStr, session.user.salt) session.user.hashedPwd = newHash.hash - if ctx.db.updateUser(session.user): jsonResp(Http200) - else: jsonResp(Http500, "unable to change pwd") + if ctx.db.updateUser(session.user): statusResp(Http200) + else: statusResp(Http500, "unable to change pwd") - except JsonParsingError: jsonResp(Http400, getCurrentExceptionMsg()) - except BadRequestError: jsonResp(Http400, getCurrentExceptionMsg()) - except AuthError: jsonResp(Http401, getCurrentExceptionMsg()) + except JsonParsingError: statusResp(Http400, getCurrentExceptionMsg()) + except BadRequestError: statusResp(Http400, getCurrentExceptionMsg()) + except AuthError: statusResp(Http401, getCurrentExceptionMsg()) except: error "internal error changing password: " & getCurrentExceptionMsg() - jsonResp(Http500) + statusResp(Http500) post "/change-pwd/@userId": checkAuth(true) @@ -248,22 +259,22 @@ proc start*(ctx: PMApiContext): void = var user = ctx.db.getUser(parseUUID(@"userId")) let newHash = hashWithSalt(jsonBody.getOrFail("newPassword").getStr, user.salt) user.hashedPwd = newHash.hash - if ctx.db.updateUser(user): jsonResp(Http200) - else: jsonResp(Http500, "unable to change pwd") + if ctx.db.updateUser(user): statusResp(Http200) + else: statusResp(Http500, "unable to change pwd") - except ValueError: jsonResp(Http400, "invalid UUID") - except JsonParsingError: jsonResp(Http400, getCurrentExceptionMsg()) - except BadRequestError: jsonResp(Http400, getCurrentExceptionMsg()) - except AuthError: jsonResp(Http401, getCurrentExceptionMsg()) - except NotFoundError: jsonResp(Http404, "no such user") + except ValueError: statusResp(Http400, "invalid UUID") + except JsonParsingError: statusResp(Http400, getCurrentExceptionMsg()) + except BadRequestError: statusResp(Http400, getCurrentExceptionMsg()) + except AuthError: statusResp(Http401, getCurrentExceptionMsg()) + except NotFoundError: statusResp(Http404, "no such user") except: error "internal error changing password: " & getCurrentExceptionMsg() - jsonResp(Http500) + statusResp(Http500) get "/user": checkAuth() - resp(Http200, $(%session.user), JSON) + jsonResp($(%session.user)) put "/user": checkAuth() @@ -276,18 +287,18 @@ proc start*(ctx: PMApiContext): void = if jsonBody.hasKey("displayName"): updatedUser.displayName = jsonBody["displayName"].getStr() - jsonResp(Http200, $(%ctx.db.updateUser(updatedUser))) + statusResp(Http200, $(%ctx.db.updateUser(updatedUser))) - except JsonParsingError: jsonResp(Http400, getCurrentExceptionMsg()) - except BadRequestError: jsonResp(Http400, getCurrentExceptionMsg()) + except JsonParsingError: statusResp(Http400, getCurrentExceptionMsg()) + except BadRequestError: statusResp(Http400, getCurrentExceptionMsg()) except: error "Could not update user information:\n\t" & getCurrentExceptionMsg() - jsonResp(Http500) + statusResp(Http500) get "/users": checkAuth(true) - resp(Http200, $(%ctx.db.getAllUsers())) + jsonResp($(%ctx.db.getAllUsers())) post "/users": checkAuth(true) @@ -304,18 +315,18 @@ proc start*(ctx: PMApiContext): void = salt: pwdAndSalt.salt, isAdmin: false) - resp($(%ctx.db.createUser(newUser)), JSON) + jsonResp($(%ctx.db.createUser(newUser))) - except JsonParsingError: jsonResp(Http400, getCurrentExceptionMsg()) - except BadRequestError: jsonResp(Http400, getCurrentExceptionMsg()) + except JsonParsingError: statusResp(Http400, getCurrentExceptionMsg()) + except BadRequestError: statusResp(Http400, getCurrentExceptionMsg()) except: error "Could not create new user:\n\t" & getCurrentExceptionMsg() - jsonResp(Http500) + statusResp(Http500) get "/users/@userId": checkAuth(true) - resp(Http200, $(%ctx.db.getUser(parseUUID(@"userId")))) + jsonResp($(%ctx.db.getUser(parseUUID(@"userId")))) delete "/users/@userId": checkAuth(true) @@ -324,18 +335,18 @@ proc start*(ctx: PMApiContext): void = try: let userId = parseUUID(@"userId") user = ctx.db.getUser(userId) - except: jsonResp(Http404) + except: statusResp(Http404) try: if not ctx.db.deleteUser(user): raiseEx "unable to delete user" - jsonResp(Http200, "user " & user.email & " deleted") + statusResp(Http200, "user " & user.email & " deleted") - except: jsonResp(Http500, getCurrentExceptionMsg()) + except: statusResp(Http500, getCurrentExceptionMsg()) get "/api-tokens": checkAuth() - resp(Http200, $(%ctx.db.findApiTokensByUserId($session.user.id))) + jsonResp($(%ctx.db.findApiTokensByUserId($session.user.id))) post "/api-tokens": checkAuth() @@ -357,40 +368,40 @@ proc start*(ctx: PMApiContext): void = let respToken = %newToken respToken["value"] = %tokenValue - resp($respToken, JSON) + jsonResp($respToken) - except JsonParsingError: jsonResp(Http400, getCurrentExceptionMsg()) - except BadRequestError: jsonResp(Http400, getCurrentExceptionMsg()) - except AuthError: jsonResp(Http401, getCurrentExceptionMsg()) + except JsonParsingError: statusResp(Http400, getCurrentExceptionMsg()) + except BadRequestError: statusResp(Http400, getCurrentExceptionMsg()) + except AuthError: statusResp(Http401, getCurrentExceptionMsg()) except: debug getCurrentExceptionMsg() - jsonResp(Http500) + statusResp(Http500) get "/api-tokens/@tokenId": checkAuth() try: - resp(Http200, $(%ctx.db.getApiToken(parseUUID(@"tokenId")))) - except NotFoundError: jsonResp(Http404, getCurrentExceptionMsg()) - except: jsonResp(Http500) + jsonResp($(%ctx.db.getApiToken(parseUUID(@"tokenId")))) + except NotFoundError: statusResp(Http404, getCurrentExceptionMsg()) + except: statusResp(Http500) delete "/api-tokens/@tokenId": checkAuth() try: let token = ctx.db.getApiToken(parseUUID(@"tokenId")) - if ctx.db.deleteApiToken(token): jsonResp(Http200) - else: jsonResp(Http500) - except NotFoundError: jsonResp(Http404, getCurrentExceptionMsg()) - except: jsonResp(Http500) + if ctx.db.deleteApiToken(token): statusResp(Http200) + else: statusResp(Http500) + except NotFoundError: statusResp(Http404, getCurrentExceptionMsg()) + except: statusResp(Http500) get "/measures": checkAuth() - try: resp($(%ctx.db.findMeasuresByUserId($session.user.id)), JSON) + try: jsonResp($(%ctx.db.findMeasuresByUserId($session.user.id))) except: error "unable to retrieve measures for user:\n\t" & getCurrentExceptionMsg() - jsonResp(Http500) + statusResp(Http500) post "/measures": checkAuth() @@ -420,45 +431,45 @@ proc start*(ctx: PMApiContext): void = description: jsonBody.getIfExists("description").getStr(""), config: config) - resp($(%ctx.db.createMeasure(newMeasure)), JSON) + jsonResp($(%ctx.db.createMeasure(newMeasure))) - except JsonParsingError: jsonResp(Http400, getCurrentExceptionMsg()) - except BadRequestError: jsonResp(Http400, getCurrentExceptionMsg()) + except JsonParsingError: statusResp(Http400, getCurrentExceptionMsg()) + except BadRequestError: statusResp(Http400, getCurrentExceptionMsg()) except: error "unable to create new measure:\n\t" & getCurrentExceptionMsg() - jsonResp(Http500) + statusResp(Http500) get "/measures/@slug": checkAuth() - try: resp($(%ctx.getMeasureForSlug(session.user.id, @"slug")), JSON) - except NotFoundError: jsonResp(Http404, getCurrentExceptionMsg()) + try: jsonResp($(%ctx.getMeasureForSlug(session.user.id, @"slug"))) + except NotFoundError: statusResp(Http404, getCurrentExceptionMsg()) except: error "unable to look up a measure by id:\n\t" & getCurrentExceptionMsg() - jsonResp(Http500) + statusResp(Http500) delete "/measures/@slug": checkAuth() try: let measure = ctx.getMeasureForSlug(session.user.id, @"slug") - if ctx.db.deleteMeasure(measure): jsonResp(Http200) + if ctx.db.deleteMeasure(measure): statusResp(Http200) else: raiseEx "" - except NotFoundError: jsonResp(Http404, getCurrentExceptionMsg()) + except NotFoundError: statusResp(Http404, getCurrentExceptionMsg()) except: error "unable to delete a measure:\n\t" & getCurrentExceptionMsg() - jsonResp(Http500) + statusResp(Http500) get "/measure/@slug": checkAuth() try: let measure = ctx.getMeasureForSlug(session.user.id, @"slug") - resp($(%ctx.db.findMeasurementsByMeasureId($measure.id)), JSON) - except NotFoundError: jsonResp(Http404, getCurrentExceptionMsg()) + jsonResp($(%ctx.db.findMeasurementsByMeasureId($measure.id))) + except NotFoundError: statusResp(Http404, getCurrentExceptionMsg()) except: error "unable to list measurements:\n\t" & getCurrentExceptionMsg() - jsonResp(Http500) + statusResp(Http500) post "/measure/@slug": checkAuth() @@ -477,29 +488,29 @@ proc start*(ctx: PMApiContext): void = if jsonBody.hasKey("extData"): jsonBody["extData"] else: newJObject()) - resp($(%ctx.db.createMeasurement(newMeasurement)), JSON) + jsonResp($(%ctx.db.createMeasurement(newMeasurement))) - except JsonParsingError: jsonResp(Http400, getCurrentExceptionMsg()) - except BadRequestError: jsonResp(Http400, getCurrentExceptionMsg()) - except NotFoundError: jsonResp(Http404, getCurrentExceptionMsg()) + except JsonParsingError: statusResp(Http400, getCurrentExceptionMsg()) + except BadRequestError: statusResp(Http400, getCurrentExceptionMsg()) + except NotFoundError: statusResp(Http404, getCurrentExceptionMsg()) except: error "unable to add measurement:\n\t" & getCurrentExceptionMsg() - jsonResp(Http500) + statusResp(Http500) get "/measure/@slug/@id": checkAuth() try: let measure = ctx.getMeasureForSlug(session.user.id, @"slug") - resp($(%ctx.getMeasurementForMeasure(measure.id, parseUUID(@"id"))), JSON) + jsonResp($(%ctx.getMeasurementForMeasure(measure.id, parseUUID(@"id")))) - except ValueError: jsonResp(Http400, getCurrentExceptionMsg()) - except JsonParsingError: jsonResp(Http400, getCurrentExceptionMsg()) - except BadRequestError: jsonResp(Http400, getCurrentExceptionMsg()) - except NotFoundError: jsonResp(Http404, getCurrentExceptionMsg()) + except ValueError: statusResp(Http400, getCurrentExceptionMsg()) + except JsonParsingError: statusResp(Http400, getCurrentExceptionMsg()) + except BadRequestError: statusResp(Http400, getCurrentExceptionMsg()) + except NotFoundError: statusResp(Http404, getCurrentExceptionMsg()) except: error "unable to retrieve measurement:\n\t" & getCurrentExceptionMsg() - jsonResp(Http500) + statusResp(Http500) put "/measure/@slug/@id": checkAuth() @@ -511,15 +522,15 @@ proc start*(ctx: PMApiContext): void = if jsonBody.hasKey("value"): measurement.value = jsonBody["value"].getInt if jsonBody.hasKey("timestamp"): measurement.timestamp = jsonBody["timestamp"].getStr.parseIso8601 if jsonBody.hasKey("extData"): measurement.extData = jsonBody["extData"] - resp($(%ctx.db.updateMeasurement(measurement)), JSON) + jsonResp($(%ctx.db.updateMeasurement(measurement))) - except ValueError: jsonResp(Http400, getCurrentExceptionMsg()) - except JsonParsingError: jsonResp(Http400, getCurrentExceptionMsg()) - except BadRequestError: jsonResp(Http400, getCurrentExceptionMsg()) - except NotFoundError: jsonResp(Http404, getCurrentExceptionMsg()) + except ValueError: statusResp(Http400, getCurrentExceptionMsg()) + except JsonParsingError: statusResp(Http400, getCurrentExceptionMsg()) + except BadRequestError: statusResp(Http400, getCurrentExceptionMsg()) + except NotFoundError: statusResp(Http404, getCurrentExceptionMsg()) except: error "unable to retrieve measurement:\n\t" & getCurrentExceptionMsg() - jsonResp(Http500) + statusResp(Http500) delete "/measure/@slug/@id": checkAuth() @@ -527,16 +538,16 @@ proc start*(ctx: PMApiContext): void = try: let measure = ctx.getMeasureForSlug(session.user.id, @"slug") let measurement = ctx.getMeasurementForMeasure(measure.id, parseUUID(@"id")) - if ctx.db.deleteMeasurement(measurement): jsonResp(Http200) + if ctx.db.deleteMeasurement(measurement): statusResp(Http200) else: raiseEx "" - except ValueError: jsonResp(Http400, getCurrentExceptionMsg()) - except JsonParsingError: jsonResp(Http400, getCurrentExceptionMsg()) - except BadRequestError: jsonResp(Http400, getCurrentExceptionMsg()) - except NotFoundError: jsonResp(Http404, getCurrentExceptionMsg()) + except ValueError: statusResp(Http400, getCurrentExceptionMsg()) + except JsonParsingError: statusResp(Http400, getCurrentExceptionMsg()) + except BadRequestError: statusResp(Http400, getCurrentExceptionMsg()) + except NotFoundError: statusResp(Http404, getCurrentExceptionMsg()) except: error "unable to delete measurement:\n\t" & getCurrentExceptionMsg() - jsonResp(Http500) + statusResp(Http500) post "/log": checkAuth() @@ -551,9 +562,9 @@ proc start*(ctx: PMApiContext): void = stacktrace: jsonBody.getIfExists("stacktrace").getStr(""), timestamp: jsonBody.getOrFail("timestamp").getStr.parseIso8601 ) - resp(Http200, $(%ctx.db.createClientLogEntry(logEntry)), JSON) - except BadRequestError: jsonResp(Http400, getCurrentExceptionMsg()) - except: jsonResp(Http500, getCurrentExceptionMsg()) + jsonResp($(%ctx.db.createClientLogEntry(logEntry))) + except BadRequestError: statusResp(Http400, getCurrentExceptionMsg()) + except: statusResp(Http500, getCurrentExceptionMsg()) post "/log/batch": checkAuth() @@ -569,15 +580,15 @@ proc start*(ctx: PMApiContext): void = stacktrace: it.getIfExists("stacktrace").getStr(""), timestamp: it.getOrFail("timestamp").getStr.parseIso8601 )) - resp(Http200, $(%respMsgs), JSON) - except BadRequestError: jsonResp(Http400, getCurrentExceptionMsg()) - except: jsonResp(Http500, getCurrentExceptionMsg()) + jsonResp($(%respMsgs)) + except BadRequestError: statusResp(Http400, getCurrentExceptionMsg()) + except: statusResp(Http500, getCurrentExceptionMsg()) post "/service/debug/stop": - if not ctx.cfg.debug: jsonResp(Http404) + if not ctx.cfg.debug: statusResp(Http404) else: let shutdownFut = sleepAsync(100) shutdownFut.callback = proc(): void = complete(stopFuture) - resp($(%"shutting down"), JSON) + jsonResp($(%"shutting down")) waitFor(stopFuture) diff --git a/api/src/util/bash/client.sh b/api/src/util/bash/client.sh index 1347da1..d7baae1 100755 --- a/api/src/util/bash/client.sh +++ b/api/src/util/bash/client.sh @@ -17,6 +17,8 @@ fi curl -s -X "$method" \ -H "Content-Type: application/json" \ - -H "Authorization: $(cat credential)"\ + -H "Authorization: $(cat credential)" \ + -H "Origin: https://curl.localhost" \ "http://${host}/api/$url" \ - -d "$data" + -d "$data" \ + -v