sinatra-portier 1.4.0 → 2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/README.md +7 -1
- data/example/app.rb +23 -22
- data/example/config.ru +8 -2
- data/lib/sinatra/browserid/helpers.rb +30 -3
- data/lib/sinatra/browserid.rb +40 -14
- metadata +63 -9
- data/example/views/index.erb +0 -21
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 7cd3476f061fb1de32d057c4f8a6161fb62e0321f47f0c6d264a31d3c16755f7
|
4
|
+
data.tar.gz: 1773f716e2a04c6e2b6de9c3fe221b4352004a3b1de09afb763846ff4ea34d17
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 974c8c6c0464f36a73d93bde35a29545874da0ae0c1832d5cac4f0588e4e774fcaf365201e403936a1138c51c39efd07ebd14fb4fd857a72348d3fbf19779ba3
|
7
|
+
data.tar.gz: 4b305bc259bad7d833a74d3d27dfbc8922b83a5bb2b6dce48b3077f433b89de7d2635f5506e8c084647fef60b29b8b1973e1329e3390959af80565053b819600
|
data/README.md
CHANGED
@@ -53,7 +53,13 @@ end
|
|
53
53
|
```
|
54
54
|
|
55
55
|
See the rdoc for more details on the helper functions. For a functioning
|
56
|
-
example app,
|
56
|
+
example app, start the app in the example directory:
|
57
|
+
|
58
|
+
```
|
59
|
+
bundle install
|
60
|
+
bundle exec rackup -p PORT
|
61
|
+
|
62
|
+
```
|
57
63
|
|
58
64
|
Available sinatra settings:
|
59
65
|
|
data/example/app.rb
CHANGED
@@ -1,28 +1,29 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
1
|
+
require 'sinatra'
|
2
|
+
require 'sinatra/browserid'
|
3
|
+
|
4
|
+
|
5
|
+
register Sinatra::BrowserID
|
6
|
+
|
7
|
+
set :sessions, true
|
8
|
+
# Disabling origin-check is needed to make webkit-browsers like Chrome work.
|
9
|
+
# Behind a proxy you will also need to disable :remote_token, regardless for which browser.
|
10
|
+
set :protection, except: [:http_origin]
|
11
|
+
get '/' do
|
12
|
+
if authorized?
|
13
|
+
"Welcome, #{authorized_email}"
|
14
|
+
else
|
15
|
+
render_login_button
|
16
|
+
end
|
17
|
+
end
|
10
18
|
|
11
|
-
|
19
|
+
get '/secure' do
|
20
|
+
authorize! # require a user be logged in
|
12
21
|
|
13
|
-
|
14
|
-
|
15
|
-
end
|
22
|
+
authorized_email # browserid email
|
23
|
+
end
|
16
24
|
|
17
|
-
|
25
|
+
get '/logout' do
|
18
26
|
logout!
|
19
27
|
|
20
28
|
redirect '/'
|
21
|
-
|
22
|
-
|
23
|
-
get '/confidential' do
|
24
|
-
authorize!
|
25
|
-
|
26
|
-
"Hey #{authorized_email}, you're authorized!"
|
27
|
-
end
|
28
|
-
end
|
29
|
+
end
|
data/example/config.ru
CHANGED
@@ -28,6 +28,28 @@ module Sinatra
|
|
28
28
|
session[:browserid_email]
|
29
29
|
end
|
30
30
|
|
31
|
+
# Normalize the email like the broker will do it, see
|
32
|
+
# https://github.com/portier/portier.github.io/blob/master/specs/Email-Normalization.md
|
33
|
+
def normalize_email(email)
|
34
|
+
begin
|
35
|
+
user, domain = email.split("@")
|
36
|
+
if user == nil or user.empty?
|
37
|
+
raise ArgumentError.new('user part must not be empty')
|
38
|
+
end
|
39
|
+
user = user.downcase
|
40
|
+
domain = SimpleIDN.to_ascii(domain).downcase
|
41
|
+
begin
|
42
|
+
IPAddr.new(domain)
|
43
|
+
rescue
|
44
|
+
# if domain could not be parsed as IP we are good
|
45
|
+
return user + "@" + domain
|
46
|
+
end
|
47
|
+
raise ArgumentError.new('domain must not be an IP')
|
48
|
+
rescue Exception => e
|
49
|
+
raise ArgumentError, 'Not a valid email adress: ' + e.message
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
31
53
|
# Returns the HTML to render the Persona login form.
|
32
54
|
# Optionally takes a URL parameter for where the user should
|
33
55
|
# be redirected to after the assert POST back.
|
@@ -39,9 +61,14 @@ module Sinatra
|
|
39
61
|
redirect_url ||= request.url
|
40
62
|
session['redirect_url'] = redirect_url
|
41
63
|
|
42
|
-
|
43
|
-
|
44
|
-
|
64
|
+
if session[:nonce]
|
65
|
+
nonce = session[:nonce]
|
66
|
+
# Try to limit how many nonces are stored by keeping the session nonce alive
|
67
|
+
Cachy.delete_key(nonce)
|
68
|
+
Cachy.cache(nonce, expires_in: 600) { true }
|
69
|
+
else
|
70
|
+
session[:nonce] = nonce = SecureRandom.base64
|
71
|
+
Cachy.cache(nonce, expires_in: 600) { true }
|
45
72
|
end
|
46
73
|
|
47
74
|
template = ERB.new(Templates::LOGIN_BUTTON)
|
data/lib/sinatra/browserid.rb
CHANGED
@@ -4,14 +4,27 @@ require "open-uri"
|
|
4
4
|
require 'json'
|
5
5
|
require 'url_safe_base64'
|
6
6
|
require 'jwt'
|
7
|
+
require 'simpleidn'
|
8
|
+
require 'ipaddr'
|
7
9
|
require "sinatra/base"
|
8
10
|
require 'sinatra/browserid/helpers'
|
9
11
|
require 'sinatra/browserid/template'
|
12
|
+
require 'addressable/uri'
|
13
|
+
require 'cachy'
|
14
|
+
require 'moneta'
|
15
|
+
|
10
16
|
|
11
17
|
# This module provides an interface to verify a users email address
|
12
18
|
# with browserid.org.
|
13
19
|
module Sinatra
|
14
20
|
module BrowserID
|
21
|
+
|
22
|
+
# Init an in-memory cache via the cachy gem. We use this
|
23
|
+
# instead of the session because of dropped sessions
|
24
|
+
# after redirects, see https://github.com/sinatra/sinatra/issues/1742.
|
25
|
+
Cachy.cache_store = Moneta.new(:Memory, expires: 600) # 10 minutes
|
26
|
+
# We need to set a global :expires here because of https://github.com/grosser/cachy/issues/7
|
27
|
+
|
15
28
|
def self.registered(app)
|
16
29
|
app.helpers BrowserID::Helpers
|
17
30
|
|
@@ -22,35 +35,48 @@ module Sinatra
|
|
22
35
|
app.set :browserid_button_text, "Log in"
|
23
36
|
|
24
37
|
app.get '/_browserid_login' do
|
25
|
-
# TODO(petef): render a page that initiates login without
|
26
|
-
# waiting for a user click.
|
27
38
|
render_login_button
|
28
39
|
end
|
29
40
|
|
30
41
|
app.post '/_browserid_assert' do
|
31
42
|
begin
|
32
|
-
#
|
33
|
-
#
|
34
|
-
|
43
|
+
# Server checks signature
|
44
|
+
# For that, fetch the public key from the LA instance (TODO: Do that beforehand for trusted instances, and generally cache the key)
|
45
|
+
public_key_jwks_uri = Addressable::URI.parse(settings.browserid_url + '/keys.json')
|
46
|
+
public_key_jwks = ::JSON.parse(URI.parse(public_key_jwks_uri).read)
|
35
47
|
public_key = OpenSSL::PKey::RSA.new
|
36
|
-
public_key.
|
37
|
-
|
48
|
+
if public_key.respond_to? :set_key
|
49
|
+
# Set n and d via the new set_key function, as direct access to n and e is blocked for some ruby and openssl versions.
|
50
|
+
# Note that we have no d, as this is a public key, which would be the third param
|
51
|
+
public_key.set_key( (OpenSSL::BN.new UrlSafeBase64.decode64(public_key_jwks["keys"][0]["n"]), 2),
|
52
|
+
(OpenSSL::BN.new UrlSafeBase64.decode64(public_key_jwks["keys"][0]["e"]), 2),
|
53
|
+
nil)
|
54
|
+
else
|
55
|
+
public_key.e = OpenSSL::BN.new UrlSafeBase64.decode64(public_key_jwks["keys"][0]["e"]), 2
|
56
|
+
public_key.n = OpenSSL::BN.new UrlSafeBase64.decode64(public_key_jwks["keys"][0]["n"]), 2
|
57
|
+
end
|
38
58
|
|
39
59
|
id_token = JWT.decode params[:id_token], public_key, true, { :algorithm => 'RS256' }
|
40
60
|
id_token = id_token[0]
|
41
|
-
#
|
61
|
+
# Needs to make sure token is still valid
|
42
62
|
if (id_token["iss"] == settings.browserid_url &&
|
43
63
|
id_token["aud"] == request.base_url.chomp('/') &&
|
44
64
|
id_token["exp"] > Time.now.to_i &&
|
45
65
|
id_token["email_verified"] &&
|
46
|
-
|
47
|
-
|
48
|
-
session
|
66
|
+
# nonce is really known to us
|
67
|
+
Cachy.get(id_token["nonce"]))
|
68
|
+
session[:browserid_email] = id_token['email']
|
69
|
+
Cachy.delete_key(id_token["nonce"])
|
70
|
+
session.delete(:nonce) # it's possible the session persisted
|
49
71
|
if session['redirect_url']
|
50
72
|
redirect session['redirect_url']
|
51
73
|
else
|
52
74
|
redirect "/"
|
53
75
|
end
|
76
|
+
else
|
77
|
+
# Even when the token check failed the nonce has to be invalidated
|
78
|
+
Cachy.delete_key(id_token["nonce"])
|
79
|
+
session.delete(:nonce)
|
54
80
|
end
|
55
81
|
rescue OpenURI::HTTPError => e
|
56
82
|
puts "could not validate token: " + e.to_s
|
@@ -58,8 +84,8 @@ module Sinatra
|
|
58
84
|
halt 403
|
59
85
|
|
60
86
|
end
|
61
|
-
end
|
62
|
-
end
|
87
|
+
end
|
88
|
+
end
|
63
89
|
register BrowserID
|
64
|
-
end
|
90
|
+
end
|
65
91
|
|
metadata
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sinatra-portier
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: '2.0'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Pete Fritchman
|
8
8
|
- Malte Paskuda
|
9
|
-
autorequire:
|
9
|
+
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2022-02-02 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: sinatra
|
@@ -53,7 +53,63 @@ dependencies:
|
|
53
53
|
- - ">="
|
54
54
|
- !ruby/object:Gem::Version
|
55
55
|
version: 0.2.2
|
56
|
-
|
56
|
+
- !ruby/object:Gem::Dependency
|
57
|
+
name: simpleidn
|
58
|
+
requirement: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: 0.0.9
|
63
|
+
type: :runtime
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - ">="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: 0.0.9
|
70
|
+
- !ruby/object:Gem::Dependency
|
71
|
+
name: addressable
|
72
|
+
requirement: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '2.8'
|
77
|
+
type: :runtime
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - ">="
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '2.8'
|
84
|
+
- !ruby/object:Gem::Dependency
|
85
|
+
name: cachy
|
86
|
+
requirement: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - ">="
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '0.4'
|
91
|
+
type: :runtime
|
92
|
+
prerelease: false
|
93
|
+
version_requirements: !ruby/object:Gem::Requirement
|
94
|
+
requirements:
|
95
|
+
- - ">="
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: '0.4'
|
98
|
+
- !ruby/object:Gem::Dependency
|
99
|
+
name: moneta
|
100
|
+
requirement: !ruby/object:Gem::Requirement
|
101
|
+
requirements:
|
102
|
+
- - ">="
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: '1.4'
|
105
|
+
type: :runtime
|
106
|
+
prerelease: false
|
107
|
+
version_requirements: !ruby/object:Gem::Requirement
|
108
|
+
requirements:
|
109
|
+
- - ">="
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
version: '1.4'
|
112
|
+
description:
|
57
113
|
email:
|
58
114
|
- malte@paskuda.biz
|
59
115
|
executables: []
|
@@ -63,7 +119,6 @@ files:
|
|
63
119
|
- README.md
|
64
120
|
- example/app.rb
|
65
121
|
- example/config.ru
|
66
|
-
- example/views/index.erb
|
67
122
|
- lib/sinatra/browserid.rb
|
68
123
|
- lib/sinatra/browserid/helpers.rb
|
69
124
|
- lib/sinatra/browserid/template.rb
|
@@ -71,7 +126,7 @@ files:
|
|
71
126
|
homepage: https://github.com/onli/sinatra-portier
|
72
127
|
licenses: []
|
73
128
|
metadata: {}
|
74
|
-
post_install_message:
|
129
|
+
post_install_message:
|
75
130
|
rdoc_options:
|
76
131
|
- "--inline-source"
|
77
132
|
require_paths:
|
@@ -87,9 +142,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
87
142
|
- !ruby/object:Gem::Version
|
88
143
|
version: '0'
|
89
144
|
requirements: []
|
90
|
-
|
91
|
-
|
92
|
-
signing_key:
|
145
|
+
rubygems_version: 3.2.32
|
146
|
+
signing_key:
|
93
147
|
specification_version: 4
|
94
148
|
summary: Sinatra extension for user authentication with portier
|
95
149
|
test_files: []
|
data/example/views/index.erb
DELETED
@@ -1,21 +0,0 @@
|
|
1
|
-
<html>
|
2
|
-
<head>
|
3
|
-
</head>
|
4
|
-
<body>
|
5
|
-
|
6
|
-
<h1>Test App</h1>
|
7
|
-
|
8
|
-
<p>
|
9
|
-
<% if authorized? %>
|
10
|
-
Hello, <%= authorized_email %> <a href="/logout">(logout)</a>
|
11
|
-
<% else %>
|
12
|
-
<%= render_login_button %>
|
13
|
-
<% end %>
|
14
|
-
</p>
|
15
|
-
|
16
|
-
<p>
|
17
|
-
see a <a href="/confidential">page that requires a login</a>.
|
18
|
-
</p>
|
19
|
-
|
20
|
-
</body>
|
21
|
-
</html>
|