token_chain 0.0.2 → 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 +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
|