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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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..e37c75e 100644
--- a/src/main/scala/lc/Main.scala
+++ b/src/main/scala/lc/Main.scala
@@ -19,16 +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))")
+
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 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,
@@ -128,6 +144,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")
@@ -139,29 +165,81 @@ 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)
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 RateLimiter extends DBConn {
+ val stmt = getConn()
+ 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 = {
+ synchronized {
+ 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
+ true
+ } else {
+ false
+ }
+ 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){
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 = 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]
+ captcha.getChallenge(param)
+ } else {
+ "Not a valid user or rate limit reached!"
+ }
resp.getHeaders().add("Content-Type","application/json")
resp.send(200, write(id))
0
@@ -194,6 +272,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()
}