implement 'sesame' auth

This commit is contained in:
Claude Brisson
2023-12-26 07:52:16 +01:00
parent 65053e47fc
commit 0b0e2539be
6 changed files with 77 additions and 14 deletions

View File

@@ -92,6 +92,7 @@
<pairgoth.logger.format>${pairgoth.logger.format}</pairgoth.logger.format> <pairgoth.logger.format>${pairgoth.logger.format}</pairgoth.logger.format>
<pairgoth.logger.level.org.jeudego.pairgoth.web.ApiServlet.api>debug</pairgoth.logger.level.org.jeudego.pairgoth.web.ApiServlet.api> <pairgoth.logger.level.org.jeudego.pairgoth.web.ApiServlet.api>debug</pairgoth.logger.level.org.jeudego.pairgoth.web.ApiServlet.api>
<pairgoth.auth>${pairgoth.auth}</pairgoth.auth> <pairgoth.auth>${pairgoth.auth}</pairgoth.auth>
<pairgoth.auth.sesame>${pairgoth.auth.sesame}</pairgoth.auth.sesame>
<org.slf4j.simpleLogger.defaultLogLevel>debug</org.slf4j.simpleLogger.defaultLogLevel> <org.slf4j.simpleLogger.defaultLogLevel>debug</org.slf4j.simpleLogger.defaultLogLevel>
</systemProperties> </systemProperties>
<webApp> <webApp>

View File

@@ -29,22 +29,31 @@ class AuthFilter: Filter {
val session: HttpSession? = request.getSession(false) val session: HttpSession? = request.getSession(false)
val auth = WebappManager.getProperty("auth") ?: throw Error("authentication not configured") val auth = WebappManager.getProperty("auth") ?: throw Error("authentication not configured")
if (auth == "none" || whitelist.contains(uri) || uri.contains(Regex("\\.(?!html)")) || session?.getAttribute("logged") != null) { if (auth == "none" || whitelisted(uri) || session?.getAttribute("logged") != null) {
chain.doFilter(req, resp) chain.doFilter(req, resp)
} else { } else {
// TODO - configure if unauth requests are redirected and/or forwarded // TODO - configure if unauth requests are redirected and/or forwarded
// TODO - protection against brute force attacks // TODO - protection against brute force attacks
if (uri == "/index") { if (uri.endsWith("/index")) {
request.getRequestDispatcher("/index-ffg").forward(req, resp) request.getRequestDispatcher("/index-ffg").forward(req, resp)
} else { } else {
response.sendRedirect("/login") response.sendRedirect("/login")
} }
} }
} }
companion object { companion object {
private val whitelist = setOf( private val whitelist = setOf(
"/index-ffg", "/index-ffg",
"/login" "/login",
"/api/login"
) )
fun whitelisted(uri: String): Boolean {
if (uri.contains(Regex("\\.(?!html)"))) return true
val nolangUri = uri.replace(Regex("^/../"), "/")
return whitelist.contains(nolangUri)
}
} }
} }

View File

@@ -0,0 +1,41 @@
package org.jeudego.pairgoth.web
import com.republicate.kson.Json
import org.slf4j.LoggerFactory
import javax.servlet.http.HttpServlet
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse
class LoginServlet: HttpServlet() {
override fun doPost(req: HttpServletRequest, resp: HttpServletResponse) {
try {
val contentType = req.contentType
val sep = contentType.indexOf(';')
val mimeType = if (sep == -1) contentType else contentType.substring(0, sep).trim { it <= ' ' }
if (!isJson(mimeType)) throw Error("expecting json")
val payload = Json.Companion.parse(req.reader.readText())?.asObject() ?: throw Error("null json")
val user = when (WebappManager.getProperty("auth")) {
"sesame" -> checkSesame(payload)
else -> null
} ?: throw Error("authentication failed")
req.session.setAttribute("logged", user)
val ret = Json.Object("status" to "ok")
resp.contentType = "application/json"
resp.writer.println(ret.toString())
} catch (t: Throwable) {
logger.error("exception while logging in", t)
resp.sendError(HttpServletResponse.SC_BAD_REQUEST)
}
}
fun checkSesame(payload: Json.Object): Boolean? {
val expected = WebappManager.getProperty("auth.sesame") ?: throw Error("sesame wrongly configured")
return if (payload.getString("sesame")?.equals(expected) == true) true else null
}
companion object {
fun isJson(mimeType: String) = "text/json" == mimeType || "application/json" == mimeType || mimeType.endsWith("+json")
val logger = LoggerFactory.getLogger("login")
}
}

