+
+
diff --git a/config/application.rb b/config/application.rb
index dddf4905b..3f23b0a94 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -27,5 +27,10 @@ module Mastodon
config.paths.add File.join('app', 'api'), glob: File.join('**', '*.rb')
config.autoload_paths += Dir[Rails.root.join('app', 'api', '*')]
+
+ config.to_prepare do
+ Doorkeeper::AuthorizationsController.layout 'auth'
+ Doorkeeper::AuthorizedApplicationsController.layout 'auth'
+ end
end
end
diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb
index 23352eac0..89747999a 100644
--- a/config/initializers/devise.rb
+++ b/config/initializers/devise.rb
@@ -61,7 +61,7 @@ Devise.setup do |config|
# given strategies, for example, `config.http_authenticatable = [:database]` will
# enable it only for database authentication. The supported strategies are:
# :database = Support basic authentication with authentication key + password
- # config.http_authenticatable = false
+ config.http_authenticatable = [:database]
# If 401 status code should be returned for AJAX requests. True by default.
# config.http_authenticatable_on_xhr = true
diff --git a/config/initializers/doorkeeper.rb b/config/initializers/doorkeeper.rb
new file mode 100644
index 000000000..10b9980b4
--- /dev/null
+++ b/config/initializers/doorkeeper.rb
@@ -0,0 +1,104 @@
+Doorkeeper.configure do
+ # Change the ORM that doorkeeper will use (needs plugins)
+ orm :active_record
+
+ # This block will be called to check whether the resource owner is authenticated or not.
+ resource_owner_authenticator do
+ current_user || redirect_to(new_user_session_url)
+ end
+
+ # If you want to restrict access to the web interface for adding oauth authorized applications, you need to declare the block below.
+ # admin_authenticator do
+ # # Put your admin authentication logic here.
+ # # Example implementation:
+ # Admin.find_by_id(session[:admin_id]) || redirect_to(new_admin_session_url)
+ # end
+
+ # Authorization Code expiration time (default 10 minutes).
+ # authorization_code_expires_in 10.minutes
+
+ # Access token expiration time (default 2 hours).
+ # If you want to disable expiration, set this to nil.
+ # access_token_expires_in 2.hours
+
+ # Assign a custom TTL for implicit grants.
+ # custom_access_token_expires_in do |oauth_client|
+ # oauth_client.application.additional_settings.implicit_oauth_expiration
+ # end
+
+ # Use a custom class for generating the access token.
+ # https://github.com/doorkeeper-gem/doorkeeper#custom-access-token-generator
+ # access_token_generator "::Doorkeeper::JWT"
+
+ # Reuse access token for the same resource owner within an application (disabled by default)
+ # Rationale: https://github.com/doorkeeper-gem/doorkeeper/issues/383
+ # reuse_access_token
+
+ # Issue access tokens with refresh token (disabled by default)
+ # use_refresh_token
+
+ # Provide support for an owner to be assigned to each registered application (disabled by default)
+ # Optional parameter :confirmation => true (default false) if you want to enforce ownership of
+ # a registered application
+ # Note: you must also run the rails g doorkeeper:application_owner generator to provide the necessary support
+ # enable_application_owner :confirmation => false
+
+ # Define access token scopes for your provider
+ # For more information go to
+ # https://github.com/doorkeeper-gem/doorkeeper/wiki/Using-Scopes
+ # default_scopes :public
+ # optional_scopes :write, :update
+
+ # Change the way client credentials are retrieved from the request object.
+ # By default it retrieves first from the `HTTP_AUTHORIZATION` header, then
+ # falls back to the `:client_id` and `:client_secret` params from the `params` object.
+ # Check out the wiki for more information on customization
+ # client_credentials :from_basic, :from_params
+
+ # Change the way access token is authenticated from the request object.
+ # By default it retrieves first from the `HTTP_AUTHORIZATION` header, then
+ # falls back to the `:access_token` or `:bearer_token` params from the `params` object.
+ # Check out the wiki for more information on customization
+ # access_token_methods :from_bearer_authorization, :from_access_token_param, :from_bearer_param
+
+ # Change the native redirect uri for client apps
+ # 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
+ # The value can be any string. Use nil to disable this feature. When disabled, clients must provide a valid URL
+ # (Similar behaviour: https://developers.google.com/accounts/docs/OAuth2InstalledApp#choosingredirecturi)
+ #
+ # native_redirect_uri 'urn:ietf:wg:oauth:2.0:oob'
+
+ # Forces the usage of the HTTPS protocol in non-native redirect uris (enabled
+ # by default in non-development environments). OAuth2 delegates security in
+ # communication to the HTTPS protocol so it is wise to keep this enabled.
+ #
+ # force_ssl_in_redirect_uri !Rails.env.development?
+
+ # Specify what grant flows are enabled in array of Strings. The valid
+ # strings and the flows they enable are:
+ #
+ # "authorization_code" => Authorization Code Grant Flow
+ # "implicit" => Implicit Grant Flow
+ # "password" => Resource Owner Password Credentials Grant Flow
+ # "client_credentials" => Client Credentials Grant Flow
+ #
+ # If not specified, Doorkeeper enables authorization_code and
+ # client_credentials.
+ #
+ # implicit and password grant flows have risks that you should understand
+ # before enabling:
+ # http://tools.ietf.org/html/rfc6819#section-4.4.2
+ # http://tools.ietf.org/html/rfc6819#section-4.4.3
+ #
+ # grant_flows %w(authorization_code client_credentials)
+
+ # Under some circumstances you might want to have applications auto-approved,
+ # so that the user skips the authorization step.
+ # For example if dealing with a trusted application.
+ # skip_authorization do |resource_owner, client|
+ # client.superapp? or resource_owner.admin?
+ # end
+
+ # WWW-Authenticate Realm (default "Doorkeeper").
+ # realm "Doorkeeper"
+end
diff --git a/config/initializers/rabl_init.rb b/config/initializers/rabl_init.rb
new file mode 100644
index 000000000..f11eb190d
--- /dev/null
+++ b/config/initializers/rabl_init.rb
@@ -0,0 +1,3 @@
+Rabl.configure do |config|
+ config.include_json_root = false
+end
diff --git a/config/initializers/reload_api.rb b/config/initializers/reload_api.rb
deleted file mode 100644
index c6b599d80..000000000
--- a/config/initializers/reload_api.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-if Rails.env.development?
- ActiveSupport::Dependencies.explicitly_unloadable_constants << 'Twitter::API'
-
- api_files = Dir[Rails.root.join('app', 'api', '**', '*.rb')]
-
- api_reloader = ActiveSupport::FileUpdateChecker.new(api_files) do
- Rails.application.reload_routes!
- end
-
- ActionDispatch::Callbacks.to_prepare do
- api_reloader.execute_if_updated
- end
-end
diff --git a/config/locales/doorkeeper.en.yml b/config/locales/doorkeeper.en.yml
new file mode 100644
index 000000000..7d2d215da
--- /dev/null
+++ b/config/locales/doorkeeper.en.yml
@@ -0,0 +1,123 @@
+en:
+ activerecord:
+ attributes:
+ doorkeeper/application:
+ name: 'Name'
+ redirect_uri: 'Redirect URI'
+ errors:
+ models:
+ doorkeeper/application:
+ attributes:
+ redirect_uri:
+ fragment_present: 'cannot contain a fragment.'
+ invalid_uri: 'must be a valid URI.'
+ relative_uri: 'must be an absolute URI.'
+ secured_uri: 'must be an HTTPS/SSL URI.'
+
+ doorkeeper:
+ applications:
+ confirmations:
+ destroy: 'Are you sure?'
+ buttons:
+ edit: 'Edit'
+ destroy: 'Destroy'
+ submit: 'Submit'
+ cancel: 'Cancel'
+ authorize: 'Authorize'
+ form:
+ error: 'Whoops! Check your form for possible errors'
+ help:
+ redirect_uri: 'Use one line per URI'
+ native_redirect_uri: 'Use %{native_redirect_uri} for local tests'
+ scopes: 'Separate scopes with spaces. Leave blank to use the default scopes.'
+ edit:
+ title: 'Edit application'
+ index:
+ title: 'Your applications'
+ new: 'New Application'
+ name: 'Name'
+ callback_url: 'Callback URL'
+ new:
+ title: 'New Application'
+ show:
+ title: 'Application: %{name}'
+ application_id: 'Application Id'
+ secret: 'Secret'
+ scopes: 'Scopes'
+ callback_urls: 'Callback urls'
+ actions: 'Actions'
+
+ authorizations:
+ buttons:
+ authorize: 'Authorize'
+ deny: 'Deny'
+ error:
+ title: 'An error has occurred'
+ new:
+ title: 'Authorization required'
+ prompt: 'Authorize %{client_name} to use your account?'
+ able_to: 'This application will be able to'
+ show:
+ title: 'Authorization code'
+
+ authorized_applications:
+ confirmations:
+ revoke: 'Are you sure?'
+ buttons:
+ revoke: 'Revoke'
+ index:
+ title: 'Your authorized applications'
+ application: 'Application'
+ created_at: 'Created At'
+ date_format: '%Y-%m-%d %H:%M:%S'
+
+ errors:
+ messages:
+ # Common error messages
+ invalid_request: 'The request is missing a required parameter, includes an unsupported parameter value, or is otherwise malformed.'
+ invalid_redirect_uri: 'The redirect uri included is not valid.'
+ unauthorized_client: 'The client is not authorized to perform this request using this method.'
+ access_denied: 'The resource owner or authorization server denied the request.'
+ invalid_scope: 'The requested scope is invalid, unknown, or malformed.'
+ server_error: 'The authorization server encountered an unexpected condition which prevented it from fulfilling the request.'
+ temporarily_unavailable: 'The authorization server is currently unable to handle the request due to a temporary overloading or maintenance of the server.'
+
+ #configuration error messages
+ credential_flow_not_configured: 'Resource Owner Password Credentials flow failed due to Doorkeeper.configure.resource_owner_from_credentials being unconfigured.'
+ resource_owner_authenticator_not_configured: 'Resource Owner find failed due to Doorkeeper.configure.resource_owner_authenticator being unconfiged.'
+
+ # Access grant errors
+ unsupported_response_type: 'The authorization server does not support this response type.'
+
+ # Access token errors
+ invalid_client: 'Client authentication failed due to unknown client, no client authentication included, or unsupported authentication method.'
+ 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.'
+ unsupported_grant_type: 'The authorization grant type is not supported by the authorization server.'
+
+ # Password Access token errors
+ invalid_resource_owner: 'The provided resource owner credentials are not valid, or resource owner cannot be found'
+
+ invalid_token:
+ revoked: "The access token was revoked"
+ expired: "The access token expired"
+ unknown: "The access token is invalid"
+
+ flash:
+ applications:
+ create:
+ notice: 'Application created.'
+ destroy:
+ notice: 'Application deleted.'
+ update:
+ notice: 'Application updated.'
+ authorized_applications:
+ destroy:
+ notice: 'Application revoked.'
+
+ layouts:
+ admin:
+ nav:
+ oauth2_provider: 'OAuth2 Provider'
+ applications: 'Applications'
+ application:
+ title: 'OAuth authorization required'
diff --git a/config/routes.rb b/config/routes.rb
index 3167cbdeb..b34837711 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -1,4 +1,6 @@
Rails.application.routes.draw do
+ use_doorkeeper
+
get '.well-known/host-meta', to: 'xrd#host_meta', as: :host_meta
get '.well-known/webfinger', to: 'xrd#webfinger', as: :webfinger
@@ -9,23 +11,36 @@ Rails.application.routes.draw do
}
resources :accounts, path: 'users', only: [:show], param: :username do
- member do
- post :follow
- post :unfollow
- end
-
- resources :stream_entries, path: 'updates', only: [:show] do
- member do
- post :reblog
- post :favourite
- end
- end
+ resources :stream_entries, path: 'updates', only: [:show]
end
namespace :api do
+ # PubSubHubbub
resources :subscriptions, only: [:show]
post '/subscriptions/:id', to: 'subscriptions#update'
+
+ # Salmon
post '/salmon/:id', to: 'salmon#update', as: :salmon
+
+ # JSON / REST API
+ resources :statuses, only: [:create, :show] do
+ member do
+ post :reblog
+ end
+ end
+
+ resources :follows, only: [:create]
+
+ resources :accounts, only: [:show] do
+ member do
+ get :statuses
+ get :followers
+ get :following
+
+ post :follow
+ post :unfollow
+ end
+ end
end
root 'home#index'
diff --git a/db/migrate/20160306172223_create_doorkeeper_tables.rb b/db/migrate/20160306172223_create_doorkeeper_tables.rb
new file mode 100644
index 000000000..e9da5f342
--- /dev/null
+++ b/db/migrate/20160306172223_create_doorkeeper_tables.rb
@@ -0,0 +1,50 @@
+class CreateDoorkeeperTables < ActiveRecord::Migration
+ def change
+ create_table :oauth_applications do |t|
+ t.string :name, null: false
+ t.string :uid, null: false
+ t.string :secret, null: false
+ t.text :redirect_uri, null: false
+ t.string :scopes, null: false, default: ''
+ t.timestamps
+ end
+
+ add_index :oauth_applications, :uid, unique: true
+
+ create_table :oauth_access_grants do |t|
+ t.integer :user_id, null: false
+ t.integer :application_id, null: false
+ t.string :token, null: false
+ t.integer :expires_in, null: false
+ t.text :redirect_uri, null: false
+ t.datetime :created_at, null: false
+ t.datetime :revoked_at
+ t.string :scopes
+ end
+
+ add_index :oauth_access_grants, :token, unique: true
+
+ create_table :oauth_access_tokens do |t|
+ t.integer :resource_owner_id
+ t.integer :application_id
+
+ # If you use a custom token generator you may need to change this column
+ # from string to text, so that it accepts tokens larger than 255
+ # characters. More info on custom token generators in:
+ # https://github.com/doorkeeper-gem/doorkeeper/tree/v3.0.0.rc1#custom-access-token-generator
+ #
+ # t.text :token, null: false
+ t.string :token, null: false
+
+ t.string :refresh_token
+ t.integer :expires_in
+ t.datetime :revoked_at
+ t.datetime :created_at, null: false
+ t.string :scopes
+ end
+
+ add_index :oauth_access_tokens, :token, unique: true
+ add_index :oauth_access_tokens, :resource_owner_id
+ add_index :oauth_access_tokens, :refresh_token, unique: true
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 0b988073c..a63c6f399 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20160305115639) do
+ActiveRecord::Schema.define(version: 20160306172223) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -67,6 +67,46 @@ ActiveRecord::Schema.define(version: 20160305115639) do
add_index "mentions", ["account_id", "status_id"], name: "index_mentions_on_account_id_and_status_id", unique: true, using: :btree
+ create_table "oauth_access_grants", force: :cascade do |t|
+ t.integer "user_id", null: false
+ t.integer "application_id", null: false
+ t.string "token", null: false
+ t.integer "expires_in", null: false
+ t.text "redirect_uri", null: false
+ t.datetime "created_at", null: false
+ t.datetime "revoked_at"
+ t.string "scopes"
+ end
+
+ add_index "oauth_access_grants", ["token"], name: "index_oauth_access_grants_on_token", unique: true, using: :btree
+
+ create_table "oauth_access_tokens", force: :cascade do |t|
+ t.integer "resource_owner_id"
+ t.integer "application_id"
+ t.string "token", null: false
+ t.string "refresh_token"
+ t.integer "expires_in"
+ t.datetime "revoked_at"
+ t.datetime "created_at", null: false
+ t.string "scopes"
+ end
+
+ add_index "oauth_access_tokens", ["refresh_token"], name: "index_oauth_access_tokens_on_refresh_token", unique: true, using: :btree
+ add_index "oauth_access_tokens", ["resource_owner_id"], name: "index_oauth_access_tokens_on_resource_owner_id", using: :btree
+ add_index "oauth_access_tokens", ["token"], name: "index_oauth_access_tokens_on_token", unique: true, using: :btree
+
+ create_table "oauth_applications", force: :cascade do |t|
+ t.string "name", null: false
+ t.string "uid", null: false
+ t.string "secret", null: false
+ t.text "redirect_uri", null: false
+ t.string "scopes", default: "", null: false
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ end
+
+ add_index "oauth_applications", ["uid"], name: "index_oauth_applications_on_uid", unique: true, using: :btree
+
create_table "statuses", force: :cascade do |t|
t.string "uri"
t.integer "account_id", null: false
diff --git a/spec/controllers/api/accounts_controller_spec.rb b/spec/controllers/api/accounts_controller_spec.rb
new file mode 100644
index 000000000..b409611ce
--- /dev/null
+++ b/spec/controllers/api/accounts_controller_spec.rb
@@ -0,0 +1,5 @@
+require 'rails_helper'
+
+RSpec.describe Api::AccountsController, type: :controller do
+
+end
diff --git a/spec/controllers/api/follows_controller_spec.rb b/spec/controllers/api/follows_controller_spec.rb
new file mode 100644
index 000000000..ba3f046dd
--- /dev/null
+++ b/spec/controllers/api/follows_controller_spec.rb
@@ -0,0 +1,5 @@
+require 'rails_helper'
+
+RSpec.describe Api::FollowsController, type: :controller do
+
+end
diff --git a/spec/controllers/api/statuses_controller_spec.rb b/spec/controllers/api/statuses_controller_spec.rb
new file mode 100644
index 000000000..bf84857bd
--- /dev/null
+++ b/spec/controllers/api/statuses_controller_spec.rb
@@ -0,0 +1,5 @@
+require 'rails_helper'
+
+RSpec.describe Api::StatusesController, type: :controller do
+
+end
diff --git a/spec/helpers/api/accounts_helper_spec.rb b/spec/helpers/api/accounts_helper_spec.rb
new file mode 100644
index 000000000..6305743e8
--- /dev/null
+++ b/spec/helpers/api/accounts_helper_spec.rb
@@ -0,0 +1,15 @@
+require 'rails_helper'
+
+# Specs in this file have access to a helper object that includes
+# the Api::AccountsHelper. For example:
+#
+# describe Api::AccountsHelper do
+# describe "string concat" do
+# it "concats two strings with spaces" do
+# expect(helper.concat_strings("this","that")).to eq("this that")
+# end
+# end
+# end
+RSpec.describe Api::AccountsHelper, type: :helper do
+ pending "add some examples to (or delete) #{__FILE__}"
+end
diff --git a/spec/helpers/api/follows_helper_spec.rb b/spec/helpers/api/follows_helper_spec.rb
new file mode 100644
index 000000000..43a86bbaa
--- /dev/null
+++ b/spec/helpers/api/follows_helper_spec.rb
@@ -0,0 +1,15 @@
+require 'rails_helper'
+
+# Specs in this file have access to a helper object that includes
+# the Api::FollowsHelper. For example:
+#
+# describe Api::FollowsHelper do
+# describe "string concat" do
+# it "concats two strings with spaces" do
+# expect(helper.concat_strings("this","that")).to eq("this that")
+# end
+# end
+# end
+RSpec.describe Api::FollowsHelper, type: :helper do
+ pending "add some examples to (or delete) #{__FILE__}"
+end
diff --git a/spec/helpers/api/statuses_helper_spec.rb b/spec/helpers/api/statuses_helper_spec.rb
new file mode 100644
index 000000000..ae5e76fb8
--- /dev/null
+++ b/spec/helpers/api/statuses_helper_spec.rb
@@ -0,0 +1,15 @@
+require 'rails_helper'
+
+# Specs in this file have access to a helper object that includes
+# the Api::StatusesHelper. For example:
+#
+# describe Api::StatusesHelper do
+# describe "string concat" do
+# it "concats two strings with spaces" do
+# expect(helper.concat_strings("this","that")).to eq("this that")
+# end
+# end
+# end
+RSpec.describe Api::StatusesHelper, type: :helper do
+ pending "add some examples to (or delete) #{__FILE__}"
+end