passageidentity 0.0.1 → 0.0.4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8c1466ff91a157560360908cbf7f037c11f14e7d4b5c7385ef8e8a41f8e33894
4
- data.tar.gz: 2edd43607d2994a65554d5e878a946ead5f1f26b0ad5b20b93351bd3dc4453a1
3
+ metadata.gz: d5dd45f9023ac4cd3eb005f4604f40229981aeca5c6da2ebd1eca685fcf54245
4
+ data.tar.gz: de14152791bbe75fb11de58d65cf72bab61f570bec22f93240f7a88c128d8eaa
5
5
  SHA512:
6
- metadata.gz: 7ffcdb146558eb0df9664feaa13fe1f04388a7bad176330d863b588f881086567a0fbcf50c59c0f4831750284946d7cfd480d7bfe69f9e6ea3954bb640e2cd75
7
- data.tar.gz: 891d8a07de1f8522cce169a64bbaf211604600b67c61166ea358e3615b33a95baa447de68c271ae53bbd4c78ee0f9916f85635b8e7f69e2899cfd81b50673ad6
6
+ metadata.gz: d7184b5a3325f54c022a0ba63c622ad38670793eeb9c3cabadb0c28c4ee9b9675f63d302a93449f1d5eac43cecbbdf0bb82c0bbb1d06132caf3d3a99f552d3d1
7
+ data.tar.gz: 65e9721e3ca3049a758eb0287f5f379b7e27ee43397cb75592c2875194155a9d83d95c47244716d9de09d88fb6bb9ae33115d4f45649d6095adfb15c5b562942
data/.gitignore ADDED
@@ -0,0 +1,58 @@
1
+ *.gem
2
+ *.rbc
3
+ /.config
4
+ /coverage/
5
+ /InstalledFiles
6
+ /pkg/
7
+ /spec/reports/
8
+ /spec/examples.txt
9
+ /test/tmp/
10
+ /test/version_tmp/
11
+ /tmp/
12
+
13
+ /tests/environment.rb
14
+
15
+ # Used by dotenv library to load environment variables.
16
+ # .env
17
+
18
+ # Ignore Byebug command history file.
19
+ .byebug_history
20
+
21
+ ## Specific to RubyMotion:
22
+ .dat*
23
+ .repl_history
24
+ build/
25
+ *.bridgesupport
26
+ build-iPhoneOS/
27
+ build-iPhoneSimulator/
28
+
29
+ ## Specific to RubyMotion (use of CocoaPods):
30
+ #
31
+ # We recommend against adding the Pods directory to your .gitignore. However
32
+ # you should judge for yourself, the pros and cons are mentioned at:
33
+ # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
34
+ #
35
+ # vendor/Pods/
36
+
37
+ ## Documentation cache and generated files:
38
+ /.yardoc/
39
+ /_yardoc/
40
+ /doc/
41
+ /rdoc/
42
+
43
+ ## Environment normalization:
44
+ /.bundle/
45
+ /vendor/bundle
46
+ /lib/bundler/man/
47
+
48
+ # for a library or gem, you might want to ignore these files since the code is
49
+ # intended to run in multiple environments; otherwise, check them in:
50
+ # Gemfile.lock
51
+ # .ruby-version
52
+ # .ruby-gemset
53
+
54
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
55
+ .rvmrc
56
+
57
+ # Used by RuboCop. Remote config files pulled in from inherit_from directive.
58
+ # .rubocop-https?--*
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,71 @@
1
+ # Ruby SDK for Passage
2
+
3
+ ## Testing the gem locally
4
+
5
+ Install the gem
6
+
7
+ ```
8
+ gem build passageidentity.gemspec
9
+ gem install ./passageidentity-0.0.1.gem
10
+ ```
11
+
12
+ Test it out:
13
+
14
+ ```
15
+ irb -Ilib -rpassageidentity
16
+ >> passage = Passage::Client.new(app_id: 'YOUR_APP_ID')
17
+ >> passage.auth.authenticate("JWT_HERE")
18
+ <passage_user_id>
19
+ ```
20
+
21
+ Run Tests:
22
+
23
+ ```
24
+ # all tests
25
+ ruby tests/all.rb
26
+ # individual test files
27
+ ruby tests/*_test.rb
28
+ ```
29
+
30
+ To test in the example app, change the Gemfile to include this path:
31
+
32
+ ```
33
+ gem "passageidentity", path: "../../passage-ruby"
34
+ ```
35
+
36
+ ## Publishing
37
+
38
+ Create an account in rubygems.org then run the following command with your username.
39
+
40
+ ```
41
+ $ curl -u <username> https://rubygems.org/api/v1/api_key.yaml >
42
+ ~/.gem/credentials; chmod 0600 ~/.gem/credentials
43
+
44
+ Enter host password for user '<username>':
45
+ ```
46
+
47
+ ```
48
+ <<<<<<< HEAD
49
+ gem push passage-0.0.0.gem
50
+ ```
51
+
52
+ You can check for the gem here:
53
+
54
+ ```
55
+ gem list -r passage
56
+ ```
57
+
58
+ =======
59
+ gem push passageidentity-0.0.0.gem
60
+
61
+ ```
62
+
63
+ You can check for the gem here:
64
+
65
+ ```
66
+
67
+ gem list -r passageidentity
68
+
69
+ ```
70
+ >>>>>>> 2d0e3f6dc3b40c621c8d16506fa6ab43b0fba673
71
+ ```
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2022 Passage
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,209 @@
1
+ <img src="https://storage.googleapis.com/passage-docs/passage-logo-gradient.svg" alt="Passage logo" style="width:250px;"/>
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/passageidentity.svg)](https://badge.fury.io/rb/passageidentity)
4
+
5
+ # passage-ruby
6
+
7
+ This Ruby SDK allows for verification of server-side authentication and user management for Ruby applications build with [Passage](https://passage.id).
8
+
9
+ Install this package using [RubyGems](https://rubygems.org/gems/passageidentity).
10
+
11
+ ```
12
+ gem install passageidentity
13
+ ```
14
+
15
+ ## Instantiating the Passage Class
16
+
17
+ Initialize the Passage Client as follows:
18
+
19
+ ```ruby
20
+ PassageClient =
21
+ Passage::Client.new(
22
+ app_id: 'YOUR APP ID',
23
+ api_key: 'YOUR APIKEY',
24
+ auth_strategy: Passage::HEADER_AUTH,
25
+ )
26
+ ```
27
+
28
+ Passage has three arguments that can be used for initialization: `app_id`, `api_key`, and `auth_strategy`.
29
+
30
+ - `app_id` is the Passage App ID that specifies which app should be authorized. It has no default value and must to be set upon initialization.
31
+ - `api_key` is an API key for the Passage app, which can be generated in the 'App Settings' section of the [Passage Console](https://console.passage.id). It is an optional parameter and not required for authenticating requests. It is required to get or update user information.
32
+ - `auth_strategy` defines where the Passage SDK should look for the authentication token. It is set by default to `Passage::COOKIE_AUTH`, but can be changed to `Passage::HEADER_AUTH`.
33
+
34
+ ## Authenticating a Request
35
+
36
+ To authenticate an HTTP request in a Rails application, you can use the Passage library in a middleware function. You need to provide Passage with your App ID in order to verify the JWTs.
37
+
38
+ ```ruby
39
+ require 'passageidentity'
40
+
41
+ class ApplicationController < ActionController::Base
42
+ PassageClient = Passage::Client.new(app_id: PASSAGE_APP_ID)
43
+ def authorize!
44
+ begin
45
+ user_id = PassageClient.auth.authenticate_request(request)
46
+ # user is authorized
47
+ rescue Exception => e
48
+ # handle exception (user is not authorized)
49
+ end
50
+ end
51
+ end
52
+ ```
53
+
54
+ ## Retrieve User Info
55
+
56
+ To retrieve information about a user, you should use the `get` method. You will need to use a Passage API key, which can be created in the Passage Console under your Application Settings. This API key grants your web server access to the Passage management APIs to get and update information about users. This API key must be protected and stored in an appropriate secure storage location. It should never be hard-coded in the repository.
57
+
58
+ ```ruby
59
+ require 'passageidentity'
60
+
61
+ class ApplicationController < ActionController::Base
62
+ PassageClient =
63
+ Passage::Client.new(app_id: PASSAGE_APP_ID, api_key: PASSAGE_API_KEY)
64
+ def authorize!
65
+ begin
66
+ user_id = PassageClient.auth.authenticate_request(request)
67
+ user = PassageClient.user.get(user_id: @user_id)
68
+ # user is authorized
69
+ rescue Exception => e
70
+ # handle exception (user is not authorized)
71
+ end
72
+ end
73
+ end
74
+ ```
75
+
76
+ The information available in the Passage User struct returned by PassageClient.user.get(user_id:):
77
+
78
+ ```ruby
79
+ Struct.new :id,
80
+ :status,
81
+ :email,
82
+ :phone,
83
+ :email_verified,
84
+ :created_at,
85
+ :updated_at,
86
+ :last_login_at,
87
+ :login_count,
88
+ :recent_events,
89
+ :webauthn,
90
+ :webauthn_devices,
91
+ :user_metadata,
92
+ ```
93
+
94
+ ## Activate/Deactivate User
95
+
96
+ You can activate or deactivate a user using the Passage SDK. These actions require an API Key and deactivating a user will prevent them from logging into your application
97
+ with Passage.
98
+
99
+ ```ruby
100
+ require 'passageidentity'
101
+
102
+ PassageClient =
103
+ Passage::Client.new(app_id: PASSAGE_APP_ID, api_key: PASSAGE_API_KEY)
104
+
105
+ user = PassageClient.user.deactivate(user_id: user_id)
106
+ user = PassageClient.user.activate(user_id: user_id)
107
+ ```
108
+
109
+ ## Create User
110
+
111
+ You can create users using their email address or phone number. Note that their phone number must be in E164 format (example shown below).
112
+
113
+ ```ruby
114
+ require 'passageidentity'
115
+
116
+ PassageClient =
117
+ Passage::Client.new(app_id: PASSAGE_APP_ID, api_key: PASSAGE_API_KEY)
118
+
119
+ user = PassageClient.user.create(email: 'exampleEmail@domain.com')
120
+ user = PassageClient.user.create(phone: '+15005550007')
121
+ ```
122
+
123
+ ## Delete User
124
+
125
+ You can delete a user using the Passage SDK.
126
+
127
+ ```ruby
128
+ require 'passageidentity'
129
+
130
+ PassageClient =
131
+ Passage::Client.new(app_id: PASSAGE_APP_ID, api_key: PASSAGE_API_KEY)
132
+
133
+ success = PassageClient.user.delete(user_id: user_id)
134
+ puts 'passage user was successfully deleted' if success
135
+ ```
136
+
137
+ ## List User Devices
138
+
139
+ You can list the devices associated with a particular Passage User.
140
+
141
+ ```ruby
142
+ require 'passageidentity'
143
+
144
+ PassageClient =
145
+ Passage::Client.new(app_id: PASSAGE_APP_ID, api_key: PASSAGE_API_KEY)
146
+
147
+ devices = PassageClient.user.list_devices(user_id: user_id)
148
+ ```
149
+
150
+ The information available in the array of Passage Device struct returned by PassageClient.user.list_devices(user_id:):
151
+
152
+ ```ruby
153
+ Struct.new :id,
154
+ :cred_id,
155
+ :friendly_name,
156
+ :usage_count,
157
+ :last_used,
158
+
159
+ ```
160
+
161
+ ## List User Devices
162
+
163
+ You can list the devices associated with a particular Passage User.
164
+
165
+ ```ruby
166
+ require 'passageidentity'
167
+
168
+ PassageClient = Passage::Client.new(app_id: PASSAGE_APP_ID, api_key: PASSAGE_API_KEY)
169
+
170
+ success = PassageClient.user.delete_device(user_id: user_id, device_id)
171
+ if success
172
+ puts "passage user device was successfully deleted"
173
+ end
174
+ ```
175
+
176
+ ## Create an Embeddable Magic Link
177
+
178
+ To create a magic link, you should use the `create_magic_link` method. You can check out our guide on embeddable magic links in our [docs](https://docs.passage.id/popular-guides/smart-links).
179
+
180
+ ```ruby
181
+ require 'passageidentity'
182
+
183
+ PassageClient =
184
+ Passage::Client.new(app_id: PASSAGE_APP_ID, api_key: PASSAGE_API_KEY)
185
+
186
+ magic_link = PassageClient.create_magic_link(user_id: user_id)
187
+ magic_link =
188
+ PassageClient.create_magic_link(
189
+ email: 'example@domain.com',
190
+ send: true,
191
+ channel: Passage::EMAIL_CHANNEL,
192
+ ttl: 120,
193
+ )
194
+ ```
195
+
196
+ The information available in the Passage Magic Link struct returned this method is below:
197
+
198
+ ```ruby
199
+ Struct.new :id,
200
+ :secret,
201
+ :activated,
202
+ :user_id,
203
+ :app_id,
204
+ :identifier,
205
+ :type,
206
+ :redirect_url,
207
+ :ttl,
208
+ :url
209
+ ```
@@ -0,0 +1,115 @@
1
+ require 'openssl'
2
+ require 'base64'
3
+ require 'jwt'
4
+ require_relative 'client'
5
+
6
+ module Passage
7
+ class Auth
8
+ @@app_cache = {}
9
+ def initialize(app_id, auth_strategy, connection)
10
+ @app_id = app_id
11
+ @auth_strategy = auth_strategy
12
+ @connection = connection
13
+
14
+ fetch_jwks
15
+ end
16
+
17
+ def fetch_app()
18
+ begin
19
+ response = @connection.get("/v1/apps/#{@app_id}")
20
+ return response.body['app']
21
+ rescue Faraday::Error => e
22
+ raise PassageError,
23
+ "failed to get Passage User. Http Status: #{e.response[:status]}. Response: #{e.response[:body]['error']}"
24
+ end
25
+ end
26
+
27
+ def fetch_jwks()
28
+ if @@app_cache[@app_id]
29
+ @jwks, @auth_origin = @@app_cache[@app_id]
30
+ else
31
+ app = fetch_app
32
+ auth_gw_connection =
33
+ Faraday.new(url: 'https://auth.passage.id') do |f|
34
+ f.request :json
35
+ f.request :retry
36
+ f.response :raise_error
37
+ f.response :json
38
+ f.adapter :net_http
39
+ end
40
+
41
+ # fetch the public key if not in cache
42
+ app = fetch_app
43
+ @auth_origin = app['auth_origin']
44
+ response =
45
+ auth_gw_connection.get("/v1/apps/#{@app_id}/.well-known/jwks.json")
46
+ @jwks = response.body
47
+ @@app_cache[@app_id] ||= [@jwks, @auth_origin]
48
+ end
49
+ end
50
+
51
+ def authenticate_request(request)
52
+ # Get the token based on the strategy
53
+ if @auth_strategy === Passage::COOKIE_STRATEGY
54
+ unless request.cookies['psg_auth_token'].present?
55
+ raise PassageError,
56
+ `missing authentication token: expected "psg_auth_token" cookie`
57
+ end
58
+ @token = request.cookies['psg_auth_token']
59
+ else
60
+ headers = request.headers
61
+ unless headers['Authorization'].present?
62
+ raise PassageError, 'no authentication token in header'
63
+ end
64
+ @token = headers['Authorization'].split(' ').last
65
+ end
66
+
67
+ # authenticate the token
68
+ if @token
69
+ return authenticate_token(@token)
70
+ else
71
+ raise PassageError, 'no authentication token'
72
+ end
73
+ nil
74
+ end
75
+
76
+ def authenticate_token(token)
77
+ kid = JWT.decode(token, nil, false)[1]['kid']
78
+ exists = false
79
+ for jwk in @jwks['keys']
80
+ if jwk['kid'] == kid
81
+ exists = true
82
+ break
83
+ end
84
+ end
85
+ fetch_jwks unless exists
86
+ begin
87
+ claims =
88
+ JWT.decode(
89
+ token,
90
+ nil,
91
+ true,
92
+ {
93
+ iss: @app_id,
94
+ verify_iss: true,
95
+ aud: @auth_origin,
96
+ verify_aud: true,
97
+ algorithms: ['RS256'],
98
+ jwks: @jwks
99
+ }
100
+ )
101
+ return claims[0]['sub']
102
+ rescue JWT::InvalidIssuerError => e
103
+ raise Passage::PassageError, e.message
104
+ rescue JWT::InvalidAudError => e
105
+ raise Passage::PassageError, e.message
106
+ rescue JWT::ExpiredSignature => e
107
+ raise Passage::PassageError, e.message
108
+ rescue JWT::IncorrectAlgorithm => e
109
+ raise Passage::PassageError, e.message
110
+ rescue JWT::DecodeError => e
111
+ raise Passage::PassageError, e.message
112
+ end
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,152 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'auth'
4
+ require_relative 'user_api'
5
+ require_relative 'error'
6
+
7
+ module Passage
8
+ User =
9
+ Struct.new :id,
10
+ :status,
11
+ :email,
12
+ :phone,
13
+ :email_verified,
14
+ :created_at,
15
+ :updated_at,
16
+ :last_login_at,
17
+ :login_count,
18
+ :recent_events,
19
+ :webauthn,
20
+ :webauthn_devices,
21
+ :user_metadata,
22
+ keyword_init: true
23
+ MagicLink =
24
+ Struct.new :id,
25
+ :secret,
26
+ :activated,
27
+ :user_id,
28
+ :app_id,
29
+ :identifier,
30
+ :type,
31
+ :redirect_url,
32
+ :ttl,
33
+ :url,
34
+ keyword_init: true
35
+ Device =
36
+ Struct.new :id,
37
+ :cred_id,
38
+ :friendly_name,
39
+ :usage_count,
40
+ :last_used,
41
+ keyword_init: true
42
+
43
+ COOKIE_STRATEGY = 0
44
+ HEADER_STRATEGY = 1
45
+
46
+ EMAIL_CHANNEL = 'email'
47
+ PHONE_CHANNEL = 'phone'
48
+
49
+ class Client
50
+ attr_reader :auth
51
+ attr_reader :user
52
+
53
+ def initialize(app_id:, api_key: '', auth_strategy: COOKIE_STRATEGY)
54
+ @api_url = 'https://api.passage.id'
55
+ @app_id = app_id
56
+ @api_key = api_key
57
+
58
+ # check for valid auth strategy
59
+ unless [COOKIE_STRATEGY, HEADER_STRATEGY].include? auth_strategy
60
+ raise PassageError, 'invalid auth strategy.'
61
+ end
62
+ @auth_strategy = auth_strategy
63
+
64
+ # setup
65
+ get_connection
66
+
67
+ # initialize auth class
68
+ @auth = Passage::Auth.new(@app_id, @auth_strategy, @connection)
69
+
70
+ # initialize user class
71
+ @user = Passage::UserAPI.new(@connection, @app_id, @api_key)
72
+ end
73
+
74
+ def get_connection
75
+ if @api_key == ''
76
+ @connection =
77
+ Faraday.new(url: @api_url) do |f|
78
+ f.request :json
79
+ f.request :retry
80
+ f.response :raise_error
81
+ f.response :json
82
+ f.adapter :net_http
83
+ end
84
+ else
85
+ @connection =
86
+ Faraday.new(
87
+ url: @api_url,
88
+ headers: {
89
+ 'Authorization' => "Bearer #{@api_key}"
90
+ }
91
+ ) do |f|
92
+ f.request :json
93
+ f.request :retry
94
+ f.response :raise_error
95
+ f.response :json
96
+ f.adapter :net_http
97
+ end
98
+ end
99
+ end
100
+
101
+ def create_magic_link(
102
+ user_id: '',
103
+ email: '',
104
+ phone: '',
105
+ channel: '',
106
+ send: false,
107
+ magic_link_path: '',
108
+ redirect_url: '',
109
+ ttl: 60
110
+ )
111
+ magic_link_req = {}
112
+ magic_link_req['user_id'] = user_id unless user_id.empty?
113
+ magic_link_req['email'] = email unless email.empty?
114
+ magic_link_req['phone'] = phone unless phone.empty?
115
+
116
+ # check to see if the channel specified is valid before sending it off to the server
117
+ unless [PHONE_CHANNEL, EMAIL_CHANNEL].include? channel
118
+ raise PassageError,
119
+ 'channel: must be either Passage::EMAIL_CHANNEL or Passage::PHONE_CHANNEL'
120
+ end
121
+ magic_link_req['channel'] = channel unless channel.empty?
122
+ magic_link_req['send'] = send
123
+ magic_link_req['magic_link_path'] = magic_link_path unless magic_link_path
124
+ .empty?
125
+ magic_link_req['redirect_url'] = redirect_url unless redirect_url.empty?
126
+ magic_link_req['ttl'] = ttl unless ttl == 0
127
+
128
+ begin
129
+ response =
130
+ @connection.post("/v1/apps/#{@app_id}/magic-links", magic_link_req)
131
+ magic_link = response.body['magic_link']
132
+ return(
133
+ Passage::MagicLink.new(
134
+ id: magic_link['id'],
135
+ secret: magic_link['secret'],
136
+ activated: magic_link['activated'],
137
+ user_id: magic_link['user_id'],
138
+ app_id: magic_link['app_id'],
139
+ identifier: magic_link['identifier'],
140
+ type: magic_link['type'],
141
+ redirect_url: magic_link['redirect_url'],
142
+ ttl: magic_link['ttl'],
143
+ url: magic_link['url']
144
+ )
145
+ )
146
+ rescue Faraday::Error => e
147
+ raise PassageError,
148
+ "failed to create Passage Magic Link. Http Status: #{e.response[:status]}. Response: #{e.response[:body]['error']}"
149
+ end
150
+ end
151
+ end
152
+ end
@@ -0,0 +1,4 @@
1
+ module Passage
2
+ class PassageError < StandardError
3
+ end
4
+ end
@@ -0,0 +1,241 @@
1
+ require_relative 'client'
2
+
3
+ module Passage
4
+ class UserAPI
5
+ # This class will require an API key
6
+ def initialize(connection, app_id, api_key)
7
+ @connection = connection
8
+ @app_id = app_id
9
+ @api_key = api_key
10
+ end
11
+
12
+ def get(user_id:)
13
+ raise PassageError, 'must supply a valid user_id' if user_id.to_s.empty?
14
+ begin
15
+ response = @connection.get("/v1/apps/#{@app_id}/users/#{user_id}")
16
+ user = response.body['user']
17
+ user.transform_keys(&:to_sym)
18
+ return(
19
+ Passage::User.new(
20
+ id: user['id'],
21
+ status: user['status'],
22
+ email: user['email'],
23
+ phone: user['phone'],
24
+ email_verified: user['email_verified'],
25
+ created_at: user['created_at'],
26
+ updated_at: user['updated_at'],
27
+ last_login_at: user['last_login_at'],
28
+ login_count: user['login_count'],
29
+ webauthn: user['webauthn'],
30
+ webauthn_devices: user['webauthn_devices'],
31
+ recent_events: user['recent_events'],
32
+ user_metadata: user['user_metadata']
33
+ )
34
+ )
35
+ rescue Faraday::Error => e
36
+ if e.is_a? Faraday::ResourceNotFound
37
+ raise PassageError,
38
+ "passage User with ID \"#{user_id}\" does not exist"
39
+ else
40
+ raise PassageError,
41
+ "failed to get Passage User. Http Status: #{e.response[:status]}. Response: #{e.response[:body]['error']}"
42
+ end
43
+ end
44
+ end
45
+
46
+ def activate(user_id:)
47
+ raise PassageError, 'must supply a valid user_id' if user_id.to_s.empty?
48
+ begin
49
+ response =
50
+ @connection.patch("/v1/apps/#{@app_id}/users/#{user_id}/activate")
51
+ user = response.body['user']
52
+ return(
53
+ Passage::User.new(
54
+ id: user['id'],
55
+ status: user['status'],
56
+ email: user['email'],
57
+ phone: user['phone'],
58
+ email_verified: user['email_verified'],
59
+ created_at: user['created_at'],
60
+ updated_at: user['updated_at'],
61
+ last_login_at: user['last_login_at'],
62
+ login_count: user['login_count'],
63
+ webauthn: user['webauthn'],
64
+ webauthn_devices: user['webauthn_devices'],
65
+ recent_events: user['recent_events'],
66
+ user_metadata: user['user_metadata']
67
+ )
68
+ )
69
+ rescue Faraday::Error => e
70
+ if e.is_a? Faraday::ResourceNotFound
71
+ raise PassageError,
72
+ "passage User with ID \"#{user_id}\" does not exist"
73
+ else
74
+ raise PassageError,
75
+ "failed to activate Passage User. Http Status: #{e.response[:status]}. Response: #{e.response[:body]['error']}"
76
+ end
77
+ end
78
+ end
79
+
80
+ def deactivate(user_id:)
81
+ raise PassageError, 'must supply a valid user_id' if user_id.to_s.empty?
82
+ begin
83
+ response =
84
+ @connection.patch("/v1/apps/#{@app_id}/users/#{user_id}/deactivate")
85
+ user = response.body['user']
86
+ return(
87
+ Passage::User.new(
88
+ id: user['id'],
89
+ status: user['status'],
90
+ email: user['email'],
91
+ phone: user['phone'],
92
+ email_verified: user['email_verified'],
93
+ created_at: user['created_at'],
94
+ updated_at: user['updated_at'],
95
+ last_login_at: user['last_login_at'],
96
+ login_count: user['login_count'],
97
+ webauthn: user['webauthn'],
98
+ webauthn_devices: user['webauthn_devices'],
99
+ recent_events: user['recent_events'],
100
+ user_metadata: user['user_metadata']
101
+ )
102
+ )
103
+ rescue Faraday::Error => e
104
+ if e.is_a? Faraday::ResourceNotFound
105
+ raise PassageError,
106
+ "passage User with ID \"#{user_id}\" does not exist"
107
+ else
108
+ raise PassageError,
109
+ "failed to deactivate Passage User. Http Status: #{e.response[:status]}. Response: #{e.response[:body]['error']}"
110
+ end
111
+ end
112
+ end
113
+
114
+ def update(user_id:, email: '', phone: '', user_metadata: {})
115
+ raise PassageError, 'must supply a valid user_id' if user_id.to_s.empty?
116
+ updates = {}
117
+ updates['email'] = email unless email.empty?
118
+ updates['phone'] = phone unless phone.empty?
119
+ updates['user_metadata'] = user_metadata unless user_metadata.empty?
120
+ begin
121
+ response =
122
+ @connection.patch("/v1/apps/#{@app_id}/users/#{user_id}", updates)
123
+ user = response.body['user']
124
+ return(
125
+ Passage::User.new(
126
+ id: user['id'],
127
+ status: user['status'],
128
+ email: user['email'],
129
+ phone: user['phone'],
130
+ email_verified: user['email_verified'],
131
+ created_at: user['created_at'],
132
+ updated_at: user['updated_at'],
133
+ last_login_at: user['last_login_at'],
134
+ login_count: user['login_count'],
135
+ webauthn: user['webauthn'],
136
+ webauthn_devices: user['webauthn_devices'],
137
+ recent_events: user['recent_events'],
138
+ user_metadata: user['user_metadata']
139
+ )
140
+ )
141
+ rescue Faraday::Error => e
142
+ if e.is_a? Faraday::ResourceNotFound
143
+ raise PassageError,
144
+ "passage User with ID \"#{user_id}\" does not exist"
145
+ else
146
+ raise PassageError,
147
+ "failed to update Passage User. Http Status: #{e.response[:status]}. Response: #{e.response[:body]['error']}"
148
+ end
149
+ end
150
+ end
151
+
152
+ def create(email: '', phone: '', user_metadata: {})
153
+ create = {}
154
+ create['email'] = email unless email.empty?
155
+ create['phone'] = phone unless phone.empty?
156
+ create['user_metadata'] = user_metadata unless user_metadata.empty?
157
+ begin
158
+ response = @connection.post("/v1/apps/#{@app_id}/users", create)
159
+ user = response.body['user']
160
+ return(
161
+ Passage::User.new(
162
+ id: user['id'],
163
+ status: user['status'],
164
+ email: user['email'],
165
+ phone: user['phone'],
166
+ email_verified: user['email_verified'],
167
+ created_at: user['created_at'],
168
+ updated_at: user['updated_at'],
169
+ last_login_at: user['last_login_at'],
170
+ login_count: user['login_count'],
171
+ webauthn: user['webauthn'],
172
+ webauthn_devices: user['webauthn_devices'],
173
+ recent_events: user['recent_events'],
174
+ user_metadata: user['user_metadata']
175
+ )
176
+ )
177
+ rescue Faraday::Error => e
178
+ raise PassageError,
179
+ "failed to create Passage User. Http Status: #{e.response[:status]}. Response: #{e.response[:body]['error']}"
180
+ end
181
+ end
182
+
183
+ def delete(user_id:)
184
+ raise PassageError, 'must supply a valid user_id' if user_id.to_s.empty?
185
+ begin
186
+ response = @connection.delete("/v1/apps/#{@app_id}/users/#{user_id}")
187
+ return true
188
+ rescue Faraday::Error => e
189
+ if e.is_a? Faraday::ResourceNotFound
190
+ raise PassageError,
191
+ "passage User with ID \"#{user_id}\" does not exist"
192
+ else
193
+ raise PassageError,
194
+ "failed to delete Passage User. Http Status: #{e.response[:status]}. Response: #{e.response[:body]['error']}"
195
+ end
196
+ end
197
+ end
198
+
199
+ def delete_device(user_id:, device_id:)
200
+ raise PassageError, 'must supply a valid user_id' if user_id.to_s.empty?
201
+ if device_id.to_s.empty?
202
+ raise PassageError, 'must supply a valid device_id'
203
+ end
204
+ begin
205
+ response =
206
+ @connection.delete(
207
+ "/v1/apps/#{@app_id}/users/#{user_id}/devices/#{device_id}"
208
+ )
209
+ return true
210
+ rescue Faraday::Error => e
211
+ raise PassageError,
212
+ "failed to delete Passage User Device. Http Status: #{e.response[:status]}. Response: #{e.response[:body]['error']}"
213
+ end
214
+ end
215
+
216
+ def list_devices(user_id:)
217
+ raise PassageError, 'must supply a valid user_id' if user_id.to_s.empty?
218
+ begin
219
+ response =
220
+ @connection.get("/v1/apps/#{@app_id}/users/#{user_id}/devices")
221
+ devicesResp = response.body['devices']
222
+ devices = Array.new
223
+ devicesResp.each do |device|
224
+ devices.append(
225
+ Passage::Device.new(
226
+ id: device['id'],
227
+ cred_id: device['cred_id'],
228
+ friendly_name: device['friendly_name'],
229
+ usage_count: device['usage_count'],
230
+ last_used: device['last_used']
231
+ )
232
+ )
233
+ end
234
+ return devices
235
+ rescue Faraday::Error => e
236
+ raise PassageError,
237
+ "failed to delete Passage User Device. Http Status: #{e.response[:status]}. Response: #{e.response[:body]['error']}"
238
+ end
239
+ end
240
+ end
241
+ end
@@ -4,6 +4,5 @@ require 'faraday'
4
4
 
5
5
  require_relative 'passageidentity/client'
6
6
 
7
- module Passage end
8
-
9
-
7
+ module Passage
8
+ end
data/passage-ruby ADDED
@@ -0,0 +1 @@
1
+ passage-ruby
@@ -0,0 +1,30 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'passageidentity'
3
+ s.version = '0.0.4'
4
+ s.summary = 'Passage SDK for biometric authentication'
5
+ s.description =
6
+ 'Enables verification of server-side authentication and user management for applications using Passage'
7
+ s.authors = ['Passage Identity']
8
+ s.email = 'support@passage.id'
9
+ s.files = ['lib/passageidentity.rb']
10
+ s.homepage = 'https://rubygems.org/gems/passageidentity'
11
+ s.license = 'MIT'
12
+
13
+ s.metadata['source_code_uri'] =
14
+ 'https://github.com/passage-identity/passage-ruby'
15
+
16
+ # Specify which files should be added to the gem when it is released.
17
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
18
+ s.require_paths = ['lib']
19
+
20
+ s.files =
21
+ Dir.chdir(File.expand_path(__dir__)) do
22
+ `git ls-files -z`.split("\x0").reject do |f|
23
+ f.match(%r{^(test|spec|features)/})
24
+ end
25
+ end
26
+
27
+ s.add_dependency 'faraday', '>= 0.17.0', '< 2.0'
28
+ s.add_dependency 'jwt', '>= 2.3.0'
29
+ s.add_dependency 'openssl', '>= 3.0.0'
30
+ end
data/tests/all.rb ADDED
@@ -0,0 +1,2 @@
1
+ Dir[File.dirname(File.absolute_path(__FILE__)) + '/**/*_test.rb']
2
+ .each { |file| require file }
@@ -0,0 +1,14 @@
1
+ require_relative '../lib/passageidentity/client'
2
+ require_relative './environment'
3
+ require 'faraday'
4
+ require 'test/unit'
5
+
6
+ class TestUserAPI < Test::Unit::TestCase
7
+ PassageClient =
8
+ Passage::Client.new(app_id: ENV['APP_ID'], api_key: ENV['API_KEY'])
9
+
10
+ def test_authenticate_token
11
+ user_id = PassageClient.auth.authenticate_token(ENV['PSG_JWT'])
12
+ assert_equal ENV['TEST_USER_ID'], user_id
13
+ end
14
+ end
@@ -0,0 +1,20 @@
1
+ require_relative '../lib/passageidentity/client'
2
+ require_relative './environment'
3
+ require 'faraday'
4
+ require 'test/unit'
5
+
6
+ class TestUserAPI < Test::Unit::TestCase
7
+ PassageClient =
8
+ Passage::Client.new(app_id: ENV['APP_ID'], api_key: ENV['API_KEY'])
9
+
10
+ def test_create_magi_link()
11
+ magic_link =
12
+ PassageClient.create_magic_link(
13
+ email: 'chris@passage.id',
14
+ channel: Passage::EMAIL_CHANNEL,
15
+ ttl: 12
16
+ )
17
+ assert_equal 12, magic_link.ttl
18
+ assert_equal 'chris@passage.id', magic_link.identifier
19
+ end
20
+ end
@@ -0,0 +1,74 @@
1
+ require_relative '../lib/passageidentity/client'
2
+ require_relative './environment'
3
+ require 'faraday'
4
+ require 'test/unit'
5
+
6
+ class TestUserAPI < Test::Unit::TestCase
7
+ PassageClient =
8
+ Passage::Client.new(app_id: ENV['APP_ID'], api_key: ENV['API_KEY'])
9
+
10
+ def setup()
11
+ $global_test_user =
12
+ PassageClient.user.create(
13
+ email: 'chris+test-ruby@passage.id',
14
+ user_metadata: {
15
+ 'example1': 'cool'
16
+ }
17
+ )
18
+ end
19
+
20
+ def test_create_delete_user()
21
+ user =
22
+ PassageClient.user.create(
23
+ email: 'chris+test-create-delete@passage.id',
24
+ user_metadata: {
25
+ 'example1': 'cool'
26
+ }
27
+ )
28
+ assert_equal 'chris+test-create-delete@passage.id', user.email
29
+ assert_equal 'cool', user.user_metadata['example1']
30
+ deleted = PassageClient.user.delete(user_id: user.id)
31
+ assert_equal true, deleted
32
+ end
33
+
34
+ def test_get_user()
35
+ user = PassageClient.user.get(user_id: $global_test_user.id)
36
+ assert_equal $global_test_user.id, user.id
37
+ end
38
+
39
+ def test_deactivate_user()
40
+ user = PassageClient.user.deactivate(user_id: $global_test_user.id)
41
+ assert_equal $global_test_user.id, user.id
42
+ assert_equal 'inactive', user.status
43
+ end
44
+
45
+ def test_activate_user()
46
+ user = PassageClient.user.activate(user_id: $global_test_user.id)
47
+ assert_equal $global_test_user.id, user.id
48
+ assert_equal 'active', user.status
49
+ end
50
+
51
+ def test_update_user()
52
+ new_email = 'chris+update_test-ruby@passage.id'
53
+ user =
54
+ PassageClient.user.update(
55
+ user_id: $global_test_user.id,
56
+ email: new_email,
57
+ user_metadata: {
58
+ 'example1': 'lame'
59
+ }
60
+ )
61
+ assert_equal $global_test_user.id, user.id
62
+ assert_equal new_email, user.email
63
+ assert_equal 'lame', user.user_metadata['example1']
64
+ end
65
+
66
+ def test_list_devices()
67
+ devices = PassageClient.user.list_devices(user_id: $global_test_user.id)
68
+ assert_equal [], devices
69
+ end
70
+
71
+ def teardown()
72
+ deleted = PassageClient.user.delete(user_id: $global_test_user.id)
73
+ end
74
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: passageidentity
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Passage Identity
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-04-18 00:00:00.000000000 Z
11
+ date: 2022-05-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -65,7 +65,21 @@ executables: []
65
65
  extensions: []
66
66
  extra_rdoc_files: []
67
67
  files:
68
+ - ".gitignore"
69
+ - CONTRIBUTING.md
70
+ - LICENSE
71
+ - README.md
68
72
  - lib/passageidentity.rb
73
+ - lib/passageidentity/auth.rb
74
+ - lib/passageidentity/client.rb
75
+ - lib/passageidentity/error.rb
76
+ - lib/passageidentity/user_api.rb
77
+ - passage-ruby
78
+ - passageidentity.gemspec
79
+ - tests/all.rb
80
+ - tests/auth_test.rb
81
+ - tests/magic_link_test.rb
82
+ - tests/user_api_test.rb
69
83
  homepage: https://rubygems.org/gems/passageidentity
70
84
  licenses:
71
85
  - MIT