keratin-authn 0.1.3 → 0.2.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: 75d95ac6c9ef32e84ecd20f8f2431c8bc95557f0
4
- data.tar.gz: fbd476314716da9dc09efa0c8ad02173902e9c5c
3
+ metadata.gz: 5bc3dc3a5993e496aa7d4a27460d799b4e3ea600
4
+ data.tar.gz: 30266ffdffaa188785ce930f0f470a5cde684746
5
5
  SHA512:
6
- metadata.gz: a051bb2f34386baccab8d922a06fb5e174f61d5030001097872e2d6169092696726e4054e973bdba27e7cc7b36e74481f9ef418455520a30195a48027c3ecfb7
7
- data.tar.gz: ed7623eea06f6736fdc4e828200adfe5c78f76a3a59fd9fd440889a2a48fc84073155da385bba01951ff0161bae983c48609efaea868db3cf8041a171856bd2a
6
+ metadata.gz: 77ac60816689bd685f7ba0e6de2122bcd6e372bccee8b6bb1e1ef6bf660278fd3617ca7e5fe57debbe792db44baa030fc55d721681b535bb2e0de6ad435604ad
7
+ data.tar.gz: 0814a5099a9b4d7c246c9a9b3dabe05e8d234c6fc86f6022cfd3ba54a3f76df521dad46a1130ef4e422a1e1125c523543d2d22efca6d6067dba15f39589db889
data/README.md CHANGED
@@ -25,53 +25,77 @@ Keratin::AuthN.config.tap do |config|
25
25
 
26
26
  # The domain of your application
27
27
  config.audience = 'myapp.com'
28
+
29
+ # HTTP basic auth for using AuthN's private endpoints
30
+ config.username = 'secret'
31
+ config.password = 'secret'
28
32
  end
29
33
  ```
30
34
 
31
- Use `Keratin::AuthN.subject_from(params[:id_token])` to validate tokens and fetch an `account_id` during signup, login, and session verification.
35
+ ### Reading the Session
32
36
 
33
- Send users to `Keratin::AuthN.logout_url(return_to: some_path)` to log them out from the AuthN server.
37
+ Use `Keratin::AuthN.subject_from(params[:authn])` to fetch an `account_id` from the session if and
38
+ only if the session is valid.
34
39
 
35
- ### Example: Signup
40
+ ### Logging Out
41
+
42
+ Send users to `Keratin::AuthN.logout_url(return_to: some_path)` to log them out from the AuthN
43
+ server. If you use [keratin/authn-js](https://github.com/keratin/authn-js), you might prefer the
44
+ logout functionality there as it can also take care of deleting the cookie.
45
+
46
+ ### Modifying Accounts
47
+
48
+ * `Keratin.authn.lock(account_id)`: will lock an account, revoking all sessions (when they time out)
49
+ and disallowing any new logins. Intended for user moderation actions.
50
+ * `Keratin.authn.unlock(account_id)`: will unlock an account, restoring normal functionality.
51
+ * `Keratin.authn.archive(account_id)`: will wipe all personal information, including username and
52
+ password. Intended for user deletion routine.
53
+
54
+ ### Example: Sessions
55
+
56
+ You should store the token in a cookie (the [keratin/authn-js](https://github.com/keratin/authn-js)
57
+ integration can do this automatically) and continue using it to verify a logged-in session:
36
58
 
37
59
  ```ruby
38
- class UsersController
39
- def create
40
- @user = User.new(params.require(:user).permit(:name, :email))
41
- @user.account_id = Keratin::AuthN.subject_from(params[:user][:id_token])
60
+ class ApplicationController
61
+ private
42
62
 
43
- # ...
63
+ def logged_in?
64
+ !! current_account_id
65
+ end
66
+
67
+ def current_user
68
+ return @current_user if defined? @current_user
69
+ @current_user = User.find_by_account_id(current_account_id)
70
+ end
71
+
72
+ def current_account_id
73
+ Keratin::AuthN.subject_from(cookies[:authn])
44
74
  end
45
75
  end
46
76
  ```
47
77
 
48
- ### Example: Login
78
+ ### Example: Signup
49
79
 
50
80
  ```ruby
51
- class SessionsController
81
+ class UsersController
52
82
  def create
53
- @user = User.find_by_account_id(Keratin::AuthN.subject_from(cookies[:id_token]))
83
+ @user = User.new(params.require(:user).permit(:name, :email))
84
+ @user.account_id = current_account_id
54
85
 
55
86
  # ...
56
87
  end
57
88
  end
58
89
  ```
59
90
 
60
- ### Example: Sessions
61
-
62
- You should store the token in a cookie and continue using it to verify a logged-in session:
91
+ ### Example: Login
63
92
 
64
93
  ```ruby
