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..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() }