port auth stuff

pull/83/head
Thibault Duplessis 2013-05-04 20:12:53 -03:00
parent 30654c8852
commit 7c2bfe9d10
27 changed files with 193 additions and 66 deletions

View File

@ -22,7 +22,7 @@ final class Env(config: Config, system: ActorSystem) {
if (ModulePreload) {
loginfo("Preloading modules")
(Env.site, Env.setup, Env.game, Env.gameSearch, Env.team,
(Env.site, Env.game, Env.setup, Env.game, Env.gameSearch, Env.team,
Env.teamSearch, Env.forumSearch, Env.message)
}

View File

@ -1,45 +1,81 @@
package controllers
import lila.app._
import lila.api._
import lila.common.LilaCookie
import lila.user.{ UserRepo, HistoryRepo }
import views._
import play.api.mvc._, Results._
import play.api.data._, Forms._
object Auth extends LilaController {
// protected def gotoLoginSucceeded[A](username: String)(implicit req: RequestHeader) = {
// val sessionId = saveAuthentication(username)
// val uri = req.session.get(AccessUri) | routes.Lobby.home.url
// Redirect(uri) withCookies LilaCookie.withSession { session
// session + ("sessionId" -> sessionId) - AccessUri
// }
// }
private def api = Env.security.api
private def forms = Env.security.forms
// protected def gotoSignupSucceeded[A](username: String)(implicit req: RequestHeader) = {
// val sessionId = saveAuthentication(username)
// Redirect(routes.User.show(username)) withCookies LilaCookie.session("sessionId", sessionId)
// }
private def gotoLoginSucceeded[A](username: String)(implicit req: RequestHeader) =
api saveAuthentication username map { sessionId
val uri = req.session.get(api.AccessUri) | routes.Lobby.home.url
Redirect(uri) withCookies LilaCookie.withSession { session
session + ("sessionId" -> sessionId) - api.AccessUri
}
}
def login = TODO
// Open { implicit ctx
// Ok(html.auth.login(loginForm))
// }
private def gotoSignupSucceeded[A](username: String)(implicit req: RequestHeader) =
api saveAuthentication username map { sessionId
Redirect(routes.User.show(username)) withCookies LilaCookie.session("sessionId", sessionId)
}
def authenticate = TODO
def logout = TODO
def signup = TODO
def signupPost = TODO
def login = Open { implicit ctx
Ok(html.auth.login(api.loginForm)) fuccess
}
protected def gotoLogoutSucceeded(implicit req: RequestHeader) = {
def authenticate = OpenBody { implicit ctx
Firewall {
implicit val req = ctx.body
api.loginForm.bindFromRequest.fold(
err BadRequest(html.auth.login(err)) fuccess,
userOption gotoLoginSucceeded(
userOption.err("authenticate error").username
)
)
}
}
def logout = Open { implicit ctx
gotoLogoutSucceeded(ctx.req) fuccess
}
def signup = Open { implicit ctx
forms.signupWithCaptcha map {
case (form, captcha) Ok(html.auth.signup(form, captcha))
}
}
def signupPost = OpenBody { implicit ctx
implicit val req = ctx.body
forms.signup.bindFromRequest.fold(
err forms.anyCaptcha map { captcha
BadRequest(html.auth.signup(err, captcha))
},
data Firewall {
UserRepo.create(data.username, data.password) flatMap { userOption
val user = userOption err "No user could be created for %s".format(data.username)
HistoryRepo.addEntry(user.id, user.elo, none) >>
gotoSignupSucceeded(user.username)
}
}
)
}
private def gotoLogoutSucceeded(implicit req: RequestHeader) = {
req.session get "sessionId" foreach lila.security.Store.delete
logoutSucceeded(req) withCookies LilaCookie.newSession
}
protected def logoutSucceeded(req: RequestHeader): PlainResult =
private def logoutSucceeded(req: RequestHeader): PlainResult =
Redirect(routes.Lobby.home)
// protected def authenticationFailed(implicit req: RequestHeader): Result =
// Redirect(routes.Auth.signup) withCookies LilaCookie.session(AccessUri, req.uri)
private def authenticationFailed(implicit req: RequestHeader): Result =
Redirect(routes.Auth.signup) withCookies LilaCookie.session(api.AccessUri, req.uri)
}

View File

@ -72,13 +72,13 @@ trait LilaController
// ctx.isGranted(perm).fold(f(ctx)(me), authorizationFailed(ctx.req))
// }
// protected def Firewall[A <: Result](a: A)(implicit ctx: Context): Result =
// env.security.firewall.accepts(ctx.req).fold(
// a, {
// env.security.firewall.logBlock(ctx.req)
// Redirect(routes.Lobby.home())
// }
// )
protected def Firewall[A <: Result](a: Fu[A])(implicit ctx: Context): Fu[Result] =
Env.security.firewall.accepts(ctx.req) flatMap {
_ fold (a, {
Env.security.firewall.logBlock(ctx.req)
fuccess { Redirect(routes.Lobby.home()) }
})
}
// protected def NoEngine[A <: Result](a: A)(implicit ctx: Context): Result =
// ctx.me.fold(false)(_.engine).fold(Forbidden(views.html.site.noEngine()), a)

View File

@ -1,13 +1,16 @@
package controllers
import lila.app._
import lila.hub.actorApi.captcha.ValidCaptcha
import views._
import makeTimeout.large
import play.api.mvc._, Results._
import play.api.data._, Forms._
import play.api.libs.json._
import play.api.libs.iteratee._
import play.api.libs.concurrent.Akka
import akka.pattern.ask
object Main extends LilaController {
@ -18,9 +21,11 @@ object Main extends LilaController {
)
}
// def captchaCheck(id: String) = Open { implicit ctx
// Ok(env.site.captcha get id valid ~get("solution") fold (1, 0))
// }
def captchaCheck(id: String) = Open { implicit ctx
Env.hub.actor.captcher ? ValidCaptcha(id, ~get("solution")) map {
case valid: Boolean Ok(valid fold (1, 0))
}
}
// def embed = Open { implicit ctx
// JsOk("""document.write("<iframe src='%s?embed=" + document.domain + "' class='lichess-iframe' allowtransparency='true' frameBorder='0' style='width: %dpx; height: %dpx;' title='Lichess free online chess'></iframe>");"""

View File

@ -3,7 +3,7 @@ package controllers
import lila.app._
import views._
import lila.security.Permission
import lila.user.{ Context, User UserModel }
import lila.user.{ Context, User UserModel, UserRepo }
import lila.common.LilaCookie
import play.api.mvc._, Results._
@ -17,7 +17,13 @@ object User extends LilaController {
// private def bookmarkApi = env.bookmark.api
// private def modApi = env.mod.api
def show(username: String) = TODO //showFilter(username, "all", 1)
def show(username: String) = Open { implicit ctx =>
UserRepo named username map { userOption =>
println(userOption)
NotFound
}
}
//showFilter(username, "all", 1)
// def showFilter(username: String, filterName: String, page: Int) = Open { implicit ctx
// Async {
@ -42,6 +48,7 @@ object User extends LilaController {
// }
// }, io(html.user.disabled(u)))
def list(page: Int) = TODO
// def list(page: Int) = Open { implicit ctx
// (page < 50).fold(
// IOk(onlineUsers map { html.user.list(paginator elo page, _) }),

View File

@ -1,11 +1,20 @@
package lila
import scalaz.Zero
import play.api.mvc.{ Result, Results }
import play.api.mvc.{ Result, PlainResult, Results }
import scala.concurrent.Future
package object app extends PackageObject with WithPlay with socket.WithSocket {
implicit val LilaResultZero = new Zero[Result] {
val zero = Results.NotFound
}
implicit val LilaPlainResultZero = new Zero[PlainResult] {
val zero = Results.NotFound
}
implicit final class LilaPimpedResult(result: Result) {
def fuccess = Future successful result
}
}

View File

@ -15,13 +15,13 @@ final class SiteMenu(trans: I18nKeys) {
val play = new Elem("play", routes.Lobby.home, trans.play)
// val game = new Elem("game", routes.Game.realtime, trans.games)
// val tournament = new Elem("tournament", routes.Tournament.home, trans.tournament)
// val user = new Elem("user", routes.User.list(page = 1), trans.people)
val user = new Elem("user", routes.User.list(page = 1), trans.people)
// val team = new Elem("team", routes.Team.home(page = 1), trans.teams)
// val forum = new Elem("forum", routes.ForumCateg.index, trans.forum)
// val message = new Elem("message", routes.Message.inbox(page = 1), trans.inbox)
private val authenticated = List(play)//, game, tournament, user, team, forum, message)
private val anonymous = List(play)//, game, tournament, user, team, forum)
private val authenticated = List(play, user)//, game, tournament, user, team, forum, message)
private val anonymous = List(play, user)//, game, tournament, user, team, forum)
def all(me: Option[User]) = me.isDefined.fold(authenticated, anonymous)
}

View File

@ -1,7 +1,6 @@
@(form: Form[_])(implicit ctx: Context)
@auth.layout(
title = trans.signIn.str()) {
@auth.layout(title = trans.signIn.str()) {
<div class="content_box small_box">
<div class="signup_box">
<h1 class="lichess_title">@trans.signIn()</h1>

View File

@ -1,4 +1,4 @@
@(form: Form[_], captcha: lila.app.site.Captcha.Challenge)(implicit ctx: Context)
@(form: Form[_], captcha: lila.common.Captcha)(implicit ctx: Context)
@auth.layout(
title = trans.signUp.str()) {

View File

@ -1,15 +1,16 @@
@(move: Field, gameId: Field, captcha: lila.app.site.Captcha.Challenge)(implicit ctx: Context)
@(move: Field, gameId: Field, captcha: lila.common.Captcha)(implicit ctx: Context)
<input type="hidden" name="@gameId.name" id="@gameId.id" value="@captcha.gameId" />
@defining(captcha.white.fold("white", "black")) { color =>
<div class="checkmateCaptcha clearfix" data-check-url="@routes.Main.captchaCheck(captcha.gameId)">
<div class="checkmateFen">
<div
class="mini_board parse_fen with_keys"
data-color="@captcha.color"
data-color="@color"
data-fen="@captcha.fen"></div>
</div>
<div class="checkmateSection">
<label>
<strong>@captcha.color.toString.capitalize plays; checkmate in one!</strong><br />
<strong>@color.capitalize plays; checkmate in one!</strong><br />
This is a chess CAPTCHA.<br />
Click on the board to make your move,<br />
and prove you are human.<br /><br />
@ -19,4 +20,4 @@
<input type="hidden" name="@move.name" id="@move.id" value="" />
</div>
</div>
}

View File

@ -0,0 +1,22 @@
@(title: String, cssClass: String = "")(content: Html)(implicit ctx: Context)
<div class="lichess_chat @cssClass">
<div class="lichess_chat_top">
<span class="title">@title</span>
<input
data-href="@routes.Setting.set("chat")"
data-enabled="@setting.chat.fold("true", "false")"
title="@trans.toggleTheChat()"
class="toggle_chat"
type="checkbox" />
</div>
<div class="lichess_chat_inner">
<div class="nano">
<ol class="lichess_messages content">@content</ol>
</div>
<form action="#">
<input class="lichess_say" value="" placeholder="@trans.talkInChat()" />
<a class="send"></a>
</form>
</div>
</div>

View File

@ -0,0 +1,16 @@
@(ex: Throwable)(implicit ctx: Context)
@site.layout(
title = "Internal server error") {
<div class="content_box small_box">
<h1>Something went wrong on this page.</h1>
<br />
<br />
<p>If the problem persists, please report it in the <a href="@routes.ForumCateg.show("lichess-feedback", 1)">forum</a>.</p>
<p>Or send me an email at thibault.duplessis&#64;gmail.com</p>
<br />
<br />
<code>@ex.getMessage</code>
</div>
}

View File

@ -0,0 +1,5 @@
@(field: play.api.data.Field, placeholder: Option[String] = None)
<input @placeholder.map { p =>
placeholder="@p"
} type="text" value="@field.value" name="@field.name" id="@field.id" />

View File

@ -0,0 +1,10 @@
@(field: play.api.data.Field, options: Iterable[(Any,String)], default: Option[String] = None)
<select id="@field.id" name="@field.name">
@default.map { d =>
<option value="">@d</option>
}
@options.map { v =>
<option value="@v._1" @(if(field.value == Some(v._1.toString)) "selected" else "")>@v._2</option>
}
</select>

View File

@ -9,7 +9,7 @@ GET / controllers.Lobby.home
# POST /@/:username/export controllers.User.export(username: String)
# GET /@/:username/:filterName controllers.User.showFilter(username: String, filterName: String, page: Int ?= 1)
GET /@/:username controllers.User.show(username: String)
# GET /people controllers.User.list(page: Int ?= 1)
GET /people controllers.User.list(page: Int ?= 1)
# GET /people/autocomplete controllers.User.autocomplete
# GET /people/online controllers.User.online
# GET /account/bio controllers.User.getBio
@ -183,7 +183,7 @@ GET /monitor controllers.Monitor.index
# Misc
POST /cli controllers.Cli.command
# GET /captcha/$id<[\w]{8}> controllers.Main.captchaCheck(id: String)
GET /captcha/$id<[\w]{8}> controllers.Main.captchaCheck(id: String)
GET /developers controllers.Main.developers
# Assets

View File

@ -35,6 +35,8 @@ trait PackageObject
implicit final class LilaPimpedOption[A](o: Option[A]) {
def zmap[B: Zero](f: A B): B = o.fold([B])(f)
def zflatMap[B: Zero](f: A Option[B]): B = o.fold([B]) { x ~f(x) }
}
implicit final class LilaPimpedMap[A, B](m: Map[A, B]) {

View File

@ -22,8 +22,16 @@ case class Tube[Doc](
implicit def reads(js: JsValue): JsResult[Doc] = reader reads js
implicit def writes(doc: Doc): JsValue = writer writes doc
def read(bson: BSONDocument): Option[Doc] =
fromMongo(JsObjectReader read bson).asOpt
def read(bson: BSONDocument): Option[Doc] = {
val js = JsObjectReader read bson
fromMongo(js) match {
case JsSuccess(v, _) Some(v)
case e {
logwarn("[tube] Cannot read %s\n%s".format(js, e))
None
}
}
}
def read(js: JsObject): JsResult[Doc] = reads(js)

View File

@ -14,6 +14,7 @@ import scala.concurrent.duration._
import play.api.libs.concurrent.Akka.system
import play.api.Play.current
import akka.actor._
import akka.pattern.{ ask, pipe }
// only works with standard chess (not chess960)
private final class Captcher extends Actor {
@ -25,6 +26,9 @@ private final class Captcher extends Actor {
case GetCaptcha(id: String) sender ! Impl.get(id).await
case NewCaptcha Impl.refresh.await
case ValidCaptcha(id: String, solution: String)
Impl get id map (_ valid solution) pipeTo sender
}
override def preStart() {
@ -60,7 +64,7 @@ private final class Captcher extends Actor {
private def find(id: String): Option[Captcha] =
challenges.list.find(_.gameId == id)
private def createFromDb: Fu[Option[Captcha]] =
private def createFromDb: Fu[Option[Captcha]] =
optionT(findCheckmateInDb(100) flatMap {
_.fold(findCheckmateInDb(1))(g fuccess(g.some))
}) flatMap fromGame
@ -68,10 +72,10 @@ private final class Captcher extends Actor {
private def findCheckmateInDb(distribution: Int): Fu[Option[Game]] =
GameRepo findRandomStandardCheckmate distribution
private def getFromDb(id: String): Fu[Option[Captcha]] =
private def getFromDb(id: String): Fu[Option[Captcha]] =
optionT($find byId id) flatMap fromGame
private def fromGame(game: Game): OptionT[Fu, Captcha] =
private def fromGame(game: Game): OptionT[Fu, Captcha] =
optionT(PgnRepo getOption game.id) flatMap { makeCaptcha(game, _) }
private def makeCaptcha(game: Game, pgnString: String): OptionT[Fu, Captcha] =

View File

@ -15,6 +15,7 @@ final class Env(
private val settings = new {
val CachedNbTtl = config duration "cached.nb.ttl"
val PaginatorMaxPerPage = config getInt "paginator.max_per_page"
val CaptcherName = config getString "captcher.name"
val CollectionGame = config getString "collection.game"
val CollectionPgn = config getString "collection.pgn"
val JsPathRaw = config getString "js_path.raw"
@ -45,6 +46,9 @@ final class Env(
lazy val gameJs = new GameJs(path = jsPath, useCache = !isDev)
// load captcher actor
system.actorOf(Props(new Captcher), name = CaptcherName)
if (!isDev) {
val scheduler = new lila.common.Scheduler(system)

View File

@ -24,7 +24,8 @@ trait CaptchedForm {
def getCaptcha(id: String): Fu[Captcha] =
(captcher ? GetCaptcha(id)).mapTo[Captcha]
def withCaptcha[A](form: Form[A]) = anyCaptcha map (form -> _)
def withCaptcha[A](form: Form[A]): Fu[(Form[A], Captcha)] =
anyCaptcha map (form -> _)
def validateCaptcha(data: CaptchedData) =
getCaptcha(data.gameId).await valid data.move.trim.toLowerCase

View File

@ -29,9 +29,6 @@ final class Env(config: Config, system: ActorSystem) {
val lobby = socketFor("lobby")
val monitor = socketFor("monitor")
val site = socketFor("site")
site ! 1
lobby ! 1
val hub = system.actorOf(Props(new Broadcast(List(
socket.lobby, socket.site
))(makeTimeout(SocketHubTimeout))), name = SocketHubName)

View File

@ -20,6 +20,7 @@ case object GetUserIds
package captcha {
case object AnyCaptcha
case class GetCaptcha(id: String)
case class ValidCaptcha(id: String, solution: String)
}
package lobby {

View File

@ -16,8 +16,7 @@ final class Env(config: Config, system: ActorSystem) {
val esIndexer = elasticsearch.Indexer.transport(
settings = Map("cluster.name" -> ESCluster),
host = ESHost,
ports = Seq(ESPort)
)
ports = Seq(ESPort))
Future {
loginfo("[search] Start ElasticSearch")

View File

@ -8,7 +8,7 @@ import play.api.data._
import play.api.data.Forms._
import ornicar.scalalib.Random
final class Api(firewall: Firewall) {
private[security] final class Api(firewall: Firewall) {
def AccessUri = "access_uri"
@ -19,8 +19,10 @@ final class Api(firewall: Firewall) {
.verifying("Invalid username or password", _.isDefined)
)
def saveAuthentication(username: String)(implicit req: RequestHeader): String =
(Random nextString 12) ~ { sessionId Store.save(sessionId, username, req) }
def saveAuthentication(username: String)(implicit req: RequestHeader): Fu[String] = {
val sessionId = Random nextString 12
Store.save(sessionId, username, req) inject sessionId
}
def authorizationFailed(req: RequestHeader): Result =
Forbidden("no permission")

View File

@ -19,8 +19,7 @@ object HistoryRepo {
$push("entries", opponentElo.fold(Json.arr(DateTime.now.getSeconds.toInt, elo)) { opElo
Json.arr(DateTime.now.getSeconds.toInt, elo, opElo)
}),
upsert = true
)
upsert = true)
def userElos(userId: String): Fu[Seq[(Int, Int, Option[Int])]] =
$find.one($select(userId)) map { historyOption