protect blog post creation with delay and captcha
to reduce the abuse a littleublog-markdown-preview
parent
eeada8aeb0
commit
1aa8579025
|
@ -54,8 +54,18 @@ final class Ublog(env: Env) extends LilaController(env) {
|
|||
|
||||
def form(username: String) = Auth { implicit ctx => me =>
|
||||
NotForKids {
|
||||
if (env.ublog.api.canBlog(me)) {
|
||||
if (!me.is(username)) Redirect(routes.Ublog.form(me.username)).fuccess
|
||||
else Ok(html.ublog.form.create(me, env.ublog.form.create)).fuccess
|
||||
else
|
||||
env.ublog.form.anyCaptcha map { captcha =>
|
||||
Ok(html.ublog.form.create(me, env.ublog.form.create, captcha))
|
||||
}
|
||||
} else
|
||||
Unauthorized(
|
||||
html.site.message.notYet(
|
||||
"Please play a few games and wait 2 days before you can create blog posts."
|
||||
)
|
||||
).fuccess
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -70,7 +80,10 @@ final class Ublog(env: Env) extends LilaController(env) {
|
|||
env.ublog.form.create
|
||||
.bindFromRequest()(ctx.body, formBinding)
|
||||
.fold(
|
||||
err => BadRequest(html.ublog.form.create(me, err)).fuccess,
|
||||
err =>
|
||||
env.ublog.form.anyCaptcha map { captcha =>
|
||||
BadRequest(html.ublog.form.create(me, err, captcha))
|
||||
},
|
||||
data =>
|
||||
CreateLimitPerUser(me.id, cost = if (me.isVerified) 1 else 3) {
|
||||
env.ublog.api.create(data, me) map { post =>
|
||||
|
|
|
@ -61,4 +61,9 @@ object captcha {
|
|||
)
|
||||
}
|
||||
)
|
||||
|
||||
def hiddenEmpty(form: lila.common.Form.FormLike) = frag(
|
||||
form3.hidden(form("gameId")),
|
||||
form3.hidden(form("move"))
|
||||
)
|
||||
}
|
||||
|
|
|
@ -103,4 +103,7 @@ object message {
|
|||
apply("Temporarily disabled")(
|
||||
"Sorry, his feature is temporarily disabled while we figure out a way to bring it back."
|
||||
)
|
||||
|
||||
def notYet(text: String)(implicit ctx: Context) =
|
||||
apply("Not yet available")(text)
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import play.api.data.Form
|
|||
import lila.api.Context
|
||||
import lila.app.templating.Environment._
|
||||
import lila.app.ui.ScalatagsTemplate._
|
||||
import lila.common.Captcha
|
||||
import lila.ublog.UblogForm.UblogPostData
|
||||
import lila.ublog.UblogPost
|
||||
import lila.user.User
|
||||
|
@ -14,15 +15,16 @@ object form {
|
|||
|
||||
import views.html.ublog.{ post => postView }
|
||||
|
||||
def create(user: User, f: Form[UblogPostData])(implicit ctx: Context) =
|
||||
def create(user: User, f: Form[UblogPostData], captcha: Captcha)(implicit ctx: Context) =
|
||||
views.html.base.layout(
|
||||
moreCss = cssTag("ublog"),
|
||||
moreJs = captchaTag,
|
||||
title = s"${trans.ublog.xBlog.txt(user.username)} • ${trans.ublog.newPost()}"
|
||||
) {
|
||||
main(cls := "box box-pad page page-small ublog-post-form")(
|
||||
h1(trans.ublog.newPost()),
|
||||
etiquette,
|
||||
inner(user, f, none)
|
||||
inner(user, f, none, captcha.some)
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -35,7 +37,7 @@ object form {
|
|||
main(cls := "box box-pad page page-small ublog-post-form")(
|
||||
h1(trans.ublog.editYourBlogPost()),
|
||||
imageForm(user, post),
|
||||
inner(user, f, post.some),
|
||||
inner(user, f, post.some, none),
|
||||
postForm(
|
||||
cls := "ublog-post-form__delete",
|
||||
action := routes.Ublog.delete(user.username, post.id.value)
|
||||
|
@ -67,8 +69,8 @@ object form {
|
|||
|
||||
def formImage(post: UblogPost) = postView.thumbnail(post, _.Small)
|
||||
|
||||
private def inner(user: User, form: Form[UblogPostData], post: Option[UblogPost])(implicit
|
||||
ctx: Context
|
||||
private def inner(user: User, form: Form[UblogPostData], post: Option[UblogPost], captcha: Option[Captcha])(
|
||||
implicit ctx: Context
|
||||
) =
|
||||
postForm(
|
||||
cls := "form3",
|
||||
|
@ -89,6 +91,9 @@ object form {
|
|||
trans.ublog.postBody(),
|
||||
help = frag(markdownAvailable, br, trans.embedsAvailable()).some
|
||||
)(form3.textarea(_)(rows := 30)),
|
||||
captcha.fold(views.html.base.captcha.hiddenEmpty(form)) { c =>
|
||||
views.html.base.captcha(form, c)
|
||||
},
|
||||
form3.actions(
|
||||
a(href := post.fold(routes.Ublog.index(user.username))(views.html.ublog.post.urlOf))(trans.cancel()),
|
||||
form3.submit(trans.apply())
|
||||
|
|
|
@ -11,7 +11,8 @@ final class Env(
|
|||
timeline: lila.hub.actors.Timeline,
|
||||
picfitApi: lila.memo.PicfitApi,
|
||||
picfitUrl: lila.memo.PicfitUrl,
|
||||
ircApi: lila.irc.IrcApi
|
||||
ircApi: lila.irc.IrcApi,
|
||||
captcher: lila.hub.actors.Captcher
|
||||
)(implicit
|
||||
ec: scala.concurrent.ExecutionContext
|
||||
) {
|
||||
|
|
|
@ -99,6 +99,11 @@ final class UblogApi(
|
|||
coll.delete.one($id(post.id)) >>
|
||||
picfitApi.deleteByRel(imageRel(post))
|
||||
|
||||
def canBlog(u: User) =
|
||||
!u.isBot && {
|
||||
(u.count.game > 0 && u.createdSinceDays(2)) || u.hasTitle || u.isVerified || u.isPatron
|
||||
}
|
||||
|
||||
private def paginatorByUser(user: User, live: Boolean, page: Int): Fu[Paginator[UblogPost.PreviewPost]] =
|
||||
Paginator(
|
||||
adapter = new Adapter[UblogPost.PreviewPost](
|
||||
|
|
|
@ -7,28 +7,49 @@ import play.api.data.Forms._
|
|||
import lila.common.Form.{ cleanNonEmptyText, cleanText, markdownImage }
|
||||
import lila.user.User
|
||||
|
||||
final class UblogForm(markup: UblogMarkup) {
|
||||
final class UblogForm(markup: UblogMarkup, val captcher: lila.hub.actors.Captcher)(implicit
|
||||
ec: scala.concurrent.ExecutionContext
|
||||
) extends lila.hub.CaptchedForm {
|
||||
|
||||
import UblogForm._
|
||||
|
||||
val create = Form(
|
||||
private val base =
|
||||
mapping(
|
||||
"title" -> cleanNonEmptyText(minLength = 3, maxLength = 100),
|
||||
"intro" -> cleanNonEmptyText(minLength = 0, maxLength = 2_000),
|
||||
"markdown" -> cleanNonEmptyText(minLength = 0, maxLength = 100_000).verifying(markdownImage.constraint),
|
||||
"live" -> boolean
|
||||
"live" -> boolean,
|
||||
"gameId" -> text,
|
||||
"move" -> text
|
||||
)(UblogPostData.apply)(UblogPostData.unapply)
|
||||
|
||||
val create = Form(
|
||||
base.verifying(captchaFailMessage, validateCaptcha _)
|
||||
)
|
||||
|
||||
def edit(post: UblogPost) =
|
||||
create.fill(
|
||||
UblogPostData(title = post.title, intro = post.intro, markdown = post.markdown, live = post.live)
|
||||
Form(base).fill(
|
||||
UblogPostData(
|
||||
title = post.title,
|
||||
intro = post.intro,
|
||||
markdown = post.markdown,
|
||||
live = post.live,
|
||||
gameId = "",
|
||||
move = ""
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
object UblogForm {
|
||||
|
||||
case class UblogPostData(title: String, intro: String, markdown: String, live: Boolean) {
|
||||
case class UblogPostData(
|
||||
title: String,
|
||||
intro: String,
|
||||
markdown: String,
|
||||
live: Boolean,
|
||||
gameId: String,
|
||||
move: String
|
||||
) {
|
||||
|
||||
def create(user: User) = {
|
||||
val now = DateTime.now
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
@import '../../../common/css/plugin';
|
||||
@import '../../../common/css/component/slist';
|
||||
@import '../../../common/css/form/form3';
|
||||
@import '../../../common/css/form/captcha';
|
||||
@import '../ublog/ublog';
|
||||
|
|
Loading…
Reference in New Issue