token_chain 0.0.2 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/token_chain/emittable_token.rb +9 -0
- data/lib/token_chain/emitter.rb +32 -0
- data/lib/token_chain/receivable_token.rb +14 -0
- data/lib/token_chain/receiver.rb +61 -0
- data/lib/token_chain/version.rb +1 -1
- data/spec/spec_helper.rb +1 -0
- data/spec/token_chain/emitter_spec.rb +55 -0
- data/spec/token_chain/receiver_spec.rb +101 -0
- data/token_chain.gemspec +1 -0
- metadata +24 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 479d3adff4faf57359d9ca967bda2bbac3042a75
|
4
|
+
data.tar.gz: 4f42d31273559187b440141e02c688b1fe245dbb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 692274eb0d93aa194d4f8a3d2aa612ead67026984b7a70b31f9c3960c5204b2495888045b5edef08fe989e33a6be33cfaf1acc4ad909aba64333e10737fa6582
|
7
|
+
data.tar.gz: 6638b86a0eb1ddbb8cb32d68c7b3e87610f242fb5e1c0c962b3e55b53b957036dc844c8ab8cd6677a896806bb15f6e4e900568b8b6ac36df58c0855ef9d7b10e
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'token_chain/emittable_token'
|
2
|
+
|
3
|
+
module TokenChain
|
4
|
+
class Emitter
|
5
|
+
|
6
|
+
def initialize(anchor: nil, passphrase: nil)
|
7
|
+
@emitter = weigh_anchor(anchor, passphrase)
|
8
|
+
end
|
9
|
+
|
10
|
+
def next_token!
|
11
|
+
anchor = @emitter.anchor
|
12
|
+
previous_token = @emitter.last_consumed_token
|
13
|
+
generator = Generator.new(anchor, previous_token)
|
14
|
+
|
15
|
+
generator.generate.tap do |token|
|
16
|
+
@emitter.last_consumed_token = token
|
17
|
+
@emitter.save
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def weigh_anchor(anchor, passphrase)
|
24
|
+
unless anchor || passphrase
|
25
|
+
raise ArgumentError.new("You must provide an anchor or a passphrase.")
|
26
|
+
end
|
27
|
+
anchor = anchor || Anchor.from(passphrase)
|
28
|
+
EmittableToken.find(anchor) || EmittableToken.create(anchor)
|
29
|
+
end
|
30
|
+
|
31
|
+
end # class Emitter
|
32
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'token_chain/anchor'
|
2
|
+
require 'token_chain/generator'
|
3
|
+
require 'token_chain/receivable_token'
|
4
|
+
|
5
|
+
module TokenChain
|
6
|
+
class Receiver
|
7
|
+
|
8
|
+
def initialize_chain(anchor)
|
9
|
+
@generator = Generator.new anchor
|
10
|
+
@generator.generate(10).each_with_index do |token, i|
|
11
|
+
ReceivableToken.create token, anchor: anchor, sequence: i
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def validate!(token)
|
16
|
+
receivable = ReceivableToken.find(token)
|
17
|
+
|
18
|
+
if receivable.nil?
|
19
|
+
raise UnknownTokenError.new("Invalid token")
|
20
|
+
elsif ! receivable.available?
|
21
|
+
raise InvalidTokenError.new("Token was previously submitted")
|
22
|
+
else # it's a valid, available token
|
23
|
+
response = { result: 'success' }
|
24
|
+
|
25
|
+
anchor = receivable.anchor
|
26
|
+
incoming_token_seq = receivable.sequence
|
27
|
+
|
28
|
+
if incoming_token_seq > 0
|
29
|
+
response[:warning] = 'Token submitted out of sequence.'
|
30
|
+
end
|
31
|
+
|
32
|
+
# resequence and prune
|
33
|
+
ReceivableToken.where(anchor: anchor).each do |rt|
|
34
|
+
old_sequence = rt.sequence
|
35
|
+
new_sequence = old_sequence - (incoming_token_seq + 1)
|
36
|
+
rt.sequence = new_sequence
|
37
|
+
if rt.sequence >= -10
|
38
|
+
rt.save
|
39
|
+
else
|
40
|
+
rt.delete
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# replentish tokens
|
45
|
+
first_new_token_seq = 9 - incoming_token_seq
|
46
|
+
(first_new_token_seq..9).each do |sequence|
|
47
|
+
ReceivableToken.create @generator.generate,
|
48
|
+
anchor: anchor,
|
49
|
+
sequence: sequence
|
50
|
+
end
|
51
|
+
|
52
|
+
return response
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
class UnknownTokenError < ArgumentError; end
|
59
|
+
class InvalidTokenError < ArgumentError; end
|
60
|
+
end
|
61
|
+
|
data/lib/token_chain/version.rb
CHANGED
data/spec/spec_helper.rb
CHANGED
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'token_chain/anchor'
|
3
|
+
require 'token_chain/emittable_token'
|
4
|
+
require 'token_chain/emitter'
|
5
|
+
|
6
|
+
module TokenChain
|
7
|
+
describe Emitter do
|
8
|
+
Given { EmittableToken.store = {} }
|
9
|
+
Given(:passphrase) { 'the rain in spain' }
|
10
|
+
Given(:anchor) { Anchor.from passphrase }
|
11
|
+
Given(:generator) { Generator.new anchor }
|
12
|
+
|
13
|
+
describe '#initialize' do
|
14
|
+
describe 'with an anchor' do
|
15
|
+
When { Emitter.new anchor: anchor }
|
16
|
+
Then { expect( EmittableToken[anchor] ).to_not be_nil }
|
17
|
+
Then { expect( EmittableToken[anchor].last_consumed_token ).to be_nil }
|
18
|
+
end
|
19
|
+
|
20
|
+
describe 'with a passphrase' do
|
21
|
+
When { Emitter.new passphrase: passphrase }
|
22
|
+
Then { expect( EmittableToken[anchor] ).to_not be_nil }
|
23
|
+
Then { expect( EmittableToken.find(anchor).last_consumed_token ).to be_nil }
|
24
|
+
end
|
25
|
+
|
26
|
+
describe 'with neither anchor nor passphrase' do
|
27
|
+
Then { expect{ Emitter.new }.to raise_error(/provide an anchor or a passphrase/) }
|
28
|
+
end
|
29
|
+
end #initialize
|
30
|
+
|
31
|
+
describe '#next_token!' do
|
32
|
+
Given(:emitter) { Emitter.new anchor: anchor }
|
33
|
+
|
34
|
+
context 'first consumed token' do
|
35
|
+
Given(:expected_token) { generator.generate }
|
36
|
+
|
37
|
+
When(:token) { emitter.next_token! }
|
38
|
+
|
39
|
+
Then { expect(token).to eq(expected_token) }
|
40
|
+
Then { expect(EmittableToken[anchor].last_consumed_token).to eq(expected_token) }
|
41
|
+
end
|
42
|
+
|
43
|
+
context 'second consumed token' do
|
44
|
+
Given(:expected_token) { generator.generate(2).last }
|
45
|
+
Given { emitter.next_token! }
|
46
|
+
|
47
|
+
When(:token) { emitter.next_token! }
|
48
|
+
|
49
|
+
Then { expect(token).to eq(expected_token) }
|
50
|
+
Then { expect(EmittableToken[anchor].last_consumed_token).to eq(expected_token) }
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'token_chain/anchor'
|
3
|
+
require 'token_chain/generator'
|
4
|
+
require 'token_chain/receivable_token'
|
5
|
+
require 'token_chain/receiver'
|
6
|
+
|
7
|
+
module TokenChain
|
8
|
+
describe Receiver do
|
9
|
+
Given { ReceivableToken.store = {} }
|
10
|
+
Given(:anchor) { Anchor.from 'the rain in spain' }
|
11
|
+
Given(:generator) { Generator.new anchor }
|
12
|
+
Given(:store) { {} }
|
13
|
+
Given(:receiver) { Receiver.new }
|
14
|
+
|
15
|
+
describe '#initialize_chain' do
|
16
|
+
When { receiver.initialize_chain anchor }
|
17
|
+
Then { expect(ReceivableToken.keys.count).to eq(10) }
|
18
|
+
Then { expect(ReceivableToken.map{|t| t.sequence}).to eq((0..9).to_a) }
|
19
|
+
Then { expect(ReceivableToken[generator.generate(5).last]).to_not be_nil }
|
20
|
+
end
|
21
|
+
|
22
|
+
describe '#validate!' do
|
23
|
+
# Return states:
|
24
|
+
# ✓ success (received token zero)
|
25
|
+
# ✓ success, warn token out of sequence
|
26
|
+
# ✓ fail: unknown token
|
27
|
+
# ✓ fail: previously submitted token
|
28
|
+
context 'totally bogus token' do
|
29
|
+
Given(:token) { 'ABC!@#' }
|
30
|
+
Then { expect{receiver.validate!(token)}.to raise_error(UnknownTokenError, "Invalid token") }
|
31
|
+
end
|
32
|
+
|
33
|
+
context 'valid token' do
|
34
|
+
Given { receiver.initialize_chain anchor }
|
35
|
+
|
36
|
+
context 'token zero' do
|
37
|
+
Given(:token) { generator.generate }
|
38
|
+
When(:response) { receiver.validate!(token) }
|
39
|
+
Then { expect(response[:result]).to eq('success') }
|
40
|
+
end
|
41
|
+
|
42
|
+
context 'out of sequence' do
|
43
|
+
Given(:skipping_to) { 3 }
|
44
|
+
When(:token) { generator.generate(skipping_to).last }
|
45
|
+
When(:response) { receiver.validate!(token) }
|
46
|
+
describe 'response' do
|
47
|
+
context 'when skipping ahead' do
|
48
|
+
Then { expect(response[:result]).to eq('success') }
|
49
|
+
Then { expect(response[:warning]).to match(/out of sequence/) }
|
50
|
+
end
|
51
|
+
context 'when submitting a previously validated token' do
|
52
|
+
Then { expect{receiver.validate!(token)}.to raise_error(/previously submitted/) }
|
53
|
+
end
|
54
|
+
end
|
55
|
+
describe 'receivable tokens' do
|
56
|
+
describe 'old/new token zero' do
|
57
|
+
Then { ReceivableToken[token].sequence == -1 }
|
58
|
+
Then { ReceivableToken[generator.generate].sequence == 0 }
|
59
|
+
end
|
60
|
+
describe 'new tokens created' do
|
61
|
+
Given(:new_last_token) { Generator.new(anchor).generate(13).last }
|
62
|
+
Given(:expected_sequences) { (-3..9).to_a }
|
63
|
+
Then { expect(ReceivableToken[new_last_token]).to be_available }
|
64
|
+
Then { ReceivableToken[new_last_token].sequence == 9 }
|
65
|
+
Then { expect(ReceivableToken.map{|rt| rt.sequence}).to eq(expected_sequences) }
|
66
|
+
end
|
67
|
+
describe 'skipped tokens' do
|
68
|
+
Given(:skipped_token) { Generator.new(anchor).generate }
|
69
|
+
Then { expect(ReceivableToken[skipped_token]).to_not be_available }
|
70
|
+
end
|
71
|
+
describe 'more than 10 tokens consumed' do
|
72
|
+
Given(:first_token) { Generator.new(anchor).generate }
|
73
|
+
Given(:second_token) { Generator.new(anchor).generate(2).last }
|
74
|
+
Given(:skipping_to) { 10 }
|
75
|
+
When { receiver.validate! generator.generate }
|
76
|
+
Then { expect(ReceivableToken[second_token]).to_not be_available }
|
77
|
+
Then { expect(ReceivableToken[first_token]).to be_nil }
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
describe 'working repeatedly through a whole bunch of iterations' do
|
83
|
+
Given { receiver.initialize_chain anchor }
|
84
|
+
Given(:client_generator) { Generator.new anchor }
|
85
|
+
Then do
|
86
|
+
(0..50).map do
|
87
|
+
skipping_to = 1 + rand(10)
|
88
|
+
token = [client_generator.generate(skipping_to)].flatten.last
|
89
|
+
response = receiver.validate!(token)
|
90
|
+
response[:result] == 'success' && ReceivableToken.keys.count <= 20
|
91
|
+
end.all? { |result| result == true }
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
|
98
|
+
end #validate!
|
99
|
+
|
100
|
+
end
|
101
|
+
end
|
data/token_chain.gemspec
CHANGED
@@ -18,6 +18,7 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
|
+
spec.add_dependency "squares", ">= 0.3.0"
|
21
22
|
spec.add_development_dependency "bundler", "~> 1.5"
|
22
23
|
spec.add_development_dependency "rake"
|
23
24
|
spec.add_development_dependency "pry"
|
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: token_chain
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Joel Helbling
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-02-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: squares
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.3.0
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.3.0
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: bundler
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -96,11 +110,17 @@ files:
|
|
96
110
|
- Rakefile
|
97
111
|
- lib/token_chain.rb
|
98
112
|
- lib/token_chain/anchor.rb
|
113
|
+
- lib/token_chain/emittable_token.rb
|
114
|
+
- lib/token_chain/emitter.rb
|
99
115
|
- lib/token_chain/generator.rb
|
116
|
+
- lib/token_chain/receivable_token.rb
|
117
|
+
- lib/token_chain/receiver.rb
|
100
118
|
- lib/token_chain/version.rb
|
101
119
|
- spec/spec_helper.rb
|
102
120
|
- spec/token_chain/anchor_spec.rb
|
121
|
+
- spec/token_chain/emitter_spec.rb
|
103
122
|
- spec/token_chain/generator_spec.rb
|
123
|
+
- spec/token_chain/receiver_spec.rb
|
104
124
|
- spec/token_chain_spec.rb
|
105
125
|
- token_chain.gemspec
|
106
126
|
homepage: http://github.com/joelhelbling/token_chain
|
@@ -130,5 +150,7 @@ summary: Generates a deterministic chain of tokens from a passphrase.
|
|
130
150
|
test_files:
|
131
151
|
- spec/spec_helper.rb
|
132
152
|
- spec/token_chain/anchor_spec.rb
|
153
|
+
- spec/token_chain/emitter_spec.rb
|
133
154
|
- spec/token_chain/generator_spec.rb
|
155
|
+
- spec/token_chain/receiver_spec.rb
|
134
156
|
- spec/token_chain_spec.rb
|