/
app.rb
369 lines (295 loc) · 9.88 KB
/
app.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
puts RUBY_VERSION
puts ENV['RACK']
here = File.expand_path File.dirname(__FILE__)
require "#{here}/init"
require 'sinatra'
if ENV['RACK_ENV'] == 'development'
require 'wrong'
include Wrong::D
else
def d msg=nil
puts "#{caller.first}: #{yield.inspect}"
end
end
puts "requiring"
require_in("lib")
require_in("web")
# monkey patch for better oauth errors
load File.expand_path( "#{here}/monkey/consumer.rb")
class Sharebro < Sinatra::Application
include Erector::Mixin
include Say
session_domain = begin case ENV['RACK_ENV']
when 'production'
"sharebro.org"
else
"localhost"
end
end
enable :show_exceptions # until we get a better exception reporting mechanism
enable :method_override # POST _method=delete => DELETE
enable :sessions
# http://stackoverflow.com/questions/6115136/in-a-sinatra-app-on-heroku-session-is-not-shared-across-dynos
set :session_secret, ENV['SESSION_SECRET'] || 'tetrafluoride'
# for some reason Rack::Session::Cookie doesn't work. Sinatra uses Rack::Session::Abstract::SessionHash -- probably monkey patches it or uses it in a weird way
# use Rack::Session::Cookie, :key => 'sharebro.rack.session',
# :domain => session_domain,
# :path => '/',
# :expire_after => 2592000,
# :secret => 'tetrafluoride'
def initialize
super
@here = File.expand_path(File.dirname(__FILE__))
end
attr_reader :here
def app_host
case ENV['RACK_ENV']
when 'production'
"sharebro.org"
else
"localhost:9292"
end
end
get '/favicon.ico' do
send_file "#{here}/favicon.ico"
end
get '/sendto-icon.ico' do
send_file "#{here}/favicon.ico"
# send_file "#{here}/img/sharebro-logo.png"
end
# google oauth verification file
get '/google66d87a0b5d48cf21.html' do
send_file "#{here}/google66d87a0b5d48cf21.html"
end
get "/" do
app_page(Home).to_html
end
# build plain-widget pages
[Links, Features, RoadMap, Vision].each do |widget|
get "/#{widget.name.downcase}" do
app_page(widget).to_html
end
end
## auth needed from here on
before do
puts "in before"
# clean up old cookies
[:access_token, :authenticated_id].each do |name|
if session[name]
session.delete(name)
end
end
# proper way: store account id in session
if session[:current_account_id]
current_account_id = session[:current_account_id]
@current_account = Accounts.get current_account_id
if @current_account.nil?
# can't find the account, so clean the session
session.delete(:current_account_id)
end
end
end
def signed_in?
@current_account
end
# only call current_account if you need it, cause it'll redirect if there is none
# otherwise call signed_in? to check
def current_account
@current_account || (puts "no current account; redirecting"; redirect "/auth_needed?back=#{back_pack}")
end
def login_status
if signed_in?
LoginStatus::Authenticated.new(google_data: google_data)
else
LoginStatus::Unauthenticated
end
end
def google_api
@google_api ||= begin
if (access_token_data = current_account["google"]["accessToken"])
GoogleApi.new(access_token_data)
else
# no api access token, so authorize
authorize
end
rescue OAuth::Unauthorized => e
# uh-oh, bad token, so we're not authorized after all
# in the future, we will separate authentication (login) from authorization (oauth) but for now,
# we just have to do the oauth tango again, so we'll mark the current account as having no
# token and redirect to oauth
say "deleting bad access token"
@current_account['google'].delete('accessToken')
Accounts.put(@current_account)
authorize
end
end
def access_token
google_api.access_token
end
def fetch_json api_path
google_api.fetch_json(api_path)
end
def google_data
@google_data ||= GoogleData.new(google_api)
end
def app_page main
AppPage.new(main: main, login_status: login_status)
#, message: "We are currently experimenting with authorization. If things don't work right, please try again soon.")
end
def lipsumar_feeds
Lipsumar.new(google_data).lipsumar_feeds
end
get '/about' do
app_page(About).to_html
end
get '/home2' do
params = {current_account: signed_in? ? current_account : nil}
app_page(Home2.new(params)).to_html
end
get '/sharebros' do
app_page(Sharebros.new(:google_data => google_data, :lipsumar_feeds => lipsumar_feeds)).to_html
end
# todo: proper widget-based message page
def message_page title, msg_html
<<-HTML
<html>
<title>sharebro.org - #{title}</title>
<body>
<h1><a href="/">sharebro.org</a> - #{title}</h1>
<div style="border: 3px solid green; padding: 2em; max-width: 30em; margin: auto;">
#{msg_html}
</div>
</body></html>
HTML
end
get "/auth_needed" do
message_page "authorization needed", <<-HTML
The action you just attempted requires authorization from google.
<p style='font-size: 18pt; background: #f0fff0; text-align: center;'>
<a href="/sign_in?back=#{params[:back]}"><b>Click here</b> to start the OAuth Tango.</a>
</p>
<p>
You will need to sign in to your Google account and then click "Grant Access". This allows us to fetch your user info and friends list so we can revive your sharebros. It does not give us access to any other Google info like your password or Gmail account.
</p>
<p>
You can revoke access at any time at Google's site (under 'My Account') but we will preserve your data so you can use it later.
HTML
end
# force an authorization
get "/sign_in" do
back_to = if params[:back]
back_unpack # kind of lame that we have to unpack then let the authorizer repack
else
"/"
end
authorize(back_to)
end
get "/sign_out" do
unauth
redirect "/"
end
def unauth
session.delete(:access_token)
session.delete(:current_account_id)
end
# base64 encode
def back_pack path = nil
path ||= request.fullpath # set it here so a client can pass "nil" to mean "you figure it out"
([path].pack("m").gsub("\n", '')).tap{|s| say "packed #{path} into #{s}"}
end
# base64 decode
def back_unpack path = params[:back]
path.unpack("m").first.tap{|u| say "unpacked #{path} into #{u}"}
end
def create_authorizer(options = {})
Authorizer.new({:callback_url => "#{request.base_url}/oauth_callback?back=#{back_pack options[:back]}"} << options )
end
def authorize back = nil
session.delete(:request_token)
authorizer = create_authorizer :back => back
session[:request_token] = authorizer.request_token #.token
puts "redirecting to #{authorizer.authorize_url}"
redirect authorizer.authorize_url
end
get "/oauth_callback" do
puts "in oauth_callback -- back=#{params[back].inspect}"
authorizer = create_authorizer :request_token => session[:request_token]
session.delete(:request_token)
access_token = authorizer.access_token(
oauth_verifier: params[:oauth_verifier],
oauth_token: params[:oauth_token],
)
@google_api = GoogleApi.new(access_token)
d("in oauth_callback"){@google_api}
@current_account = Accounts.write(google_data.user_id, access_token)
d("in oauth_callback"){@current_account}
session[:current_account_id] = @current_account["_id"]
redirect params[:back] ? (back_unpack params[:back]) : "/sharebros"
end
get "/googled" do
redirect '/sandbox'
end
get "/sandbox" do
path = params[:path]
data = if path && !path.empty?
fetch_json(path)
end
app_page(Sandbox.new(path: path, data: data)).to_html
end
post "/subscribe_you" do
redirect "/subscribe?user_ids=#{google_data.user_id}"
end
post "/subscribe" do
user_ids = params[:user_ids] && params[:user_ids].split(',')
Ant.request(:object, :class => "Subscribe", :account_id => current_account["_id"], :user_ids => user_ids)
app_page(Subscribed).to_html
end
# admin only
ALEX_GOOGLE_USER_ID = "15504357426492542506"
def admin?
current_account['google']['userId'] == ALEX_GOOGLE_USER_ID
end
get "/admin" do
redirect '/' unless admin?
app_page(Admin).to_html
end
get '/send_to' do
item = params.pluck("title", "url", "source")
cmd = SendTo.new(google_api, params["url"])
result = cmd.perform
case result
when :needs_auth
authorize("/send_to?.....")
when :error, :not_found
return app_page(Raw.new(
:title => result.to_s,
:data => {:params => params}.merge(cmd.info))).to_html
when :not_found
return message_page("Not Shared", "Couldn't find '#{params['title']}' from #{params['source']}")
when :ok
return message_page("Shared", "Shared '<a href='#{params['url']}'>#{params['title']}</a>' from feed '#{params['source']}'")
else
return app_page(Raw.new(
:title => "unknown result #{result}",
:data => {:params => params}.merge(cmd.info)
)).to_html
end
end
# see http://www.google.com/reader/settings?display=edit-extras , click "Send To"
post '/add_send_to_link' do
cmd = AddSendToLink.new(google_api, current_account)
result = cmd.perform
if result == :ok
message_page("added Send To Sharebro link", "added 'Send To' link -- go look for it in Google Reader")
else
app_page(Raw.new(:title => "error adding Send To link", :data => {:params => params}.merge(cmd.info))).to_html
end
end
delete '/send_to' do
message_page("not implemented", "sorry, but to remove the Send To Sharebro link, use the Google Reader Settings")
end
get '/env' do
redirect '/' unless admin?
app_page(Raw.new(:data => ENV.to_hash)).to_html
end
end