-
Notifications
You must be signed in to change notification settings - Fork 6
[WIP] add guides for bookingsync omniauth app #209
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,303 @@ | ||
| # Building New Rails Application With Devise And BookingSync Integration | ||
|
|
||
| If you need to integrate your existing Rails application (which most likely uses Devise for authentication) with BookingSync, that's the exact guide you are looking for. | ||
|
|
||
| ## Setup | ||
|
|
||
| After setting up environment for Ruby (check either [rvm](https://rvm.io) or [rbenv](https://github.com/rbenv/rbenv)), install `bundler` and `rails` gems: | ||
|
|
||
| ~~~ | ||
| gem install bundler | ||
| gem install rails | ||
| ~~~ | ||
|
|
||
| And generate new Rails application: | ||
|
|
||
| ~~~ | ||
| rails new omniauth-bookingsync-app | ||
| cd omniauth-bookingsync-app | ||
| ~~~ | ||
|
|
||
| To finish the setup we just need to create the database: | ||
|
|
||
| ~~~ | ||
| bundle exec rake db:create | ||
| ~~~ | ||
|
|
||
| To verify it works, just run: | ||
|
|
||
| ~~~ | ||
| rails server | ||
| ~~~ | ||
|
|
||
| And visit `http://localhost:3000`. | ||
|
|
||
|
|
||
| ## Adding Devise | ||
|
|
||
| Add [`devise`](https://github.com/plataformatec/devise) gem to Gemfile: | ||
|
|
||
| ~~~ Gemfile | ||
| gem 'devise' | ||
| ~~~ | ||
|
|
||
| And run: | ||
|
|
||
| ~~~ | ||
| bundle install | ||
| ~~~ | ||
|
|
||
| Then run the generator: | ||
|
|
||
| ~~~ | ||
| rails generate devise:install | ||
| rails generate devise:views | ||
| ~~~ | ||
|
|
||
| We don't need to have a signup / signin alternative with BookingSync, just a way to integrate the account, so we can remove this part from `app/views/devise/shared/_links.html.erb`: | ||
|
|
||
| ~~~ html app/views/devise/shared/_links.html.erb | ||
| <%- if devise_mapping.omniauthable? %> | ||
| <%- resource_class.omniauth_providers.each do |provider| %> | ||
| <%= link_to "Sign in with #{OmniAuth::Utils.camelize(provider)}", omniauth_authorize_path(resource_name, provider) %><br /> | ||
| <%- end -%> | ||
| <% end -%> | ||
| ~~~ | ||
|
|
||
| Another step would be generating a model for Devise, for the sake of simplicity let's call it `Account` to match the concept from BookingSync authentication resource: | ||
|
|
||
| ~~~ | ||
| rails generate devise account | ||
| ~~~ | ||
|
|
||
| The default migration generated by that command should be sufficient for this example app. Now you can run the migration: | ||
|
|
||
| ~~~ | ||
| rake db:migrate | ||
| ~~~ | ||
|
|
||
| ## Adding OmniAuth and integrating BookingSync | ||
|
|
||
| Go to the [BookingSync Partners section](https://www.bookingsync.com/en/partners/login) and register you account or sign in if you already have one. After you sign in, create a new application. You will need to the provide a name, an admin URL (the URL to which the user should be redirected to after signing in when developing `embedded` application) and a redirect URI. Here's an example: | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. register |
||
|
|
||
| <div> | ||
| <img alt="BookingSync" src="/images/bookingsync_new_application.png"> | ||
| </div> | ||
|
|
||
| In this case we want to have a separate application, not the one that should be accessible only from the applications' store, so `standalone` application is the way to go. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why, no reasons to have a standalone application if you can embedded it.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why would you want to have embedded application? If it's separate from BookingSync ecosystem, you would probably want to have it standalone instead of embedding, no? Or maybe I don't get the use case, but can't really think why it would be better to have embedded application here.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. no, the UX of users is greatly improved with Embedded applications. This keeps your management on single window and keep BookingSync sidebar small on the left. |
||
|
|
||
| Remember about providing data for both **en** and **fr** locales. You will also need to install this application on your BookingSync account using `Private App Access Code` in the applications' store. | ||
|
|
||
| Now add [omniauth-bookingsync](https://github.com/BookingSync/omniauth-bookingsync) gem to your Gemfile: | ||
|
|
||
| ~~~ | ||
| gem 'omniauth-bookingsync' | ||
| ~~~ | ||
|
|
||
| And create the initializer (e.g. `config/initializers/omniauth.rb`) for OmniAuth: | ||
|
|
||
| ~~~ ruby | ||
| Rails.application.config.middleware.use OmniAuth::Builder do | ||
| provider :bookingsync, ENV["BOOKINGSYNC_APPLICATION_ID"], ENV["BOOKINGSYNC_SECRET"], scope: "public rentals_read" | ||
| end | ||
| ~~~ | ||
|
|
||
| For the sake of example we added extra `rentals_read` scope besides `public` which is always included. You can learn more about available scopes [here](http://developers.bookingsync.com/reference/authorization/#scopes). | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
|
||
| To avoid hardcoding sensitive data, you can use [dotenv](https://github.com/bkeepers/dotenv) gem. | ||
|
|
||
| You can get both the application and app secret in partners section. | ||
|
|
||
| Now we need to generate some migrations for handling authentication process. To keep it simple here, we will add it to the `Account` model directly, but you will probably want to have a separte to-one relationship with other model like `BookingSyncAcount`. The details how the process works is beyond the scope of this guide, you can learn more about it [here](https://github.com/plataformatec/devise/wiki/OmniAuth:-Overview). | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
|
||
| ~~~ | ||
| rails g migration AddOAuthFieldsToAccounts provider:string uid:integer:index \ | ||
| name:string oauth_access_token:string oauth_refresh_token:string \ | ||
| oauth_expires_at:string | ||
| rake db:migrate | ||
| ~~~ | ||
|
|
||
| Now, make your `Account` model omniauthable: | ||
|
|
||
| ~~~ ruby app/models/user.rb | ||
| devise :omniauthable, omniauth_providers: [:bookingsync] | ||
| ~~~ | ||
|
|
||
| The next step would be authenticating and integrating BookingSync account. But to integrate the existing account, we need to be signed in already in the first place. Let's create some `admin` page that requires user to be logged in. First, let's add it to the routes, making it also a root path: | ||
|
|
||
| ~~~ ruby config/routes.rb | ||
| Rails.application.routes.draw do | ||
| root to: "admin#index" | ||
|
|
||
| devise_for :accounts, controllers: { omniauth_callbacks: "accounts/omniauth_callbacks" } | ||
|
|
||
| get "admin" => "admin#index" | ||
| end | ||
| ~~~ | ||
|
|
||
| Now let's create an `AdminController`: | ||
|
|
||
| ~~~ ruby app/controller/admin_controller.rb | ||
| class AdminController < ApplicationController | ||
| before_action :authenticate_account! | ||
|
|
||
| def index | ||
| end | ||
| end | ||
| ~~~ | ||
|
|
||
| And the last step would be creating a view: | ||
|
|
||
| ~~~ html app/views/admin/index.html.erb | ||
| <h1>Admin Section</h1> | ||
|
|
||
| <% if current_account.authorized? %> | ||
| <% current_account.remote_rentals.each do |rental| %> | ||
| <p> | ||
| <%= rental.name %> | ||
| </p> | ||
| <% end %> | ||
| <% else %> | ||
| <%= link_to "Connect with BookingSync", account_bookingsync_omniauth_authorize_path %> | ||
| <% end %> | ||
| ~~~ | ||
|
|
||
| To make sure it's working correctly we want to display some rentals coming from BookingSync API, we will call them `remote_rentals`. We will also need to add `Account#authorized?` method for checking if this account has already been authorized to access BookingSync API. | ||
|
|
||
| Let's update the routes and add a controller for handling authentication process and OAuth workflow: | ||
|
|
||
| ~~~ ruby config/routes.rb | ||
| Rails.application.routes.draw do | ||
| root to: "admin#index" | ||
|
|
||
| devise_for :accounts, controllers: { omniauth_callbacks: "accounts/omniauth_callbacks" } | ||
|
|
||
| get "admin" => "admin#index" | ||
| end | ||
| ~~~ | ||
|
|
||
| ~~~ ruby app/controllers/accounts/omniauth_callbacks_controller.rb | ||
| class Accounts::OmniauthCallbacksController < Devise::OmniauthCallbacksController | ||
| before_action :authenticate_account! | ||
|
|
||
| def bookingsync | ||
| current_account.assign_omniauth_attributes(omniauth_attribues) | ||
|
|
||
| if current_account.save | ||
| set_flash_message(:notice, :success, kind: "BookingSync") if is_navigational_format? | ||
| redirect_to admin_path | ||
| else | ||
| redirect_to root_path | ||
| end | ||
| end | ||
|
|
||
| def failure | ||
| redirect_to root_path | ||
| end | ||
|
|
||
| private | ||
|
|
||
| def omniauth_attribues | ||
| request.env["omniauth.auth"] | ||
| end | ||
| end | ||
| ~~~ | ||
|
|
||
| Most of the logic is already handled here by `OmniAuth` and `devise`, so we don't need to do much ourselves. We just need to `Account#assign_omniauth_attributes` method to handle attributes assignment such as `uid`, `name` and token-related attributes. Here's the implementation: | ||
|
|
||
| ~~~ app/models/account.rb | ||
| class Account < ApplicationRecord | ||
| # Include default devise modules. Others available are: | ||
| # :confirmable, :lockable, :timeoutable and :omniauthable | ||
| devise :database_authenticatable, :registerable, | ||
| :recoverable, :rememberable, :trackable, :validatable | ||
|
|
||
| devise :omniauthable, omniauth_providers: [:bookingsync] | ||
|
|
||
| def assign_omniauth_attributes(auth) | ||
| self.uid = auth.uid | ||
| self.provider = auth.provides | ||
| self.name = auth.info.business_name | ||
| update_token(auth.credentials) | ||
| end | ||
|
|
||
| def update_token(token) | ||
| self.oauth_access_token = token.token | ||
| self.oauth_refresh_token = token.refresh_token | ||
| self.oauth_expires_at = token.expires_at | ||
| end | ||
|
|
||
| def authorized? | ||
| oauth_access_token.present? && oauth_refresh_token.present? | ||
| end | ||
| end | ||
| ~~~ | ||
|
|
||
| We also added `Account#authorized?` method which checks if the token and refresh token are present. | ||
|
|
||
| That way we have all the logic required for connecting the application's account with BookingSync account. The only thing left is providing the way to fetch rentals. | ||
|
|
||
| ## Adding bookingsync-api And Fetching Rentals | ||
|
|
||
| Let's add `bookingsync-api` gem which handles the communication with BookingSync API: | ||
|
|
||
| ~~~ | ||
| gem "bookingsync-api" | ||
| ~~~ | ||
|
|
||
| Now we can add some methods to `Account` model: | ||
|
|
||
| ~~~ ruby app/models/account.rb | ||
| class Account < ApplicationRecord | ||
| # some previous methods already covered before go here | ||
|
|
||
| def remote_rentals | ||
| bookingsync_api_client.rentals | ||
| end | ||
|
|
||
| def bookingsync_api_client | ||
| @bookingsync_api_client ||= BookingSync::API.new(oauth_token) | ||
| end | ||
|
|
||
| def oauth_token | ||
| token.token | ||
| end | ||
|
|
||
| def token | ||
| @token ||= begin | ||
| token_options = {} | ||
| if oauth_refresh_token | ||
| token_options[:refresh_token] = oauth_refresh_token | ||
| token_options[:expires_at] = oauth_expires_at | ||
| end | ||
|
|
||
| token = OAuth2::AccessToken.new(oauth_client, oauth_access_token, token_options) | ||
|
|
||
| if token.expired? | ||
| refresh_token!(token) | ||
| else | ||
| token | ||
| end | ||
| end | ||
| end | ||
|
|
||
| def refresh_token!(current_token = token) | ||
| @token = current_token.refresh!.tap { |new_token| update_token(new_token) } | ||
| save! | ||
| @token | ||
| end | ||
|
|
||
| def oauth_client | ||
| client_options = { | ||
| site: "https://www.bookingsync.com", | ||
| connection_opts: { headers: { accept: "application/vnd.api+json" } } | ||
| } | ||
| client_options[:ssl] = { verify: true } | ||
| OAuth2::Client.new(ENV["BOOKINGSYNC_APPLICATION_ID"], ENV["BOOKINGSYNC_SECRET"], client_options) | ||
| end | ||
| end | ||
| ~~~ | ||
|
|
||
| `Account#remote_rentals` method is responsible for fetching `rentals` from the API and it simply delegates the logic to `bookingsync_api_client`. The API client requires `oauth_token`, which is returned in `token` method. This method also ensures that the token will also be valid - if the token expires, it will perform a proper request using `refresh_token` to get a new one. Fetching tokens requires properly configured `oauth_client` with the right headers and application's data. | ||
|
|
||
| And that's it! Now you can sign in, click the link to connect your account with BookingSync, perform authorization and after redirecting back to you app you should see a list of rentals (if you have created any). | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. back to |
||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This isn't properly formatted on the Readme page. Is this a comment? Please refer the rendered page https://github.com/BookingSync/bookingsync-api-docs/blob/fb1069e6f88e69a07936a094d2bae1a64c512c42/content/guides/set-up-rails-appplication-with-omniauth-bookingsync.md
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not really a comment, it's a part of the docs, just not sure why it doesn't really work for html / erb.