diff --git a/.env.production.sample b/.env.production.sample
index 83ab698a096583..5362aea59964c3 100644
--- a/.env.production.sample
+++ b/.env.production.sample
@@ -92,3 +92,6 @@ SMTP_FROM_ADDRESS=notifications@example.com
STREAMING_CLUSTER_NUM=1
SENTRY_DSN=
+
+QIITA_CLIENT_ID=
+QIITA_CLIENT_SECRET=
diff --git a/.env.sample b/.env.sample
index 3c0a0c5bfe453f..8eb24aae99536a 100644
--- a/.env.sample
+++ b/.env.sample
@@ -19,3 +19,6 @@ OTP_SECRET=RANDOM_STRING
# Cluster number setting for streaming API server.
# If you comment out following line, cluster number will be `numOfCpuCores - 1`.
STREAMING_CLUSTER_NUM=1
+
+QIITA_CLIENT_ID=
+QIITA_CLIENT_SECRET=
diff --git a/Gemfile b/Gemfile
index e341c0fe48d87e..9001a282a293ef 100644
--- a/Gemfile
+++ b/Gemfile
@@ -38,6 +38,8 @@ gem 'link_header'
gem 'local_time'
gem 'nokogiri'
gem 'oj'
+gem 'omniauth'
+gem 'omniauth_qiita'
gem 'ostatus2', '~> 2.0'
gem 'ox'
gem 'rabl'
diff --git a/Gemfile.lock b/Gemfile.lock
index 236344562d47b7..a019b97303b388 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -159,7 +159,7 @@ GEM
fabrication (2.16.1)
faker (1.7.3)
i18n (~> 0.5)
- faraday (0.12.1)
+ faraday (0.11.0)
multipart-post (>= 1.2, < 3)
fast_blank (1.0.0)
font-awesome-rails (4.7.0.1)
@@ -183,6 +183,7 @@ GEM
hamlit (>= 1.2.0)
railties (>= 4.0.1)
hashdiff (0.3.2)
+ hashie (3.5.5)
highline (1.7.8)
hiredis (0.6.1)
htmlentities (4.3.4)
@@ -216,6 +217,7 @@ GEM
railties (>= 4.2.0)
thor (>= 0.14, < 2.0)
json (2.1.0)
+ jwt (1.5.6)
kaminari (1.0.1)
activesupport (>= 4.1.0)
kaminari-actionview (= 1.0.1)
@@ -258,6 +260,8 @@ GEM
mimemagic (0.3.2)
mini_portile2 (2.1.0)
minitest (5.10.1)
+ multi_json (1.12.1)
+ multi_xml (0.6.0)
multipart-post (2.0.0)
net-scp (1.2.1)
net-ssh (>= 2.6.5)
@@ -267,7 +271,22 @@ GEM
mini_portile2 (~> 2.1.0)
nokogumbo (1.4.10)
nokogiri
+ oauth2 (1.3.1)
+ faraday (>= 0.8, < 0.12)
+ jwt (~> 1.0)
+ multi_json (~> 1.3)
+ multi_xml (~> 0.5)
+ rack (>= 1.2, < 3)
oj (3.0.2)
+ omniauth (1.6.1)
+ hashie (>= 3.4.6, < 3.6.0)
+ rack (>= 1.6.2, < 3)
+ omniauth-oauth2 (1.4.0)
+ oauth2 (~> 1.0)
+ omniauth (~> 1.2)
+ omniauth_qiita (0.1.0)
+ multi_json (~> 1.10)
+ omniauth-oauth2 (~> 1.2)
openssl (2.0.3)
orm_adapter (0.5.0)
ostatus2 (2.0.0)
@@ -537,6 +556,8 @@ DEPENDENCIES
microformats2
nokogiri
oj
+ omniauth
+ omniauth_qiita
ostatus2 (~> 2.0)
ox
paperclip (~> 5.1)
diff --git a/app/assets/javascripts/components/features/ui/components/alert_bar.jsx b/app/assets/javascripts/components/features/ui/components/alert_bar.jsx
new file mode 100644
index 00000000000000..a3c8e19117c271
--- /dev/null
+++ b/app/assets/javascripts/components/features/ui/components/alert_bar.jsx
@@ -0,0 +1,27 @@
+import { Link } from 'react-router';
+import { FormattedMessage } from 'react-intl';
+import PropTypes from 'prop-types';
+
+export default class AlertBar extends React.Component {
+
+ render () {
+ const { isEmailConfirmed } = this.props;
+
+ return (
+
+
{mountedColumns}
diff --git a/app/assets/javascripts/components/locales/en.jsx b/app/assets/javascripts/components/locales/en.jsx
index afe714cac16d8b..fcb47cf2de3c8a 100644
--- a/app/assets/javascripts/components/locales/en.jsx
+++ b/app/assets/javascripts/components/locales/en.jsx
@@ -24,6 +24,7 @@ const en = {
"account.unblock": "Unblock @{name}",
"account.unfollow": "Unfollow",
"account.unmute": "Unmute @{name}",
+ "alert_bar.email_confirm_alert": "Your email address is not confirmed. Please confirm the sent email in 24 hours after registration.",
"boost_modal.combo": "You can press {combo} to skip this next time",
"column.blocks": "Blocked users",
"column.community": "Local timeline",
diff --git a/app/assets/javascripts/components/locales/ja.jsx b/app/assets/javascripts/components/locales/ja.jsx
index 6a753652702477..802b542259c55f 100644
--- a/app/assets/javascripts/components/locales/ja.jsx
+++ b/app/assets/javascripts/components/locales/ja.jsx
@@ -14,6 +14,7 @@ const ja = {
"account.unblock": "ブロック解除",
"account.unfollow": "フォロー解除",
"account.unmute": "ミュート解除",
+ "alert_bar.email_confirm_alert": "メールアドレスの確認が完了していません。登録後24時間以内に、登録したアドレス宛に送信されたメールを確認してください。",
"boost_modal.combo": "次からは{combo}を押せば、これをスキップできます。",
"column.blocks": "ブロックしたユーザー",
"column.community": "ローカルタイムライン",
diff --git a/app/assets/stylesheets/alert.scss b/app/assets/stylesheets/alert.scss
new file mode 100644
index 00000000000000..1de89944623079
--- /dev/null
+++ b/app/assets/stylesheets/alert.scss
@@ -0,0 +1,11 @@
+.alert-bar {
+ .alert {
+ border: 1px solid transparent;
+ border-radius: 4px;
+ padding: 12px;
+
+ color: DarkGoldenrod;
+ background-color: Beige;
+ border-color: Goldenrod;
+ }
+}
diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss
index 5becf213d61ad2..a7e035ab5a046b 100644
--- a/app/assets/stylesheets/application.scss
+++ b/app/assets/stylesheets/application.scss
@@ -21,4 +21,6 @@
@import 'rtl';
@import 'themes';
+@import 'alert';
@import 'code';
+@import 'qiita';
diff --git a/app/assets/stylesheets/qiita.scss b/app/assets/stylesheets/qiita.scss
new file mode 100644
index 00000000000000..76733d5ee9493d
--- /dev/null
+++ b/app/assets/stylesheets/qiita.scss
@@ -0,0 +1,56 @@
+.qiita {
+ .button {
+ display: inline-block;
+ margin-bottom: 0;
+ font-weight: normal;
+ text-align: center;
+ vertical-align: middle;
+ touch-action: manipulation;
+ cursor: pointer;
+ background-image: none;
+ border: 1px solid transparent;
+ white-space: nowrap;
+ padding: 6px 12px;
+ font-size: 14px;
+ line-height: 1.42857;
+ border-radius: 3px;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ }
+
+ .button-primary {
+ border-color: #4ea30a;
+ background-color: #59bb0c;
+ color: #fff;
+ }
+
+ .registration {
+ width: 100%;
+ text-transform: none;
+ }
+}
+
+.registrations {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+
+ .qiita {
+ width: 300px;
+ margin: 5px auto;
+ text-align: center;
+ }
+
+ .password_registration_closed_caution {
+ width: 300px;
+ margin: 10px auto;
+ }
+
+ .info a {
+ color: white;
+ text-transform: none;
+ }
+}
diff --git a/app/controllers/admin/settings_controller.rb b/app/controllers/admin/settings_controller.rb
index fc9064068c3612..2cc3f416c6fe16 100644
--- a/app/controllers/admin/settings_controller.rb
+++ b/app/controllers/admin/settings_controller.rb
@@ -2,7 +2,7 @@
module Admin
class SettingsController < BaseController
- BOOLEAN_SETTINGS = %w(open_registrations).freeze
+ BOOLEAN_SETTINGS = %w(open_registrations prohibit_registrations_except_qiita_oauth).freeze
def index
@settings = Setting.all_as_records
diff --git a/app/controllers/auth/omniauth_callbacks_controller.rb b/app/controllers/auth/omniauth_callbacks_controller.rb
new file mode 100644
index 00000000000000..8809e72df9634c
--- /dev/null
+++ b/app/controllers/auth/omniauth_callbacks_controller.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+class Auth::OmniauthCallbacksController < Devise::OmniauthCallbacksController
+ def qiita
+ auth_hash = request.env['omniauth.auth']
+
+ if current_user
+ authorization = QiitaAuthorization.find_or_initialize_by(uid: auth_hash[:uid]) do |qiita_authorization|
+ authorization.user = current_user
+ end
+
+ if authorization.save
+ flash[:notice] = I18n.t('omniauth_callbacks.success')
+ else
+ flash[:alert] = I18n.t('omniauth_callbacks.failure')
+ end
+ redirect_to settings_qiita_authorizations_path
+ else
+ if authorization = QiitaAuthorization.find_by(uid: auth_hash[:uid])
+ sign_in(authorization.user)
+ redirect_to web_path
+ else
+ store_omniauth_auth
+ redirect_to new_user_oauth_registration_path
+ end
+ end
+ end
+
+ private
+
+ def store_omniauth_auth
+ session[:devise_omniauth_auth] = request.env['omniauth.auth']
+ end
+end
diff --git a/app/controllers/auth/registrations_controller.rb b/app/controllers/auth/registrations_controller.rb
index dd30be32a40e98..410418f2f1c5a0 100644
--- a/app/controllers/auth/registrations_controller.rb
+++ b/app/controllers/auth/registrations_controller.rb
@@ -29,7 +29,15 @@ def after_inactive_sign_up_path_for(_resource)
end
def check_enabled_registrations
- redirect_to root_path if single_user_mode? || !Setting.open_registrations
+ redirect_to root_path if single_user_mode? || !Setting.open_registrations || Setting.prohibit_registrations_except_qiita_oauth
+ end
+
+ def update_resource(resource, params)
+ if resource.try(:has_dummy_password?)
+ resource.update_without_current_password(params)
+ else
+ super
+ end
end
private
diff --git a/app/controllers/oauth_registrations_controller.rb b/app/controllers/oauth_registrations_controller.rb
new file mode 100644
index 00000000000000..7184d9457ac76f
--- /dev/null
+++ b/app/controllers/oauth_registrations_controller.rb
@@ -0,0 +1,40 @@
+class OauthRegistrationsController < DeviseController
+ layout 'auth'
+
+ before_action :check_enabled_registrations
+ before_action :require_omniauth_auth
+ before_action :require_no_authentication
+
+ def new
+ @oauth_registration = Form::OauthRegistration.from_omniauth_auth(omniauth_auth)
+ end
+
+ def create
+ @oauth_registration = Form::OauthRegistration.from_omniauth_auth(omniauth_auth)
+ @oauth_registration.assign_attributes(
+ params.require(:form_oauth_registration).permit(:email, :username, :password, :password_confirmation).merge(locale: I18n.locale)
+ )
+
+ if @oauth_registration.save
+ sign_in(@oauth_registration.user)
+ redirect_to web_path
+ flash[:notice] = I18n.t('oauth_registration.success')
+ else
+ render :new, status: :unprocessable_entity
+ end
+ end
+
+ private
+
+ def omniauth_auth
+ @omniauth_auth ||= session[:devise_omniauth_auth].try(:deep_symbolize_keys)
+ end
+
+ def check_enabled_registrations
+ redirect_to root_path if single_user_mode? || !Setting.open_registrations
+ end
+
+ def require_omniauth_auth
+ redirect_to root_path unless omniauth_auth
+ end
+end
diff --git a/app/controllers/settings/qiita_authorizations_controller.rb b/app/controllers/settings/qiita_authorizations_controller.rb
new file mode 100644
index 00000000000000..ef55e2886a087d
--- /dev/null
+++ b/app/controllers/settings/qiita_authorizations_controller.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+class Settings::QiitaAuthorizationsController < ApplicationController
+ layout 'admin'
+
+ before_action :authenticate_user!
+
+ def show
+ @account = current_account
+ @qiita_authorization = current_account.user.qiita_authorization
+ end
+end
diff --git a/app/models/form/oauth_registration.rb b/app/models/form/oauth_registration.rb
new file mode 100644
index 00000000000000..0bdea4e7932488
--- /dev/null
+++ b/app/models/form/oauth_registration.rb
@@ -0,0 +1,89 @@
+# frozen_string_literal: true
+
+class Form::OauthRegistration
+ include ActiveModel::Model
+
+ attr_accessor :user, :provider, :locale, :avatar, :email, :uid, :username, :token
+ validate :validate_user
+
+ class UnsupportedProviderError < StandardError; end
+
+ class << self
+ def from_omniauth_auth(auth)
+ case auth[:provider]
+ when 'qiita'
+ new(
+ provider: auth[:provider],
+ avatar: auth[:info][:image],
+ uid: auth[:uid],
+ username: normalize_username(auth[:uid]),
+ token: auth[:credentials][:token],
+ )
+ else
+ fail UnsupportedProviderError
+ end
+ end
+
+ private
+
+ def normalize_username(username)
+ username.to_s.downcase.tr('-', '_').gsub('@github', '').remove(/[^a-z0-9_]/i)
+ end
+ end
+
+ def save
+ return false if invalid?
+
+ ApplicationRecord.transaction do
+ self.user = build_user
+ oauth_authentication = build_authorization(user)
+ user.save! && oauth_authentication.save!
+ end
+
+ true
+ rescue ActiveRecord::RecordInvalid
+ false
+ end
+
+ private
+
+ def validate_user
+ user = build_user
+ user.valid?
+
+ [user, user.account].each do |record|
+ record.errors.each do |key, value|
+ errors.add(key, value) if respond_to?(key)
+ end
+ end
+ end
+
+ def build_user
+ password = SecureRandom.base64
+
+ User.new(
+ email: email,
+ locale: locale,
+ password: password,
+ password_confirmation: password,
+ dummy_password_flag: true,
+ account_attributes: {
+ username: username,
+ avatar: avatar
+ },
+ )
+ end
+
+ def build_authorization(user)
+ case provider
+ when 'qiita'
+ QiitaAuthorization.new(
+ uid: uid,
+ user: user,
+ token: token,
+ )
+ else
+ fail UnsupportedProviderError
+ end
+ end
+end
diff --git a/app/models/qiita_authorization.rb b/app/models/qiita_authorization.rb
new file mode 100644
index 00000000000000..c1a0bc1787b07b
--- /dev/null
+++ b/app/models/qiita_authorization.rb
@@ -0,0 +1,3 @@
+class QiitaAuthorization < ApplicationRecord
+ belongs_to :user, inverse_of: :qiita_authorization
+end
diff --git a/app/models/user.rb b/app/models/user.rb
index 48e7b80887664e..3d174ae6f1c931 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -6,10 +6,12 @@ class User < ApplicationRecord
devise :registerable, :recoverable,
:rememberable, :trackable, :validatable, :confirmable,
:two_factor_authenticatable, :two_factor_backupable,
+ :omniauthable,
otp_secret_encryption_key: ENV['OTP_SECRET'],
otp_number_of_backup_codes: 10
belongs_to :account, inverse_of: :user, required: true
+ has_one :qiita_authorization, inverse_of: :user, dependent: :destroy
accepts_nested_attributes_for :account
validates :locale, inclusion: I18n.available_locales.map(&:to_s), unless: 'locale.nil?'
@@ -19,6 +21,8 @@ class User < ApplicationRecord
scope :admins, -> { where(admin: true) }
scope :confirmed, -> { where.not(confirmed_at: nil) }
+ before_validation :disable_dummy_password_flag, on: :update, if: :encrypted_password_changed?
+
def confirmed?
confirmed_at.present?
end
@@ -38,4 +42,25 @@ def setting_boost_modal
def setting_auto_play_gif
settings.auto_play_gif
end
+
+ def has_dummy_password?
+ dummy_password_flag
+ end
+
+ def disable_dummy_password_flag
+ self.dummy_password_flag = false
+ true
+ end
+
+ def update_without_current_password(params, *options)
+ if params[:password].blank?
+ params.delete(:password)
+ params.delete(:password_confirmation) if params[:password_confirmation].blank?
+ end
+ p params
+
+ result = update_attributes(params, *options)
+ clean_up_passwords
+ result
+ end
end
diff --git a/app/presenters/instance_presenter.rb b/app/presenters/instance_presenter.rb
index 9a69809d0e5841..8cc08e6c2845f5 100644
--- a/app/presenters/instance_presenter.rb
+++ b/app/presenters/instance_presenter.rb
@@ -5,6 +5,7 @@ class InstancePresenter
:closed_registrations_message,
:site_contact_email,
:open_registrations,
+ :prohibit_registrations_except_qiita_oauth,
:site_description,
:site_extended_description,
to: Setting
@@ -29,4 +30,8 @@ def domain_count
def version_number
Mastodon::Version
end
+
+ def open_password_registrations
+ open_registrations && !prohibit_registrations_except_qiita_oauth
+ end
end
diff --git a/app/views/about/_registration.html.haml b/app/views/about/_registration.html.haml
index c7a9a488b48efb..00e4da051d27d4 100644
--- a/app/views/about/_registration.html.haml
+++ b/app/views/about/_registration.html.haml
@@ -1,30 +1,44 @@
-= simple_form_for(new_user, url: user_registration_path) do |f|
- = f.simple_fields_for :account do |account_fields|
- = account_fields.input :username,
- autofocus: true,
- placeholder: t('simple_form.labels.defaults.username'),
- required: true,
- input_html: { 'aria-label' => t('simple_form.labels.defaults.username') }
+.registrations
+ = render 'shared/qiita_authentication'
- = f.input :email,
- placeholder: t('simple_form.labels.defaults.email'),
- required: true,
- input_html: { 'aria-label' => t('simple_form.labels.defaults.email') }
- = f.input :password,
- autocomplete: "off",
- placeholder: t('simple_form.labels.defaults.password'),
- required: true,
- input_html: { 'aria-label' => t('simple_form.labels.defaults.password') }
- = f.input :password_confirmation,
- autocomplete: "off",
- placeholder: t('simple_form.labels.defaults.confirm_password'),
- required: true,
- input_html: { 'aria-label' => t('simple_form.labels.defaults.confirm_password') }
+ - if @instance_presenter.open_password_registrations
- .actions
- = f.button :button, t('about.get_started'), type: :submit
+ = simple_form_for(new_user, url: user_registration_path) do |f|
- .info
- = link_to t('auth.login'), new_user_session_path, class: 'webapp-btn'
- ·
- = link_to t('about.about_this'), about_more_path
+ = f.simple_fields_for :account do |account_fields|
+ = account_fields.input :username,
+ autofocus: true,
+ placeholder: t('simple_form.labels.defaults.username'),
+ required: true,
+ input_html: { 'aria-label' => t('simple_form.labels.defaults.username') }
+
+ = f.input :email,
+ placeholder: t('simple_form.labels.defaults.email'),
+ required: true,
+ input_html: { 'aria-label' => t('simple_form.labels.defaults.email') }
+ = f.input :password,
+ autocomplete: "off",
+ placeholder: t('simple_form.labels.defaults.password'),
+ required: true,
+ input_html: { 'aria-label' => t('simple_form.labels.defaults.password') }
+ = f.input :password_confirmation,
+ autocomplete: "off",
+ placeholder: t('simple_form.labels.defaults.confirm_password'),
+ required: true,
+ input_html: { 'aria-label' => t('simple_form.labels.defaults.confirm_password') }
+
+ .actions
+ = f.button :button, t('about.get_started'), type: :submit
+
+ .info
+ = link_to t('auth.login'), new_user_session_path, class: 'webapp-btn'
+ ·
+ = link_to t('about.about_this'), about_more_path
+ - else
+
+ .password_registration_closed_caution
+ = t('auth.password_signup_closed_caution')
+ .info
+ = link_to t('auth.login'), new_user_session_path
+ ·
+ = link_to t('about.about_this'), about_more_path
diff --git a/app/views/admin/settings/index.html.haml b/app/views/admin/settings/index.html.haml
index b00e75a1666400..f8cfe5da716e75 100644
--- a/app/views/admin/settings/index.html.haml
+++ b/app/views/admin/settings/index.html.haml
@@ -29,6 +29,10 @@
%strong= t('admin.settings.site_description_extended.title')
%p= t('admin.settings.site_description_extended.desc_html')
%td= best_in_place @settings['site_extended_description'], :value, as: :textarea, url: admin_setting_path(@settings['site_extended_description'])
+ %tr
+ %td
+ %strong= t('admin.settings.prohibit_registrations_except_qiita_oauth.title')
+ %td= best_in_place @settings['prohibit_registrations_except_qiita_oauth'], :value, as: :checkbox, collection: { false: t('admin.settings.prohibit_registrations_except_qiita_oauth.disabled'), true: t('admin.settings.prohibit_registrations_except_qiita_oauth.enabled')}, url: admin_setting_path(@settings['prohibit_registrations_except_qiita_oauth'])
%tr
%td
%strong= t('admin.settings.registrations.open.title')
diff --git a/app/views/auth/registrations/edit.html.haml b/app/views/auth/registrations/edit.html.haml
index 39b726f9c21190..e878a5111e8205 100644
--- a/app/views/auth/registrations/edit.html.haml
+++ b/app/views/auth/registrations/edit.html.haml
@@ -5,9 +5,13 @@
= render 'shared/error_messages', object: resource
= f.input :email, placeholder: t('simple_form.labels.defaults.email'), input_html: { 'aria-label' => t('simple_form.labels.defaults.email') }
- = f.input :password, autocomplete: "off", placeholder: t('simple_form.labels.defaults.new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.new_password') }
- = f.input :password_confirmation, autocomplete: "off", placeholder: t('simple_form.labels.defaults.confirm_new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.confirm_new_password') }
- = f.input :current_password, autocomplete: "off", placeholder: t('simple_form.labels.defaults.current_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.current_password') }
+ - if current_user.has_dummy_password?
+ = f.input :password, autocomplete: "off", placeholder: t('simple_form.labels.defaults.password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.password') }
+ = f.input :password_confirmation, autocomplete: "off", placeholder: t('simple_form.labels.defaults.confirm_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.confirm_password') }
+ - else
+ = f.input :password, autocomplete: "off", placeholder: t('simple_form.labels.defaults.new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.new_password') }
+ = f.input :password_confirmation, autocomplete: "off", placeholder: t('simple_form.labels.defaults.confirm_new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.confirm_new_password') }
+ = f.input :current_password, autocomplete: "off", placeholder: t('simple_form.labels.defaults.current_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.current_password') }
.actions
= f.button :button, t('generic.save_changes'), type: :submit
diff --git a/app/views/auth/sessions/new.html.haml b/app/views/auth/sessions/new.html.haml
index 93b9629f1d4bdb..d5bcecb5619def 100644
--- a/app/views/auth/sessions/new.html.haml
+++ b/app/views/auth/sessions/new.html.haml
@@ -1,6 +1,10 @@
- content_for :page_title do
= t('auth.login')
+= render 'shared/qiita_authentication'
+
+%hr
+
= simple_form_for(resource, as: resource_name, url: session_path(resource_name)) do |f|
= f.input :email, autofocus: true, placeholder: t('simple_form.labels.defaults.email'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.email') }
= f.input :password, placeholder: t('simple_form.labels.defaults.password'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.password') }
diff --git a/app/views/home/initial_state.json.rabl b/app/views/home/initial_state.json.rabl
index b599b5cf003f06..cf490c9910ce6f 100644
--- a/app/views/home/initial_state.json.rabl
+++ b/app/views/home/initial_state.json.rabl
@@ -10,6 +10,7 @@ node(:meta) do
admin: @admin.try(:id),
boost_modal: current_account.user.setting_boost_modal,
auto_play_gif: current_account.user.setting_auto_play_gif,
+ is_email_confirmed: current_user.confirmed?,
}
end
diff --git a/app/views/oauth_registrations/new.html.haml b/app/views/oauth_registrations/new.html.haml
new file mode 100644
index 00000000000000..b44ca07164541f
--- /dev/null
+++ b/app/views/oauth_registrations/new.html.haml
@@ -0,0 +1,15 @@
+- content_for :page_title do
+ = t('auth.oauth_registration')
+
+= simple_form_for(@oauth_registration, url: user_oauth_registration_path, method: 'POST') do |f|
+ = render 'shared/error_messages', object: f.object
+
+ .omniauth
+ = f.label :username
+ = f.input :username, autofocus: true, placeholdoer: t('simple_form.labels.defaults.username'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.username') }
+
+ = f.label :email
+ = f.input :email, autofocus: true, placeholder: t('simple_form.labels.defaults.email'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.email') }
+
+ .actions
+ = f.button :button, t('auth.register'), type: :submit
diff --git a/app/views/settings/qiita_authorizations/show.html.haml b/app/views/settings/qiita_authorizations/show.html.haml
new file mode 100644
index 00000000000000..0f910576e35873
--- /dev/null
+++ b/app/views/settings/qiita_authorizations/show.html.haml
@@ -0,0 +1,11 @@
+- content_for :page_title do
+ = t('settings.qiita_authorizations')
+
+%table.table
+ %tbody
+ %tr
+ %th User ID
+ %th Status
+ %tr
+ %td= @qiita_authorization.try(:uid)
+ %td= @qiita_authorization ? '連携中' : link_to('連携する', '/auth/auth/qiita')
diff --git a/app/views/settings/shared/_links.html.haml b/app/views/settings/shared/_links.html.haml
index 0abb5a7abb17ae..25f1e5c1ea09b9 100644
--- a/app/views/settings/shared/_links.html.haml
+++ b/app/views/settings/shared/_links.html.haml
@@ -7,4 +7,6 @@
%li= link_to t('auth.change_password'), edit_user_registration_path
- if controller_name != 'two_factor_authentications'
%li= link_to t('settings.two_factor_authentication'), settings_two_factor_authentication_path
+ - if controller_name != 'qiita_authorizations'
+ %li= link_to t('settings.qiita_authorizations'), settings_qiita_authorizations_path
%li= link_to t('settings.back'), root_path
diff --git a/app/views/shared/_qiita_authentication.html.haml b/app/views/shared/_qiita_authentication.html.haml
new file mode 100644
index 00000000000000..c293368cfba259
--- /dev/null
+++ b/app/views/shared/_qiita_authentication.html.haml
@@ -0,0 +1,2 @@
+.qiita
+ = link_to 'Qiitaアカウントで参加', '/auth/auth/qiita', class: 'button button-primary registration'
diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb
index 4754c2c8ca22bd..fa224e4f778236 100644
--- a/config/initializers/devise.rb
+++ b/config/initializers/devise.rb
@@ -114,7 +114,7 @@
# able to access the website for two days without confirming their account,
# access will be blocked just in the third day. Default is 0.days, meaning
# the user cannot access the website without confirming their account.
- # config.allow_unconfirmed_access_for = 2.days
+ config.allow_unconfirmed_access_for = 1.days
# A period that the user is allowed to confirm their account before their
# token becomes invalid. For example, if set to 3.days, the user can confirm
@@ -243,6 +243,7 @@
# Add a new OmniAuth provider. Check the wiki for more information on setting
# up on your models and hooks.
# config.omniauth :github, 'APP_ID', 'APP_SECRET', scope: 'user,public_repo'
+ config.omniauth :qiita, ENV['QIITA_CLIENT_ID'], ENV['QIITA_CLIENT_SECRET'], scope: 'read_qiita'
# ==> Warden configuration
# If you want to use other strategies, that are not supported by Devise, or
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 6f54e343ea5b65..4ef3c7d29524d3 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -170,6 +170,10 @@ en:
disabled: Disabled
enabled: Enabled
title: Open registration
+ prohibit_registrations_except_qiita_oauth:
+ title: Prohibit registration except Qiita OAuth
+ disabled: Don't Prohibit
+ enabled: Prohibit
setting: Setting
site_description:
desc_html: Displayed as a paragraph on the frontpage and used as a meta tag.
You can use HTML tags, in particular
<a>
and
<em>
.
@@ -196,6 +200,8 @@ en:
resend_confirmation: Resend confirmation instructions
reset_password: Reset password
set_new_password: Set new password
+ oauth_registration: User Registration
+ password_signup_closed_caution: For now, we allow registrations only with Qiita account.
authorize_follow:
error: Unfortunately, there was an error looking up the remote account
follow: Follow
@@ -304,6 +310,7 @@ en:
preferences: Preferences
settings: Settings
two_factor_authentication: Two-factor Authentication
+ qiita_authorizations: Qiita Authorizations
statuses:
open_in_web: Open in web
over_character_limit: character limit of %{max} exceeded
@@ -339,3 +346,8 @@ en:
users:
invalid_email: The e-mail address is invalid
invalid_otp_token: Invalid two-factor code
+ omniauth_callbacks:
+ success: Succeed to integrate
+ failure: Failed to integrate
+ oauth_registration:
+ success: Succeed to register
diff --git a/config/locales/ja.yml b/config/locales/ja.yml
index 7a53541f123e8b..10cdf1e9bccd04 100644
--- a/config/locales/ja.yml
+++ b/config/locales/ja.yml
@@ -177,6 +177,10 @@ ja:
disabled: 無効
enabled: 有効
title: 新規登録を受け付ける
+ prohibit_registrations_except_qiita_oauth:
+ title: Qiita連携以外の新規登録を禁止する
+ disabled: 禁止しない
+ enabled: 禁止する
setting: 設定
site_description:
desc_html: トップページへの表示と meta タグに使用されます。
HTMLタグ、特に
<a>
と
<em>
が利用可能です。
@@ -203,6 +207,8 @@ ja:
resend_confirmation: 確認メールを再送する
reset_password: パスワードを再発行
set_new_password: 新しいパスワード
+ oauth_registration: ユーザー登録
+ password_signup_closed_caution: 現在は負荷対策のため、Qiitaアカウント連携以外での新規登録を停止しています。
authorize_follow:
error: 残念ながら、リモートアカウントにエラーが発生しました。
follow: フォロー
@@ -311,6 +317,7 @@ ja:
preferences: ユーザー設定
settings: 設定
two_factor_authentication: 二段階認証
+ qiita_authorizations: Qiita連携
statuses:
open_in_web: Webで開く
over_character_limit: 上限は %{max}文字までです
@@ -346,3 +353,8 @@ ja:
users:
invalid_email: メールアドレスが無効です
invalid_otp_token: 二段階認証コードが間違っています
+ omniauth_callbacks:
+ success: 連携に成功しました
+ failure: 連携に失敗しました
+ oauth_registration:
+ success: ユーザー登録しました
diff --git a/config/navigation.rb b/config/navigation.rb
index 16bc86696dff39..daeff41c0ec1c2 100644
--- a/config/navigation.rb
+++ b/config/navigation.rb
@@ -13,6 +13,7 @@
settings.item :export, safe_join([fa_icon('cloud-download fw'), t('settings.export')]), settings_export_url
settings.item :authorized_apps, safe_join([fa_icon('list fw'), t('settings.authorized_apps')]), oauth_authorized_applications_url
settings.item :follower_domains, safe_join([fa_icon('users fw'), t('settings.followers')]), settings_follower_domains_url
+ settings.item :qiita_authorizations, safe_join([fa_icon('users fw'), t('settings.qiita_authorizations')]), settings_qiita_authorizations_url
end
primary.item :admin, safe_join([fa_icon('cogs fw'), t('admin.title')]), admin_reports_url, if: proc { current_user.admin? } do |admin|
diff --git a/config/routes.rb b/config/routes.rb
index 9adaffcafd92f2..e5102ef98a10ae 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -23,8 +23,15 @@
registrations: 'auth/registrations',
passwords: 'auth/passwords',
confirmations: 'auth/confirmations',
+ omniauth_callbacks: 'auth/omniauth_callbacks',
}
+ devise_scope :user do
+ with_devise_exclusive_scope('/auth', :user, {}) do
+ resource :oauth_registration, only: [:new, :create], path: 'oauth/oauth_registrations'
+ end
+ end
+
get '/users/:username', to: redirect('/@%{username}'), constraints: { format: :html }
resources :accounts, path: 'users', only: [:show], param: :username do
@@ -65,6 +72,7 @@
end
resource :follower_domains, only: [:show, :update]
+ resource :qiita_authorizations, only: [:show]
end
resources :media, only: [:show]
diff --git a/config/settings.yml b/config/settings.yml
index 9813963b28ec15..cf5a996d1e9855 100644
--- a/config/settings.yml
+++ b/config/settings.yml
@@ -14,6 +14,7 @@ defaults: &defaults
site_contact_email: ''
open_registrations: true
closed_registrations_message: ''
+ prohibit_registrations_except_qiita_oauth: false
boost_modal: false
auto_play_gif: true
notification_emails:
diff --git a/db/migrate/20170504103736_create_qiita_authorizations.rb b/db/migrate/20170504103736_create_qiita_authorizations.rb
new file mode 100644
index 00000000000000..50bea7045540b6
--- /dev/null
+++ b/db/migrate/20170504103736_create_qiita_authorizations.rb
@@ -0,0 +1,12 @@
+class CreateQiitaAuthorizations < ActiveRecord::Migration[5.0]
+ def change
+ create_table :qiita_authorizations do |t|
+ t.belongs_to :user, foreign_key: true
+ t.string :uid
+ t.string :token
+
+ t.index :uid, unique: true
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20170517123337_add_dummy_password_flag_to_user.rb b/db/migrate/20170517123337_add_dummy_password_flag_to_user.rb
new file mode 100644
index 00000000000000..db88026169b57f
--- /dev/null
+++ b/db/migrate/20170517123337_add_dummy_password_flag_to_user.rb
@@ -0,0 +1,5 @@
+class AddDummyPasswordFlagToUser < ActiveRecord::Migration[5.0]
+ def change
+ add_column :users, :dummy_password_flag, :boolean, default: false, null: false
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 66326f2e208938..8d700d8d6c12ae 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20170425202925) do
+ActiveRecord::Schema.define(version: 20170517123337) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -214,6 +214,16 @@
t.index ["status_id"], name: "index_preview_cards_on_status_id", unique: true, using: :btree
end
+ create_table "qiita_authorizations", force: :cascade do |t|
+ t.integer "user_id"
+ t.string "uid"
+ t.string "token"
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.index ["uid"], name: "index_qiita_authorizations_on_uid", unique: true, using: :btree
+ t.index ["user_id"], name: "index_qiita_authorizations_on_user_id", using: :btree
+ end
+
create_table "reports", force: :cascade do |t|
t.integer "account_id", null: false
t.integer "target_account_id", null: false
@@ -326,6 +336,7 @@
t.boolean "otp_required_for_login"
t.datetime "last_emailed_at"
t.string "otp_backup_codes", array: true
+ t.boolean "dummy_password_flag", default: false, null: false
t.index ["account_id"], name: "index_users_on_account_id", using: :btree
t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true, using: :btree
t.index ["email"], name: "index_users_on_email", unique: true, using: :btree
@@ -340,5 +351,6 @@
t.index ["user_id"], name: "index_web_settings_on_user_id", unique: true, using: :btree
end
+ add_foreign_key "qiita_authorizations", "users"
add_foreign_key "statuses", "statuses", column: "reblog_of_id", on_delete: :cascade
end
diff --git a/spec/fabricators/qiita_authorization_fabricator.rb b/spec/fabricators/qiita_authorization_fabricator.rb
new file mode 100644
index 00000000000000..b2b79070ea911e
--- /dev/null
+++ b/spec/fabricators/qiita_authorization_fabricator.rb
@@ -0,0 +1,5 @@
+Fabricator(:qiita_authorization) do
+ user nil
+ uid "qiitan"
+ provider "qiita"
+end
diff --git a/spec/models/oauth_authorization_spec.rb b/spec/models/oauth_authorization_spec.rb
new file mode 100644
index 00000000000000..1ccc5dcc208303
--- /dev/null
+++ b/spec/models/oauth_authorization_spec.rb
@@ -0,0 +1,5 @@
+require 'rails_helper'
+
+RSpec.describe QiitaAuthorization, type: :model do
+ pending "add some examples to (or delete) #{__FILE__}"
+end