[Scala]SecureSocialで、すべてのビューを独自のビューで置き換えるメモ[Play]


## はじめに

[Scala+Play+SecureSocialでTwitterにログイン認証してユーザ名とアバターを表示するメモ](http://qiita.com/JunSuzukiJapan@github/items/ff0412617b0e99a6166f)の続きですが、この記事単独でも使えるかと思います。

なお、すべてのビューを表示して確認していないので、おかしなところもあるかもしれません。

## 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を定義して各ビューで使い回します。

```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

ルートページ。
（ログインしていないときには、ログインページに飛ばされる。）


```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

```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

```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

```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

```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

```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

```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

```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

```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

```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

```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

```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

```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

```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デフォルトの表示とほぼ同じですが、上記のコードの中身を自分の好きにカスタマイズしていけばいいでしょう。