Auth still in progress
This commit is contained in:
@@ -10,6 +10,7 @@ import org.apache.http.NameValuePair
|
||||
import org.jeudego.pairgoth.util.AESCryptograph
|
||||
import org.jeudego.pairgoth.util.ApiClient.JsonApiClient
|
||||
import org.jeudego.pairgoth.util.Cryptograph
|
||||
import org.jeudego.pairgoth.util.Randomizer
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.io.IOException
|
||||
@@ -55,7 +56,7 @@ abstract class OAuthHelper {
|
||||
companion object {
|
||||
protected var logger: Logger = LoggerFactory.getLogger("oauth")
|
||||
private val cryptograph: Cryptograph = AESCryptograph().apply {
|
||||
init("0efd28fb53cbac42")
|
||||
init(Randomizer.randomString(16))
|
||||
}
|
||||
}
|
||||
}
|
@@ -15,11 +15,11 @@ object CredentialsChecker {
|
||||
val sha256 = hasher.digest(password.toByteArray(StandardCharsets.UTF_8)).toHexString()
|
||||
DriverManager.getConnection("jdbc:sqlite:$CREDENTIALS_DB").use { conn ->
|
||||
val rs =
|
||||
conn.prepareStatement("SELECT 1 FROM cred WHERE email = ? AND password = ?").apply {
|
||||
conn.prepareStatement("SELECT id FROM cred WHERE email = ? AND password = ?").apply {
|
||||
setString(1, email)
|
||||
setString(2, password)
|
||||
}.executeQuery()
|
||||
return if (rs.next()) Json.Object("email" to email) else null
|
||||
return if (rs.next()) Json.Object("id" to "${rs.getInt("id")}", "email" to email) else null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ object CredentialsChecker {
|
||||
fun initDatabase() {
|
||||
if (!File(CREDENTIALS_DB).exists()) {
|
||||
DriverManager.getConnection("jdbc:sqlite:$CREDENTIALS_DB").use { conn ->
|
||||
conn.createStatement().executeUpdate("CREATE TABLE cred (email VARCHAR(200) UNIQUE NOT NULL, password VARCHAR(200) NOT NULL)")
|
||||
conn.createStatement().executeUpdate("CREATE TABLE cred (id INTEGER PRIMARY KEY AUTOINCREMENT, email VARCHAR(200) UNIQUE NOT NULL, password VARCHAR(200) NOT NULL)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -6,8 +6,10 @@ import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import okhttp3.internal.EMPTY_REQUEST
|
||||
import org.jeudego.pairgoth.web.AuthFilter
|
||||
import org.jeudego.pairgoth.web.WebappManager
|
||||
import org.slf4j.LoggerFactory
|
||||
import javax.servlet.http.HttpServletRequest
|
||||
|
||||
class ApiTool {
|
||||
companion object {
|
||||
@@ -19,8 +21,18 @@ class ApiTool {
|
||||
}
|
||||
val logger = LoggerFactory.getLogger("api")
|
||||
}
|
||||
private lateinit var request: HttpServletRequest
|
||||
fun setRequest(req: HttpServletRequest) {
|
||||
request = req
|
||||
}
|
||||
private fun getBearer() = AuthFilter.getBearer(request)
|
||||
|
||||
private val client = OkHttpClient()
|
||||
private fun prepare(url: String) = Request.Builder().url("$apiRoot$url").header("Accept", JSON)
|
||||
private fun prepare(url: String) =
|
||||
Request.Builder().url("$apiRoot$url")
|
||||
.header("Accept", JSON)
|
||||
.header("Authorization", "Bearer ${getBearer()}")
|
||||
|
||||
private fun Json.toRequestBody() = toString().toRequestBody(JSON.toMediaType())
|
||||
private fun Request.Builder.process(): Json {
|
||||
try {
|
||||
|
@@ -2,12 +2,15 @@ package org.jeudego.pairgoth.web
|
||||
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.proxy.AsyncProxyServlet;
|
||||
import org.jeudego.pairgoth.view.ApiTool
|
||||
import javax.servlet.http.HttpServletRequest
|
||||
|
||||
class ApiServlet : AsyncProxyServlet() {
|
||||
|
||||
override fun addProxyHeaders(clientRequest: HttpServletRequest, proxyRequest: Request) {
|
||||
// proxyRequest.header("X-EGC-User", some user id...)
|
||||
AuthFilter.getBearer(clientRequest)?.let { bearer ->
|
||||
proxyRequest.header("Authorization", "Bearer $bearer")
|
||||
}
|
||||
}
|
||||
|
||||
override fun rewriteTarget(clientRequest: HttpServletRequest): String {
|
||||
|
@@ -1,6 +1,16 @@
|
||||
package org.jeudego.pairgoth.web
|
||||
|
||||
import com.republicate.kson.Json
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import org.jeudego.pairgoth.oauth.OauthHelperFactory
|
||||
import org.jeudego.pairgoth.util.AESCryptograph
|
||||
import org.jeudego.pairgoth.view.ApiTool
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.security.MessageDigest
|
||||
import javax.servlet.Filter
|
||||
import javax.servlet.FilterChain
|
||||
import javax.servlet.FilterConfig
|
||||
@@ -36,12 +46,13 @@ class AuthFilter: Filter {
|
||||
val helper = OauthHelperFactory.getHelper(provider)
|
||||
val accessToken = helper.getAccessToken(request.session.id, request.getParameter("code") ?: "")
|
||||
val user = helper.getUserInfos(accessToken)
|
||||
request.session.setAttribute("logged", user)
|
||||
handleSuccessfulLogin(req, user)
|
||||
request.session.setAttribute(SESSION_KEY_USER, user)
|
||||
response.sendRedirect("/index")
|
||||
return
|
||||
}
|
||||
|
||||
if (auth == "none" || whitelisted(uri) || forwarded || session?.getAttribute("logged") != null) {
|
||||
if (auth == "none" || whitelisted(uri) || forwarded || session?.getAttribute(SESSION_KEY_USER) != null) {
|
||||
chain.doFilter(req, resp)
|
||||
} else {
|
||||
// TODO - protection against brute force attacks
|
||||
@@ -54,6 +65,14 @@ class AuthFilter: Filter {
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val SESSION_KEY_USER = "logged"
|
||||
const val SESSION_KEY_API_TOKEN = "pairgoth-api-token"
|
||||
|
||||
private val logger = LoggerFactory.getLogger("auth")
|
||||
private val cryptograph = AESCryptograph().apply { init(sharedSecret) }
|
||||
private val hasher = MessageDigest.getInstance("SHA-256")
|
||||
private val client = OkHttpClient()
|
||||
|
||||
private val whitelist = setOf(
|
||||
"/login",
|
||||
"/index-ffg",
|
||||
@@ -66,5 +85,66 @@ class AuthFilter: Filter {
|
||||
val nolangUri = uri.replace(Regex("^/../"), "/")
|
||||
return whitelist.contains(nolangUri)
|
||||
}
|
||||
|
||||
fun handleSuccessfulLogin(req: HttpServletRequest, user: Json.Object) {
|
||||
logger.info("successful login for $user")
|
||||
req.session.setAttribute(SESSION_KEY_USER, user)
|
||||
fetchApiToken(req, user)?.also { token ->
|
||||
req.session.setAttribute(SESSION_KEY_API_TOKEN, token)
|
||||
}
|
||||
}
|
||||
|
||||
fun fetchApiToken(req: HttpServletRequest, user: Json.Object): String? {
|
||||
val challengeReq = Request.Builder().url("${ApiTool.apiRoot}tour/token")
|
||||
.header("Authorization", "Bearer ${getBearer(req)}")
|
||||
.build()
|
||||
val challengeResp = client.newCall(challengeReq).execute()
|
||||
if (challengeResp.code == HttpServletResponse.SC_UNAUTHORIZED) {
|
||||
val email = user.getString("email") ?: "-"
|
||||
val challenge = challengeResp.headers["WWW-Authenticate"]
|
||||
if (challenge != null) {
|
||||
val signature = hasher.digest(
|
||||
"${
|
||||
req.session.id
|
||||
}:${
|
||||
challenge
|
||||
}:${
|
||||
email
|
||||
}".toByteArray(StandardCharsets.UTF_8))
|
||||
val answer = Json.Object(
|
||||
"session" to req.session.id,
|
||||
"email" to email,
|
||||
"signature" to signature
|
||||
)
|
||||
val answerReq = Request.Builder().url("${ApiTool.apiRoot}tour/token").post(
|
||||
answer.toString().toRequestBody(ApiTool.JSON.toMediaType())
|
||||
).build()
|
||||
val answerResp = client.newCall(answerReq).execute()
|
||||
if (answerResp.isSuccessful && "json" == answerResp.body?.contentType()?.subtype) {
|
||||
val payload = Json.parse(answerResp.body!!.string())
|
||||
if (payload != null && payload.isObject) {
|
||||
val token = payload.asObject().getString("token")
|
||||
if (token != null) return token
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun clearApiToken(req: HttpServletRequest) {
|
||||
val deleteTokenReq = Request.Builder().url("${ApiTool.apiRoot}tour/token").delete().build()
|
||||
client.newCall(deleteTokenReq).execute()
|
||||
}
|
||||
|
||||
fun getBearer(req: HttpServletRequest): String {
|
||||
val session = req.session
|
||||
return cryptograph.webEncrypt(
|
||||
"${
|
||||
session.id
|
||||
}:${
|
||||
session.getAttribute(SESSION_KEY_API_TOKEN) ?: ""
|
||||
}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -20,7 +20,7 @@ class LoginServlet: HttpServlet() {
|
||||
"sesame" -> checkSesame(payload)
|
||||
else -> checkLoginPass(payload)
|
||||
} ?: throw Error("authentication failed")
|
||||
req.session.setAttribute("logged", user)
|
||||
AuthFilter.handleSuccessfulLogin(req, user)
|
||||
val ret = Json.Object("status" to "ok")
|
||||
resp.contentType = "application/json"
|
||||
resp.writer.println(ret.toString())
|
||||
@@ -34,7 +34,7 @@ class LoginServlet: HttpServlet() {
|
||||
|
||||
fun checkSesame(payload: Json.Object): Json.Object? {
|
||||
val expected = WebappManager.properties.getProperty("auth.sesame") ?: throw Error("sesame wrongly configured")
|
||||
return if (payload.getString("sesame")?.equals(expected) == true) Json.Object("logged" to true) else null
|
||||
return if (payload.getString("sesame")?.equals(expected) == true) Json.Object(AuthFilter.SESSION_KEY_USER to true) else null
|
||||
}
|
||||
|
||||
fun checkLoginPass(payload: Json.Object): Json.Object? {
|
||||
|
@@ -1,6 +1,8 @@
|
||||
package org.jeudego.pairgoth.web
|
||||
|
||||
import com.republicate.kson.Json
|
||||
import okhttp3.Request
|
||||
import org.jeudego.pairgoth.view.ApiTool
|
||||
import org.slf4j.LoggerFactory
|
||||
import javax.servlet.http.HttpServlet
|
||||
import javax.servlet.http.HttpServletRequest
|
||||
@@ -9,7 +11,9 @@ import javax.servlet.http.HttpServletResponse
|
||||
class LogoutServlet: HttpServlet() {
|
||||
|
||||
override fun doPost(req: HttpServletRequest, resp: HttpServletResponse) {
|
||||
req.session.removeAttribute("logged")
|
||||
AuthFilter.clearApiToken(req)
|
||||
req.session.removeAttribute(AuthFilter.SESSION_KEY_USER)
|
||||
req.session.removeAttribute(AuthFilter.SESSION_KEY_API_TOKEN)
|
||||
val ret = Json.Object("status" to "ok")
|
||||
resp.contentType = "application/json"
|
||||
resp.writer.println(ret.toString())
|
||||
|
@@ -19,9 +19,8 @@ let headers = function(withJson) {
|
||||
if (withJson) {
|
||||
ret['Content-Type'] = 'application/json';
|
||||
}
|
||||
let accessToken = store('accessToken');
|
||||
if (accessToken) {
|
||||
ret['Authorization'] = `Bearer ${accessToken}`;
|
||||
if (apiToken) {
|
||||
ret['Authorization'] = `Bearer ${apiToken}`;
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
|
@@ -45,6 +45,7 @@
|
||||
<!-- error messages included as html elements so that they are translated -->
|
||||
<div id="required_field" class="hidden">Required field</div>
|
||||
<script type="text/javascript">
|
||||
const apiToken = '$!api.bearer';
|
||||
#if($tour)
|
||||
const tour_id = ${tour.id};
|
||||
const tour_rounds = ${tour.rounds};
|
||||
|
Reference in New Issue
Block a user