clarion 0.1.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 +7 -0
- data/.gitignore +11 -0
- data/.rspec +3 -0
- data/.travis.yml +5 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +69 -0
- data/LICENSE.txt +21 -0
- data/README.md +66 -0
- data/Rakefile +6 -0
- data/app/public/register.js +82 -0
- data/app/public/sign.js +67 -0
- data/app/public/test.js +81 -0
- data/app/public/u2f-api.js +748 -0
- data/app/views/authn.erb +51 -0
- data/app/views/layout.erb +128 -0
- data/app/views/register.erb +55 -0
- data/app/views/test.erb +35 -0
- data/app/views/test_callback.erb +10 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/bin/test-authn +11 -0
- data/clarion.gemspec +32 -0
- data/config.ru +63 -0
- data/dev.rb +30 -0
- data/docs/api.md +113 -0
- data/docs/counters.md +14 -0
- data/docs/stores.md +13 -0
- data/lib/clarion.rb +3 -0
- data/lib/clarion/app.rb +271 -0
- data/lib/clarion/authenticator.rb +51 -0
- data/lib/clarion/authn.rb +106 -0
- data/lib/clarion/config.rb +58 -0
- data/lib/clarion/const_finder.rb +24 -0
- data/lib/clarion/counters.rb +9 -0
- data/lib/clarion/counters/base.rb +17 -0
- data/lib/clarion/counters/dynamodb.rb +45 -0
- data/lib/clarion/counters/memory.rb +29 -0
- data/lib/clarion/key.rb +76 -0
- data/lib/clarion/registrator.rb +26 -0
- data/lib/clarion/stores.rb +9 -0
- data/lib/clarion/stores/base.rb +17 -0
- data/lib/clarion/stores/memory.rb +30 -0
- data/lib/clarion/stores/s3.rb +54 -0
- data/lib/clarion/version.rb +3 -0
- metadata +199 -0
data/app/views/authn.erb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
<style type="text/css">
|
2
|
+
#procession > div {
|
3
|
+
display: none;
|
4
|
+
}
|
5
|
+
#procession.procession_init > div.procession_init {
|
6
|
+
display: block;
|
7
|
+
}
|
8
|
+
#procession.procession_unsupported > div.procession_unsupported {
|
9
|
+
display: block;
|
10
|
+
}
|
11
|
+
#procession.procession_wait > div.procession_wait {
|
12
|
+
display: block;
|
13
|
+
}
|
14
|
+
#procession.procession_contact > div.procession_contact {
|
15
|
+
display: block;
|
16
|
+
}
|
17
|
+
#procession.procession_ok > div.procession_ok {
|
18
|
+
display: block;
|
19
|
+
}
|
20
|
+
#procession.procession_error > div.procession_error {
|
21
|
+
display: block;
|
22
|
+
}
|
23
|
+
</style>
|
24
|
+
|
25
|
+
<p class='center'><strong>U2F 2FA <%- if @authn.name -%> for <%= @authn.name %><%- end -%></strong></p>
|
26
|
+
<div id="procession" class="procession_init" data-authn-id="<%= @authn.id %>" data-app-id="<%= @app_id %>" data-requests='<%= @requests.to_json %>' data-challenge='<%= @challenge.to_json %>' data-req-id='<%= @req_id %>'>
|
27
|
+
<div class="procession_init">
|
28
|
+
<p>Initializing...</p>
|
29
|
+
</div>
|
30
|
+
<div class="procession_unsupported">
|
31
|
+
<p>You have to use browser supporting FIDO U2F</p>
|
32
|
+
</div>
|
33
|
+
<div class="procession_wait">
|
34
|
+
<p>Insert and tap your security key.</p>
|
35
|
+
</div>
|
36
|
+
<div class="procession_contact">
|
37
|
+
<p>Contacting...</p>
|
38
|
+
</div>
|
39
|
+
<div class="procession_ok">
|
40
|
+
<p>OK: You may now close this page.</p>
|
41
|
+
</div>
|
42
|
+
<div class="procession_error">
|
43
|
+
<p>Error: Reload and try again?</p>
|
44
|
+
</div>
|
45
|
+
</div>
|
46
|
+
<%- if @authn.comment -%>
|
47
|
+
<p><small><%= @authn.comment %></small></p>
|
48
|
+
<%- end -%>
|
49
|
+
|
50
|
+
|
51
|
+
<script src="/sign.js"></script>
|
@@ -0,0 +1,128 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<meta charset='utf-8'>
|
5
|
+
<title>Clarion</title>
|
6
|
+
<style type="text/css">
|
7
|
+
* {
|
8
|
+
font-family: 'Avenir Next', sans-serif;
|
9
|
+
font-size: 16px;
|
10
|
+
}
|
11
|
+
pre, code {
|
12
|
+
font-family: 'Monaco', monospace;
|
13
|
+
}
|
14
|
+
pre, pre code {
|
15
|
+
width: 100%;
|
16
|
+
white-space: pre-wrap;
|
17
|
+
word-wrap: break-word;
|
18
|
+
font-size: 14px;
|
19
|
+
}
|
20
|
+
strong {
|
21
|
+
font-weight: bold;
|
22
|
+
}
|
23
|
+
small {
|
24
|
+
font-size: 12px;
|
25
|
+
}
|
26
|
+
|
27
|
+
.center {
|
28
|
+
text-align: center;
|
29
|
+
}
|
30
|
+
|
31
|
+
body {
|
32
|
+
text-align: center;
|
33
|
+
background-color: white;
|
34
|
+
}
|
35
|
+
|
36
|
+
.hidden {
|
37
|
+
display: none;
|
38
|
+
}
|
39
|
+
|
40
|
+
.container {
|
41
|
+
margin-left: auto;
|
42
|
+
margin-right: auto;
|
43
|
+
width: 400px;
|
44
|
+
text-align: left;
|
45
|
+
padding-top: 12px;
|
46
|
+
}
|
47
|
+
|
48
|
+
.box, .form, .notice, .error {
|
49
|
+
border-radius: 3px;
|
50
|
+
margin-bottom: 12px;
|
51
|
+
padding: 8px 20px;
|
52
|
+
}
|
53
|
+
|
54
|
+
.box {
|
55
|
+
padding: 12px 20px;
|
56
|
+
border: 1px solid #ddd;
|
57
|
+
background-color: white;
|
58
|
+
}
|
59
|
+
|
60
|
+
.notice {
|
61
|
+
border: 1px solid #D6E9C6;
|
62
|
+
background-color: #DFF0D8;
|
63
|
+
color: #3C763D;
|
64
|
+
}
|
65
|
+
|
66
|
+
.error {
|
67
|
+
border: 1px solid #EBCCD1;
|
68
|
+
background-color: #F2DEDE;
|
69
|
+
color: #A94442;
|
70
|
+
}
|
71
|
+
|
72
|
+
|
73
|
+
.box input, .box button {
|
74
|
+
width: 100%;
|
75
|
+
-moz-box-sizing: border-box;
|
76
|
+
-webkit-box-sizing: border-box;
|
77
|
+
box-sizing: border-box;
|
78
|
+
|
79
|
+
font-size: 20px;
|
80
|
+
padding: 6px 4px;
|
81
|
+
|
82
|
+
border: 1px solid #e9e9e9;
|
83
|
+
border-radius: 3px;
|
84
|
+
}
|
85
|
+
|
86
|
+
input[type="submit"], button {
|
87
|
+
background-color: #337AB7;
|
88
|
+
color: white;
|
89
|
+
}
|
90
|
+
|
91
|
+
input[type="submit"]:hover, button:hover {
|
92
|
+
background-color: #286090;
|
93
|
+
}
|
94
|
+
|
95
|
+
.credit {
|
96
|
+
text-align: center;
|
97
|
+
}
|
98
|
+
|
99
|
+
.credit, .credit * {
|
100
|
+
color: #767676;
|
101
|
+
font-size: 12px;
|
102
|
+
}
|
103
|
+
</style>
|
104
|
+
<script src="/u2f-api.js"></script>
|
105
|
+
</head>
|
106
|
+
|
107
|
+
<body>
|
108
|
+
<div class="container">
|
109
|
+
<% notice ||= nil; error ||= nil %>
|
110
|
+
<% if notice %>
|
111
|
+
<div class="notice"><%= notice %></div>
|
112
|
+
<% end %>
|
113
|
+
|
114
|
+
<% if error %>
|
115
|
+
<div class="error"><%= error %></div>
|
116
|
+
<% end %>
|
117
|
+
|
118
|
+
<div class="box">
|
119
|
+
<%== yield %>
|
120
|
+
</div>
|
121
|
+
|
122
|
+
<div class="credit">
|
123
|
+
Powered by <a href="https://github.com/sorah/clarion">sorah/clarion</a>
|
124
|
+
</div>
|
125
|
+
</div>
|
126
|
+
|
127
|
+
<body>
|
128
|
+
</body>
|
@@ -0,0 +1,55 @@
|
|
1
|
+
<style type="text/css">
|
2
|
+
#procession > div {
|
3
|
+
display: none;
|
4
|
+
}
|
5
|
+
#procession.procession_init > div.procession_init {
|
6
|
+
display: block;
|
7
|
+
}
|
8
|
+
#procession.procession_unsupported > div.procession_unsupported {
|
9
|
+
display: block;
|
10
|
+
}
|
11
|
+
#procession.procession_wait > div.procession_wait {
|
12
|
+
display: block;
|
13
|
+
}
|
14
|
+
#procession.procession_contact > div.procession_contact {
|
15
|
+
display: block;
|
16
|
+
}
|
17
|
+
#procession.procession_ok > div.procession_ok {
|
18
|
+
display: block;
|
19
|
+
}
|
20
|
+
#procession.procession_error > div.procession_error {
|
21
|
+
display: block;
|
22
|
+
}
|
23
|
+
</style>
|
24
|
+
|
25
|
+
<p><strong>U2F key registration<%- if @name -%> for <%= @name %><%- end -%></strong></p>
|
26
|
+
<div id="procession" class="procession_init" data-app-id="<%= @app_id %>" data-requests='<%= @requests.to_json %>' data-state='<%= @state %>' data-callback='<%= @callback %>' data-reg-id='<%= @reg_id %>'>
|
27
|
+
<form id="callback_form" class="hidden" method='POST'>
|
28
|
+
<input type="hidden" name="state" value="<%= @state %>">
|
29
|
+
<input type="hidden" name="data" value="">
|
30
|
+
</form>
|
31
|
+
<div class="procession_init">
|
32
|
+
<p>Loading...</p>
|
33
|
+
</div>
|
34
|
+
<div class="procession_unsupported">
|
35
|
+
<p>You have to use browser supporting FIDO U2F</p>
|
36
|
+
</div>
|
37
|
+
<div class="procession_wait">
|
38
|
+
<p>Insert and tap your security key.</p>
|
39
|
+
</div>
|
40
|
+
<div class="procession_contact">
|
41
|
+
<p>Contacting...</p>
|
42
|
+
</div>
|
43
|
+
<div class="procession_ok">
|
44
|
+
<p>OK...</p>
|
45
|
+
</div>
|
46
|
+
<div class="procession_error">
|
47
|
+
<p>Error: try again from the previous page?</p>
|
48
|
+
</div>
|
49
|
+
</div>
|
50
|
+
<%- if @comment -%>
|
51
|
+
<p><small><%= @comment %></small></p>
|
52
|
+
<%- end -%>
|
53
|
+
|
54
|
+
|
55
|
+
<script src="/register.js"></script>
|
data/app/views/test.erb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
<form action="<%= @register_url %>" method="POST">
|
2
|
+
<input type="hidden" name="name" value="<%= @name %>">
|
3
|
+
<input type="hidden" name="comment" value="<%= @comment %>">
|
4
|
+
<input type="hidden" name="state" value="<%= @state%>">
|
5
|
+
<input type="hidden" name="callback" value="<%= @callback %>">
|
6
|
+
<input type="hidden" name="public_key" value="<%= @public_key %>">
|
7
|
+
<input type="submit" value="Register (POST)">
|
8
|
+
</form>
|
9
|
+
|
10
|
+
<form id="callback_form" class="hidden" method='POST' action="/test/callback">
|
11
|
+
<input type="hidden" name="state" value="">
|
12
|
+
<input type="hidden" name="data" value="">
|
13
|
+
</form>
|
14
|
+
|
15
|
+
<button id="register_cb_button" data-url="/register?<%= URI.encode_www_form(name: @name, comment: @comment, state: @state, callback: "js:#{request.base_url}", public_key: @public_key)%>">Register (JS callback)</button>
|
16
|
+
<script>
|
17
|
+
"use strict";
|
18
|
+
document.addEventListener("DOMContentLoaded", function() {
|
19
|
+
window.addEventListener("message", function(event) {
|
20
|
+
console.log(event.data);
|
21
|
+
if (event.data.clarion_key) {
|
22
|
+
let data = event.data.clarion_key;
|
23
|
+
let form = document.getElementById("callback_form");
|
24
|
+
form.querySelector('[name=state]').value = data.state;
|
25
|
+
form.querySelector('[name=data]').value = data.data;
|
26
|
+
form.submit();
|
27
|
+
}
|
28
|
+
}, false);
|
29
|
+
|
30
|
+
let button = document.getElementById("register_cb_button");
|
31
|
+
button.addEventListener("click", function() {
|
32
|
+
window.open(button.attributes['data-url'].value, '_blank');
|
33
|
+
});
|
34
|
+
});
|
35
|
+
</script>
|
@@ -0,0 +1,10 @@
|
|
1
|
+
<pre><code><%= @key.to_json(:all) %></code></pre>
|
2
|
+
|
3
|
+
<button id="start_authn_button">Create Authn</button>
|
4
|
+
<button class='hidden' id="open_authn_button">Open Authn</button>
|
5
|
+
<pre>
|
6
|
+
<code id="authn_test" data-key='<%= @key.to_json(:all) %>'>
|
7
|
+
</code>
|
8
|
+
</pre>
|
9
|
+
|
10
|
+
<script src="/test.js"></script>
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "clarion"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/bin/test-authn
ADDED
data/clarion.gemspec
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require "clarion/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "clarion"
|
8
|
+
spec.version = Clarion::VERSION
|
9
|
+
spec.authors = ["Sorah Fukumori"]
|
10
|
+
spec.email = ["sorah@cookpad.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{Web-based FIDO U2F Helper for CLI operations (SSH login...)}
|
13
|
+
spec.homepage = "https://github.com/sorah/clarion"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
17
|
+
f.match(%r{^(test|spec|features)/})
|
18
|
+
end
|
19
|
+
spec.bindir = "exe"
|
20
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
21
|
+
spec.require_paths = ["lib"]
|
22
|
+
|
23
|
+
spec.add_dependency "u2f"
|
24
|
+
spec.add_dependency "sinatra"
|
25
|
+
spec.add_dependency "erubis"
|
26
|
+
spec.add_dependency "aws-sdk-s3"
|
27
|
+
spec.add_dependency "aws-sdk-dynamodb"
|
28
|
+
|
29
|
+
spec.add_development_dependency "bundler"
|
30
|
+
spec.add_development_dependency "rake"
|
31
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
32
|
+
end
|
data/config.ru
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'bundler/setup'
|
2
|
+
require 'securerandom'
|
3
|
+
|
4
|
+
require 'clarion'
|
5
|
+
|
6
|
+
if ENV['RACK_ENV'] == 'production'
|
7
|
+
raise 'Set $SECRET_KEY_BASE' unless ENV['SECRET_KEY_BASE']
|
8
|
+
end
|
9
|
+
|
10
|
+
if ENV['CLARION_DEV_HTTPS']
|
11
|
+
use(Class.new do
|
12
|
+
def initialize(app)
|
13
|
+
@app = app
|
14
|
+
end
|
15
|
+
def call(env)
|
16
|
+
@app.call env.merge('HTTPS' => 'on')
|
17
|
+
end
|
18
|
+
end)
|
19
|
+
end
|
20
|
+
|
21
|
+
config = {
|
22
|
+
registration_allowed_url: Regexp.new(ENV.fetch('CLARION_REGISTRATION_ALLOWED_URL')),
|
23
|
+
authn_default_expires_in: ENV.fetch('CLARION_AUTHN_DEFAULT_EXPIRES_IN', 300).to_i,
|
24
|
+
}
|
25
|
+
|
26
|
+
case ENV.fetch('CLARION_STORE', 's3')
|
27
|
+
when 's3'
|
28
|
+
config[:store] = {
|
29
|
+
kind: :s3,
|
30
|
+
region: ENV.fetch('CLARION_STORE_S3_REGION'),
|
31
|
+
bucket: ENV.fetch('CLARION_STORE_S3_BUCKET'),
|
32
|
+
prefix: ENV.fetch('CLARION_STORE_S3_PREFIX'),
|
33
|
+
}
|
34
|
+
when 'memory'
|
35
|
+
config[:store] = {kind: :memory}
|
36
|
+
else
|
37
|
+
raise ArgumentError, "Unsupported $CLARION_STORE"
|
38
|
+
end
|
39
|
+
|
40
|
+
case ENV.fetch('CLARION_COUNTER', nil)
|
41
|
+
when 'dynamodb'
|
42
|
+
config[:counter] = {
|
43
|
+
kind: :dynamodb,
|
44
|
+
region: ENV.fetch('CLARION_COUNTER_DYNAMODB_REGION'),
|
45
|
+
table_name: ENV.fetch('CLARION_COUNTER_DYNAMODB_TABLE'),
|
46
|
+
}
|
47
|
+
when 'memory'
|
48
|
+
config[:counter] = {kind: :memory}
|
49
|
+
when nil
|
50
|
+
# do nothing
|
51
|
+
else
|
52
|
+
raise ArgumentError, "Unsupported $CLARION_COUNTER"
|
53
|
+
end
|
54
|
+
|
55
|
+
use(
|
56
|
+
Rack::Session::Cookie,
|
57
|
+
key: 'clarionsess',
|
58
|
+
expire_after: 3600,
|
59
|
+
secure: ENV['RACK_ENV'] == 'production',
|
60
|
+
secret: ENV.fetch('SECRET_KEY_BASE', SecureRandom.base64(256)),
|
61
|
+
)
|
62
|
+
|
63
|
+
run Clarion.app(Clarion::Config.new(config))
|
data/dev.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'bundler/setup'
|
3
|
+
require 'webrick'
|
4
|
+
require 'webrick/ssl'
|
5
|
+
require 'logger'
|
6
|
+
require 'rack'
|
7
|
+
ENV['RACK_ENV'] = 'development'
|
8
|
+
ENV['CLARION_DEV_HTTPS']='1'
|
9
|
+
ENV['CLARION_REGISTRATION_ALLOWED_URL']='.+'
|
10
|
+
|
11
|
+
ENV['CLARION_STORE']='memory'
|
12
|
+
ENV['CLARION_COUNTER']='memory'
|
13
|
+
|
14
|
+
|
15
|
+
app, _ = Rack::Builder.parse_file(File.join(__dir__, 'config.ru'))
|
16
|
+
|
17
|
+
rack_options = {
|
18
|
+
app: app,
|
19
|
+
Port: ENV.fetch('PORT', 3000).to_i,
|
20
|
+
Host: ENV.fetch('BIND', '127.0.0.1'),
|
21
|
+
environment: 'development',
|
22
|
+
server: 'webrick',
|
23
|
+
Logger: Logger.new($stdout),#.tap { |_| _.level = Logger::DEBUG },
|
24
|
+
SSLEnable: true,
|
25
|
+
SSLCertName: 'CN=localhost',
|
26
|
+
SSLCertComment: 'dummy',
|
27
|
+
SSLStartImmediately: true,
|
28
|
+
}
|
29
|
+
server = Rack::Server.new(rack_options)
|
30
|
+
server.start
|