From 889405507e2242cb627e0b88fcaf5c14016ca542 Mon Sep 17 00:00:00 2001 From: Rahul Rudragoudar Date: Mon, 3 Jun 2019 12:56:45 +0530 Subject: [PATCH 01/10] Add email registration support Signed-off-by: Rahul Rudragoudar --- src/main/scala/lc/Main.scala | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/main/scala/lc/Main.scala b/src/main/scala/lc/Main.scala index 63cdff3..58258ad 100644 --- a/src/main/scala/lc/Main.scala +++ b/src/main/scala/lc/Main.scala @@ -24,11 +24,13 @@ class Captcha(throttle: Int) { val stmt: Statement = con.createStatement() stmt.execute("CREATE TABLE IF NOT EXISTS challenge(token varchar, id varchar, secret varchar, provider varchar, contentType varchar, image blob, solved boolean default False, PRIMARY KEY(token))") stmt.execute("CREATE TABLE IF NOT EXISTS mapId(uuid varchar, token varchar, PRIMARY KEY(uuid), FOREIGN KEY(token) REFERENCES challenge(token))") + stmt.execute("CREATE TABLE IF NOT EXISTS users(email varchar, hash int)") val insertPstmt: PreparedStatement = con.prepareStatement("INSERT INTO challenge(token, id, secret, provider, contentType, image) VALUES (?, ?, ?, ?, ?, ?)") val mapPstmt: PreparedStatement = con.prepareStatement("INSERT INTO mapId(uuid, token) VALUES (?, ?)") val selectPstmt: PreparedStatement = con.prepareStatement("SELECT secret, provider FROM challenge WHERE token = ?") val imagePstmt: PreparedStatement = con.prepareStatement("SELECT image FROM challenge c, mapId m WHERE c.token=m.token AND m.uuid = ?") val updatePstmt: PreparedStatement = con.prepareStatement("UPDATE challenge SET solved = True WHERE token = ?") + val userPstmt: PreparedStatement = con.prepareStatement("INSERT INTO users(email, hash) VALUES (?,?)") val filters = Map("FilterChallenge" -> new FilterChallenge, "FontFunCaptcha" -> new FontFunCaptcha, @@ -121,6 +123,16 @@ class Captcha(throttle: Int) { filters(provider).checkAnswer(secret, answer.answer) } + def getHash(email: String): Int = { + val secret = "" + val str = email+secret + val hash = str.hashCode() + userPstmt.setString(1, email) + userPstmt.setInt(2, hash) + userPstmt.executeUpdate() + hash + } + def display(): Unit = { val rs: ResultSet = stmt.executeQuery("SELECT * FROM challenge") println("token\t\tid\t\tsecret\t\tsolved") @@ -187,6 +199,17 @@ class Server(port: Int){ 0 },"POST") + host.addContext("/v1/register", new FileContextHandler(new File("client/"))) + + host.addContext("/v1/token", (req,resp) => { + val params = req.getParams() + val hash = captcha.getHash(params.get("email")) + val token = Secret(hash) + resp.getHeaders().add("Content-Type", "application/json") + resp.send(200, write(token)) + 0 + }) + def start(): Unit = { server.start() } From c45c9f0b995554e85b52a354dafd7e13199436fa Mon Sep 17 00:00:00 2001 From: Rahul Rudragoudar Date: Mon, 3 Jun 2019 12:59:02 +0530 Subject: [PATCH 02/10] Add basic webpage template Signed-off-by: Rahul Rudragoudar --- client/index.html | 23 +++++++++++++++++++++++ client/script.js | 9 +++++++++ client/style.css | 24 ++++++++++++++++++++++++ 3 files changed, 56 insertions(+) create mode 100644 client/index.html create mode 100644 client/script.js create mode 100644 client/style.css diff --git a/client/index.html b/client/index.html new file mode 100644 index 0000000..2c9bb31 --- /dev/null +++ b/client/index.html @@ -0,0 +1,23 @@ + + + + Libre Captcha + + + +
+

Libre Captcha


+

Open Source solution to Captchas

+

v0.2 (Beta)

+
+
+ + + +
+
+

+
+ + + diff --git a/client/script.js b/client/script.js new file mode 100644 index 0000000..068467d --- /dev/null +++ b/client/script.js @@ -0,0 +1,9 @@ +document.getElementById("reg-btn").addEventListener("click", function(){ + var email = document.getElementById("email").value; + var url = window.location.origin+"/v1/token?email="+email + fetch(url) + .then(res => res.json()) + .then((data) => { + document.getElementById("token").innerHTML = "SECRET "+data.token; + }) +}) diff --git a/client/style.css b/client/style.css new file mode 100644 index 0000000..cc18455 --- /dev/null +++ b/client/style.css @@ -0,0 +1,24 @@ +body { + font-family: sans-serif; +} + +.header { + text-align: center; +} + +.form { + width: 200px; + margin: 0 auto; +} + +.form input { + width: 100%; + margin: 2px; + padding: 2px; +} + +#token { + margin: 10px; + padding: 3px; + text-align: center; +} From 67372f29b654474b0b4ddf410631004d38a088a5 Mon Sep 17 00:00:00 2001 From: Rahul Rudragoudar Date: Mon, 3 Jun 2019 13:05:07 +0530 Subject: [PATCH 03/10] Bug fix Query updated to map uuid to captcha token Signed-off-by: Rahul Rudragoudar --- src/main/scala/lc/Main.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/scala/lc/Main.scala b/src/main/scala/lc/Main.scala index 58258ad..1c4f055 100644 --- a/src/main/scala/lc/Main.scala +++ b/src/main/scala/lc/Main.scala @@ -29,7 +29,7 @@ class Captcha(throttle: Int) { val mapPstmt: PreparedStatement = con.prepareStatement("INSERT INTO mapId(uuid, token) VALUES (?, ?)") val selectPstmt: PreparedStatement = con.prepareStatement("SELECT secret, provider FROM challenge WHERE token = ?") val imagePstmt: PreparedStatement = con.prepareStatement("SELECT image FROM challenge c, mapId m WHERE c.token=m.token AND m.uuid = ?") - val updatePstmt: PreparedStatement = con.prepareStatement("UPDATE challenge SET solved = True WHERE token = ?") + val updatePstmt: PreparedStatement = con.prepareStatement("UPDATE challenge c, mapId m SET c.solved = True WHERE c.token = m.token AND m.uuid = ?") val userPstmt: PreparedStatement = con.prepareStatement("INSERT INTO users(email, hash) VALUES (?,?)") val filters = Map("FilterChallenge" -> new FilterChallenge, From 604c8736498b3ef8186a4cd8061deb699f5db348 Mon Sep 17 00:00:00 2001 From: Rahul Rudragoudar Date: Mon, 3 Jun 2019 13:20:40 +0530 Subject: [PATCH 04/10] Update travis Signed-off-by: Rahul Rudragoudar --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index a44bb25..b9d64a9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,5 @@ language: scala -jdk: oraclejdk8 +jdk: oraclejdk11 scala: - 2.12.8 script: From fc48a1924ae0eac27997f20a00fd0e1e6577365e Mon Sep 17 00:00:00 2001 From: Rahul Rudragoudar Date: Mon, 3 Jun 2019 13:26:08 +0530 Subject: [PATCH 05/10] Minor fixes Signed-off-by: Rahul Rudragoudar --- src/main/scala/lc/Main.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/scala/lc/Main.scala b/src/main/scala/lc/Main.scala index 1c4f055..9466b17 100644 --- a/src/main/scala/lc/Main.scala +++ b/src/main/scala/lc/Main.scala @@ -154,6 +154,7 @@ case class Size(height: Int, width: Int) case class Parameters(level: String, media: String, input_type: String, size: Option[Size]) case class Id(id: String) case class Answer(answer: String, id: String) +case class Secret(token: Int) class Server(port: Int){ val captcha = new Captcha(0) From 7c7285b9f7f2a63f0b338f9b375b8c299fb88a02 Mon Sep 17 00:00:00 2001 From: Rahul Rudragoudar Date: Sun, 9 Jun 2019 14:04:25 +0530 Subject: [PATCH 06/10] Conflict resolution Signed-off-by: Rahul Rudragoudar --- client/index.html | 23 +++++++++++++++++++++++ client/script.js | 9 +++++++++ client/style.css | 24 ++++++++++++++++++++++++ src/main/scala/lc/Main.scala | 26 +++++++++++++++++++++++++- 4 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 client/index.html create mode 100644 client/script.js create mode 100644 client/style.css diff --git a/client/index.html b/client/index.html new file mode 100644 index 0000000..2c9bb31 --- /dev/null +++ b/client/index.html @@ -0,0 +1,23 @@ + + + + Libre Captcha + + + +
+

Libre Captcha


+

Open Source solution to Captchas

+

v0.2 (Beta)

+
+
+ + + +
+
+

+
+ + + diff --git a/client/script.js b/client/script.js new file mode 100644 index 0000000..068467d --- /dev/null +++ b/client/script.js @@ -0,0 +1,9 @@ +document.getElementById("reg-btn").addEventListener("click", function(){ + var email = document.getElementById("email").value; + var url = window.location.origin+"/v1/token?email="+email + fetch(url) + .then(res => res.json()) + .then((data) => { + document.getElementById("token").innerHTML = "SECRET "+data.token; + }) +}) diff --git a/client/style.css b/client/style.css new file mode 100644 index 0000000..cc18455 --- /dev/null +++ b/client/style.css @@ -0,0 +1,24 @@ +body { + font-family: sans-serif; +} + +.header { + text-align: center; +} + +.form { + width: 200px; + margin: 0 auto; +} + +.form input { + width: 100%; + margin: 2px; + padding: 2px; +} + +#token { + margin: 10px; + padding: 3px; + text-align: center; +} diff --git a/src/main/scala/lc/Main.scala b/src/main/scala/lc/Main.scala index d011e96..412e27b 100644 --- a/src/main/scala/lc/Main.scala +++ b/src/main/scala/lc/Main.scala @@ -24,11 +24,13 @@ class Captcha(throttle: Int) { val stmt: Statement = con.createStatement() stmt.execute("CREATE TABLE IF NOT EXISTS challenge(token varchar, id varchar, secret varchar, provider varchar, contentType varchar, image blob, solved boolean default False, PRIMARY KEY(token))") stmt.execute("CREATE TABLE IF NOT EXISTS mapId(uuid varchar, token varchar, PRIMARY KEY(uuid), FOREIGN KEY(token) REFERENCES challenge(token))") + stmt.execute("CREATE TABLE IF NOT EXISTS users(email varchar, hash int)") val insertPstmt: PreparedStatement = con.prepareStatement("INSERT INTO challenge(token, id, secret, provider, contentType, image) VALUES (?, ?, ?, ?, ?, ?)") val mapPstmt: PreparedStatement = con.prepareStatement("INSERT INTO mapId(uuid, token) VALUES (?, ?)") val selectPstmt: PreparedStatement = con.prepareStatement("SELECT secret, provider FROM challenge WHERE token = ?") val imagePstmt: PreparedStatement = con.prepareStatement("SELECT image FROM challenge c, mapId m WHERE c.token=m.token AND m.uuid = ?") - val updatePstmt: PreparedStatement = con.prepareStatement("UPDATE challenge SET solved = True WHERE token = ?") + val updatePstmt: PreparedStatement = con.prepareStatement("UPDATE challenge c, mapId m SET c.solved = True WHERE c.token = m.token AND m.uuid = ?") + val userPstmt: PreparedStatement = con.prepareStatement("INSERT INTO users(email, hash) VALUES (?,?)") val providers = Map("FilterChallenge" -> new FilterChallenge, "FontFunCaptcha" -> new FontFunCaptcha, @@ -128,6 +130,16 @@ class Captcha(throttle: Int) { providers(provider).checkAnswer(secret, answer.answer) } + def getHash(email: String): Int = { + val secret = "" + val str = email+secret + val hash = str.hashCode() + userPstmt.setString(1, email) + userPstmt.setInt(2, hash) + userPstmt.executeUpdate() + hash + } + def display(): Unit = { val rs: ResultSet = stmt.executeQuery("SELECT * FROM challenge") println("token\t\tid\t\tsecret\t\tsolved") @@ -149,6 +161,7 @@ case class Size(height: Int, width: Int) case class Parameters(level: String, media: String, input_type: String, size: Option[Size]) case class Id(id: String) case class Answer(answer: String, id: String) +case class Secret(token: Int) class Server(port: Int){ val captcha = new Captcha(0) @@ -194,6 +207,17 @@ class Server(port: Int){ 0 },"POST") + host.addContext("/v1/register", new FileContextHandler(new File("client/"))) + + host.addContext("/v1/token", (req,resp) => { + val params = req.getParams() + val hash = captcha.getHash(params.get("email")) + val token = Secret(hash) + resp.getHeaders().add("Content-Type", "application/json") + resp.send(200, write(token)) + 0 + }) + def start(): Unit = { server.start() } From 886055c6429c5e73536d7bd11050c435c6105040 Mon Sep 17 00:00:00 2001 From: Rahul Rudragoudar Date: Sun, 9 Jun 2019 14:12:21 +0530 Subject: [PATCH 07/10] Update query to cross refernce uuid to token Signed-off-by: Rahul Rudragoudar --- src/main/scala/lc/Main.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/scala/lc/Main.scala b/src/main/scala/lc/Main.scala index 412e27b..18e4753 100644 --- a/src/main/scala/lc/Main.scala +++ b/src/main/scala/lc/Main.scala @@ -29,7 +29,7 @@ class Captcha(throttle: Int) { val mapPstmt: PreparedStatement = con.prepareStatement("INSERT INTO mapId(uuid, token) VALUES (?, ?)") val selectPstmt: PreparedStatement = con.prepareStatement("SELECT secret, provider FROM challenge WHERE token = ?") val imagePstmt: PreparedStatement = con.prepareStatement("SELECT image FROM challenge c, mapId m WHERE c.token=m.token AND m.uuid = ?") - val updatePstmt: PreparedStatement = con.prepareStatement("UPDATE challenge c, mapId m SET c.solved = True WHERE c.token = m.token AND m.uuid = ?") + val updatePstmt: PreparedStatement = con.prepareStatement("UPDATE challenge SET solved = True WHERE token = (SELECT m.token FROM mapId m, challenge c WHERE m.token=c.token AND m.uuid = ?)") val userPstmt: PreparedStatement = con.prepareStatement("INSERT INTO users(email, hash) VALUES (?,?)") val providers = Map("FilterChallenge" -> new FilterChallenge, From 1a57942a0bc9f4d2ed23140d5f3d66d6a8ac939f Mon Sep 17 00:00:00 2001 From: Rahul Rudragoudar Date: Thu, 13 Jun 2019 00:48:29 +0530 Subject: [PATCH 08/10] User validation for every request Implemented a separate class for db connection, for the ease of accessing queries Signed-off-by: Rahul Rudragoudar --- src/main/scala/lc/Main.scala | 62 ++++++++++++++++++++++++++++-------- 1 file changed, 49 insertions(+), 13 deletions(-) diff --git a/src/main/scala/lc/Main.scala b/src/main/scala/lc/Main.scala index 18e4753..4b8a236 100644 --- a/src/main/scala/lc/Main.scala +++ b/src/main/scala/lc/Main.scala @@ -19,18 +19,32 @@ import java.util.concurrent._ import java.util.UUID import scala.Array -class Captcha(throttle: Int) { +class DBConn(){ val con: Connection = DriverManager.getConnection("jdbc:h2:./captcha", "sa", "") - val stmt: Statement = con.createStatement() - stmt.execute("CREATE TABLE IF NOT EXISTS challenge(token varchar, id varchar, secret varchar, provider varchar, contentType varchar, image blob, solved boolean default False, PRIMARY KEY(token))") - stmt.execute("CREATE TABLE IF NOT EXISTS mapId(uuid varchar, token varchar, PRIMARY KEY(uuid), FOREIGN KEY(token) REFERENCES challenge(token))") - stmt.execute("CREATE TABLE IF NOT EXISTS users(email varchar, hash int)") + val insertPstmt: PreparedStatement = con.prepareStatement("INSERT INTO challenge(token, id, secret, provider, contentType, image) VALUES (?, ?, ?, ?, ?, ?)") val mapPstmt: PreparedStatement = con.prepareStatement("INSERT INTO mapId(uuid, token) VALUES (?, ?)") val selectPstmt: PreparedStatement = con.prepareStatement("SELECT secret, provider FROM challenge WHERE token = ?") val imagePstmt: PreparedStatement = con.prepareStatement("SELECT image FROM challenge c, mapId m WHERE c.token=m.token AND m.uuid = ?") val updatePstmt: PreparedStatement = con.prepareStatement("UPDATE challenge SET solved = True WHERE token = (SELECT m.token FROM mapId m, challenge c WHERE m.token=c.token AND m.uuid = ?)") val userPstmt: PreparedStatement = con.prepareStatement("INSERT INTO users(email, hash) VALUES (?,?)") + val validatePstmt: PreparedStatement = con.prepareStatement("SELECT hash FROM users WHERE hash = ? LIMIT 1") + + def getConn(): Statement = { + con.createStatement() + } + + def closeConnection(): Unit = { + con.close() + } +} + +class Captcha(throttle: Int) extends DBConn { + + val stmt = getConn() + stmt.execute("CREATE TABLE IF NOT EXISTS challenge(token varchar, id varchar, secret varchar, provider varchar, contentType varchar, image blob, solved boolean default False, PRIMARY KEY(token))") + stmt.execute("CREATE TABLE IF NOT EXISTS mapId(uuid varchar, token varchar, PRIMARY KEY(uuid), FOREIGN KEY(token) REFERENCES challenge(token))") + stmt.execute("CREATE TABLE IF NOT EXISTS users(email varchar, hash int)") val providers = Map("FilterChallenge" -> new FilterChallenge, "FontFunCaptcha" -> new FontFunCaptcha, @@ -151,10 +165,6 @@ class Captcha(throttle: Int) { println(s"${token}\t\t${id}\t\t${secret}\t\t${solved}") } } - - def closeConnection(): Unit = { - con.close() - } } case class Size(height: Int, width: Int) @@ -163,18 +173,44 @@ case class Id(id: String) case class Answer(answer: String, id: String) case class Secret(token: Int) +class RateLimiter extends DBConn { + val stmt = getConn() + val userActive = collection.mutable.Map[Int, Int]() + + def validateUser(user: Int) : Boolean = { + validatePstmt.setInt(1, user) + val rs = validatePstmt.executeQuery() + val validated = if(rs.next()){ + val hash = rs.getInt("hash") + userActive(hash) = 0 + true + } else { + false + } + validated + } +} + class Server(port: Int){ val captcha = new Captcha(0) + val rateLimiter = new RateLimiter() val server = new HTTPServer(port) val host = server.getVirtualHost(null) implicit val formats = DefaultFormats host.addContext("/v1/captcha",(req, resp) => { - val body = req.getJson() - val json = parse(body) - val param = json.extract[Parameters] - val id = captcha.getChallenge(param) + val accessToken = if(req.getHeaders().get("access-token") != null){ + req.getHeaders().get("access-token").toInt + } else 0 + val id = if(true == rateLimiter.validateUser(accessToken)){ + val body = req.getJson() + val json = parse(body) + val param = json.extract[Parameters] + captcha.getChallenge(param) + } else { + "Not a valid user! Please register." + } resp.getHeaders().add("Content-Type","application/json") resp.send(200, write(id)) 0 From a63cf3976d90a0e508318bba068cb73f092d94cb Mon Sep 17 00:00:00 2001 From: Rahul Rudragoudar Date: Mon, 17 Jun 2019 00:20:18 +0530 Subject: [PATCH 09/10] Add rate limiter Add user validation for every request Signed-off-by: Rahul Rudragoudar --- src/main/scala/lc/Main.scala | 48 ++++++++++++++++++++++++++++-------- 1 file changed, 38 insertions(+), 10 deletions(-) diff --git a/src/main/scala/lc/Main.scala b/src/main/scala/lc/Main.scala index 4b8a236..e53f46b 100644 --- a/src/main/scala/lc/Main.scala +++ b/src/main/scala/lc/Main.scala @@ -175,20 +175,48 @@ case class Secret(token: Int) class RateLimiter extends DBConn { val stmt = getConn() - val userActive = collection.mutable.Map[Int, Int]() + val userLastActive = collection.mutable.Map[Int, Long]() + val userAllowance = collection.mutable.Map[Int, Double]() + val rate = 2.0 + val per = 45.0 + val allowance = rate def validateUser(user: Int) : Boolean = { - validatePstmt.setInt(1, user) - val rs = validatePstmt.executeQuery() - val validated = if(rs.next()){ - val hash = rs.getInt("hash") - userActive(hash) = 0 + val allow = if(userLastActive.contains(user)){ true } else { - false + validatePstmt.setInt(1, user) + val rs = validatePstmt.executeQuery() + val validated = if(rs.next()){ + val hash = rs.getInt("hash") + userLastActive(hash) = System.currentTimeMillis() + userAllowance(hash) = allowance + true + } else { + false + } + validated } - validated + allow } + + def checkLimit(user: Int): Boolean = { + synchronized { + val current = System.currentTimeMillis() + val time_passed = (current - userLastActive(user)) / 1000000000 + userLastActive(user) = current + userAllowance(user) += time_passed * (rate/per) + if(userAllowance(user) > rate){ userAllowance(user) = rate } + val allow = if(userAllowance(user) < 1.0){ + false + } else { + userAllowance(user) -= 1.0 + true + } + allow + } + } + } class Server(port: Int){ @@ -203,13 +231,13 @@ class Server(port: Int){ val accessToken = if(req.getHeaders().get("access-token") != null){ req.getHeaders().get("access-token").toInt } else 0 - val id = if(true == rateLimiter.validateUser(accessToken)){ + val id = if(true == rateLimiter.validateUser(accessToken) && true == rateLimiter.checkLimit(accessToken)){ val body = req.getJson() val json = parse(body) val param = json.extract[Parameters] captcha.getChallenge(param) } else { - "Not a valid user! Please register." + "Not a valid user or rate limit reached!" } resp.getHeaders().add("Content-Type","application/json") resp.send(200, write(id)) From d65d05003835cf301db45a251da365f6f7d06941 Mon Sep 17 00:00:00 2001 From: Rahul Rudragoudar Date: Thu, 20 Jun 2019 11:45:29 +0530 Subject: [PATCH 10/10] Minor fixes Signed-off-by: Rahul Rudragoudar --- src/main/scala/lc/Main.scala | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/src/main/scala/lc/Main.scala b/src/main/scala/lc/Main.scala index e53f46b..e37c75e 100644 --- a/src/main/scala/lc/Main.scala +++ b/src/main/scala/lc/Main.scala @@ -182,22 +182,24 @@ class RateLimiter extends DBConn { val allowance = rate def validateUser(user: Int) : Boolean = { - val allow = if(userLastActive.contains(user)){ - true - } else { - validatePstmt.setInt(1, user) - val rs = validatePstmt.executeQuery() - val validated = if(rs.next()){ - val hash = rs.getInt("hash") - userLastActive(hash) = System.currentTimeMillis() - userAllowance(hash) = allowance + synchronized { + val allow = if(userLastActive.contains(user)){ true } else { - false + validatePstmt.setInt(1, user) + val rs = validatePstmt.executeQuery() + val validated = if(rs.next()){ + val hash = rs.getInt("hash") + userLastActive(hash) = System.currentTimeMillis() + userAllowance(hash) = allowance + true + } else { + false + } + validated } - validated + allow } - allow } def checkLimit(user: Int): Boolean = { @@ -228,10 +230,9 @@ class Server(port: Int){ implicit val formats = DefaultFormats host.addContext("/v1/captcha",(req, resp) => { - val accessToken = if(req.getHeaders().get("access-token") != null){ - req.getHeaders().get("access-token").toInt - } else 0 - val id = if(true == rateLimiter.validateUser(accessToken) && true == rateLimiter.checkLimit(accessToken)){ + val accessToken = Option(req.getHeaders().get("access-token")).map(_.toInt) + val access = accessToken.map(t => rateLimiter.validateUser(t) && rateLimiter.checkLimit(t)).getOrElse(false) + val id = if(access){ val body = req.getJson() val json = parse(body) val param = json.extract[Parameters]