Sinatra App with multiple users - ruby

What is the easiest way to allow for multiple users in a Sinatra web app. I've previously used an authorization class that allows for one username and password, but what if I want to allow users to sign up for a simple web app and allow them all their own login credentials?
Thank you so much!

If HTTP basic auth is sufficient I'd recommend defining two methods like this:
helpers do
def protected!
unless authorized?
response["WWW-Authenticate"] = 'Basic realm="Protected Area"'
throw(:halt, [401, "Not authorized\n"])
end
end
def authorized?
#auth ||= Rack::Auth::Basic::Request.new(request.env)
if #auth.provided? && #auth.basic? && #auth.credentials
username,password = #auth.credentials
# verify credentials are correct
end
end
end
Call protected! from any action that should be protected (or use a before block to protect everything). I leave the credential verification to you since I don't know how you're storing user account information.

The sinatra-authentication gem looks like an easy and powerful solution for adding users, authentication and permissions to sinatra apps.

Related

Rails 5 Using Devise and acts_as_tenant

I have an application that's using Devise to handle sign in/registration. I'm also using acts_as_tenant. I need to ensure a Tenant is set every time someone goes to register/sign in. For acts as tenant to work, the tenant must be set prior to authentication. Right now I'm using a before_action on my ApplicationController but the issue is that method gets called even if someone has invalid credentials etc, and I'm trying to avoid having to write if statements in the method to figure out if I have a valid user or not.
What is the best way to achieve this? Anyone in a similar situation?
If you need to set your tenant before authenticating with Devise, you need to use acts_as_tenant's manual tenant setting method so that you can control the timing.
# application_controller.rb
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
# tell acts_as_tenant you'll manually set the tenant
set_current_tenant_through_filter
# manually set the tenant before Devise auth
prepend_before_action :set_current_tenant_by_subdomain
# Devise auth as usual but after tenant is set
before_action :authenticate_user!
private
def set_current_tenant_by_subdomain
current_organization = Organization.find_by_subdomain(request.subdomain)
set_current_tenant(current_organization)
end
end

Google adwords api how to authorize with service account impersonation

I'm trying to develop a ruby script to query the Google Adwords API using service account credentials. I know that the json credentials file works in another script which isn't ruby but I can't get past authorization in this ruby script. I can't find any examples using this pattern. I don't want to use OAuth2. I know this script is not well developed but I'm just trying to get the basics. What am I doing wrong here?
My company has a G Suite account and I have full administrator permissions.
Here is my code:
#!/usr/bin/ruby
require 'googleauth'
require 'fileutils'
require 'adwords_api'
API_VERSION = :v201809
scopes = ["https://www.googleapis.com/auth/adwords"]
credential_file = File.open('credentials/adwords-production.json')
prn = "adwords#mycompany.com"
authorizer = Google::Auth::ServiceAccountCredentials.make_creds(json_key_io: credential_file, scope: scopes, prn: prn)
def get_report_fields(report_type)
adwords = AdwordsApi::Api.new
report_def_srv = adwords.service(:ReportDefinitionService, API_VERSION)
# Get report fields.
fields = report_def_srv.get_report_fields(report_type)
if fields
puts "Report type '%s' contains the following fields:" % report_type
fields.each do |field|
puts ' - %s (%s)' % [field[:field_name], field[:field_type]]
puts ' := [%s]' % field[:enum_values].join(', ') if field[:enum_values]
end
end
end
begin
report_type = 'ACCOUNT_PERFORMANCE_REPORT'
get_report_fields(report_type)
end
From Google's docs here:
All AdWords API calls must be authorized through OAuth2. OAuth2 enables your AdWords API client app to access a user's Google Ads account without having to handle or store the user's login info.
It appears you have to use OAuth2 Authentication.
Further reading here confirms this:
Your app will need to access user data and contact other Google services on your behalf. Authentication via OAuth2 allows your app to operate on behalf of your account.
To enable your app to access the API, you need an OAuth2 client ID and client secret.
You say you have done this somewhere else where you were able to use a JSON file, so I looked into the source a little.
The source for the Ruby API is here and here. It appears there really is no other way to manage credentials, at least in the Ruby API. Looking here:
# Retrieve correct soap_header_handler.
#
# Args:
# - auth_handler: instance of an AdsCommon::Auth::BaseHandler subclass to
# handle authentication
# - version: intended API version
# - header_ns: header namespace
# - default_ns: default namespace
#
# Returns:
# - SOAP header handler
#
def soap_header_handler(auth_handler, version, header_ns, default_ns)
auth_method = #config.read('authentication.method', :OAUTH2)
handler_class = case auth_method
when :OAUTH2, :OAUTH2_SERVICE_ACCOUNT
AdsCommon::SavonHeaders::OAuthHeaderHandler
else
raise AdsCommon::Errors::AuthError,
"Unknown auth method: %s" % auth_method
end
return handler_class.new(#credential_handler, auth_handler, header_ns,
default_ns, version)
end
Notice that unless OAuth is used, it throws an error. There really is no other way to authenticate. Even if you managed to authenticate another way and try passing it to the credential manager, it will reject it.
Short answer: This is not possible in the Ruby API without hacking it some way.

