passageidentity 0.0.3 → 0.0.6

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: a592cfe2a08870771b7bb52f737eada85f6b631bf6570a1f239a2187667352b4
4
- data.tar.gz: 48e62a0beb94c042a0dffbeae6ea62dcb10010ad13e418d7c3958acf0d6aa8b3
3
+ metadata.gz: 2a690a22154faaee08c7f917391722ba209f13eed6b321a71112906c45c94c4b
4
+ data.tar.gz: 33d3a29b62ef42d6239d31082a06e9fc070271e4ebbccfa612fe0b6610c6dc58
5
5
  SHA512:
6
- metadata.gz: b6ef3d1a59fb0e04597003fba47d6d5d73ccf9b0a47a5e25f99d7298adf2e656ba1d8b1f01303dcc8b7dd91b57f29ec8e66f906b5f05fe77f551845a706188af
7
- data.tar.gz: 6560d66712f9bccfd9d0f80eb30bbf5e45602a25cb5099e77461891be284831f0db73a551b7d764df302fae6450b7af86a39494509acbdbd2c027580ba984fec
6
+ metadata.gz: 205210cafaa264ea5b1709c9d09a98f37d4f0c5c306a435a5b59972ed620160ad73a5bda9eb610e0df1204127d75c21b4f7138e9ffa5ea41b3a710c7d8098159
7
+ data.tar.gz: b06a3b13b4f95c3439f3acefee1872656ac151e88d0fa88e0a1b4800662114de2e25c745020ca26de21321b1c0db4f95ee03a977ed8950492e309fd134be6e0e
@@ -0,0 +1,30 @@
1
+ name: Deploy Ruby Gem
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+
7
+ jobs:
8
+ build:
9
+ name: Build + Publish
10
+ runs-on: ubuntu-latest
11
+ permissions:
12
+ contents: read
13
+ packages: write
14
+
15
+ steps:
16
+ - uses: actions/checkout@v2
17
+ - uses: ruby/setup-ruby@v1
18
+ with:
19
+ ruby-version: '3.0'
20
+
21
+ - name: Publish to RubyGems
22
+ run: |
23
+ mkdir -p $HOME/.gem
24
+ touch $HOME/.gem/credentials
25
+ chmod 0600 $HOME/.gem/credentials
26
+ printf -- "---\n:rubygems_api_key: ${GEM_HOST_API_KEY}\n" > $HOME/.gem/credentials
27
+ gem build *.gemspec
28
+ gem push *.gem
29
+ env:
30
+ GEM_HOST_API_KEY: "${{secrets.RUBYGEMS_AUTH_TOKEN}}"
@@ -0,0 +1,34 @@
1
+ name: PR Checks
2
+
3
+ on:
4
+ pull_request:
5
+ branches: [ main ]
6
+
7
+ env:
8
+ API_KEY: ${{ secrets.API_KEY }}
9
+ APP_ID: ${{ secrets.APP_ID }}
10
+ PSG_JWT: ${{ secrets.PSG_JWT }}
11
+ TEST_USER_ID: ${{ secrets.TEST_USER_ID }}
12
+
13
+ jobs:
14
+ build:
15
+ name: Test and Lint
16
+ runs-on: ubuntu-latest
17
+
18
+ steps:
19
+ - uses: actions/checkout@v2
20
+ - uses: ruby/setup-ruby@v1
21
+ with:
22
+ ruby-version: '3.0'
23
+
24
+ - name: Run Tests
25
+ run: |
26
+ gem build passageidentity.gemspec -o test.gem
27
+ gem install test.gem
28
+ rm test.gem
29
+ ruby tests/all.rb
30
+ - name: Run Linting
31
+ run: |
32
+ npm install -g prettier @prettier/plugin-ruby
33
+ gem install bundler prettier_print syntax_tree syntax_tree-haml syntax_tree-rbs
34
+ prettier --check '**/*.rb'
data/.gitignore CHANGED
@@ -10,8 +10,7 @@
10
10
  /test/version_tmp/
11
11
  /tmp/
12
12
 
13
- /tests/environment.rb
14
-
13
+ .env
15
14
  # Used by dotenv library to load environment variables.
16
15
  # .env
17
16
 
data/CONTRIBUTING.md CHANGED
@@ -5,8 +5,8 @@
5
5
  Install the gem
6
6
 
7
7
  ```
8
- gem build passageidentity.gemspec
9
- gem install ./passageidentity-0.0.1.gem
8
+ gem build passageidentity.gemspec -o {$FILE_NAME}.gem
9
+ gem install ./{$FILE_NAME}.gem
10
10
  ```
11
11
 
12
12
  Test it out:
@@ -27,6 +27,15 @@ ruby tests/all.rb
27
27
  ruby tests/*_test.rb
28
28
  ```
29
29
 
30
+ Run Linter:
31
+
32
+ ```
33
+ npm install -g prettier @prettier/plugin-ruby
34
+ gem install bundler prettier_print syntax_tree syntax_tree-haml syntax_tree-rbs
35
+ prettier --write '**/*.rb'
36
+ ```
37
+
38
+
30
39
  To test in the example app, change the Gemfile to include this path:
31
40
 
32
41
  ```
@@ -45,21 +54,13 @@ Enter host password for user '<username>':
45
54
  ```
46
55
 
47
56
  ```
48
- <<<<<<< HEAD
49
57
  gem push passage-0.0.0.gem
50
58
  ```
51
59
 
52
60
  You can check for the gem here:
61
+
53
62
  ```
54
63
  gem list -r passage
55
64
  ```
56
- =======
57
- gem push passageidentity-0.0.0.gem
58
- ```
59
65
 
60
- You can check for the gem here:
61
-
62
- ```
63
- gem list -r passageidentity
64
66
  ```
65
- >>>>>>> 2d0e3f6dc3b40c621c8d16506fa6ab43b0fba673
@@ -1,61 +1,104 @@
1
- require 'openssl'
2
- require 'base64'
3
- require 'jwt'
4
- require_relative 'client'
1
+ require "openssl"
2
+ require "base64"
3
+ require "jwt"
4
+ require_relative "client"
5
5
 
6
6
  module Passage
7
7
  class Auth
8
- def initialize(app_id, auth_strategy, public_key, auth_origin)
8
+ @@app_cache = {}
9
+ def initialize(app_id, auth_strategy, connection)
9
10
  @app_id = app_id
10
11
  @auth_strategy = auth_strategy
11
- @auth_origin = auth_origin
12
+ @connection = connection
12
13
 
13
- # bas64 decode and then parse the public key
14
- # when we have JWKS endpoint, this will get easier I think
15
- @public_key = OpenSSL::PKey::RSA.new(Base64.decode64(public_key))
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 App. 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
16
49
  end
17
50
 
18
51
  def authenticate_request(request)
19
52
  # Get the token based on the strategy
20
53
  if @auth_strategy === Passage::COOKIE_STRATEGY
21
- unless request.cookies['psg_auth_token'].present?
54
+ unless request.cookies["psg_auth_token"].present?
22
55
  raise PassageError,
23
56
  `missing authentication token: expected "psg_auth_token" cookie`
24
57
  end
25
- @token = request.cookies['psg_auth_token']
58
+ @token = request.cookies["psg_auth_token"]
26
59
  else
27
60
  headers = request.headers
28
- unless headers['Authorization'].present?
29
- raise PassageError, 'no authentication token in header'
61
+ unless headers["Authorization"].present?
62
+ raise PassageError, "no authentication token in header"
30
63
  end
31
- @token = headers['Authorization'].split(' ').last
64
+ @token = headers["Authorization"].split(" ").last
32
65
  end
33
66
 
34
67
  # authenticate the token
35
68
  if @token
36
69
  return authenticate_token(@token)
37
70
  else
38
- raise PassageError, 'no authentication token'
71
+ raise PassageError, "no authentication token"
39
72
  end
40
73
  nil
41
74
  end
42
75
 
43
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
44
86
  begin
45
87
  claims =
46
88
  JWT.decode(
47
89
  token,
48
- @public_key,
90
+ nil,
49
91
  true,
50
92
  {
51
93
  iss: @app_id,
52
94
  verify_iss: true,
53
95
  aud: @auth_origin,
54
96
  verify_aud: true,
55
- algorithms: ['RS256']
97
+ algorithms: ["RS256"],
98
+ jwks: @jwks
56
99
  }
57
100
  )
58
- return claims[0]['sub']
101
+ return claims[0]["sub"]
59
102
  rescue JWT::InvalidIssuerError => e
60
103
  raise Passage::PassageError, e.message
61
104
  rescue JWT::InvalidAudError => e
@@ -1,10 +1,24 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'auth'
4
- require_relative 'user_api'
5
- require_relative 'error'
3
+ require_relative "auth"
4
+ require_relative "user_api"
5
+ require_relative "error"
6
6
 
7
7
  module Passage
8
+ App =
9
+ Struct.new :name,
10
+ :id,
11
+ :auth_origin,
12
+ :redirect_url,
13
+ :login_url,
14
+ :rsa_public_key,
15
+ :allowed_identifer,
16
+ :required_identifier,
17
+ :require_email_verification,
18
+ :session_timeout_length,
19
+ :user_metadata_schema,
20
+ :layouts,
21
+ keyword_init: true
8
22
  User =
9
23
  Struct.new :id,
10
24
  :status,
@@ -43,40 +57,36 @@ module Passage
43
57
  COOKIE_STRATEGY = 0
44
58
  HEADER_STRATEGY = 1
45
59
 
46
- EMAIL_CHANNEL = 'email'
47
- PHONE_CHANNEL = 'phone'
60
+ EMAIL_CHANNEL = "email"
61
+ PHONE_CHANNEL = "phone"
48
62
 
49
63
  class Client
50
- @@app_cache = {}
51
-
52
64
  attr_reader :auth
53
65
  attr_reader :user
54
66
 
55
- def initialize(app_id:, api_key: '', auth_strategy: COOKIE_STRATEGY)
56
- @api_url = 'https://api.passage.id'
67
+ def initialize(app_id:, api_key: "", auth_strategy: COOKIE_STRATEGY)
68
+ @api_url = "https://api.passage.id"
57
69
  @app_id = app_id
58
70
  @api_key = api_key
59
71
 
60
72
  # check for valid auth strategy
61
73
  unless [COOKIE_STRATEGY, HEADER_STRATEGY].include? auth_strategy
62
- raise PassageError, 'invalid auth strategy.'
74
+ raise PassageError, "invalid auth strategy."
63
75
  end
64
76
  @auth_strategy = auth_strategy
65
77
 
66
78
  # setup
67
79
  get_connection
68
- fetch_public_key(@connection)
69
80
 
70
81
  # initialize auth class
71
- @auth =
72
- Passage::Auth.new(@app_id, @auth_strategy, @public_key, @auth_origin)
82
+ @auth = Passage::Auth.new(@app_id, @auth_strategy, @connection)
73
83
 
74
84
  # initialize user class
75
85
  @user = Passage::UserAPI.new(@connection, @app_id, @api_key)
76
86
  end
77
87
 
78
88
  def get_connection
79
- if @api_key == ''
89
+ if @api_key == ""
80
90
  @connection =
81
91
  Faraday.new(url: @api_url) do |f|
82
92
  f.request :json
@@ -90,7 +100,7 @@ module Passage
90
100
  Faraday.new(
91
101
  url: @api_url,
92
102
  headers: {
93
- 'Authorization' => "Bearer #{@api_key}"
103
+ "Authorization" => "Bearer #{@api_key}"
94
104
  }
95
105
  ) do |f|
96
106
  f.request :json
@@ -102,66 +112,79 @@ module Passage
102
112
  end
103
113
  end
104
114
 
105
- def fetch_public_key(conn)
106
- if @@app_cache[@app_id]
107
- @public_key, @auth_origin = @@app_cache[@app_id]
108
- else
109
- # fetch the public key if not in cache
110
- response = conn.get("/v1/apps/#{@app_id}")
111
- @public_key = response.body['app']['rsa_public_key']
112
- @auth_origin = response.body['app']['auth_origin']
113
- @@app_cache[@app_id] ||= [@public_key, @auth_origin]
115
+ def get_app()
116
+ begin
117
+ app_info = @auth.fetch_app()
118
+ return(
119
+ Passage::App.new(
120
+ name: app_info["name"],
121
+ id: app_info["id"],
122
+ auth_origin: app_info["auth_origin"],
123
+ redirect_url: app_info["redirect_url"],
124
+ login_url: app_info["login_url"],
125
+ rsa_public_key: app_info["rsa_public_key"],
126
+ allowed_identifer: app_info["allowed_identifer"],
127
+ required_identifier: app_info["required_identifier"],
128
+ require_email_verification: app_info["require_email_verification"],
129
+ session_timeout_length: app_info["session_timeout_length"],
130
+ user_metadata_schema: app_info["user_metadata_schema"],
131
+ layouts: app_info["layouts"]
132
+ )
133
+ )
134
+ rescue => e
135
+ raise e
114
136
  end
115
137
  end
116
138
 
117
139
  def create_magic_link(
118
- user_id: '',
119
- email: '',
120
- phone: '',
121
- channel: '',
140
+ user_id: "",
141
+ email: "",
142
+ phone: "",
143
+ channel: "",
122
144
  send: false,
123
- magic_link_path: '',
124
- redirect_url: '',
145
+ magic_link_path: "",
146
+ redirect_url: "",
125
147
  ttl: 60
126
148
  )
127
149
  magic_link_req = {}
128
- magic_link_req['user_id'] = user_id unless user_id.empty?
129
- magic_link_req['email'] = email unless email.empty?
130
- magic_link_req['phone'] = phone unless phone.empty?
150
+ magic_link_req["user_id"] = user_id unless user_id.empty?
151
+ magic_link_req["email"] = email unless email.empty?
152
+ magic_link_req["phone"] = phone unless phone.empty?
131
153
 
132
154
  # check to see if the channel specified is valid before sending it off to the server
133
155
  unless [PHONE_CHANNEL, EMAIL_CHANNEL].include? channel
134
156
  raise PassageError,
135
- 'channel: must be either Passage::EMAIL_CHANNEL or Passage::PHONE_CHANNEL'
157
+ "channel: must be either Passage::EMAIL_CHANNEL or Passage::PHONE_CHANNEL"
136
158
  end
137
- magic_link_req['channel'] = channel unless channel.empty?
138
- magic_link_req['send'] = send
139
- magic_link_req['magic_link_path'] = magic_link_path unless magic_link_path
140
- .empty?
141
- magic_link_req['redirect_url'] = redirect_url unless redirect_url.empty?
142
- magic_link_req['ttl'] = ttl unless ttl == 0
159
+ magic_link_req["channel"] = channel unless channel.empty?
160
+ magic_link_req["send"] = send
161
+ magic_link_req[
162
+ "magic_link_path"
163
+ ] = magic_link_path unless magic_link_path.empty?
164
+ magic_link_req["redirect_url"] = redirect_url unless redirect_url.empty?
165
+ magic_link_req["ttl"] = ttl unless ttl == 0
143
166
 
144
167
  begin
145
168
  response =
146
169
  @connection.post("/v1/apps/#{@app_id}/magic-links", magic_link_req)
147
- magic_link = response.body['magic_link']
170
+ magic_link = response.body["magic_link"]
148
171
  return(
149
172
  Passage::MagicLink.new(
150
- id: magic_link['id'],
151
- secret: magic_link['secret'],
152
- activated: magic_link['activated'],
153
- user_id: magic_link['user_id'],
154
- app_id: magic_link['app_id'],
155
- identifier: magic_link['identifier'],
156
- type: magic_link['type'],
157
- redirect_url: magic_link['redirect_url'],
158
- ttl: magic_link['ttl'],
159
- url: magic_link['url']
173
+ id: magic_link["id"],
174
+ secret: magic_link["secret"],
175
+ activated: magic_link["activated"],
176
+ user_id: magic_link["user_id"],
177
+ app_id: magic_link["app_id"],
178
+ identifier: magic_link["identifier"],
179
+ type: magic_link["type"],
180
+ redirect_url: magic_link["redirect_url"],
181
+ ttl: magic_link["ttl"],
182
+ url: magic_link["url"]
160
183
  )
161
184
  )
162
185
  rescue Faraday::Error => e
163
186
  raise PassageError,
164
- "failed to create Passage Magic Link. Http Status: #{e.response[:status]}. Response: #{e.response[:body]['error']}"
187
+ "failed to create Passage Magic Link. Http Status: #{e.response[:status]}. Response: #{e.response[:body]["error"]}"
165
188
  end
166
189
  end
167
190
  end
@@ -1,4 +1,4 @@
1
- require_relative 'client'
1
+ require_relative "client"
2
2
 
3
3
  module Passage
4
4
  class UserAPI
@@ -10,26 +10,26 @@ module Passage
10
10
  end
11
11
 
12
12
  def get(user_id:)
13
- raise PassageError, 'must supply a valid user_id' if user_id.to_s.empty?
13
+ raise PassageError, "must supply a valid user_id" if user_id.to_s.empty?
14
14
  begin
15
15
  response = @connection.get("/v1/apps/#{@app_id}/users/#{user_id}")
16
- user = response.body['user']
16
+ user = response.body["user"]
17
17
  user.transform_keys(&:to_sym)
18
18
  return(
19
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']
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
33
  )
34
34
  )
35
35
  rescue Faraday::Error => e
@@ -38,32 +38,32 @@ module Passage
38
38
  "passage User with ID \"#{user_id}\" does not exist"
39
39
  else
40
40
  raise PassageError,
41
- "failed to get Passage User. Http Status: #{e.response[:status]}. Response: #{e.response[:body]['error']}"
41
+ "failed to get Passage User. Http Status: #{e.response[:status]}. Response: #{e.response[:body]["error"]}"
42
42
  end
43
43
  end
44
44
  end
45
45
 
46
46
  def activate(user_id:)
47
- raise PassageError, 'must supply a valid user_id' if user_id.to_s.empty?
47
+ raise PassageError, "must supply a valid user_id" if user_id.to_s.empty?
48
48
  begin
49
49
  response =
50
50
  @connection.patch("/v1/apps/#{@app_id}/users/#{user_id}/activate")
51
- user = response.body['user']
51
+ user = response.body["user"]
52
52
  return(
53
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']
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
67
  )
68
68
  )
69
69
  rescue Faraday::Error => e
@@ -72,32 +72,32 @@ module Passage
72
72
  "passage User with ID \"#{user_id}\" does not exist"
73
73
  else
74
74
  raise PassageError,
75
- "failed to activate Passage User. Http Status: #{e.response[:status]}. Response: #{e.response[:body]['error']}"
75
+ "failed to activate Passage User. Http Status: #{e.response[:status]}. Response: #{e.response[:body]["error"]}"
76
76
  end
77
77
  end
78
78
  end
79
79
 
80
80
  def deactivate(user_id:)
81
- raise PassageError, 'must supply a valid user_id' if user_id.to_s.empty?
81
+ raise PassageError, "must supply a valid user_id" if user_id.to_s.empty?
82
82
  begin
83
83
  response =
84
84
  @connection.patch("/v1/apps/#{@app_id}/users/#{user_id}/deactivate")
85
- user = response.body['user']
85
+ user = response.body["user"]
86
86
  return(
87
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']
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
101
  )
102
102
  )
103
103
  rescue Faraday::Error => e
@@ -106,36 +106,36 @@ module Passage
106
106
  "passage User with ID \"#{user_id}\" does not exist"
107
107
  else
108
108
  raise PassageError,
109
- "failed to deactivate Passage User. Http Status: #{e.response[:status]}. Response: #{e.response[:body]['error']}"
109
+ "failed to deactivate Passage User. Http Status: #{e.response[:status]}. Response: #{e.response[:body]["error"]}"
110
110
  end
111
111
  end
112
112
  end
113
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?
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
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?
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
120
  begin
121
121
  response =
122
122
  @connection.patch("/v1/apps/#{@app_id}/users/#{user_id}", updates)
123
- user = response.body['user']
123
+ user = response.body["user"]
124
124
  return(
125
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']
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
139
  )
140
140
  )
141
141
  rescue Faraday::Error => e
@@ -144,44 +144,44 @@ module Passage
144
144
  "passage User with ID \"#{user_id}\" does not exist"
145
145
  else
146
146
  raise PassageError,
147
- "failed to update Passage User. Http Status: #{e.response[:status]}. Response: #{e.response[:body]['error']}"
147
+ "failed to update Passage User. Http Status: #{e.response[:status]}. Response: #{e.response[:body]["error"]}"
148
148
  end
149
149
  end
150
150
  end
151
151
 
152
- def create(email: '', phone: '', user_metadata: {})
152
+ def create(email: "", phone: "", user_metadata: {})
153
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?
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
157
  begin
158
158
  response = @connection.post("/v1/apps/#{@app_id}/users", create)
159
- user = response.body['user']
159
+ user = response.body["user"]
160
160
  return(
161
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']
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
175
  )
176
176
  )
177
177
  rescue Faraday::Error => e
178
178
  raise PassageError,
179
- "failed to create Passage User. Http Status: #{e.response[:status]}. Response: #{e.response[:body]['error']}"
179
+ "failed to create Passage User. Http Status: #{e.response[:status]}. Response: #{e.response[:body]["error"]}"
180
180
  end
181
181
  end
182
182
 
183
183
  def delete(user_id:)
184
- raise PassageError, 'must supply a valid user_id' if user_id.to_s.empty?
184
+ raise PassageError, "must supply a valid user_id" if user_id.to_s.empty?
185
185
  begin
186
186
  response = @connection.delete("/v1/apps/#{@app_id}/users/#{user_id}")
187
187
  return true
@@ -191,15 +191,15 @@ module Passage
191
191
  "passage User with ID \"#{user_id}\" does not exist"
192
192
  else
193
193
  raise PassageError,
194
- "failed to delete Passage User. Http Status: #{e.response[:status]}. Response: #{e.response[:body]['error']}"
194
+ "failed to delete Passage User. Http Status: #{e.response[:status]}. Response: #{e.response[:body]["error"]}"
195
195
  end
196
196
  end
197
197
  end
198
198
 
199
199
  def delete_device(user_id:, device_id:)
200
- raise PassageError, 'must supply a valid user_id' if user_id.to_s.empty?
200
+ raise PassageError, "must supply a valid user_id" if user_id.to_s.empty?
201
201
  if device_id.to_s.empty?
202
- raise PassageError, 'must supply a valid device_id'
202
+ raise PassageError, "must supply a valid device_id"
203
203
  end
204
204
  begin
205
205
  response =
@@ -209,32 +209,32 @@ module Passage
209
209
  return true
210
210
  rescue Faraday::Error => e
211
211
  raise PassageError,
212
- "failed to delete Passage User Device. Http Status: #{e.response[:status]}. Response: #{e.response[:body]['error']}"
212
+ "failed to delete Passage User Device. Http Status: #{e.response[:status]}. Response: #{e.response[:body]["error"]}"
213
213
  end
214
214
  end
215
215
 
216
216
  def list_devices(user_id:)
217
- raise PassageError, 'must supply a valid user_id' if user_id.to_s.empty?
217
+ raise PassageError, "must supply a valid user_id" if user_id.to_s.empty?
218
218
  begin
219
219
  response =
220
220
  @connection.get("/v1/apps/#{@app_id}/users/#{user_id}/devices")
221
- devicesResp = response.body['devices']
221
+ devicesResp = response.body["devices"]
222
222
  devices = Array.new
223
223
  devicesResp.each do |device|
224
224
  devices.append(
225
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']
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
231
  )
232
232
  )
233
233
  end
234
234
  return devices
235
235
  rescue Faraday::Error => e
236
236
  raise PassageError,
237
- "failed to delete Passage User Device. Http Status: #{e.response[:status]}. Response: #{e.response[:body]['error']}"
237
+ "failed to delete Passage User Device. Http Status: #{e.response[:status]}. Response: #{e.response[:body]["error"]}"
238
238
  end
239
239
  end
240
240
  end
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'faraday'
3
+ require "faraday"
4
4
 
5
- require_relative 'passageidentity/client'
5
+ require_relative "passageidentity/client"
6
6
 
7
7
  module Passage
8
8
  end
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'passageidentity'
3
- s.version = '0.0.3'
3
+ s.version = '0.0.6'
4
4
  s.summary = 'Passage SDK for biometric authentication'
5
5
  s.description =
6
6
  'Enables verification of server-side authentication and user management for applications using Passage'
@@ -27,4 +27,5 @@ Gem::Specification.new do |s|
27
27
  s.add_dependency 'faraday', '>= 0.17.0', '< 2.0'
28
28
  s.add_dependency 'jwt', '>= 2.3.0'
29
29
  s.add_dependency 'openssl', '>= 3.0.0'
30
+ s.add_dependency 'dotenv', '>= 2.7.6'
30
31
  end
data/tests/all.rb CHANGED
@@ -1,2 +1,3 @@
1
- Dir[File.dirname(File.absolute_path(__FILE__)) + '/**/*_test.rb']
2
- .each { |file| require file }
1
+ Dir[File.dirname(File.absolute_path(__FILE__)) + "/**/*_test.rb"].each do |file|
2
+ require file
3
+ end
data/tests/app_test.rb ADDED
@@ -0,0 +1,15 @@
1
+ require_relative "../lib/passageidentity/client"
2
+ require "dotenv"
3
+ require "faraday"
4
+ require "test/unit"
5
+
6
+ Dotenv.load(".env")
7
+ class TestAppAPI < Test::Unit::TestCase
8
+ PassageClient =
9
+ Passage::Client.new(app_id: ENV["APP_ID"], api_key: ENV["API_KEY"])
10
+
11
+ def test_get_app()
12
+ app = PassageClient.get_app()
13
+ assert_equal ENV["APP_ID"], app.id
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ require_relative "../lib/passageidentity/client"
2
+ require "dotenv"
3
+ require "faraday"
4
+ require "test/unit"
5
+
6
+ Dotenv.load(".env")
7
+ class TestUserAPI < Test::Unit::TestCase
8
+ PassageClient =
9
+ Passage::Client.new(app_id: ENV["APP_ID"], api_key: ENV["API_KEY"])
10
+
11
+ def test_authenticate_token
12
+ user_id = PassageClient.auth.authenticate_token(ENV["PSG_JWT"])
13
+ assert_equal ENV["TEST_USER_ID"], user_id
14
+ end
15
+ end
@@ -1,20 +1,21 @@
1
- require_relative '../lib/passageidentity/client'
2
- require_relative './environment'
3
- require 'faraday'
4
- require 'test/unit'
1
+ require_relative "../lib/passageidentity/client"
2
+ require "dotenv"
3
+ require "faraday"
4
+ require "test/unit"
5
5
 
6
+ Dotenv.load(".env")
6
7
  class TestUserAPI < Test::Unit::TestCase
7
8
  PassageClient =
8
- Passage::Client.new(app_id: ENV['APP_ID'], api_key: ENV['API_KEY'])
9
+ Passage::Client.new(app_id: ENV["APP_ID"], api_key: ENV["API_KEY"])
9
10
 
10
11
  def test_create_magi_link()
11
12
  magic_link =
12
13
  PassageClient.create_magic_link(
13
- email: 'chris@passage.id',
14
+ email: "chris@passage.id",
14
15
  channel: Passage::EMAIL_CHANNEL,
15
16
  ttl: 12
16
17
  )
17
18
  assert_equal 12, magic_link.ttl
18
- assert_equal 'chris@passage.id', magic_link.identifier
19
+ assert_equal "chris@passage.id", magic_link.identifier
19
20
  end
20
21
  end
@@ -1,18 +1,19 @@
1
- require_relative '../lib/passageidentity/client'
2
- require_relative './environment'
3
- require 'faraday'
4
- require 'test/unit'
1
+ require_relative "../lib/passageidentity/client"
2
+ require "dotenv"
3
+ require "faraday"
4
+ require "test/unit"
5
5
 
6
+ Dotenv.load(".env")
6
7
  class TestUserAPI < Test::Unit::TestCase
7
8
  PassageClient =
8
- Passage::Client.new(app_id: ENV['APP_ID'], api_key: ENV['API_KEY'])
9
+ Passage::Client.new(app_id: ENV["APP_ID"], api_key: ENV["API_KEY"])
9
10
 
10
11
  def setup()
11
12
  $global_test_user =
12
13
  PassageClient.user.create(
13
- email: 'chris+test-ruby@passage.id',
14
+ email: "chris+test-ruby@passage.id",
14
15
  user_metadata: {
15
- 'example1': 'cool'
16
+ example1: "cool"
16
17
  }
17
18
  )
18
19
  end
@@ -20,13 +21,13 @@ class TestUserAPI < Test::Unit::TestCase
20
21
  def test_create_delete_user()
21
22
  user =
22
23
  PassageClient.user.create(
23
- email: 'chris+test-create-delete@passage.id',
24
+ email: "chris+test-create-delete@passage.id",
24
25
  user_metadata: {
25
- 'example1': 'cool'
26
+ example1: "cool"
26
27
  }
27
28
  )
28
- assert_equal 'chris+test-create-delete@passage.id', user.email
29
- assert_equal 'cool', user.user_metadata['example1']
29
+ assert_equal "chris+test-create-delete@passage.id", user.email
30
+ assert_equal "cool", user.user_metadata["example1"]
30
31
  deleted = PassageClient.user.delete(user_id: user.id)
31
32
  assert_equal true, deleted
32
33
  end
@@ -39,28 +40,28 @@ class TestUserAPI < Test::Unit::TestCase
39
40
  def test_deactivate_user()
40
41
  user = PassageClient.user.deactivate(user_id: $global_test_user.id)
41
42
  assert_equal $global_test_user.id, user.id
42
- assert_equal 'inactive', user.status
43
+ assert_equal "inactive", user.status
43
44
  end
44
45
 
45
46
  def test_activate_user()
46
47
  user = PassageClient.user.activate(user_id: $global_test_user.id)
47
48
  assert_equal $global_test_user.id, user.id
48
- assert_equal 'active', user.status
49
+ assert_equal "active", user.status
49
50
  end
50
51
 
51
52
  def test_update_user()
52
- new_email = 'chris+update_test-ruby@passage.id'
53
+ new_email = "chris+update_test-ruby@passage.id"
53
54
  user =
54
55
  PassageClient.user.update(
55
56
  user_id: $global_test_user.id,
56
57
  email: new_email,
57
58
  user_metadata: {
58
- 'example1': 'lame'
59
+ example1: "lame"
59
60
  }
60
61
  )
61
62
  assert_equal $global_test_user.id, user.id
62
63
  assert_equal new_email, user.email
63
- assert_equal 'lame', user.user_metadata['example1']
64
+ assert_equal "lame", user.user_metadata["example1"]
64
65
  end
65
66
 
66
67
  def test_list_devices()
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.3
4
+ version: 0.0.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Passage Identity
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-04-29 00:00:00.000000000 Z
11
+ date: 2022-05-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -58,6 +58,20 @@ dependencies:
58
58
  - - ">="
59
59
  - !ruby/object:Gem::Version
60
60
  version: 3.0.0
61
+ - !ruby/object:Gem::Dependency
62
+ name: dotenv
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: 2.7.6
68
+ type: :runtime
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: 2.7.6
61
75
  description: Enables verification of server-side authentication and user management
62
76
  for applications using Passage
63
77
  email: support@passage.id
@@ -65,23 +79,21 @@ executables: []
65
79
  extensions: []
66
80
  extra_rdoc_files: []
67
81
  files:
82
+ - ".github/workflows/deploy.yml"
83
+ - ".github/workflows/on_pr.yml"
68
84
  - ".gitignore"
69
85
  - CONTRIBUTING.md
70
86
  - LICENSE
71
87
  - README.md
72
- - lib/passage.rb
73
- - lib/passage/auth.rb
74
- - lib/passage/client.rb
75
- - lib/passage/request.rb
76
- - lib/passage/user.rb
77
88
  - lib/passageidentity.rb
78
89
  - lib/passageidentity/auth.rb
79
90
  - lib/passageidentity/client.rb
80
91
  - lib/passageidentity/error.rb
81
92
  - lib/passageidentity/user_api.rb
82
- - passage-ruby
83
93
  - passageidentity.gemspec
84
94
  - tests/all.rb
95
+ - tests/app_test.rb
96
+ - tests/auth_test.rb
85
97
  - tests/magic_link_test.rb
86
98
  - tests/user_api_test.rb
87
99
  homepage: https://rubygems.org/gems/passageidentity
@@ -89,7 +101,7 @@ licenses:
89
101
  - MIT
90
102
  metadata:
91
103
  source_code_uri: https://github.com/passage-identity/passage-ruby
92
- post_install_message:
104
+ post_install_message:
93
105
  rdoc_options: []
94
106
  require_paths:
95
107
  - lib
@@ -104,8 +116,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
104
116
  - !ruby/object:Gem::Version
105
117
  version: '0'
106
118
  requirements: []
107
- rubygems_version: 3.3.11
108
- signing_key:
119
+ rubygems_version: 3.2.33
120
+ signing_key:
109
121
  specification_version: 4
110
122
  summary: Passage SDK for biometric authentication
111
123
  test_files: []
data/lib/passage/auth.rb DELETED
@@ -1,35 +0,0 @@
1
- require 'openssl'
2
- require 'base64'
3
- require 'jwt'
4
-
5
- module Passage
6
- class Auth
7
-
8
- def initialize(app_id, public_key, auth_origin)
9
-
10
- @app_id = app_id
11
- @auth_origin = auth_origin
12
-
13
- # bas64 decode and then parse the public key
14
- # when we have JWKS endpoint, this will get easier I think
15
- @public_key = OpenSSL::PKey::RSA.new(Base64.decode64(public_key))
16
-
17
- end
18
-
19
- def authenticate(token)
20
-
21
- begin
22
- claims = JWT.decode(token, @public_key, true,{ iss: @app_id, verify_iss: true, aud: @auth_origin, verify_aud: true, algorithms: ["RS256"] })
23
- return claims[0]["sub"]
24
- rescue JWT::InvalidIssuerError
25
- raise JWTInvalidIssuerError
26
- rescue JWT::InvalidAudError
27
- raise JWTInvalidAudienceError
28
- rescue JWT::ExpiredSignature
29
- raise JWTExpiredSignatureError
30
- rescue JWT::IncorrectAlgorithm
31
- raise JWTIncorrectAlgorithmError
32
- end
33
- end
34
- end
35
- end
@@ -1,39 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'request'
4
- require_relative 'auth'
5
-
6
- module Passage
7
- class Client
8
-
9
- attr_reader :auth
10
-
11
- def initialize(app_id:)
12
- @api_url = "https://api.passage.id"
13
- @app_id = app_id
14
-
15
- get_connection()
16
-
17
- fetch_public_key(@connection)
18
-
19
- @auth = Passage::Auth.new(@app_id, @public_key, @auth_origin)
20
- end
21
-
22
- def get_connection
23
- @connection = Faraday.new(url: @api_url) do |f|
24
- f.request :json
25
- f.request :retry
26
- f.response :json
27
- f.adapter :net_http
28
- end
29
- end
30
-
31
- def fetch_public_key(conn)
32
- response = conn.get("/v1/apps/" + @app_id)
33
- # TODO Add error handling
34
- @public_key = response.body["app"]["rsa_public_key"]
35
- @auth_origin = response.body["app"]["auth_origin"]
36
- end
37
-
38
- end
39
- end
@@ -1,32 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Passage
4
- module Request
5
- def get_request(path)
6
- @connection.get(
7
- path
8
- ).body
9
- end
10
-
11
- def post_request(path, data)
12
- @connection.post(
13
- path,
14
- data
15
- ).body
16
- end
17
-
18
- def put_request(path, data)
19
- @connection.put(
20
- path,
21
- data
22
- ).body
23
- end
24
-
25
- def delete_request(path)
26
- @connection.delete(
27
- path
28
- ).body
29
- end
30
-
31
- end
32
- end
data/lib/passage/user.rb DELETED
File without changes
data/lib/passage.rb DELETED
@@ -1,9 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'faraday'
4
-
5
- require_relative 'passage/client'
6
-
7
- module Passage end
8
-
9
-
data/passage-ruby DELETED
@@ -1 +0,0 @@
1
- passage-ruby