Use sorcery for logins

This gives us more flexibility and allows us to use password authentication
later. Also we don't need to build the login functionality ourself.
This commit is contained in:
2020-04-13 14:59:07 +02:00
parent 73c184a4a0
commit c478cfc7af
12 changed files with 642 additions and 62 deletions

View File

@@ -37,4 +37,9 @@ $light: #fff;
body {
min-height: 100vh;
}
.button.google {
background: rgb(225, 98, 89);
color: white;
}

View File

@@ -0,0 +1,34 @@
class OauthsController < ApplicationController
# Sends the user on a trip to the provider,
# and after authorizing there back to the callback url.
def oauth
login_at(params[:provider])
end
def callback
provider = params[:provider]
if @user = login_from(provider)
redirect_to root_path, :notice => "Logged in from #{provider.titleize}!"
else
begin
@user = create_from(provider)
if authentication = @user.authentications.find_by(provider: provider)
authentication.update({
access_token: @access_token.token,
refresh_token: @access_token.refresh_token,
expires_at: Time.at(@access_token.expires_at)
})
end
reset_session
auto_login(@user)
redirect_to root_path, :notice => "Logged in from #{provider.titleize}!"
rescue
Rails.logger.error("Failed to login from #{provider}")
redirect_to root_path, :alert => "Failed to login from #{provider.titleize}!"
end
end
end
end

View File

@@ -1,29 +1,7 @@
require 'google/apis/oauth2_v2'
class SessionsController < ApplicationController
def new
reset_session
redirect_to auth_client.authorization_uri.to_s
end
def auth
reset_session
if params[:error]
flash[:error] = 'Login failed'
redirect_to root_url
else
auth_client.code = params[:code]
auth_client.fetch_access_token!
@user, @authentication = User.find_by_oauth_info(auth_client)
if @user.persisted? && @authentication.persisted?
session[:user_id] = @user.id.to_s
redirect_to forms_url
else
flash[:error] = 'Login failed'
redirect_to root_url
end
end
end
def destroy
@@ -31,18 +9,4 @@ class SessionsController < ApplicationController
redirect_to root_url
end
private
def auth_client
@auth_client ||= CLIENT_SECRETS.to_authorization.tap do |c|
c.update!(
scope: 'https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/spreadsheets',
redirect_uri: auth_url,
additional_parameters: {
'access_type' => 'offline', # offline access
'include_granted_scopes' => 'true' # incremental auth
}
)
end
end
end

View File

@@ -1,6 +1,8 @@
class Authentication < ApplicationRecord
belongs_to :user
scope :for, -> (provider) { where(provider: provider) }
encrypts :access_token
encrypts :refresh_token
@@ -9,6 +11,7 @@ class Authentication < ApplicationRecord
end
def google_authorization
return nil unless provider == 'google'
@google_authorization ||= CLIENT_SECRETS.to_authorization.tap do |c|
c.access_token = self.access_token
c.refresh_token = self.refresh_token

View File

@@ -1,40 +1,18 @@
class User < ApplicationRecord
authenticates_with_sorcery!
has_many :authentications, dependent: :destroy
has_many :forms, dependent: :destroy
def self.find_by_oauth_info(auth_client)
oauth = Google::Apis::Oauth2V2::Oauth2Service.new
oauth.authorization = auth_client
user_info = oauth.get_userinfo
if user = User.find_by(google_id: user_info.id)
authentication = user.authentications.last
authentication.access_token = auth_client.access_token if auth_client.access_token.present?
authentication.refresh_token = auth_client.refresh_token if auth_client.refresh_token.present?
authentication.expires_at = Time.at(auth_client.expires_at) if auth_client.expires_at.present?
authentication.save
return user, authentication
else
user = User.create(name: user_info.name, email: user_info.email, google_id: user_info.id)
authentication = user.authentications.create(
access_token: auth_client.access_token,
refresh_token: auth_client.refresh_token,
expires_at: Time.at(auth_client.expires_at)
)
return user, authentication
end
end
def deactivate!(reason = nil)
# currently we only use deactivate if we get an authentication exception appending data to a spreadsheet
authentications.last&.update(expires_at: Time.current)
end
def active?
authentications.last.present? && !authentications.last.expired?
authentications.any? { |a| !a.expired? }
end
def google_authorization
authentications.last.google_authorization
authentications.for(:google).last.google_authorization
end
end

View File

@@ -0,0 +1,22 @@
<div class="columns">
<div class="column">
</div>
<div class="column">
<div class="container has-text-centered">
<h1 class="title has-text-centered">
Login
</h1>
<div class="button google">
<svg viewBox="0 0 15 15" class="googleLogo" style="width: 14px; height: 14px; display: block; fill: currentcolor; flex-shrink: 0; backface-visibility: hidden; margin-right: 6px;"><path d="M 7.28571 6.4125L 7.28571 9L 11.3929 9C 11.2143 10.0875 10.1429 12.225 7.28571 12.225C 4.78571 12.225 2.78571 10.0875 2.78571 7.5C 2.78571 4.9125 4.82143 2.775 7.28571 2.775C 8.71429 2.775 9.64286 3.4125 10.1786 3.9375L 12.1429 1.9875C 10.8929 0.75 9.25 0 7.28571 0C 3.25 0 0 3.3375 0 7.5C 0 11.6625 3.25 15 7.28571 15C 11.5 15 14.25 11.9625 14.25 7.6875C 14.25 7.2 14.2143 6.825 14.1429 6.45L 7.28571 6.45L 7.28571 6.4125Z"></path></svg>
Login with Google
</div>
<hr>
<p>
As tinyforms builds on Google Sheets.<br>
Simply login with your Google account.
</p>
</div>
</div>
<div class="column">
</div>
</div>