Commit a64aff2f1c1ddc77b00211489d74fbc23c0c2fa2

Authored by Florian Unglaub
1 parent 4ce034ca

Omniauth Support

@@ -8,6 +8,10 @@ gem "mysql2" @@ -8,6 +8,10 @@ gem "mysql2"
8 8
9 # Auth 9 # Auth
10 gem "devise", "~> 2.1.0" 10 gem "devise", "~> 2.1.0"
  11 +gem 'omniauth'
  12 +gem 'omniauth-google-oauth2'
  13 +gem 'omniauth-twitter'
  14 +gem 'omniauth-github'
11 15
12 # GITLAB patched libs 16 # GITLAB patched libs
13 gem "grit", :git => "https://github.com/gitlabhq/grit.git", :ref => "7f35cb98ff17d534a07e3ce6ec3d580f67402837" 17 gem "grit", :git => "https://github.com/gitlabhq/grit.git", :ref => "7f35cb98ff17d534a07e3ce6ec3d580f67402837"
@@ -166,6 +166,8 @@ GEM @@ -166,6 +166,8 @@ GEM
166 eventmachine (0.12.10) 166 eventmachine (0.12.10)
167 execjs (1.4.0) 167 execjs (1.4.0)
168 multi_json (~> 1.0) 168 multi_json (~> 1.0)
  169 + faraday (0.8.1)
  170 + multipart-post (~> 1.1)
169 ffaker (1.14.0) 171 ffaker (1.14.0)
170 ffi (1.0.11) 172 ffi (1.0.11)
171 foreman (0.47.0) 173 foreman (0.47.0)
@@ -191,6 +193,7 @@ GEM @@ -191,6 +193,7 @@ GEM
191 httparty (0.8.3) 193 httparty (0.8.3)
192 multi_json (~> 1.0) 194 multi_json (~> 1.0)
193 multi_xml 195 multi_xml
  196 + httpauth (0.1)
194 i18n (0.6.0) 197 i18n (0.6.0)
195 journey (1.0.4) 198 journey (1.0.4)
196 jquery-rails (2.0.2) 199 jquery-rails (2.0.2)
@@ -200,6 +203,8 @@ GEM @@ -200,6 +203,8 @@ GEM
200 jquery-rails 203 jquery-rails
201 railties (>= 3.1.0) 204 railties (>= 3.1.0)
202 json (1.7.4) 205 json (1.7.4)
  206 + jwt (0.1.5)
  207 + multi_json (>= 1.0)
203 kaminari (0.13.0) 208 kaminari (0.13.0)
204 actionpack (>= 3.0.0) 209 actionpack (>= 3.0.0)
205 activesupport (>= 3.0.0) 210 activesupport (>= 3.0.0)
@@ -223,12 +228,35 @@ GEM @@ -223,12 +228,35 @@ GEM
223 sprockets (~> 2.0) 228 sprockets (~> 2.0)
224 multi_json (1.3.6) 229 multi_json (1.3.6)
225 multi_xml (0.5.1) 230 multi_xml (0.5.1)
  231 + multipart-post (1.1.5)
226 mysql2 (0.3.11) 232 mysql2 (0.3.11)
227 net-ldap (0.2.2) 233 net-ldap (0.2.2)
228 nokogiri (1.5.3) 234 nokogiri (1.5.3)
  235 + oauth (0.4.6)
  236 + oauth2 (0.8.0)
  237 + faraday (~> 0.8)
  238 + httpauth (~> 0.1)
  239 + jwt (~> 0.1.4)
  240 + multi_json (~> 1.0)
  241 + rack (~> 1.2)
229 omniauth (1.1.0) 242 omniauth (1.1.0)
230 hashie (~> 1.2) 243 hashie (~> 1.2)
231 rack 244 rack
  245 + omniauth-github (1.0.1)
  246 + omniauth (~> 1.0)
  247 + omniauth-oauth2 (~> 1.0)
  248 + omniauth-google-oauth2 (0.1.13)
  249 + omniauth (~> 1.0)
  250 + omniauth-oauth2
  251 + omniauth-oauth (1.0.1)
  252 + oauth
  253 + omniauth (~> 1.0)
  254 + omniauth-oauth2 (1.1.0)
  255 + oauth2 (~> 0.8.0)
  256 + omniauth (~> 1.0)
  257 + omniauth-twitter (0.0.12)
  258 + multi_json (~> 1.3)
  259 + omniauth-oauth (~> 1.0)
