diff --git a/api/api.txt b/api/api.txt index 67ac8d3..395404c 100644 --- a/api/api.txt +++ b/api/api.txt @@ -3,10 +3,10 @@ Personal Measure API ### Measure -☐ GET /measure Get a list of all defined measures for this user. -☐ POST /measure Create a new measure (post the definition). -☐ GET /measure/ Get the definition for a specific measure. -☐ DELETE /measure/ Delete a measure (and all values associated with it). +☐ GET /measures Get a list of all defined measures for this user. +☐ POST /measures Create a new measure (post the definition). +☐ GET /measures/ Get the definition for a specific measure. +☐ DELETE /measures/ Delete a measure (and all values associated with it). ### Values @@ -18,9 +18,11 @@ Personal Measure API ### Auth -☐ GET /auth-token Given a valid username/password combo, get an auth token. -☐ GET /user Given a valid auth token, return the user details. -☐ PSOT /app-token With a valid session, create a new app token. +☑ GET /auth-token Given a valid username/password combo, get an auth token. +☑ GET /user Given a valid auth token, return the user details. +☐ GET /api-tokens List api tokens. +☐ DELETE /api-tokens/ Delete a specific api token. +☐ POST /api-tokens With a valid session, create a new api token. Legend ------ diff --git a/api/personal_measure.config.json b/api/personal_measure_api.config.json similarity index 100% rename from api/personal_measure.config.json rename to api/personal_measure_api.config.json diff --git a/api/src/main/nim/personal_measure_api.nim b/api/src/main/nim/personal_measure_api.nim index 6b904cf..ba61b68 100644 --- a/api/src/main/nim/personal_measure_api.nim +++ b/api/src/main/nim/personal_measure_api.nim @@ -1,8 +1,12 @@ import cliutils, docopt, logging, jester, json, os, strutils, tables -import personal_measure_apipkg/configuration -import personal_measure_apipkg/version import personal_measure_apipkg/api +import personal_measure_apipkg/configuration +import personal_measure_apipkg/service +import personal_measure_apipkg/version + +import personal_measure_apipkg/db +import personal_measure_apipkg/models const DEFAULT_CONFIG = PMApiConfig( authSecret: "change me", @@ -15,7 +19,7 @@ proc loadConfig*(args: Table[string, docopt.Value] = initTable[string, docopt.Va let filePath = if args["--config"]: $args["--config"] - else: "personal_measure.config.json" + else: "personal_measure_api.config.json" var json: JsonNode try: json = parseFile(filePath) @@ -57,6 +61,7 @@ when isMainModule: Usage: personal_measure_api test [options] personal_measure_api serve [options] + personal_measure_api hashpwd [] [options] Options: @@ -68,15 +73,19 @@ Options: let args = docopt(doc, version = PM_API_VERSION) let ctx = initContext(args) - if args["test"]: - echo "Test" + if args["hashpwd"]: + let cost = + if args[""]: parseInt($args[""]) + else: 11 + + echo hashPwd($args[""], cast[int8](cost)) + + if args["test"]: + echo "test" + echo ctx.db.getUserByEmail("jonathan@jdbernard.com") + + if args["serve"]: start(ctx) - if args["serve"]: - start(PMApiConfig( - debug: true, - port: 8090, - pwdCost: 11, - dbConnString: "host=localhost port=5500 username=postgres password=password dbname=personal_measure")) except: fatal "pit: " & getCurrentExceptionMsg() #raise getCurrentException() diff --git a/api/src/main/nim/personal_measure_apipkg/api.nim b/api/src/main/nim/personal_measure_apipkg/api.nim index e794adb..0df45c2 100644 --- a/api/src/main/nim/personal_measure_apipkg/api.nim +++ b/api/src/main/nim/personal_measure_apipkg/api.nim @@ -1,9 +1,6 @@ -import asyncdispatch, jester, json, jwt, strutils, times, timeutils, uuids - -import ./db -import ./configuration -import ./models -import ./service +import asyncdispatch, jester, json, jwt, logging, options, sequtils, strutils, + times, timeutils, uuids +import ./db, ./configuration, ./models, ./service, ./version const JSON = "application/json" @@ -101,7 +98,10 @@ proc makeAuthToken*(ctx: PMApiContext, email, pwd: string): string = # find the user record var user: User - try: user = ctx.db.getUserByEmail(email) + try: + let users = ctx.db.getUserByEmail(email) + if users.len != 1: raiseEx "" + user = users[0] except: raiseEx "invalid username or password" if not validatePwd(user, pwd): raiseEx "invalid username or password" @@ -117,17 +117,19 @@ template checkAuth() = var session {.inject.}: Session - try: session = extractSession(cfg, request) + try: session = extractSession(ctx, request) except: debug "Auth failed: " & getCurrentExceptionMsg() jsonResp(Http401, "Unauthorized", @{"WWW-Authenticate": "Bearer"}) -proc start*(cfg: PMApiConfig): void = +proc start*(ctx: PMApiContext): void = + + if ctx.cfg.debug: setLogFilter(lvlDebug) var stopFuture = newFuture[void]() settings: - port = Port(cfg.port) + port = Port(ctx.cfg.port) appName = "/api" routes: @@ -135,12 +137,57 @@ proc start*(cfg: PMApiConfig): void = get "/version": resp($(%("personal_measure_api v" & PM_API_VERSION)), JSON) + post "/auth-token": + var email, pwd: string + try: + let jsonBody = parseJson(request.body) + email = jsonBody["email"].getStr + pwd = jsonBody["password"].getStr + except: jsonResp(Http400) + + try: + let authToken = makeAuthToken(ctx, email, pwd) + resp($(%authToken), JSON) + except: jsonResp(Http401, getCurrentExceptionMsg()) + + get "/user": + checkAuth() + + resp(Http200, $(%session.user), JSON) + post "/service/debug/stop": - if not cfg.debug: jsonResp(Http404) + if not ctx.cfg.debug: jsonResp(Http404) else: let shutdownFut = sleepAsync(100) shutdownFut.callback = proc(): void = complete(stopFuture) resp($(%"shutting down"), JSON) + get "/api-tokens": + checkAuth() + + resp(Http200, $(%ctx.db.getApiTokenByUserId($session.user.id))) + + post "/api-tokens": + checkAuth() + + var newToken: ApiToken + try: + let jsonBody = parseJson(request.body) + newToken = ApiToken( + id: genUUID(), + userId: session.user.id, + name: jsonBody["name"].getStr, + expires: none[DateTime](), + hashedToken: "") + except: jsonResp(Http400) + + try: + let tokenValue = "" # TODO + newToken.hashedToken = hashPwd(tokenValue) + ctx.db.createApiToken(token) + let respToken = %newToken + newToken["value"] = tokenValue + resp($newToken, JSON) + waitFor(stopFuture) diff --git a/api/src/main/nim/personal_measure_apipkg/models.nim b/api/src/main/nim/personal_measure_apipkg/models.nim index c57877f..f4b3677 100644 --- a/api/src/main/nim/personal_measure_apipkg/models.nim +++ b/api/src/main/nim/personal_measure_apipkg/models.nim @@ -1,4 +1,4 @@ -import options, times, timeutils, uuids +import json, options, times, timeutils, uuids type User* = object @@ -46,3 +46,44 @@ proc `$`*(m: Measure): string = proc `$`*(v: Value): string = return "Value " & ($v.id)[0..6] & " - " & ($v.measureId)[0..6] & " = " & $v.value + +proc `%`*(u: User): JsonNode = + result = %*{ + "id": $u.id, + "email": u.email, + "displayName": u.displayName + } + +proc `%`*(tok: ApiToken): JsonNode = + result = %*{ + "id": $tok.id, + "userId": $tok.userId, + "name": tok.name + } + + if tok.expires.isSome: + result["expires"] = %(tok.expires.get.formatIso8601) + +proc `%`*(m: Measure): JsonNode = + result = %*{ + "id": $m.id, + "userId": $m.userId, + "slug": m.slug, + "name": m.name, + "description": m.description, + "domainUnits": m.domainUnits, + "rangeUnits": m.rangeUnits, + "analysis": m.analysis + } + + if m.domainSource.isSome: result["domainSource"] = %(m.domainSource.get) + if m.rangeSource.isSome: result["rangeSource"] = %(m.rangeSource.get) + +proc `%`*(v: Value): JsonNode = + result = %*{ + "id": $v.id, + "measureId": $v.measureId, + "value": v.value, + "timestampe": v.timestamp.formatIso8601, + "extData": v.extData + } diff --git a/api/src/main/sql/migrations/20190214122514-initial-schema-up.sql b/api/src/main/sql/migrations/20190214122514-initial-schema-up.sql index 0208ea8..28b329c 100644 --- a/api/src/main/sql/migrations/20190214122514-initial-schema-up.sql +++ b/api/src/main/sql/migrations/20190214122514-initial-schema-up.sql @@ -4,7 +4,7 @@ create extension if not exists "uuid-ossp"; create table "users" ( id uuid default uuid_generate_v4() primary key, display_name varchar not null, - email varchar not null, + email varchar not null unique, hashed_pwd varchar not null );