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