redd 0.8.5 → 0.8.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +42 -8
- data/lib/redd/api_client.rb +1 -1
- data/lib/redd/error.rb +6 -0
- data/lib/redd/middleware.rb +123 -0
- data/lib/redd/models/access.rb +19 -2
- data/lib/redd/utilities/error_handler.rb +1 -0
- data/lib/redd/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9143e5925475c5e0143ac3aacb6c10666986c18f
|
4
|
+
data.tar.gz: 6c7be0b33836c21aa36765937795cabe6c7aa8e6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 541cffb6f3ad7b15b2d1560b30ae400d7d9db898290c7a252ee9ad2c7bae3d1c8aa9591d202e9f32190f28e0fe37539b33443a0d8f5adccc0c3af8aa84f5def4
|
7
|
+
data.tar.gz: 707d66c874d7c74fbbe1710d52247f5dbc4e5c5e88fc4ab66b7c739075a30b4cfeb28d59965d762779b61d3bd9ca323432b6e543316f24237f916b694612d12c
|
data/README.md
CHANGED
@@ -1,8 +1,7 @@
|
|
1
1
|
<div align="center">
|
2
2
|
<p>
|
3
3
|
<!-- Redd -->
|
4
|
-
<img src="logo.png" width="500"><br>
|
5
|
-
|
4
|
+
<img src="https://raw.githubusercontent.com/avinashbot/redd/master/logo.png" width="500"><br>
|
6
5
|
<!-- Badges -->
|
7
6
|
<a href="https://rubygems.org/gems/redd">
|
8
7
|
<img src="http://img.shields.io/gem/v/redd.svg?style=flat-square" alt="Gem Version">
|
@@ -14,7 +13,6 @@
|
|
14
13
|
<img src="http://img.shields.io/gem/dt/redd.svg?style=flat-square" alt="Gem Downloads">
|
15
14
|
</a>
|
16
15
|
</p>
|
17
|
-
|
18
16
|
<!-- Intro Text -->
|
19
17
|
<p>
|
20
18
|
<strong>Redd</strong> is a <strong>batteries-included</strong>
|
@@ -46,31 +44,67 @@ session = Redd.it(
|
|
46
44
|
|
47
45
|
session.subreddit('all').comment_stream do |comment|
|
48
46
|
if comment.body.include?('roll a dice')
|
49
|
-
comment.reply("
|
47
|
+
comment.reply("It's a #{rand(1..6)}!")
|
50
48
|
elsif comment.body.include?('flip a coin')
|
51
|
-
comment.reply("
|
49
|
+
comment.reply("It's a #{%w(heads tails).sample}!")
|
50
|
+
end
|
51
|
+
end
|
52
|
+
```
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
require 'sinatra'
|
56
|
+
require 'redd/middleware'
|
57
|
+
|
58
|
+
use Rack::Session::Cookie
|
59
|
+
use Redd::Middleware,
|
60
|
+
user_agent: 'Redd:Username App:v1.0.0 (by /u/Mustermind)',
|
61
|
+
client_id: 'PQgS0UaX9l70oQ',
|
62
|
+
secret: 'PsF_kVZrW8nSVCG5kNsIgl-AaXE',
|
63
|
+
redirect_uri: 'http://localhost:4567/auth/reddit/callback',
|
64
|
+
scope: %w(identity),
|
65
|
+
via: '/auth/reddit'
|
66
|
+
|
67
|
+
get '/' do
|
68
|
+
reddit = request.env['redd.session']
|
69
|
+
|
70
|
+
if reddit
|
71
|
+
"Hello /u/#{reddit.me.name}! <a href='/logout'>Logout</a>"
|
72
|
+
else
|
73
|
+
"<a href='/auth/reddit'>Sign in with reddit</a>"
|
52
74
|
end
|
53
75
|
end
|
76
|
+
|
77
|
+
get '/auth/reddit/callback' do
|
78
|
+
redirect to('/') unless request.env['redd.error']
|
79
|
+
"Error: #{request.env['redd.error'].message} (<a href='/'>Back</a>)"
|
80
|
+
end
|
81
|
+
|
82
|
+
get '/logout' do
|
83
|
+
request.env['redd.session'] = nil
|
84
|
+
redirect to('/')
|
85
|
+
end
|
54
86
|
```
|
55
87
|
|
56
88
|
### FAQ
|
57
89
|
|
58
|
-
####
|
90
|
+
#### Are those examples fully functional?
|
59
91
|
**Yes**, that's all there is to it! You don't need to handle rate-limiting, refresh access tokens or protect against issues on reddit's end (like 5xx errors).
|
60
92
|
|
61
93
|
#### Where can I find the documentation?
|
62
94
|
|
63
95
|
[**Gem**](http://www.rubydoc.info/gems/redd/Redd/Models/Session) / [**GitHub**](http://www.rubydoc.info/github/avinashbot/redd/master/Redd/Models/Session)
|
64
96
|
|
97
|
+
#### Where can I ask for help if I'm having issues?
|
98
|
+
Check out the [**official subreddit**](https://www.reddit.com/r/Redd) or raise a [**GitHub issue**](https://github.com/avinashbot/redd/issues/new).
|
99
|
+
|
65
100
|
#### How do I request a feature / contribute?
|
66
101
|
|
67
|
-
- The quickest way to get a feature into Redd is to raise a GitHub issue.
|
102
|
+
- The quickest way to get a feature into Redd is to raise a [**GitHub issue**](https://github.com/avinashbot/redd/issues/new).
|
68
103
|
- Pull requests are also appreciated!
|
69
104
|
- Don't hesitate! There are no stupid questions!
|
70
105
|
|
71
106
|
#### How can I contact you?
|
72
107
|
[Reddit](https://www.reddit.com/message/compose/?to=Mustermind) /
|
73
|
-
[GitHub](https://github.com/avinashbot/redd/issues/new) /
|
74
108
|
[Email](mailto:avinash@dwarapu.me)
|
75
109
|
|
76
110
|
---
|
data/lib/redd/api_client.rb
CHANGED
@@ -87,7 +87,7 @@ module Redd
|
|
87
87
|
# If access is nil, panic
|
88
88
|
raise 'client access is nil, try calling #authenticate' if @access.nil?
|
89
89
|
# Refresh access if auto_refresh is enabled
|
90
|
-
refresh if @access.
|
90
|
+
refresh if @auto_refresh && @access.permanent? && @access.expired?
|
91
91
|
end
|
92
92
|
|
93
93
|
def handle_retryable_errors
|
data/lib/redd/error.rb
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Redd
|
4
|
+
# An error raised by {Redd::Middleware} when there was an error returned by reddit.
|
5
|
+
class TokenRetrievalError < StandardError; end
|
6
|
+
|
4
7
|
# An error with the API.
|
5
8
|
class APIError < StandardError
|
6
9
|
attr_reader :response, :name
|
@@ -42,6 +45,9 @@ module Redd
|
|
42
45
|
# Returned when reddit raises a 404 error.
|
43
46
|
class NotFound < ResponseError; end
|
44
47
|
|
48
|
+
# Too many requests and not enough rate limiting.
|
49
|
+
class TooManyRequests < ResponseError; end
|
50
|
+
|
45
51
|
# An unknown error on reddit's end. Usually fixed with a retry.
|
46
52
|
class ServerError < ResponseError; end
|
47
53
|
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rack'
|
4
|
+
require 'securerandom'
|
5
|
+
|
6
|
+
require_relative '../redd'
|
7
|
+
|
8
|
+
module Redd
|
9
|
+
# Rack middleware.
|
10
|
+
class Middleware
|
11
|
+
# @param opts [Hash] the options to create the object with
|
12
|
+
# @option opts [String] :user_agent your app's *unique* and *descriptive* user agent
|
13
|
+
# @option opts [String] :client_id the client id of your app
|
14
|
+
# @option opts [String] :redirect_uri the provided redirect URI
|
15
|
+
# @option opts [String] :secret ('') the app secret (for the web type)
|
16
|
+
# @option opts [Array<String>] :scope (['identity']) a list of scopes to request
|
17
|
+
# @option opts ['temporary', 'permanent'] :duration ('permanent') the duration to request the
|
18
|
+
# code for.
|
19
|
+
# @option opts [Boolean] :auto_refresh (true) allow refreshing a permanent access automatically
|
20
|
+
# (only if duration is 'permanent')
|
21
|
+
# @option opts [String] :via ('/auth/reddit') the relative path in the application that
|
22
|
+
# redirects a user to reddit
|
23
|
+
def initialize(app, opts = {})
|
24
|
+
@app = app
|
25
|
+
strategy_opts = opts.select { |k| %i(user_agent client_id secret redirect_uri).include?(k) }
|
26
|
+
@strategy = Redd::AuthStrategies::Web.new(strategy_opts)
|
27
|
+
|
28
|
+
@user_agent = opts.fetch(:user_agent, "Redd:Web Application:v#{Redd::VERSION} (by unknown)")
|
29
|
+
@client_id = opts.fetch(:client_id)
|
30
|
+
@redirect_uri = opts.fetch(:redirect_uri)
|
31
|
+
@scope = opts.fetch(:scope, ['identity'])
|
32
|
+
@duration = opts.fetch(:duration, 'permanent')
|
33
|
+
@auto_refresh = opts.fetch(:auto_refresh, true) && @duration == 'permanent'
|
34
|
+
@via = opts.fetch(:via, '/auth/reddit')
|
35
|
+
end
|
36
|
+
|
37
|
+
def call(env)
|
38
|
+
# This is done for thread safety so that each thread has its own copy
|
39
|
+
# of the middleware logic.
|
40
|
+
dup._call(env)
|
41
|
+
end
|
42
|
+
|
43
|
+
protected
|
44
|
+
|
45
|
+
def _call(env)
|
46
|
+
@request = Rack::Request.new(env)
|
47
|
+
return redirect_to_reddit! if @request.path == @via
|
48
|
+
|
49
|
+
before_call
|
50
|
+
response = @app.call(env)
|
51
|
+
after_call
|
52
|
+
response
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
# Creates a unique state and redirects the user to reddit for authentication.
|
58
|
+
def redirect_to_reddit!
|
59
|
+
state = SecureRandom.urlsafe_base64
|
60
|
+
url = Redd.url(
|
61
|
+
client_id: @client_id,
|
62
|
+
redirect_uri: @redirect_uri,
|
63
|
+
scope: @scope,
|
64
|
+
duration: @duration,
|
65
|
+
state: state
|
66
|
+
)
|
67
|
+
@request.session[:redd_state] = state
|
68
|
+
[302, { 'Location' => url }, []]
|
69
|
+
end
|
70
|
+
|
71
|
+
# Do any setup before calling the rest of the application.
|
72
|
+
def before_call
|
73
|
+
# Convert the code to an access token if returning from authentication.
|
74
|
+
create_session! if @request.base_url + @request.path == @redirect_uri
|
75
|
+
# Clear the state for any other request.
|
76
|
+
@request.session.delete(:redd_state)
|
77
|
+
# Load a Session model from the access token in the user's cookies.
|
78
|
+
@request.env['redd.session'] = (@request.session[:redd_session] ? parse_session : nil)
|
79
|
+
end
|
80
|
+
|
81
|
+
# Do any cleanup or changes after calling the application.
|
82
|
+
def after_call
|
83
|
+
env_session = @request.env['redd.session']
|
84
|
+
if env_session && env_session.client.access
|
85
|
+
# Make sure to flush any changes made to the Session client to the browser.
|
86
|
+
@request.session[:redd_session] = env_session.client.access.to_h
|
87
|
+
else
|
88
|
+
# Clear the session if the app explicitly set 'redd.session' to nil.
|
89
|
+
@request.session.delete(:redd_session)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# Assigns a single string representing a reddit authentication errors.
|
94
|
+
def handle_token_error
|
95
|
+
message = nil
|
96
|
+
message = 'invalid_state' if @request.GET['state'] != @request.session[:redd_state]
|
97
|
+
message = @request.GET['error'] if @request.GET['error']
|
98
|
+
raise Redd::TokenRetrievalError, message if message
|
99
|
+
end
|
100
|
+
|
101
|
+
# Store the access token and other details in the user's browser, assigning any errors to
|
102
|
+
# the 'redd.error' env variable.
|
103
|
+
def create_session!
|
104
|
+
# Skip authorizing if there was an error from the authorization.
|
105
|
+
handle_token_error
|
106
|
+
# Try to get a code (the rescue block will also prevent crazy crashes)
|
107
|
+
access = @strategy.authenticate(@request.GET['code'])
|
108
|
+
@request.session[:redd_session] = access.to_h
|
109
|
+
rescue Redd::TokenRetrievalError, Redd::ResponseError => error
|
110
|
+
@request.env['redd.error'] = error
|
111
|
+
end
|
112
|
+
|
113
|
+
# Return a {Redd::Models::Session} based on the hash saved into the browser's session.
|
114
|
+
def parse_session
|
115
|
+
client = Redd::APIClient.new(
|
116
|
+
@strategy,
|
117
|
+
user_agent: @user_agent, limit_time: 0, auto_refresh: @auto_refresh
|
118
|
+
)
|
119
|
+
client.access = Redd::Models::Access.new(@strategy, @request.session[:redd_session])
|
120
|
+
Redd::Models::Session.new(client)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
data/lib/redd/models/access.rb
CHANGED
@@ -5,10 +5,27 @@ require_relative 'basic_model'
|
|
5
5
|
module Redd
|
6
6
|
module Models
|
7
7
|
# Models access_token and related keys.
|
8
|
+
# @note This model also supports an additional key, called `:created_at` which is a UNIX time
|
9
|
+
# representing the time the access was created. The default value is the time the object was
|
10
|
+
# initialized.
|
8
11
|
class Access < BasicModel
|
12
|
+
# Create a non-lazily initialized Access.
|
13
|
+
# @param client [Object] (deprecated) the client to create the object with
|
14
|
+
# @param attributes [Hash] the Access's attributes
|
15
|
+
# @example
|
16
|
+
# access = Redd::Models::Access.new(access_token: ...)
|
17
|
+
def initialize(client = nil, attributes = {})
|
18
|
+
if client.is_a?(Hash)
|
19
|
+
super(nil, client)
|
20
|
+
else
|
21
|
+
super(client, attributes)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
9
25
|
def expired?(grace_period = 60)
|
26
|
+
# We're not sure, so we just assume it hasn't expired.
|
10
27
|
return false unless @attributes[:expires_in]
|
11
|
-
Time.now > @created_at + (@attributes[:expires_in] - grace_period)
|
28
|
+
Time.now.to_i > @attributes[:created_at] + (@attributes[:expires_in] - grace_period)
|
12
29
|
end
|
13
30
|
|
14
31
|
def permanent?
|
@@ -18,7 +35,7 @@ module Redd
|
|
18
35
|
private
|
19
36
|
|
20
37
|
def after_initialize
|
21
|
-
@created_at
|
38
|
+
@attributes[:created_at] ||= Time.now.to_i
|
22
39
|
end
|
23
40
|
end
|
24
41
|
end
|
data/lib/redd/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: redd
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.8.
|
4
|
+
version: 0.8.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Avinash Dwarapu
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-03-
|
11
|
+
date: 2017-03-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: http
|
@@ -162,6 +162,7 @@ files:
|
|
162
162
|
- lib/redd/auth_strategies/web.rb
|
163
163
|
- lib/redd/client.rb
|
164
164
|
- lib/redd/error.rb
|
165
|
+
- lib/redd/middleware.rb
|
165
166
|
- lib/redd/models/access.rb
|
166
167
|
- lib/redd/models/basic_model.rb
|
167
168
|
- lib/redd/models/comment.rb
|