diff --git a/module-code/app/securesocial/controllers/ProviderController.scala b/module-code/app/securesocial/controllers/ProviderController.scala index 04d1ba764..81d8bf0e9 100644 --- a/module-code/app/securesocial/controllers/ProviderController.scala +++ b/module-code/app/securesocial/controllers/ProviderController.scala @@ -25,7 +25,7 @@ import providers.utils.RoutesHelper import securesocial.core.LoginEvent import securesocial.core.AccessDeniedException import scala.Some - +import securesocial.core.providers.UsernamePasswordProvider /** * A controller to provide the authentication entry point @@ -107,17 +107,21 @@ object ProviderController extends Controller } } - def completeAuthentication(user: Identity, session: Session)(implicit request: RequestHeader): PlainResult = { - if ( Logger.isDebugEnabled ) { + def completeAuthentication(user: Identity, session: Session)(implicit request: Request[AnyContent]): PlainResult = { + if (Logger.isDebugEnabled) { Logger.debug("[securesocial] user logged in : [" + user + "]") } + val withSession = Events.fire(new LoginEvent(user)).getOrElse(session) Authenticator.create(user) match { case Right(authenticator) => { + val form = UsernamePasswordProvider.loginForm.bindFromRequest() + val rememberCookie = form.fold(error => None, credentials => credentials.remember) Redirect(toUrl).withSession(withSession - SecureSocial.OriginalUrlKey - IdentityProvider.SessionId - - OAuth1Provider.CacheKey).withCookies(authenticator.toCookie) + OAuth1Provider.CacheKey).withCookies(authenticator.toCookie(rememberCookie)) + } case Left(error) => { // improve this diff --git a/module-code/app/securesocial/controllers/TemplatesPlugin.scala b/module-code/app/securesocial/controllers/TemplatesPlugin.scala index 564ef90c7..4f6c5ba88 100644 --- a/module-code/app/securesocial/controllers/TemplatesPlugin.scala +++ b/module-code/app/securesocial/controllers/TemplatesPlugin.scala @@ -23,6 +23,7 @@ import securesocial.core.{Identity, SecuredRequest, SocialUser} import play.api.data.Form import securesocial.controllers.Registration.RegistrationInfo import securesocial.controllers.PasswordChange.ChangeInfo +import securesocial.core.providers.UsernamePasswordProvider.LoginInfo /** @@ -44,7 +45,7 @@ trait TemplatesPlugin extends Plugin { * @tparam A * @return */ - def getLoginPage[A](implicit request: Request[A], form: Form[(String, String)], msg: Option[String] = None): Html + def getLoginPage[A](implicit request: Request[A], form: Form[LoginInfo], msg: Option[String] = None): Html /** * Returns the html for the signup page @@ -165,7 +166,7 @@ trait TemplatesPlugin extends Plugin { * @param application */ class DefaultTemplatesPlugin(application: Application) extends TemplatesPlugin { - override def getLoginPage[A](implicit request: Request[A], form: Form[(String, String)], + override def getLoginPage[A](implicit request: Request[A], form: Form[LoginInfo], msg: Option[String] = None): Html = { securesocial.views.html.login(form, msg) diff --git a/module-code/app/securesocial/core/Authenticator.scala b/module-code/app/securesocial/core/Authenticator.scala index 5c9a727ab..69408db5d 100644 --- a/module-code/app/securesocial/core/Authenticator.scala +++ b/module-code/app/securesocial/core/Authenticator.scala @@ -43,12 +43,14 @@ case class Authenticator(id: String, identityId: IdentityId, creationDate: DateT * * @return a cookie instance */ - def toCookie: Cookie = { + def toCookie(): Cookie = toCookie(None) + def toCookie(remeberMe : Option[Boolean]): Cookie = { import Authenticator._ Cookie( cookieName, id, - if ( makeTransient ) Transient else Some(absoluteTimeoutInSeconds), + if ( remeberMe.getOrElse(makeTransient) ) Transient else Some(absoluteTimeoutInSeconds), + cookiePath, cookieDomain, secure = cookieSecure, diff --git a/module-code/app/securesocial/core/providers/UsernamePasswordProvider.scala b/module-code/app/securesocial/core/providers/UsernamePasswordProvider.scala index 67fe8ae7f..3953587bb 100644 --- a/module-code/app/securesocial/core/providers/UsernamePasswordProvider.scala +++ b/module-code/app/securesocial/core/providers/UsernamePasswordProvider.scala @@ -26,6 +26,7 @@ import Play.current import com.typesafe.plugin._ import securesocial.controllers.TemplatesPlugin import org.joda.time.DateTime +import play.api.mvc.Session /** * A username password provider @@ -43,11 +44,11 @@ class UsernamePasswordProvider(application: Application) extends IdentityProvide form.fold( errors => Left(badRequest(errors, request)), credentials => { - val userId = IdentityId(credentials._1, id) + val userId = IdentityId(credentials.username, id) val result = for ( user <- UserService.find(userId) ; pinfo <- user.passwordInfo ; - hasher <- Registry.hashers.get(pinfo.hasher) if hasher.matches(pinfo, credentials._2) + hasher <- Registry.hashers.get(pinfo.hasher) if hasher.matches(pinfo,credentials.password) ) yield ( Right(SocialUser(user)) ) @@ -58,11 +59,12 @@ class UsernamePasswordProvider(application: Application) extends IdentityProvide ) } - private def badRequest[A](f: Form[(String,String)], request: Request[A], msg: Option[String] = None): PlainResult = { + private def badRequest[A](f: Form[UsernamePasswordProvider.LoginInfo], request: Request[A], msg: Option[String] = None): PlainResult = { Results.BadRequest(use[TemplatesPlugin].getLoginPage(request, f, msg)) } - def fillProfile(user: SocialUser) = { + + def fillProfile(user: SocialUser) = { GravatarHelper.avatarFor(user.email.get) match { case Some(url) if url != user.avatarUrl => user.copy( avatarUrl = Some(url)) case _ => user @@ -78,13 +80,28 @@ object UsernamePasswordProvider { private val Hasher = "securesocial.userpass.hasher" private val EnableTokenJob = "securesocial.userpass.enableTokenJob" private val SignupSkipLogin = "securesocial.userpass.signupSkipLogin" + private val RememberMe = "securesocial.userpass.withRememberMe" + - val loginForm = Form( - tuple( + case class LoginInfo(username: String, password: String, remember: Option[Boolean]) + + val loginFormWithRemember = Form[LoginInfo]( + mapping( "username" -> nonEmptyText, - "password" -> nonEmptyText - ) - ) + "password" -> nonEmptyText, + "remember" -> boolean) // binding + ((username, password, remember) => LoginInfo(username, password, Some(remember))) + (info => Some(info.username, info.password, info.remember.getOrElse(false))) + ) + val loginFormWithoutRemember = Form[LoginInfo]( + mapping( + "username" -> nonEmptyText, + "password" -> nonEmptyText) + // binding + ((username, password) => LoginInfo(username, password, None)) // unbinding + (info => Some(info.username, info.password))) + + val loginForm = if (UsernamePasswordProvider.withRememberMe) loginFormWithRemember else loginFormWithoutRemember lazy val withUserNameSupport = current.configuration.getBoolean(Key).getOrElse(false) lazy val sendWelcomeEmail = current.configuration.getBoolean(SendWelcomeEmailKey).getOrElse(true) @@ -92,6 +109,8 @@ object UsernamePasswordProvider { lazy val hasher = current.configuration.getString(Hasher).getOrElse(PasswordHasher.BCryptHasher) lazy val enableTokenJob = current.configuration.getBoolean(EnableTokenJob).getOrElse(true) lazy val signupSkipLogin = current.configuration.getBoolean(SignupSkipLogin).getOrElse(false) + lazy val withRememberMe = current.configuration.getBoolean(RememberMe).getOrElse(false) + } /** diff --git a/module-code/app/securesocial/views/login.scala.html b/module-code/app/securesocial/views/login.scala.html index 75e7fdfa9..fb089a264 100644 --- a/module-code/app/securesocial/views/login.scala.html +++ b/module-code/app/securesocial/views/login.scala.html @@ -1,4 +1,4 @@ -@(loginForm: Form[(String,String)], errorMsg: Option[String] = None)(implicit request: RequestHeader) +@(loginForm: Form[securesocial.core.providers.UsernamePasswordProvider.LoginInfo], errorMsg: Option[String] = None)(implicit request: RequestHeader) @import helper._ @import securesocial.core.Registry diff --git a/module-code/app/securesocial/views/provider.scala.html b/module-code/app/securesocial/views/provider.scala.html index 4a682ebcf..2051d1415 100644 --- a/module-code/app/securesocial/views/provider.scala.html +++ b/module-code/app/securesocial/views/provider.scala.html @@ -1,4 +1,4 @@ -@(providerId: String, loginForm: Option[Form[(String, String)]] = None)(implicit request: RequestHeader) +@(providerId: String, loginForm: Option[Form[securesocial.core.providers.UsernamePasswordProvider.LoginInfo]] = None)(implicit request: RequestHeader) @import securesocial.core.Registry @import securesocial.core.IdentityProvider @@ -40,6 +40,14 @@ '_label -> Messages("securesocial.signup.password1"), 'class -> "input-xlarge" ) + + @if( UsernamePasswordProvider.withRememberMe ) { + @helper.checkbox( + loginForm.get("remember"), + '_label -> Messages("securesocial.login.remember"), + 'class -> "input-xlarge" + ) + }
diff --git a/module-code/conf/messages.zh-CN b/module-code/conf/messages.zh-CN index b28663f51..b9d411e68 100644 --- a/module-code/conf/messages.zh-CN +++ b/module-code/conf/messages.zh-CN @@ -3,6 +3,7 @@ securesocial.appName=Secure Social 2 # Login page securesocial.login.title=登录 +securesocial.login.remember=記住我 securesocial.login.instructions=用你在这里的账号或点击下面这些网站的图标登录 securesocial.login.accessDenied=你被拒绝访问你的账号。请允许用该账号登录。 securesocial.login.errorLoggingIn=在你登录时系统出错了,请再试一次。 diff --git a/samples/scala/demo/conf/securesocial.conf b/samples/scala/demo/conf/securesocial.conf index fd93477ac..4e1d1830f 100644 --- a/samples/scala/demo/conf/securesocial.conf +++ b/samples/scala/demo/conf/securesocial.conf @@ -164,6 +164,7 @@ securesocial { # Enable username support, otherwise SecureSocial will use the emails as user names # withUserNameSupport=false + withRememberMe = false sendWelcomeEmail=true enableGravatarSupport=true tokenDuration=60