I’m trying to authenticate users through remote authentication service. I’ve written helper method for sending message to service and waiting for result:
def authenticateAwait(email: String,
password: String
): Either[String, Option[User]] = {
try {
val future = authenticate(email, password)
Right(Await.result(future, timeout.duration))
} catch {
case _ ⇒ Left("Unable to connect to authentication server")
}
}
It returns Left[String] with an error description if message can’t be sent, or there is no response. If service response received, it returns Right[Option[User]]. Service responds with Option[User] depending on authentication result.
To perform actual authentication I’ve created form with a couple of validators, here it is:
val loginForm = Form(
tuple(
"email" → email,
"password" → nonEmptyText
) verifying ("Invalid email or password", result => result match {
case (email, password) ⇒
User.authenticateAwait(email, password) match {
case Left(_) ⇒ true
case Right(optUser) ⇒ optUser.isDefined
}
}) verifying ("Unable to connect to authentication server", result => result match {
case (email, password) ⇒
User.authenticateAwait(email, password) match {
case Left(_) ⇒ false
case Right(optUser) ⇒ true
}
})
)
One thing worries me about this code, it calls authenticateAwait twice. It means exactly two messages will be sent per single validation. What I actually need, is to call authenticateAwait once, store result and perform various validations on it. It seems there is no simple solution.
To perform authentication, access to the form fields required, it means the form should be bound and then validated, but there is no way to attach errors to the existing form(am I wrong?).
Errors can be attached to the form only during its creation, therefore I should perform authentication in the validators, but then aforementioned problem occurs.
The temporary solution I came with is to define a method and a var inside it.
def loginForm = {
var authResponse: Either[String, Option[commons.User]] = null
Form(
tuple(
"email" → email,
"password" → nonEmptyText
) verifying ("Invalid email or password", result ⇒ result match {
case (email, password) ⇒
authResponse = User.authenticateAwait(email, password)
authResponse match {
case Left(_) ⇒ true
case Right(optUser) ⇒ optUser.isDefined
}
}) verifying ("Unable to connect to authentication server", result ⇒ result match {
case (email, password) ⇒
authResponse match {
case Left(_) ⇒ false
case Right(optUser) ⇒ true
}
})
)
}
This is clearly a hack. Are there any better solutions?
Update:
In my opinion, form should only sanitize input, but authentication should be performed later outside the form.
The problem is that errors are sent to the view as a part of the Form and it is impossible to attach errors to the existing form. There are no simple way of creating new form with the errors too.
What you have to understand is that Form is immutable. But there is a easy to use utility method to construct a new form with errors added: