I want to let my meteor users login through a ruby app.
Where I am
I have two websites, both on the same domain, both share the same MongoDB.
One is a METEOR-app with accounts-password (which uses bcrypt)
The other is a RUBY ON RAILS-app which uses devise (which also uses bcrypt) for authentication.
On both apps I can register and login, separately.
When I transfer (copy/paste) the encrypted_password from Meteor's "bcrypt" field to Ruby's "encrypted_password" and try to login I get rejected. It does not work vice versa. Then I recreated the kind of salting by the meteor app in my ruby app (SHA-256 plain-password-hashing before they got compared against).
(here is the meteor accounts-password source file (https://github.com/meteor/meteor/blob/oplog-backlog-on-1.0.3.1/packages/accounts-password/password_server.js ))
and this is my Ruby implementation:
class BCryptSHA256Hasher < Hasher
def initialize
#algorithm = :bcrypt_sha256
#cost = 10
#digest = OpenSSL::Digest::SHA256.new
end
def salt
BCrypt::Engine.generate_salt(#cost)
end
def get_password_string(password)
#digest.digest(password) unless #digest.nil?
end
def encode(password, salt)
password = get_password_string(password)
hash = BCrypt::Engine.hash_secret(password, salt)
return hash
end
def verify(password, encoded)
password_digest = get_password_string(password)
hash = BCrypt::Engine.hash_secret(password_digest, encoded)
# password = "asdfasdf"
# encoded = "$2a$10$FqvtI7zNgmdWJJG1n9JwZewVYrzEn38JIxEGwmMviMsZsrCmYHqWm"
# hash = "$2a$10$FqvtI7zNgmdWJJG1n9JwZe22XU1hRDSNtHIrnYve9FbmjjqJCLhZi"
# constant_time_comparison:
constant_time_compare(encoded, hash)
end
def constant_time_compare(a, b)
check = a.bytesize ^ b.bytesize
a.bytes.zip(b.bytes) { |x, y| check |= x ^ y }
check == 0
end
end
Here is a valid User-document, which will be used by both servers:
{
"_id": "g4BPfpavJGGTNgJcE",
"authentication_token": "iZqmCsYS1Y9Xxh6t22-X",
"confirmed_at": new Date(1457963598783),
"createdAt": new Date(1457963456581),
"current_sign_in_at": new Date(1457966356123),
"current_sign_in_ip": "127.0.0.1",
"email": "demo#demo.com",
"emails": [
{
"address": "demo#demo.com",
"verified": true
}
],
"encrypted_password": "$2a$10$7/PJw51HgXfzYJWpaBHGj.QoRCTl0E29X0ZYTZPQhLRo69DGi8Xou",
"failed_attempts": 0,
"last_sign_in_at": new Date(1457966356123),
"last_sign_in_ip": "127.0.0.1",
"profile": {
"_id": ObjectId("56e6c1e7a54d7595e099da27"),
"firstName": "asdf",
"lastName": "asdf"
},
"reset_password_sent_at": null,
"reset_password_token": null,
"services": {
"_id": ObjectId("56e6c1e7a54d7595e099da28"),
"password": {
"bcrypt": "$2a$10$7/PJw51HgXfzYJWpaBHGj.QoRCTl0E29X0ZYTZPQhLRo69DGi8Xou"
},
"resume": {
"loginTokens": [
]
}
},
"sign_in_count": 1,
"updated_at": new Date(1457966356127),
"username": "mediatainment"
}
I think #maxpleaner's comment is the best way to handle authentication. But if really need to authenticate users separately, then just monkey patch devise.
config/initializers/devise_meteor_adapter.rb
module DeviseMeteorAdapter
def digest(klass, password)
klass.pepper = nil
password = ::Digest::SHA256.hexdigest(password)
super
end
def compare(klass, hashed_password, password)
klass.pepper = nil
password = ::Digest::SHA256.hexdigest(password)
super
end
end
Devise::Encryptor.singleton_class.prepend(DeviseMeteorAdapter)
WARNING: Not tested.
Related
Let's say I want to set up a validation contract for addresses, but then I also want to set up a validator for users, and for coffee shops; both of which include an address, is it possible to re-use the AddressContract in UserContract and CoffeeShopContract?
For example, the data I want to validate might look like:
# Address
{
"first_line": "100 Main street",
"zipcode": "12345",
}
# User
{
"first_name": "Joe",
"last_name": "Bloggs",
"address:" {
"first_line": "123 Boulevard",
"zipcode": "12346",
}
}
# Coffee Shop
{
"shop": "Central Perk",
"floor_space": "2000sqm",
"address:" {
"first_line": "126 Boulevard",
"zipcode": "12347",
}
}
Yes you can reuse schemas (See: Reusing Schemas)
It would look something like this:
require 'dry/validation'
class AddressContract < Dry::Validation::Contract
params do
required(:first_line).value(:string)
required(:zipcode).value(:string)
end
end
class UserContract < Dry::Validation::Contract
params do
required(:first_name).value(:string)
required(:last_name).value(:string)
required(:address).schema(AddressContract.schema)
end
end
a = {first_line: '123 Street Rd'}
u = {first_name: 'engineers', last_name: 'mnky', address: a }
AddressContract.new.(a)
#=> #<Dry::Validation::Result{:first_line=>"123 Street Rd"} errors={:zipcode=>["is missing"]}>
UserContract.new.(u)
#=> #<Dry::Validation::Result{:first_name=>"engineers", :last_name=>"mnky", :address=>{:first_line=>"123 Street Rd"}} errors={:address=>{:zipcode=>["is missing"]}}>
Alternatively you can create schema mixins as well e.g.
AddressSchema = Dry::Schema.Params do
required(:first_line).value(:string)
required(:zipcode).value(:string)
end
class AddressContract < Dry::Validation::Contract
params(AddressSchema)
end
class UserContract < Dry::Validation::Contract
params do
required(:first_name).value(:string)
required(:last_name).value(:string)
required(:address).schema(AddressSchema)
end
end
##base_url = 'https://api-de-tarefas.herokuapp.com/users'
##body =
{
"user": {
"email": Faker::Internet.email,
"password": Faker::Number.number(6),
"password_confirmation":
}
}.to_json
I'm trying to pass in the body the value generated by gem faker in the password field to the password_verification field.
Just determine the value upfront:
##base_url = 'https://api-de-tarefas.herokuapp.com/users'
password = Faker::Number.number(6)
##body = {
"user": {
"email": Faker::Internet.email,
"password": password,
"password_confirmation": password
}
}.to_json
Btw do you really need class variables (starting with ##)? I cannot think of many situations in which a class variable is a good approach.
How can I enforce that my Grape Entity always returns an array (collection) even if its just a singular object? I have heard that some people create a helper method that gets called inside their endpoint, but I have not found any examples of anyone doing that, online.
The default functionality of an Entity is that it returns an object if only a single document (mongoid object) is returned. If a collection of documents is returned then it returns an array, I dont want my client application having to do a check every time to see if an object or an array got returned from my API.
## Resource (HTTP Endpoint)
desc 'List departments a user can and cannot access'
params do
requires :user_id
end
get :department_access do
#user = BACKBONE::User.find(#access_key.user_id)
requires_admin!
user = BACKBONE::User.find(params[:user_id])
can_access = BACKBONE::Department.user_can_access(user)
no_access = BACKBONE::Department.user_cannot_access(user)
present_success can_access
present :can_access, can_access, with: BACKBONE::Entities::DepartmentBase
present :no_access, no_access, with: BACKBONE::Entities::DepartmentBase
end
-
## Entity
module BACKBONE
module Entities
class DepartmentBase < BACKBONE::Entities::Mongoid
expose :name
expose :prefix
with_options(format_with: :mongo_id) do
expose :company_id
end
end
end
end
JSON Response
{
"status": "success",
"request_time": 0.009812,
"records": 1,
"can_access": {
"id": "59699d1a78cee4f8d07528fc",
"created_at": "2017-07-14T21:42:02.666-07:00",
"updated_at": "2017-07-14T21:42:02.666-07:00",
"name": "Tenant Improvement",
"prefix": "CACC",
"company_id": "596927fb670f6eec21c4f409"
},
"no_access": {
"id": "59699cca78cee4f8d07528fb",
"created_at": "2017-07-14T21:40:42.005-07:00",
"updated_at": "2017-07-14T21:40:42.005-07:00",
"name": "Field Operations",
"prefix": "CACC",
"company_id": "596927fb670f6eec21c4f409"
}
}
a coworker and I came up with a solution with a helper, create a helper method that always returns an array:
def present_array(key, data, entity)
d = (data.class.respond_to? :count) ? [data] : data
d = [] if d.nil?
present key, d, entity
end
Trying to return the string "image-2016-05-05+19%3A13%3A49.058890.jpg" from of this relatively complex JSON object:
{
"Type": "Notification",
"MessageId": "e3a008de-7053-530e-b2b4-4778704d30a0",
"TopicArn": "arn:aws:sns:us-west-2:xxxx:xxxx",
"Subject": "Amazon S3 Notification",
"Message": "{\"Records\":[{\"eventVersion\":\"2.0\",\"eventSource\":\"aws:s3\",\"awsRegion\":\"us-west-2\",\"eventTime\":\"2016-05-06T02:13:50.030Z\",\"eventName\":\"ObjectCreated:Put\",\"userIdentity\":{\"principalId\":\"AWS:AIDAIZ6VOIJWE82389JSE\"},\"requestParameters\":{\"sourceIPAddress\":\"0.0.0.0\"},\"responseElements\":{\"x-amz-request-id\":\"F819FA912DBD16\",\"x-amz-id-2\":\"7oOWHPhWsgjBW6XSj8DiSj8Sj8801LKJn5NLRn8JmYsNxJXKWqlkjDFL092zHuWYZn7pIKcRwX6g=\"},\"s3\":{\"s3SchemaVersion\":\"1.0\",\"configurationId\":\"image-notification\",\"bucket\":{\"name\":\"project\",\"ownerIdentity\":{\"principalId\":\"A17D10FQZ\"},\"arn\":\"arn:aws:s3:::project\"},\"object\":{\"key\":\"image-2016-05-05+19%3A13%3A49.058890.jpg\",\"size\":54098,\"eTag\":\"fbc4bakjf8asdj8f890ece3474c55974927c\",\"sequencer\":\"00572LKJDF389238CA7B04BD\"}}}]}",
"Timestamp": "2016-05-06T02:13:50.126Z",
"SignatureVersion": "1",
"Signature": "Lao5PoEchryYf1slxxxlyI0GB2Xrv03VFC+4JVlji0y1El+rQGL837PYRHdj2m/dGD9/ynJxPhIBWcoJxX4D7MBsNqaZXilqJtjp+t8Rku0avErgWQVQG+rjZcdVbSU12DI/Ku0v9LhYg2/Js+ofYGPZH9U4C+Jfup5wjgHXah4BGNmF3TO+oq08Y56edhMxV25URDcU+z5aaVW2sK2tlnynSNzLuAF5TlKuuLmYr3Buci83FkU46l6Bz/ENba1BlGGqT8P+ljdf9092z+iP42T9qUzj1HL9p9SjEDIam/03n1039JS01gbPpgdo6/2Z6kZK3LvrVRBzI0voFitLg==",
"SigningCertURL": "https://sns.us-west-2.amazonaws.com/SimpleNotificationService-bbxxx750dd426323fafd95ee9390147a5624348ee.pem",
"UnsubscribeURL": "https://sns.us-west-2.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:us-west-2:332531341234:xxxx:0e43fsSDF40e-d4a7-46c0-95ab-4fd11739267b"
}
Without having to do this:
#key = JSON.parse(#request[:Message])["Records"][0]["s3"]["object"]["key"]
Is there a way to parse and search through this JSON object to return the aforementioned string by providing a keyword such as "image"?
You could use hashie deepLocate
request = JSON.parse(#request)
request.extend(Hashie::Extensions::DeepLocate)
request.deep_locate -> (key, value, object) { key == :key && value.include?("image") }
#=> { :key => "image-2016-05-05+19%3A13%3A49.058890.jpg" }
Apart from using the value to search, if you know the key, you could do this to find the deeply nested value of that key.
def nested_hash_value(obj,key)
if obj.respond_to?(:key?) && obj.key?(key)
obj[key]
elsif obj.respond_to?(:each)
r = nil
obj.find{ |*a| r=nested_hash_value(a.last,key) }
r
end
end
p nested_hash_value(JSON.parse(#request),:key)
#=> image-2016-05-05+19%3A13%3A49.058890.jpg
I looked into different resources and still get confused on how to parse a json format to a custom object, for example
class Resident
attr_accessor :phone, :addr
def initialize(phone, addr)
#phone = phone
#addr = addr
end
end
and JSON file
{
"Resident": [
{
"phone": "12345",
"addr": "xxxxx"
}, {
"phone": "12345",
"addr": "xxxxx"
}, {
"phone": "12345",
"addr": "xxxxx"
}
]
}
what's the correct way to parse the json file into a array of 3 Resident object?
Today i was looking for something that converts json to an object, and this works like a charm:
person = JSON.parse(json_string, object_class: OpenStruct)
This way you could do person.education.school or person[0].education.school if the response is an array
I'm leaving it here because might be useful for someone
The following code is more simple:
require 'json'
data = JSON.parse(json_data)
residents = data['Resident'].map { |rd| Resident.new(rd['phone'], rd['addr']) }
If you're using ActiveModel::Serializers::JSON you can just call from_json(json) and your object will be mapped with those values.
class Person
include ActiveModel::Serializers::JSON
attr_accessor :name, :age, :awesome
def attributes=(hash)
hash.each do |key, value|
send("#{key}=", value)
end
end
def attributes
instance_values
end
end
json = {name: 'bob', age: 22, awesome: true}.to_json
person = Person.new
person.from_json(json) # => #<Person:0x007fec5e7a0088 #age=22, #awesome=true, #name="bob">
person.name # => "bob"
person.age # => 22
person.awesome # => true
require 'json'
class Resident
attr_accessor :phone, :addr
def initialize(phone, addr)
#phone = phone
#addr = addr
end
end
s = '{"Resident":[{"phone":"12345","addr":"xxxxx"},{"phone":"12345","addr":"xxxxx"},{"phone":"12345","addr":"xxxxx"}]}'
j = JSON.parse(s)
objects = j['Resident'].inject([]) { |o,d| o << Resident.new( d['phone'], d['addr'] ) }
p objects[0].phone
"12345"
We recently released a Ruby library static_struct that solves the issue. Check it out.