monk-id 1.0.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.
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
@@ -0,0 +1 @@
1
+ lib/**/*.rb
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in monk-id.gemspec
4
+ gemspec
@@ -0,0 +1,40 @@
1
+ Monk ID Ruby
2
+ ============
3
+
4
+ Full API docs: http://monkdev.github.io/monk-id-ruby/Monk/Id.html
5
+
6
+ Add to your `Gemfile`:
7
+
8
+ ```ruby
9
+ gem 'monk-id', :github => 'MonkDev/monk-id-ruby', :branch => 'next'
10
+ ```
11
+
12
+ For Rails and Sinatra, copy `config/monk_id.sample.yml` in this repository to
13
+ `config/monkid.yml` in your app. This will be loaded automatically. All other
14
+ apps need to load their config explicitly:
15
+
16
+ ```ruby
17
+ Monk::Id.load_config('/path/to/monk_id.yml', 'development')
18
+ ```
19
+
20
+ Next, load the payload:
21
+
22
+ ```ruby
23
+ Monk::Id.load_payload(params[:monk_id_payload])
24
+ ```
25
+
26
+ Or, if using the `cookie` option, simply pass in a hash-like cookies object:
27
+
28
+ ```ruby
29
+ Monk::Id.load_payload(cookies)
30
+ ```
31
+
32
+ Then you can access the user's ID and email:
33
+
34
+ ```ruby
35
+ Monk::Id.user_id
36
+ Monk::Id.user_email
37
+ ```
38
+
39
+ `nil` is returned if the user isn't signed in or the payload can't be decoded
40
+ and verified.
@@ -0,0 +1 @@
1
+ require 'bundler/gem_tasks'
@@ -0,0 +1,11 @@
1
+ development:
2
+ app_id: YOUR_APP_ID
3
+ app_secret: YOUR_APP_SECRET
4
+
5
+ staging:
6
+ app_id: YOUR_APP_ID
7
+ app_secret: YOUR_APP_SECRET
8
+
9
+ production:
10
+ app_id: YOUR_APP_ID
11
+ app_secret: YOUR_APP_SECRET
@@ -0,0 +1,186 @@
1
+ require 'base64'
2
+ require 'json'
3
+ require 'openssl'
4
+ require 'yaml'
5
+
6
+ # Global Monk namespace.
7
+ module Monk
8
+ # Integrate Monk ID on the server-side by accessing payloads from the
9
+ # client-side JavaScript.
10
+ #
11
+ # @author Monk Development, Inc.
12
+ module Id
13
+ # Expected path of config file in Rails and Sinatra relative to the app's
14
+ # root directory.
15
+ CONFIG_FILE = 'config/monk_id.yml'.freeze
16
+
17
+ # Name of the cookie that (optionally) stores the payload.
18
+ COOKIE_NAME = '_monkIdPayload'.freeze
19
+
20
+ class << self
21
+ # Load a YAML config file for a specific environment. Rails and Sinatra
22
+ # apps don't need to call this method if the config file is stored at
23
+ # {CONFIG_FILE}, as it's loaded automatically.
24
+ #
25
+ # @param path [String] Path of YAML config file to load. Leave `nil` to
26
+ # read from environment's `MONK_ID_CONFIG` value.
27
+ # @param environment [String] Environment section to use. Leave `nil` to
28
+ # read from environment's `MONK_ID_ENV` value. Defaults to
29
+ # `development`.
30
+ # @raise [StandardError] If the file doesn't exist or can't be read.
31
+ # @return [Hash<String>] Loaded config values.
32
+ def load_config(path = nil, environment = nil)
33
+ if defined? Rails
34
+ path ||= File.join(Rails.root, CONFIG_FILE)
35
+ environment ||= Rails.env
36
+ elsif defined? Sinatra
37
+ path ||= File.join(Sinatra::Application.settings.root, CONFIG_FILE)
38
+ environment ||= Sinatra::Application.settings.environment.to_s
39
+ else
40
+ path ||= ENV['MONK_ID_CONFIG']
41
+ environment ||= ENV['MONK_ID_ENV'] || 'development'
42
+ end
43
+
44
+ config = YAML.load_file(path)[environment]
45
+
46
+ verify_config(config)
47
+
48
+ @@config = config
49
+ end
50
+
51
+ # Get a config value. Attempts to load the config if it hasn't already
52
+ # been loaded.
53
+ #
54
+ # @param key [String] Name of config value.
55
+ # @raise [StandardError] If the config can't be loaded.
56
+ # @return [*] Config value.
57
+ def config(key)
58
+ load_config unless @@config
59
+
60
+ @@config[key]
61
+ end
62
+
63
+ # Load a payload from the client-side.
64
+ #
65
+ # @param encoded_payload [String, #[]] Encoded payload or Hash-like
66
+ # cookies object to automatically load the payload from.
67
+ # @return [Hash<Symbol>] Decoded and verified payload. Empty if there's no
68
+ # payload or it fails verification.
69
+ def load_payload(encoded_payload = nil)
70
+ if encoded_payload.is_a? String
71
+ payload = encoded_payload
72
+ elsif encoded_payload.respond_to? :[]
73
+ payload = encoded_payload[COOKIE_NAME]
74
+ end
75
+
76
+ return @@payload = {} unless payload
77
+
78
+ begin
79
+ payload = decode_payload(payload)
80
+ verified = verify_payload(payload)
81
+ rescue
82
+ verified = false
83
+ end
84
+
85
+ @@payload = verified ? payload : {}
86
+ end
87
+
88
+ # Get the signed in user's UUID.
89
+ #
90
+ # @return [String] If signed in user.
91
+ # @return [nil] If no signed in user.
92
+ def user_id
93
+ payload_user :id
94
+ end
95
+
96
+ # Get the signed in user's email address.
97
+ #
98
+ # @return [String] If signed in user.
99
+ # @return [nil] If no signed in user.
100
+ def user_email
101
+ payload_user :email
102
+ end
103
+
104
+ # Check whether there's a signed in user.
105
+ #
106
+ # @return [Boolean] Whether there's a signed in user.
107
+ def signed_in?
108
+ !!user_id
109
+ end
110
+
111
+ protected
112
+
113
+ # Loaded config values.
114
+ @@config = nil
115
+
116
+ # Loaded payload.
117
+ @@payload = nil
118
+
119
+ # Verify that a config has all the required values.
120
+ #
121
+ # @param config [Hash<String>] Config values.
122
+ # @raise [RuntimeError] If invalid.
123
+ # @return [true] If valid.
124
+ def verify_config(config)
125
+ raise 'no config loaded' unless config
126
+ raise 'no `app_id` config value' unless config['app_id']
127
+ raise 'no `app_secret` config value' unless config['app_secret']
128
+
129
+ true
130
+ end
131
+
132
+ # Decode a payload from the client-side.
133
+ #
134
+ # @param encoded_payload [String] Encoded payload.
135
+ # @raise [JSON::ParserError] If invalid JSON.
136
+ # @return [Hash<Symbol>] Decoded payload.
137
+ def decode_payload(encoded_payload)
138
+ JSON.parse(Base64.decode64(encoded_payload), :symbolize_names => true)
139
+ end
140
+
141
+ # Generate the expected signature of a payload using the app's secret.
142
+ #
143
+ # @param payload [Hash<Symbol>] Decoded payload.
144
+ # @return [String] Expected signature of the payload.
145
+ def expected_signature(payload)
146
+ payload_clone = payload.clone
147
+ payload_clone[:user].delete(:signature)
148
+
149
+ OpenSSL::HMAC.digest(OpenSSL::Digest::SHA512.new, config('app_secret'), JSON.generate(payload_clone[:user]))
150
+ end
151
+
152
+ # Verify that a payload hasn't been tampered with or faked by comparing
153
+ # signatures.
154
+ #
155
+ # @param payload [Hash<Symbol>] Decoded payload.
156
+ # @return [Boolean] Whether the payload is legit.
157
+ def verify_payload(payload)
158
+ signature = Base64.decode64(payload[:user][:signature])
159
+
160
+ signature == expected_signature(payload)
161
+ end
162
+
163
+ # Get the loaded payload.
164
+ #
165
+ # @return [Hash<Symbol>] Loaded payload. Empty if there's no payload or it
166
+ # failed verification.
167
+ def payload
168
+ @@payload || load_payload
169
+ end
170
+
171
+ # Get a value from the `user` hash of the loaded payload.
172
+ #
173
+ # @param key [Symbol] Name of value.
174
+ # @return [*] Requested value or `nil` if not set.
175
+ def payload_user(key)
176
+ payload = self.payload
177
+
178
+ if payload.key?(:user)
179
+ payload[:user][key]
180
+ else
181
+ nil
182
+ end
183
+ end
184
+ end
185
+ end
186
+ end
@@ -0,0 +1,6 @@
1
+ module Monk
2
+ module Id
3
+ # Current version of the library.
4
+ VERSION = '1.0.0'.freeze
5
+ end
6
+ end
@@ -0,0 +1,24 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'monk/id/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'monk-id'
8
+ spec.version = Monk::Id::VERSION.dup
9
+ spec.authors = ['Monk Development, Inc.']
10
+ spec.email = ['support@monkdevelopment.com']
11
+ spec.description = 'Integrate Monk ID on the server-side by accessing payloads from the client-side JavaScript.'
12
+ spec.summary = spec.description
13
+ spec.homepage = 'https://github.com/MonkDev/monk-id-ruby'
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.add_development_dependency 'bundler', '~> 1.3'
22
+ spec.add_development_dependency 'rake'
23
+ spec.add_development_dependency 'yard'
24
+ end
metadata ADDED
@@ -0,0 +1,106 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: monk-id
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Monk Development, Inc.
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2014-02-11 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: bundler
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '1.3'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '1.3'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rake
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: yard
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ description: Integrate Monk ID on the server-side by accessing payloads from the client-side
63
+ JavaScript.
64
+ email:
65
+ - support@monkdevelopment.com
66
+ executables: []
67
+ extensions: []
68
+ extra_rdoc_files: []
69
+ files:
70
+ - .gitignore
71
+ - .yardopts
72
+ - Gemfile
73
+ - README.md
74
+ - Rakefile
75
+ - config/monk_id.sample.yml
76
+ - lib/monk/id.rb
77
+ - lib/monk/id/version.rb
78
+ - monk-id.gemspec
79
+ homepage: https://github.com/MonkDev/monk-id-ruby
80
+ licenses:
81
+ - MIT
82
+ post_install_message:
83
+ rdoc_options: []
84
+ require_paths:
85
+ - lib
86
+ required_ruby_version: !ruby/object:Gem::Requirement
87
+ none: false
88
+ requirements:
89
+ - - ! '>='
90
+ - !ruby/object:Gem::Version
91
+ version: '0'
92
+ required_rubygems_version: !ruby/object:Gem::Requirement
93
+ none: false
94
+ requirements:
95
+ - - ! '>='
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
98
+ requirements: []
99
+ rubyforge_project:
100
+ rubygems_version: 1.8.23
101
+ signing_key:
102
+ specification_version: 3
103
+ summary: Integrate Monk ID on the server-side by accessing payloads from the client-side
104
+ JavaScript.
105
+ test_files: []
106
+ has_rdoc: