epb-auth-tools 1.0.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 782e5a8d8575cc614987e76c1475c6e3bb21126a5f1d69d35534151d9812a861
4
+ data.tar.gz: 3cb5ed246374496bc8170503bc77c0557c80cb7b6d8f44e57c09548555b9ef6b
5
+ SHA512:
6
+ metadata.gz: dc06e83c6313dccbae073da2adbc20d694279b7a99ae7e4232b4dad54ca9fa07411e6bc78cfa9c23a3de78ec8285df89da31d7b3c585eafc5312bc44fe7ef85e
7
+ data.tar.gz: e9899d2035a1955efb170ad2379207d9b3fdd69126f8f7a2d5655719cdb712a163987756332c7172e84234f2cde1c59957b82f210659ebb3d741aea640e1718a
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Auth
4
+ require_relative 'errors'
5
+ require_relative 'http_client'
6
+ require_relative 'token'
7
+ require_relative 'token_processor'
8
+
9
+ require_relative 'sinatra/conditional'
10
+ end
data/lib/errors.rb ADDED
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Auth
4
+ module Errors
5
+ class Error < RuntimeError; end
6
+
7
+ class Processor < Auth::Errors::Error; end
8
+ class ProcessorHasNoSecret < Auth::Errors::Error; end
9
+ class ProcessorHasNoIssuer < Auth::Errors::Error; end
10
+
11
+ class Token < Auth::Errors::Error; end
12
+
13
+ class TokenMissing < Auth::Errors::Token; end
14
+ class TokenPayloadError < Auth::Errors::Token; end
15
+ class TokenExpired < Auth::Errors::TokenPayloadError; end
16
+ class TokenNotYetValid < Auth::Errors::TokenPayloadError; end
17
+ class TokenHasNoIssuer < Auth::Errors::TokenPayloadError; end
18
+ class TokenHasNoSubject < Auth::Errors::TokenPayloadError; end
19
+ class TokenHasNoIssuedAt < Auth::Errors::TokenPayloadError; end
20
+ class TokenHasNoExpiry < Auth::Errors::TokenPayloadError; end
21
+ class TokenIssuerIncorrect < Auth::Errors::TokenPayloadError; end
22
+
23
+ class TokenDecodeError < Auth::Errors::Token; end
24
+ class TokenTamperDetected < Auth::Errors::TokenDecodeError; end
25
+
26
+ class Client < Auth::Errors::Error; end
27
+
28
+ class ClientHasNoAuthServer < Auth::Errors::Client; end
29
+ class ClientHasNoClientId < Auth::Errors::Client; end
30
+ class ClientHasNoClientSecret < Auth::Errors::Client; end
31
+ class ClientHasNoBaseUri < Auth::Errors::Client; end
32
+
33
+ class Network < Auth::Errors::Error; end
34
+ class NetworkConnectionFailed < Auth::Errors::Network; end
35
+ end
36
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'oauth2'
4
+
5
+ module Auth
6
+ class HttpClient
7
+ attr_reader :authenticated_client
8
+
9
+ def initialize(
10
+ client_id = nil,
11
+ client_secret = nil,
12
+ auth_server = nil,
13
+ base_uri = nil,
14
+ auth_client = OAuth2::Client
15
+ )
16
+ raise Auth::Errors::ClientHasNoClientId if client_id.nil?
17
+ raise Auth::Errors::ClientHasNoClientSecret if client_secret.nil?
18
+ raise Auth::Errors::ClientHasNoAuthServer if auth_server.nil?
19
+ raise Auth::Errors::ClientHasNoBaseUri if base_uri.nil?
20
+
21
+ @authenticated_client = nil
22
+
23
+ site_url = URI.parse(auth_server)
24
+ token_url = site_url.path + '/oauth/token'
25
+ authorisation_url = site_url.path + '/oauth/token'
26
+ site_url = "#{site_url.scheme}://#{site_url.host}:#{site_url.port}"
27
+
28
+
29
+ @base_uri = base_uri
30
+ @client =
31
+ auth_client.new client_id,
32
+ client_secret,
33
+ site: site_url,
34
+ token_url: token_url,
35
+ authorisation_url: authorisation_url,
36
+ raise_errors: false
37
+ end
38
+
39
+ def refresh
40
+ @authenticated_client = @client.client_credentials.get_token
41
+ end
42
+
43
+ def refresh?
44
+ @authenticated_client.nil? || @authenticated_client.expired?
45
+ end
46
+
47
+ def self.delegate(*methods)
48
+ methods.each do |method_name|
49
+ define_method(method_name) do |*args, &block|
50
+ request method_name, *args, &block
51
+ end
52
+ end
53
+ end
54
+
55
+ delegate :get, :post, :put
56
+
57
+ def request(method_name, *args, &block)
58
+ refresh? && refresh
59
+
60
+ args[0] = @base_uri + args[0]
61
+
62
+ if @authenticated_client.respond_to? method_name
63
+ response = @authenticated_client.send method_name, *args, &block
64
+ if response.body.is_a?(::Hash) &&
65
+ response.body[:error] == 'Auth::Errors::TokenExpired'
66
+ refresh
67
+ response = @authenticated_client.send method_name, *args, &block
68
+ end
69
+
70
+ response
71
+ end
72
+ rescue Faraday::ConnectionFailed
73
+ raise Auth::Errors::NetworkConnectionFailed
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Auth
4
+ module Sinatra
5
+ class Conditional
6
+ def self.process_request(env)
7
+ jwt_token = env.fetch('HTTP_AUTHORIZATION', '').slice(7..-1)
8
+ processor =
9
+ Auth::TokenProcessor.new ENV['JWT_SECRET'], ENV['JWT_ISSUER']
10
+ processor.process jwt_token
11
+ end
12
+ end
13
+ end
14
+ end
data/lib/token.rb ADDED
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Auth
4
+ class Token
5
+ def initialize(payload)
6
+ @payload = JSON.parse payload.to_json
7
+ validate_payload
8
+ end
9
+
10
+ def scope?(scope)
11
+ @payload['scopes'].include? scope
12
+ end
13
+
14
+ def scopes?(scopes)
15
+ scopes.all? { |scope| @payload['scopes'].include? scope }
16
+ end
17
+
18
+ def supplemental(property = nil)
19
+ unless property.nil? || @payload['sup'][property].nil?
20
+ return @payload['sup'][property]
21
+ end
22
+
23
+ @payload['sup']
24
+ end
25
+
26
+ def encode(jwt_secret)
27
+ JWT.encode @payload, jwt_secret, 'HS256'
28
+ end
29
+
30
+ private
31
+
32
+ def validate_payload
33
+ raise Auth::Errors::TokenHasNoIssuer unless @payload.key?('iss')
34
+ raise Auth::Errors::TokenHasNoIssuedAt unless @payload.key?('iat')
35
+ unless @payload['iat'] <= Time.now.to_i
36
+ raise Auth::Errors::TokenNotYetValid
37
+ end
38
+ raise Auth::Errors::TokenHasNoSubject unless @payload.key?('sub')
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'jwt'
4
+
5
+ module Auth
6
+ class TokenProcessor
7
+ def initialize(jwt_secret = nil, jwt_issuer = nil)
8
+ raise Auth::Errors::ProcessorHasNoSecret if jwt_secret.nil?
9
+ raise Auth::Errors::ProcessorHasNoIssuer if jwt_issuer.nil?
10
+
11
+ @jwt_secret = jwt_secret
12
+ @jwt_issuer = jwt_issuer
13
+ end
14
+
15
+ def process(token = nil)
16
+ raise Auth::Errors::TokenMissing if token.nil?
17
+
18
+ payload, _header = jwt_process token
19
+
20
+ raise Auth::Errors::TokenHasNoIssuer unless payload.key?('iss')
21
+ unless payload['iss'] == @jwt_issuer
22
+ raise Auth::Errors::TokenIssuerIncorrect
23
+ end
24
+
25
+ Auth::Token.new payload
26
+ end
27
+
28
+ private
29
+
30
+ def jwt_process(token)
31
+ options = { algorithm: 'HS256', iss: @jwt_issuer }
32
+
33
+ JWT.decode token, @jwt_secret, true, options
34
+ rescue JWT::ExpiredSignature
35
+ raise Auth::Errors::TokenExpired
36
+ rescue JWT::VerificationError
37
+ raise Auth::Errors::TokenTamperDetected
38
+ rescue JWT::DecodeError
39
+ raise Auth::Errors::TokenDecodeError
40
+ end
41
+ end
42
+ end
metadata ADDED
@@ -0,0 +1,78 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: epb-auth-tools
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Lawrence Goldstien <lawrence.goldstien@madetech.com>
8
+ - Yusuf Sheikh <yusuf@madetech.com>
9
+ - Jaseera <jaseera@madetech.com>
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2020-03-11 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: jwt
17
+ requirement: !ruby/object:Gem::Requirement
18
+ requirements:
19
+ - - "~>"
20
+ - !ruby/object:Gem::Version
21
+ version: '2.2'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - "~>"
27
+ - !ruby/object:Gem::Version
28
+ version: '2.2'
29
+ - !ruby/object:Gem::Dependency
30
+ name: oauth2
31
+ requirement: !ruby/object:Gem::Requirement
32
+ requirements:
33
+ - - "~>"
34
+ - !ruby/object:Gem::Version
35
+ version: '1.4'
36
+ type: :runtime
37
+ prerelease: false
38
+ version_requirements: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - "~>"
41
+ - !ruby/object:Gem::Version
42
+ version: '1.4'
43
+ description:
44
+ email:
45
+ executables: []
46
+ extensions: []
47
+ extra_rdoc_files: []
48
+ files:
49
+ - lib/epb_auth_tools.rb
50
+ - lib/errors.rb
51
+ - lib/http_client.rb
52
+ - lib/sinatra/conditional.rb
53
+ - lib/token.rb
54
+ - lib/token_processor.rb
55
+ homepage: https://github.com/communitiesuk/epb-auth-tools
56
+ licenses:
57
+ - MIT
58
+ metadata: {}
59
+ post_install_message:
60
+ rdoc_options: []
61
+ require_paths:
62
+ - lib
63
+ required_ruby_version: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ required_rubygems_version: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: '0'
73
+ requirements: []
74
+ rubygems_version: 3.0.6
75
+ signing_key:
76
+ specification_version: 4
77
+ summary: Tools for authentication and authorisation with JWTs and OAuth
78
+ test_files: []