socialmux 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.travis.yml +9 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +220 -0
- data/Rakefile +1 -0
- data/lib/socialmux/auth_mapper/base.rb +25 -0
- data/lib/socialmux/auth_mapper/facebook.rb +7 -0
- data/lib/socialmux/auth_mapper/github.rb +7 -0
- data/lib/socialmux/auth_mapper/google_oauth2.rb +7 -0
- data/lib/socialmux/auth_mapper/guess_name.rb +22 -0
- data/lib/socialmux/auth_mapper/linkedin.rb +7 -0
- data/lib/socialmux/auth_mapper/twitter.rb +7 -0
- data/lib/socialmux/auth_mapper.rb +26 -0
- data/lib/socialmux/event.rb +13 -0
- data/lib/socialmux/result.rb +15 -0
- data/lib/socialmux/strategy.rb +69 -0
- data/lib/socialmux/version.rb +4 -0
- data/lib/socialmux.rb +9 -0
- data/socialmux.gemspec +31 -0
- data/spec/fixtures/facebook_response.json +158 -0
- data/spec/fixtures/github_response.json +65 -0
- data/spec/fixtures/google_response.json +35 -0
- data/spec/fixtures/linkedin_response.json +45 -0
- data/spec/fixtures/twitter_response.json +239 -0
- data/spec/lib/.gitkeep +0 -0
- data/spec/lib/auth_mapper/facebook_spec.rb +18 -0
- data/spec/lib/auth_mapper/github_spec.rb +18 -0
- data/spec/lib/auth_mapper/google_oauth2_spec.rb +19 -0
- data/spec/lib/auth_mapper/linkedin_spec.rb +20 -0
- data/spec/lib/auth_mapper/twitter_spec.rb +22 -0
- data/spec/lib/result_spec.rb +17 -0
- data/spec/lib/strategy_spec.rb +112 -0
- data/spec/spec_helper.rb +12 -0
- data/spec/support/fixture_helpers.rb +13 -0
- data/spec/support/rspec_config.rb +11 -0
- metadata +210 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 4feb9f255148e3abc9a97d01afe6a156bbfa80e8
|
4
|
+
data.tar.gz: fb8b815274c1aaee7ead7c86a9132ca6c1c80cb1
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 7768d1a811be1d8f5dd570e74692ef26e3f97d3a3c40467f5ff103a92c800b545388c83cfe0425df3bfbcdf166d28aa3b9e633ee1a006270e55c3f4fd16b7c7d
|
7
|
+
data.tar.gz: d064285e28aaeb95718e11a38ca81609d22433e3cfe9042b80568c73826334b4ec864420ccdd37a498ae87239d0274944d6d232581a810ff9e1fa75925d8f5d5
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Stefano Verna
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,220 @@
|
|
1
|
+
# Socialmux
|
2
|
+
|
3
|
+
[](https://travis-ci.org/stefanoverna/socialmux)
|
4
|
+
[](https://coveralls.io/r/stefanoverna/socialmux?branch=master)
|
5
|
+
|
6
|
+
Socialmux implements a strategy to add multiple social providers to the same
|
7
|
+
user. It's meant to be used togheter with OmniAuth.
|
8
|
+
|
9
|
+
* Socialmux works with any DB schema and ORM, as it delegates DB management to
|
10
|
+
a simple adapter class the developer needs to implement.
|
11
|
+
|
12
|
+
* Socialmux works with any authentication system, as it just prepares the User
|
13
|
+
model, and leave the authentication part to your controllers.
|
14
|
+
|
15
|
+
## The strategy
|
16
|
+
|
17
|
+
* If a matching social account is already present in the database:
|
18
|
+
* it returns the linked user and no change is made;
|
19
|
+
|
20
|
+
* If a user is already signed in:
|
21
|
+
* it fills up any user blank attribute with data coming from the
|
22
|
+
authentication;
|
23
|
+
* it builds a new social authentication model, and adds it to the user model;
|
24
|
+
* it returns the modified user without saving it;
|
25
|
+
|
26
|
+
* If an user exists with the same email:
|
27
|
+
* it fills up any user blank attribute with data coming from the
|
28
|
+
authentication;
|
29
|
+
* it builds a new social authentication model, and adds it to the user model;
|
30
|
+
* it returns the modified user without saving it;
|
31
|
+
|
32
|
+
* Otherwise a new User instance gets instantiated:
|
33
|
+
* it fills up any user blank attribute with data coming from the
|
34
|
+
authentication;
|
35
|
+
* it builds a new social authentication model, and adds it to the user model;
|
36
|
+
* it returns the modified user without saving it;
|
37
|
+
|
38
|
+
## Installation
|
39
|
+
|
40
|
+
Add this line to your application's Gemfile:
|
41
|
+
|
42
|
+
gem 'socialmux'
|
43
|
+
|
44
|
+
And then execute:
|
45
|
+
|
46
|
+
$ bundle
|
47
|
+
|
48
|
+
Or install it yourself as:
|
49
|
+
|
50
|
+
$ gem install socialmux
|
51
|
+
|
52
|
+
## Typical Usage
|
53
|
+
|
54
|
+
This is a real word example for the following schema:
|
55
|
+
|
56
|
+
* `User (email, password)`
|
57
|
+
* `Profile (user_id, first_name, last_name)`
|
58
|
+
* `SocialAuth (user_id, provider, uid)`
|
59
|
+
|
60
|
+
User requires an email, so if an external auth provider does not give us this
|
61
|
+
information, a form will be shown for the user to fill it in.
|
62
|
+
|
63
|
+
### `config/routes.rb`
|
64
|
+
|
65
|
+
```ruby
|
66
|
+
MyApp::Application.routes.draw do
|
67
|
+
devise_for :users
|
68
|
+
|
69
|
+
# OmniAuth route
|
70
|
+
match '/auth/:provider/callback' => 'social#callback', via: [:get, :post]
|
71
|
+
|
72
|
+
# This is where the magic happens :)
|
73
|
+
match '/auth/complete' => 'social#complete', as: :social_complete, via: [:get, :post], as: :social_complete
|
74
|
+
|
75
|
+
# ...
|
76
|
+
end
|
77
|
+
```
|
78
|
+
|
79
|
+
### `app/services/social_schema_adapter.rb`
|
80
|
+
|
81
|
+
This is the adapter to the database. Socialmux requires these methods'
|
82
|
+
interfaces. The implementation will differ based on the project.
|
83
|
+
|
84
|
+
```ruby
|
85
|
+
class SocialSchemaAdapter
|
86
|
+
def init_user
|
87
|
+
User.new
|
88
|
+
end
|
89
|
+
|
90
|
+
def find_user_with_email(email)
|
91
|
+
User.where(email: email).first
|
92
|
+
end
|
93
|
+
|
94
|
+
def find_user_with_authentication(data)
|
95
|
+
auth = SocialAuth.where(provider: data.provider, uid: data.uid).first
|
96
|
+
auth.user if auth.present?
|
97
|
+
end
|
98
|
+
|
99
|
+
def update_user_with_params(user, user_params)
|
100
|
+
user.attributes = user_params
|
101
|
+
end
|
102
|
+
|
103
|
+
def update_user_with_data_if_blank(user, data)
|
104
|
+
profile = user.profile || user.build_profile
|
105
|
+
|
106
|
+
update_attr_if_blank(user, :email, data.email)
|
107
|
+
update_attr_if_blank(profile, :first_name, data.first_name)
|
108
|
+
update_attr_if_blank(profile, :last_name, data.last_name)
|
109
|
+
end
|
110
|
+
|
111
|
+
def build_authentication(user, data)
|
112
|
+
auth_attrs = { provider: data.provider, uid: data.uid }
|
113
|
+
user.social_auths.build(auth_attrs)
|
114
|
+
end
|
115
|
+
|
116
|
+
private
|
117
|
+
|
118
|
+
def update_attr_if_blank(record, key, value)
|
119
|
+
current_value = record.send(key)
|
120
|
+
record.send("#{key}=", value) if current_value.blank?
|
121
|
+
end
|
122
|
+
end
|
123
|
+
```
|
124
|
+
|
125
|
+
### `app/controllers/social_controller.rb`
|
126
|
+
|
127
|
+
```ruby
|
128
|
+
class SocialController < ApplicationController
|
129
|
+
def callback
|
130
|
+
session[:omniauth] ||= request.env['omniauth.auth']
|
131
|
+
redirect_to social_complete_path
|
132
|
+
end
|
133
|
+
|
134
|
+
def complete
|
135
|
+
|
136
|
+
# Here we instanciate our Socialmux strategy with all the required
|
137
|
+
# arguments
|
138
|
+
|
139
|
+
authentication = Socialmux::Strategy.new(
|
140
|
+
adapter: SocialSchemaAdapter.new,
|
141
|
+
current_user: current_user,
|
142
|
+
omniauth_data: session[:omniauth],
|
143
|
+
user_params: user_params
|
144
|
+
)
|
145
|
+
|
146
|
+
# `.result` returns a Socialmux::Result, which gives us the
|
147
|
+
# found/generated user that we need to sign in
|
148
|
+
result = authentication.result
|
149
|
+
@user = result.user
|
150
|
+
|
151
|
+
# If you cannot save the model, it's because some required fields
|
152
|
+
# could not be extracted from the thirdy party authentication source.
|
153
|
+
# We render a form that points to this very same action.
|
154
|
+
|
155
|
+
if @user.save
|
156
|
+
sign_in_and_notice(@user, result.event)
|
157
|
+
else
|
158
|
+
render :complete
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
private
|
163
|
+
|
164
|
+
def user_params
|
165
|
+
if request.post?
|
166
|
+
params.require(:user).permit(:email, profile_attributes: [ :first_name, :last_name ])
|
167
|
+
else
|
168
|
+
{}
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def sign_in_and_notice(user, event)
|
173
|
+
flash[:notice] = "Result: #{event}"
|
174
|
+
session.delete(:omniauth)
|
175
|
+
sign_in_and_redirect(user)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
```
|
179
|
+
|
180
|
+
### `app/views/social/complete.html.slim`
|
181
|
+
|
182
|
+
```slim
|
183
|
+
h1 Sign up with Social
|
184
|
+
|
185
|
+
= simple_form_for @user, url: social_complete_path do |f|
|
186
|
+
|
187
|
+
p You're close to finish the registration process... In order to be able to restore your account in the future, please insert your email!
|
188
|
+
|
189
|
+
= f.input :email
|
190
|
+
|
191
|
+
fieldset
|
192
|
+
legend Optional data
|
193
|
+
|
194
|
+
= f.simple_fields_for :profile do |p|
|
195
|
+
= p.input :first_name
|
196
|
+
= p.input :last_name
|
197
|
+
|
198
|
+
= f.button :submit
|
199
|
+
```
|
200
|
+
|
201
|
+
## About sessions
|
202
|
+
|
203
|
+
In the above example, OmniAuth `AuthResult` is stored in session until the user
|
204
|
+
completes the sign up process.
|
205
|
+
|
206
|
+
If you use cookies, this won't work and raise a `CookieOverflow` error. Use a
|
207
|
+
session store backed by an Active Record class:
|
208
|
+
|
209
|
+
```ruby
|
210
|
+
gem 'activerecord-session_store', github: 'rails/activerecord-session_store'
|
211
|
+
```
|
212
|
+
|
213
|
+
## Contributing
|
214
|
+
|
215
|
+
1. Fork it
|
216
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
217
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
218
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
219
|
+
5. Create new Pull Request
|
220
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'hashie'
|
2
|
+
require 'active_support/core_ext/module/delegation'
|
3
|
+
|
4
|
+
module Socialmux
|
5
|
+
module AuthMapper
|
6
|
+
class Base
|
7
|
+
attr_reader :data
|
8
|
+
|
9
|
+
delegate :uid, to: :data
|
10
|
+
delegate :info, to: :data
|
11
|
+
delegate :provider, to: :data
|
12
|
+
|
13
|
+
delegate :first_name, to: :info
|
14
|
+
delegate :last_name, to: :info
|
15
|
+
delegate :email, to: :info
|
16
|
+
delegate :image, to: :info
|
17
|
+
delegate :description, to: :info
|
18
|
+
|
19
|
+
def initialize(data)
|
20
|
+
@data = Hashie::Mash.new(data)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Socialmux
|
2
|
+
module AuthMapper
|
3
|
+
class GuessName < Base
|
4
|
+
def first_name
|
5
|
+
name_chunks.first
|
6
|
+
end
|
7
|
+
|
8
|
+
def last_name
|
9
|
+
last_name = name_chunks.dup
|
10
|
+
last_name.shift
|
11
|
+
last_name.join(" ")
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def name_chunks
|
17
|
+
info.name.split(/\s+/)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'hashie'
|
2
|
+
|
3
|
+
require 'socialmux/auth_mapper/base'
|
4
|
+
require 'socialmux/auth_mapper/guess_name'
|
5
|
+
require 'socialmux/auth_mapper/facebook'
|
6
|
+
require 'socialmux/auth_mapper/twitter'
|
7
|
+
require 'socialmux/auth_mapper/github'
|
8
|
+
require 'socialmux/auth_mapper/google_oauth2'
|
9
|
+
require 'socialmux/auth_mapper/linkedin'
|
10
|
+
|
11
|
+
module Socialmux
|
12
|
+
module AuthMapper
|
13
|
+
class NotFound < RuntimeError; end
|
14
|
+
|
15
|
+
def self.init_with_data(data)
|
16
|
+
data = Hashie::Mash.new(data)
|
17
|
+
|
18
|
+
klass_name = "Social::AuthMapper::#{data.provider.classify}"
|
19
|
+
klass = klass_name.constantize
|
20
|
+
klass.new(data)
|
21
|
+
rescue NameError
|
22
|
+
raise NotFound, "Mapper #{klass_name} for provider #{data.provider} not found!"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Socialmux
|
2
|
+
module Event
|
3
|
+
SIGN_IN = :sign_in
|
4
|
+
SIGN_UP = :sign_up
|
5
|
+
TOUCHED_CURRENT_USER = :updated_current_user
|
6
|
+
TOUCHED_EMAIL_USER = :updated_email_user
|
7
|
+
|
8
|
+
def self.all
|
9
|
+
[ SIGN_IN, SIGN_UP, TOUCHED_CURRENT_USER, TOUCHED_EMAIL_USER ]
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'active_support/core_ext/hash/reverse_merge'
|
2
|
+
require 'active_support/core_ext/hash/keys'
|
3
|
+
|
4
|
+
module Socialmux
|
5
|
+
class Strategy
|
6
|
+
attr_reader :adapter
|
7
|
+
attr_reader :current_user
|
8
|
+
attr_reader :omniauth_data
|
9
|
+
attr_reader :user_params
|
10
|
+
|
11
|
+
def initialize(options)
|
12
|
+
options.assert_valid_keys(:adapter,
|
13
|
+
:current_user,
|
14
|
+
:omniauth_data,
|
15
|
+
:user_params)
|
16
|
+
|
17
|
+
options.each do |key, value|
|
18
|
+
instance_variable_set "@#{key}", value
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def result
|
23
|
+
returning_user ||
|
24
|
+
augmented_current_user ||
|
25
|
+
augmented_user_with_same_email ||
|
26
|
+
new_user
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def returning_user
|
32
|
+
user = adapter.find_user_with_authentication(data)
|
33
|
+
Result.new(user, Event::SIGN_IN) if user
|
34
|
+
end
|
35
|
+
|
36
|
+
def augmented_current_user
|
37
|
+
return nil if !current_user
|
38
|
+
|
39
|
+
augment_user(current_user)
|
40
|
+
Result.new(current_user, Event::TOUCHED_CURRENT_USER)
|
41
|
+
end
|
42
|
+
|
43
|
+
def augmented_user_with_same_email
|
44
|
+
user = adapter.find_user_with_email(data.email)
|
45
|
+
return if !user
|
46
|
+
|
47
|
+
augment_user(user)
|
48
|
+
return Result.new(user, Event::TOUCHED_EMAIL_USER)
|
49
|
+
end
|
50
|
+
|
51
|
+
def new_user
|
52
|
+
user = adapter.init_user
|
53
|
+
augment_user(user)
|
54
|
+
Result.new(user, Event::SIGN_UP)
|
55
|
+
end
|
56
|
+
|
57
|
+
def data
|
58
|
+
@data ||= AuthMapper.init_with_data(omniauth_data)
|
59
|
+
end
|
60
|
+
|
61
|
+
def augment_user(user)
|
62
|
+
adapter.update_user_with_data_if_blank(user, data)
|
63
|
+
adapter.update_user_with_params(user, user_params)
|
64
|
+
adapter.build_authentication(user, data)
|
65
|
+
user
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
data/lib/socialmux.rb
ADDED
data/socialmux.gemspec
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'socialmux/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "socialmux"
|
8
|
+
spec.version = Socialmux::VERSION
|
9
|
+
spec.authors = ["Stefano Verna"]
|
10
|
+
spec.email = ["stefano.verna@welaika.com"]
|
11
|
+
spec.description = %q{Socialmux implements a strategy to add multiple social providers to the same user. It's meant to be used togheter with OmniAuth.}
|
12
|
+
spec.summary = %q{Socialmux implements a strategy to add multiple social providers to the same user. It's meant to be used togheter with OmniAuth.}
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency 'bundler', '~> 1.3'
|
22
|
+
spec.add_development_dependency 'rake'
|
23
|
+
spec.add_development_dependency 'coveralls'
|
24
|
+
spec.add_development_dependency 'mocha'
|
25
|
+
spec.add_development_dependency 'bourne'
|
26
|
+
spec.add_development_dependency 'rspec'
|
27
|
+
|
28
|
+
spec.add_dependency 'activesupport'
|
29
|
+
spec.add_dependency 'hashie'
|
30
|
+
end
|
31
|
+
|