Skip to content
Snippets Groups Projects
Commit a9e40a3d authored by Eugen Rochko's avatar Eugen Rochko
Browse files

Adding OAuth access scopes, fixing OAuth authorization UI, adding rate limiting

to the API
parent 17122df8
No related branches found
No related tags found
No related merge requests found
Showing
with 172 additions and 86 deletions
Rails:
Enabled: true
Metrics/LineLength:
Enabled: false
Style/PerlBackrefs:
AutoCorrect: false
Style/ClassAndModuleChildren:
Enabled: false
Documentation:
Metrics/BlockNesting:
Max: 2
Metrics/LineLength:
AllowURI: true
Enabled: false
Metrics/MethodLength:
CountComments: false
Max: 10
Metrics/ModuleLength:
Max: 100
Metrics/ParameterLists:
Max: 4
CountKeywordArgs: true
Style/AccessModifierIndentation:
EnforcedStyle: indent
Style/CollectionMethods:
Enabled: true
PreferredMethods:
find_all: 'select'
Style/Documentation:
Enabled: false
Style/DoubleNegation:
Enabled: false
Style/FrozenStringLiteralComment:
Enabled: false
Style/SpaceInsideHashLiteralBraces:
EnforcedStyle: space
Style/TrailingCommaInLiteral:
EnforcedStyleForMultiline: 'comma'
Style/RegexpLiteral:
Enabled: false
AllCops:
TargetRubyVersion: 2.2
Exclude:
- 'spec/**/*'
- 'db/**/*'
- 'app/views/**/*'
- 'config/**/*'
......@@ -85,18 +85,7 @@
}
}
.prompt {
font-size: 16px;
color: #9baec8;
text-align: center;
.prompt-highlight {
font-weight: 500;
color: #fff;
}
}
code.copypasteable {
code {
display: block;
font-family: 'Roboto Mono', monospace;
font-weight: 400;
......@@ -110,42 +99,42 @@
.actions {
margin-top: 30px;
}
button {
display: block;
width: 100%;
border: 0;
border-radius: 4px;
background: #2b90d9;
color: #fff;
font-size: 18px;
padding: 10px;
text-transform: uppercase;
cursor: pointer;
font-weight: 500;
outline: 0;
margin-bottom: 10px;
button {
display: block;
width: 100%;
border: 0;
border-radius: 4px;
background: #2b90d9;
color: #fff;
font-size: 18px;
padding: 10px;
text-transform: uppercase;
cursor: pointer;
font-weight: 500;
outline: 0;
margin-bottom: 10px;
&:hover {
background-color: lighten(#2b90d9, 5%);
}
&:hover {
background-color: lighten(#2b90d9, 5%);
}
&:active, &:focus {
position: relative;
top: 1px;
background-color: darken(#2b90d9, 5%);
}
&:active, &:focus {
position: relative;
top: 1px;
background-color: darken(#2b90d9, 5%);
}
&.negative {
background: #df405a;
&.negative {
background: #df405a;
&:hover {
background-color: lighten(#df405a, 5%);
}
&:hover {
background-color: lighten(#df405a, 5%);
}
&:active, &:focus {
background-color: darken(#df405a, 5%);
}
&:active, &:focus {
background-color: darken(#df405a, 5%);
}
}
}
......@@ -180,3 +169,18 @@
}
}
.oauth-prompt {
margin-bottom: 30px;
text-align: center;
color: #9baec8;
h2 {
font-size: 16px;
margin-bottom: 30px;
}
strong {
color: #d9e1e8;
font-weight: 500;
}
}
# Be sure to restart your server when you modify this file. Action Cable runs in a loop that does not support auto reloading.
class PublicChannel < ApplicationCable::Channel
def subscribed
stream_from 'timeline:public', -> (encoded_message) do
stream_from 'timeline:public', lambda do |encoded_message|
message = ActiveSupport::JSON.decode(encoded_message)
status = Status.find_by(id: message['id'])
......
class Api::V1::AccountsController < ApiController
before_action :doorkeeper_authorize!
before_action -> { doorkeeper_authorize! :read }, except: [:follow, :unfollow, :block, :unblock]
before_action -> { doorkeeper_authorize! :follow }, only: [:follow, :unfollow, :block, :unblock]
before_action :set_account, except: [:verify_credentials, :suggestions]
respond_to :json
......
class Api::V1::FollowsController < ApiController
before_action :doorkeeper_authorize!
before_action -> { doorkeeper_authorize! :follow }
respond_to :json
def create
......
class Api::V1::MediaController < ApiController
before_action :doorkeeper_authorize!
before_action -> { doorkeeper_authorize! :write }
respond_to :json
def create
......
class Api::V1::StatusesController < ApiController
before_action :doorkeeper_authorize!
before_action -> { doorkeeper_authorize! :read }, except: [:create, :destroy, :reblog, :unreblog, :favourite, :unfavourite]
before_action -> { doorkeeper_authorize! :write }, only: [:create, :destroy, :reblog, :unreblog, :favourite, :unfavourite]
respond_to :json
def show
......
class ApiController < ApplicationController
protect_from_forgery with: :null_session
skip_before_action :verify_authenticity_token
before_action :set_rate_limit_headers
rescue_from ActiveRecord::RecordInvalid do |e|
render json: { error: e.to_s }, status: 422
end
......@@ -22,8 +25,27 @@ class ApiController < ApplicationController
render json: { error: 'Remote SSL certificate could not be verified' }, status: 503
end
def doorkeeper_unauthorized_render_options(*)
{ json: { error: 'Not authorized' } }
end
def doorkeeper_forbidden_render_options(*)
{ json: { error: 'This action is outside the authorized scopes' } }
end
protected
def set_rate_limit_headers
return if request.env['rack.attack.throttle_data'].nil?
now = Time.now.utc
match_data = request.env['rack.attack.throttle_data']['api']
response.headers['X-RateLimit-Limit'] = match_data[:limit].to_s
response.headers['X-RateLimit-Remaining'] = (match_data[:limit] - match_data[:count]).to_s
response.headers['X-RateLimit-Reset'] = (now + (match_data[:period] - now.to_i % match_data[:period])).to_s
end
def current_resource_owner
User.find(doorkeeper_token.resource_owner_id) if doorkeeper_token
end
......
......@@ -15,6 +15,6 @@ class HomeController < ApplicationController
end
def find_or_create_access_token
Doorkeeper::AccessToken.find_or_create_for(Doorkeeper::Application.where(superapp: true).first, current_user.id, nil, Doorkeeper.configuration.access_token_expires_in, Doorkeeper.configuration.refresh_token_enabled?)
Doorkeeper::AccessToken.find_or_create_for(Doorkeeper::Application.where(superapp: true).first, current_user.id, 'read write follow', Doorkeeper.configuration.access_token_expires_in, Doorkeeper.configuration.refresh_token_enabled?)
end
end
class Oauth::AuthorizationsController < Doorkeeper::AuthorizationsController
before_action :store_current_location
private
def store_current_location
store_location_for(:user, request.url)
end
end
......@@ -7,7 +7,7 @@ class Feed
def get(limit, max_id = nil, since_id = nil)
max_id = '+inf' if max_id.blank?
since_id = '-inf' if since_id.blank?
unhydrated = redis.zrevrangebyscore(key, "(#{max_id}", "(#{since_id}", limit: [0, limit], with_scores: true).collect(&:last).map(&:to_i)
unhydrated = redis.zrevrangebyscore(key, "(#{max_id}", "(#{since_id}", limit: [0, limit], with_scores: true).map(&:last).map(&:to_i)
# If we're after most recent items and none are there, we need to precompute the feed
if unhydrated.empty? && max_id == '+inf' && since_id == '-inf'
......
......@@ -34,7 +34,8 @@ class MediaAttachment < ApplicationRecord
image? ? 'image' : 'video'
end
private
private
def self.file_styles(f)
if f.instance.image?
{
......
.prompt= raw t('.prompt', client_name: "<strong class=\"prompt-highlight\">#{ @pre_auth.client.name }</strong>")
/- if @pre_auth.scopes.count > 0
/ .scope-permission-prompt
/ %p= t('.able_to')
/ %ul.scope-permissions
/ - @pre_auth.scopes.each do |scope|
/ %li= t scope, scope: [:doorkeeper, :scopes]
.actions
= form_tag oauth_authorization_path, method: :post do
= hidden_field_tag :client_id, @pre_auth.client.uid
= hidden_field_tag :redirect_uri, @pre_auth.redirect_uri
= hidden_field_tag :state, @pre_auth.state
= hidden_field_tag :response_type, @pre_auth.response_type
= hidden_field_tag :scope, @pre_auth.scope
= button_tag t('doorkeeper.authorizations.buttons.authorize'), type: :submit
= form_tag oauth_authorization_path, method: :delete do
= hidden_field_tag :client_id, @pre_auth.client.uid
= hidden_field_tag :redirect_uri, @pre_auth.redirect_uri
= hidden_field_tag :state, @pre_auth.state
= hidden_field_tag :response_type, @pre_auth.response_type
= hidden_field_tag :scope, @pre_auth.scope
= button_tag t('doorkeeper.authorizations.buttons.deny'), type: :submit, class: 'negative'
.prompt= t('.title')
%code.copypasteable= params[:code]
.prompt= t('doorkeeper.authorizations.error.title')
#error_explanation
.flash-message#error_explanation
= @pre_auth.error_response.body[:error_description]
.oauth-prompt
%h2
Application
%strong=@pre_auth.client.name
requests access to your account
%p
It will be able to
= @pre_auth.scopes.map { |scope| t(scope, scope: [:doorkeeper, :scopes]) }.map { |s| "<strong>#{s}</strong>"}.to_sentence.html_safe
= form_tag oauth_authorization_path, method: :post, class: 'simple_form' do
= hidden_field_tag :client_id, @pre_auth.client.uid
= hidden_field_tag :redirect_uri, @pre_auth.redirect_uri
= hidden_field_tag :state, @pre_auth.state
= hidden_field_tag :response_type, @pre_auth.response_type
= hidden_field_tag :scope, @pre_auth.scope
= button_tag t('doorkeeper.authorizations.buttons.authorize'), type: :submit
= form_tag oauth_authorization_path, method: :delete, class: 'simple_form' do
= hidden_field_tag :client_id, @pre_auth.client.uid
= hidden_field_tag :redirect_uri, @pre_auth.redirect_uri
= hidden_field_tag :state, @pre_auth.state
= hidden_field_tag :response_type, @pre_auth.response_type
= hidden_field_tag :scope, @pre_auth.scope
= button_tag t('doorkeeper.authorizations.buttons.deny'), type: :submit, class: 'negative'
%code= params[:code]
......@@ -12,7 +12,7 @@ Rails.application.configure do
# Full error reports are disabled and caching is turned on.
config.consider_all_requests_local = false
config.action_controller.perform_caching = false
config.action_controller.perform_caching = true
# Disable serving static files from the `/public` folder by default since
# Apache or NGINX already handles this.
......
......@@ -50,8 +50,8 @@ Doorkeeper.configure do
# 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, :follow
default_scopes :read
optional_scopes :write, :follow
# Change the way client credentials are retrieved from the request object.
# By default it retrieves first from the `HTTP_AUTHORIZATION` header, then
......
Rabl.configure do |config|
config.cache_all_output = true
config.cache_all_output = false
config.cache_sources = !!Rails.env.production?
config.include_json_root = false
config.view_paths = [Rails.root.join('app/views')]
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment