authify-api 0.0.6 → 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 +4 -4
- data/.rubocop.yml +5 -0
- data/Gemfile +3 -0
- data/README.md +154 -6
- data/Rakefile +20 -0
- data/authify-api.gemspec +4 -2
- data/config.ru +3 -2
- data/db/migrate/20170201220029_create_api_keys.rb +2 -2
- data/db/migrate/20170208021933_add_admin_to_user.rb +9 -0
- data/db/migrate/20170208022427_set_default_for_user_admin.rb +7 -0
- data/db/schema.rb +8 -6
- data/lib/authify/api.rb +6 -4
- data/lib/authify/api/controllers/apikey.rb +50 -0
- data/lib/authify/api/controllers/group.rb +34 -6
- data/lib/authify/api/controllers/identity.rb +49 -0
- data/lib/authify/api/controllers/organization.rb +57 -7
- data/lib/authify/api/controllers/user.rb +54 -15
- data/lib/authify/api/helpers/api_user.rb +16 -1
- data/lib/authify/api/helpers/jwt_encryption.rb +21 -8
- data/lib/authify/api/models/{api_key.rb → apikey.rb} +16 -2
- data/lib/authify/api/models/user.rb +2 -4
- data/lib/authify/api/serializers/{api_key_serializer.rb → apikey_serializer.rb} +7 -0
- data/lib/authify/api/serializers/group_serializer.rb +2 -0
- data/lib/authify/api/serializers/identity_serializer.rb +14 -0
- data/lib/authify/api/serializers/organization_serializer.rb +8 -0
- data/lib/authify/api/serializers/user_serializer.rb +4 -1
- data/lib/authify/api/services/api.rb +28 -33
- data/lib/authify/api/services/jwt_provider.rb +30 -8
- data/lib/authify/api/services/registration.rb +63 -0
- data/lib/authify/api/version.rb +2 -2
- metadata +46 -15
- data/lib/authify/api/controllers/api_key.rb +0 -36
@@ -0,0 +1,49 @@
|
|
1
|
+
module Authify
|
2
|
+
module API
|
3
|
+
module Controllers
|
4
|
+
Identity = proc do
|
5
|
+
helpers do
|
6
|
+
def find(id)
|
7
|
+
Models::Identity.find(id.to_i)
|
8
|
+
end
|
9
|
+
|
10
|
+
def role
|
11
|
+
Array(super).tap do |a|
|
12
|
+
a << :myself if current_user && current_user.identities.include?(resource)
|
13
|
+
end.uniq
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
index(roles: [:admin, :trusted]) do
|
18
|
+
Models::Identity.all
|
19
|
+
end
|
20
|
+
|
21
|
+
show(roles: [:myself, :admin, :trusted]) do
|
22
|
+
last_modified resource.updated_at
|
23
|
+
next resource
|
24
|
+
end
|
25
|
+
|
26
|
+
create(roles: [:user]) do |attributes|
|
27
|
+
ident = Models::Identity.new attributes
|
28
|
+
current_user.identities << ident
|
29
|
+
current_user.save
|
30
|
+
next ident.id, ident
|
31
|
+
end
|
32
|
+
|
33
|
+
destroy(roles: [:myself, :admin]) do
|
34
|
+
resource.destroy
|
35
|
+
end
|
36
|
+
|
37
|
+
show_many do |ids|
|
38
|
+
Models::Identity.find(ids)
|
39
|
+
end
|
40
|
+
|
41
|
+
has_one :user do
|
42
|
+
pluck(roles: [:myself, :admin, :trusted]) do
|
43
|
+
resource.user
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -6,13 +6,51 @@ module Authify
|
|
6
6
|
def find(id)
|
7
7
|
Models::Organization.find(id.to_i)
|
8
8
|
end
|
9
|
+
|
10
|
+
def role
|
11
|
+
Array(super).tap do |a|
|
12
|
+
a << :owner if resource && current_user.admin_for?(resource)
|
13
|
+
a << :member if resource && resource.users.include?(current_user)
|
14
|
+
end.uniq
|
15
|
+
end
|
16
|
+
|
17
|
+
def modifiable_fields
|
18
|
+
[
|
19
|
+
:name,
|
20
|
+
:public_email,
|
21
|
+
:gravatar_email,
|
22
|
+
:billing_email,
|
23
|
+
:description,
|
24
|
+
:url,
|
25
|
+
:location
|
26
|
+
]
|
27
|
+
end
|
28
|
+
|
29
|
+
def filtered_attributes(attributes)
|
30
|
+
attributes.select do |k, _v|
|
31
|
+
modifiable_fields.include?(k)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def filter(collection, fields = {})
|
36
|
+
collection.where(fields)
|
37
|
+
end
|
38
|
+
|
39
|
+
def sort(collection, fields = {})
|
40
|
+
collection.order(fields)
|
41
|
+
end
|
9
42
|
end
|
10
43
|
|
11
|
-
index do
|
44
|
+
index(roles: [:admin]) do
|
12
45
|
Models::Organization.all
|
13
46
|
end
|
14
47
|
|
15
|
-
|
48
|
+
get '/mine' do
|
49
|
+
halt(403) unless can?(:create)
|
50
|
+
serialize_models current_user.organizations
|
51
|
+
end
|
52
|
+
|
53
|
+
show(roles: [:user]) do
|
16
54
|
last_modified resource.updated_at
|
17
55
|
next resource
|
18
56
|
end
|
@@ -21,24 +59,36 @@ module Authify
|
|
21
59
|
Models::Organization.find(ids)
|
22
60
|
end
|
23
61
|
|
24
|
-
|
25
|
-
|
62
|
+
create(roles: [:user]) do |attrs|
|
63
|
+
o = Models::Organization.new(attrs)
|
64
|
+
o.admins << current_user
|
65
|
+
o.save
|
66
|
+
next o
|
67
|
+
end
|
68
|
+
|
69
|
+
update(roles: [:owner, :admin]) do |attrs|
|
70
|
+
resource.update filtered_attributes(attrs)
|
71
|
+
next resource
|
72
|
+
end
|
73
|
+
|
74
|
+
destroy(roles: [:owner, :admin]) do
|
75
|
+
resource.destroy
|
26
76
|
end
|
27
77
|
|
28
78
|
has_many :users do
|
29
|
-
fetch do
|
79
|
+
fetch(roles: [:owner, :admin, :member]) do
|
30
80
|
resource.users
|
31
81
|
end
|
32
82
|
end
|
33
83
|
|
34
84
|
has_many :admins do
|
35
|
-
fetch do
|
85
|
+
fetch(roles: [:owner, :admin, :member]) do
|
36
86
|
resource.admins
|
37
87
|
end
|
38
88
|
end
|
39
89
|
|
40
90
|
has_many :groups do
|
41
|
-
fetch do
|
91
|
+
fetch(roles: [:owner, :admin]) do
|
42
92
|
resource.groups
|
43
93
|
end
|
44
94
|
end
|
@@ -6,63 +6,102 @@ module Authify
|
|
6
6
|
def find(id)
|
7
7
|
Models::User.find(id.to_i)
|
8
8
|
end
|
9
|
+
|
10
|
+
def role
|
11
|
+
Array(super).tap do |a|
|
12
|
+
a << :myself if current_user && resource && (resource.id == current_user.id)
|
13
|
+
end.uniq
|
14
|
+
end
|
15
|
+
|
16
|
+
def modifiable_fields
|
17
|
+
[:full_name, :email].tap do |a|
|
18
|
+
a << :admin if role.include?(:admin)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def filtered_attributes(attributes)
|
23
|
+
attributes.select do |k, _v|
|
24
|
+
modifiable_fields.include?(k)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def filter(collection, fields = {})
|
29
|
+
collection.where(fields)
|
30
|
+
end
|
31
|
+
|
32
|
+
def sort(collection, fields = {})
|
33
|
+
collection.order(fields)
|
34
|
+
end
|
9
35
|
end
|
10
36
|
|
11
|
-
index do
|
37
|
+
index(roles: [:user, :trusted]) do
|
12
38
|
Models::User.all
|
13
39
|
end
|
14
40
|
|
15
|
-
show do
|
41
|
+
show(roles: [:user, :trusted]) do
|
16
42
|
last_modified resource.updated_at
|
17
43
|
next resource
|
18
44
|
end
|
19
45
|
|
46
|
+
create(roles: [:admin]) do |attributes|
|
47
|
+
user = Models::User.new filtered_attributes(attributes)
|
48
|
+
user.save
|
49
|
+
next user
|
50
|
+
end
|
51
|
+
|
20
52
|
show_many do |ids|
|
21
53
|
Models::User.find(ids)
|
22
54
|
end
|
23
55
|
|
24
|
-
has_many :
|
25
|
-
fetch do
|
26
|
-
resource.
|
56
|
+
has_many :apikeys do
|
57
|
+
fetch(roles: [:myself, :admin]) do
|
58
|
+
resource.apikeys
|
27
59
|
end
|
28
60
|
|
29
|
-
clear do
|
30
|
-
resource.
|
61
|
+
clear(roles: [:myself, :admin]) do
|
62
|
+
resource.apikeys.destroy_all
|
31
63
|
resource.save
|
32
64
|
end
|
33
65
|
|
34
|
-
subtract do |rios|
|
35
|
-
refs = rios.map { |attrs| Models::APIKey.
|
36
|
-
|
66
|
+
subtract(roles: [:myself, :admin]) do |rios|
|
67
|
+
refs = rios.map { |attrs| Models::APIKey.find(attrs) }
|
68
|
+
# This actually calls #destroy on the keys (we don't need orphaned keys)
|
69
|
+
resource.apikeys.destroy(refs)
|
37
70
|
resource.save
|
38
71
|
end
|
39
72
|
end
|
40
73
|
|
41
74
|
has_many :identities do
|
42
|
-
fetch do
|
75
|
+
fetch(roles: [:myself, :admin, :trusted]) do
|
43
76
|
resource.identities
|
44
77
|
end
|
45
78
|
|
46
|
-
clear do
|
79
|
+
clear(roles: [:myself, :admin]) do
|
47
80
|
resource.identities.destroy_all
|
48
81
|
resource.save
|
49
82
|
end
|
50
83
|
|
51
|
-
|
84
|
+
merge(roles: [:myself]) do |rios|
|
52
85
|
refs = rios.map { |attrs| Models::Identities.new(attrs) }
|
86
|
+
resource.identities << refs
|
87
|
+
resource.save
|
88
|
+
end
|
89
|
+
|
90
|
+
subtract(roles: [:myself, :admin]) do |rios|
|
91
|
+
refs = rios.map { |attrs| Models::Identities.find(attrs) }
|
53
92
|
resource.identities.destroy(refs)
|
54
93
|
resource.save
|
55
94
|
end
|
56
95
|
end
|
57
96
|
|
58
97
|
has_many :organizations do
|
59
|
-
fetch do
|
98
|
+
fetch(roles: [:user, :myself]) do
|
60
99
|
resource.organizations
|
61
100
|
end
|
62
101
|
end
|
63
102
|
|
64
103
|
has_many :groups do
|
65
|
-
fetch do
|
104
|
+
fetch(roles: [:myself, :admin]) do
|
66
105
|
resource.groups
|
67
106
|
end
|
68
107
|
end
|
@@ -4,7 +4,16 @@ module Authify
|
|
4
4
|
# Helper methods for API users
|
5
5
|
module APIUser
|
6
6
|
def current_user
|
7
|
-
|
7
|
+
email = env.key?(:user) ? env[:user]['username'] : nil
|
8
|
+
@current_user ||= email ? Models::User.find_by_email(email) : nil
|
9
|
+
end
|
10
|
+
|
11
|
+
def remote_app
|
12
|
+
@remote_app ||= if processed_headers.key?('x_authify_access')
|
13
|
+
access = processed_headers['x_authify_access']
|
14
|
+
secret = processed_headers['x_authify_secret']
|
15
|
+
Models::TrustedDelegate.from_access_key(access, secret)
|
16
|
+
end
|
8
17
|
end
|
9
18
|
|
10
19
|
def update_current_user(user)
|
@@ -20,6 +29,12 @@ module Authify
|
|
20
29
|
@current_user = found_user
|
21
30
|
end
|
22
31
|
|
32
|
+
def processed_headers
|
33
|
+
@headers ||= request.env.dup.each_with_object({}) do |(k, v), acc|
|
34
|
+
acc[Regexp.last_match(1).downcase] = v if k =~ /^http_(.*)/i
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
23
38
|
# This shouldn't work via API calls
|
24
39
|
def auth
|
25
40
|
{}
|
@@ -5,27 +5,40 @@ module Authify
|
|
5
5
|
module JWTEncryption
|
6
6
|
include Core::Helpers::JWTSSL
|
7
7
|
|
8
|
-
def jwt_token
|
9
|
-
|
8
|
+
def jwt_token(user = nil)
|
9
|
+
user ||= current_user
|
10
|
+
JWT.encode jwt_payload(user), private_key, CONFIG[:jwt][:algorithm]
|
10
11
|
end
|
11
12
|
|
12
13
|
def jwt_payload(user)
|
13
14
|
{
|
14
|
-
exp: Time.now.to_i + 60 *
|
15
|
+
exp: Time.now.to_i + 60 * CONFIG[:jwt][:expiration].to_i,
|
15
16
|
iat: Time.now.to_i,
|
16
17
|
iss: CONFIG[:jwt][:issuer],
|
17
|
-
scopes: Core::Constants::JWTSCOPES
|
18
|
+
scopes: Core::Constants::JWTSCOPES.dup.tap do |scopes|
|
19
|
+
scopes << :admin_access if current_user && current_user.admin?
|
20
|
+
end,
|
18
21
|
user: {
|
19
22
|
username: user.email,
|
20
23
|
uid: user.id,
|
21
|
-
organizations: user
|
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 } }
|
24
|
+
organizations: simple_orgs_by_user(user)
|
25
25
|
}
|
26
26
|
}
|
27
27
|
end
|
28
28
|
|
29
|
+
def simple_orgs_by_user(user)
|
30
|
+
user.organizations.map do |o|
|
31
|
+
{
|
32
|
+
name: o.name,
|
33
|
+
oid: o.id,
|
34
|
+
admin: o.admins.include?(user),
|
35
|
+
memberships: o.groups.select { |g| g.users.include?(user) }.map do |g|
|
36
|
+
{ name: g.name, gid: g.id }
|
37
|
+
end
|
38
|
+
}
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
29
42
|
def with_jwt(req, scope)
|
30
43
|
scopes, user = req.env.values_at :scopes, :user
|
31
44
|
set_current_user Models::User.from_username(user['username'])
|
@@ -3,6 +3,7 @@ module Authify
|
|
3
3
|
module Models
|
4
4
|
# Additional, revocable user access
|
5
5
|
class APIKey < ActiveRecord::Base
|
6
|
+
self.table_name = 'apikeys'
|
6
7
|
include Core::SecureHashing
|
7
8
|
extend Core::SecureHashing
|
8
9
|
include JSONAPIUtils
|
@@ -25,12 +26,25 @@ module Authify
|
|
25
26
|
compare_salted_sha512(unencrypted_string, secret_key_digest)
|
26
27
|
end
|
27
28
|
|
29
|
+
def set_access!
|
30
|
+
self.access_key = self.class.generate_access_key
|
31
|
+
end
|
32
|
+
|
28
33
|
def set_secret!
|
29
|
-
self.secret_key = self.class.
|
34
|
+
self.secret_key = self.class.generate_secret_key
|
35
|
+
end
|
36
|
+
|
37
|
+
def populate!
|
38
|
+
set_access!
|
39
|
+
set_secret!
|
30
40
|
end
|
31
41
|
|
32
42
|
def self.generate_access_key
|
33
|
-
to_hex(SecureRandom.gen_random(32))[0...
|
43
|
+
to_hex(SecureRandom.gen_random(32))[0...20]
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.generate_secret_key
|
47
|
+
to_hex(SecureRandom.gen_random(32))[0...32] + to_hex(SecureRandom.gen_random(32))[0...32]
|
34
48
|
end
|
35
49
|
|
36
50
|
private
|
@@ -11,9 +11,7 @@ module Authify
|
|
11
11
|
validates_uniqueness_of :email
|
12
12
|
validates_format_of :email, with: /[-a-z0-9_+\.+]+\@([-a-z0-9]+\.)+[a-z0-9]{2,4}/i
|
13
13
|
|
14
|
-
|
15
|
-
|
16
|
-
has_many :api_keys,
|
14
|
+
has_many :apikeys,
|
17
15
|
class_name: 'Authify::API::Models::APIKey',
|
18
16
|
dependent: :destroy
|
19
17
|
|
@@ -44,7 +42,7 @@ module Authify
|
|
44
42
|
end
|
45
43
|
|
46
44
|
def admin_for?(organization)
|
47
|
-
organization.admins.include?(self)
|
45
|
+
admin? || organization.admins.include?(self)
|
48
46
|
end
|
49
47
|
|
50
48
|
def self.from_api_key(access, secret)
|
@@ -6,9 +6,17 @@ module Authify
|
|
6
6
|
include JSONAPI::Serializer
|
7
7
|
|
8
8
|
attribute :name
|
9
|
+
attribute :public_email
|
10
|
+
attribute :gravatar_email
|
11
|
+
attribute :billing_email
|
9
12
|
attribute :description
|
13
|
+
attribute :url
|
14
|
+
attribute :location
|
15
|
+
attribute :created_at
|
16
|
+
|
10
17
|
has_many :groups
|
11
18
|
has_many :users
|
19
|
+
has_many :admins
|
12
20
|
end
|
13
21
|
end
|
14
22
|
end
|