232 orm_adapter (0.3.0) 260 orm_adapter (0.3.0)
233 polyglot (0.3.3) 261 polyglot (0.3.3)
234 posix-spawn (0.3.6) 262 posix-spawn (0.3.6)
@@ -411,7 +439,11 @@ DEPENDENCIES @@ -411,7 +439,11 @@ DEPENDENCIES
411 minitest (>= 2.10) 439 minitest (>= 2.10)
412 modernizr (= 2.5.3) 440 modernizr (= 2.5.3)
413 mysql2 441 mysql2
  442 + omniauth
  443 + omniauth-github
  444 + omniauth-google-oauth2
414 omniauth-ldap! 445 omniauth-ldap!
  446 + omniauth-twitter
415 pry 447 pry
416 pygments.rb! 448 pygments.rb!
417 rack-mini-profiler 449 rack-mini-profiler
app/assets/stylesheets/auth_methods.scss 0 → 100644
@@ -0,0 +1,10 @@ @@ -0,0 +1,10 @@
  1 +.auth_methods {
  2 + &ul {
  3 + margin: 0;
  4 + text-align:center;
  5 + padding: 5px;
  6 + &li {
  7 + display: inline;
  8 + }
  9 + }
  10 +}
app/assets/stylesheets/main.scss
@@ -3,8 +3,8 @@ @@ -3,8 +3,8 @@
3 3
4 /** GITLAB colors **/ 4 /** GITLAB colors **/
5 $text_color:#222; 5 $text_color:#222;
6 -$lite_text_color: #666;  
7 -$link_color:#2A79A3; 6 +$lite_text_color: #666;
  7 +$link_color:#2A79A3;