65
- class ApplicationController
66
- private
67
-
68
- def logged_in?
69
- !! Keratin::AuthN.subject_from(cookies[:id_token])
70
- end
94
+ class SessionsController
95
+ def create
96
+ @user = current_user
71
97
 
72
- def current_user
73
- return @current_user if defined? @current_user
74
- @current_user = User.find_by_account_id(Keratin::AuthN.subject_from(cookies[:id_token])
98
+ # ...
75
99
  end
76
100
  end
77
101
  ```
data/bin/console CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require "bundler/setup"
4
- require "auth/rb"
4
+ require "keratin/authn"
5
5
 
6
6
  # You can add fixtures and/or initialization code here to make experimenting
7
7
  # with your gem easier. You can also use a different console, if you like.
data/lib/keratin/authn.rb CHANGED
@@ -6,52 +6,60 @@ require_relative 'authn/issuer'
6
6
  require 'lru_redux'
7
7
  require 'json/jwt'
8
8
 
9
- module Keratin::AuthN
10
- class Config
11
- # the domain (host) of the main application.
12
- # e.g. "audience.tech"
13
- attr_accessor :audience
14
-
15
- # the base url of the service handling authentication
16
- # e.g. "https://issuer.tech"
17
- attr_accessor :issuer
18
-
19
- # the path where we can fetch configuration from our issuer.
20
- #
21
- # default: "/configuration"
22
- attr_accessor :configuration_path
23
-
24
- # how long (in seconds) to keep keys in the keychain before refreshing.
25
- # default: 3600
26
- attr_accessor :keychain_ttl
9
+ module Keratin
10
+ def self.authn
11
+ @authn ||= AuthN::Issuer.new(AuthN.config.issuer,
12
+ username: AuthN.config.username,
13
+ password: AuthN.config.password
14
+ )
27
15
  end
28
16
 
29
- def self.config
30
- @config ||= Config.new.tap do |config|
31
- config.configuration_path = '/configuration'
32
- config.keychain_ttl = 3600
17
+ module AuthN
18
+ class Config
19
+ # the domain (host) of the main application.
20
+ # e.g. "audience.tech"
21
+ attr_accessor :audience
22
+
23
+ # the base url of the service handling authentication
24
+ # e.g. "https://issuer.tech"
25
+ attr_accessor :issuer
26
+
27
+ # how long (in seconds) to keep keys in the keychain before refreshing.
28
+ # default: 3600
29
+ attr_accessor :keychain_ttl
30
+
31
+ # the http basic auth username for accessing private endpoints of the authn issuer.
32
+ attr_accessor :username
33
+
34
+ # the http basic auth password for accessing private endpoints of the authn issuer.
35
+ attr_accessor :password
33
36
  end
34
- end
35
37
 
36
- def self.keychain
37
- @keychain ||= LruRedux::TTL::ThreadSafeCache.new(25, config.keychain_ttl)
38
- end
38
+ def self.config
39
+ @config ||= Config.new.tap do |config|
40
+ config.keychain_ttl = 3600
41
+ end
42
+ end
39
43
 
40
- class << self
41
- # safely fetches a subject from the id token after checking relevant claims and
42
- # verifying the signature.
43
- #
44
- # this may involve HTTP requests to fetch the issuer's configuration and JWKs.
45
- def subject_from(id_token)
46
- verifier = IDTokenVerifier.new(id_token, keychain)
47
- verifier.subject if verifier.verified?
44
+ def self.keychain
45
+ @keychain ||= LruRedux::TTL::ThreadSafeCache.new(25, config.keychain_ttl)
48
46
  end
49
47
 
50
- def logout_url(return_to: nil)
51
- query = {redirect_uri: return_to}.to_param if return_to
48
+ class << self
49
+ # safely fetches a subject from the id token after checking relevant claims and
50
+ # verifying the signature.
51
+ #
52
+ # this may involve HTTP requests to fetch the issuer's configuration and JWKs.
53
+ def subject_from(id_token)
54
+ verifier = IDTokenVerifier.new(id_token, keychain)
55
+ verifier.subject if verifier.verified?
56
+ end
52
57
 
53
- "#{config.issuer}/sessions/logout#{?? if query}#{query}"
58
+ def logout_url(return_to: nil)
59
+ query = {redirect_uri: return_to}.to_param if return_to
60
+
61
+ "#{config.issuer}/sessions/logout#{?? if query}#{query}"
62
+ end
54
63
  end
55
64
  end
56
-
57
65
  end
@@ -1,10 +1,18 @@
1
+ require 'keratin/client'
1
2
  require 'net/http'
2
3
 
3
4
  module Keratin::AuthN
4
- class Issuer
5
- def initialize(str)
6
- @uri = str
7
- @config_uri = @uri.chomp('/') + Keratin::AuthN.config.configuration_path
5
+ class Issuer < Keratin::Client
6
+ def lock(account_id)
7
+ patch(path: "/accounts/:account_id/lock").result
8
+ end
9
+
10
+ def unlock(account_id)
11
+ patch(path: "/accounts/:account_id/unlock").result
12
+ end
13
+
14
+ def archive(account_id)
15
+ delete(path: "/accounts/:account_id").result
8
16
  end
9
17
 
10
18
  def signing_key
@@ -12,16 +20,12 @@ module Keratin::AuthN
12
20
  end
13
21
 
14
22
  def configuration
15
- @configuration ||= JSON.parse(
16
- Net::HTTP.get(URI.parse(@config_uri))
17
- )
23
+ @configuration ||= get(path: '/configuration').data
18
24
  end
19
25
 
20
26
  def keys
21
27
  @keys ||= JSON::JWK::Set.new(
22
- JSON.parse(
23
- Net::HTTP.get(URI.parse(configuration['jwks_uri']))
24
- )
28
+ get(path: URI.parse(configuration['jwks_uri']).path).data
25
29
  )
26
30
  end
27
31
  end
@@ -26,7 +26,7 @@ module Keratin::AuthN
26
26
  # stubs the endpoints necessary to validate a signed JWT
27
27
  private def stub_auth_server(issuer: Keratin::AuthN.config.issuer, keypair: jws_keypair)
28
28
  Keratin::AuthN.keychain.clear
29
- stub_request(:get, "#{issuer}#{Keratin::AuthN.config.configuration_path}").to_return(
29
+ stub_request(:get, "#{issuer}/configuration").to_return(
30
30
  status: 200,
31
31
  body: {'jwks_uri' => "#{issuer}/jwks"}.to_json
32
32
  )
@@ -1,5 +1,5 @@
1
1
  module Keratin
2
2
  module AuthN
3
- VERSION = "0.1.3"
3
+ VERSION = "0.2.0"
4
4
  end
5
5
  end
@@ -0,0 +1,78 @@
1
+ module Keratin
2
+ class Error < StandardError; end
3
+
4
+ class ServiceResult
5
+ attr_reader :data
6
+ attr_reader :result
7
+
8
+ def initialize(data)
9
+ @data = data
10
+ @result = data['result']
11
+ end
12
+ end
13
+
14
+ class ClientError < Keratin::Error
15
+ attr_reader :errors
16
+
17
+ def initialize(errors)
18
+ @errors = errors.map{|e| [e['field'], e['message']] }
19
+ .group_by(&:first)
20
+ .map{|k, v| [k, v.map(&:last)] }
21
+ .to_h
22
+
23
+ super(@errors.inspect)
24
+ end
25
+ end
26
+
27
+ class ServiceError < Keratin::Error
28
+ end
29
+
30
+ class Client
31
+
32
+ attr_reader :base
33
+
34
+ def initialize(base_url, username: nil, password: nil)
35
+ @base = base_url.chomp('/')
36
+ @auth = [username, password] if username && password
37
+ end
38
+
39
+ private def get(**opts)
40
+ submit(Net::HTTP::Get, **opts)
41
+ end
42
+
43
+ private def patch(**opts)
44
+ submit(Net::HTTP::Patch, **opts)
45
+ end
46
+
47
+ private def delete(**opts)
48
+ submit(Net::HTTP::Delete, **opts)
49
+ end
50
+
51
+ private def submit(request_klass, path:)
52
+ uri = URI.parse("#{base}#{path}")
53
+
54
+ request = request_klass.new(uri)
55
+ request.basic_auth(*@auth) if @auth
56
+
57
+ Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') do |http|
58
+ http.open_timeout = 0.5
59
+ http.read_timeout = 2.0
60
+
61
+ case response = http.request(request)
62
+ when Net::HTTPSuccess
63
+ return ServiceResult.new(JSON.parse(response.body))
64
+ when Net::HTTPRedirection
65
+ return ServiceResult.new('result' => {
66
+ 'location' => response['Location']
67
+ })
68
+ when Net::HTTPClientError
69
+ raise ClientError.new(JSON.parse(response.body)['errors'])
70
+ when Net::HTTPServerError
71
+ raise ServiceError.new(response.body)
72
+ end
73
+ end
74
+ rescue Net::OpenTimeout, Net::ReadTimeout => e
75
+ raise ServiceError.new(e.message)
76
+ end
77
+ end
78
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: keratin-authn
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lance Ivy
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-11-28 00:00:00.000000000 Z
11
+ date: 2016-12-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: json-jwt
@@ -132,6 +132,7 @@ files:
132
132
  - lib/keratin/authn/test/helpers.rb
133
133
  - lib/keratin/authn/testing.rb
134
134
  - lib/keratin/authn/version.rb
135
+ - lib/keratin/client.rb
135
136
  homepage:
136
137
  licenses:
137
138
  - LGPL-3.0