goinstant-auth 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.
- checksums.yaml +7 -0
- data/LICENSE +27 -0
- data/README.md +191 -0
- data/lib/goinstant/auth.rb +51 -0
- data/lib/goinstant/auth/signer.rb +140 -0
- data/spec/goinstant/auth/signer_spec.rb +188 -0
- data/spec/spec_helper.rb +6 -0
- metadata +50 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: e57579ef9a598241a353985a3e2196c9356595a8
|
4
|
+
data.tar.gz: 791f93734b346080b6cef9b2f2fb96f89c167de2
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 93cbae429b8bf7f8404159209c383454c998b04e21aa617ce33b816191b7fd6a051369f172cdde902b066e6f1f3668ce5fdec242042faf0bb394438398962ac1
|
7
|
+
data.tar.gz: 948cbffa95ab09c084b18616928aa4b4e8ec83ff5f3164fbc8590e9c53da0ed81b69fa4ec36597374d8f44c0df3830ecfc14559e8aa697a08d182bfcb203fb9d
|
data/LICENSE
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
Copyright (c) 2013, GoInstant Inc., a salesforce.com company
|
2
|
+
All rights reserved.
|
3
|
+
|
4
|
+
Redistribution and use in source and binary forms, with or without modification,
|
5
|
+
are permitted provided that the following conditions are met:
|
6
|
+
|
7
|
+
* Redistributions of source code must retain the above copyright notice, this
|
8
|
+
list of conditions and the following disclaimer.
|
9
|
+
|
10
|
+
* Redistributions in binary form must reproduce the above copyright notice, this
|
11
|
+
list of conditions and the following disclaimer in the documentation and/or
|
12
|
+
other materials provided with the distribution.
|
13
|
+
|
14
|
+
* Neither the name of GoInstant Inc., a salesforce.com company, nor the names
|
15
|
+
of its contributors may be used to endorse or promote products derived from
|
16
|
+
this software without specific prior written permission.
|
17
|
+
|
18
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
19
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
20
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
21
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
22
|
+
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
23
|
+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
24
|
+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
25
|
+
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
26
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
27
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/README.md
ADDED
@@ -0,0 +1,191 @@
|
|
1
|
+
# ruby-goinstant-auth
|
2
|
+
|
3
|
+
GoInstant Authentication for Your Ruby Application
|
4
|
+
|
5
|
+
[](https://magnum.travis-ci.com/goinstant/ruby-goinstant-auth)
|
6
|
+
|
7
|
+
This is an implementation of JWT tokens consistent with what's specified in the
|
8
|
+
[GoInstant Users and Authentication
|
9
|
+
Guide](https://developers.goinstant.com/v1/guides/users_and_authentication.html).
|
10
|
+
|
11
|
+
This library is not intended as a general-use JWT library. For a more general
|
12
|
+
library, see [progrium/ruby-jwt](https://github.com/progrium/ruby-jwt) (but
|
13
|
+
please note its supported version).
|
14
|
+
At the time of this writing, GoInstant supports the [JWT IETF draft
|
15
|
+
version 08](https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-08).
|
16
|
+
|
17
|
+
# Installation
|
18
|
+
|
19
|
+
```sh
|
20
|
+
gem install goinstant-auth
|
21
|
+
```
|
22
|
+
|
23
|
+
# Usage
|
24
|
+
|
25
|
+
Construct a signer with your goinstant application key. The application key
|
26
|
+
should be in base64url or base64 string format. To get your key, go to [your
|
27
|
+
goinstant dashboard](https://goinstant.com/dashboard) and click on your App.
|
28
|
+
|
29
|
+
:warning: **Remember, the Secret Key needs to be treated like a password!**
|
30
|
+
Never share it with your users!
|
31
|
+
|
32
|
+
```ruby
|
33
|
+
require 'goinstant/auth'
|
34
|
+
|
35
|
+
signer = GoInstant::Auth::Signer.new(secret_key)
|
36
|
+
```
|
37
|
+
|
38
|
+
You can then use this `signer` to create as many tokens as you want. The
|
39
|
+
`:domain` parameter should be replaced with your website's domain. Groups are
|
40
|
+
optional.
|
41
|
+
|
42
|
+
```ruby
|
43
|
+
jwt = signer.sign({
|
44
|
+
:domain => 'example.com', # TODO: replace me
|
45
|
+
:id => user.id,
|
46
|
+
:display_name => user.full_name,
|
47
|
+
:groups => [
|
48
|
+
{
|
49
|
+
:id => 'room-42',
|
50
|
+
:display_name => 'Room 42 ACL Group'
|
51
|
+
}
|
52
|
+
]
|
53
|
+
})
|
54
|
+
```
|
55
|
+
|
56
|
+
This token can be safely inlined into an ERB template. For example, a fairly
|
57
|
+
basic template for calling [`goinstant.connect`
|
58
|
+
call](https://developers.goinstant.com/v1/javascript_api/connect.html) might
|
59
|
+
look like this:
|
60
|
+
|
61
|
+
```html
|
62
|
+
<script type="text/javascript">
|
63
|
+
(function() {
|
64
|
+
var token = "<%= token %>";
|
65
|
+
var url = 'https://goinstant.net/YOURACCOUNT/YOURAPP'
|
66
|
+
|
67
|
+
var opts = {
|
68
|
+
user: token,
|
69
|
+
rooms: [ 'room-42' ]
|
70
|
+
};
|
71
|
+
|
72
|
+
goinstant.connect(url, opts, function(err, conn, room) {
|
73
|
+
if (err) {
|
74
|
+
throw err;
|
75
|
+
}
|
76
|
+
runYourApp(room);
|
77
|
+
});
|
78
|
+
}());
|
79
|
+
</script>
|
80
|
+
```
|
81
|
+
|
82
|
+
# Methods
|
83
|
+
|
84
|
+
## `GoInstant::Auth::Signer`
|
85
|
+
|
86
|
+
Re-usable object for creating user tokens.
|
87
|
+
|
88
|
+
### `.new(secret_key)`
|
89
|
+
|
90
|
+
Constructs a `Signer` object from a base64url or base64 secret key string.
|
91
|
+
|
92
|
+
Throws an Error if the `secretKey` could not be parsed.
|
93
|
+
|
94
|
+
### `#sign(user_data, extra_headers={})`
|
95
|
+
|
96
|
+
Creates a JWT as a JWS in Compact Serialization format. Can be called multiple
|
97
|
+
times on the same object, saving you from having to load your secret GoInstant
|
98
|
+
application key every time.
|
99
|
+
|
100
|
+
`user_data` is a Hash with the following required fields, plus any other
|
101
|
+
custom ones you want to include in the JWT.
|
102
|
+
|
103
|
+
- `:domain` - the domain of your website
|
104
|
+
- `:id` - the unique, permanent identity of this user on your website
|
105
|
+
- `:display_name` - the name to initially display for this user
|
106
|
+
- `:groups` - an array of groups, each group requiring:
|
107
|
+
- `:id` - the unique ID of this group, which is handy for defining [GoInstant ACLs](https://developers.goinstant.com/v1/guides/creating_and_managing_acl.html)
|
108
|
+
- `:display_name` - the name to display for this group
|
109
|
+
|
110
|
+
`extra_headers` is completely optional. It's used to define any additional
|
111
|
+
[JWS header fields](http://tools.ietf.org/html/draft-ietf-jose-json-web-signature-11#section-4.1)
|
112
|
+
that you want to include.
|
113
|
+
|
114
|
+
# Technicals
|
115
|
+
|
116
|
+
The `sign()` method's `user_data` maps to the following JWT claims.
|
117
|
+
The authoritative list of claims used in GoInstant can be found in the [Users and Authentication Guide](https://developers.goinstant.com/v1/guides/users_and_authentication.html#which-reserved-claims-are-required).
|
118
|
+
|
119
|
+
- `:domain` -> `iss` (standard claim)
|
120
|
+
- `:id` -> `sub` (standard claim)
|
121
|
+
- `:display_name` -> `dn` (GoInstant private claim)
|
122
|
+
- `:groups` -> `g` (GoInstant private claim)
|
123
|
+
- `:id` -> `id` (GoInstant private claim)
|
124
|
+
- `:display_name` -> `dn` (GoInstant private claim)
|
125
|
+
- `'goinstant.net'` -> `aud` (standard claim) _automatically added_
|
126
|
+
|
127
|
+
For the `extra_headers` parameter in `sign()`, the `alg` and `typ` headers will
|
128
|
+
be overridden by this library.
|
129
|
+
|
130
|
+
# Contributing
|
131
|
+
|
132
|
+
## Development Dependencies
|
133
|
+
|
134
|
+
Base dependencies are as follows.
|
135
|
+
|
136
|
+
- [ruby](https://www.ruby-lang.org/en/downloads/) >= 1.9.2
|
137
|
+
- [rubygems](https://rubygems.org/pages/download) >= 2.1
|
138
|
+
|
139
|
+
The following gems should then be installed via `gem install`:
|
140
|
+
|
141
|
+
- [bundler](http://bundler.io/) >= 1.5
|
142
|
+
- [rake](http://rake.rubyforge.org/) >= 10.1
|
143
|
+
|
144
|
+
## Set-up
|
145
|
+
|
146
|
+
```sh
|
147
|
+
git clone git@github.com:goinstant/ruby-goinstant-auth.git
|
148
|
+
cd ruby-goinstant-auth
|
149
|
+
|
150
|
+
# if you haven't already:
|
151
|
+
gem install bundler
|
152
|
+
gem install rake
|
153
|
+
|
154
|
+
# then, install dev dependencies:
|
155
|
+
bundle install
|
156
|
+
```
|
157
|
+
|
158
|
+
## Testing
|
159
|
+
|
160
|
+
Testing is done with RSpec. Tests are located in the `spec/` folder.
|
161
|
+
|
162
|
+
```sh
|
163
|
+
rake # 'test' is the default
|
164
|
+
```
|
165
|
+
|
166
|
+
## Developer Documentation
|
167
|
+
|
168
|
+
You can view the documentation generated by `yard` easily:
|
169
|
+
|
170
|
+
```sh
|
171
|
+
gem install yard
|
172
|
+
rake doc
|
173
|
+
open doc/frames.html
|
174
|
+
```
|
175
|
+
|
176
|
+
# Support
|
177
|
+
|
178
|
+
Email [GoInstant Support](mailto:support@goinstant.com) or stop by [#goinstant
|
179
|
+
on freenode](irc://irc.freenode.net/#goinstant).
|
180
|
+
|
181
|
+
For responsible disclosures, email [GoInstant Security](mailto:security@goinstant.com).
|
182
|
+
|
183
|
+
To [file a bug](https://github.com/goinstant/node-goinstant-auth/issues) or
|
184
|
+
[propose a patch](https://github.com/goinstant/node-goinstant-auth/pulls),
|
185
|
+
please use github directly.
|
186
|
+
|
187
|
+
# Legal
|
188
|
+
|
189
|
+
© 2013 GoInstant Inc., a salesforce.com company. All Rights Reserved.
|
190
|
+
|
191
|
+
Licensed under the 3-clause BSD license
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'base64'
|
3
|
+
require_relative 'auth/signer'
|
4
|
+
|
5
|
+
# GoInstant Ruby Modules
|
6
|
+
module GoInstant
|
7
|
+
|
8
|
+
# Authentication classes and functions for use with GoInstant
|
9
|
+
module Auth
|
10
|
+
|
11
|
+
# Pads base64 strings.
|
12
|
+
# @param str [String]
|
13
|
+
# @return [String] padded (extra '=' added to multiple of 4).
|
14
|
+
def self.pad64(str)
|
15
|
+
rem = str.size % 4
|
16
|
+
if rem > 0 then
|
17
|
+
str << ("=" * (4-rem))
|
18
|
+
end
|
19
|
+
return str
|
20
|
+
end
|
21
|
+
|
22
|
+
# Encodes base64 and base64url encoded strings.
|
23
|
+
# @param str [String]
|
24
|
+
# @return [String]
|
25
|
+
def self.encode64(str)
|
26
|
+
return Base64.urlsafe_encode64(str).sub(/=+$/,'')
|
27
|
+
end
|
28
|
+
|
29
|
+
# Decodes base64 and base64url encoded strings.
|
30
|
+
# @param str [String]
|
31
|
+
# @return [String]
|
32
|
+
def self.decode64(str)
|
33
|
+
str = str.gsub(/\s+/,'').tr('-_','+/').sub(/=+$/,'')
|
34
|
+
return Base64.decode64(pad64(str))
|
35
|
+
end
|
36
|
+
|
37
|
+
# Decode the Compact Serialization of a thing.
|
38
|
+
# @param str [String]
|
39
|
+
# @return [Hash|String|Object]
|
40
|
+
def self.compact_decode(str)
|
41
|
+
return JSON.parse(decode64(str))
|
42
|
+
end
|
43
|
+
|
44
|
+
# Create a Compact Serialization of a thing.
|
45
|
+
# @param thing [Hash|String|Object] anything, passed to JSON.generate.
|
46
|
+
# @return [String]
|
47
|
+
def self.compact_encode(thing)
|
48
|
+
return encode64(JSON.generate(thing))
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
#encoding: UTF-8
|
2
|
+
require 'openssl'
|
3
|
+
|
4
|
+
module GoInstant
|
5
|
+
module Auth
|
6
|
+
|
7
|
+
# Thrown when a Signer has problems with its input.
|
8
|
+
class SignerError < StandardError
|
9
|
+
end
|
10
|
+
|
11
|
+
# Creates JWTs from user-hashes, signing with your GoInstant app secret key.
|
12
|
+
class Signer
|
13
|
+
|
14
|
+
# Create a Signer with a particular key.
|
15
|
+
#
|
16
|
+
# A single Signer can be used to create multiple tokens.
|
17
|
+
#
|
18
|
+
# @param secret_key [String] A base64 or base64url format string
|
19
|
+
# representing the secret key for your GoInstant App.
|
20
|
+
#
|
21
|
+
# @raise [TypeError] when the key isn't in base64/base64url format.
|
22
|
+
# @raise [StandardError] when the key is too short
|
23
|
+
#
|
24
|
+
def initialize(secret_key)
|
25
|
+
if secret_key.nil? then
|
26
|
+
raise TypeError.new('Signer requires key in base64url or base64 format')
|
27
|
+
end
|
28
|
+
|
29
|
+
@binary_key = Auth.decode64(secret_key)
|
30
|
+
if !@binary_key or @binary_key == '' then
|
31
|
+
raise TypeError.new('Signer requires key in base64url or base64 format')
|
32
|
+
end
|
33
|
+
|
34
|
+
if @binary_key.size < 32 then
|
35
|
+
raise StandardError.new(
|
36
|
+
'expected key length >= 32 bytes, got %d bytes' % @binary_key.size
|
37
|
+
)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Create and sign a token for a user.
|
42
|
+
#
|
43
|
+
# @param user_data [Hash] properties about this user.
|
44
|
+
# See README.md for a complete list of options.
|
45
|
+
# @param extra_headers [Hash] Optional, additional JWT headers to include.
|
46
|
+
#
|
47
|
+
# @raise [SignerError] if a required user or group claim is missing
|
48
|
+
#
|
49
|
+
# @return [String] a JWS Compact Serialization format-string representing this user.
|
50
|
+
#
|
51
|
+
def sign(user_data, extra_headers={})
|
52
|
+
if !user_data.is_a?(Hash) then
|
53
|
+
raise SignerError.new('Signer#sign() requires a user_data Hash')
|
54
|
+
end
|
55
|
+
claims = user_data.clone
|
56
|
+
Signer.map_required_claims(claims, REQUIRED_CLAIMS)
|
57
|
+
Signer.map_optional_claims(claims, OPTIONAL_CLAIMS)
|
58
|
+
claims[:aud] = 'goinstant.net'
|
59
|
+
|
60
|
+
if claims.has_key?(:g) then
|
61
|
+
groups = claims[:g]
|
62
|
+
if !groups.is_a?(Array) then
|
63
|
+
raise SignerError.new('groups must be an Array')
|
64
|
+
end
|
65
|
+
i = 0
|
66
|
+
claims[:g] = groups.map do |group|
|
67
|
+
group = group.clone
|
68
|
+
msg = "group #{i} missing required key: %s"
|
69
|
+
i += 1
|
70
|
+
Signer.map_required_claims(group, REQUIRED_GROUP_CLAIMS, msg)
|
71
|
+
end
|
72
|
+
else
|
73
|
+
claims[:g] = []
|
74
|
+
end
|
75
|
+
|
76
|
+
headers = extra_headers.clone
|
77
|
+
headers[:typ] = 'JWT'
|
78
|
+
headers[:alg] = 'HS256'
|
79
|
+
|
80
|
+
signing_input = '%s.%s' % [headers, claims].map{ |x| Auth.compact_encode(x) }
|
81
|
+
sig = OpenSSL::HMAC::digest('SHA256', @binary_key, signing_input)
|
82
|
+
return '%s.%s' % [ signing_input, Auth.encode64(sig) ]
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
# Required user_data properties and their corresponding JWT claim name
|
88
|
+
#
|
89
|
+
REQUIRED_CLAIMS = {
|
90
|
+
:domain => :iss,
|
91
|
+
:id => :sub,
|
92
|
+
:display_name => :dn
|
93
|
+
}
|
94
|
+
|
95
|
+
# Optional user_data properties and their corresponding JWT claim name
|
96
|
+
#
|
97
|
+
OPTIONAL_CLAIMS = {
|
98
|
+
:groups => :g
|
99
|
+
}
|
100
|
+
|
101
|
+
# Required group properties and their corresponding JWT claim name
|
102
|
+
#
|
103
|
+
REQUIRED_GROUP_CLAIMS = {
|
104
|
+
:id => :id,
|
105
|
+
:display_name => :dn
|
106
|
+
}
|
107
|
+
|
108
|
+
# Maps required claims, mutating in-place (caller should clone).
|
109
|
+
#
|
110
|
+
# @raise [SignerError] if a required claim is missing
|
111
|
+
# @param claims [Hash] modified to use JWT claim names
|
112
|
+
# @param table [Hash] conversion table of required keys
|
113
|
+
# @param msg [String] message format for the SignerError
|
114
|
+
def self.map_required_claims(claims, table, msg="missing required key: %s")
|
115
|
+
table.each do |name,claimName|
|
116
|
+
if !claims.has_key?(name) then
|
117
|
+
raise SignerError.new(msg % name)
|
118
|
+
end
|
119
|
+
claims[claimName] = claims.delete(name)
|
120
|
+
end
|
121
|
+
return claims
|
122
|
+
end
|
123
|
+
|
124
|
+
# Maps optional claims, mutating in-place (caller should clone).
|
125
|
+
#
|
126
|
+
# @param claims [Hash] modified to use JWT claim names
|
127
|
+
# @param table [Hash] conversion table of optional keys
|
128
|
+
def self.map_optional_claims(claims, table)
|
129
|
+
table.each do |name,claimName|
|
130
|
+
if claims.has_key?(name) then
|
131
|
+
claims[claimName] = claims.delete(name)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
return claims
|
135
|
+
end
|
136
|
+
|
137
|
+
end
|
138
|
+
|
139
|
+
end
|
140
|
+
end
|
@@ -0,0 +1,188 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'rspec'
|
3
|
+
|
4
|
+
describe GoInstant::Auth::Signer do
|
5
|
+
it "doesn't accept nil" do
|
6
|
+
expect {
|
7
|
+
GoInstant::Auth::Signer.new(nil)
|
8
|
+
}.to raise_error(TypeError, 'Signer requires key in base64url or base64 format')
|
9
|
+
end
|
10
|
+
|
11
|
+
it "needs base64" do
|
12
|
+
expect {
|
13
|
+
GoInstant::Auth::Signer.new('!@#$%^&*()')
|
14
|
+
}.to raise_error(TypeError, 'Signer requires key in base64url or base64 format')
|
15
|
+
end
|
16
|
+
|
17
|
+
it "decodes base64url" do
|
18
|
+
signer = GoInstant::Auth::Signer.new('HKYdFdnezle2yrI2_Ph3cHz144bISk-cvuAbeAAA999')
|
19
|
+
signer.should_not == nil
|
20
|
+
end
|
21
|
+
|
22
|
+
it "decodes base64" do
|
23
|
+
signer = GoInstant::Auth::Signer.new('HKYdFdnezle2yrI2/Ph3cHz144bISk+cvuAbeAAA999')
|
24
|
+
signer.should_not == nil
|
25
|
+
end
|
26
|
+
|
27
|
+
it "handles base64 with padding too" do
|
28
|
+
signer = GoInstant::Auth::Signer.new('HKYdFdnezle2yrI2/Ph3cHz144bISk+cvuAbeAAA999=')
|
29
|
+
signer.should_not == nil
|
30
|
+
end
|
31
|
+
|
32
|
+
it "can compact serialization encode" do
|
33
|
+
str = GoInstant::Auth.compact_encode({ :asdf => 42 })
|
34
|
+
str.should == 'eyJhc2RmIjo0Mn0' # note: no padding
|
35
|
+
decoded = GoInstant::Auth.compact_decode(str)
|
36
|
+
expect(decoded).to be_a_kind_of(Hash)
|
37
|
+
end
|
38
|
+
|
39
|
+
it "complains if too short" do
|
40
|
+
expect {
|
41
|
+
GoInstant::Auth::Signer.new('abcd')
|
42
|
+
}.to raise_error(StandardError, 'expected key length >= 32 bytes, got 3 bytes')
|
43
|
+
end
|
44
|
+
|
45
|
+
def validate_jwt(jwt, expect_claims, expect_sig)
|
46
|
+
parts = jwt.split(/\./)
|
47
|
+
parts.size.should == 3
|
48
|
+
|
49
|
+
header = GoInstant::Auth.compact_decode(parts[0])
|
50
|
+
header['typ'].should == 'JWT'
|
51
|
+
header['alg'].should == 'HS256'
|
52
|
+
|
53
|
+
claims = GoInstant::Auth.compact_decode(parts[1])
|
54
|
+
expect(claims).to eql(expect_claims)
|
55
|
+
|
56
|
+
sig = parts[2]
|
57
|
+
sig.size.should == 43
|
58
|
+
sig.should == expect_sig
|
59
|
+
end
|
60
|
+
|
61
|
+
context "with valid key," do
|
62
|
+
before do
|
63
|
+
secret_key = 'HKYdFdnezle2yrI2_Ph3cHz144bISk-cvuAbeAAA999'
|
64
|
+
@signer = GoInstant::Auth::Signer.new(secret_key)
|
65
|
+
end
|
66
|
+
|
67
|
+
it "won't accept string user_data" do
|
68
|
+
expect {
|
69
|
+
@signer.sign('asdfasdf')
|
70
|
+
}.to raise_error(GoInstant::Auth::SignerError, 'Signer#sign() requires a user_data Hash')
|
71
|
+
end
|
72
|
+
|
73
|
+
it "needs user_data to have an id" do
|
74
|
+
user_data = {
|
75
|
+
:domain => 'example.com',
|
76
|
+
:display_name => 'bob'
|
77
|
+
}
|
78
|
+
expect {
|
79
|
+
@signer.sign(user_data)
|
80
|
+
}.to raise_error(GoInstant::Auth::SignerError, 'missing required key: id')
|
81
|
+
end
|
82
|
+
|
83
|
+
it "needs user_data to have a display_name" do
|
84
|
+
user_data = {
|
85
|
+
:id => 'bar',
|
86
|
+
:domain => 'example.com'
|
87
|
+
}
|
88
|
+
expect {
|
89
|
+
@signer.sign(user_data)
|
90
|
+
}.to raise_error(GoInstant::Auth::SignerError, 'missing required key: display_name')
|
91
|
+
end
|
92
|
+
|
93
|
+
it "needs user_data to have a domain" do
|
94
|
+
user_data = {
|
95
|
+
:id => 'bar',
|
96
|
+
:display_name => 'bob'
|
97
|
+
}
|
98
|
+
expect {
|
99
|
+
@signer.sign(user_data)
|
100
|
+
}.to raise_error(GoInstant::Auth::SignerError, 'missing required key: domain')
|
101
|
+
end
|
102
|
+
|
103
|
+
it "checks that groups is an array, if present" do
|
104
|
+
user_data = {
|
105
|
+
:id => 'bar',
|
106
|
+
:domain => 'example.com',
|
107
|
+
:display_name => 'bob',
|
108
|
+
:groups => 'nope'
|
109
|
+
}
|
110
|
+
expect {
|
111
|
+
@signer.sign(user_data)
|
112
|
+
}.to raise_error(GoInstant::Auth::SignerError, 'groups must be an Array')
|
113
|
+
end
|
114
|
+
|
115
|
+
it "happily signs without groups" do
|
116
|
+
user_data = {
|
117
|
+
:id => 'bar',
|
118
|
+
:domain => 'example.com',
|
119
|
+
:display_name => 'bob'
|
120
|
+
}
|
121
|
+
|
122
|
+
jwt = @signer.sign(user_data)
|
123
|
+
validate_jwt(jwt, {
|
124
|
+
'aud' => 'goinstant.net',
|
125
|
+
'sub' => 'bar',
|
126
|
+
'iss' => 'example.com',
|
127
|
+
'dn' => 'bob',
|
128
|
+
'g' => []
|
129
|
+
}, 'GtNNsSjgB4ubwW4aFQlgT2E1F8UO7VMxf7ppXmBRlGc')
|
130
|
+
end
|
131
|
+
|
132
|
+
it 'needs groups to have an id' do
|
133
|
+
user_data = {
|
134
|
+
:id => 'bar',
|
135
|
+
:domain => 'example.com',
|
136
|
+
:display_name => 'bob',
|
137
|
+
:groups => [
|
138
|
+
{ :display_name => 'MyGroup' }
|
139
|
+
]
|
140
|
+
}
|
141
|
+
|
142
|
+
expect {
|
143
|
+
@signer.sign(user_data)
|
144
|
+
}.to raise_error(GoInstant::Auth::SignerError, 'group 0 missing required key: id')
|
145
|
+
end
|
146
|
+
|
147
|
+
it 'needs groups to have a display_name' do
|
148
|
+
user_data = {
|
149
|
+
:id => 'bar',
|
150
|
+
:domain => 'example.com',
|
151
|
+
:display_name => 'bob',
|
152
|
+
:groups => [
|
153
|
+
{ :id => 99, :display_name => 'Gretzky Lovers' },
|
154
|
+
{ :id => 1234 }
|
155
|
+
]
|
156
|
+
}
|
157
|
+
|
158
|
+
expect {
|
159
|
+
@signer.sign(user_data)
|
160
|
+
}.to raise_error(GoInstant::Auth::SignerError, 'group 1 missing required key: display_name')
|
161
|
+
end
|
162
|
+
|
163
|
+
it 'happily signs with groups' do
|
164
|
+
user_data = {
|
165
|
+
:id => 'bar',
|
166
|
+
:domain => 'example.com',
|
167
|
+
:display_name => 'bob',
|
168
|
+
:groups => [
|
169
|
+
{ :id => 1234, :display_name => 'Group 1234' },
|
170
|
+
{ :id => 42, :display_name => 'Meaning Group' }
|
171
|
+
]
|
172
|
+
}
|
173
|
+
|
174
|
+
jwt = @signer.sign(user_data)
|
175
|
+
validate_jwt(jwt, {
|
176
|
+
'aud' => 'goinstant.net',
|
177
|
+
'sub' => 'bar',
|
178
|
+
'iss' => 'example.com',
|
179
|
+
'dn' => 'bob',
|
180
|
+
'g' => [
|
181
|
+
{ 'id' => 1234, 'dn' => 'Group 1234' },
|
182
|
+
{ 'id' => 42, 'dn' => 'Meaning Group' }
|
183
|
+
]
|
184
|
+
}, '5isd3i1A4so7MwKm0VHWYHuWRy3WwGFipO0kkelNRLU')
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: goinstant-auth
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- GoInstant Inc.
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-11-19 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: Lets your users log-in to your GoInstant app
|
14
|
+
email: support@goinstant.org
|
15
|
+
executables: []
|
16
|
+
extensions: []
|
17
|
+
extra_rdoc_files: []
|
18
|
+
files:
|
19
|
+
- lib/goinstant/auth/signer.rb
|
20
|
+
- lib/goinstant/auth.rb
|
21
|
+
- spec/goinstant/auth/signer_spec.rb
|
22
|
+
- spec/spec_helper.rb
|
23
|
+
- LICENSE
|
24
|
+
- README.md
|
25
|
+
homepage: https://github.com/goinstant/ruby-goinstant-auth
|
26
|
+
licenses:
|
27
|
+
- BSD-3-Clause
|
28
|
+
metadata: {}
|
29
|
+
post_install_message:
|
30
|
+
rdoc_options: []
|
31
|
+
require_paths:
|
32
|
+
- lib
|
33
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
34
|
+
requirements:
|
35
|
+
- - '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
39
|
+
requirements:
|
40
|
+
- - '>='
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: '0'
|
43
|
+
requirements: []
|
44
|
+
rubyforge_project:
|
45
|
+
rubygems_version: 2.0.3
|
46
|
+
signing_key:
|
47
|
+
specification_version: 4
|
48
|
+
summary: GoInstant Authentication for Your Ruby Application
|
49
|
+
test_files: []
|
50
|
+
has_rdoc:
|