Skip to content

Commit bcc8402

Browse files
committed
Updated to play 2.6.x, updated sbt version, added silhouette support
Thanks to: https://github.com/mohiva/play-silhouette-angular-seed/ for the silhouette example And thanks to: ochrons#76 For some of the version updates needed
1 parent 900e66b commit bcc8402

19 files changed

Lines changed: 676 additions & 18 deletions

build.sbt

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ lazy val client: Project = (project in file("client"))
3030
scalacOptions ++= elideOptions.value,
3131
jsDependencies ++= Settings.jsDependencies.value,
3232
// RuntimeDOM is needed for tests
33-
jsDependencies += RuntimeDOM % "test",
33+
jsEnv in Test := new org.scalajs.jsenv.jsdomnodejs.JSDOMNodeJSEnv,
3434
// yes, we want to package JS dependencies
3535
skip in packageJSDependencies := false,
3636
// use Scala.js provided launcher code to start the client app
@@ -53,6 +53,9 @@ lazy val server = (project in file("server"))
5353
scalaVersion := Settings.versions.scala,
5454
scalacOptions ++= Settings.scalacOptions,
5555
libraryDependencies ++= Settings.jvmDependencies.value,
56+
libraryDependencies += guice,
57+
libraryDependencies += ehcache,
58+
libraryDependencies += cacheApi,
5659
commands += ReleaseCmd,
5760
// triggers scalaJSPipeline when using compile or continuous compilation
5861
compile in Compile := ((compile in Compile) dependsOn scalaJSPipeline).value,
@@ -82,5 +85,4 @@ lazy val ReleaseCmd = Command.command("release") {
8285

8386
// lazy val root = (project in file(".")).aggregate(client, server)
8487

85-
// loads the Play server project at sbt startup
86-
onLoad in Global := (Command.process("project server", _: State)) compose (onLoad in Global).value
88+
onLoad in Global := ("project server" :: (_: State)) compose (onLoad in Global).value

project/Settings.scala

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ object Settings {
2525
val scala = "2.11.11"
2626
val scalaDom = "0.9.3"
2727
val scalajsReact = "1.1.0"
28+
val silhouette = "5.0.0"
2829
val scalaCSS = "0.5.3"
2930
val log4js = "1.4.10"
3031
val autowire = "0.2.6"
@@ -37,7 +38,7 @@ object Settings {
3738
val bootstrap = "3.3.6"
3839
val chartjs = "2.1.3"
3940

40-
val scalajsScripts = "1.0.0"
41+
val scalajsScripts = "1.1.1"
4142
}
4243

4344
/**
@@ -54,7 +55,13 @@ object Settings {
5455
"com.vmunier" %% "scalajs-scripts" % versions.scalajsScripts,
5556
"org.webjars" % "font-awesome" % "4.3.0-1" % Provided,
5657
"org.webjars" % "bootstrap" % versions.bootstrap % Provided,
57-
"com.lihaoyi" %% "utest" % versions.uTest % Test
58+
"com.lihaoyi" %% "utest" % versions.uTest % Test,
59+
"com.mohiva" %% "play-silhouette" % versions.silhouette,
60+
"com.mohiva" %% "play-silhouette-password-bcrypt" % versions.silhouette,
61+
"com.mohiva" %% "play-silhouette-crypto-jca" % versions.silhouette,
62+
"com.mohiva" %% "play-silhouette-persistence" % versions.silhouette,
63+
"com.mohiva" %% "play-silhouette-testkit" % versions.silhouette % Test
64+
5865
))
5966

6067
/** Dependencies only used by the JS project (note the use of %%% instead of %%) */

project/build.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
sbt.version=0.13.15
1+
sbt.version=1.1.1

project/plugins.sbt

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
// repository for Typesafe plugins
22
resolvers += "Typesafe Releases" at "http://repo.typesafe.com/typesafe/releases/"
33

4-
addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.18")
4+
addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.22")
55

6-
addSbtPlugin("com.typesafe.sbt" % "sbt-less" % "1.0.6")
6+
addSbtPlugin("com.typesafe.sbt" % "sbt-less" % "1.1.2")
77

8-
addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.0.0")
8+
addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.3.3")
99

10-
addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.5.15")
10+
addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.12")
1111

12-
addSbtPlugin("com.vmunier" % "sbt-web-scalajs" % "1.0.5")
12+
addSbtPlugin("com.vmunier" % "sbt-web-scalajs" % "1.0.6")
1313

14-
addSbtPlugin("com.typesafe.sbt" % "sbt-digest" % "1.1.0")
14+
addSbtPlugin("com.typesafe.sbt" % "sbt-digest" % "1.1.4")
1515

16-
addSbtPlugin("com.typesafe.sbt" % "sbt-gzip" % "1.0.0")
16+
addSbtPlugin("com.typesafe.sbt" % "sbt-gzip" % "1.0.2")

server/src/main/resources/application.conf

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,22 @@
44
application.cdn = ""
55
application.cdn=${?APPLICATION_CDN}
66

7+
play.modules.enabled += "modules.SilhouetteModule"
8+
79
spatutorial {
10+
11+
play.filters{
12+
13+
headers {
14+
contentSecurityPolicy = "default-src 'self' cdnjs.cloudflare.com ;"
15+
contentSecurityPolicy = ${play.filters.headers.contentSecurityPolicy}" img-src 'self' ;"
16+
contentSecurityPolicy = ${play.filters.headers.contentSecurityPolicy}" style-src 'self' 'unsafe-inline' maxcdn.bootstrapcdn.com cdn.jsdelivr.net fonts.googleapis.com;"
17+
contentSecurityPolicy = ${play.filters.headers.contentSecurityPolicy}" font-src 'self' fonts.googleapis.com maxcdn.bootstrapcdn.com;"
18+
}
19+
20+
csrf.cookie.name="PLAY_CSRF_TOKEN"
21+
}
22+
823
}
24+
25+
include "silhouette.conf"

server/src/main/resources/routes

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,10 @@ GET /assets/*file controllers.Assets.versioned(path="/pu
1212
# Autowire calls
1313
POST /api/*path controllers.Application.autowireApi(path: String)
1414

15+
# Authentication routes
16+
GET /signOut controllers.AuthenticationController.signOut
17+
POST /signIn controllers.AuthenticationController.signIn
18+
POST /signUp controllers.AuthenticationController.signUp
19+
1520
# Logging
1621
POST /logging controllers.Application.logging
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
2+
silhouette {
3+
# JWT authenticator settings
4+
authenticator.headerName="X-Auth-Token"
5+
authenticator.issuerClaim="play-angular-silhouette"
6+
authenticator.encryptSubject=true
7+
authenticator.authenticatorExpiry=12 hours
8+
authenticator.sharedSecret="changeme"
9+
10+
authenticator.rememberMe.authenticatorExpiry=30 days
11+
authenticator.rememberMe.authenticatorIdleTimeout=5 days
12+
13+
authenticator.crypter.key = "[changeme]"
14+
}

server/src/main/scala/controllers/Application.scala

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,27 +3,37 @@ package controllers
33
import java.nio.ByteBuffer
44

55
import boopickle.Default._
6-
import com.google.inject.Inject
6+
import javax.inject.{Inject, Singleton}
7+
8+
import com.mohiva.play.silhouette.api.SilhouetteProvider
79
import play.api.{Configuration, Environment}
810
import play.api.mvc._
911
import services.ApiService
1012
import spatutorial.shared.Api
13+
import utils.auth.DefaultEnv
1114

12-
import scala.concurrent.ExecutionContext.Implicits.global
15+
import scala.concurrent.ExecutionContext
1316

1417
object Router extends autowire.Server[ByteBuffer, Pickler, Pickler] {
1518
override def read[R: Pickler](p: ByteBuffer) = Unpickle[R].fromBytes(p)
1619
override def write[R: Pickler](r: R) = Pickle.intoBytes(r)
1720
}
1821

19-
class Application @Inject() (implicit val config: Configuration, env: Environment) extends Controller {
22+
@Singleton
23+
class Application @Inject()
24+
(
25+
cc: ControllerComponents,
26+
silhouetteProvider: SilhouetteProvider[DefaultEnv]
27+
)
28+
(implicit val config: Configuration, env: Environment, ec: ExecutionContext )
29+
extends AbstractController(cc) {
2030
val apiService = new ApiService()
2131

2232
def index = Action {
2333
Ok(views.html.index("SPA tutorial"))
2434
}
2535

26-
def autowireApi(path: String) = Action.async(parse.raw) {
36+
def autowireApi(path: String) = silhouetteProvider.SecuredAction.async(parse.raw) {
2737
implicit request =>
2838
println(s"Request path: $path")
2939

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
package controllers
2+
3+
import java.util.UUID
4+
import javax.inject.Inject
5+
6+
import com.mohiva.play.silhouette.api.util.{Clock, Credentials, PasswordHasher}
7+
import play.api.mvc._
8+
import com.mohiva.play.silhouette.api._
9+
import com.mohiva.play.silhouette.impl.providers.CredentialsProvider
10+
import play.api.mvc.ControllerComponents
11+
import com.mohiva.play.silhouette.api.exceptions.ProviderException
12+
import com.mohiva.play.silhouette.api.repositories.AuthInfoRepository
13+
import com.mohiva.play.silhouette.impl.exceptions.IdentityNotFoundException
14+
import forms.{SignInForm, SignUpForm}
15+
import models.User
16+
import models.services.UserService
17+
import play.api.Configuration
18+
import play.api.libs.json._
19+
import play.api.libs.functional.syntax._
20+
import utils.auth.DefaultEnv
21+
22+
import scala.concurrent.{ExecutionContext, Future}
23+
24+
25+
class AuthenticationController @Inject() (
26+
cc: ControllerComponents,
27+
silhouette: SilhouetteProvider[DefaultEnv],
28+
userService: UserService,
29+
authInfoRepository: AuthInfoRepository,
30+
passwordHasher: PasswordHasher,
31+
credentialsProvider: CredentialsProvider,
32+
configuration: Configuration,
33+
clock: Clock
34+
)( implicit ec: ExecutionContext )
35+
extends AbstractController(cc) {
36+
37+
38+
/**
39+
* Converts the JSON into a `SignInForm.Data` object.
40+
*/
41+
implicit val dataReads = (
42+
(__ \ 'email).read[String] and
43+
(__ \ 'password).read[String] and
44+
(__ \ 'rememberMe).read[Boolean]
45+
)(SignInForm.Data.apply _)
46+
47+
48+
/**
49+
* Handles the submitted JSON data.
50+
*
51+
* @return The result to display.
52+
*/
53+
def signIn = Action.async(parse.json) { implicit request =>
54+
request.body.validate[SignInForm.Data].map { data =>
55+
credentialsProvider.authenticate(Credentials(data.email, data.password)).flatMap { loginInfo =>
56+
userService.retrieve(loginInfo).flatMap {
57+
case Some(user) => silhouette.env.authenticatorService.create(loginInfo).flatMap { authenticator =>
58+
silhouette.env.authenticatorService.init(authenticator).map { token =>
59+
Ok(Json.obj("token" -> token))
60+
}
61+
}
62+
case None => Future.failed(new IdentityNotFoundException("Couldn't find user"))
63+
}
64+
}.recover {
65+
case e: ProviderException =>
66+
Unauthorized(Json.obj("message" -> "invalid credentials"))
67+
}
68+
}.recoverTotal {
69+
case error =>
70+
Future.successful(Unauthorized(Json.obj("message" -> "invalid credentials")))
71+
}
72+
}
73+
74+
/**
75+
* Handles the submitted JSON data.
76+
*
77+
* @return The result to display.
78+
*/
79+
def signUp = Action.async(parse.json) { implicit request =>
80+
request.body.validate[SignUpForm.Data].map { data =>
81+
val loginInfo = LoginInfo(CredentialsProvider.ID, data.email)
82+
userService.retrieve(loginInfo).flatMap {
83+
case Some(user) =>
84+
Future.successful(BadRequest(Json.obj("message" -> "user exists")))
85+
case None =>
86+
val authInfo = passwordHasher.hash(data.password)
87+
val user = User(
88+
userID = UUID.randomUUID(),
89+
loginInfo = loginInfo,
90+
firstName = Some(data.firstName),
91+
lastName = Some(data.lastName),
92+
fullName = Some(data.firstName + " " + data.lastName),
93+
email = Some(data.email),
94+
avatarURL = None
95+
)
96+
for {
97+
user <- userService.save(user)
98+
authInfo <- authInfoRepository.add(loginInfo, authInfo)
99+
authenticator <- silhouette.env.authenticatorService.create(loginInfo)
100+
token <- silhouette.env.authenticatorService.init(authenticator)
101+
} yield {
102+
Ok(Json.obj("token" -> token))
103+
}
104+
}
105+
}.recoverTotal {
106+
case error =>
107+
Future.successful(Unauthorized(Json.obj("message" -> "invalid.data")))
108+
}
109+
}
110+
111+
/**
112+
* Manages the sign out action.
113+
*/
114+
def signOut = silhouette.SecuredAction.async { implicit request =>
115+
silhouette.env.authenticatorService.discard(request.authenticator, Ok)
116+
}
117+
118+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package forms
2+
3+
import play.api.data.Form
4+
import play.api.data.Forms._
5+
6+
/**
7+
* The form which handles the submission of the credentials.
8+
*/
9+
object SignInForm {
10+
11+
/**
12+
* A play framework form.
13+
*/
14+
val form = Form(
15+
mapping(
16+
"email" -> email,
17+
"password" -> nonEmptyText,
18+
"rememberMe" -> boolean
19+
)(Data.apply)(Data.unapply)
20+
)
21+
22+
/**
23+
* The form data.
24+
*
25+
* @param email The email of the user.
26+
* @param password The password of the user.
27+
* @param rememberMe Indicates if the user should stay logged in on the next visit.
28+
*/
29+
case class Data(
30+
email: String,
31+
password: String,
32+
rememberMe: Boolean)
33+
}

0 commit comments

Comments
 (0)