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