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.
Files changed (44) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +9 -0
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +23 -0
  5. data/.travis.yml +13 -0
  6. data/Gemfile +4 -0
  7. data/LICENSE.txt +21 -0
  8. data/README.md +41 -0
  9. data/Rakefile +38 -0
  10. data/authify-api.gemspec +45 -0
  11. data/bin/console +14 -0
  12. data/bin/setup +8 -0
  13. data/config.ru +9 -0
  14. data/db/migrate/20170201213901_create_users.rb +12 -0
  15. data/db/migrate/20170201220029_create_api_keys.rb +12 -0
  16. data/db/migrate/20170202004557_create_identities.rb +12 -0
  17. data/db/migrate/20170203231922_create_organizations_and_organization_memberships.rb +22 -0
  18. data/db/migrate/20170203231929_create_groups.rb +17 -0
  19. data/db/migrate/20170204001405_create_trusted_delegates.rb +13 -0
  20. data/db/schema.rb +95 -0
  21. data/lib/authify/api.rb +51 -0
  22. data/lib/authify/api/controllers/api_key.rb +36 -0
  23. data/lib/authify/api/controllers/group.rb +40 -0
  24. data/lib/authify/api/controllers/organization.rb +48 -0
  25. data/lib/authify/api/controllers/user.rb +72 -0
  26. data/lib/authify/api/helpers/api_user.rb +30 -0
  27. data/lib/authify/api/helpers/jwt_encryption.rb +42 -0
  28. data/lib/authify/api/jsonapi_utils.rb +12 -0
  29. data/lib/authify/api/models/api_key.rb +44 -0
  30. data/lib/authify/api/models/group.rb +17 -0
  31. data/lib/authify/api/models/identity.rb +14 -0
  32. data/lib/authify/api/models/organization.rb +26 -0
  33. data/lib/authify/api/models/organization_membership.rb +16 -0
  34. data/lib/authify/api/models/trusted_delegate.rb +44 -0
  35. data/lib/authify/api/models/user.rb +73 -0
  36. data/lib/authify/api/serializers/api_key_serializer.rb +13 -0
  37. data/lib/authify/api/serializers/group_serializer.rb +15 -0
  38. data/lib/authify/api/serializers/organization_serializer.rb +15 -0
  39. data/lib/authify/api/serializers/user_serializer.rb +17 -0
  40. data/lib/authify/api/service.rb +11 -0
  41. data/lib/authify/api/services/api.rb +58 -0
  42. data/lib/authify/api/services/jwt_provider.rb +61 -0
  43. data/lib/authify/api/version.rb +9 -0
  44. metadata +324 -0
@@ -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