8 $active_link_color:#2FA0BB; 8 $active_link_color:#2FA0BB;
9 $active_bg_color:#79C3E0; 9 $active_bg_color:#79C3E0;
10 $active_bd_color: #2FA0BB; 10 $active_bd_color: #2FA0BB;
@@ -31,7 +31,7 @@ $hover: #FDF5D9; @@ -31,7 +31,7 @@ $hover: #FDF5D9;
31 box-shadow: 0 0 3px #ddd; 31 box-shadow: 0 0 3px #ddd;
32 } 32 }
33 33
34 -@mixin solid_shade { 34 +@mixin solid_shade {
35 -moz-box-shadow: 0 0 0 3px #eee; 35 -moz-box-shadow: 0 0 0 3px #eee;
36 -webkit-box-shadow: 0 0 0 3px #eee; 36 -webkit-box-shadow: 0 0 0 3px #eee;
37 box-shadow: 0 0 0 3px #eee; 37 box-shadow: 0 0 0 3px #eee;
@@ -73,21 +73,21 @@ $hover: #FDF5D9; @@ -73,21 +73,21 @@ $hover: #FDF5D9;
73 73
74 74
75 /** 75 /**
76 - * Header of application. 76 + * Header of application.
77 * Contain application logo, search panel, profile icon 77 * Contain application logo, search panel, profile icon
78 */ 78 */
79 @import "sections/header.scss"; 79 @import "sections/header.scss";
80 80
81 /** 81 /**
82 - * Navigation menu of application. 82 + * Navigation menu of application.
83 * Panel with links to pages depends on project, profile or admin area 83 * Panel with links to pages depends on project, profile or admin area
84 */ 84 */
85 @import "sections/nav.scss"; 85 @import "sections/nav.scss";
86 86
87 /** 87 /**
88 - * This file represent some UI that can be changed  
89 - * during web app restyle or theme select.  
90 - * 88 + * This file represent some UI that can be changed
  89 + * during web app restyle or theme select.
  90 + *
91 * Next items should be placed there 91 * Next items should be placed there
92 * - link, button colors 92 * - link, button colors
93 * - header restyles 93 * - header restyles
@@ -118,11 +118,11 @@ $hover: #FDF5D9; @@ -118,11 +118,11 @@ $hover: #FDF5D9;
118 * Most of application styles placed here. 118 * Most of application styles placed here.
119 * This file represent common UI that should not be changed between themes 119 * This file represent common UI that should not be changed between themes
120 * or project restyling like form width or user avatar class or commit title 120 * or project restyling like form width or user avatar class or commit title
121 - * 121 + *
122 * TODO: clean it 122 * TODO: clean it
123 */ 123 */
124 @import "common.scss"; 124 @import "common.scss";
125 - 125 +@import "auth_methods.scss";
126 126
127 /** 127 /**
128 * Styles related to specific part of app 128 * Styles related to specific part of app
@@ -140,17 +140,17 @@ $hover: #FDF5D9; @@ -140,17 +140,17 @@ $hover: #FDF5D9;
140 @import "ref_select.scss"; 140 @import "ref_select.scss";
141 141
142 /** 142 /**
143 - * Code (files list) styles. Browsing project files there 143 + * Code (files list) styles. Browsing project files there
144 */ 144 */
145 @import "sections/tree.scss"; 145 @import "sections/tree.scss";
146 146
147 /** 147 /**
148 - * This file represent notes(comments) styles 148 + * This file represent notes(comments) styles
149 */ 149 */
150 @import "sections/notes.scss"; 150 @import "sections/notes.scss";
151 151
152 /** 152 /**
153 - * Devise styles 153 + * Devise styles
154 */ 154 */
155 @import "sections/login.scss"; 155 @import "sections/login.scss";
156 156
app/controllers/omniauth_callbacks_controller.rb
@@ -9,7 +9,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController @@ -9,7 +9,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
9 error ||= env["omniauth.error.type"].to_s 9 error ||= env["omniauth.error.type"].to_s
10 error.to_s.humanize if error 10 error.to_s.humanize if error
11 end 11 end
12 - 12 +
13 def ldap 13 def ldap
14 # We only find ourselves here if the authentication to LDAP was successful. 14 # We only find ourselves here if the authentication to LDAP was successful.
15 info = request.env["omniauth.auth"]["info"] 15 info = request.env["omniauth.auth"]["info"]
@@ -20,4 +20,34 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController @@ -20,4 +20,34 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
20 sign_in_and_redirect @user 20 sign_in_and_redirect @user
21 end 21 end
22 22
  23 + Settings.omniauth_providers.each do |provider|
  24 + define_method provider['name'] do
  25 + handle_omniauth
  26 + end
  27 + end
  28 +
  29 + private
  30 +
  31 + def handle_omniauth
  32 + oauth = request.env['omniauth.auth']
  33 + provider, uid = oauth['provider'], oauth['uid']
  34 +
  35 + if current_user
  36 + # Change a logged-in user's authentication method:
  37 + current_user.uid = uid
  38 + current_user.provider = provider
  39 + current_user.save
  40 + redirect_to profile_path
  41 + else
  42 + @user = User.find_by_provider_and_uid(provider, uid)
  43 +
  44 + if @user
  45 + sign_in_and_redirect @user
  46 + else
  47 + flash[:notice] = "There's no such user!"
  48 + redirect_to new_user_session_path
  49 + end
  50 + end
  51 + end
  52 +
23 end 53 end
app/helpers/application_helper.rb
@@ -75,16 +75,16 @@ module ApplicationHelper @@ -75,16 +75,16 @@ module ApplicationHelper
75 end 75 end
76 76
77 def show_last_push_widget?(event) 77 def show_last_push_widget?(event)
78 - event && 78 + event &&
79 event.last_push_to_non_root? && 79 event.last_push_to_non_root? &&
80 !event.rm_ref? && 80 !event.rm_ref? &&
81 - event.project && 81 + event.project &&
82 event.project.merge_requests_enabled 82 event.project.merge_requests_enabled
83 end 83 end
84 84
85 def tab_class(tab_key) 85 def tab_class(tab_key)
86 active = case tab_key 86 active = case tab_key
87 - 87 +
88 # Project Area 88 # Project Area
89 when :wall; wall_tab? 89 when :wall; wall_tab?
90 when :wiki; controller.controller_name == "wikis" 90 when :wiki; controller.controller_name == "wikis"
@@ -123,4 +123,9 @@ module ApplicationHelper @@ -123,4 +123,9 @@ module ApplicationHelper
123 def hexdigest(string) 123 def hexdigest(string)
124 Digest::SHA1.hexdigest string 124 Digest::SHA1.hexdigest string
125 end 125 end
  126 +
  127 + def authbutton(provider, size = 64)
  128 + image_tag("authbuttons/#{provider.to_s.split('_').first}_#{size}.png",
  129 + alt: "Sign in with #{provider.to_s.titleize}" )
  130 + end
126 end 131 end
app/views/devise/sessions/new.html.erb
@@ -14,10 +14,15 @@ @@ -14,10 +14,15 @@
14 <div class="right"> <%= render :partial => "devise/shared/links" %></div> 14 <div class="right"> <%= render :partial => "devise/shared/links" %></div>
15 15
16 <%- if devise_mapping.omniauthable? %> 16 <%- if devise_mapping.omniauthable? %>
17 - <%- resource_class.omniauth_providers.each do |provider| %>  
18 - <hr/>  
19 - <%= link_to "Sign in with #{provider.to_s.titleize}", omniauth_authorize_path(resource_name, provider), :class => "btn primary" %><br />  
20 - <% end -%> 17 + <hr/>
  18 + <div class="auth_methods">
  19 + <ul>
  20 + <%- resource_class.omniauth_providers.each do |provider| %>
  21 + <li><%= link_to authbutton(provider),
  22 + omniauth_authorize_path(resource_name, provider) %></li>
  23 + <% end -%>
  24 + </ul>
  25 + </div>
21 <% end -%> 26 <% end -%>
22 27
23 <% end %> 28 <% end %>
app/views/layouts/profile.html.haml
@@ -10,7 +10,7 @@ @@ -10,7 +10,7 @@
10 = link_to "Profile", profile_path 10 = link_to "Profile", profile_path
11 11
12 %li{class: tab_class(:password)} 12 %li{class: tab_class(:password)}
13 - = link_to "Password", profile_password_path 13 + = link_to "Authentication", profile_password_path
14 14
15 %li{class: tab_class(:ssh_keys)} 15 %li{class: tab_class(:ssh_keys)}
16 = link_to keys_path do 16 = link_to keys_path do
app/views/profile/password.html.haml
1 %h3.page_title Password 1 %h3.page_title Password
2 %hr 2 %hr
  3 +
3 = form_for @user, url: profile_password_path, method: :put do |f| 4 = form_for @user, url: profile_password_path, method: :put do |f|
4 - .data  
5 - %p.slead After successful password update you will be redirected to login page where you should login with new password  
6 - -if @user.errors.any?  
7 - .alert-message.block-message.error  
8 - %ul  
9 - - @user.errors.full_messages.each do |msg|  
10 - %li= msg 5 + .row
  6 + .span7
  7 + .data
  8 + %p.slead After successful password update you will be redirected to login page where you should login with new password
  9 + -if @user.errors.any?
  10 + .alert-message.block-message.error
  11 + %ul
  12 + - @user.errors.full_messages.each do |msg|
  13 + %li= msg
  14 +
  15 + .clearfix
  16 + = f.label :password
  17 + .input= f.password_field :password
  18 + .clearfix
  19 + = f.label :password_confirmation
  20 + .input= f.password_field :password_confirmation
11 21
12 - .clearfix  
13 - = f.label :password  
14 - .input= f.password_field :password  
15 - .clearfix  
16 - = f.label :password_confirmation  
17 - .input= f.password_field :password_confirmation 22 + - if Settings.omniauth.enabled
  23 + .span5.right
  24 + .auth_methods.alert.alert-info
  25 + %strong Tip: Use one of the following sites to login
  26 + %ul
  27 + - User.omniauth_providers.each do |provider|
  28 + %li= link_to authbutton(provider), |
  29 + omniauth_authorize_path(User, provider) |
18 .actions 30 .actions
19 = f.submit 'Save', class: "btn primary" 31 = f.submit 'Save', class: "btn primary"
app/views/profile/show.html.haml
@@ -49,6 +49,13 @@ @@ -49,6 +49,13 @@
49 %strong Tip: 49 %strong Tip:
50 You can change your avatar at gravatar.com 50 You can change your avatar at gravatar.com
51 51
  52 + - if Settings.omniauth.enabled && @user.provider?
  53 + %h4
  54 + Omniauth Providers:
  55 + = link_to "Change", profile_password_path, class: "btn small right"
  56 + You can login through #{@user.provider.titleize}!
  57 + = authbutton(@user.provider, 32)
  58 +
52 %h4 59 %h4
53 Personal projects: 60 Personal projects:
54 %small.right 61 %small.right
config/gitlab.yml.example
1 -# # # # # # # # # # # # # # # # # # 1 +# # # # # # # # # # # # # # # # # #
2 # Gitlab application config file # 2 # Gitlab application config file #
3 # # # # # # # # # # # # # # # # # # 3 # # # # # # # # # # # # # # # # # #
4 4
@@ -19,14 +19,14 @@ email: @@ -19,14 +19,14 @@ email:
19 19
20 # Application specific settings 20 # Application specific settings
21 # Like default project limit for user etc 21 # Like default project limit for user etc
22 -app:  
23 - default_projects_limit: 10 22 +app:
  23 + default_projects_limit: 10
24 # backup_path: "/vol/backups" # default: Rails.root + backups/ 24 # backup_path: "/vol/backups" # default: Rails.root + backups/
25 # backup_keep_time: 604800 # default: 0 (forever) (in seconds) 25 # backup_keep_time: 604800 # default: 0 (forever) (in seconds)
26 26
27 27
28 -#  
29 -# 2. Advanced settings: 28 +#
  29 +# 2. Advanced settings:
30 # ========================== 30 # ==========================
31 31
32 # Git Hosting configuration 32 # Git Hosting configuration
@@ -49,3 +49,15 @@ git: @@ -49,3 +49,15 @@ git:
49 git_max_size: 5242880 # 5.megabytes 49 git_max_size: 5242880 # 5.megabytes
50 # Git timeout to read commit, in seconds 50 # Git timeout to read commit, in seconds
51 git_timeout: 10 51 git_timeout: 10
  52 +
  53 +# Omniauth configuration
  54 +# omniauth:
  55 +# enabled: true
  56 +# providers:
  57 +# - { name: 'google_oauth2', app_id: 'YOUR APP ID',
  58 +# app_secret: 'YOUR APP SECRET',
  59 +# args: { access_type: 'offline', approval_prompt: '' } }
  60 +# - { name: 'twitter', app_id: 'YOUR APP ID',
  61 +# app_secret: 'YOUR APP SECRET'}
  62 +# - { name: 'github', app_id: 'YOUR APP ID',
  63 +# app_secret: 'YOUR APP SECRET' }
config/initializers/1_settings.rb
@@ -6,7 +6,7 @@ class Settings &lt; Settingslogic @@ -6,7 +6,7 @@ class Settings &lt; Settingslogic
6 self.web['protocol'] ||= web.https ? "https" : "http" 6 self.web['protocol'] ||= web.https ? "https" : "http"
7 end 7 end
8 8
9 - def web_host 9 + def web_host
10 self.web['host'] ||= 'localhost' 10 self.web['host'] ||= 'localhost'
11 end 11 end
12 12
@@ -14,11 +14,11 @@ class Settings &lt; Settingslogic @@ -14,11 +14,11 @@ class Settings &lt; Settingslogic
14 self.email['from'] ||= ("notify@" + web_host) 14 self.email['from'] ||= ("notify@" + web_host)
15 end 15 end
16 16
17 - def url 17 + def url
18 self['url'] ||= build_url 18 self['url'] ||= build_url
19 - end 19 + end
20 20
21 - def web_port 21 + def web_port
22 if web.https 22 if web.https
23 web['port'] = 443 23 web['port'] = 443
24 else 24 else
@@ -36,7 +36,7 @@ class Settings &lt; Settingslogic @@ -36,7 +36,7 @@ class Settings &lt; Settingslogic
36 raw_url << web_host 36 raw_url << web_host
37 37
38 if web_custom_port? 38 if web_custom_port?
39 - raw_url << ":#{web_port}" 39 + raw_url << ":#{web_port}"
40 end 40 end
41 41
42 raw_url 42 raw_url
@@ -111,5 +111,14 @@ class Settings &lt; Settingslogic @@ -111,5 +111,14 @@ class Settings &lt; Settingslogic
111 def backup_keep_time 111 def backup_keep_time
112 app['backup_keep_time'] || 0 112 app['backup_keep_time'] || 0
113 end 113 end
  114 +
  115 + def omniauth_enabled?
  116 + omniauth['enabled'] || false
  117 + end
  118 +
  119 + def omniauth_providers
  120 + omniauth['providers'] || []
  121 + end
  122 +
114 end 123 end
115 end 124 end
db/migrate/20120803152018_add_provider_and_uid_to_users.rb 0 → 100644
@@ -0,0 +1,6 @@ @@ -0,0 +1,6 @@
  1 +class AddProviderAndUidToUsers < ActiveRecord::Migration
  2 + def change
  3 + add_column :users, :provider, :string
  4 + add_column :users, :uid, :string
  5 + end
  6 +end
@@ -11,7 +11,7 @@ @@ -11,7 +11,7 @@
11 # 11 #
12 # It's strongly recommended to check this file into your version control system. 12 # It's strongly recommended to check this file into your version control system.
13 13
14 -ActiveRecord::Schema.define(:version => 20120712080407) do 14 +ActiveRecord::Schema.define(:version => 20120803152018) do
15 15
16 create_table "events", :force => true do |t| 16 create_table "events", :force => true do |t|
17 t.string "target_type" 17 t.string "target_type"
@@ -146,31 +146,33 @@ ActiveRecord::Schema.define(:version =&gt; 20120712080407) do @@ -146,31 +146,33 @@ ActiveRecord::Schema.define(:version =&gt; 20120712080407) do
146 end 146 end
147 147
148 create_table "users", :force => true do |t| 148 create_table "users", :force => true do |t|
149 - t.string "email", :default => "", :null => false  
150 - t.string "encrypted_password", :limit => 128, :default => "", :null => false 149 + t.string "email", :default => "", :null => false
  150 + t.string "encrypted_password", :default => "", :null => false
151 t.string "reset_password_token" 151 t.string "reset_password_token"
152 t.datetime "reset_password_sent_at" 152 t.datetime "reset_password_sent_at"
153 t.datetime "remember_created_at" 153 t.datetime "remember_created_at"
154 - t.integer "sign_in_count", :default => 0 154 + t.integer "sign_in_count", :default => 0
155 t.datetime "current_sign_in_at" 155 t.datetime "current_sign_in_at"
156 t.datetime "last_sign_in_at" 156 t.datetime "last_sign_in_at"
157 t.string "current_sign_in_ip" 157 t.string "current_sign_in_ip"
158 t.string "last_sign_in_ip" 158 t.string "last_sign_in_ip"
159 - t.datetime "created_at", :null => false  
160 - t.datetime "updated_at", :null => false 159 + t.datetime "created_at", :null => false
  160 + t.datetime "updated_at", :null => false
161 t.string "name" 161 t.string "name"
162 - t.boolean "admin", :default => false, :null => false  
163 - t.integer "projects_limit", :default => 10  
164 - t.string "skype", :default => "", :null => false  
165 - t.string "linkedin", :default => "", :null => false  
166 - t.string "twitter", :default => "", :null => false 162 + t.boolean "admin", :default => false, :null => false
  163 + t.integer "projects_limit", :default => 10
  164 + t.string "skype", :default => "", :null => false
  165 + t.string "linkedin", :default => "", :null => false
  166 + t.string "twitter", :default => "", :null => false
167 t.string "authentication_token" 167 t.string "authentication_token"
168 - t.boolean "dark_scheme", :default => false, :null => false  
169 - t.integer "theme_id", :default => 1, :null => false 168 + t.boolean "dark_scheme", :default => false, :null => false
  169 + t.integer "theme_id", :default => 1, :null => false
170 t.string "bio" 170 t.string "bio"
171 - t.boolean "blocked", :default => false, :null => false  
172 - t.integer "failed_attempts", :default => 0 171 + t.boolean "blocked", :default => false, :null => false
  172 + t.integer "failed_attempts", :default => 0
173 t.datetime "locked_at" 173 t.datetime "locked_at"
  174 + t.string "provider"
  175 + t.string "uid"
174 end 176 end
175 177
176 add_index "users", ["email"], :name => "index_users_on_email", :unique => true 178 add_index "users", ["email"], :name => "index_users_on_email", :unique => true
vendor/assets/images/authbuttons/github_32.png 0 → 100644

1.89 KB

vendor/assets/images/authbuttons/github_64.png 0 → 100644

4.34 KB

vendor/assets/images/authbuttons/google_32.png 0 → 100644

1.58 KB

vendor/assets/images/authbuttons/google_64.png 0 → 100644

3.37 KB

vendor/assets/images/authbuttons/twitter_32.png 0 → 100644

1.41 KB

vendor/assets/images/authbuttons/twitter_64.png 0 → 100644

3.3 KB