authify-api 0.0.5
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/.gitignore +9 -0
- data/.rspec +2 -0
- data/.rubocop.yml +23 -0
- data/.travis.yml +13 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +41 -0
- data/Rakefile +38 -0
- data/authify-api.gemspec +45 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/config.ru +9 -0
- data/db/migrate/20170201213901_create_users.rb +12 -0
- data/db/migrate/20170201220029_create_api_keys.rb +12 -0
- data/db/migrate/20170202004557_create_identities.rb +12 -0
- data/db/migrate/20170203231922_create_organizations_and_organization_memberships.rb +22 -0
- data/db/migrate/20170203231929_create_groups.rb +17 -0
- data/db/migrate/20170204001405_create_trusted_delegates.rb +13 -0
- data/db/schema.rb +95 -0
- data/lib/authify/api.rb +51 -0
- data/lib/authify/api/controllers/api_key.rb +36 -0
- data/lib/authify/api/controllers/group.rb +40 -0
- data/lib/authify/api/controllers/organization.rb +48 -0
- data/lib/authify/api/controllers/user.rb +72 -0
- data/lib/authify/api/helpers/api_user.rb +30 -0
- data/lib/authify/api/helpers/jwt_encryption.rb +42 -0
- data/lib/authify/api/jsonapi_utils.rb +12 -0
- data/lib/authify/api/models/api_key.rb +44 -0
- data/lib/authify/api/models/group.rb +17 -0
- data/lib/authify/api/models/identity.rb +14 -0
- data/lib/authify/api/models/organization.rb +26 -0
- data/lib/authify/api/models/organization_membership.rb +16 -0
- data/lib/authify/api/models/trusted_delegate.rb +44 -0
- data/lib/authify/api/models/user.rb +73 -0
- data/lib/authify/api/serializers/api_key_serializer.rb +13 -0
- data/lib/authify/api/serializers/group_serializer.rb +15 -0
- data/lib/authify/api/serializers/organization_serializer.rb +15 -0
- data/lib/authify/api/serializers/user_serializer.rb +17 -0
- data/lib/authify/api/service.rb +11 -0
- data/lib/authify/api/services/api.rb +58 -0
- data/lib/authify/api/services/jwt_provider.rb +61 -0
- data/lib/authify/api/version.rb +9 -0
- metadata +324 -0
data/lib/authify/api.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
# Standard Library Requirements
|
2
|
+
|
3
|
+
# External Requirements
|
4
|
+
require 'authify/core'
|
5
|
+
require 'authify/middleware'
|
6
|
+
require 'sinatra/base'
|
7
|
+
require 'sinatra/activerecord'
|
8
|
+
require 'jsonapi-serializers'
|
9
|
+
require 'sinatra/jsonapi'
|
10
|
+
require 'tilt/erb'
|
11
|
+
require 'connection_pool'
|
12
|
+
require 'moneta'
|
13
|
+
|
14
|
+
# Internal Requirements
|
15
|
+
module Authify
|
16
|
+
module API
|
17
|
+
CONFIG = Core::CONFIG.merge(
|
18
|
+
db: {
|
19
|
+
url: ENV['AUTHIFY_DB_URL'] || 'mysql2://root@localhost:3306/authifydb'
|
20
|
+
},
|
21
|
+
session_secret: ENV['AUTHIFY_SESSION_SECRET'] || '1q2w3e4r5t6y7u8i9o0pazsxdcfvgbhnjmkl10zm',
|
22
|
+
redis: {
|
23
|
+
host: ENV['AUTHIFY_REDIS_HOST'] || 'localhost',
|
24
|
+
port: ENV['AUTHIFY_REDIS_PORT'] || '6379'
|
25
|
+
}
|
26
|
+
)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
require 'authify/api/version'
|
31
|
+
require 'authify/api/jsonapi_utils'
|
32
|
+
require 'authify/api/controllers/api_key'
|
33
|
+
require 'authify/api/controllers/group'
|
34
|
+
require 'authify/api/controllers/organization'
|
35
|
+
require 'authify/api/controllers/user'
|
36
|
+
require 'authify/api/serializers/api_key_serializer'
|
37
|
+
require 'authify/api/serializers/group_serializer'
|
38
|
+
require 'authify/api/serializers/user_serializer'
|
39
|
+
require 'authify/api/serializers/organization_serializer'
|
40
|
+
require 'authify/api/models/api_key'
|
41
|
+
require 'authify/api/models/group'
|
42
|
+
require 'authify/api/models/identity'
|
43
|
+
require 'authify/api/models/organization'
|
44
|
+
require 'authify/api/models/organization_membership'
|
45
|
+
require 'authify/api/models/trusted_delegate'
|
46
|
+
require 'authify/api/models/user'
|
47
|
+
require 'authify/api/helpers/jwt_encryption'
|
48
|
+
require 'authify/api/helpers/api_user'
|
49
|
+
require 'authify/api/service'
|
50
|
+
require 'authify/api/services/api'
|
51
|
+
require 'authify/api/services/jwt_provider'
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Authify
|
2
|
+
module API
|
3
|
+
module Controllers
|
4
|
+
APIKey = proc do
|
5
|
+
helpers do
|
6
|
+
def find(id)
|
7
|
+
Models::APIKey.find(id.to_i)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
index do
|
12
|
+
Models::APIKey.all
|
13
|
+
end
|
14
|
+
|
15
|
+
show do
|
16
|
+
last_modified resource.updated_at
|
17
|
+
next resource
|
18
|
+
end
|
19
|
+
|
20
|
+
destroy do
|
21
|
+
resource.destroy
|
22
|
+
end
|
23
|
+
|
24
|
+
show_many do |ids|
|
25
|
+
Models::APIKey.find(ids)
|
26
|
+
end
|
27
|
+
|
28
|
+
has_one :user do
|
29
|
+
pluck do
|
30
|
+
resource.user
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Authify
|
2
|
+
module API
|
3
|
+
module Controllers
|
4
|
+
Group = proc do
|
5
|
+
helpers do
|
6
|
+
def find(id)
|
7
|
+
Models::Group.find(id.to_i)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
index do
|
12
|
+
Models::Group.all
|
13
|
+
end
|
14
|
+
|
15
|
+
show do
|
16
|
+
last_modified resource.updated_at
|
17
|
+
next resource
|
18
|
+
end
|
19
|
+
|
20
|
+
create do |attrs|
|
21
|
+
Models::Group.new(attrs)
|
22
|
+
end
|
23
|
+
|
24
|
+
destroy do
|
25
|
+
resource.destroy
|
26
|
+
end
|
27
|
+
|
28
|
+
show_many do |ids|
|
29
|
+
Models::Group.find(ids)
|
30
|
+
end
|
31
|
+
|
32
|
+
has_many :users do
|
33
|
+
fetch do
|
34
|
+
resource.users
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Authify
|
2
|
+
module API
|
3
|
+
module Controllers
|
4
|
+
Organization = proc do
|
5
|
+
helpers do
|
6
|
+
def find(id)
|
7
|
+
Models::Organization.find(id.to_i)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
index do
|
12
|
+
Models::Organization.all
|
13
|
+
end
|
14
|
+
|
15
|
+
show do
|
16
|
+
last_modified resource.updated_at
|
17
|
+
next resource
|
18
|
+
end
|
19
|
+
|
20
|
+
show_many do |ids|
|
21
|
+
Models::Organization.find(ids)
|
22
|
+
end
|
23
|
+
|
24
|
+
destroy do
|
25
|
+
resource.destroy if @current_user.admin_for?(resource)
|
26
|
+
end
|
27
|
+
|
28
|
+
has_many :users do
|
29
|
+
fetch do
|
30
|
+
resource.users
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
has_many :admins do
|
35
|
+
fetch do
|
36
|
+
resource.admins
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
has_many :groups do
|
41
|
+
fetch do
|
42
|
+
resource.groups
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module Authify
|
2
|
+
module API
|
3
|
+
module Controllers
|
4
|
+
User = proc do
|
5
|
+
helpers do
|
6
|
+
def find(id)
|
7
|
+
Models::User.find(id.to_i)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
index do
|
12
|
+
Models::User.all
|
13
|
+
end
|
14
|
+
|
15
|
+
show do
|
16
|
+
last_modified resource.updated_at
|
17
|
+
next resource
|
18
|
+
end
|
19
|
+
|
20
|
+
show_many do |ids|
|
21
|
+
Models::User.find(ids)
|
22
|
+
end
|
23
|
+
|
24
|
+
has_many :api_keys do
|
25
|
+
fetch do
|
26
|
+
resource.api_keys
|
27
|
+
end
|
28
|
+
|
29
|
+
clear do
|
30
|
+
resource.api_keys.destroy_all
|
31
|
+
resource.save
|
32
|
+
end
|
33
|
+
|
34
|
+
subtract do |rios|
|
35
|
+
refs = rios.map { |attrs| Models::APIKey.new(attrs) }
|
36
|
+
resource.api_keys.destroy(refs)
|
37
|
+
resource.save
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
has_many :identities do
|
42
|
+
fetch do
|
43
|
+
resource.identities
|
44
|
+
end
|
45
|
+
|
46
|
+
clear do
|
47
|
+
resource.identities.destroy_all
|
48
|
+
resource.save
|
49
|
+
end
|
50
|
+
|
51
|
+
subtract do |rios|
|
52
|
+
refs = rios.map { |attrs| Models::Identities.new(attrs) }
|
53
|
+
resource.identities.destroy(refs)
|
54
|
+
resource.save
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
has_many :organizations do
|
59
|
+
fetch do
|
60
|
+
resource.organizations
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
has_many :groups do
|
65
|
+
fetch do
|
66
|
+
resource.groups
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Authify
|
2
|
+
module API
|
3
|
+
module Helpers
|
4
|
+
# Helper methods for API users
|
5
|
+
module APIUser
|
6
|
+
def current_user
|
7
|
+
@current_user ||= Models::User.find_by_email(env['user']['username'])
|
8
|
+
end
|
9
|
+
|
10
|
+
def update_current_user(user)
|
11
|
+
found_user = if user.is_a? Authify::API::Models::User
|
12
|
+
user
|
13
|
+
elsif user.is_a? Integer
|
14
|
+
Models::User.find(user)
|
15
|
+
elsif user.is_a? String
|
16
|
+
Models::User.find_by_email(user)
|
17
|
+
end
|
18
|
+
|
19
|
+
halt 422 unless found_user
|
20
|
+
@current_user = found_user
|
21
|
+
end
|
22
|
+
|
23
|
+
# This shouldn't work via API calls
|
24
|
+
def auth
|
25
|
+
{}
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Authify
|
2
|
+
module API
|
3
|
+
module Helpers
|
4
|
+
# Helper methods for working with JWT encryption
|
5
|
+
module JWTEncryption
|
6
|
+
include Core::Helpers::JWTSSL
|
7
|
+
|
8
|
+
def jwt_token
|
9
|
+
JWT.encode jwt_payload(current_user), private_key, 'ES256'
|
10
|
+
end
|
11
|
+
|
12
|
+
def jwt_payload(user)
|
13
|
+
{
|
14
|
+
exp: Time.now.to_i + 60 * 60,
|
15
|
+
iat: Time.now.to_i,
|
16
|
+
iss: CONFIG[:jwt][:issuer],
|
17
|
+
scopes: Core::Constants::JWTSCOPES,
|
18
|
+
user: {
|
19
|
+
username: user.email,
|
20
|
+
uid: user.id,
|
21
|
+
organizations: user.organizations.map do |o|
|
22
|
+
{ name: o.name, oid: o.id, admin: o.admins.include?(user) }
|
23
|
+
end,
|
24
|
+
groups: user.groups.map { |g| { name: g.name, gid: g.id } }
|
25
|
+
}
|
26
|
+
}
|
27
|
+
end
|
28
|
+
|
29
|
+
def with_jwt(req, scope)
|
30
|
+
scopes, user = req.env.values_at :scopes, :user
|
31
|
+
set_current_user Models::User.from_username(user['username'])
|
32
|
+
|
33
|
+
if scopes.include?(scope) && current_user
|
34
|
+
yield req
|
35
|
+
else
|
36
|
+
halt 403
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Authify
|
2
|
+
module API
|
3
|
+
# JSON API related Model utility methods
|
4
|
+
module JSONAPIUtils
|
5
|
+
def jsonapi_serializer_class_name
|
6
|
+
this_class = self.class.name.split('::').last
|
7
|
+
new_class = "Authify::API::Serializers::#{this_class}Serializer"
|
8
|
+
new_class.split('::').inject(Object) { |o, c| o.const_get c }
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Authify
|
2
|
+
module API
|
3
|
+
module Models
|
4
|
+
# Additional, revocable user access
|
5
|
+
class APIKey < ActiveRecord::Base
|
6
|
+
include Core::SecureHashing
|
7
|
+
extend Core::SecureHashing
|
8
|
+
include JSONAPIUtils
|
9
|
+
|
10
|
+
attr_reader :secret_key
|
11
|
+
|
12
|
+
validates_presence_of :access_key
|
13
|
+
validates_presence_of :secret_key_digest
|
14
|
+
|
15
|
+
belongs_to :user,
|
16
|
+
required: true,
|
17
|
+
class_name: 'Authify::API::Models::User'
|
18
|
+
|
19
|
+
def secret_key=(unencrypted_string)
|
20
|
+
@secret_key = unencrypted_string
|
21
|
+
self.secret_key_digest = salted_sha512(unencrypted_string) if viable(unencrypted_string)
|
22
|
+
end
|
23
|
+
|
24
|
+
def compare_secret(unencrypted_string)
|
25
|
+
compare_salted_sha512(unencrypted_string, secret_key_digest)
|
26
|
+
end
|
27
|
+
|
28
|
+
def set_secret!
|
29
|
+
self.secret_key = self.class.generate_access_key + self.class.generate_access_key
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.generate_access_key
|
33
|
+
to_hex(SecureRandom.gen_random(32))[0...32]
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def viable(string)
|
39
|
+
string && !string.empty?
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Authify
|
2
|
+
module API
|
3
|
+
module Models
|
4
|
+
# A way of grouping multiple users
|
5
|
+
class Group < ActiveRecord::Base
|
6
|
+
include JSONAPIUtils
|
7
|
+
|
8
|
+
belongs_to :organization,
|
9
|
+
required: true,
|
10
|
+
class_name: 'Authify::API::Models::Organization'
|
11
|
+
|
12
|
+
has_and_belongs_to_many :users,
|
13
|
+
class_name: 'Authify::API::Models::User'
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Authify
|
2
|
+
module API
|
3
|
+
module Models
|
4
|
+
# External identities mapped to users (GitHub, Facebook, etc.)
|
5
|
+
class Identity < ActiveRecord::Base
|
6
|
+
include JSONAPIUtils
|
7
|
+
|
8
|
+
belongs_to :user,
|
9
|
+
required: true,
|
10
|
+
class_name: 'Authify::API::Models::User'
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Authify
|
2
|
+
module API
|
3
|
+
module Models
|
4
|
+
# High-level logical organization of groups and users
|
5
|
+
class Organization < ActiveRecord::Base
|
6
|
+
include JSONAPIUtils
|
7
|
+
|
8
|
+
has_many :organization_memberships,
|
9
|
+
class_name: 'Authify::API::Models::OrganizationMembership',
|
10
|
+
dependent: :destroy
|
11
|
+
|
12
|
+
has_many :users,
|
13
|
+
through: :organization_memberships,
|
14
|
+
class_name: 'Authify::API::Models::User'
|
15
|
+
|
16
|
+
has_many :groups,
|
17
|
+
class_name: 'Authify::API::Models::Group',
|
18
|
+
dependent: :destroy
|
19
|
+
|
20
|
+
has_many :admins, -> { where admin: true },
|
21
|
+
through: :organization_memberships,
|
22
|
+
class_name: 'Authify::API::Models::User'
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|