diff --git a/web/src/main/kotlin/ru/ysu/lms/web/DateUtils.kt b/web/src/main/kotlin/ru/ysu/lms/web/DateUtils.kt new file mode 100644 index 0000000..b3a2bb1 --- /dev/null +++ b/web/src/main/kotlin/ru/ysu/lms/web/DateUtils.kt @@ -0,0 +1,21 @@ +package ru.ysu.lms.web + +import java.time.Instant +import java.time.ZoneId +import java.time.ZonedDateTime +import java.time.format.DateTimeFormatter + +object DateUtils { + private val MSK = ZoneId.of("Europe/Moscow") + private val FORMAT = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm") + + fun formatMsk(value: String?): String? = + value?.let { + try { + val zdt = ZonedDateTime.ofInstant(Instant.parse(it), MSK) + zdt.format(FORMAT) + } catch (_: Exception) { + it + } + } +} \ No newline at end of file diff --git a/web/src/main/kotlin/ru/ysu/lms/web/admin/AdminController.kt b/web/src/main/kotlin/ru/ysu/lms/web/admin/AdminController.kt index ffaf518..f72ffd0 100644 --- a/web/src/main/kotlin/ru/ysu/lms/web/admin/AdminController.kt +++ b/web/src/main/kotlin/ru/ysu/lms/web/admin/AdminController.kt @@ -283,6 +283,8 @@ class AdminController( model.addAttribute("lab", lab) model.addAttribute("groups", groupSections) model.addAttribute("descriptionHtml", markdown.render(lab.descriptionMd)) + model.addAttribute("deadlineMsk", ru.ysu.lms.web.DateUtils.formatMsk(lab.deadlineAt)) + model.addAttribute("defenseMsk", ru.ysu.lms.web.DateUtils.formatMsk(lab.defenseAt)) model.addAttribute("deadlineLocal", lab.deadlineAt?.take(16) ?: "") model.addAttribute("defenseLocal", lab.defenseAt?.take(16) ?: "") return "admin/lab" diff --git a/web/src/main/kotlin/ru/ysu/lms/web/api/ApiDtos.kt b/web/src/main/kotlin/ru/ysu/lms/web/api/ApiDtos.kt index bb6bc39..11698ee 100644 --- a/web/src/main/kotlin/ru/ysu/lms/web/api/ApiDtos.kt +++ b/web/src/main/kotlin/ru/ysu/lms/web/api/ApiDtos.kt @@ -153,6 +153,8 @@ data class MeAssignmentDto( val pointsMax: Any? = null, ) { var statementHtml: String = "" + var deadlineMsk: String? = null + var defenseMsk: String? = null } @JsonIgnoreProperties(ignoreUnknown = true) diff --git a/web/src/main/kotlin/ru/ysu/lms/web/auth/AuthPageController.kt b/web/src/main/kotlin/ru/ysu/lms/web/auth/AuthPageController.kt index 460ff1f..e9294eb 100644 --- a/web/src/main/kotlin/ru/ysu/lms/web/auth/AuthPageController.kt +++ b/web/src/main/kotlin/ru/ysu/lms/web/auth/AuthPageController.kt @@ -27,6 +27,10 @@ class AuthPageController(private val api: ApiClient) { if (currentUser(session) != null) { return "redirect:/" } + (session.getAttribute("csrfError") as? String)?.let { + session.removeAttribute("csrfError") + model.addAttribute("error", it) + } model.addAttribute("adminExists", api.adminExists()) return "login" } diff --git a/web/src/main/kotlin/ru/ysu/lms/web/config/CsrfInterceptor.kt b/web/src/main/kotlin/ru/ysu/lms/web/config/CsrfInterceptor.kt index 8d8e504..323855a 100644 --- a/web/src/main/kotlin/ru/ysu/lms/web/config/CsrfInterceptor.kt +++ b/web/src/main/kotlin/ru/ysu/lms/web/config/CsrfInterceptor.kt @@ -29,13 +29,26 @@ class CsrfInterceptor : HandlerInterceptor { if (request.method.equals("POST", ignoreCase = true)) { val sent = request.getParameter(FIELD) if (sent != token) { - response.sendError(HttpServletResponse.SC_FORBIDDEN, "CSRF token mismatch") + request.getSession().setAttribute("csrfError", "CSRF-токен устарел. Попробуйте ещё раз.") + response.sendRedirect("/login") return false } } return true } + override fun afterCompletion( + request: HttpServletRequest, + response: HttpServletResponse, + handler: Any, + ex: Exception?, + ) { + // после успешного POST-запроса чистим флаг ошибки (если был) + if (request.method.equals("POST", ignoreCase = true)) { + request.getSession().removeAttribute("csrfError") + } + } + companion object { const val ATTR = "csrfToken" const val FIELD = "_csrf" diff --git a/web/src/main/kotlin/ru/ysu/lms/web/student/StudentController.kt b/web/src/main/kotlin/ru/ysu/lms/web/student/StudentController.kt index 862eacf..4cc6cb1 100644 --- a/web/src/main/kotlin/ru/ysu/lms/web/student/StudentController.kt +++ b/web/src/main/kotlin/ru/ysu/lms/web/student/StudentController.kt @@ -21,7 +21,11 @@ class StudentController( fun dashboard(session: HttpSession, model: Model): String { val token = session.getAttribute(SessionKeys.TOKEN) as? String ?: return "redirect:/login" val assignments = api.meAssignments(token).map { - it.apply { statementHtml = markdown.render(statementMd) } + it.apply { + statementHtml = markdown.render(statementMd) + deadlineMsk = ru.ysu.lms.web.DateUtils.formatMsk(deadlineAt) + defenseMsk = ru.ysu.lms.web.DateUtils.formatMsk(defenseAt) + } } model.addAttribute("assignments", assignments) return "student/dashboard" diff --git a/web/src/main/resources/application.yml b/web/src/main/resources/application.yml index 3343281..df7fd0c 100644 --- a/web/src/main/resources/application.yml +++ b/web/src/main/resources/application.yml @@ -5,6 +5,7 @@ server: cookie: http-only: true same-site: lax + tracking-modes: cookie spring: application: diff --git a/web/src/main/resources/templates/admin/lab.html b/web/src/main/resources/templates/admin/lab.html index bcf4293..8f27e34 100644 --- a/web/src/main/resources/templates/admin/lab.html +++ b/web/src/main/resources/templates/admin/lab.html @@ -13,8 +13,8 @@ - · Срок: - · Защита: + · Срок: + · Защита: · Максимум: б.
diff --git a/web/src/main/resources/templates/student/dashboard.html b/web/src/main/resources/templates/student/dashboard.html index a3a5382..af1c7f4 100644 --- a/web/src/main/resources/templates/student/dashboard.html +++ b/web/src/main/resources/templates/student/dashboard.html @@ -20,8 +20,8 @@—
-—
-—
+—
+—