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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 877b8521a7597a6aedfb24724b85549c49de3a63
4
- data.tar.gz: 6959f880395bb00ee5dc938adf69161b9cd8e8d6
3
+ metadata.gz: 479d3adff4faf57359d9ca967bda2bbac3042a75
4
+ data.tar.gz: 4f42d31273559187b440141e02c688b1fe245dbb
5
5
  SHA512:
6
- metadata.gz: b43d61f54a8bb43a509d0dea506e22895d2b4093099a3151373c4d279fb16412f79b896aba3378e208b9692305c7316b6b3ae78a87fffeb4af80d3cca4bd4349
7
- data.tar.gz: a11b081c7cf5209c1977d09288cb146ef3a597c0f67d40c228c2d3e4d313061e2545af53fee2963097bd2b407c7f82dede6b552b70c57b9d2181a2f34ebeaf93
6
+ metadata.gz: 692274eb0d93aa194d4f8a3d2aa612ead67026984b7a70b31f9c3960c5204b2495888045b5edef08fe989e33a6be33cfaf1acc4ad909aba64333e10737fa6582
7
+ data.tar.gz: 6638b86a0eb1ddbb8cb32d68c7b3e87610f242fb5e1c0c962b3e55b53b957036dc844c8ab8cd6677a896806bb15f6e4e900568b8b6ac36df58c0855ef9d7b10e
@@ -0,0 +1,9 @@
1
+ require 'squares'
2
+
3
+ module TokenChain
4
+ class EmittableToken < Squares::Base
5
+ property :last_consumed_token
6
+
7
+ alias_method :anchor, :id
8
+ end
9
+ end
@@ -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,14 @@
1
+ require 'squares'
2
+
3
+ module TokenChain
4
+ class ReceivableToken < Squares::Base
5
+ property :anchor
6
+ property :sequence, default: 0
7
+
8
+ alias_method :token, :id
9
+
10
+ def available?
11
+ sequence >= 0
12
+ end
13
+ end
14
+ 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
+
@@ -1,3 +1,3 @@
1
1
  module TokenChain
2
- VERSION = "0.0.2"
2
+ VERSION = "0.1.0"
3
3
  end
data/spec/spec_helper.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
2
2
 
3
3
  require 'rspec/given'
4
+ require 'pry'
4
5
  require 'digest/sha2'
5
6
  require 'token_chain'
6
7
 
@@ -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.2
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-01-06 00:00:00.000000000 Z
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