Building an OmniAuth strategy with a custom workflow

I want to achieve the following login workflow:
user clicks on login button
user gets redirected to our authentication platform
user submits the login credentials and then gets redirected back to our
website via a pre-set callback URL
the OmniAuth strategy must decode the response (using our SDK) and
save the result in the omniauth.auth hash
Is this process easily achievable using a OmniAuth Strategy? It's not very clear to me from the documentation, and the majority of already built strategies seem to be using the OAuth workflow.
Apparently this is quite easy to do.
The OmniAuth strategy
module OmniAuth
module Strategies
class Service
include OmniAuth::Strategy
def request_phase
redirect AUTHENTICATION_URL
end
uid { #user_details.user_id }
def extra
#user_details # Return a hash with user data
end
def callback_phase
# Configure Service SDK
#user_details = Service.user_data # Make SDK call to get user details
super
end
end
end
end
The App
1) Add a login button with the authentication URL:
<%= link_to 'Login', 'auth/service' %>
2) Add a callback route
get '/auth/service/callback', to: 'sessions#create'
3) Handle the callback response in the controller
class SessionsController < ApplicationController
def create
#user = User.find_or_create_by(service_id: auth_hash.uid)
# Handle #user
end
end

Authentication for Sinatra REST API app

I'm building an API with Sinatra (using Angular for the client side and want others to have access to API) and have it also be an OAuth provider. I am wondering what the best route to take (work off existing gems or roll own solution off Warden or something).
Have used devise and doorkeeper for authentication and oauth before with Rails, wondering what best solution for Sinatra is.
Ideally I don't want the views or be able to extend/mod the actions of an existing solution, as I'm interacting with it purely as an API.
I just recently did the same thing using the following answer from S/O
What is a very simple authentication scheme for Sinatra/Rack
It implies a user model, but instead of using that, I just set a user and admin password in my config file. Then I had a login form that just took a password. When the user enters that password, I checked it against the one in settings and set the session['user'] to :admin or :user according to whichever it matched (or nil if none). Then on each of my routes, I called auth: :user or auth: :admin accordingly.
APIs normally accept your login request and send you an authentication token which you need to pass back in each call. This is very similar to cookie based sessions where your browser automatically passes back the cookie which is acquired on initial website visit.
From what I've seen in Sinatra's docs, you could make a session-based authentication system like this:
enable :session
disable :show_exceptions
use Rack::Session::Pool,
key: 'session_id'
post '/login' do
user = User.login_success(params)
halt 401 if user.nil?
session[:user] = user
200
end
get '/fun' do
user = session[:user]
halt 401 if user.nil?
halt 403 if !user.has_permission_for '/fun'
"fun was had"
end
Now all you need to do in your client is to pass back the cookie token returned in response to initial visit when requesting an API function. This can be done with any web client library that supports cookie stores (such as libcurl) or by inserting the session cookie into the request header manually. Rack::Minitest functionality also supports cookies, so you can test your API with minitest.
See Sinatra API Authentication.
Quick summary:
Sinatra has no built-in auth.
It's best to build auth yourself (see the link).
There are gems available, but you probably won't need them for something as simple as an API.

Ruby On Rails authentication with Devise and (warden) callbacks

I have a rails app that is using Devise, with a User model, no scope.
I've also added activeadmin gem in to the app, Active Admin is a gem used for adding an admin dashboard in your application. It uses Devise for logging in users and creates a separate admin_user model for the admins.
I am allowing anonymous, non-logged in users to create shopping carts, creating a session[:cart_id]. If a user logs in I want associate the user with the cart, something like
Cart.find(session[:cart_id]).user = current_user
I was planning to use Wardens callbacks wardens callbacks to impliment this, something like so :
Warden::Manager.after_set_user :scope => :user do |user, auth, opts|
Cart.find(session[:cart_id]).user = user
end
However I get an error if I do that:
<% unless user_signed_in? %> throws an error :admin_user user is not logged in
Anyone got any ideas what is going on?
I've looked at related questions, but no help:
How to access session from Warden/Devise after_authentication callback in Rails
Where should warden callbacks be placed in a rails app?
The AdminUser model that Active Admin uses also executes this callback. So, maybe, an if can solve your problem:
Warden::Manager.after_set_user :scope => :user do |user, auth, opts|
Cart.find(session[:cart_id]).user = user if user.class == User
end
Actually it turned out the issue was solved by setting the default scope in warden,in the devise initializer file.
# Configure the default scope given to Warden. By default it's the first
# devise role declared in your routes (usually :user).
# config.default_scope = :user
Since the active admin routes were added above the devise routes for my user, the adminuser became the default user.

Resources