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 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