clarion 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|