View File

@@ -88,6 +88,12 @@
<load-on-startup>1</load-on-startup> <load-on-startup>1</load-on-startup>
<async-supported>true</async-supported> <async-supported>true</async-supported>
</servlet> </servlet>
<servlet>
<servlet-name>login</servlet-name>
<servlet-class>org.jeudego.pairgoth.web.LoginServlet</servlet-class>
<load-on-startup>1</load-on-startup>
<async-supported>true</async-supported>
</servlet>
<!-- servlet mappings --> <!-- servlet mappings -->
<servlet-mapping> <servlet-mapping>
@@ -110,6 +116,10 @@
<servlet-name>import</servlet-name> <servlet-name>import</servlet-name>
<url-pattern>/api/import/*</url-pattern> <url-pattern>/api/import/*</url-pattern>
</servlet-mapping> </servlet-mapping>
<servlet-mapping>
<servlet-name>login</servlet-name>
<url-pattern>/api/login</url-pattern>
</servlet-mapping>
<!-- context params --> <!-- context params -->
<context-param> <context-param>

View File

@@ -219,7 +219,7 @@ onLoad(() => {
break; break;
} }
case 'ArrowDown': { case 'ArrowDown': {
if (searchResultShown()) { if (typeof(searchResultShown) === 'function' && searchResultShown()) {
let lines = $('.result-line'); let lines = $('.result-line');
if (typeof (searchHighlight) === 'undefined') searchHighlight = 0; if (typeof (searchHighlight) === 'undefined') searchHighlight = 0;
else ++searchHighlight; else ++searchHighlight;
@@ -230,7 +230,7 @@ onLoad(() => {
break; break;
} }
case 'ArrowUp': { case 'ArrowUp': {
if (searchResultShown()) { if (typeof(searchResultShown) === 'function' && searchResultShown()) {
let lines = $('.result-line'); let lines = $('.result-line');
if (typeof (searchHighlight) === 'undefined') searchHighlight = 0; if (typeof (searchHighlight) === 'undefined') searchHighlight = 0;
else --searchHighlight; else --searchHighlight;
@@ -241,10 +241,12 @@ onLoad(() => {
break; break;
} }
case 'Enter': { case 'Enter': {
if (searchResultShown()) { if (typeof(searchResultShown) === 'function') {
fillPlayer(searchResult[searchHighlight]); if (searchResultShown()) {
} else { fillPlayer(searchResult[searchHighlight]);
$('#register')[0].click(); } else {
$('#register')[0].click();
}
} }
break; break;
} }

View File

@@ -1,8 +1,8 @@
<div id="login" class="section"> <div id="login" class="section">
<form id="login-form" class="ui form"> <form id="login-form" class="ui form" autocomplete="off">
<div class="field"> <div class="field">
<label>Enter the magic word</label> <label>Enter the magic word</label>
<input type="text" name="sesame"/> <input type="text" name="sesame" autocomplete="false"/>
<button type="submit" class="ui green floating button">Log in</button> <button type="submit" class="ui green floating button">Log in</button>
</div> </div>
</form> </form>
@@ -10,9 +10,9 @@
<script type="text/javascript"> <script type="text/javascript">
onLoad(()=>{ onLoad(()=>{
$('#login-form').on('submit', e => { $('#login-form').on('submit', e => {
api.postJson('login', { sesame: $('input[name="sesame"]').val() }) api.postJson('login', { sesame: $('input[name="sesame"]')[0].value })
.then(resp => { .then(resp => {
if (resp !== 'error') { if (resp !== 'error' && resp.status === 'ok') {
document.location.href = '/index' document.location.href = '/index'
} }
}); });