はじめに
Scala+Play+SecureSocialでTwitterにログイン認証してユーザ名とアバターを表示するメモの続きですが、この記事単独でも使えるかと思います。
なお、すべてのビューを表示して確認していないので、おかしなところもあるかもしれません。
play.pluginsファイルの修正
9997番を独自ビューのクラスMyViewに変更します。
conf/play.plugins
9994:securesocial.core.DefaultAuthenticatorStore
9995:securesocial.core.DefaultIdGenerator
9996:securesocial.core.providers.utils.DefaultPasswordValidator
# 9997:securesocial.controllers.DefaultTemplatesPlugin
9997:service.MyView
9998:service.MyUserService
9999:securesocial.core.providers.utils.BCryptPasswordHasher
10000:securesocial.core.providers.TwitterProvider
MyViewsクラスの追加
MyViewsクラスはDefaultTemplatesPluginクラスを継承し、
DefaultTemplatesPluginのメソッドをすべてオーバーライドしてしまいます。
app/views/MyViews.scala
package views
import play.api.mvc._
import play.api.templates._
import play.api.data._
import securesocial.core._
import securesocial.controllers._
import securesocial.controllers.Registration.RegistrationInfo
import securesocial.controllers.PasswordChange.ChangeInfo
class MyViews(application: play.api.Application) extends DefaultTemplatesPlugin(application)
{
/**
* Returns the html for the login page
* @param request
* @tparam A
* @return
*/
override def getLoginPage[A](implicit request: Request[A], form: Form[(String, String)],
msg: Option[String] = None): Html =
{
views.html.myLogin(form, msg)
}
/**
* Returns the html for the signup page
*
* @param request
* @tparam A
* @return
*/
override def getSignUpPage[A](implicit request: Request[A], form: Form[RegistrationInfo], token: String): Html = {
views.html.mySignUp(form, token)
}
/**
* Returns the html for the start signup page
*
* @param request
* @tparam A
* @return
*/
override def getStartSignUpPage[A](implicit request: Request[A], form: Form[String]): Html = {
views.html.myStartSignUp(form)
}
/**
* Returns the html for the reset password page
*
* @param request
* @tparam A
* @return
*/
override def getStartResetPasswordPage[A](implicit request: Request[A], form: Form[String]): Html = {
views.html.myStartResetPassword(form)
}
/**
* Returns the html for the start reset page
*
* @param request
* @tparam A
* @return
*/
override def getResetPasswordPage[A](implicit request: Request[A], form: Form[(String, String)], token: String): Html = {
views.html.myResetPasswordPage(form, token)
}
/**
* Returns the html for the change password page
*
* @param request
* @param form
* @tparam A
* @return
*/
override def getPasswordChangePage[A](implicit request: SecuredRequest[A], form: Form[ChangeInfo]): Html = {
views.html.myPasswordChange(form)
}
/**
* Returns the email sent when a user starts the sign up process
*
* @param token the token used to identify the request
* @param request the current http request
* @return a String with the text and/or html body for the email
*/
override def getSignUpEmail(token: String)(implicit request: RequestHeader): (Option[Txt], Option[Html]) = {
(None, Some(views.html.mySignUpEmail(token)))
}
/**
* Returns the email sent when the user is already registered
*
* @param user the user
* @param request the current request
* @return a String with the text and/or html body for the email
*/
override def getAlreadyRegisteredEmail(user: Identity)(implicit request: RequestHeader): (Option[Txt], Option[Html]) = {
(None, Some(views.html.myAlreadyRegisteredEmail(user)))
}
/**
* Returns the welcome email sent when the user finished the sign up process
*
* @param user the user
* @param request the current request
* @return a String with the text and/or html body for the email
*/
override def getWelcomeEmail(user: Identity)(implicit request: RequestHeader): (Option[Txt], Option[Html]) = {
(None, Some(views.html.myWelcomeEmail(user)))
}
/**
* Returns the email sent when a user tries to reset the password but there is no account for
* that email address in the system
*
* @param request the current request
* @return a String with the text and/or html body for the email
*/
override def getUnknownEmailNotice()(implicit request: RequestHeader): (Option[Txt], Option[Html]) = {
(None, Some(views.html.myUnknownEmailNotice(request)))
}
/**
* Returns the email sent to the user to reset the password
*
* @param user the user
* @param token the token used to identify the request
* @param request the current http request
* @return a String with the text and/or html body for the email
*/
override def getSendPasswordResetEmail(user: Identity, token: String)(implicit request: RequestHeader): (Option[Txt], Option[Html]) = {
(None, Some(views.html.myPasswordResetEmail(user, token)))
}
/**
* Returns the email sent as a confirmation of a password change
*
* @param user the user
* @param request the current http request
* @return a String with the text and/or html body for the email
*/
override def getPasswordChangedNoticeEmail(user: Identity)(implicit request: RequestHeader): (Option[Txt], Option[Html]) = {
(None, Some(views.html.myPasswordChangedNotice(user)))
}
/**
* Returns the html of the Not Authorized page
*
* @param request the current http request
* @return a String with the text and/or html body for the email
*/
override def getNotAuthorizedPage[A](implicit request: Request[A]): Html = {
views.html.myNotAuthorizedPage()
}
}
カスタムビューの作成
SecureSocialでデフォルトで表示されるビューを、独自ビューに書き換えていきます。
main.scala.html
メインのHtmlを定義して各ビューで使い回します。
app/views/main.scala.html
@(title: String)(content: Html)(implicit lang: Lang)
@import securesocial.core.providers.utils.RoutesHelper
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>@title</title>
<link rel="stylesheet" media="screen" href="@RoutesHelper.bootstrapCssPath">
<link rel="shortcut icon" type="image/png" href="@RoutesHelper.faviconPath">
<link rel="stylesheet" media="screen" href="@RoutesHelper.customCssPath.getOrElse("")">
<script src="@RoutesHelper.jqueryPath" type="text/javascript"></script>
</head>
<body>
<div class="navbar navbar-fixed-top">
<div class="navbar-inner">
<div class="container">
<span class="brand" >MyApp</span>
</div>
</div>
</div>
<div class="container" style="padding-top:30px">
@content
</div>
</body>
</html>
index.scala.html
ルートページ。
(ログインしていないときには、ログインページに飛ばされる。)
app/views/index.scala.html
@(user: String, provider: String, avatarUrl: Option[String])(implicit request: RequestHeader, lang: Lang)
@import helper._
@import securesocial.core.Registry
@import securesocial.core.AuthenticationMethod._
@import securesocial.core.providers.UsernamePasswordProvider.UsernamePassword
@main("Welcome") {
<div class="page-header">
<h1>Welcome!</h1>
</div>
Welcome @(user)@@@(provider)!
@avatarUrl match {
case Some(url) => { <image src="@url" /> }
case None => {}
}
<p>
<a href="/logout">Logout</a>
</p>
}
alreadyRegisteredEmail.scala.html
app/views/myAlreadyRegisteredEmail.scala.html
@(user: securesocial.core.Identity)(implicit request: RequestHeader)
@import securesocial.core.IdentityProvider
<html>
<body>
<p>Hello @user.firstName,</p>
<p>You tried to sign up but you already have an account with us. If you don't remember your password please go
<a href="@securesocial.core.providers.utils.RoutesHelper.startResetPassword().absoluteURL(IdentityProvider.sslEnabled)">here</a> to reset it.</p>
</body>
</html>
login.scala.html
app/views/myLogin.scala.html
@(loginForm: Form[(String,String)], errorMsg: Option[String] = None)(implicit request: RequestHeader, lang: Lang)
@import helper._
@import securesocial.core.Registry
@import securesocial.core.AuthenticationMethod._
@import securesocial.core.providers.UsernamePasswordProvider.UsernamePassword
@main("Login") {
<div class="page-header">
<h1>Login</h1>
</div>
@errorMsg.map { msg =>
<div class="alert alert-error">
@Messages(msg)
</div>
}
@request.flash.get("success").map { msg =>
<div class="alert alert-info">
@msg
</div>
}
@request.flash.get("error").map { msg =>
<div class="alert alert-error">
@msg
</div>
}
@defining( Registry.providers.all.values.filter( _.id != UsernamePassword) ) { externalProviders =>
@if( externalProviders.size > 0 ) {
<div class="clearfix">
<p>@Messages("securesocial.login.instructions")</p>
<p>
@for(p <- externalProviders) {
@securesocial.views.html.provider(p.id)
}
</p>
</div>
}
@Registry.providers.get(UsernamePassword).map { up =>
<div class="clearfix">
@if( externalProviders.size > 0 ) {
<p>@Messages("securesocial.login.useEmailAndPassword")</p>
} else {
<p>@Messages("securesocial.login.useEmailAndPasswordOnly")</p>
}
@securesocial.views.html.provider("userpass", Some(loginForm))
</div>
}
}
}
notAuthorizedPage.scala.html
app/views/myNotAuthorizedPage.scala.html
@()(implicit request: RequestHeader, lang: Lang)
@securesocial.views.html.main(Messages("securesocial.notAuthorized.title")) {
<div class="page-header">
<h1>@Messages("securesocial.notAuthorized.title")</h1>
</div>
<div class="alert alert-error">
@Messages("securesocial.notAuthorized.message")
</div>
}
passwordChange.scala.html
myPasswordChange.scala.html
@(passwordChangeForm:Form[securesocial.controllers.PasswordChange.ChangeInfo])(implicit request: RequestHeader, lang: Lang)
@import securesocial.core.providers.UsernamePasswordProvider
@import securesocial.core.IdentityProvider
@import helper._
@implicitFieldConstructor = @{ FieldConstructor(securesocial.views.html.inputFieldConstructor.f) }
@securesocial.views.html.main( Messages("securesocial.passwordChange.title") ) {
<div class="page-header">
<h1>@Messages("securesocial.passwordChange.title")</h1>
</div>
@request.flash.get("error").map { msg =>
<div class="alert alert-error">
@msg
</div>
}
@if( request.flash.get("success").isDefined ) {
<div class="alert alert-info">
@request.flash.get("success").get
</div>
<div class="form-actions">
<a class="btn" href="@securesocial.controllers.ProviderController.toUrl(request.session)">@Messages("securesocial.passwordChange.okButton")</a>
</div>
} else {
<form action="@securesocial.core.providers.utils.RoutesHelper.handlePasswordChange.absoluteURL(IdentityProvider.sslEnabled)"
class="form-horizontal"
autocomplete= "off"
method="POST"
>
<fieldset>
@helper.inputPassword(
passwordChangeForm("currentPassword"),
'_label -> Messages("securesocial.passwordChange.currentPassword"),
'class -> "input-xlarge"
)
@helper.inputPassword(
passwordChangeForm("newPassword.password1"),
'_label -> Messages("securesocial.passwordChange.newPassword1"),
'class -> "input-xlarge"
)
@helper.inputPassword(
passwordChangeForm("newPassword.password2"),
'_label -> Messages("securesocial.passwordChange.newPassword2"),
'_error -> passwordChangeForm.error("newPassword"),
'class -> "input-xlarge"
)
<div class="form-actions">
<button type="submit" class="btn btn-primary">@Messages("securesocial.passwordChange.changeButton")</button>
<a class="btn" href="@securesocial.controllers.ProviderController.toUrl(request.session)">@Messages("securesocial.signup.cancel")</a>
</div>
</fieldset>
</form>
}
}
passwordChangedNotice.scala.html
app/views/myPasswordChangedNotice.scala.html
@(user: securesocial.core.Identity)(implicit request: RequestHeader)
@import securesocial.core.IdentityProvider
<html>
<body>
<p>Hello @user.firstName,</p>
<p>Your password was updated. Please log in using your new password by clicking
<a href="@securesocial.core.providers.utils.RoutesHelper.login.absoluteURL(IdentityProvider.sslEnabled)">here</a></p>
</body>
</html>
passwordResetEmail.scala.html
app/views/myPasswordResetEmail.scala.html
@(user: securesocial.core.Identity, token: String)(implicit request: RequestHeader)
@import securesocial.core.IdentityProvider
<html>
<body>
<p>Hello @user.firstName,</p>
<p>Please follow this
<a href="@securesocial.core.providers.utils.RoutesHelper.resetPassword(token).absoluteURL(IdentityProvider.sslEnabled)">
link</a> to reset your password.
</p>
</body>
</html>
resetPasswordPage.scala.html
app/views/myResetPasswordPage.scala.html
@(resetForm:Form[(String, String)], token: String)(implicit request: RequestHeader)
@import helper._
@import securesocial.core.IdentityProvider
@implicitFieldConstructor = @{ FieldConstructor(securesocial.views.html.inputFieldConstructor.f) }
@securesocial.views.html.main( Messages("securesocial.password.title") ) {
<div class="page-header">
<h1>@Messages("securesocial.password.title")</h1>
</div>
@request.flash.get("error").map { msg =>
<div class="alert alert-error">
@Messages(msg)
</div>
}
<form action="@securesocial.core.providers.utils.RoutesHelper.handleResetPassword(token).absoluteURL(IdentityProvider.sslEnabled)"
class="form-horizontal"
autocomplete="off"
method="POST"
>
<fieldset>
@helper.inputPassword(
resetForm("password.password1"),
'_label -> Messages("securesocial.signup.password1"),
'class -> "input-xlarge"
)
@helper.inputPassword(
resetForm("password.password2"),
'_label -> Messages("securesocial.signup.password2"),
'_error -> resetForm.error("password"),
'class -> "input-xlarge"
)
<div class="form-actions">
<button type="submit" class="btn btn-primary">@Messages("securesocial.password.reset")</button>
</div>
</fieldset>
</form>
}
signUp.scala.html
app/views/mySignUp.scala.html
@(signUpForm:Form[securesocial.controllers.Registration.RegistrationInfo], mailToken: String)(implicit request: RequestHeader, lang: Lang)
@import securesocial.core.providers.UsernamePasswordProvider
@import securesocial.core.IdentityProvider
@import helper._
@implicitFieldConstructor = @{ FieldConstructor(securesocial.views.html.inputFieldConstructor.f) }
@securesocial.views.html.main( Messages("securesocial.signup.title") ) {
<div class="page-header">
<h1>SignUp</h1>
</div>
@request.flash.get("error").map { msg =>
<div class="alert alert-error">
@msg
</div>
}
<form action="@securesocial.controllers.routes.Registration.handleSignUp(mailToken).absoluteURL(IdentityProvider.sslEnabled)"
class="form-horizontal"
autocomplete= "off"
method="POST"
>
<fieldset>
@if( UsernamePasswordProvider.withUserNameSupport ) {
@helper.inputText(
signUpForm("userName"),
'_label -> Messages("securesocial.signup.username"),
'class -> "input-xlarge"
)
}
@helper.inputText(
signUpForm("firstName"),
'_label -> Messages("securesocial.signup.firstName"),
'class -> "input-xlarge"
)
@helper.inputText(
signUpForm("lastName"),
'_label -> Messages("securesocial.signup.lastName"),
'class -> "input-xlarge"
)
@helper.inputPassword(
signUpForm("password.password1"),
'_label -> Messages("securesocial.signup.password1"),
'class -> "input-xlarge"
)
@helper.inputPassword(
signUpForm("password.password2"),
'_label -> Messages("securesocial.signup.password2"),
'_error -> signUpForm.error("password"),
'class -> "input-xlarge"
)
<div class="form-actions">
<button type="submit" class="btn btn-primary">@Messages("securesocial.signup.createAccount")</button>
<a class="btn" href="@views.html.mylogin">@Messages("securesocial.signup.cancel")</a>
</div>
</fieldset>
</form>
}
signUpEmail.scala.html
mySignUpEmail.scala.html
@(token: String)(implicit request: RequestHeader)
@import securesocial.core.IdentityProvider
<html>
<body>
<p>Hello,</p>
<p>Please follow this
<a href="@securesocial.core.providers.utils.RoutesHelper.signUp(token).absoluteURL(IdentityProvider.sslEnabled)">link</a> to complete your registration.
</p>
</body>
</html>
startResetPassword.scala.html
app/views/myStartResetPassword.scala.html
@(startForm:Form[String])(implicit request: RequestHeader)
@import helper._
@import securesocial.core.IdentityProvider
@implicitFieldConstructor = @{ FieldConstructor(securesocial.views.html.inputFieldConstructor.f) }
@securesocial.views.html.main( Messages("securesocial.password.title") ) {
<div class="page-header">
<h1>@Messages("securesocial.password.title")</h1>
</div>
@request.flash.get("error").map { msg =>
<div class="alert alert-error">
@Messages(msg)
</div>
}
<form action="@securesocial.core.providers.utils.RoutesHelper.handleStartResetPassword().absoluteURL(IdentityProvider.sslEnabled)"
class="form-horizontal"
autocomplete="off"
method="POST"
>
<fieldset>
@helper.inputText(
startForm("email"),
'_label -> Messages("securesocial.signup.email1"),
'class -> "input-xlarge"
)
<div class="form-actions">
<button type="submit" class="btn btn-primary">@Messages("securesocial.password.reset")</button>
<a class="btn" href="@securesocial.core.providers.utils.RoutesHelper.login()">@Messages("securesocial.signup.cancel")</a>
</div>
</fieldset>
</form>
}
startSignUp.scala.html
app/views/myStartSignUp.scala.html
@(startForm:Form[String])(implicit request: RequestHeader, lang: Lang)
@import helper._
@implicitFieldConstructor = @{ FieldConstructor(securesocial.views.html.inputFieldConstructor.f) }
@views.html.myMain( "MyApp" ) {
<div class="page-header">
<h1>Sign Up</h1>
</div>
@request.flash.get("error").map { msg =>
<div class="alert alert-error">
@Messages(msg)
</div>
}
<form action="@views.html.mySignUp"
class="form-horizontal"
autocomplete="off" method="post"
>
<fieldset>
@helper.inputText(
startForm("email"),
'_label -> Messages("securesocial.signup.email1"),
'class -> "input-xlarge"
)
<div class="form-actions">
<button type="submit" class="btn btn-primary">@Messages("securesocial.signup.createAccount")</button>
<a class="btn" href="@views.html.mylogin">@Messages("securesocial.signup.cancel")</a>
</div>
</fieldset>
</form>
}
unknownEmailNotice.scala.html
app/views/myUnknownEmailNotice.scala.html
@(implicit request: RequestHeader)
<html>
<body>
<p>Hello,</p>
<p>We received a request to reset a password in our system. The attempt has failed because we do not have
a registered account with this email address. It could be that you logged in using an external account such as Twitter
or Facebook.</p>
<p>
If you never created an account with us ignore this email, otherwise if you think you have an account with us contact
tech support for further assistance.
</p>
</body>
</html>
welcomeEmail.scala.html
app/views/myWelcomeEmail.scala.html
@(user: securesocial.core.Identity)(implicit request: RequestHeader)
@import securesocial.core.IdentityProvider
<html>
<body>
<p>Welcome @user.firstName,</p>
<p>
Your new account is ready.
Click <a href="@securesocial.core.providers.utils.RoutesHelper.login.absoluteURL(IdentityProvider.sslEnabled)">here</a> to log in</p>
</body>
</html>
カスタマイズ
このままでは、元のSecureSocialデフォルトの表示とほぼ同じですが、上記のコードの中身を自分の好きにカスタマイズしていけばいいでしょう。