jm81auth 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|