├── files ├── .irbrc ├── db │ └── seeds.rb ├── app │ ├── controllers │ │ ├── users_controller.rb │ │ └── application_controller.rb │ ├── resources │ │ ├── user_resource.rb │ │ └── application_resource.rb │ └── models │ │ └── user.rb ├── config │ ├── routes.rb │ ├── initializers │ │ ├── cors.rb │ │ └── doorkeeper.rb │ └── locales │ │ └── doorkeeper.en.yml ├── spec │ ├── factories │ │ ├── user.rb │ │ └── access_token.rb │ ├── support │ │ └── with_a_logged_in_user.rb │ ├── requests │ │ └── register_spec.rb │ └── rails_helper.rb ├── bin │ └── sample-data ├── README.md └── .circleci │ └── config.yml ├── rc ├── bin └── apiup ├── .gitignore ├── LICENSE.txt ├── template.rb └── README.md /files/.irbrc: -------------------------------------------------------------------------------- 1 | IRB.conf[:USE_AUTOCOMPLETE] = false 2 | -------------------------------------------------------------------------------- /files/db/seeds.rb: -------------------------------------------------------------------------------- 1 | User.create!(email: "example@example.com", password: "password") 2 | -------------------------------------------------------------------------------- /files/app/controllers/users_controller.rb: -------------------------------------------------------------------------------- 1 | class UsersController < ApplicationController 2 | end 3 | -------------------------------------------------------------------------------- /files/app/resources/user_resource.rb: -------------------------------------------------------------------------------- 1 | class UserResource < ApplicationResource 2 | attributes :email, :password 3 | end 4 | -------------------------------------------------------------------------------- /files/config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | use_doorkeeper 3 | jsonapi_resources :users, only: %w[create] 4 | end 5 | -------------------------------------------------------------------------------- /files/app/models/user.rb: -------------------------------------------------------------------------------- 1 | class User < ApplicationRecord 2 | has_secure_password 3 | 4 | validates :email, presence: true, uniqueness: true 5 | end 6 | -------------------------------------------------------------------------------- /rc: -------------------------------------------------------------------------------- 1 | --api 2 | --skip-action-cable 3 | --skip-action-mailbox 4 | --skip-active-storage 5 | --skip-bootsnap 6 | --skip-bundle 7 | --skip-test 8 | --database=postgresql 9 | -------------------------------------------------------------------------------- /files/spec/factories/user.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :user do 3 | sequence(:email) { |n| "example#{n}@example.com" } 4 | password { "password" } 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /files/spec/factories/access_token.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :access_token, class: "Doorkeeper::AccessToken" do 3 | resource_owner_id { FactoryBot.create(:user).id } 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /bin/apiup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | basedir = File.expand_path('..', File.dirname(__FILE__)) 4 | project_name = ARGV[0] 5 | 6 | command = "rails new --rc=#{basedir}/rc --template=#{basedir}/template.rb #{project_name}" 7 | puts command 8 | exec command 9 | -------------------------------------------------------------------------------- /files/app/resources/application_resource.rb: -------------------------------------------------------------------------------- 1 | class ApplicationResource < JSONAPI::Resource 2 | abstract 3 | 4 | private 5 | 6 | def current_user 7 | context.fetch(:current_user) 8 | end 9 | 10 | class << self 11 | private 12 | 13 | def current_user(options) 14 | options.fetch(:context).fetch(:current_user) 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /files/bin/sample-data: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require_relative "../config/environment" 3 | 4 | if Rails.env != "development" 5 | puts "Rails environment is #{Rails.env}; not gonna truncate THAT!" 6 | exit false 7 | end 8 | 9 | # `db:seed` is for actual production reference data 10 | # this file is for sample dev data 11 | # you may want to `destroy_all` first so it's idempotent 12 | -------------------------------------------------------------------------------- /files/spec/support/with_a_logged_in_user.rb: -------------------------------------------------------------------------------- 1 | RSpec.shared_context "with a logged in user" do 2 | let!(:user) { FactoryBot.create(:user) } 3 | let!(:token) do 4 | FactoryBot.create(:access_token, resource_owner_id: user.id).token 5 | end 6 | let(:headers) do 7 | { 8 | "Authorization" => "Bearer #{token}", 9 | "Content-Type" => "application/vnd.api+json" 10 | } 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /files/app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | include JSONAPI::ActsAsResourceController 3 | skip_before_action :verify_authenticity_token 4 | 5 | private 6 | 7 | def context 8 | {current_user: current_user} 9 | end 10 | 11 | def current_user 12 | @current_user ||= User.find(doorkeeper_token.resource_owner_id) if doorkeeper_token 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # General 2 | .DS_Store 3 | .AppleDouble 4 | .LSOverride 5 | 6 | # Icon must end with two \r 7 | Icon 8 | 9 | # Thumbnails 10 | ._* 11 | 12 | # Files that might appear in the root of a volume 13 | .DocumentRevisions-V100 14 | .fseventsd 15 | .Spotlight-V100 16 | .TemporaryItems 17 | .Trashes 18 | .VolumeIcon.icns 19 | .com.apple.timemachine.donotpresent 20 | 21 | # Directories potentially created on remote AFP share 22 | .AppleDB 23 | .AppleDesktop 24 | Network Trash Folder 25 | Temporary Items 26 | .apdisk 27 | -------------------------------------------------------------------------------- /files/config/initializers/cors.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Avoid CORS issues when API is called from the frontend app. 4 | # Handle Cross-Origin Resource Sharing (CORS) in order to accept cross-origin AJAX requests. 5 | 6 | # Read more: https://github.com/cyu/rack-cors 7 | 8 | Rails.application.config.middleware.insert_before 0, Rack::Cors do 9 | allow do 10 | origins "*" 11 | 12 | resource "*", 13 | headers: :any, 14 | methods: [:get, :post, :put, :patch, :delete, :options, :head] 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2018 Josh Justice 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this project except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /files/spec/requests/register_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.describe "registering", type: :request do 4 | let(:headers) { 5 | { 6 | "Content-Type" => "application/vnd.api+json" 7 | } 8 | } 9 | 10 | it "allows creating a user" do 11 | email = "example@example.com" 12 | password = "mypassword" 13 | 14 | params = { 15 | data: { 16 | type: "users", 17 | attributes: { 18 | email: email, 19 | password: password 20 | } 21 | } 22 | } 23 | 24 | post "/users", params: params.to_json, headers: headers 25 | 26 | expect(response.status).to eq(201) 27 | expect(User.count).to eq(1) 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /files/README.md: -------------------------------------------------------------------------------- 1 | # [APP NAME] 2 | 3 | This is a short description of your app. 4 | 5 | ## Getting Started 6 | 7 | ### Requirements 8 | 9 | 1. Ruby 10 | 1. PostgreSQL (e.g. [Postgres.app][postgres-app]) 11 | 12 | ### Setup 13 | 14 | ```sh 15 | $ bundle install 16 | $ rails db:setup 17 | ``` 18 | 19 | ### Development 20 | 21 | To generate models, resources, and controllers: 22 | 23 | ```bash 24 | $ rails generate model widget [fields] 25 | $ rails generate jsonapi:resource widget 26 | $ rails generate jsonapi:controller widget 27 | ``` 28 | 29 | ### Testing 30 | 31 | ```sh 32 | $ bin/rspec 33 | ``` 34 | 35 | In request tests, you can use the user and access token factories to create test data to access protected resources: 36 | 37 | ```ruby 38 | user = FactoryBot.create(:user) 39 | token = FactoryBot.create(:access_token, resource_owner_id: user.id).token 40 | headers = { 41 | 'Authorization' => "Bearer #{token}", 42 | 'Content-Type' => 'application/vnd.api+json', 43 | } 44 | 45 | # assuming you have a Widget model that belongs to a User 46 | FactoryBot.create(:widget, user: user) 47 | 48 | get '/widgets', headers: headers 49 | ``` 50 | 51 | ### Running 52 | 53 | ```sh 54 | $ rails server 55 | ``` 56 | 57 | [postgres-app]: http://postgresapp.com 58 | -------------------------------------------------------------------------------- /files/.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | build: 4 | docker: 5 | # specify the version you desire here 6 | - image: cimg/ruby:3.2.0 7 | environment: 8 | PGHOST: 127.0.0.1 9 | PGUSER: rails 10 | 11 | # Specify service dependencies here if necessary 12 | # CircleCI maintains a library of pre-built images 13 | # documented at https://circleci.com/docs/2.0/circleci-images/ 14 | - image: cimg/postgres:15.1 15 | environment: 16 | POSTGRES_USER: rails 17 | POSTGRES_PASSWORD: apppassword 18 | 19 | working_directory: ~/repo 20 | 21 | steps: 22 | - checkout 23 | 24 | # Download and cache dependencies 25 | - restore_cache: 26 | keys: 27 | - v1-dependencies-{{ checksum "Gemfile.lock" }} 28 | # fallback to using the latest cache if no exact match is found 29 | - v1-dependencies- 30 | 31 | - run: 32 | name: install dependencies 33 | command: | 34 | gem update bundler 35 | bundle config set path 'vendor/bundle' 36 | bundle install --jobs=4 --retry=3 37 | 38 | - save_cache: 39 | paths: 40 | - ./vendor/bundle 41 | key: v1-dependencies-{{ checksum "Gemfile.lock" }} 42 | 43 | # Database setup 44 | - run: bundle exec rails db:create 45 | - run: bundle exec rails db:schema:load 46 | 47 | # run tests! 48 | - run: 49 | name: run tests 50 | command: | 51 | mkdir /tmp/test-results 52 | TEST_FILES="$(circleci tests glob "spec/**/*_spec.rb" | circleci tests split --split-by=timings)" 53 | 54 | bundle exec rspec --format progress \ 55 | --format RspecJunitFormatter \ 56 | --out /tmp/test-results/rspec.xml \ 57 | --format progress \ 58 | $TEST_FILES 59 | 60 | # collect reports 61 | - store_test_results: 62 | path: /tmp/test-results 63 | - store_artifacts: 64 | path: /tmp/test-results 65 | destination: test-results 66 | -------------------------------------------------------------------------------- /files/spec/rails_helper.rb: -------------------------------------------------------------------------------- 1 | # This file is copied to spec/ when you run 'rails generate rspec:install' 2 | require "spec_helper" 3 | ENV["RAILS_ENV"] ||= "test" 4 | require File.expand_path("../../config/environment", __FILE__) 5 | # Prevent database truncation if the environment is production 6 | abort("The Rails environment is running in production mode!") if Rails.env.production? 7 | require "rspec/rails" 8 | # Add additional requires below this line. Rails is not loaded until this point! 9 | 10 | # Requires supporting ruby files with custom matchers and macros, etc, in 11 | # spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are 12 | # run as spec files by default. This means that files in spec/support that end 13 | # in _spec.rb will both be required and run as specs, causing the specs to be 14 | # run twice. It is recommended that you do not name files matching this glob to 15 | # end with _spec.rb. You can configure this pattern with the --pattern 16 | # option on the command line or in ~/.rspec, .rspec or `.rspec-local`. 17 | # 18 | # The following line is provided for convenience purposes. It has the downside 19 | # of increasing the boot-up time by auto-requiring all files in the support 20 | # directory. Alternatively, in the individual `*_spec.rb` files, manually 21 | # require only the support files necessary. 22 | # 23 | Dir[Rails.root.join("spec", "support", "**", "*.rb")].each { |f| require f } 24 | 25 | # Checks for pending migrations and applies them before tests are run. 26 | # If you are not using ActiveRecord, you can remove these lines. 27 | begin 28 | ActiveRecord::Migration.maintain_test_schema! 29 | rescue ActiveRecord::PendingMigrationError => e 30 | puts e.to_s.strip 31 | exit 1 32 | end 33 | RSpec.configure do |config| 34 | # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures 35 | config.fixture_path = "#{::Rails.root}/spec/fixtures" 36 | 37 | # If you're not using ActiveRecord, or you'd prefer not to run each of your 38 | # examples within a transaction, remove the following line or assign false 39 | # instead of true. 40 | config.use_transactional_fixtures = true 41 | 42 | # RSpec Rails can automatically mix in different behaviours to your tests 43 | # based on their file location, for example enabling you to call `get` and 44 | # `post` in specs under `spec/controllers`. 45 | # 46 | # You can disable this behaviour by removing the line below, and instead 47 | # explicitly tag your specs with their type, e.g.: 48 | # 49 | # RSpec.describe UsersController, :type => :controller do 50 | # # ... 51 | # end 52 | # 53 | # The different available types are documented in the features, such as in 54 | # https://relishapp.com/rspec/rspec-rails/docs 55 | config.infer_spec_type_from_file_location! 56 | 57 | # Filter lines from Rails gems in backtraces. 58 | config.filter_rails_from_backtrace! 59 | # arbitrary gems may also be filtered via: 60 | # config.filter_gems_from_backtrace("gem name") 61 | end 62 | -------------------------------------------------------------------------------- /template.rb: -------------------------------------------------------------------------------- 1 | def commit(message) 2 | git add: "." 3 | git commit: "-m '#{message}'" 4 | end 5 | 6 | def copy_file(file_name, directory = ".") 7 | inside(directory) do 8 | puts "CURRENT PATH: #{File.dirname(__FILE__)}" 9 | file_path = File.expand_path("files/#{file_name}", File.dirname(__FILE__)) 10 | run "cp #{file_path} ." 11 | end 12 | end 13 | 14 | def remove_file(file_name) 15 | run "rm #{file_name}" 16 | end 17 | 18 | git :init 19 | commit "Create Rails app" 20 | 21 | copy_file "../files/README.md" 22 | run %(sed -i '' "s/\\[APP NAME\\]/#{app_path.titleize}/" README.md) 23 | commit "Use markdown readme" 24 | 25 | run "sed -i '' '/^.*#/ d' Gemfile" 26 | commit "Remove Gemfile comments" 27 | 28 | run "sed -i '' '/tzinfo-data/ d' Gemfile" 29 | commit "Remove unused gems" 30 | 31 | gem "rack-cors" 32 | gem "jsonapi-resources" 33 | gem "bcrypt" 34 | gem "doorkeeper" 35 | 36 | commit "Add gems for all environments" 37 | 38 | gem_group :development do 39 | gem "bullet" 40 | gem "dotenv-rails" 41 | end 42 | 43 | gem_group :development, :test do 44 | gem "rspec-rails" 45 | gem "coderay" 46 | gem "standard" 47 | end 48 | 49 | commit "Add development gems" 50 | 51 | gem_group :test do 52 | gem "factory_bot_rails" 53 | gem "rspec_junit_formatter" 54 | end 55 | 56 | commit "Add test gems" 57 | 58 | gem_group :production do 59 | gem "rack-attack" 60 | end 61 | commit "Add production gems" 62 | 63 | run "bundle install" 64 | commit "Bundle gems" 65 | 66 | # https://github.com/doorkeeper-gem/doorkeeper/issues/1577 67 | copy_file "../files/config/initializers/doorkeeper.rb", "config/initializers" 68 | run "rails generate doorkeeper:install -f" 69 | run "rails generate doorkeeper:migration" 70 | 71 | # TODO: match variable number of spaces 72 | run "sed -i '' 's/t.references :application, null: false/t.references :application/' db/migrate/*_create_doorkeeper_tables.rb" 73 | 74 | copy_file "../files/config/initializers/doorkeeper.rb", "config/initializers" 75 | copy_file "../files/config/locales/doorkeeper.en.yml", "config/locales" 76 | copy_file "../files/spec/factories/access_token.rb", "spec/factories" 77 | commit "Configure doorkeeper" 78 | 79 | run "bundle binstubs bundler --force" 80 | run "bundle binstubs rspec-core" 81 | run "rails generate rspec:install" 82 | commit "Set up RSpec" 83 | 84 | run "rails generate model user email:string:uniq password_digest:string" 85 | copy_file "../files/db/seeds.rb", "db" 86 | copy_file "../files/app/models/user.rb", "app/models" 87 | copy_file "../files/spec/factories/user.rb", "spec/factories" 88 | remove_file "spec/models/user_spec.rb" 89 | commit "Add user model" 90 | 91 | copy_file "../files/app/controllers/application_controller.rb", "app/controllers" 92 | copy_file "../files/app/resources/application_resource.rb", "app/resources" 93 | commit "Expose Doorkeeper user to JR" 94 | 95 | copy_file "../files/app/controllers/users_controller.rb", "app/controllers" 96 | copy_file "../files/app/resources/user_resource.rb", "app/resources" 97 | copy_file "../files/config/routes.rb", "config" 98 | copy_file "../files/spec/requests/register_spec.rb", "spec/requests" 99 | commit "Expose user create endpoint" 100 | 101 | copy_file "../files/spec/rails_helper.rb", "spec" 102 | copy_file "../files/spec/support/with_a_logged_in_user.rb", "spec/support" 103 | commit "Add shared context for setting up a logged in user" 104 | 105 | copy_file "../files/config/initializers/cors.rb", "config/initializers" 106 | commit "Configure CORS" 107 | 108 | copy_file "../files/bin/sample-data", "bin" 109 | commit "Add sample data script" 110 | 111 | copy_file "../files/.circleci/config.yml", ".circleci" 112 | commit "Configure CircleCI" 113 | 114 | run "rails db:create" 115 | run "rails db:migrate" 116 | run "rails db:seed" 117 | commit "Set up database" 118 | 119 | copy_file "../files/.irbrc", ".irbrc" 120 | commit "Disable console autocomplete" 121 | 122 | run "bundle exec standardrb --fix" 123 | commit "Format to Standard" 124 | 125 | # TODO: clean up gem file 126 | # TODO: Ruby version in gemfile? 127 | # TODO: better error output 128 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # apiup 2 | 3 | Creates a new Rails API pre-configured for JSON API and OAuth 2 authentication: 4 | 5 | - Configures [JSONAPI::Resources](http://jsonapi-resources.com/) for [JSON API](http://jsonapi.org/) 6 | - Configures [Doorkeeper](https://github.com/doorkeeper-gem/doorkeeper) for OAuth 2 authentication 7 | - Creates a User model with [`has_secure_password`](https://api.rubyonrails.org/classes/ActiveModel/SecurePassword/ClassMethods.html#method-i-has_secure_password) for password storage 8 | - Sets up a `POST /users` endpoint for registration 9 | - Configures [factory_bot](https://github.com/thoughtbot/factory_bot) factories for User and access tokens to make request specs easy 10 | - Passes the `current_user` to JSONAPI::Resources 11 | 12 | Also includes the following setup: 13 | 14 | - Enables Rails API mode 15 | - Removes Action Cable, Active Storage, and Bootsnap 16 | - Uses Postgres instead of SQLite 17 | - Uses [RSpec](http://rspec.info/) instead of Minitest 18 | - Disables authenticity token 19 | - Enables [CORS](https://github.com/cyu/rack-cors) 20 | - Configures a CircleCI configuration file for continuous integration 21 | - Adds: 22 | - [Bullet](https://github.com/flyerhzm/bullet) 23 | - [Dotenv](https://github.com/bkeepers/dotenv) 24 | - [Faker](https://github.com/stympy/faker) 25 | - [Rack-Attack](https://github.com/kickstarter/rack-attack) 26 | 27 | To learn more, see ["Authorizing jsonapi_resources"](https://www.bignerdranch.com/blog/authorizing-jsonapi-resources-part-1-visibility/). 28 | 29 | ## Installation 30 | 31 | Download the repo, then run `bin/apiup NEW-APP-NAME`. 32 | 33 | To be able to run `apiup` from anywhere, add the repo's `bin` directory to your `PATH`. 34 | 35 | ## Usage 36 | 37 | You can set up your API using typical Rails, JSONAPI::Resources, and Doorkeeper features. Here are some common first steps. 38 | 39 | ### Creating a model 40 | 41 | Say you're creating a project management app. Start with generating a Project model: 42 | 43 | ```sh 44 | $ rails generate model project name:string 45 | ``` 46 | 47 | You can add `field:type` pairs to automatically add them: 48 | 49 | The list of available types is at 50 | 51 | If you want a record to be connected to another record, add the name of that model, with the `:references` field type. For example, to associate the record with a user, add `user:references`. 52 | 53 | ### Creating a resource 54 | 55 | Resources control the public view of your model that is exposed. This is the main class you'll modify. 56 | 57 | ```sh 58 | $ rails generate jsonapi:resource project 59 | ``` 60 | 61 | Then update the resource to inherit from `ApplicationResource`. 62 | 63 | Add each attribute you want publicly visible. Add each `has_many` or `has_one` relationship you want to expose as well: 64 | 65 | ```ruby 66 | class ProjectResource < ApplicationResource 67 | attribute :name 68 | has_many :stories 69 | end 70 | ``` 71 | 72 | If you want to automatically assign a created record to the logged-in user, pass a blog to `before_create` (note that `current_user` will only be available if you inherit from `ApplicationResource`): 73 | 74 | ```ruby 75 | before_create do 76 | _model.user = current_user 77 | end 78 | ``` 79 | 80 | You may also want to prevent manually assigning the `user` by removing it from the list of creatable and updatable fields: 81 | 82 | ```ruby 83 | def self.creatable_fields(context) 84 | super - [:user] 85 | end 86 | 87 | def self.updatable_fields(context) 88 | super - [:user] 89 | end 90 | ``` 91 | 92 | If you want to limit the records shown, override `self.records`. For example, to return only records belonging to the current user: 93 | 94 | ```ruby 95 | def self.records(options = {}) 96 | user = current_user(options) 97 | user.projects 98 | end 99 | ``` 100 | 101 | (Note that the class method `current_user` requires `options` to be passed to it, whereas the instance method `current_user` does not.) 102 | 103 | ### Creating a controller 104 | 105 | To create a controller for a JSON:API resource: 106 | 107 | ```sh 108 | $ rails generate jsonapi:controller projects 109 | ``` 110 | 111 | Update the controller to inherit from `ApplicationController`. This disables CSRF and makes the `current_user` available to the resources. 112 | 113 | If you don't want a controller to be available to users who aren't logged in, add: 114 | 115 | ```ruby 116 | before_action :doorkeeper_authorize! 117 | ``` 118 | 119 | You shouldn't need to customize anything else in the controller. 120 | 121 | ### Adding routes 122 | 123 | Add the following to `routes.rb`: 124 | 125 | ```ruby 126 | jsonapi_resources :projects 127 | ``` 128 | 129 | Not only will `jsonapi_resources` add the routes for the projects model, it will also add nested routes for any models related to projects. 130 | 131 | ## Thanks 132 | 133 | Based on [this blog post](http://iamvery.com/2015/02/17/rails-new-for-you.html) by [iamvery](https://github.com/iamvery). 134 | 135 | ## License 136 | 137 | Apache-2.0. See `License.txt` for details. 138 | -------------------------------------------------------------------------------- /files/config/locales/doorkeeper.en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | activerecord: 3 | attributes: 4 | doorkeeper/application: 5 | name: 'Name' 6 | redirect_uri: 'Redirect URI' 7 | errors: 8 | models: 9 | doorkeeper/application: 10 | attributes: 11 | redirect_uri: 12 | fragment_present: 'cannot contain a fragment.' 13 | invalid_uri: 'must be a valid URI.' 14 | relative_uri: 'must be an absolute URI.' 15 | secured_uri: 'must be an HTTPS/SSL URI.' 16 | forbidden_uri: 'is forbidden by the server.' 17 | 18 | doorkeeper: 19 | applications: 20 | confirmations: 21 | destroy: 'Are you sure?' 22 | buttons: 23 | edit: 'Edit' 24 | destroy: 'Destroy' 25 | submit: 'Submit' 26 | cancel: 'Cancel' 27 | authorize: 'Authorize' 28 | form: 29 | error: 'Whoops! Check your form for possible errors' 30 | help: 31 | confidential: 'Application will be used where the client secret can be kept confidential. Native mobile apps and Single Page Apps are considered non-confidential.' 32 | redirect_uri: 'Use one line per URI' 33 | native_redirect_uri: 'Use %{native_redirect_uri} if you want to add localhost URIs for development purposes' 34 | scopes: 'Separate scopes with spaces. Leave blank to use the default scopes.' 35 | edit: 36 | title: 'Edit application' 37 | index: 38 | title: 'Your applications' 39 | new: 'New Application' 40 | name: 'Name' 41 | callback_url: 'Callback URL' 42 | confidential: 'Confidential?' 43 | confidentiality: 44 | 'yes': 'Yes' 45 | 'no': 'No' 46 | new: 47 | title: 'New Application' 48 | show: 49 | title: 'Application: %{name}' 50 | application_id: 'Application Id' 51 | secret: 'Secret' 52 | scopes: 'Scopes' 53 | confidential: 'Confidential' 54 | callback_urls: 'Callback urls' 55 | actions: 'Actions' 56 | 57 | authorizations: 58 | buttons: 59 | authorize: 'Authorize' 60 | deny: 'Deny' 61 | error: 62 | title: 'An error has occurred' 63 | new: 64 | title: 'Authorization required' 65 | prompt: 'Authorize %{client_name} to use your account?' 66 | able_to: 'This application will be able to' 67 | show: 68 | title: 'Authorization code' 69 | 70 | authorized_applications: 71 | confirmations: 72 | revoke: 'Are you sure?' 73 | buttons: 74 | revoke: 'Revoke' 75 | index: 76 | title: 'Your authorized applications' 77 | application: 'Application' 78 | created_at: 'Created At' 79 | date_format: '%Y-%m-%d %H:%M:%S' 80 | 81 | errors: 82 | messages: 83 | # Common error messages 84 | invalid_request: 'The request is missing a required parameter, includes an unsupported parameter value, or is otherwise malformed.' 85 | invalid_redirect_uri: "The requested redirect uri is malformed or doesn't match client redirect URI." 86 | unauthorized_client: 'The client is not authorized to perform this request using this method.' 87 | access_denied: 'The resource owner or authorization server denied the request.' 88 | invalid_scope: 'The requested scope is invalid, unknown, or malformed.' 89 | server_error: 'The authorization server encountered an unexpected condition which prevented it from fulfilling the request.' 90 | temporarily_unavailable: 'The authorization server is currently unable to handle the request due to a temporary overloading or maintenance of the server.' 91 | 92 | # Configuration error messages 93 | credential_flow_not_configured: 'Resource Owner Password Credentials flow failed due to Doorkeeper.configure.resource_owner_from_credentials being unconfigured.' 94 | resource_owner_authenticator_not_configured: 'Resource Owner find failed due to Doorkeeper.configure.resource_owner_authenticator being unconfigured.' 95 | 96 | # Access grant errors 97 | unsupported_response_type: 'The authorization server does not support this response type.' 98 | 99 | # Access token errors 100 | invalid_client: 'Client authentication failed due to unknown client, no client authentication included, or unsupported authentication method.' 101 | invalid_grant: 'The provided authorization grant is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client.' 102 | invalid_user_or_password: 'Invalid username or password.' 103 | unsupported_grant_type: 'The authorization grant type is not supported by the authorization server.' 104 | 105 | invalid_token: 106 | revoked: "The access token was revoked" 107 | expired: "The access token expired" 108 | unknown: "The access token is invalid" 109 | 110 | flash: 111 | applications: 112 | create: 113 | notice: 'Application created.' 114 | destroy: 115 | notice: 'Application deleted.' 116 | update: 117 | notice: 'Application updated.' 118 | authorized_applications: 119 | destroy: 120 | notice: 'Application revoked.' 121 | 122 | layouts: 123 | admin: 124 | nav: 125 | oauth2_provider: 'OAuth2 Provider' 126 | applications: 'Applications' 127 | home: 'Home' 128 | application: 129 | title: 'OAuth authorization required' 130 | -------------------------------------------------------------------------------- /files/config/initializers/doorkeeper.rb: -------------------------------------------------------------------------------- 1 | Doorkeeper.configure do 2 | # Change the ORM that doorkeeper will use (needs plugins) 3 | orm :active_record 4 | 5 | grant_flows %w[password] 6 | 7 | # see https://github.com/doorkeeper-gem/doorkeeper/issues/561#issuecomment-612857163 8 | skip_client_authentication_for_password_grant true 9 | 10 | resource_owner_from_credentials do 11 | user = User.find_by(email: params[:username]) 12 | if user&.authenticate(params[:password]) 13 | user 14 | else 15 | raise Doorkeeper::Errors::DoorkeeperError.new("invalid_user_or_password") 16 | end 17 | end 18 | 19 | # This block will be called to check whether the resource owner is authenticated or not. 20 | resource_owner_authenticator do 21 | fail "Please configure doorkeeper resource_owner_authenticator block located in #{__FILE__}" 22 | # Put your resource owner authentication logic here. 23 | # Example implementation: 24 | # User.find_by_id(session[:user_id]) || redirect_to(new_user_session_url) 25 | end 26 | 27 | # If you want to restrict access to the web interface for adding oauth authorized applications, you need to declare the block below. 28 | # admin_authenticator do 29 | # # Put your admin authentication logic here. 30 | # # Example implementation: 31 | # Admin.find_by_id(session[:admin_id]) || redirect_to(new_admin_session_url) 32 | # end 33 | 34 | # Authorization Code expiration time (default 10 minutes). 35 | # authorization_code_expires_in 10.minutes 36 | 37 | # Access token expiration time (default 2 hours). 38 | # If you want to disable expiration, set this to nil. 39 | # access_token_expires_in 2.hours 40 | 41 | # Assign a custom TTL for implicit grants. 42 | # custom_access_token_expires_in do |oauth_client| 43 | # oauth_client.application.additional_settings.implicit_oauth_expiration 44 | # end 45 | 46 | # Use a custom class for generating the access token. 47 | # https://github.com/doorkeeper-gem/doorkeeper#custom-access-token-generator 48 | # access_token_generator '::Doorkeeper::JWT' 49 | 50 | # The controller Doorkeeper::ApplicationController inherits from. 51 | # Defaults to ActionController::Base. 52 | # https://github.com/doorkeeper-gem/doorkeeper#custom-base-controller 53 | # base_controller 'ApplicationController' 54 | 55 | # Reuse access token for the same resource owner within an application (disabled by default) 56 | # Rationale: https://github.com/doorkeeper-gem/doorkeeper/issues/383 57 | # reuse_access_token 58 | 59 | # Issue access tokens with refresh token (disabled by default) 60 | # use_refresh_token 61 | 62 | # Provide support for an owner to be assigned to each registered application (disabled by default) 63 | # Optional parameter confirmation: true (default false) if you want to enforce ownership of 64 | # a registered application 65 | # Note: you must also run the rails g doorkeeper:application_owner generator to provide the necessary support 66 | # enable_application_owner confirmation: false 67 | 68 | # Define access token scopes for your provider 69 | # For more information go to 70 | # https://github.com/doorkeeper-gem/doorkeeper/wiki/Using-Scopes 71 | # default_scopes :public 72 | # optional_scopes :write, :update 73 | 74 | # Change the way client credentials are retrieved from the request object. 75 | # By default it retrieves first from the `HTTP_AUTHORIZATION` header, then 76 | # falls back to the `:client_id` and `:client_secret` params from the `params` object. 77 | # Check out https://github.com/doorkeeper-gem/doorkeeper/wiki/Changing-how-clients-are-authenticated 78 | # for more information on customization 79 | # client_credentials :from_basic, :from_params 80 | 81 | # Change the way access token is authenticated from the request object. 82 | # By default it retrieves first from the `HTTP_AUTHORIZATION` header, then 83 | # falls back to the `:access_token` or `:bearer_token` params from the `params` object. 84 | # Check out https://github.com/doorkeeper-gem/doorkeeper/wiki/Changing-how-clients-are-authenticated 85 | # for more information on customization 86 | # access_token_methods :from_bearer_authorization, :from_access_token_param, :from_bearer_param 87 | 88 | # Change the native redirect uri for client apps 89 | # When clients register with the following redirect uri, they won't be redirected to any server and the authorization code will be displayed within the provider 90 | # The value can be any string. Use nil to disable this feature. When disabled, clients must provide a valid URL 91 | # (Similar behaviour: https://developers.google.com/accounts/docs/OAuth2InstalledApp#choosingredirecturi) 92 | # 93 | # native_redirect_uri 'urn:ietf:wg:oauth:2.0:oob' 94 | 95 | # Forces the usage of the HTTPS protocol in non-native redirect uris (enabled 96 | # by default in non-development environments). OAuth2 delegates security in 97 | # communication to the HTTPS protocol so it is wise to keep this enabled. 98 | # 99 | # Callable objects such as proc, lambda, block or any object that responds to 100 | # #call can be used in order to allow conditional checks (to allow non-SSL 101 | # redirects to localhost for example). 102 | # 103 | # force_ssl_in_redirect_uri !Rails.env.development? 104 | # 105 | # force_ssl_in_redirect_uri { |uri| uri.host != 'localhost' } 106 | 107 | # Specify what redirect URI's you want to block during creation. Any redirect 108 | # URI is whitelisted by default. 109 | # 110 | # You can use this option in order to forbid URI's with 'javascript' scheme 111 | # for example. 112 | # 113 | # forbid_redirect_uri { |uri| uri.scheme.to_s.downcase == 'javascript' } 114 | 115 | # Specify what grant flows are enabled in array of Strings. The valid 116 | # strings and the flows they enable are: 117 | # 118 | # "authorization_code" => Authorization Code Grant Flow 119 | # "implicit" => Implicit Grant Flow 120 | # "password" => Resource Owner Password Credentials Grant Flow 121 | # "client_credentials" => Client Credentials Grant Flow 122 | # 123 | # If not specified, Doorkeeper enables authorization_code and 124 | # client_credentials. 125 | # 126 | # implicit and password grant flows have risks that you should understand 127 | # before enabling: 128 | # http://tools.ietf.org/html/rfc6819#section-4.4.2 129 | # http://tools.ietf.org/html/rfc6819#section-4.4.3 130 | # 131 | # grant_flows %w[authorization_code client_credentials] 132 | 133 | # Hook into the strategies' request & response life-cycle in case your 134 | # application needs advanced customization or logging: 135 | # 136 | # before_successful_strategy_response do |request| 137 | # puts "BEFORE HOOK FIRED! #{request}" 138 | # end 139 | # 140 | # after_successful_strategy_response do |request, response| 141 | # puts "AFTER HOOK FIRED! #{request}, #{response}" 142 | # end 143 | 144 | # Under some circumstances you might want to have applications auto-approved, 145 | # so that the user skips the authorization step. 146 | # For example if dealing with a trusted application. 147 | # skip_authorization do |resource_owner, client| 148 | # client.superapp? or resource_owner.admin? 149 | # end 150 | 151 | # WWW-Authenticate Realm (default "Doorkeeper"). 152 | # realm "Doorkeeper" 153 | end 154 | --------------------------------------------------------------------------------