Search code examples
mongodbscalaplayframeworkplayframework-2.0securesocial

MongoUserService for SecureSocial (Play Framework) persists the same user multiple times


This is my implementation of the UserService class. It's a slightly modified version of this implementation (http://www.shrikar.com/blog/2013/10/26/playframework-securesocial-and-mongodb/)

I use it with the default securesocial FacebookProvider. Everything works fine except for the fact that it duplicates user entries everytime i log in. I would like to persist the user only once. I thought this was handled by the plugin itself but i'm not sure it is the case. Is it up to me to check if the user exists and insert the logged user only if there are no other entries for the same user? Or there is something else that is wrong?

class MongoUserService(application: Application) extends UserServicePlugin(application) with Controller with MongoController {
  def collection: JSONCollection = db.collection[JSONCollection]("users")
  def tokens: JSONCollection = db.collection[JSONCollection]("tokens")
  val outPutUser = (__ \ "id").json.prune

  def retIdentity(json: JsObject): Identity = {
    val userid = (json \ "userid").as[String]

    val provider = (json \ "provider").as[String]
    val firstname = (json \ "firstname").as[String]
    val lastname = (json \ "lastname").as[String]
    val email = (json \ "email").as[String]
    val avatar = (json \ "avatar").as[String]
    val hash = (json \ "password" \ "hasher").as[String]
    val password = (json \ "password" \ "password").as[String]
    println("password : " + password)
    val salt = (json \ "password" \ "salt").asOpt[String]
    val authmethod = (json \ "authmethod").as[String]

    val identity: IdentityId = new IdentityId(userid, authmethod)
    val authMethod: AuthenticationMethod = new AuthenticationMethod(authmethod)
    val pwdInfo: PasswordInfo = new PasswordInfo(hash, password)
    val serial:Integer=((json\"serial").as[Long]).toInt
    val user: NWOUser = new NWOUser(identity, firstname, lastname, firstname, Some(email), Some(avatar), authMethod, None, None, Some(pwdInfo),serial)
    user
  }

  def generateSerial():Integer={
    val collection = db[JSONCollection]("serial")
    val cursor = collection.find(Json.obj()).cursor[JsObject]
    val futureserial = cursor.headOption.map {
      case Some(i) => i
      case None => 0
    }
    val jobj = Await.result(futureserial, 5 seconds)
    val newSerial=jobj match {
      case x: Boolean => 0
      case _ =>retSerial(jobj.asInstanceOf[JsObject])+1

    }
    collection.update(Json.obj(), Json.obj("$set"->Json.obj("serial"->newSerial))).onComplete {
  case _=>println("updated")
}
    newSerial
  }
  def retSerial(json: JsObject): Integer = {
    println(json)
     val serial=(json \ "serial").as[Long]
     serial.toInt
  }

  def findByEmailAndProvider(email: String, providerId: String): Option[Identity] = {
    val cursor = collection.find(Json.obj("userid" -> email, "provider" -> providerId)).cursor[JsObject]
    val futureuser = cursor.headOption.map {
      case Some(user) => user
      case None => false
    }
    val jobj = Await.result(futureuser, 5 seconds)

    jobj match {
      case x: Boolean => None
      case _ => Some(retIdentity(jobj.asInstanceOf[JsObject]))

    }
  }

  def save(user: Identity): Identity = {

    val email = user.email match {
      case Some(email) => email
      case _ => "N/A"
    }

    val avatar = user.avatarUrl match {
      case Some(url) => url
      case _ => "N/A"
    }
    val savejson = Json.obj(
      "userid" -> user.identityId.userId,
      "provider" -> user.identityId.providerId,
      "firstname" -> user.firstName,
      "lastname" -> user.lastName,
      "email" -> email,
      "avatar" -> avatar,
      "authmethod" -> user.authMethod.method,
      "serial"->this.generateSerial.toLong,
       user.passwordInfo match {
       case None =>"password" -> Json.obj(   "hasher" -> "",
          "password" -> "",
          "salt" -> "")

       case x =>"password" -> Json.obj(
            "hasher" -> x.get.hasher,
          "password" -> x.get.password,
          "salt" -> x.get.salt)

      },
      "created_at" -> Json.obj("$date" -> new Date()),
      "updated_at" -> Json.obj("$date" -> new Date()))
    println(Json.toJson(savejson))
    collection.insert(savejson)
    user
  }

  def find(id: IdentityId): Option[Identity] = {
    findByEmailAndProvider(id.userId, id.providerId)
  }

  def save(token: Token) {
    val tokentosave = Json.obj(
      "uuid" -> token.uuid,
      "email" -> token.email,
      "creation_time" -> Json.obj("$date" -> token.creationTime),
      "expiration_time" -> Json.obj("$date" -> token.expirationTime),
      "isSignUp" -> token.isSignUp)
    tokens.save(tokentosave)
  }

  def findToken(token: String): Option[Token] = {

    val cursor = tokens.find(Json.obj("uuid" -> token)).cursor[JsObject]
    val futureuser = cursor.headOption.map {
      case Some(user) => user
      case None => false
    }
    val jobj = Await.result(futureuser, 5 seconds)
    jobj match {
      case x: Boolean => None
      case obj: JsObject => {
        println(obj)
        val uuid = (obj \ "uuid").as[String]
        val email = (obj \ "email").as[String]
        val created = (obj \ "creation_time" \ "$date").as[Long]
        val expire = (obj \ "expiration_time" \ "$date").as[Long]
        val signup = (obj \ "isSignUp").as[Boolean]
        val df = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss")
        Some(new Token(uuid, email, new DateTime(created), new DateTime(expire), signup))
      }
    }
  }

  def deleteToken(uuid: String) {}

  def deleteExpiredTokens() {}
}  

Solution

  • You can solve it by using db.collection.[update][1] with upsert=true, rather than insert. With this flag, it will insert an object identified by the first element (query) in the call when it doesn't exist, and update the existing one.

    So, instead of

    collection.insert(savejson)
    

    try

    collection.update(
        Json.obj("userid" -> user.identityId.userId), 
        savejson, 
        Json.obj("upsert" -> true)
    )