jm81auth 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 +7 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +3 -0
- data/Rakefile +18 -0
- data/lib/jm81auth/configuration.rb +9 -0
- data/lib/jm81auth/models/auth_method.rb +32 -0
- data/lib/jm81auth/models/auth_token.rb +101 -0
- data/lib/jm81auth/models/user.rb +56 -0
- data/lib/jm81auth/oauth/base.rb +71 -0
- data/lib/jm81auth/oauth/github.rb +13 -0
- data/lib/jm81auth/version.rb +3 -0
- data/lib/jm81auth.rb +23 -0
- data/spec/factories/auth_methods.rb +9 -0
- data/spec/factories/auth_tokens.rb +9 -0
- data/spec/factories/users.rb +5 -0
- data/spec/jm81auth/models/auth_method_spec.rb +75 -0
- data/spec/jm81auth/models/auth_token_spec.rb +193 -0
- data/spec/jm81auth/models/user_spec.rb +263 -0
- data/spec/jm81auth/oauth/base_spec.rb +171 -0
- data/spec/spec_helper.rb +62 -0
- metadata +169 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 5054fd53d58c6211c8000f49769e161bcba9482e
|
4
|
+
data.tar.gz: 5ae3e3f048318ff1bd5859695067ab805e255386
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: dfe794d0b2d0bf1d226351ab9c8fb424e939805a1e414617abb591f3d78cd14eb6483c8a634fd9b8a074892d64ef27707ac1515e20ce0806a86064679ae55a52
|
7
|
+
data.tar.gz: 0053aa9455f3a95bdeeaa919909255d02ecc58d660352f3c2796323db5894a4ee78059e4553af1a06e4e36f1079a924dc3b314cddc464f8e3c41d9f23bb5ac0e
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2017 YOURNAME
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
begin
|
2
|
+
require 'bundler/setup'
|
3
|
+
rescue LoadError
|
4
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'rspec/core/rake_task'
|
8
|
+
RSpec::Core::RakeTask.new(:spec)
|
9
|
+
|
10
|
+
require 'rdoc/task'
|
11
|
+
|
12
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
13
|
+
rdoc.rdoc_dir = 'rdoc'
|
14
|
+
rdoc.title = 'Jm81auth'
|
15
|
+
rdoc.options << '--line-numbers'
|
16
|
+
rdoc.rdoc_files.include('README.rdoc')
|
17
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
18
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Jm81auth
|
2
|
+
module Models
|
3
|
+
module AuthMethod
|
4
|
+
def self.included(base)
|
5
|
+
base.extend ClassMethods
|
6
|
+
|
7
|
+
base.class_eval do
|
8
|
+
many_to_one :user
|
9
|
+
one_to_many :auth_tokens
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# Create AuthToken, setting user and last_used_at
|
14
|
+
#
|
15
|
+
# @return [AuthToken]
|
16
|
+
def create_token
|
17
|
+
add_auth_token user: user, last_used_at: Time.now.utc
|
18
|
+
end
|
19
|
+
|
20
|
+
module ClassMethods
|
21
|
+
# Get an AuthMethod using provider data conditions (#provider_name and
|
22
|
+
# #provider_id).
|
23
|
+
#
|
24
|
+
# @param provider_data [Hash]
|
25
|
+
# @return [AuthMethod]
|
26
|
+
def by_provider_data provider_data
|
27
|
+
where(provider_data).first
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
module Jm81auth
|
2
|
+
module Models
|
3
|
+
module AuthToken
|
4
|
+
def self.included(base)
|
5
|
+
base.extend ClassMethods
|
6
|
+
|
7
|
+
base.class_eval do
|
8
|
+
plugin :timestamps
|
9
|
+
|
10
|
+
many_to_one :auth_method
|
11
|
+
many_to_one :user
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class DecodeError < StandardError
|
16
|
+
end
|
17
|
+
|
18
|
+
# Set #closed_at. Called, for example, when logging out.
|
19
|
+
def close!
|
20
|
+
self.update(closed_at: Time.now) unless self.closed_at
|
21
|
+
end
|
22
|
+
|
23
|
+
# @return [String]
|
24
|
+
# { auth_token_id: self.id } encoded via JWT for passing to client.
|
25
|
+
def encoded
|
26
|
+
self.class.encode auth_token_id: self.id
|
27
|
+
end
|
28
|
+
|
29
|
+
# @return [Boolean] Is this token expired?
|
30
|
+
def expired?
|
31
|
+
!open?
|
32
|
+
end
|
33
|
+
|
34
|
+
# @return [Boolean] True if token is not expired or closed.
|
35
|
+
def open?
|
36
|
+
!(last_used_at.nil?) && !closed_at && Time.now <= expires_at
|
37
|
+
end
|
38
|
+
|
39
|
+
# @return [DateTime] Time when this token expires.
|
40
|
+
def expires_at
|
41
|
+
last_used_at + self.class.config.expires_seconds
|
42
|
+
end
|
43
|
+
|
44
|
+
module ClassMethods
|
45
|
+
|
46
|
+
# Decode a JWT token and get AuthToken based on stored ID.
|
47
|
+
#
|
48
|
+
# @see #encoded
|
49
|
+
# @param token [String] JWT encoded hash with AuthToken#id
|
50
|
+
# @raise [DecodeError] auth_token_id is missing or no AuthToken found.
|
51
|
+
# @return [AuthToken]
|
52
|
+
def decode token
|
53
|
+
payload = JWT.decode(
|
54
|
+
token, config.jwt_secret, config.jwt_algorithm
|
55
|
+
).first
|
56
|
+
|
57
|
+
auth_token = self[payload['auth_token_id']]
|
58
|
+
|
59
|
+
if payload['auth_token_id'].nil?
|
60
|
+
raise DecodeError, "auth_token_id missing: #{payload}"
|
61
|
+
elsif auth_token.nil?
|
62
|
+
raise DecodeError, "auth_token_id not found: #{payload}"
|
63
|
+
end
|
64
|
+
|
65
|
+
auth_token
|
66
|
+
end
|
67
|
+
|
68
|
+
# Encode a value using jwt_secret and jwt_algorithm.
|
69
|
+
#
|
70
|
+
# @param value [Hash, Array]
|
71
|
+
# @return [String] Encoded value
|
72
|
+
def encode value
|
73
|
+
JWT.encode value, config.jwt_secret, config.jwt_algorithm
|
74
|
+
end
|
75
|
+
|
76
|
+
# Decode a JWT token and get AuthToken based on stored ID. If an open
|
77
|
+
# AuthToken is found, update its last_used_at value.
|
78
|
+
#
|
79
|
+
# @see #decode
|
80
|
+
# @param token [String] JWT encoded hash with AuthToken#id
|
81
|
+
# @raise [DecodeError] auth_token_id is missing or no AuthToken found.
|
82
|
+
# @return [AuthToken]
|
83
|
+
def use token
|
84
|
+
auth_token = decode token
|
85
|
+
|
86
|
+
if auth_token && !auth_token.expired?
|
87
|
+
auth_token.update(last_used_at: Time.now)
|
88
|
+
auth_token
|
89
|
+
else
|
90
|
+
nil
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# @return [Configuration]
|
95
|
+
def config
|
96
|
+
Jm81auth.config
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Jm81auth
|
2
|
+
module Models
|
3
|
+
module User
|
4
|
+
EMAIL_REGEX = /.@./
|
5
|
+
|
6
|
+
def self.included(base)
|
7
|
+
base.extend ClassMethods
|
8
|
+
|
9
|
+
base.class_eval do
|
10
|
+
one_to_many :auth_methods
|
11
|
+
one_to_many :auth_tokens
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
module ClassMethods
|
16
|
+
# Find user by email address. Returns nil if the email address is not
|
17
|
+
# valid (for a minimal version of valid)
|
18
|
+
#
|
19
|
+
# @param email [~to_s] Email Address
|
20
|
+
# @return [User, nil]
|
21
|
+
def find_by_email email
|
22
|
+
if email.to_s =~ EMAIL_REGEX
|
23
|
+
where(email: email.to_s.downcase.strip).first
|
24
|
+
else
|
25
|
+
nil
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Login from OAuth.
|
30
|
+
#
|
31
|
+
# First try to find an AuthMethod matching the provider data. If none,
|
32
|
+
# find or create a User based on email, then create an AuthMethod.
|
33
|
+
# Finally, create and return an AuthToken.
|
34
|
+
#
|
35
|
+
# @param oauth [OAuth::Base]
|
36
|
+
# OAuth login object, include #provider_data (Hash with provider_name
|
37
|
+
# and provider_id), #email and #display_name.
|
38
|
+
# @return [AuthToken]
|
39
|
+
def oauth_login oauth
|
40
|
+
method = ::AuthMethod.by_provider_data oauth.provider_data
|
41
|
+
|
42
|
+
if !method
|
43
|
+
user = find_by_email(oauth.email) || create(
|
44
|
+
email: oauth.email.downcase,
|
45
|
+
display_name: oauth.display_name
|
46
|
+
)
|
47
|
+
|
48
|
+
method = user.add_auth_method oauth.provider_data
|
49
|
+
end
|
50
|
+
|
51
|
+
method.create_token
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# Based on satellizer example:
|
2
|
+
# https://github.com/sahat/satellizer/blob/master/examples/server/ruby
|
3
|
+
|
4
|
+
module Jm81auth
|
5
|
+
module OAuth
|
6
|
+
class Base
|
7
|
+
# Setup @params from params param (Har, har). Also, set @access_token,
|
8
|
+
# either from params Hash, or by calling #get_access_token. @params is the
|
9
|
+
# expected params needed by #get_access_token.
|
10
|
+
#
|
11
|
+
# @param params [Hash]
|
12
|
+
# Expected to contain :code, :redirectUri, :clientId, and, optionally,
|
13
|
+
# :access_token
|
14
|
+
def initialize params
|
15
|
+
@params = {
|
16
|
+
code: params[:code],
|
17
|
+
redirect_uri: params[:redirectUri],
|
18
|
+
client_id: params[:clientId],
|
19
|
+
client_secret: Jm81auth.config.client_secrets[provider_name]
|
20
|
+
}
|
21
|
+
|
22
|
+
@access_token = params[:access_token] || get_access_token
|
23
|
+
end
|
24
|
+
|
25
|
+
# @return [Hash] Data returned by accessing data URL.
|
26
|
+
def data
|
27
|
+
@data or get_data
|
28
|
+
end
|
29
|
+
|
30
|
+
# @return [String] Display name (e.g. "Jane Doe") from data.
|
31
|
+
def display_name
|
32
|
+
data['name']
|
33
|
+
end
|
34
|
+
|
35
|
+
# @return [String] Email address from data.
|
36
|
+
def email
|
37
|
+
data['email']
|
38
|
+
end
|
39
|
+
|
40
|
+
# Get data via get request to provider's data URL.
|
41
|
+
#
|
42
|
+
# @return [Hash]
|
43
|
+
def get_data
|
44
|
+
response = client.get(self.class::DATA_URL, access_token: @access_token)
|
45
|
+
@data = JSON.parse(response.body)
|
46
|
+
end
|
47
|
+
|
48
|
+
# @return [String] Provider name, based on class name.
|
49
|
+
def provider_name
|
50
|
+
self.class.name.split('::').last.downcase
|
51
|
+
end
|
52
|
+
|
53
|
+
# @return [String] Provider assigned ID, from data.
|
54
|
+
def provider_id
|
55
|
+
data['id'] || data['sub']
|
56
|
+
end
|
57
|
+
|
58
|
+
# @return [Hash] provider_name and provider_id
|
59
|
+
def provider_data
|
60
|
+
{ provider_name: provider_name, provider_id: provider_id }
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
# @return [HTTPClient]
|
66
|
+
def client
|
67
|
+
@client ||= HTTPClient.new
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Jm81auth
|
2
|
+
module OAuth
|
3
|
+
class Github < Base
|
4
|
+
ACCESS_TOKEN_URL = 'https://github.com/login/oauth/access_token'
|
5
|
+
DATA_URL = 'https://api.github.com/user'
|
6
|
+
|
7
|
+
def get_access_token
|
8
|
+
response = client.post(ACCESS_TOKEN_URL, @params)
|
9
|
+
Rack::Utils.parse_nested_query(response.body)['access_token']
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
data/lib/jm81auth.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'httpclient'
|
2
|
+
require 'jwt'
|
3
|
+
|
4
|
+
module Jm81auth
|
5
|
+
class << self
|
6
|
+
def config &block
|
7
|
+
@config ||= Configuration.new
|
8
|
+
@config.instance_eval(&block) if block_given?
|
9
|
+
@config
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
require 'jm81auth/configuration'
|
15
|
+
|
16
|
+
require 'jm81auth/models/auth_method'
|
17
|
+
require 'jm81auth/models/auth_token'
|
18
|
+
require 'jm81auth/models/user'
|
19
|
+
|
20
|
+
require 'jm81auth/oauth/base'
|
21
|
+
require 'jm81auth/oauth/github'
|
22
|
+
|
23
|
+
require 'jm81auth/version'
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe AuthMethod, type: :model do
|
4
|
+
subject(:auth_method) { FactoryGirl.build(:auth_method) }
|
5
|
+
|
6
|
+
it { is_expected.to be_valid }
|
7
|
+
|
8
|
+
describe '#user' do
|
9
|
+
it 'belongs to a User' do
|
10
|
+
expect(auth_method.user).to be_a(User)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe '#auth_tokens' do
|
15
|
+
before(:each) { auth_method.save }
|
16
|
+
|
17
|
+
let!(:auth_token) do
|
18
|
+
auth_method.add_auth_token(user: auth_method.user, last_used_at: Time.now)
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'has many' do
|
22
|
+
expect(auth_token).to be_a(AuthToken)
|
23
|
+
expect(auth_method.auth_tokens).to eq([auth_token])
|
24
|
+
expect(AuthToken[auth_token.id].auth_method).to eq(auth_method)
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'cascades deletes' do
|
28
|
+
expect { auth_method.destroy }.
|
29
|
+
to raise_error(Sequel::ForeignKeyConstraintViolation)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe '#create_token' do
|
34
|
+
before(:each) { auth_method.save }
|
35
|
+
|
36
|
+
it 'creates an AuthToken, setting user and last_used_at' do
|
37
|
+
token = nil
|
38
|
+
expect { token = auth_method.create_token }.
|
39
|
+
to change(AuthToken, :count).by(1)
|
40
|
+
|
41
|
+
token.reload
|
42
|
+
expect(token.user).to be_a(User)
|
43
|
+
expect(token.user).to eq(auth_method.user)
|
44
|
+
expect(token.last_used_at).to be_a(Time)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe '.by_provider_data' do
|
49
|
+
let!(:auth_methods) do
|
50
|
+
[
|
51
|
+
FactoryGirl.create(
|
52
|
+
:auth_method, provider_name: 'github', provider_id: 2
|
53
|
+
),
|
54
|
+
FactoryGirl.create(
|
55
|
+
:auth_method, provider_name: 'github', provider_id: 4
|
56
|
+
)
|
57
|
+
]
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'gets an AuthMethod from provider_name and provider_id' do
|
61
|
+
expect(
|
62
|
+
AuthMethod.by_provider_data(provider_name: 'github', provider_id: 2)
|
63
|
+
).to eq(auth_methods[0])
|
64
|
+
expect(
|
65
|
+
AuthMethod.by_provider_data(provider_name: 'github', provider_id: '4')
|
66
|
+
).to eq(auth_methods[1])
|
67
|
+
expect(
|
68
|
+
AuthMethod.by_provider_data(provider_name: 'other', provider_id: 2)
|
69
|
+
).to be(nil)
|
70
|
+
expect(
|
71
|
+
AuthMethod.by_provider_data(provider_name: 'github', provider_id: 1)
|
72
|
+
).to be(nil)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,193 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe AuthToken, type: :model do
|
4
|
+
subject(:auth_token) { FactoryGirl.build(:auth_token) }
|
5
|
+
|
6
|
+
it { is_expected.to be_valid }
|
7
|
+
|
8
|
+
describe '#auth_method' do
|
9
|
+
it 'belongs to a AuthMethod' do
|
10
|
+
expect(auth_token.auth_method).to be_a(AuthMethod)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe '#user' do
|
15
|
+
it 'belongs to a User' do
|
16
|
+
expect(auth_token.user).to be_a(User)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe '#close!' do
|
21
|
+
context '#closed_at is not set' do
|
22
|
+
it 'sets #closed_at' do
|
23
|
+
auth_token.save
|
24
|
+
auth_token.close!
|
25
|
+
expect(auth_token.reload.closed_at).to be_a(Time)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
context '#closed_at is set' do
|
30
|
+
it 'does nothing' do
|
31
|
+
auth_token.closed_at = Time.now - 1200
|
32
|
+
auth_token.save
|
33
|
+
auth_token.close!
|
34
|
+
expect(auth_token.reload.closed_at).to be <= Time.now - 1200
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe '#encoded' do
|
40
|
+
before(:each) { auth_token.save }
|
41
|
+
|
42
|
+
it 'returns a String encoding the id' do
|
43
|
+
encoded = auth_token.encoded
|
44
|
+
expect(auth_token.encoded).to be_a(String)
|
45
|
+
decoded = JWT.decode(
|
46
|
+
encoded, Jm81auth.config.jwt_secret, Jm81auth.config.jwt_algorithm
|
47
|
+
)
|
48
|
+
expect(decoded[0]).to eq({'auth_token_id' => auth_token.id})
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe '#expires_at' do
|
53
|
+
it 'is 30 days after last_used_at' do
|
54
|
+
auth_token.last_used_at = Time.parse('2015-08-01 15:00')
|
55
|
+
expect(auth_token.expires_at).to eq(Time.parse('2015-08-31 15:00'))
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe '#open? (#expired? is opposite)' do
|
60
|
+
context '#last_used_at not set' do
|
61
|
+
it 'is false' do
|
62
|
+
auth_token.last_used_at = nil
|
63
|
+
expect(auth_token.open?).to be(false)
|
64
|
+
expect(auth_token.expired?).to be(true)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
context '#closed_at is set' do
|
69
|
+
it 'is false' do
|
70
|
+
auth_token.closed_at = Time.now
|
71
|
+
expect(auth_token.open?).to be(false)
|
72
|
+
expect(auth_token.expired?).to be(true)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
context '#last_used_at is more than 30 days ago' do
|
77
|
+
it 'is false' do
|
78
|
+
auth_token.last_used_at = (Date.today - 30).to_time
|
79
|
+
expect(auth_token.open?).to be(false)
|
80
|
+
expect(auth_token.expired?).to be(true)
|
81
|
+
auth_token.last_used_at = (Date.today - 31).to_time
|
82
|
+
expect(auth_token.open?).to be(false)
|
83
|
+
expect(auth_token.expired?).to be(true)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
context '#last_used_at is less than 30 days ago' do
|
88
|
+
it 'is true' do
|
89
|
+
auth_token.last_used_at = (Date.today - 29).to_time
|
90
|
+
expect(auth_token.open?).to be(true)
|
91
|
+
expect(auth_token.expired?).to be(false)
|
92
|
+
auth_token.last_used_at = Time.now
|
93
|
+
expect(auth_token.open?).to be(true)
|
94
|
+
expect(auth_token.expired?).to be(false)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
describe '.decode' do
|
100
|
+
def encoded id
|
101
|
+
AuthToken.encode auth_token_id: id
|
102
|
+
end
|
103
|
+
|
104
|
+
before(:each) { auth_token.save }
|
105
|
+
|
106
|
+
it 'returns AuthToken based on encoded token' do
|
107
|
+
other = FactoryGirl.create(:auth_token)
|
108
|
+
expect(AuthToken.decode(encoded(auth_token.id))).to eq(auth_token)
|
109
|
+
expect(AuthToken.decode(encoded(auth_token.id))).to_not eq(other)
|
110
|
+
end
|
111
|
+
|
112
|
+
context 'auth_token_id missing from decoded hash' do
|
113
|
+
it 'raises DecodeError' do
|
114
|
+
expect { AuthToken.decode(AuthToken.encode(something: 'else')) }.
|
115
|
+
to raise_error(AuthToken::DecodeError)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
context 'No AuthToken found for auth_token_id from decoded hash' do
|
120
|
+
it 'raises DecodeError' do
|
121
|
+
expect { AuthToken.decode(encoded(-10)) }.
|
122
|
+
to raise_error(AuthToken::DecodeError)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
describe '.encode' do
|
128
|
+
it 'encodes a value using JWT' do
|
129
|
+
encoded = AuthToken.encode({a: 1, b: 2})
|
130
|
+
expect(auth_token.encoded).to be_a(String)
|
131
|
+
decoded = JWT.decode(
|
132
|
+
encoded, Jm81auth.config.jwt_secret, Jm81auth.config.jwt_algorithm
|
133
|
+
)
|
134
|
+
expect(decoded[0]).to eq({'a' => 1, 'b' => 2})
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
describe '.use' do
|
139
|
+
def encoded id
|
140
|
+
AuthToken.encode auth_token_id: id
|
141
|
+
end
|
142
|
+
|
143
|
+
before(:each) do
|
144
|
+
auth_token.save
|
145
|
+
end
|
146
|
+
|
147
|
+
context 'found open AuthToken' do
|
148
|
+
before(:each) do
|
149
|
+
auth_token.last_used_at = Time.now - 3600
|
150
|
+
auth_token.save
|
151
|
+
end
|
152
|
+
|
153
|
+
let!(:other) { FactoryGirl.create(:auth_token) }
|
154
|
+
|
155
|
+
it 'updates last_used_at' do
|
156
|
+
found = AuthToken.use(encoded(auth_token.id))
|
157
|
+
expect(found.last_used_at).to be > Time.now - 600
|
158
|
+
expect(found.last_used_at.to_i).
|
159
|
+
to eq(auth_token.reload.last_used_at.to_i)
|
160
|
+
end
|
161
|
+
|
162
|
+
it 'returns found AuthToken' do
|
163
|
+
expect(AuthToken.use(encoded(auth_token.id))).to be === auth_token
|
164
|
+
expect(AuthToken.use(encoded(auth_token.id))).to_not be === other
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
context 'found expired AuthToken' do
|
169
|
+
before(:each) do
|
170
|
+
auth_token.last_used_at = (Date.today - 40).to_time
|
171
|
+
auth_token.save
|
172
|
+
end
|
173
|
+
|
174
|
+
let!(:last_used_at) { auth_token.last_used_at }
|
175
|
+
|
176
|
+
it 'does not update last_used_at' do
|
177
|
+
AuthToken.use(encoded(auth_token.id))
|
178
|
+
expect(auth_token.reload.last_used_at.to_i).to eq(last_used_at.to_i)
|
179
|
+
end
|
180
|
+
|
181
|
+
it 'returns nil' do
|
182
|
+
expect(AuthToken.use(encoded(auth_token.id))).to be(nil)
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
context 'No AuthToken found for auth_token_id from decoded hash' do
|
187
|
+
it 'raises DecodeError' do
|
188
|
+
expect { AuthToken.use(encoded(-10)) }.
|
189
|
+
to raise_error(AuthToken::DecodeError)
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
@@ -0,0 +1,263 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe User, type: :model do
|
4
|
+
subject(:user) { FactoryGirl.build(:user) }
|
5
|
+
|
6
|
+
it { is_expected.to be_valid }
|
7
|
+
|
8
|
+
describe '#auth_methods' do
|
9
|
+
before(:each) { user.save }
|
10
|
+
|
11
|
+
let!(:auth_method) do
|
12
|
+
user.add_auth_method provider_name: :test, provider_id: 1
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'has many' do
|
16
|
+
expect(auth_method).to be_a(AuthMethod)
|
17
|
+
expect(user.auth_methods).to eq([auth_method])
|
18
|
+
expect(AuthMethod[auth_method.id].user).to eq(user)
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'cascades deletes' do
|
22
|
+
user.destroy
|
23
|
+
expect(AuthMethod[auth_method.id]).to be(nil)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe '#auth_tokens' do
|
28
|
+
before(:each) { user.save }
|
29
|
+
|
30
|
+
let!(:auth_token) do
|
31
|
+
user.add_auth_token(
|
32
|
+
auth_method: FactoryGirl.create(:auth_method), last_used_at: Time.now
|
33
|
+
)
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'has many' do
|
37
|
+
expect(auth_token).to be_a(AuthToken)
|
38
|
+
expect(user.auth_tokens).to eq([auth_token])
|
39
|
+
expect(AuthToken[auth_token.id].user).to eq(user)
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'restricts deletes' do
|
43
|
+
expect { user.destroy }.
|
44
|
+
to raise_error(Sequel::ForeignKeyConstraintViolation)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe '.find_by_email' do
|
49
|
+
let!(:existing) { FactoryGirl.create(:user, email: 'test@example.com') }
|
50
|
+
|
51
|
+
context 'existing User with given email' do
|
52
|
+
it 'gets existing User' do
|
53
|
+
expect(User.find_by_email('test@example.com')).to eq(existing)
|
54
|
+
expect(User.find_by_email(' test@example.com ')).to eq(existing)
|
55
|
+
expect(User.find_by_email('TEST@example.com')).to eq(existing)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
context 'no existing User with given email' do
|
60
|
+
it 'is nil' do
|
61
|
+
expect(User).to receive(:where).twice.and_call_original
|
62
|
+
expect(User.find_by_email('other@example.com')).to be(nil)
|
63
|
+
expect(User.find_by_email('test@example.org')).to be(nil)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
context 'nil email' do
|
68
|
+
it 'is nil' do
|
69
|
+
expect(User).to_not receive(:where)
|
70
|
+
expect(User.find_by_email(nil)).to be(nil)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
context 'invalid email' do
|
75
|
+
it 'is nil' do
|
76
|
+
expect(User).to_not receive(:where)
|
77
|
+
expect(User.find_by_email('')).to be(nil)
|
78
|
+
expect(User.find_by_email('@example.com')).to be(nil)
|
79
|
+
expect(User.find_by_email('test')).to be(nil)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
describe '.oauth_login' do
|
85
|
+
let(:oauth) { double 'Jm81auth::OAuth' }
|
86
|
+
|
87
|
+
let!(:user) do
|
88
|
+
FactoryGirl.create :user, email: 'existing-user@example.com',
|
89
|
+
display_name: 'Existing User'
|
90
|
+
end
|
91
|
+
|
92
|
+
let!(:auth_method) do
|
93
|
+
FactoryGirl.create :auth_method,
|
94
|
+
user: user, provider_name: 'github', provider_id: 5
|
95
|
+
end
|
96
|
+
|
97
|
+
def login
|
98
|
+
@login_token = User.oauth_login oauth
|
99
|
+
@login_user = @login_token.user
|
100
|
+
@login_method = @login_token.auth_method
|
101
|
+
user.reload
|
102
|
+
auth_method.reload
|
103
|
+
end
|
104
|
+
|
105
|
+
context 'existing AuthMethod' do
|
106
|
+
before(:each) do
|
107
|
+
expect(oauth).to_not receive(:email)
|
108
|
+
|
109
|
+
expect(oauth).to receive(:provider_data) do
|
110
|
+
{ provider_name: 'github', provider_id: 5 }
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
it 'uses existing (does not create) User' do
|
115
|
+
expect { login }.to_not change(User, :count)
|
116
|
+
expect(@login_user.id).to eq(user.id)
|
117
|
+
expect(@login_user.email).to eq('existing-user@example.com')
|
118
|
+
expect(@login_user.display_name).to eq('Existing User')
|
119
|
+
end
|
120
|
+
|
121
|
+
it 'uses existing (does not create) AuthMethod' do
|
122
|
+
expect { login }.to_not change(AuthMethod, :count)
|
123
|
+
expect(@login_method.id).to eq(auth_method.id)
|
124
|
+
end
|
125
|
+
|
126
|
+
it 'creates and returns AuthToken' do
|
127
|
+
expect { login }.to change(AuthToken, :count).by(1)
|
128
|
+
expect(@login_token.user.id).to eq(user.id)
|
129
|
+
expect(@login_token.auth_method.id).to eq(auth_method.id)
|
130
|
+
expect(@login_token.last_used_at).to be_a(Time)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
context 'no existing AuthMethod' do
|
135
|
+
before(:each) do
|
136
|
+
expect(oauth).to receive(:provider_data).twice do
|
137
|
+
{ provider_name: 'github', provider_id: 10 }
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
context 'User found with same email' do
|
142
|
+
before(:each) do
|
143
|
+
expect(oauth).to receive(:email) { 'existing-user@example.com' }
|
144
|
+
end
|
145
|
+
|
146
|
+
it 'uses existing (does not create) a new User' do
|
147
|
+
expect { login }.to_not change(User, :count)
|
148
|
+
expect(@login_user.id).to eq(user.id)
|
149
|
+
expect(@login_user.email).to eq('existing-user@example.com')
|
150
|
+
expect(@login_user.display_name).to eq('Existing User')
|
151
|
+
end
|
152
|
+
|
153
|
+
it 'creates a new AuthMethod' do
|
154
|
+
expect { login }.to change(AuthMethod, :count).by(1)
|
155
|
+
expect(auth_method.provider_id).to eq(5)
|
156
|
+
expect(@login_method.id).to_not eq(auth_method.id)
|
157
|
+
expect(@login_method.provider_name).to eq('github')
|
158
|
+
expect(@login_method.provider_id).to eq(10)
|
159
|
+
expect(@login_method.user.id).to eq(user.id)
|
160
|
+
end
|
161
|
+
|
162
|
+
it 'creates and returns AuthToken' do
|
163
|
+
expect { login }.to change(AuthToken, :count).by(1)
|
164
|
+
expect(@login_token.user.id).to eq(user.id)
|
165
|
+
expect(@login_token.auth_method.id).to eq(@login_method.id)
|
166
|
+
expect(@login_token.last_used_at).to be_a(Time)
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
context 'No User found with valid OAuth email' do
|
171
|
+
before(:each) do
|
172
|
+
expect(oauth).to receive(:email).twice { 'new-user@example.com' }
|
173
|
+
expect(oauth).to receive(:display_name) { 'New Name' }
|
174
|
+
end
|
175
|
+
|
176
|
+
it 'creates a new User' do
|
177
|
+
expect { login }.to change(User, :count).by(1)
|
178
|
+
expect(user.display_name).to eq('Existing User')
|
179
|
+
expect(@login_user.email).to eq('new-user@example.com')
|
180
|
+
expect(@login_user.display_name).to eq('New Name')
|
181
|
+
end
|
182
|
+
|
183
|
+
it 'creates a new AuthMethod' do
|
184
|
+
expect { login }.to change(AuthMethod, :count).by(1)
|
185
|
+
expect(auth_method.provider_id).to eq(5)
|
186
|
+
expect(@login_method.id).to_not eq(auth_method.id)
|
187
|
+
expect(@login_method.provider_name).to eq('github')
|
188
|
+
expect(@login_method.provider_id).to eq(10)
|
189
|
+
expect(@login_method.user.id).to eq(@login_user.id)
|
190
|
+
end
|
191
|
+
|
192
|
+
it 'creates and returns AuthToken' do
|
193
|
+
expect { login }.to change(AuthToken, :count).by(1)
|
194
|
+
expect(@login_token.user.id).to eq(@login_user.id)
|
195
|
+
expect(@login_token.auth_method.id).to eq(@login_method.id)
|
196
|
+
expect(@login_token.last_used_at).to be_a(Time)
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
context 'OAuth email is empty' do
|
201
|
+
before(:each) do
|
202
|
+
user.update email: ''
|
203
|
+
expect(oauth).to receive(:email).twice { '' }
|
204
|
+
expect(oauth).to receive(:display_name) { 'New Name' }
|
205
|
+
end
|
206
|
+
|
207
|
+
it 'creates a new User' do
|
208
|
+
expect { login }.to change(User, :count).by(1)
|
209
|
+
expect(user.display_name).to eq('Existing User')
|
210
|
+
expect(@login_user.email).to eq('')
|
211
|
+
expect(@login_user.display_name).to eq('New Name')
|
212
|
+
end
|
213
|
+
|
214
|
+
it 'creates a new AuthMethod' do
|
215
|
+
expect { login }.to change(AuthMethod, :count).by(1)
|
216
|
+
expect(auth_method.provider_id).to eq(5)
|
217
|
+
expect(@login_method.id).to_not eq(auth_method.id)
|
218
|
+
expect(@login_method.provider_name).to eq('github')
|
219
|
+
expect(@login_method.provider_id).to eq(10)
|
220
|
+
expect(@login_method.user.id).to eq(@login_user.id)
|
221
|
+
end
|
222
|
+
|
223
|
+
it 'creates and returns AuthToken' do
|
224
|
+
expect { login }.to change(AuthToken, :count).by(1)
|
225
|
+
expect(@login_token.user.id).to eq(@login_user.id)
|
226
|
+
expect(@login_token.auth_method.id).to eq(@login_method.id)
|
227
|
+
expect(@login_token.last_used_at).to be_a(Time)
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
context 'OAuth email is invalid' do
|
232
|
+
before(:each) do
|
233
|
+
user.update email: 'invalid'
|
234
|
+
expect(oauth).to receive(:email).twice { 'invalid' }
|
235
|
+
expect(oauth).to receive(:display_name) { 'New Name' }
|
236
|
+
end
|
237
|
+
|
238
|
+
it 'creates a new User' do
|
239
|
+
expect { login }.to change(User, :count).by(1)
|
240
|
+
expect(user.display_name).to eq('Existing User')
|
241
|
+
expect(@login_user.email).to eq('invalid')
|
242
|
+
expect(@login_user.display_name).to eq('New Name')
|
243
|
+
end
|
244
|
+
|
245
|
+
it 'creates a new AuthMethod' do
|
246
|
+
expect { login }.to change(AuthMethod, :count).by(1)
|
247
|
+
expect(auth_method.provider_id).to eq(5)
|
248
|
+
expect(@login_method.id).to_not eq(auth_method.id)
|
249
|
+
expect(@login_method.provider_name).to eq('github')
|
250
|
+
expect(@login_method.provider_id).to eq(10)
|
251
|
+
expect(@login_method.user.id).to eq(@login_user.id)
|
252
|
+
end
|
253
|
+
|
254
|
+
it 'creates and returns AuthToken' do
|
255
|
+
expect { login }.to change(AuthToken, :count).by(1)
|
256
|
+
expect(@login_token.user.id).to eq(@login_user.id)
|
257
|
+
expect(@login_token.auth_method.id).to eq(@login_method.id)
|
258
|
+
expect(@login_token.last_used_at).to be_a(Time)
|
259
|
+
end
|
260
|
+
end
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|
@@ -0,0 +1,171 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module SpecModels
|
4
|
+
class OAuthProvider < Jm81auth::OAuth::Base
|
5
|
+
DATA_URL = 'https://example.org/data'
|
6
|
+
|
7
|
+
def get_access_token
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
RSpec.describe Jm81auth::OAuth::Base do
|
13
|
+
let(:model) { SpecModels::OAuthProvider }
|
14
|
+
|
15
|
+
subject(:oauth) do
|
16
|
+
model.new access_token: 'TEST'
|
17
|
+
end
|
18
|
+
|
19
|
+
let(:data) do
|
20
|
+
{
|
21
|
+
'id' => '123',
|
22
|
+
'sub' => '456',
|
23
|
+
'email' => 'first.last@example.com',
|
24
|
+
'name' => 'First Last'
|
25
|
+
}
|
26
|
+
end
|
27
|
+
|
28
|
+
let(:json_data) do
|
29
|
+
<<-EOJSON
|
30
|
+
{
|
31
|
+
"id": "123",
|
32
|
+
"sub": "456",
|
33
|
+
"email": "first.last@example.com",
|
34
|
+
"name": "First Last"
|
35
|
+
}
|
36
|
+
EOJSON
|
37
|
+
end
|
38
|
+
|
39
|
+
let(:http_response) do
|
40
|
+
mock_response = double('http_response')
|
41
|
+
allow(mock_response).to receive(:body).and_return(json_data)
|
42
|
+
mock_response
|
43
|
+
end
|
44
|
+
|
45
|
+
before(:each) do
|
46
|
+
allow_any_instance_of(HTTPClient).to receive(:get) { http_response }
|
47
|
+
end
|
48
|
+
|
49
|
+
describe '#initialize' do
|
50
|
+
let(:params_hash) do
|
51
|
+
{
|
52
|
+
code: 'CODE',
|
53
|
+
redirectUri: 'https://example.org/redirect',
|
54
|
+
clientId: 'CLIENT_ID',
|
55
|
+
other: 'OTHER',
|
56
|
+
access_token: 'ACCESS_TOKEN'
|
57
|
+
}
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'set @params' do
|
61
|
+
new_oauth = Jm81auth::OAuth::Base.new(params_hash)
|
62
|
+
|
63
|
+
expect(new_oauth.instance_variable_get(:@params)).to eq({
|
64
|
+
code: 'CODE',
|
65
|
+
redirect_uri: 'https://example.org/redirect',
|
66
|
+
client_id: 'CLIENT_ID',
|
67
|
+
client_secret: nil
|
68
|
+
})
|
69
|
+
end
|
70
|
+
|
71
|
+
context 'params includes access_token' do
|
72
|
+
it 'sets @access_token from params' do
|
73
|
+
expect_any_instance_of(model).to_not receive(:get_access_token)
|
74
|
+
new_oauth = model.new(params_hash)
|
75
|
+
expect(new_oauth.instance_variable_get(:@access_token)).
|
76
|
+
to eq('ACCESS_TOKEN')
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
context 'params does not include access_token' do
|
81
|
+
it 'set access_token using get_access_token' do
|
82
|
+
expect_any_instance_of(model).
|
83
|
+
to receive(:get_access_token).and_return('123abc')
|
84
|
+
new_oauth = model.new(params_hash.merge(access_token: nil))
|
85
|
+
expect(new_oauth.instance_variable_get(:@access_token)).
|
86
|
+
to eq('123abc')
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
describe 'data' do
|
92
|
+
context '@data set' do
|
93
|
+
let(:set_data) do
|
94
|
+
data.merge('email' => 'other@example.com')
|
95
|
+
end
|
96
|
+
|
97
|
+
before(:each) { oauth.instance_variable_set(:@data, set_data) }
|
98
|
+
|
99
|
+
it 'returns @data' do
|
100
|
+
expect(oauth).to_not receive(:get_data)
|
101
|
+
expect(oauth.data).to eq(set_data)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
context '@data not set' do
|
106
|
+
it 'sets @data using get_data' do
|
107
|
+
expect(oauth).to receive(:get_data).and_call_original
|
108
|
+
oauth.data
|
109
|
+
end
|
110
|
+
|
111
|
+
it 'returns @data' do
|
112
|
+
expect(oauth.data).to eq(data)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
describe '#display_name' do
|
118
|
+
it "gets data['name']" do
|
119
|
+
expect(oauth.display_name).to eq('First Last')
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
describe '#email' do
|
124
|
+
it "gets data['email']" do
|
125
|
+
expect(oauth.email).to eq('first.last@example.com')
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
describe '#get_data' do
|
130
|
+
it 'gets data from provider' do
|
131
|
+
expect_any_instance_of(HTTPClient).to receive(:get) { http_response }
|
132
|
+
expect(oauth.get_data).to eq(data)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
describe '#provider_name' do
|
137
|
+
it 'gets provider name based on the class name' do
|
138
|
+
expect(oauth.provider_name).to eq('oauthprovider')
|
139
|
+
expect(Jm81auth::OAuth::Base.new(access_token: 'a').provider_name).
|
140
|
+
to eq('base')
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
describe 'provider_id' do
|
145
|
+
context "data['id'] is set" do
|
146
|
+
it "is data['id']" do
|
147
|
+
expect(oauth.provider_id).to eq('123')
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
context "data['id'] is not set" do
|
152
|
+
before(:each) do
|
153
|
+
expect(http_response).
|
154
|
+
to receive(:body).and_return(json_data.gsub(/id/, 'no_id'))
|
155
|
+
end
|
156
|
+
|
157
|
+
it "is data['sub']" do
|
158
|
+
expect(oauth.provider_id).to eq('456')
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
describe 'provider_data' do
|
164
|
+
it 'is a hash with provider_name and provider_id' do
|
165
|
+
expect(oauth.provider_data).to eq({
|
166
|
+
provider_name: 'oauthprovider',
|
167
|
+
provider_id: '123'
|
168
|
+
})
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler/setup'
|
3
|
+
Bundler.require
|
4
|
+
require 'jm81auth'
|
5
|
+
require 'sequel'
|
6
|
+
require 'factory_girl'
|
7
|
+
|
8
|
+
Dir['./spec/support/**/*.rb'].sort.each { |f| require f}
|
9
|
+
|
10
|
+
RSpec.configure do |config|
|
11
|
+
config.run_all_when_everything_filtered = true
|
12
|
+
|
13
|
+
config.include FactoryGirl::Syntax::Methods
|
14
|
+
|
15
|
+
config.before :suite do
|
16
|
+
FactoryGirl.find_definitions
|
17
|
+
end
|
18
|
+
|
19
|
+
config.around :each do |example|
|
20
|
+
DB.transaction(rollback: :always, auto_savepoint: true) { example.run }
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
DB = Sequel.sqlite
|
25
|
+
|
26
|
+
DB.create_table(:users) do
|
27
|
+
primary_key :id
|
28
|
+
column :email, "varchar(255)"
|
29
|
+
column :display_name, "varchar(255)"
|
30
|
+
end
|
31
|
+
|
32
|
+
DB.create_table(:auth_methods) do
|
33
|
+
primary_key :id
|
34
|
+
foreign_key :user_id, :users, :on_delete=>:cascade, :on_update=>:cascade
|
35
|
+
column :provider_name, "varchar(255)", :null=>false
|
36
|
+
column :provider_id, "integer unsigned", :null=>false
|
37
|
+
end
|
38
|
+
|
39
|
+
DB.create_table(:auth_tokens) do
|
40
|
+
primary_key :id
|
41
|
+
foreign_key :user_id, :users, :on_delete=>:restrict, :on_update=>:cascade
|
42
|
+
foreign_key :auth_method_id, :auth_methods, :on_delete=>:restrict, :on_update=>:cascade
|
43
|
+
column :created_at, "timestamp", :null=>false
|
44
|
+
column :last_used_at, "timestamp", :null=>false
|
45
|
+
column :closed_at, "timestamp"
|
46
|
+
end
|
47
|
+
|
48
|
+
Jm81auth.config do |config|
|
49
|
+
config.jwt_secret = 'testsecret'
|
50
|
+
end
|
51
|
+
|
52
|
+
class AuthMethod < Sequel::Model
|
53
|
+
include Jm81auth::Models::AuthMethod
|
54
|
+
end
|
55
|
+
|
56
|
+
class AuthToken < Sequel::Model
|
57
|
+
include Jm81auth::Models::AuthToken
|
58
|
+
end
|
59
|
+
|
60
|
+
class User < Sequel::Model
|
61
|
+
include Jm81auth::Models::User
|
62
|
+
end
|
metadata
ADDED
@@ -0,0 +1,169 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: jm81auth
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jared Morgan
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-03-01 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: httpclient
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.6'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2.6'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: jwt
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.5'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.5'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: factory_girl
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '4.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '4.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: sequel
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '4.0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '4.0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: sqlite3
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '1.3'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '1.3'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rspec
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '3.2'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '3.2'
|
111
|
+
description: I have no excuse for giving the world yet another auth lib.
|
112
|
+
email:
|
113
|
+
- jmorgan@mchost.net
|
114
|
+
executables: []
|
115
|
+
extensions: []
|
116
|
+
extra_rdoc_files: []
|
117
|
+
files:
|
118
|
+
- MIT-LICENSE
|
119
|
+
- README.rdoc
|
120
|
+
- Rakefile
|
121
|
+
- lib/jm81auth.rb
|
122
|
+
- lib/jm81auth/configuration.rb
|
123
|
+
- lib/jm81auth/models/auth_method.rb
|
124
|
+
- lib/jm81auth/models/auth_token.rb
|
125
|
+
- lib/jm81auth/models/user.rb
|
126
|
+
- lib/jm81auth/oauth/base.rb
|
127
|
+
- lib/jm81auth/oauth/github.rb
|
128
|
+
- lib/jm81auth/version.rb
|
129
|
+
- spec/factories/auth_methods.rb
|
130
|
+
- spec/factories/auth_tokens.rb
|
131
|
+
- spec/factories/users.rb
|
132
|
+
- spec/jm81auth/models/auth_method_spec.rb
|
133
|
+
- spec/jm81auth/models/auth_token_spec.rb
|
134
|
+
- spec/jm81auth/models/user_spec.rb
|
135
|
+
- spec/jm81auth/oauth/base_spec.rb
|
136
|
+
- spec/spec_helper.rb
|
137
|
+
homepage: https://github.com/jm81/jm81auth
|
138
|
+
licenses:
|
139
|
+
- MIT
|
140
|
+
metadata: {}
|
141
|
+
post_install_message:
|
142
|
+
rdoc_options: []
|
143
|
+
require_paths:
|
144
|
+
- lib
|
145
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
146
|
+
requirements:
|
147
|
+
- - ">="
|
148
|
+
- !ruby/object:Gem::Version
|
149
|
+
version: '0'
|
150
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
151
|
+
requirements:
|
152
|
+
- - ">="
|
153
|
+
- !ruby/object:Gem::Version
|
154
|
+
version: '0'
|
155
|
+
requirements: []
|
156
|
+
rubyforge_project:
|
157
|
+
rubygems_version: 2.2.2
|
158
|
+
signing_key:
|
159
|
+
specification_version: 4
|
160
|
+
summary: An authentication library for Rails API
|
161
|
+
test_files:
|
162
|
+
- spec/factories/users.rb
|
163
|
+
- spec/factories/auth_tokens.rb
|
164
|
+
- spec/factories/auth_methods.rb
|
165
|
+
- spec/jm81auth/models/auth_token_spec.rb
|
166
|
+
- spec/jm81auth/models/user_spec.rb
|
167
|
+
- spec/jm81auth/models/auth_method_spec.rb
|
168
|
+
- spec/jm81auth/oauth/base_spec.rb
|
169
|
+
- spec/spec_helper.rb
|