2013-06-14 08:35:34 +00:00
/* *
2016-10-26 11:03:14 +00:00
Open Bank Project - API
2016-11-06 10:48:59 +00:00
Copyright ( C ) 2011 - 2016 , TESOBE Ltd
2016-10-26 11:03:14 +00:00
This program is free software : you can redistribute it and / or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation , either version 3 of the License , or
( at your option ) any later version .
This program is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU Affero General Public License for more details .
You should have received a copy of the GNU Affero General Public License
along with this program . If not , see < http : //www.gnu.org/licenses/>.
Email : contact @ tesobe.com
2016-11-06 10:48:59 +00:00
TESOBE Ltd
2016-10-26 11:03:14 +00:00
Osloerstrasse 16 / 17
Berlin 13359 , Germany
This product includes software developed at
TESOBE ( http : //www.tesobe.com/)
by
Simon Redfern : simon AT tesobe DOT com
Stefan Bethge : stefan AT tesobe DOT com
Everett Sochowski : everett AT tesobe DOT com
Ayoub Benali : ayoub AT tesobe DOT com
*/
2013-06-14 08:35:34 +00:00
package code.model.dataAccess
2016-09-02 17:53:39 +00:00
import java.util.UUID
2018-01-31 15:03:46 +00:00
import code.api.util.ErrorMessages._
2017-07-13 13:19:12 +00:00
import code.accountholder.AccountHolders
2017-11-30 12:16:57 +00:00
import code.api.GatewayLogin.gateway
2017-07-17 08:42:22 +00:00
import code.api.util.APIUtil. { hasAnOAuthHeader , isValidStrongPassword , _ }
2016-12-30 23:12:12 +00:00
import code.api.util. { APIUtil , ErrorMessages }
2017-11-30 12:16:57 +00:00
import code.api. { DirectLogin , GatewayLogin , OAuthHandshake }
import code.bankconnectors. { Connector , InboundAccountCommon , InboundUser }
2013-06-14 08:35:34 +00:00
import net.liftweb.common._
2017-01-22 05:54:28 +00:00
import net.liftweb.http._
2016-04-11 08:44:12 +00:00
import net.liftweb.mapper._
import net.liftweb.util.Mailer. { BCC , From , Subject , To }
import net.liftweb.util._
2017-08-04 07:00:59 +00:00
import net.liftweb.util.Bindable
2013-06-14 08:35:34 +00:00
2016-04-11 08:44:12 +00:00
import scala.xml. { NodeSeq , Text }
2016-12-31 15:16:24 +00:00
import code.loginattempts.LoginAttempt
2017-07-13 13:19:12 +00:00
import code.model._
2017-02-22 16:55:50 +00:00
import code.users.Users
2017-02-20 08:28:26 +00:00
import code.util.Helper
2017-07-13 13:19:12 +00:00
import code.views.Views
2016-12-31 15:16:24 +00:00
2017-11-30 12:16:57 +00:00
import scala.collection.immutable.List
2016-12-31 15:16:24 +00:00
2013-06-14 08:35:34 +00:00
/* *
* An O - R mapped "User" class that includes first name , last name , password
2016-05-25 00:16:09 +00:00
*
*
2017-03-21 14:48:55 +00:00
* // TODO Document the difference between this and AuthUser / ResourceUser
2017-05-31 12:30:33 +00:00
*
* 1 AuthUser : is used for authentication , only for webpage Login in stuff
* 1 ) It is MegaProtoUser , has lots of methods for validation username , password , email . . . .
* Such as lost password , reset password . . . . .
* Lift have some helper methods to make these things easily .
*
*
*
* 2 ResourceUser : is only a normal LongKeyedMapper
* 1 ) All the accounts , transactions , roles , views , accountHolders , customers . . . should be linked to ResourceUser . userId_ field .
* 2 ) The consumer keys , tokens are also belong ResourceUser
*
*
* 3 RelationShips :
* 1 ) When `Sign up` new user --> create AuthUser --> call AuthUser . save ( ) --> create ResourceUser user .
* They share the same username and email .
* 2 ) AuthUser `user` field as the Foreign Key to link to Resource User .
* one AuthUser <---> one ResourceUser
*
2016-05-25 00:16:09 +00:00
*
2013-06-14 08:35:34 +00:00
*/
2017-02-09 13:34:42 +00:00
class AuthUser extends MegaProtoUser [ AuthUser ] with Logger {
def getSingleton = AuthUser // what's the "meta" server
2014-01-07 09:51:48 +00:00
2017-02-09 13:34:42 +00:00
object user extends MappedLongForeignKey ( this , ResourceUser )
2014-01-07 09:51:48 +00:00
2016-09-02 17:53:39 +00:00
/* *
* The username field for the User .
*/
lazy val username : userName = new userName ( )
class userName extends MappedString ( this , 64 ) {
override def displayName = S . ? ( "username" )
override def dbIndexed_? = true
override def validations = valUnique ( S . ? ( "unique.username" ) ) _ : : super.validations
override val fieldId = Some ( Text ( "txtUsername" ) )
}
2017-03-09 10:51:09 +00:00
override lazy val password = new MyPasswordNew
class MyPasswordNew extends MappedPassword ( this ) {
override def displayName = fieldOwner . passwordDisplayName
private var passwordValue = ""
private var invalidPw = false
private var invalidMsg = ""
2017-04-28 07:04:50 +00:00
// TODO Remove double negative and abreviation.
// TODO “invalidPw” = false -> “strongPassword = true” etc.
2017-03-09 10:51:09 +00:00
override def setFromAny ( f : Any ) : String = {
f match {
case a : Array [ String ] if ( a . length == 2 && a ( 0 ) == a ( 1 ) ) => {
passwordValue = a ( 0 ) . toString ;
if ( isValidStrongPassword ( passwordValue ) )
invalidPw = false
else {
invalidPw = true
invalidMsg = S . ? ( ErrorMessages . InvalidStrongPasswordFormat )
}
this . set ( a ( 0 ) )
}
case l : List [ String ] if ( l . length == 2 && l . head == l ( 1 ) ) => {
passwordValue = l ( 0 ) . toString ;
if ( isValidStrongPassword ( passwordValue ) )
invalidPw = false
else {
invalidPw = true
invalidMsg = S . ? ( ErrorMessages . InvalidStrongPasswordFormat )
}
this . set ( l . head )
}
case _ => {
invalidPw = true ;
invalidMsg = S . ? ( "passwords.do.not.match" )
}
}
get
}
override def validate : List [ FieldError ] = {
if ( super . validate . nonEmpty ) super . validate
else if ( ! invalidPw && password . get != "*" ) Nil
else if ( invalidPw ) List ( FieldError ( this , Text ( invalidMsg ) ) )
else List ( FieldError ( this , Text ( S . ? ( "password.must.be.set" ) ) ) )
}
}
2016-09-02 17:53:39 +00:00
2016-01-15 17:37:06 +00:00
/* *
* The provider field for the User .
*/
lazy val provider : userProvider = new userProvider ( )
class userProvider extends MappedString ( this , 64 ) {
override def displayName = S . ? ( "provider" )
override val fieldId = Some ( Text ( "txtProvider" ) )
}
2014-12-10 16:58:17 +00:00
2016-01-15 17:37:06 +00:00
def getProvider ( ) = {
if ( provider . get == null ) {
2018-03-06 15:22:08 +00:00
APIUtil . getPropsValue ( "hostname" , "" )
} else if ( provider . get == "" || provider . get == APIUtil . getPropsValue ( "hostname" , "" ) ) {
APIUtil . getPropsValue ( "hostname" , "" )
2016-01-15 17:37:06 +00:00
} else {
provider . get
}
}
2017-02-09 13:34:42 +00:00
def createUnsavedResourceUser ( ) : ResourceUser = {
2018-01-31 15:03:46 +00:00
val user = Users . users . vend . createUnsavedResourceUser ( getProvider ( ) , Some ( username . get ) , Some ( username . get ) , Some ( email . get ) , None ) . openOrThrowException ( attemptedToOpenAnEmptyBox )
2017-02-20 13:13:18 +00:00
user
2016-09-02 17:53:39 +00:00
}
2017-02-09 13:34:42 +00:00
def getResourceUsersByEmail ( userEmail : String ) : List [ ResourceUser ] = {
2017-02-22 16:55:50 +00:00
Users . users . vend . getUserByEmail ( userEmail ) match {
case Full ( userList ) => userList
case Empty => List ( )
}
2014-12-10 16:58:17 +00:00
}
2017-02-09 13:34:42 +00:00
def getResourceUsers ( ) : List [ ResourceUser ] = {
2017-02-22 16:55:50 +00:00
Users . users . vend . getAllUsers match {
case Full ( userList ) => userList
case Empty => List ( )
}
2016-11-17 14:50:51 +00:00
}
2017-02-09 13:34:42 +00:00
def getResourceUserByUsername ( username : String ) : Box [ ResourceUser ] = {
2017-02-22 16:55:50 +00:00
Users . users . vend . getUserByUserName ( username )
2016-06-20 09:08:37 +00:00
}
2014-12-10 16:58:17 +00:00
override def save ( ) : Boolean = {
if ( ! ( user defined_? ) ) {
2017-03-21 14:48:55 +00:00
info ( "user reference is null. We will create a ResourceUser" )
2017-02-09 13:34:42 +00:00
val resourceUser = createUnsavedResourceUser ( )
2017-02-22 16:55:50 +00:00
val savedUser = Users . users . vend . saveResourceUser ( resourceUser )
2017-02-21 10:59:27 +00:00
user ( savedUser ) //is this saving resourceUser into a user field?
2014-01-07 09:51:48 +00:00
}
else {
2017-03-21 14:48:55 +00:00
info ( "user reference is not null. Trying to update the ResourceUser" )
2017-02-22 16:55:50 +00:00
Users . users . vend . getResourceUserByResourceUserId ( user . get ) . map { u => {
2014-01-07 15:25:15 +00:00
info ( "API User found " )
2017-08-04 07:00:59 +00:00
u . name_ ( username . get )
. email ( email . get )
. providerId ( username . get )
2014-01-07 09:51:48 +00:00
. save
}
}
}
super . save ( )
}
override def delete_! ( ) : Boolean = {
2017-08-04 07:00:59 +00:00
user . obj . map ( u => Users . users . vend . deleteResourceUser ( u . id . get ) )
2014-01-07 09:51:48 +00:00
super . delete_!
}
2016-08-05 14:05:04 +00:00
// Regex to validate an email address as per W3C recommendations: https://www.w3.org/TR/html5/forms.html#valid-e-mail-address
private val emailRegex = """^[a-zA-Z0-9\.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$""" . r
def isEmailValid ( e : String ) : Boolean = e match {
case null => false
case e if e . trim . isEmpty => false
case e if emailRegex . findFirstMatchIn ( e ) . isDefined => true
case _ => false
}
// Override the validate method of MappedEmail class
// There's no way to override the default emailPattern from MappedEmail object
override lazy val email = new MyEmail ( this , 48 ) {
2016-09-02 17:53:39 +00:00
override def validations = super . validations
override def dbIndexed_? = false
2016-08-05 14:05:04 +00:00
override def validate = if ( isEmailValid ( i_is_! ) ) Nil else List ( FieldError ( this , Text ( S . ? ( "invalid.email.address" ) ) ) )
}
2013-06-14 08:35:34 +00:00
}
/* *
* The singleton that has methods for accessing the database
*/
2017-02-09 13:34:42 +00:00
object AuthUser extends AuthUser with MetaMegaProtoUser [ AuthUser ] {
2014-01-07 09:51:48 +00:00
import net.liftweb.util.Helpers._
2016-12-30 23:12:12 +00:00
/* * Marking the locked state to show different error message */
2017-01-17 14:50:14 +00:00
val usernameLockedStateCode = Long . MaxValue
2016-12-30 23:12:12 +00:00
2018-03-06 15:22:08 +00:00
val connector = APIUtil . getPropsValue ( "connector" ) . openOrThrowException ( "no connector set" )
2016-12-19 16:29:36 +00:00
2018-03-06 15:22:08 +00:00
override def emailFrom = APIUtil . getPropsValue ( "mail.users.userinfo.sender.address" , "sender-not-set" )
2013-06-14 08:35:34 +00:00
2014-01-07 09:51:48 +00:00
override def screenWrap = Full ( < lift : surround with = "default" at = "content" >< lift : bind /></ lift : surround > )
2013-06-14 08:35:34 +00:00
// define the order fields will appear in forms and output
2016-09-02 17:53:39 +00:00
override def fieldOrder = List ( id , firstName , lastName , email , username , password , provider )
override def signupFields = List ( firstName , lastName , email , username , password )
2013-06-14 08:35:34 +00:00
2017-03-21 14:48:55 +00:00
// If we want to validate email addresses set this to false
2018-02-16 08:53:09 +00:00
override def skipEmailValidation = APIUtil . getPropsAsBoolValue ( "authUser.skipEmailValidation" , true )
2013-06-14 08:35:34 +00:00
override def loginXhtml = {
2014-04-15 08:50:56 +00:00
val loginXml = Templates ( List ( "templates-hidden" , "_login" ) ) . map ( {
2013-06-14 08:35:34 +00:00
"form [action]" # > { S . uri } &
2015-10-04 18:59:08 +00:00
"#loginText * " # > { S . ? ( "log.in" ) } &
2016-09-02 17:53:39 +00:00
"#usernameText * " # > { S . ? ( "username" ) } &
2015-10-04 18:59:08 +00:00
"#passwordText * " # > { S . ? ( "password" ) } &
2016-12-27 10:08:43 +00:00
"autocomplete=off [autocomplete] " # > APIUtil . getAutocompleteValue &
2013-06-14 08:35:34 +00:00
"#recoverPasswordLink * " # > {
"a [href]" # > { lostPasswordPath . mkString ( "/" , "/" , "" ) } &
2015-10-04 18:59:08 +00:00
"a *" # > { S . ? ( "recover.password" ) }
2013-06-14 08:35:34 +00:00
} &
"#SignUpLink * " # > {
2017-02-09 13:34:42 +00:00
"a [href]" # > { AuthUser . signUpPath . foldLeft ( "" ) ( _ + "/" + _ ) } &
2015-10-04 18:59:08 +00:00
"a *" # > { S . ? ( "sign.up" ) }
2013-06-14 08:35:34 +00:00
}
} )
2015-10-04 18:59:08 +00:00
< div > { loginXml getOrElse NodeSeq . Empty } </ div >
2013-06-14 08:35:34 +00:00
}
2017-07-11 10:04:58 +00:00
2017-01-22 05:54:28 +00:00
/* *
2017-07-11 10:04:58 +00:00
* Find current ResourceUser from the server .
2017-03-29 08:34:29 +00:00
* This method has no parameters , it depends on different login types :
* AuthUser : AuthUser.currentUser
* OAuthHandshake : OAuthHandshake.getUser
* DirectLogin : DirectLogin.getUser
2017-07-11 10:04:58 +00:00
* to get the current Resourceuser .
*
2017-01-22 05:54:28 +00:00
*/
2017-07-11 10:04:58 +00:00
def getCurrentUser : Box [ User ] = {
2018-01-15 21:05:24 +00:00
val authorization = S . request . map ( _ . header ( "Authorization" ) ) . flatten
2017-01-22 05:54:28 +00:00
for {
2017-07-11 10:04:58 +00:00
resourceUser <- if ( AuthUser . currentUser . isDefined )
2017-07-20 08:09:40 +00:00
//AuthUser.currentUser.get.user.foreign // this will be issue when the resource user is in remote side
2018-01-31 15:03:46 +00:00
Users . users . vend . getUserByUserName ( AuthUser . currentUser . openOrThrowException ( ErrorMessages . attemptedToOpenAnEmptyBox ) . username . get )
2018-01-15 21:05:24 +00:00
else if ( hasDirectLoginHeader ( authorization ) )
2017-07-11 10:04:58 +00:00
DirectLogin . getUser
2018-01-15 21:05:24 +00:00
else if ( hasAnOAuthHeader ( authorization ) ) {
2017-07-11 08:29:19 +00:00
OAuthHandshake . getUser
2018-01-15 21:05:24 +00:00
} else if ( hasGatewayHeader ( authorization ) ) {
2017-11-30 12:16:57 +00:00
GatewayLogin . getUser
2017-07-11 10:04:58 +00:00
} else {
debug ( ErrorMessages . CurrentUserNotFoundException )
Failure ( ErrorMessages . CurrentUserNotFoundException )
//This is a big problem, if there is no current user from here.
//throw new RuntimeException(ErrorMessages.CurrentUserNotFoundException)
}
2017-01-22 05:54:28 +00:00
} yield {
2017-07-11 10:04:58 +00:00
resourceUser
2017-01-22 05:54:28 +00:00
}
2017-07-11 10:04:58 +00:00
}
/* *
* get current user .
* Note : 1 . it will call getCurrentUser method ,
*
*/
def getCurrentUserUsername : String = {
getCurrentUser match {
case Full ( user ) => user . name
case _ => "" //TODO need more error handling for different user cases
}
}
/* *
* get current user . userId
* Note : 1 . resourceuser has two ids : id ( Long ) and userid_ ( String ) ,
*
* @return return userid_ ( String ) .
*/
def getCurrentResourceUserUserId : String = {
getCurrentUser match {
case Full ( user ) => user . userId
case _ => "" //TODO need more error handling for different user cases
2017-01-22 05:54:28 +00:00
}
2017-07-11 10:04:58 +00:00
}
2015-10-04 18:59:08 +00:00
2016-12-16 15:01:34 +00:00
/* *
* The string that 's generated when the user name is not found . By
* default : S . ? ( " email . address . not . found " )
2016-12-17 19:44:00 +00:00
* The function is overridden in order to prevent leak of information at password reset page if username / email exists or do not exist .
* I . e . we want to prevent case in which an anonymous user can get information from the message does some username / email exist or no in our system .
2016-12-16 15:01:34 +00:00
*/
override def userNameNotFoundString : String = "Thank you. If we found a matching user, password reset instructions have been sent."
2014-11-04 11:44:04 +00:00
/* *
2015-10-07 18:18:25 +00:00
* Overridden to use the hostname set in the props file
2014-11-04 11:44:04 +00:00
*/
2016-09-05 20:04:16 +00:00
override def sendPasswordReset ( name : String ) {
2017-07-04 09:32:55 +00:00
findUserByUsernameLocally ( name ) match {
2014-11-04 11:44:04 +00:00
case Full ( user ) if user . validated_? =>
user . resetUniqueId ( ) . save
2018-03-06 15:22:08 +00:00
val resetLink = APIUtil . getPropsValue ( "hostname" , "ERROR" ) +
2014-11-04 11:44:04 +00:00
passwordResetPath . mkString ( "/" , "/" , "/" ) + urlEncode ( user . getUniqueId ( ) )
Mailer . sendMail ( From ( emailFrom ) , Subject ( passwordResetEmailSubject ) ,
2016-05-24 17:14:26 +00:00
To ( user . getEmail ) : :
2014-11-04 11:44:04 +00:00
generateResetEmailBodies ( user , resetLink ) : ::
2016-05-24 17:14:26 +00:00
( bccEmail . toList . map ( BCC ( _ ) ) ) : _ * )
2014-11-04 11:44:04 +00:00
2016-12-16 15:01:34 +00:00
S . notice ( S . ? ( userNameNotFoundString ) )
2014-11-04 11:44:04 +00:00
S . redirectTo ( homePage )
case Full ( user ) =>
sendValidationEmail ( user )
S . notice ( S . ? ( "account.validation.resent" ) )
S . redirectTo ( homePage )
case _ => S . error ( userNameNotFoundString )
}
}
2015-10-04 18:59:08 +00:00
override def lostPasswordXhtml = {
2017-07-21 10:47:42 +00:00
< div id = "recover-password" >
< h1 > Recover Password </ h1 >
< div id = "recover-password-explanation" > Enter your email address or username and we 'll email you a link to reset your password </ div >
< form action = { S . uri } method = "post" >
< div class = "form-group" >
2017-11-01 16:56:35 +00:00
< label > Username or email address </ label > < span id = "recover-password-email" >< input id = "email" type = "text" /></ span >
2015-10-04 18:59:08 +00:00
</ div >
2017-07-21 10:47:42 +00:00
< div id = "recover-password-submit" >
2017-11-01 16:56:35 +00:00
< input type = "submit" />
2015-10-04 18:59:08 +00:00
</ div >
</ form >
</ div >
}
override def lostPassword = {
2017-08-04 07:00:59 +00:00
val bind =
2017-11-01 16:56:35 +00:00
"#email" # > SHtml . text ( "" , sendPasswordReset _ ) &
2017-08-04 07:00:59 +00:00
"type=submit" # > lostPasswordSubmitButton ( S . ? ( "submit" ) )
bind ( lostPasswordXhtml )
2015-10-04 18:59:08 +00:00
}
//override def def passwordResetMailBody(user: TheUserType, resetLink: String): Elem = { }
2014-11-04 11:44:04 +00:00
/* *
* Overriden to use the hostname set in the props file
*/
override def sendValidationEmail ( user : TheUserType ) {
2018-03-06 15:22:08 +00:00
val resetLink = APIUtil . getPropsValue ( "hostname" , "ERROR" ) + "/" + validateUserPath . mkString ( "/" ) +
2014-11-04 11:44:04 +00:00
"/" + urlEncode ( user . getUniqueId ( ) )
val email : String = user . getEmail
val msgXml = signupMailBody ( user , resetLink )
Mailer . sendMail ( From ( emailFrom ) , Subject ( signupMailSubject ) ,
2016-05-24 17:14:26 +00:00
To ( user . getEmail ) : :
2014-11-04 11:44:04 +00:00
generateValidationEmailBodies ( user , resetLink ) : ::
2016-05-24 17:14:26 +00:00
( bccEmail . toList . map ( BCC ( _ ) ) ) : _ * )
2014-11-04 11:44:04 +00:00
}
2013-06-18 13:03:40 +00:00
/* *
* Set this to redirect to a certain page after a failed login
*/
object failedLoginRedirect extends SessionVar [ Box [ String ] ] ( Empty ) {
override lazy val __nameSalt = Helpers . nextFuncName
}
2013-07-01 08:06:12 +00:00
2016-09-28 11:23:02 +00:00
def agreeTerms = {
2018-03-06 15:22:08 +00:00
val url = APIUtil . getPropsValue ( "webui_agree_terms_url" , "" )
2016-09-28 11:23:02 +00:00
if ( url . isEmpty ) {
s" "
} else {
2017-07-21 10:47:42 +00:00
scala . xml . Unparsed ( s""" <div id= " signup-agree-terms " class= " checkbox " ><label><input type= " checkbox " />I hereby agree to the <a href= " $url " title= " T & C " >Terms and Conditions</a></label></div> """ )
2016-09-28 11:23:02 +00:00
}
}
2017-02-09 13:34:42 +00:00
override def signupXhtml ( user : AuthUser ) = {
2017-07-21 10:47:42 +00:00
< div id = "signup" >
< form method = "post" action = { S . uri } >
< h1 > Sign Up </ h1 >
< div id = "signup-error" class = "alert alert-danger hide" >< span data - lift = "Msg?id=error" /></ div >
{ localForm ( user , false , signupFields ) }
{ agreeTerms }
< div id = "signup-submit" >
2017-08-18 18:26:34 +00:00
< input type = "submit" />
2017-07-21 10:47:42 +00:00
</ div >
</ form >
2015-10-04 18:59:08 +00:00
</ div >
2014-04-30 15:41:49 +00:00
}
2017-07-21 10:47:42 +00:00
override def localForm ( user : TheUserType , ignorePassword : Boolean , fields : List [ FieldPointerType ] ) : NodeSeq = {
for {
pointer <- fields
field <- computeFieldFromPointer ( user , pointer ) . toList
if field . show_? && ( ! ignorePassword || ! pointer . isPasswordField_? )
form <- field . toForm . toList
} yield < div class = "form-group" >< label > { field . displayName } </ label > { form } </ div >
}
2016-01-15 17:37:06 +00:00
def userLoginFailed = {
info ( "failed: " + failedLoginRedirect . get )
2017-02-20 08:28:26 +00:00
// variable redir is from failedLoginRedirect, it is set-up in OAuthAuthorisation.scala as following code:
// val currentUrl = S.uriAndQueryString.getOrElse("/")
// AuthUser.failedLoginRedirect.set(Full(Helpers.appendParams(currentUrl, List((FailedLoginParam, "true")))))
val redir = failedLoginRedirect . get
//Check the internal redirect, in case for open redirect issue.
// variable redir is from loginRedirect, it is set-up in OAuthAuthorisation.scala as following code:
// val currentUrl = S.uriAndQueryString.getOrElse("/")
// AuthUser.loginRedirect.set(Full(Helpers.appendParams(currentUrl, List((LogUserOutParam, "false")))))
if ( Helper . isValidInternalRedirectUrl ( redir . toString ) ) {
S . redirectTo ( redir . toString )
} else {
S . error ( S . ? ( ErrorMessages . InvalidInternalRedirectUrl ) )
info ( ErrorMessages . InvalidInternalRedirectUrl + loginRedirect . get )
}
2016-01-15 17:37:06 +00:00
S . error ( "login" , S . ? ( "Invalid Username or Password" ) )
}
2016-05-25 00:16:09 +00:00
2016-12-31 15:16:24 +00:00
2017-02-09 13:34:42 +00:00
def getResourceUserId ( username : String , password : String ) : Box [ Long ] = {
2017-07-04 09:32:55 +00:00
findUserByUsernameLocally ( username ) match {
2018-03-06 15:22:08 +00:00
case Full ( user ) if ( user . getProvider ( ) == APIUtil . getPropsValue ( "hostname" , "" ) ) =>
2016-12-31 15:16:24 +00:00
if (
user . validated_? &&
2016-12-31 18:44:45 +00:00
// User is NOT locked AND the password is good
2016-12-31 15:16:24 +00:00
! LoginAttempt . userIsLocked ( username ) &&
2016-01-29 22:03:30 +00:00
user . testPassword ( Full ( password ) ) )
2016-12-31 15:16:24 +00:00
{
// We logged in correctly, so reset badLoginAttempts counter (if it exists)
LoginAttempt . resetBadLoginAttempts ( username )
2017-08-04 07:00:59 +00:00
Full ( user . user . get ) // Return the user.
2016-12-31 15:16:24 +00:00
}
2017-01-17 14:50:14 +00:00
// User is unlocked AND password is bad
2016-12-31 15:16:24 +00:00
else if (
user . validated_? &&
2017-01-17 14:50:14 +00:00
! LoginAttempt . userIsLocked ( username ) &&
2016-12-31 15:16:24 +00:00
! user . testPassword ( Full ( password ) )
2016-12-30 23:12:12 +00:00
) {
2016-12-31 15:16:24 +00:00
LoginAttempt . incrementBadLoginAttempts ( username )
2016-12-30 23:12:12 +00:00
Empty
}
2016-12-31 18:44:45 +00:00
// User is locked
2017-01-23 00:24:47 +00:00
else if ( LoginAttempt . userIsLocked ( username ) )
{
2017-01-17 14:50:14 +00:00
LoginAttempt . incrementBadLoginAttempts ( username )
2016-12-31 15:16:24 +00:00
info ( ErrorMessages . UsernameHasBeenLocked )
2017-01-18 11:57:17 +00:00
//TODO need to fix, use Failure instead, it is used to show the error message to the GUI
2017-02-09 13:34:42 +00:00
Full ( usernameLockedStateCode )
2016-12-30 23:12:12 +00:00
}
2016-01-29 22:03:30 +00:00
else {
2017-02-09 13:34:42 +00:00
// Nothing worked, so just increment bad login attempts
2017-01-23 00:24:47 +00:00
LoginAttempt . incrementBadLoginAttempts ( username )
Empty
}
2018-03-06 15:22:08 +00:00
case Full ( user ) if ( user . getProvider ( ) != APIUtil . getPropsValue ( "hostname" , "" ) ) =>
2016-12-19 16:29:36 +00:00
connector match {
2018-02-16 08:53:09 +00:00
case Helper . matchAnyKafka ( ) if ( APIUtil . getPropsAsBoolValue ( "kafka.user.authentication" , false ) &&
2017-01-23 00:24:47 +00:00
! LoginAttempt . userIsLocked ( username ) ) =>
val userId = for { kafkaUser <- getUserFromConnector ( username , password )
kafkaUserId <- tryo { kafkaUser . user } } yield {
LoginAttempt . resetBadLoginAttempts ( username )
2017-08-04 07:00:59 +00:00
kafkaUserId . get
2017-02-09 13:34:42 +00:00
}
2017-01-23 00:24:47 +00:00
userId match {
case Full ( l : Long ) => Full ( l )
case _ =>
LoginAttempt . incrementBadLoginAttempts ( username )
2017-02-09 13:34:42 +00:00
Empty
2017-01-23 00:24:47 +00:00
}
2018-02-16 08:53:09 +00:00
case "obpjvm" if ( APIUtil . getPropsAsBoolValue ( "obpjvm.user.authentication" , false ) &&
2017-01-23 00:24:47 +00:00
! LoginAttempt . userIsLocked ( username ) ) =>
val userId = for { obpjvmUser <- getUserFromConnector ( username , password )
2017-02-09 13:34:42 +00:00
obpjvmUserId <- tryo { obpjvmUser . user } } yield {
2017-01-23 00:24:47 +00:00
LoginAttempt . resetBadLoginAttempts ( username )
2017-08-04 07:00:59 +00:00
obpjvmUserId . get
2017-01-23 00:24:47 +00:00
}
userId match {
case Full ( l : Long ) => Full ( l )
2017-02-09 13:34:42 +00:00
case _ =>
2017-01-23 00:24:47 +00:00
LoginAttempt . incrementBadLoginAttempts ( username )
2017-02-09 13:34:42 +00:00
Empty
2017-01-23 00:24:47 +00:00
}
2017-02-09 13:34:42 +00:00
case _ =>
2017-01-23 00:24:47 +00:00
LoginAttempt . incrementBadLoginAttempts ( username )
Empty
2016-06-21 07:36:27 +00:00
}
2016-12-19 16:29:36 +00:00
2017-01-23 00:24:47 +00:00
case _ =>
LoginAttempt . incrementBadLoginAttempts ( username )
Empty
2016-01-29 22:03:30 +00:00
}
}
2017-05-31 11:49:31 +00:00
/* *
* This method is belong to AuthUser , it is used for authentication ( Login stuff )
* 1 get the user over connector .
* 2 check whether it is existing in AuthUser table in obp side .
* 3 if not existing , will create new AuthUser .
* @return Return the authUser
*/
2017-02-09 13:34:42 +00:00
def getUserFromConnector ( name : String , password : String ) : Box [ AuthUser ] = {
2016-12-04 21:47:51 +00:00
Connector . connector . vend . getUser ( name , password ) match {
2017-03-13 15:44:53 +00:00
case Full ( InboundUser ( extEmail , extPassword , extUsername ) ) => {
2016-01-29 22:03:30 +00:00
info ( "external user authenticated. login redir: " + loginRedirect . get )
val redir = loginRedirect . get match {
case Full ( url ) =>
loginRedirect ( Empty )
url
case _ =>
homePage
}
2016-12-19 16:29:36 +00:00
val extProvider = connector
2016-01-29 22:03:30 +00:00
2017-07-04 09:32:55 +00:00
val user = findUserByUsernameLocally ( name ) match {
2016-01-29 22:03:30 +00:00
// Check if the external user is already created locally
2016-12-21 18:13:24 +00:00
case Full ( user ) if user . validated_?
2016-12-23 14:30:27 +00:00
// && user.provider == extProvider
2016-12-21 18:13:24 +00:00
=> {
2016-01-29 22:03:30 +00:00
// Return existing user if found
info ( "external user already exists locally, using that one" )
user
}
// If not found, create new user
case _ => {
2017-02-09 13:34:42 +00:00
// Create AuthUser using fetched data from Kafka
2016-01-29 22:03:30 +00:00
// assuming that user's email is always validated
2016-03-28 18:05:42 +00:00
info ( "external user " + extEmail + " does not exist locally, creating one" )
2017-02-09 13:34:42 +00:00
val newUser = AuthUser . create
2016-09-05 20:04:16 +00:00
. firstName ( extUsername )
2016-01-29 22:03:30 +00:00
. email ( extEmail )
2016-09-05 20:04:16 +00:00
. username ( extUsername )
2016-01-29 22:03:30 +00:00
// No need to store password, so store dummy string instead
2016-09-02 17:53:39 +00:00
. password ( UUID . randomUUID ( ) . toString )
2016-01-29 22:03:30 +00:00
. provider ( extProvider )
. validated ( true )
// Save the user in order to be able to log in
newUser . save ( )
// Return created user
newUser
}
}
Full ( user )
}
case _ => {
Empty
}
}
}
2013-06-18 13:03:40 +00:00
//overridden to allow a redirection if login fails
2017-05-31 11:49:31 +00:00
/* *
* Success cases :
* case1 : user validated && user not locked && user . provider from localhost && password correct --> Login in
* case2 : user validated && user not locked && user . provider not localhost && password correct --> Login in
* case3 : user from remote && checked over connector --> Login in
*
* Error cases :
* case1 : user is locked --> UsernameHasBeenLocked
* case2 : user . validated_? --> account.validation.error
* case3 : right username but wrong password --> Invalid Login Credentials
* case4 : wrong username --> Invalid Login Credentials
* case5 : UnKnow error --> UnexpectedErrorDuringLogin
*/
2013-06-18 13:03:40 +00:00
override def login = {
2016-12-23 14:30:27 +00:00
def loginAction = {
if ( S . post_? ) {
2016-12-30 23:12:12 +00:00
val usernameFromGui = S . param ( "username" ) . getOrElse ( "" )
2017-01-12 13:49:10 +00:00
val passwordFromGui = S . param ( "password" ) . getOrElse ( "" )
2017-07-04 09:32:55 +00:00
findUserByUsernameLocally ( usernameFromGui ) match {
2017-01-23 00:24:47 +00:00
// Check if user came from localhost and
// if User is NOT locked and password is good
2016-12-23 14:30:27 +00:00
case Full ( user ) if user . validated_? &&
2018-03-06 15:22:08 +00:00
user . getProvider ( ) == APIUtil . getPropsValue ( "hostname" , "" ) &&
2016-12-31 16:00:43 +00:00
! LoginAttempt . userIsLocked ( usernameFromGui ) &&
2017-01-23 00:24:47 +00:00
user . testPassword ( Full ( passwordFromGui ) ) => {
// Reset any bad attempts
LoginAttempt . resetBadLoginAttempts ( usernameFromGui )
val preLoginState = capturePreLoginState ( )
info ( "login redir: " + loginRedirect . get )
val redir = loginRedirect . get match {
case Full ( url ) =>
loginRedirect ( Empty )
url
case _ =>
homePage
}
2017-07-05 23:59:46 +00:00
// registeredUserHelper(user.username)
2017-02-20 08:28:26 +00:00
//Check the internal redirect, in case for open redirect issue.
// variable redir is from loginRedirect, it is set-up in OAuthAuthorisation.scala as following code:
// val currentUrl = S.uriAndQueryString.getOrElse("/")
// AuthUser.loginRedirect.set(Full(Helpers.appendParams(currentUrl, List((LogUserOutParam, "false")))))
if ( Helper . isValidInternalRedirectUrl ( redir . toString ) ) {
2017-01-23 00:24:47 +00:00
logUserIn ( user , ( ) => {
S . notice ( S . ? ( "logged.in" ) )
preLoginState ( )
S . redirectTo ( redir )
} )
2017-02-20 08:28:26 +00:00
} else {
S . error ( S . ? ( ErrorMessages . InvalidInternalRedirectUrl ) )
info ( ErrorMessages . InvalidInternalRedirectUrl + loginRedirect . get )
2013-06-18 13:03:40 +00:00
}
2017-02-20 08:28:26 +00:00
}
2013-06-18 13:03:40 +00:00
2017-02-13 18:01:56 +00:00
// Check if user came from kafka/obpjvm and
// if User is NOT locked. Then check username and password
// from connector in case they changed on the south-side
case Full ( user ) if user . validated_? &&
2018-03-06 15:22:08 +00:00
user . getProvider ( ) != APIUtil . getPropsValue ( "hostname" , "" ) &&
2017-02-13 18:01:56 +00:00
! LoginAttempt . userIsLocked ( usernameFromGui ) &&
testExternalPassword ( Full ( user . username . get ) , Full ( passwordFromGui ) ) . getOrElse ( false ) => {
// Reset any bad attempts
LoginAttempt . resetBadLoginAttempts ( usernameFromGui )
val preLoginState = capturePreLoginState ( )
info ( "login redir: " + loginRedirect . get )
val redir = loginRedirect . get match {
case Full ( url ) =>
loginRedirect ( Empty )
url
case _ =>
homePage
}
2017-05-31 11:49:31 +00:00
//This method is used for connector = kafka* || obpjvm*
//It will update the views and createAccountHolder ....
2017-08-04 07:00:59 +00:00
registeredUserHelper ( user . username . get )
2017-02-20 08:28:26 +00:00
//Check the internal redirect, in case for open redirect issue.
// variable redir is from loginRedirect, it is set-up in OAuthAuthorisation.scala as following code:
// val currentUrl = S.uriAndQueryString.getOrElse("/")
// AuthUser.loginRedirect.set(Full(Helpers.appendParams(currentUrl, List((LogUserOutParam, "false")))))
if ( Helper . isValidInternalRedirectUrl ( redir . toString ) ) {
logUserIn ( user , ( ) => {
S . notice ( S . ? ( "logged.in" ) )
preLoginState ( )
S . redirectTo ( redir )
} )
} else {
S . error ( S . ? ( ErrorMessages . InvalidInternalRedirectUrl ) )
info ( ErrorMessages . InvalidInternalRedirectUrl + loginRedirect . get )
}
2017-02-13 18:01:56 +00:00
}
2017-01-17 14:50:14 +00:00
// If user is unlocked AND bad password, increment bad login attempt counter.
2016-12-30 23:12:12 +00:00
case Full ( user ) if user . validated_? &&
2018-03-06 15:22:08 +00:00
user . getProvider ( ) == APIUtil . getPropsValue ( "hostname" , "" ) &&
2017-01-17 14:50:14 +00:00
! LoginAttempt . userIsLocked ( usernameFromGui ) &&
2017-02-09 13:34:42 +00:00
! user . testPassword ( Full ( passwordFromGui ) ) =>
2017-01-12 13:49:10 +00:00
LoginAttempt . incrementBadLoginAttempts ( usernameFromGui )
2016-12-31 16:00:43 +00:00
S . error ( S . ? ( "Invalid Login Credentials" ) ) // TODO constant / i18n for this string
2016-12-30 23:12:12 +00:00
2017-01-22 15:42:59 +00:00
// If user is locked, send the error to GUI
2017-01-23 00:24:47 +00:00
case Full ( user ) if LoginAttempt . userIsLocked ( usernameFromGui ) =>
2017-01-17 14:50:14 +00:00
LoginAttempt . incrementBadLoginAttempts ( usernameFromGui )
2017-01-12 13:49:10 +00:00
S . error ( S . ? ( ErrorMessages . UsernameHasBeenLocked ) )
2016-12-30 23:12:12 +00:00
2016-12-23 14:30:27 +00:00
case Full ( user ) if ! user . validated_? =>
2017-03-21 15:23:25 +00:00
S . error ( S . ? ( "account.validation.error" ) ) // Note: This does not seem to get hit when user is not validated.
2015-05-16 18:38:42 +00:00
2017-01-23 00:24:47 +00:00
// If not found locally, try to authenticate user via Kafka, if enabled in props
2017-03-06 15:45:47 +00:00
case Empty if ( connector . startsWith ( "kafka" ) || connector == "obpjvm" ) &&
2018-02-16 08:53:09 +00:00
( APIUtil . getPropsAsBoolValue ( "kafka.user.authentication" , false ) ||
APIUtil . getPropsAsBoolValue ( "obpjvm.user.authentication" , false ) ) =>
2016-12-23 14:30:27 +00:00
val preLoginState = capturePreLoginState ( )
info ( "login redir: " + loginRedirect . get )
val redir = loginRedirect . get match {
case Full ( url ) =>
loginRedirect ( Empty )
url
case _ =>
homePage
}
for {
2017-01-23 00:24:47 +00:00
user_ <- externalUserHelper ( usernameFromGui , passwordFromGui )
2016-12-23 14:30:27 +00:00
} yield {
2017-02-09 13:34:42 +00:00
user_
2017-01-23 00:24:47 +00:00
} match {
2017-02-09 13:34:42 +00:00
case u : AuthUser =>
2017-01-23 00:24:47 +00:00
LoginAttempt . resetBadLoginAttempts ( usernameFromGui )
2017-02-20 08:28:26 +00:00
//Check the internal redirect, in case for open redirect issue.
// variable redir is from loginRedirect, it is set-up in OAuthAuthorisation.scala as following code:
// val currentUrl = S.uriAndQueryString.getOrElse("/")
// AuthUser.loginRedirect.set(Full(Helpers.appendParams(currentUrl, List((LogUserOutParam, "false")))))
if ( Helper . isValidInternalRedirectUrl ( redir . toString ) ) {
logUserIn ( u , ( ) => {
S . notice ( S . ? ( "logged.in" ) )
preLoginState ( )
S . redirectTo ( redir )
} )
} else {
S . error ( S . ? ( ErrorMessages . InvalidInternalRedirectUrl ) )
info ( ErrorMessages . InvalidInternalRedirectUrl + loginRedirect . get )
}
2017-01-23 00:24:47 +00:00
case _ =>
2017-08-04 07:00:59 +00:00
LoginAttempt . incrementBadLoginAttempts ( username . get )
2017-01-23 00:24:47 +00:00
Empty
2016-12-23 14:30:27 +00:00
}
2017-05-31 11:49:31 +00:00
//If the username is not exiting, throw the error message.
case Empty => S . error ( S . ? ( "Invalid Login Credentials" ) )
2017-01-23 00:24:47 +00:00
case _ =>
LoginAttempt . incrementBadLoginAttempts ( usernameFromGui )
2017-03-21 15:23:25 +00:00
S . error ( S . ? ( ErrorMessages . UnexpectedErrorDuringLogin ) ) // Note we hit this if user has not clicked email validation link
2013-06-18 13:03:40 +00:00
}
}
}
2016-12-23 14:30:27 +00:00
// In this function we bind submit button to loginAction function.
// In case that unique token of submit button cannot be paired submit action will be omitted.
// Implemented in order to prevent a CSRF attack
def insertSubmitButton = {
scala . xml . XML . loadString ( loginSubmitButton ( S . ? ( "Login" ) , loginAction _ ) . toString ( ) . replace ( "type=\"submit\"" , "class=\"submit\" type=\"submit\"" ) )
}
2017-08-04 07:00:59 +00:00
val bind =
"submit" # > insertSubmitButton
bind ( loginXhtml )
2013-06-18 13:03:40 +00:00
}
2017-05-31 11:49:31 +00:00
/* *
* The user authentications is not exciting in obp side , it need get the user from south - side
*/
2017-02-13 18:01:56 +00:00
def testExternalPassword ( usernameFromGui : Box [ String ] , passwordFromGui : Box [ String ] ) : Box [ Boolean ] = {
2017-03-06 15:45:47 +00:00
if ( connector . startsWith ( "kafka" ) || connector == "obpjvm" ) {
2017-02-13 18:01:56 +00:00
val res = for {
username <- usernameFromGui
password <- passwordFromGui
user <- getUserFromConnector ( username , password )
} yield user match {
case user : AuthUser => true
case _ => false
}
res
} else Empty
}
2017-04-17 18:30:31 +00:00
/* *
* This method will update the views and createAccountHolder . . . .
*/
2017-02-09 13:34:42 +00:00
def externalUserHelper ( name : String , password : String ) : Box [ AuthUser ] = {
2017-03-06 15:45:47 +00:00
if ( connector . startsWith ( "kafka" ) || connector == "obpjvm" ) {
2016-07-27 18:29:14 +00:00
for {
2016-12-19 16:29:36 +00:00
user <- getUserFromConnector ( name , password )
2017-07-20 08:09:40 +00:00
//u <- user.user.foreign // this will be issue when the resource user is in remote side
u <- Users . users . vend . getUserByUserName ( name )
2017-07-17 10:36:33 +00:00
v <- Full ( updateUserAccountViews ( u ) )
2016-07-27 18:29:14 +00:00
} yield {
user
}
2016-10-15 22:20:26 +00:00
} else Empty
2016-07-27 18:29:14 +00:00
}
2017-04-17 18:30:31 +00:00
/* *
* This method will update the views and createAccountHolder . . . .
*/
2016-10-26 11:03:14 +00:00
def registeredUserHelper ( username : String ) = {
2017-03-06 15:45:47 +00:00
if ( connector . startsWith ( "kafka" ) || connector == "obpjvm" ) {
2016-10-26 11:03:14 +00:00
for {
2017-02-22 16:55:50 +00:00
u <- Users . users . vend . getUserByUserName ( username )
2017-07-17 10:36:33 +00:00
v <- Full ( updateUserAccountViews ( u ) )
2016-10-26 11:03:14 +00:00
} yield v
}
}
2017-07-13 13:19:12 +00:00
/* *
2017-07-17 09:23:13 +00:00
* This is a helper method
* update the views , accountHolders for OBP side when sign up new remote user
*
2017-07-13 13:19:12 +00:00
*/
2017-10-04 10:22:19 +00:00
def updateUserAccountViews ( user : User ) : Unit = {
2017-07-14 08:59:50 +00:00
//get all accounts from Kafka
2018-01-31 15:03:46 +00:00
val accounts = Connector . connector . vend . getBankAccounts ( user . name , false ) . openOrThrowException ( attemptedToOpenAnEmptyBox )
2017-07-13 13:19:12 +00:00
debug ( s" -->AuthUser.updateUserAccountViews.accounts : ${ accounts } " )
2017-11-30 12:16:57 +00:00
updateUserAccountViews ( user , accounts )
}
/* *
* This is a helper method
* update the views , accountHolders for OBP side when sign up new remote user
*
*/
def updateUserAccountViews ( user : User , accounts : List [ InboundAccountCommon ] ) : Unit = {
2017-07-13 13:19:12 +00:00
for {
2017-07-17 09:23:13 +00:00
account <- accounts
2017-11-30 12:16:57 +00:00
viewId <- account . viewsToGenerate
bankAccountUID <- Full ( BankIdAccountId ( BankId ( account . bankId ) , AccountId ( account . accountId ) ) )
2017-07-14 08:59:50 +00:00
view <- Views . views . vend . getOrCreateAccountView ( bankAccountUID , viewId )
2017-07-13 13:19:12 +00:00
} yield {
2017-07-14 08:59:50 +00:00
Views . views . vend . getOrCreateViewPrivilege ( view , user )
AccountHolders . accountHolders . vend . getOrCreateAccountHolder ( user , bankAccountUID )
2017-07-13 13:19:12 +00:00
}
}
2017-05-31 11:49:31 +00:00
/* *
2017-07-04 09:32:55 +00:00
* Find the authUser by author user name ( authUser and resourceUser are the same ) .
* Only search for the local database .
2017-05-31 11:49:31 +00:00
*/
2017-07-04 09:32:55 +00:00
protected def findUserByUsernameLocally ( name : String ) : Box [ TheUserType ] = {
2016-09-05 20:04:16 +00:00
find ( By ( this . username , name ) )
}
2016-07-27 18:29:14 +00:00
2014-04-15 08:51:36 +00:00
//overridden to allow redirect to loginRedirect after signup. This is mostly to allow
// loginFirst menu items to work if the user doesn't have an account. Without this,
// if a user tries to access a logged-in only page, and then signs up, they don't get redirected
// back to the proper page.
override def signup = {
val theUser : TheUserType = mutateUserOnSignup ( createNewUserInstance ( ) )
val theName = signUpPath . mkString ( "" )
2017-02-20 08:28:26 +00:00
//Check the internal redirect, in case for open redirect issue.
// variable redir is from loginRedirect, it is set-up in OAuthAuthorisation.scala as following code:
// val currentUrl = S.uriAndQueryString.getOrElse("/")
// AuthUser.loginRedirect.set(Full(Helpers.appendParams(currentUrl, List((LogUserOutParam, "false")))))
2014-04-15 08:51:36 +00:00
val loginRedirectSave = loginRedirect . is
def testSignup ( ) {
validateSignup ( theUser ) match {
case Nil =>
2017-02-20 08:28:26 +00:00
//here we check loginRedirectSave (different from implementation in super class)
val redir = loginRedirectSave match {
case Full ( url ) =>
loginRedirect ( Empty )
url
case _ =>
homePage
}
if ( Helper . isValidInternalRedirectUrl ( redir . toString ) ) {
actionsAfterSignup ( theUser , ( ) => {
S . redirectTo ( redir )
} )
} else {
S . error ( S . ? ( ErrorMessages . InvalidInternalRedirectUrl ) )
info ( ErrorMessages . InvalidInternalRedirectUrl + loginRedirect . get )
}
2014-04-15 08:51:36 +00:00
2015-10-04 18:59:08 +00:00
case xs =>
2017-07-21 10:47:42 +00:00
xs . foreach ( e => S . error ( "error" , e . msg ) )
2015-10-04 18:59:08 +00:00
signupFunc ( Full ( innerSignup _ ) )
2014-04-15 08:51:36 +00:00
}
}
2017-08-09 19:56:36 +00:00
def innerSignup = {
2017-08-18 18:26:34 +00:00
val bind = "type=submit" # > signupSubmitButton ( S . ? ( "sign.up" ) , testSignup _ )
bind ( signupXhtml ( theUser ) )
2017-08-09 19:56:36 +00:00
}
2014-04-15 08:51:36 +00:00
innerSignup
}
2016-01-15 17:37:06 +00:00
}