authify-api 0.0.5

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