Víc džavaskriptu
Tento commit je obsažen v:
rodič
dc07e1f650
revize
3ed82b79a9
@ -1,11 +1,9 @@
|
|||||||
use captcha::{gen, Difficulty};
|
|
||||||
use redis::{cmd, AsyncCommands, JsonAsyncCommands};
|
use redis::{cmd, AsyncCommands, JsonAsyncCommands};
|
||||||
use sha256::digest;
|
|
||||||
use sqlx::{query, query_as, types::Json};
|
use sqlx::{query, query_as, types::Json};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use super::models::{Board, File};
|
use super::models::{Board, File};
|
||||||
use crate::{cfg::BoardCfg, ctx::Ctx, error::NekrochanError, CAPTCHA};
|
use crate::{cfg::BoardCfg, ctx::Ctx, error::NekrochanError};
|
||||||
|
|
||||||
impl Board {
|
impl Board {
|
||||||
pub async fn create(
|
pub async fn create(
|
||||||
@ -216,52 +214,6 @@ impl Board {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Board {
|
|
||||||
pub fn thread_captcha(&self) -> Option<(String, String)> {
|
|
||||||
let captcha = match self.config.thread_captcha.as_str() {
|
|
||||||
"easy" => gen(Difficulty::Easy),
|
|
||||||
"medium" => gen(Difficulty::Medium),
|
|
||||||
"hard" => gen(Difficulty::Hard),
|
|
||||||
_ => return None,
|
|
||||||
};
|
|
||||||
|
|
||||||
let base64 = captcha.as_base64()?;
|
|
||||||
|
|
||||||
let board = self.id.clone();
|
|
||||||
let difficulty = self.config.thread_captcha.clone();
|
|
||||||
let id = digest(base64.as_bytes());
|
|
||||||
|
|
||||||
let key = (board, difficulty, id.clone());
|
|
||||||
let solution = captcha.chars_as_string();
|
|
||||||
|
|
||||||
CAPTCHA.write().ok()?.insert(key, solution);
|
|
||||||
|
|
||||||
Some((id, base64))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn reply_captcha(&self) -> Option<(String, String)> {
|
|
||||||
let captcha = match self.config.reply_captcha.as_str() {
|
|
||||||
"easy" => gen(Difficulty::Easy),
|
|
||||||
"medium" => gen(Difficulty::Medium),
|
|
||||||
"hard" => gen(Difficulty::Hard),
|
|
||||||
_ => return None,
|
|
||||||
};
|
|
||||||
|
|
||||||
let base64 = captcha.as_base64()?;
|
|
||||||
|
|
||||||
let board = self.id.clone();
|
|
||||||
let difficulty = self.config.thread_captcha.clone();
|
|
||||||
let id = digest(base64.as_bytes());
|
|
||||||
|
|
||||||
let key = (board, difficulty, id.clone());
|
|
||||||
let solution = captcha.chars_as_string();
|
|
||||||
|
|
||||||
CAPTCHA.write().ok()?.insert(key, solution);
|
|
||||||
|
|
||||||
Some((id, base64))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn update_overboard(ctx: &Ctx, boards: Vec<Board>) -> Result<(), NekrochanError> {
|
async fn update_overboard(ctx: &Ctx, boards: Vec<Board>) -> Result<(), NekrochanError> {
|
||||||
query("DROP VIEW IF EXISTS overboard")
|
query("DROP VIEW IF EXISTS overboard")
|
||||||
.execute(ctx.db())
|
.execute(ctx.db())
|
||||||
|
@ -54,12 +54,16 @@ pub enum NekrochanError {
|
|||||||
InternalError,
|
InternalError,
|
||||||
#[error("Neplatný autentizační token. Vymaž soubory cookie.")]
|
#[error("Neplatný autentizační token. Vymaž soubory cookie.")]
|
||||||
InvalidAuthError,
|
InvalidAuthError,
|
||||||
|
#[error("Tato CAPTCHA vypršela nebo neexistuje.")]
|
||||||
|
InvalidCaptchaError,
|
||||||
#[error("Neplatná strana.")]
|
#[error("Neplatná strana.")]
|
||||||
InvalidPageError,
|
InvalidPageError,
|
||||||
#[error("Obsah musí mít 1-20000 znaků.")]
|
#[error("Obsah musí mít 1-20000 znaků.")]
|
||||||
NewsContentFormatError,
|
NewsContentFormatError,
|
||||||
#[error("Titulek musí mít 1-100 znaků.")]
|
#[error("Titulek musí mít 1-100 znaků.")]
|
||||||
NewsTitleFormatError,
|
NewsTitleFormatError,
|
||||||
|
#[error("Tato nástěnka nevyžaduje CAPTCHA.")]
|
||||||
|
NoCaptchaError,
|
||||||
#[error("Příspěvek musí mít obsah.")]
|
#[error("Příspěvek musí mít obsah.")]
|
||||||
NoContentError,
|
NoContentError,
|
||||||
#[error("Příspěvek musí mít soubor.")]
|
#[error("Příspěvek musí mít soubor.")]
|
||||||
@ -86,8 +90,6 @@ pub enum NekrochanError {
|
|||||||
ReplyReplyError,
|
ReplyReplyError,
|
||||||
#[error("Na této nástěnce se musí vyplnit CAPTCHA.")]
|
#[error("Na této nástěnce se musí vyplnit CAPTCHA.")]
|
||||||
RequiredCaptchaError,
|
RequiredCaptchaError,
|
||||||
#[error("Tato CAPTCHA neexistuje nebo už byla vyřešena.")]
|
|
||||||
SolvedCaptchaError,
|
|
||||||
#[error("Toto vlákno je uzamčené.")]
|
#[error("Toto vlákno je uzamčené.")]
|
||||||
ThreadLockError,
|
ThreadLockError,
|
||||||
#[error("Tento ban nelze odvolat.")]
|
#[error("Tento ban nelze odvolat.")]
|
||||||
@ -237,9 +239,11 @@ impl ResponseError for NekrochanError {
|
|||||||
NekrochanError::InsufficientPermissionError => StatusCode::FORBIDDEN,
|
NekrochanError::InsufficientPermissionError => StatusCode::FORBIDDEN,
|
||||||
NekrochanError::InternalError => StatusCode::INTERNAL_SERVER_ERROR,
|
NekrochanError::InternalError => StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
NekrochanError::InvalidAuthError => StatusCode::UNAUTHORIZED,
|
NekrochanError::InvalidAuthError => StatusCode::UNAUTHORIZED,
|
||||||
|
NekrochanError::InvalidCaptchaError => StatusCode::BAD_REQUEST,
|
||||||
NekrochanError::InvalidPageError => StatusCode::BAD_REQUEST,
|
NekrochanError::InvalidPageError => StatusCode::BAD_REQUEST,
|
||||||
NekrochanError::NewsContentFormatError => StatusCode::BAD_REQUEST,
|
NekrochanError::NewsContentFormatError => StatusCode::BAD_REQUEST,
|
||||||
NekrochanError::NewsTitleFormatError => StatusCode::BAD_REQUEST,
|
NekrochanError::NewsTitleFormatError => StatusCode::BAD_REQUEST,
|
||||||
|
NekrochanError::NoCaptchaError => StatusCode::NOT_FOUND,
|
||||||
NekrochanError::NoContentError => StatusCode::BAD_REQUEST,
|
NekrochanError::NoContentError => StatusCode::BAD_REQUEST,
|
||||||
NekrochanError::NoFileError => StatusCode::BAD_REQUEST,
|
NekrochanError::NoFileError => StatusCode::BAD_REQUEST,
|
||||||
NekrochanError::NoPostsError => StatusCode::BAD_REQUEST,
|
NekrochanError::NoPostsError => StatusCode::BAD_REQUEST,
|
||||||
@ -253,7 +257,6 @@ impl ResponseError for NekrochanError {
|
|||||||
NekrochanError::ReplyReplyError => StatusCode::BAD_REQUEST,
|
NekrochanError::ReplyReplyError => StatusCode::BAD_REQUEST,
|
||||||
NekrochanError::ReportFormatError => StatusCode::BAD_REQUEST,
|
NekrochanError::ReportFormatError => StatusCode::BAD_REQUEST,
|
||||||
NekrochanError::RequiredCaptchaError => StatusCode::UNAUTHORIZED,
|
NekrochanError::RequiredCaptchaError => StatusCode::UNAUTHORIZED,
|
||||||
NekrochanError::SolvedCaptchaError => StatusCode::BAD_REQUEST,
|
|
||||||
NekrochanError::ThreadLockError => StatusCode::FORBIDDEN,
|
NekrochanError::ThreadLockError => StatusCode::FORBIDDEN,
|
||||||
NekrochanError::UnappealableError => StatusCode::BAD_REQUEST,
|
NekrochanError::UnappealableError => StatusCode::BAD_REQUEST,
|
||||||
NekrochanError::UsernameFormatError => StatusCode::BAD_REQUEST,
|
NekrochanError::UsernameFormatError => StatusCode::BAD_REQUEST,
|
||||||
|
@ -1,11 +1,4 @@
|
|||||||
use error::NekrochanError;
|
use error::NekrochanError;
|
||||||
use lazy_static::lazy_static;
|
|
||||||
use std::{collections::HashMap, sync::RwLock};
|
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
pub static ref CAPTCHA: RwLock<HashMap<(String, String, String), String>> =
|
|
||||||
RwLock::new(HashMap::new());
|
|
||||||
}
|
|
||||||
|
|
||||||
const GENERIC_PAGE_SIZE: i64 = 15;
|
const GENERIC_PAGE_SIZE: i64 = 15;
|
||||||
|
|
||||||
|
@ -63,8 +63,9 @@ async fn run() -> Result<(), Error> {
|
|||||||
.service(web::board::board)
|
.service(web::board::board)
|
||||||
.service(web::board_catalog::board_catalog)
|
.service(web::board_catalog::board_catalog)
|
||||||
.service(web::index::index)
|
.service(web::index::index)
|
||||||
.service(web::ip_posts::ip_posts)
|
.service(web::captcha::captcha)
|
||||||
.service(web::edit_posts::edit_posts)
|
.service(web::edit_posts::edit_posts)
|
||||||
|
.service(web::ip_posts::ip_posts)
|
||||||
.service(web::login::login_get)
|
.service(web::login::login_get)
|
||||||
.service(web::login::login_post)
|
.service(web::login::login_post)
|
||||||
.service(web::logout::logout)
|
.service(web::logout::logout)
|
||||||
|
@ -19,7 +19,6 @@ use crate::{
|
|||||||
ban_response,
|
ban_response,
|
||||||
tcx::{account_from_auth_opt, ip_from_req},
|
tcx::{account_from_auth_opt, ip_from_req},
|
||||||
},
|
},
|
||||||
CAPTCHA,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(MultipartForm)]
|
#[derive(MultipartForm)]
|
||||||
@ -95,36 +94,23 @@ pub async fn create_post(
|
|||||||
None => None,
|
None => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let difficulties = ["easy", "medium", "hard"];
|
if (thread.is_none() && board.config.0.thread_captcha != "off")
|
||||||
|
|| (thread.is_some() && board.config.0.reply_captcha != "off")
|
||||||
let difficulty = if thread.is_none() {
|
{
|
||||||
if difficulties.contains(&board.config.0.thread_captcha.as_str()) {
|
|
||||||
Some(board.config.0.thread_captcha.clone())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
} else if difficulties.contains(&board.config.0.reply_captcha.as_str()) {
|
|
||||||
Some(board.config.0.reply_captcha.clone())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(difficulty) = difficulty {
|
|
||||||
let board = board.id.clone();
|
let board = board.id.clone();
|
||||||
|
|
||||||
let id = form
|
let id = form
|
||||||
.captcha_id
|
.captcha_id
|
||||||
.ok_or(NekrochanError::RequiredCaptchaError)?
|
.ok_or(NekrochanError::RequiredCaptchaError)?
|
||||||
.0;
|
.0;
|
||||||
|
|
||||||
let key = (board, difficulty, id);
|
let key = format!("captcha:{board}:{id}");
|
||||||
|
|
||||||
let solution = form
|
let solution = form
|
||||||
.captcha_solution
|
.captcha_solution
|
||||||
.ok_or(NekrochanError::RequiredCaptchaError)?;
|
.ok_or(NekrochanError::RequiredCaptchaError)?;
|
||||||
|
|
||||||
let actual_solution = CAPTCHA
|
let actual_solution: String = ctx.cache().get_del(key).await?;
|
||||||
.write()?
|
|
||||||
.remove(&key)
|
|
||||||
.ok_or(NekrochanError::SolvedCaptchaError)?;
|
|
||||||
|
|
||||||
if solution.trim() != actual_solution {
|
if solution.trim() != actual_solution {
|
||||||
return Err(NekrochanError::IncorrectCaptchaError);
|
return Err(NekrochanError::IncorrectCaptchaError);
|
||||||
|
55
src/web/captcha.rs
Normální soubor
55
src/web/captcha.rs
Normální soubor
@ -0,0 +1,55 @@
|
|||||||
|
use ::captcha::{gen, Difficulty};
|
||||||
|
use actix_web::{
|
||||||
|
get,
|
||||||
|
web::{Data, Json, Query},
|
||||||
|
};
|
||||||
|
use redis::AsyncCommands;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use sha256::digest;
|
||||||
|
|
||||||
|
use crate::{ctx::Ctx, db::models::Board, error::NekrochanError};
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct CaptchaQuery {
|
||||||
|
pub board: String,
|
||||||
|
pub reply: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct CaptchaResponse {
|
||||||
|
pub png: String,
|
||||||
|
pub id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/captcha")]
|
||||||
|
pub async fn captcha(
|
||||||
|
ctx: Data<Ctx>,
|
||||||
|
Query(query): Query<CaptchaQuery>,
|
||||||
|
) -> Result<Json<CaptchaResponse>, NekrochanError> {
|
||||||
|
let board = Board::read(&ctx, query.board.clone())
|
||||||
|
.await?
|
||||||
|
.ok_or(NekrochanError::BoardNotFound(query.board))?;
|
||||||
|
|
||||||
|
let captcha = match board.config.thread_captcha.as_str() {
|
||||||
|
"easy" => gen(Difficulty::Easy),
|
||||||
|
"medium" => gen(Difficulty::Medium),
|
||||||
|
"hard" => gen(Difficulty::Hard),
|
||||||
|
_ => return Err(NekrochanError::NoCaptchaError),
|
||||||
|
};
|
||||||
|
|
||||||
|
// >NOOOOOOOO YOU NEED TO MAKE A NEW ERROR TYPE FOR THIS ERROR THAT CAN ONLY HAPPEN ONCE IN THE CODE NOOOOOOOOOO
|
||||||
|
let png = captcha.as_base64().ok_or(NekrochanError::NoCaptchaError)?;
|
||||||
|
|
||||||
|
let board = board.id;
|
||||||
|
let id = digest(png.as_bytes());
|
||||||
|
|
||||||
|
let key = format!("captcha:{board}:{id}");
|
||||||
|
let solution = captcha.chars_as_string();
|
||||||
|
|
||||||
|
ctx.cache().set(&key, solution).await?;
|
||||||
|
ctx.cache().expire(&key, 600).await?;
|
||||||
|
|
||||||
|
let res = CaptchaResponse { png, id };
|
||||||
|
|
||||||
|
Ok(Json(res))
|
||||||
|
}
|
@ -32,6 +32,7 @@ pub async fn login_get(ctx: Data<Ctx>, req: HttpRequest) -> Result<HttpResponse,
|
|||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct LogInForm {
|
pub struct LogInForm {
|
||||||
username: String,
|
username: String,
|
||||||
|
#[serde(rename = "account_password")]
|
||||||
password: String,
|
password: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
use actix_web::{http::StatusCode, HttpRequest, HttpResponse, HttpResponseBuilder};
|
|
||||||
use askama::Template;
|
|
||||||
|
|
||||||
pub mod actions;
|
pub mod actions;
|
||||||
pub mod board;
|
pub mod board;
|
||||||
pub mod board_catalog;
|
pub mod board_catalog;
|
||||||
|
pub mod captcha;
|
||||||
pub mod edit_posts;
|
pub mod edit_posts;
|
||||||
pub mod index;
|
pub mod index;
|
||||||
|
pub mod ip_posts;
|
||||||
pub mod login;
|
pub mod login;
|
||||||
pub mod logout;
|
pub mod logout;
|
||||||
pub mod news;
|
pub mod news;
|
||||||
@ -14,7 +13,9 @@ pub mod overboard_catalog;
|
|||||||
pub mod staff;
|
pub mod staff;
|
||||||
pub mod tcx;
|
pub mod tcx;
|
||||||
pub mod thread;
|
pub mod thread;
|
||||||
pub mod ip_posts;
|
|
||||||
|
use actix_web::{http::StatusCode, HttpRequest, HttpResponse, HttpResponseBuilder};
|
||||||
|
use askama::Template;
|
||||||
|
|
||||||
use self::tcx::TemplateCtx;
|
use self::tcx::TemplateCtx;
|
||||||
use crate::{ctx::Ctx, db::models::Ban, error::NekrochanError, filters};
|
use crate::{ctx::Ctx, db::models::Ban, error::NekrochanError, filters};
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
use actix_web::HttpRequest;
|
use actix_web::HttpRequest;
|
||||||
use rand::{distributions::Alphanumeric, thread_rng, Rng};
|
|
||||||
use redis::AsyncCommands;
|
use redis::AsyncCommands;
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashSet,
|
collections::HashSet,
|
||||||
@ -17,7 +16,6 @@ pub struct TemplateCtx {
|
|||||||
pub account: Option<String>,
|
pub account: Option<String>,
|
||||||
pub perms: PermissionWrapper,
|
pub perms: PermissionWrapper,
|
||||||
pub name: Option<String>,
|
pub name: Option<String>,
|
||||||
pub password: String,
|
|
||||||
pub ip: IpAddr,
|
pub ip: IpAddr,
|
||||||
pub yous: HashSet<String>,
|
pub yous: HashSet<String>,
|
||||||
}
|
}
|
||||||
@ -35,16 +33,6 @@ impl TemplateCtx {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let name = req.cookie("name").map(|cookie| cookie.value().into());
|
let name = req.cookie("name").map(|cookie| cookie.value().into());
|
||||||
let password_cookie = req.cookie("password").map(|cookie| cookie.value().into());
|
|
||||||
|
|
||||||
let password: String = match password_cookie {
|
|
||||||
Some(password) => password,
|
|
||||||
None => thread_rng()
|
|
||||||
.sample_iter(&Alphanumeric)
|
|
||||||
.take(8)
|
|
||||||
.map(char::from)
|
|
||||||
.collect(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let (ip, _) = ip_from_req(req)?;
|
let (ip, _) = ip_from_req(req)?;
|
||||||
let yous = ctx.cache().zrange(format!("by_ip:{ip}"), 0, -1).await?;
|
let yous = ctx.cache().zrange(format!("by_ip:{ip}"), 0, -1).await?;
|
||||||
@ -56,7 +44,6 @@ impl TemplateCtx {
|
|||||||
boards,
|
boards,
|
||||||
perms,
|
perms,
|
||||||
name,
|
name,
|
||||||
password,
|
|
||||||
ip,
|
ip,
|
||||||
yous,
|
yous,
|
||||||
account,
|
account,
|
||||||
|
25
static/js/captcha.js
Normální soubor
25
static/js/captcha.js
Normální soubor
@ -0,0 +1,25 @@
|
|||||||
|
$(function () {
|
||||||
|
$("#get-captcha").click(function () {
|
||||||
|
let btn = $(this);
|
||||||
|
|
||||||
|
let board = btn.attr("data-board");
|
||||||
|
let reply = btn.attr("data-reply");
|
||||||
|
let req_url = `/captcha?board=${board}&reply=${reply}`;
|
||||||
|
|
||||||
|
btn.text("Získat CAPTCHA");
|
||||||
|
btn.prop("disabled", true);
|
||||||
|
btn.addClass("loading");
|
||||||
|
|
||||||
|
$.get(req_url, function (data, _) {
|
||||||
|
try {
|
||||||
|
$("#captcha").replaceWith(`<img id="captcha" src="data:image/png;base64,${data.png}">`);
|
||||||
|
$("#captcha-id").prop("value", data.id);
|
||||||
|
} catch {
|
||||||
|
btn.append(" [Chyba]");
|
||||||
|
}
|
||||||
|
|
||||||
|
btn.prop("disabled", false);
|
||||||
|
btn.removeClass("loading");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
48
static/js/password.js
Normální soubor
48
static/js/password.js
Normální soubor
@ -0,0 +1,48 @@
|
|||||||
|
$(function () {
|
||||||
|
let password = get_cookie("password");
|
||||||
|
|
||||||
|
if (password === "") {
|
||||||
|
password = generate_password();
|
||||||
|
set_cookie("password", password);
|
||||||
|
}
|
||||||
|
|
||||||
|
$('input[name="password"]').prop("value", password);
|
||||||
|
});
|
||||||
|
|
||||||
|
function generate_password() {
|
||||||
|
let chars =
|
||||||
|
"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||||
|
let password_length = 8;
|
||||||
|
let password = "";
|
||||||
|
|
||||||
|
for (let i = 0; i <= password_length; i++) {
|
||||||
|
let random_number = Math.floor(Math.random() * chars.length);
|
||||||
|
password += chars.substring(random_number, random_number + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return password;
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_cookie(cname) {
|
||||||
|
let name = cname + "=";
|
||||||
|
let decodedCookie = decodeURIComponent(document.cookie);
|
||||||
|
let ca = decodedCookie.split(";");
|
||||||
|
|
||||||
|
for (let i = 0; i < ca.length; i++) {
|
||||||
|
let c = ca[i];
|
||||||
|
|
||||||
|
while (c.charAt(0) == " ") {
|
||||||
|
c = c.substring(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c.indexOf(name) == 0) {
|
||||||
|
return c.substring(name.length, c.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_cookie(cname, cvalue) {
|
||||||
|
document.cookie = `${cname}=${cvalue};path=/`;
|
||||||
|
}
|
@ -160,12 +160,6 @@ summary {
|
|||||||
padding: 8px;
|
padding: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.captcha {
|
|
||||||
width: 100%;
|
|
||||||
image-rendering: pixelated;
|
|
||||||
border: 1px solid var(--input-border);
|
|
||||||
}
|
|
||||||
|
|
||||||
.main {
|
.main {
|
||||||
margin: 8px;
|
margin: 8px;
|
||||||
}
|
}
|
||||||
@ -223,7 +217,6 @@ summary {
|
|||||||
|
|
||||||
.form-table .button,
|
.form-table .button,
|
||||||
.full-width {
|
.full-width {
|
||||||
display: block;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -479,4 +472,17 @@ summary {
|
|||||||
|
|
||||||
.loading {
|
.loading {
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
|
cursor: wait;
|
||||||
|
}
|
||||||
|
|
||||||
|
img#captcha {
|
||||||
|
-webkit-box-sizing: border-box;
|
||||||
|
-moz-box-sizing: border-box;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
image-rendering: pixelated;
|
||||||
|
border: 1px solid var(--input-border);
|
||||||
|
margin: 0px auto;
|
||||||
}
|
}
|
||||||
|
@ -11,9 +11,11 @@
|
|||||||
<link rel="stylesheet" href="/static/style.css">
|
<link rel="stylesheet" href="/static/style.css">
|
||||||
<script src="/static/js/jquery.min.js"></script>
|
<script src="/static/js/jquery.min.js"></script>
|
||||||
<script src="/static/js/expand-image.js"></script>
|
<script src="/static/js/expand-image.js"></script>
|
||||||
|
<script src="/static/js/password.js"></script>
|
||||||
{% block scripts %}{% endblock %}
|
{% block scripts %}{% endblock %}
|
||||||
</head>
|
</head>
|
||||||
<body id="top">
|
<body>
|
||||||
|
<div id="top"></div>
|
||||||
<div class="board-links header">
|
<div class="board-links header">
|
||||||
<span class="link-group"><a href="/">domov</a></span>
|
<span class="link-group"><a href="/">domov</a></span>
|
||||||
{% call board_links::board_links() %}
|
{% call board_links::board_links() %}
|
||||||
@ -36,7 +38,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="main">
|
<div class="main">
|
||||||
{% block content %}{% endblock %}
|
{% block content %}{% endblock %}
|
||||||
<div id="bottom" class="footer">
|
<div class="footer">
|
||||||
<div class="box inline-block">
|
<div class="box inline-block">
|
||||||
<a href="https://git.nekrofilie.com/sneedmaster/nekrochan">nekrochan</a> - Projekt <a href="https://nekrofilie.com/">Nekrofilie</a>
|
<a href="https://git.nekrofilie.com/sneedmaster/nekrochan">nekrochan</a> - Projekt <a href="https://nekrofilie.com/">Nekrofilie</a>
|
||||||
<br>
|
<br>
|
||||||
@ -44,5 +46,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="bottom"></div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
{% block theme %}{{ board.config.0.board_theme }}{% endblock %}
|
{% block theme %}{{ board.config.0.board_theme }}{% endblock %}
|
||||||
{% block title %}/{{ board.id }}/ - {{ board.name }}{% endblock %}
|
{% block title %}/{{ board.id }}/ - {{ board.name }}{% endblock %}
|
||||||
|
{% block scripts %}<script src="/static/js/captcha.js"></script>{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="label">Heslo</td>
|
<td class="label">Heslo</td>
|
||||||
<td><input name="password" type="password" required=""></td>
|
<td><input name="account_password" type="password" required=""></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="2"><input class="button" type="submit" value="Přihlásit se"></td>
|
<td colspan="2"><input class="button" type="submit" value="Přihlásit se"></td>
|
||||||
|
@ -29,14 +29,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="label">Heslo</td>
|
<td class="label">Heslo</td>
|
||||||
<td>
|
<td><input name="password" type="password"></td>
|
||||||
<input
|
|
||||||
name="password"
|
|
||||||
type="text"
|
|
||||||
autocomplete="new-password"
|
|
||||||
value="{{ tcx.password }}"
|
|
||||||
>
|
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="2">
|
<td colspan="2">
|
||||||
|
@ -52,33 +52,40 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="label">Heslo <span class="small">(na odstranění)</span></td>
|
<td class="label">Heslo <span class="small">(na odstranění)</span></td>
|
||||||
<td>
|
<td><input name="password" type="password" required=""></td>
|
||||||
<input name="password" type="text" autocomplete="new-password" value="{{ tcx.password }}" required="">
|
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
{% if !(tcx.perms.bypass_captcha() || tcx.perms.owner()) %}
|
{% if !(tcx.perms.bypass_captcha() || tcx.perms.owner()) %}
|
||||||
|
{% let difficulty %}
|
||||||
{% if reply %}
|
{% if reply %}
|
||||||
{% if let Some((id, base64)) = board.reply_captcha() %}
|
{% let difficulty = board.config.0.reply_captcha.as_str() %}
|
||||||
<tr>
|
|
||||||
<td class="label">CAPTCHA</td>
|
|
||||||
<td>
|
|
||||||
<img class="captcha" src="data:image/png;base64,{{ base64 }}">
|
|
||||||
<input name="captcha_id" type="hidden" value="{{ id }}">
|
|
||||||
<input name="captcha_solution" type="text" placeholder="Řešení" required="">
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endif %}
|
|
||||||
{% else %}
|
{% else %}
|
||||||
{% if let Some((id, base64)) = board.thread_captcha() %}
|
{% let difficulty = board.config.0.thread_captcha.as_str() %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if (!reply && board.config.0.thread_captcha != "off") || (reply && board.config.0.reply_captcha != "off") %}
|
||||||
|
<noscript>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="label">CAPTCHA</td>
|
<td colspan="2">
|
||||||
<td>
|
<div class="reply-mode">CAPTCHA vyžaduje JavaScript</div>
|
||||||
<img class="captcha" src="data:image/png;base64,{{ base64 }}">
|
|
||||||
<input name="captcha_id" type="hidden" value="{{ id }}">
|
|
||||||
<input name="captcha_solution" type="text" placeholder="Řešení" required="">
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
</noscript>
|
||||||
|
<tr>
|
||||||
|
<td class="label">
|
||||||
|
CAPTCHA<br>
|
||||||
|
<span class="small">(vyprší za 10 min.)</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="input-wrapper">
|
||||||
|
<table class="full-width">
|
||||||
|
<tr><td><button id="get-captcha" type="button" class="button" data-board="{{ board.id }}" data-reply="{{ reply }}">Získat CAPTCHA</button></td></tr>
|
||||||
|
<tr><td><div id="captcha"></div></tr>
|
||||||
|
<input id="captcha-id" name="captcha_id" type="hidden" required="">
|
||||||
|
<tr><td><input name="captcha_solution" type="text" placeholder="Řešení" required=""></tr></td>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -45,7 +45,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="label">Heslo</td>
|
<td class="label">Heslo</td>
|
||||||
<td><input name="password" type="password" required=""></td>
|
<td><input name="account_password" type="password" required=""></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="2"><input class="button" type="submit" value="Vytvořit účet"></td>
|
<td colspan="2"><input class="button" type="submit" value="Vytvořit účet"></td>
|
||||||
|
@ -76,10 +76,10 @@
|
|||||||
<option value="off" {% if board.config.0.thread_captcha == "off" %}selected="selected"{% endif %}>
|
<option value="off" {% if board.config.0.thread_captcha == "off" %}selected="selected"{% endif %}>
|
||||||
Žádná
|
Žádná
|
||||||
</option>
|
</option>
|
||||||
<option value="medium" {% if board.config.0.thread_captcha == "medium" %}selected="selected"{% endif %}>
|
<option value="easy" {% if board.config.0.thread_captcha == "easy" %}selected="selected"{% endif %}>
|
||||||
Lehká
|
Lehká
|
||||||
</option>
|
</option>
|
||||||
<option value="easy" {% if board.config.0.thread_captcha == "easy" %}selected="selected"{% endif %}>
|
<option value="medium" {% if board.config.0.thread_captcha == "medium" %}selected="selected"{% endif %}>
|
||||||
Střední
|
Střední
|
||||||
</option>
|
</option>
|
||||||
<option value="hard" {% if board.config.0.thread_captcha == "hard" %}selected="selected"{% endif %}>
|
<option value="hard" {% if board.config.0.thread_captcha == "hard" %}selected="selected"{% endif %}>
|
||||||
@ -96,10 +96,10 @@
|
|||||||
<option value="off" {% if board.config.0.reply_captcha == "off" %}selected="selected"{% endif %}>
|
<option value="off" {% if board.config.0.reply_captcha == "off" %}selected="selected"{% endif %}>
|
||||||
Žádná
|
Žádná
|
||||||
</option>
|
</option>
|
||||||
<option value="medium" {% if board.config.0.reply_captcha == "medium" %}selected="selected"{% endif %}>
|
<option value="easy" {% if board.config.0.reply_captcha == "easy" %}selected="selected"{% endif %}>
|
||||||
Lehká
|
Lehká
|
||||||
</option>
|
</option>
|
||||||
<option value="easy" {% if board.config.0.reply_captcha == "easy" %}selected="selected"{% endif %}>
|
<option value="medium" {% if board.config.0.reply_captcha == "medium" %}selected="selected"{% endif %}>
|
||||||
Střední
|
Střední
|
||||||
</option>
|
</option>
|
||||||
<option value="hard" {% if board.config.0.reply_captcha == "hard" %}selected="selected"{% endif %}>
|
<option value="hard" {% if board.config.0.reply_captcha == "hard" %}selected="selected"{% endif %}>
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
{% block theme %}{{ board.config.0.board_theme }}{% endblock %}
|
{% block theme %}{{ board.config.0.board_theme }}{% endblock %}
|
||||||
{% block title %}/{{ board.id }}/ - {{ thread.content_nomarkup|inline_post }}{% endblock %}
|
{% block title %}/{{ board.id }}/ - {{ thread.content_nomarkup|inline_post }}{% endblock %}
|
||||||
{% block scripts %}<script src="/static/js/update-thread.js"></script>{% endblock %}
|
{% block scripts %}<script src="/static/js/captcha.js"></script>{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
Načítá se…
Odkázat v novém úkolu
Zablokovat Uživatele