Reputation: 569
Can't seem to create a functional way to insert a user from Java for Devise. Currently there are these fields: "_id", "access_level", "confirmation_sent_at", "confirmation_token", "confirmed_at", "email", "encrypted_password", "sign_in_count"
I am able to insert a document that counts as a user. The problem is that when I go to: http://www.mysite.com:3000/users/confirmation?confirmation_token=TOKENHERE
I get a message saying that it's invalid.
EDIT 1: When I resend confirmation instructions for this user (WHICH GENERATES A NEW TOKEN), the user can be logged into. This confirms my doubts about the token being the problem. How can I port Devise's token generator to Java?
EDIT 2: When I register on site, it says I should check for a confirmation link. However, if I go into the Mongo shell, manually take out the confirmation token and paste it to site.com/users/confirmation?confirmation_token= then it doesn't work! However, if I actually use the confirmation link I was sent, it works. How can I make a VALID token, all from Java. Please help!
Upvotes: 2
Views: 806
Reputation: 181
In case anyone else finds themselves trying to get a java or scala app to coexist with a rails app, I hacked up the following. Its in scala but uses java apis so should be easy to read. As far as I can tell it replicates Devise's behavior, and if I hit the confirmation link in the rails app with the raw token rails/devise generates the same encoded string.
import java.security.spec.KeySpec
import javax.crypto.SecretKey
import javax.crypto.SecretKeyFactory
import javax.crypto.spec.PBEKeySpec
import javax.crypto.spec.SecretKeySpec
import javax.crypto.Mac
import javax.xml.bind.DatatypeConverter
import java.util.Base64
// copy functionality from Rails Devise
object TokenGenerator {
// sample values 9exithzwZ8P9meqdVs3K => 54364224169895883e87c8412be5874039b470e26e762cb3ddc37c0bdcf014f5
// 5zNMi6egbyPoDUy2t3NY => 75bd5d53aa36d3fc61ac186b4c6e2be8353e6b39536d3cf846719284e05474ca
private val deviseSecret = sys.env("DEVISE_SECRET")
private val factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1")
val encoder = Base64.getUrlEncoder()
case class TokenInfo(raw: String, encoded: String)
def createConfirmationToken: TokenInfo = {
// copy behavior from rails world. Don't know why it does this
val replacements = Map('l' -> "s", 'I' -> "x", 'O' -> "y", '0' -> "z")
// make a raw key of 20 chars, doesn't seem to matter what they are, just need url valid set
val bytes = new Array[Byte](16)
scala.util.Random.nextBytes(bytes)
val raw = encoder.encodeToString(bytes).take(20).foldLeft(""){(acc, x) => acc ++ replacements.get(x).getOrElse(x.toString)}
TokenInfo(raw, digestForConfirmationToken(raw))
}
private def generateKey(salt: String): Array[Byte] = {
val iter = 65536
val keySize = 512
val spec = new PBEKeySpec(deviseSecret.toCharArray, salt.getBytes("UTF-8"), iter, keySize)
val sk = factory.generateSecret(spec)
val skspec = new SecretKeySpec(sk.getEncoded, "AES")
skspec.getEncoded
}
def sha256HexDigest(s: String, key: Array[Byte]): String = {
val mac = Mac.getInstance("HmacSHA256")
val keySpec = new SecretKeySpec(key, "RAW")
mac.init(keySpec)
val result: Array[Byte] = mac.doFinal(s.getBytes())
DatatypeConverter.printHexBinary(result).toLowerCase
}
private def getDigest(raw: String, salt: String) = sha256HexDigest(raw, generateKey(salt))
// devise uses salt "Devise #{column}", in this case its confirmation_token
def digestForConfirmationToken(raw: String) = getDigest(raw, "Devise confirmation_token")
}
Upvotes: 0
Reputation: 31
For this quoestion you should refer to this stackoverflow answer and to the Rails API of protect_from_forgery.
The short answer is to disable forgery protection in your controller, but this makes your application vulnerable to CSRF attacks:
skip_before_action :verify_authenticity_token
The better way would be to authenticate with a JSON or XML request as these requests are not protected by CSRF protection. You can find a solution for devise here.
Monkey patch devise to save unencoded confirmation token. In your config/initializers/devise.rb
module Devise
module Models
module Confirmable
def generate_confirmation_token
raw, enc = Devise.token_generator.generate(self.class, :confirmation_token)
@raw_confirmation_token = raw
self.my_unencoded_column = raw # Patch
self.confirmation_token = enc
self.confirmation_sent_at = Time.now.utc
end
end
end
end
Upvotes: 0