diff --git a/Gemfile b/Gemfile index 9f3d3046cd..ee53b9187b 100644 --- a/Gemfile +++ b/Gemfile @@ -30,6 +30,7 @@ gem 'cloudinary', '~> 1.11.1' gem 'figaro', '1.1.1' gem 'google-api-client', '~> 0.30.2' gem 'kaminari', '1.1.1' +gem 'omniauth-github' gem 'omniauth-google-oauth2', '~> 0.7.0' gem 'omniauth-rails_csrf_protection', '~> 0.1.2' gem 'premailer-rails' @@ -84,6 +85,7 @@ group :development, :test do gem 'bullet' + gem 'awesome_print' gem 'webdrivers' end diff --git a/Gemfile.lock b/Gemfile.lock old mode 100644 new mode 100755 index db9f4846c1..9b94801e5a --- a/Gemfile.lock +++ b/Gemfile.lock @@ -51,6 +51,7 @@ GEM rake (>= 10.4, < 13.0) arel (9.0.0) ast (2.4.0) + awesome_print (1.8.0) aws_cf_signer (0.1.3) bcrypt (3.1.13) better_errors (2.6.0) @@ -225,6 +226,9 @@ GEM omniauth (1.9.1) hashie (>= 3.4.6) rack (>= 1.6.2, < 3) + omniauth-github (1.3.0) + omniauth (~> 1.5) + omniauth-oauth2 (>= 1.4.0, < 2.0) omniauth-google-oauth2 (0.7.0) jwt (>= 2.0) omniauth (>= 1.1.1) @@ -495,6 +499,7 @@ PLATFORMS DEPENDENCIES activerecord-import annotate (~> 2.7) + awesome_print bcrypt (= 3.1.13) better_errors (~> 2.5) bullet @@ -522,6 +527,7 @@ DEPENDENCIES jquery-rails (= 4.3.5) kaminari (= 1.1.1) letter_opener + omniauth-github omniauth-google-oauth2 (~> 0.7.0) omniauth-rails_csrf_protection (~> 0.1.2) pg (= 1.1.4) diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb index 7805994619..7e45c876bb 100644 --- a/app/controllers/omniauth_callbacks_controller.rb +++ b/app/controllers/omniauth_callbacks_controller.rb @@ -10,7 +10,20 @@ def google_oauth2 kind: t('omniauth.google')) sign_in_and_redirect @user, event: :authentication else - redirect_to new_user_session_path, notice: t('omniauth.access_denied') + redirect_to new_user_session_path, alert: t('omniauth.access_denied') + end + end + + def github + @user = User.from_omniauth request.env['omniauth.auth'] + + if @user.valid? + flash[:notice] = I18n.t('devise.omniauth_callbacks.success', + kind: t('omniauth.github')) + + sign_in_and_redirect @user, event: :authentication + else + redirect_to new_user_session_path, alert: t('omniauth.access_denied') end end diff --git a/app/models/user.rb b/app/models/user.rb index 01cd481739..f75bcf57e1 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -55,7 +55,7 @@ class User < ApplicationRecord # :confirmable, :lockable, :timeoutable and :omniauthable devise :invitable, :database_authenticatable, :registerable, :uid, :recoverable, :rememberable, :trackable, :validatable, :omniauthable, - omniauth_providers: [:google_oauth2] + omniauth_providers: %i[google_oauth2 github] mount_uploader :avatar, AvatarUploader @@ -81,24 +81,38 @@ class User < ApplicationRecord validates :locale, inclusion: { in: Rails.application.config.i18n.available_locales.map(&:to_s).push(nil) } - validate :password_complexity + + # add unless statement here instead of the concern for readability + # TODO: refactor the Password validator concern + validate :password_complexity, unless: :oauth_provided? def active_for_authentication? super && !banned end - def self.find_for_google_oauth2(access_token) - user = find_or_initialize_by(email: access_token.info.email) - user.name ||= access_token.info.name + # TODO: to refactor and to move to builder pattern + # UserBuilder::GoogleOauth2 + def self.find_for_google_oauth2(auth) + user = find_or_initialize_by(email: auth.info.email) + user.name ||= auth.info.name user.password ||= Devise.friendly_token[0, 20] - update_access_token_fields(user: user, access_token: access_token) + update_access_token_fields(user: user, access_token: auth) user end + # to refactor, could be single oauth method to begin with + def self.from_omniauth(auth) + where(provider: auth.provider, + uid: auth.provider + auth.uid).first_or_create do |user| + UserBuilder::Builder.build(user: user, auth: auth) + end + end + def google_access_token google_access_token_expired? ? update_access_token : token end + # TODO: refactor this and use one checker def google_oauth2_enabled? token.present? end @@ -140,6 +154,11 @@ def update_access_token private + # checks if user is created from oauth + def oauth_provided? + provider.present? || token.present? + end + def google_access_token_expired? !access_expires_at || Time.zone.now > access_expires_at end diff --git a/app/services/user_builder/base.rb b/app/services/user_builder/base.rb new file mode 100644 index 0000000000..d0e03de1b0 --- /dev/null +++ b/app/services/user_builder/base.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true +module UserBuilder + class Base + attr_reader :user + + def self.call(user:, auth:) + new(user: user, auth: auth) + end + + def initialize(user:, auth:) + @user = user + @user.tap do + user.provider = auth.provider + user.name = auth.info.name + user.uid = provider_uid(auth) + user.email = auth.info.email + user.password = default_password + end + end + + private + + def default_password + Devise.friendly_token[0, 20] + end + + def provider_uid(auth) + auth.provider + auth.uid + end + end +end diff --git a/app/services/user_builder/builder.rb b/app/services/user_builder/builder.rb new file mode 100644 index 0000000000..c153302abb --- /dev/null +++ b/app/services/user_builder/builder.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true +module UserBuilder + class Builder + # Adds new omni auth providers here + SERVICE = { + # 'google_oauth' => GoogleOauth2 + }.freeze + + # UserBuilder::Builder.build(user: user, provider: provider, auth: auth) + def self.build(user:, auth:) + service = SERVICE.fetch(auth.provider, UserBuilder::Base) + service.call(user: user, auth: auth) + end + end +end diff --git a/app/views/devise/shared/_links.erb b/app/views/devise/shared/_links.erb index 6a446c8d79..8d0c78661e 100644 --- a/app/views/devise/shared/_links.erb +++ b/app/views/devise/shared/_links.erb @@ -1,11 +1,16 @@
<%- if devise_mapping.omniauthable? and request.base_url != 'http://0.0.0.0:3000' %> - <%- resource_class.omniauth_providers.each do |provider| %> - <% if provider.to_s == "google_oauth2" && (current_page?(new_user_session_path) || current_page?(new_user_password_path)) %> - <%= button_to t('devise.shared.sign_in_google'), omniauth_authorize_path(resource_name, provider), method: :post, class: 'buttonGhostM' %> - <% elsif provider.to_s == "google_oauth2" %> - <%= button_to t('devise.shared.sign_up_google'), omniauth_authorize_path(resource_name, provider), method: :post, class: 'buttonGhostM' %> - <% end -%> + <%- resource_class.omniauth_providers.each do |provider| %> + + <% if provider.to_s == "google_oauth2" && (current_page?(new_user_session_path) || current_page?(new_user_password_path)) %> + <%= button_to t('devise.shared.sign_in_google'), omniauth_authorize_path(resource_name, provider), method: :post, class: 'buttonGhostM smallMarginBottom' %> + <% elsif provider.to_s == "github" && (current_page?(new_user_session_path) || current_page?(new_user_password_path)) %> + <%= button_to t('devise.shared.sign_in_github'), omniauth_authorize_path(resource_name, provider), method: :post, class: 'buttonGhostM smallMarginBottom' %> + <% elsif provider.to_s == "google_oauth2" %> + <%= button_to t('devise.shared.sign_up_google'), omniauth_authorize_path(resource_name, provider), method: :post, class: 'buttonGhostM smallMarginBottom' %> + <% elsif provider.to_s == 'github' %> + <%= button_to t('devise.shared.sign_up_github'), omniauth_authorize_path(resource_name, provider), method: :post, class: 'buttonGhostM smallMarginBottom' %> + <% end -%> <% end -%> <% end -%>
diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index a29cf9033c..96958113e9 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -317,4 +317,6 @@ approval_prompt: 'select_account consent force', scope: 'userinfo.email,userinfo.profile,calendar' ) + + config.omniauth :github, ENV['GITHUB_CLIENT_ID'], ENV['GITHUB_CLIENT_SECRET'], scope: 'user:email' end diff --git a/config/locales/de.yml b/config/locales/de.yml index 6ad91b5d80..6e73cf7b8e 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -511,6 +511,7 @@ de: mehr blockiert. omniauth: google: Google + github: Github access_denied: 'Zutritt verweigert' pages: about: @@ -1183,6 +1184,8 @@ de: shared: sign_in_google: 'Mit Google einloggen' sign_up_google: 'Mit Google anmelden' + sign_in_github: 'Sign in with Github' + sign_up_github: 'Sign up with Github' confirmation: 'Keine Anweisungen zur Bestätigung erhalten?' unlock: 'Keine Anweisungen zur Freischaltung erhalten?' unlock: diff --git a/config/locales/en.yml b/config/locales/en.yml index 9c7d2e3ffe..7e4ed96c59 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -483,6 +483,7 @@ en: %{code_of_conduct_link}. You are no longer banned. omniauth: google: Google + github: Github access_denied: 'Access Denied' pages: about: @@ -1107,6 +1108,8 @@ en: shared: sign_in_google: 'Sign in with Google' sign_up_google: 'Sign up with Google' + sign_in_github: 'Sign in with Github' + sign_up_github: 'Sign up with Github' confirmation: 'Didn''t receive confirmation instructions?' unlock: 'Didn''t receive unlock instructions?' unlock: diff --git a/config/locales/es.yml b/config/locales/es.yml index 068cceb515..71952f1f46 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -434,6 +434,7 @@ es: %{code_of_conduct_link}. Ya no estás baneado/a.' omniauth: google: Google + github: Github access_denied: 'Acceso Denegado' pages: about: @@ -972,6 +973,8 @@ es: shared: sign_in_google: 'Registrarse con Google' sign_up_google: 'Ingresar con Google' + sign_in_github: 'Sign in with Github' + sign_up_github: 'Sign up with Github' confirmation: '¿No recibiste instrucciones de confirmación?' unlock: '¿No recibiste instrucciones para desbloquear?' unlock: diff --git a/config/locales/fr.yml b/config/locales/fr.yml index b1269b46e4..e3ce1a0555 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -491,6 +491,7 @@ fr: %{code_of_conduct_link}. omniauth: google: Google + github: Github access_denied: 'Accès refusé' pages: about: @@ -1160,6 +1161,8 @@ fr: shared: sign_in_google: "S'identifier avec Google" sign_up_google: "S'enregistrer avec Google" + sign_in_github: 'Sign in with Github' + sign_up_github: 'Sign up with Github' confirmation: "Vous n'avez pas reçu vous informations de confirmation ?" unlock: >- Vous n'avez pas reçu vos informations pour débloquer votre compte ? diff --git a/config/locales/hi.yml b/config/locales/hi.yml index fb0bd6ae7a..0e52c37d10 100644 --- a/config/locales/hi.yml +++ b/config/locales/hi.yml @@ -414,6 +414,7 @@ hi: किया गया था। अब आप प्रतिबंधित नहीं हैं' omniauth: google: 'गूगल' + github: 'Github' access_denied: 'पहुंच अस्वीकृत' pages: about: @@ -888,6 +889,8 @@ hi: shared: sign_in_google: 'Google के साथ साइन इन करें' sign_up_google: 'Google के साथ साइन अप करें' + sign_in_github: 'Sign in with Github' + sign_up_github: 'Sign up with Github' confirmation: 'पुष्टि निर्देश प्राप्त नहीं हुआ?' unlock: 'क्या अनलॉक निर्देश प्राप्त नहीं होंगे?' unlock: diff --git a/config/locales/it.yml b/config/locales/it.yml index 0581085acc..1336501cd2 100644 --- a/config/locales/it.yml +++ b/config/locales/it.yml @@ -440,6 +440,7 @@ it: nostra %{code_of_conduct_link}. Non sei più bannato.' omniauth: google: Google + github: Github access_denied: 'Accesso Negato' pages: about: @@ -965,6 +966,8 @@ it: shared: sign_in_google: 'Effettua il login con Google' sign_up_google: 'Registrati con Google' + sign_in_github: 'Sign in with Github' + sign_up_github: 'Sign up with Github' confirmation: 'Non hai ricevuto le istruzioni per confermare la tua EMail?' unlock: 'Non hai ricevuto le istruzioni per sbloccare il tuo account?' unlock: diff --git a/config/locales/nb.yml b/config/locales/nb.yml index c950c522a8..e8c3b64061 100644 --- a/config/locales/nb.yml +++ b/config/locales/nb.yml @@ -436,6 +436,7 @@ nb: %{code_of_conduct_link}. Du er ikke lenger utestengt.' omniauth: google: Google + github: Github access_denied: 'Adgang avvist' pages: about: @@ -957,6 +958,8 @@ nb: shared: sign_in_google: 'Logg inn med Google' sign_up_google: 'Registrer deg med Google' + sign_in_github: 'Sign in with Github' + sign_up_github: 'Sign up with Github' confirmation: 'Mottok ikke bekreftelsesinstruksjoner?' unlock: 'Mottok ikke opplåsningsinstruksjoner?' unlock: diff --git a/config/locales/nl.yml b/config/locales/nl.yml index 274b5ce3d6..9b3d324dd1 100644 --- a/config/locales/nl.yml +++ b/config/locales/nl.yml @@ -447,6 +447,7 @@ nl: %{code_of_conduct_link} schond. Je bent niet langer verbannen.' omniauth: google: Google + github: Github access_denied: 'Toegang geweigerd' pages: about: @@ -976,6 +977,8 @@ nl: shared: sign_in_google: 'Log in met Google' sign_up_google: 'Registreren met Google' + sign_in_github: 'Sign in with Github' + sign_up_github: 'Sign up with Github' confirmation: 'Bevestiginginstructies niet ontvangen?' unlock: 'Ontgrendelingsinstructies niet ontvangen?' unlock: diff --git a/config/locales/pt-BR.yml b/config/locales/pt-BR.yml index a3c1247551..433bff4264 100644 --- a/config/locales/pt-BR.yml +++ b/config/locales/pt-BR.yml @@ -446,6 +446,7 @@ pt-BR: %{code_of_conduct_link}. Você não está mais banido.' omniauth: google: Google + github: Github access_denied: 'Acesso negado' pages: about: @@ -1000,6 +1001,8 @@ pt-BR: shared: sign_in_google: 'Entrar com sua conta Google' sign_up_google: 'Cadastre-se com sua conta Google' + sign_in_github: 'Sign in with Github' + sign_up_github: 'Sign up with Github' confirmation: 'Não recebeu as instruções de confirmação?' unlock: 'Não recebeu as instruções de desbloqueio?' unlock: diff --git a/config/locales/sv.yml b/config/locales/sv.yml index c91b9b43ab..e2c0d5dba2 100644 --- a/config/locales/sv.yml +++ b/config/locales/sv.yml @@ -433,6 +433,7 @@ sv: %{code_of_conduct_link}. Du är inte längre förbjuden.' omniauth: google: Google + github: Github access_denied: 'Åtkomst nekad' pages: about: @@ -952,6 +953,8 @@ sv: shared: sign_in_google: 'Logga in med Google' sign_up_google: 'Registrera dig hos Google' + sign_in_github: 'Logga in med Github' + sign_up_github: 'Registrera dig hos Github' confirmation: 'Fick du inte bekräftelsesinstruktioner?' unlock: 'Fick du inte upplåsningsinstruktioner?' unlock: diff --git a/config/locales/vi.yml b/config/locales/vi.yml index 54d8bab904..b50e4428d3 100644 --- a/config/locales/vi.yml +++ b/config/locales/vi.yml @@ -435,6 +435,7 @@ vi: %{code_of_conduct_link} của chúng tôi. Bạn không còn bị cấm nữa.' omniauth: google: Google + github: Github access_denied: 'Truy nhập từ chối' pages: about: @@ -965,6 +966,8 @@ vi: shared: sign_in_google: 'Đăng nhập với Google' sign_up_google: 'Đăng ký với Google' + sign_in_github: 'Sign in with Github' + sign_up_github: 'Sign up with Github' confirmation: 'Không nhận được hướng dẫn xác nhận?' unlock: 'Không nhận được hứng dẫn mở khóa?' unlock: diff --git a/config/locales/zh-CN.yml b/config/locales/zh-CN.yml index 80f38de8c5..5f076f7bc4 100644 --- a/config/locales/zh-CN.yml +++ b/config/locales/zh-CN.yml @@ -397,6 +397,7 @@ zh-CN: 您不再被禁止。' omniauth: google: 谷歌 + github: Github access_denied: '拒绝访问' pages: about: @@ -828,6 +829,8 @@ zh-CN: shared: sign_in_google: '使用Google登录' sign_up_google: '使用Google注册' + sign_in_github: 'Sign in with Github' + sign_up_github: 'Sign up with Github' confirmation: '没有收到确认指示吗?' unlock: '没有收到解锁指令吗?' unlock: diff --git a/spec/services/user_builder/base.rb b/spec/services/user_builder/base.rb new file mode 100644 index 0000000000..67f413a4cd --- /dev/null +++ b/spec/services/user_builder/base.rb @@ -0,0 +1,31 @@ +describe UserBuilder::Base do + describe '.call' do + subject { described_class } + + let(:user_one) { FactoryBot.build(:user1) } + + let(:auth) { OmniAuth::AuthHash.new({ + :provider => 'github', + :uid => '123545', + :info => info }) + } + + let(:info) do + { name: 'joe', email: 'test@email.com' } + end + + let(:builder) { UserBuilder::Base.call(user: user_one, auth: auth) } + + it 'builds the user object' do + expect(builder.user).to eql(user_one) + end + + it 'creates correct user uid' do + expect(builder.user.uid).to eql('github123545') + end + + it 'creates user under provider' do + expect(builder.user.provider).to eql('github') + end + end +end