passkeys-rails 0.1.7 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 631e7354b6296e5fcc14144ba3bd305ca9047c31f9e5b0173f5eb7123f14fa71
4
- data.tar.gz: 65e1f2d4cbd179ddee7449318b49a838d166db64db9221e1f079ac7e57735bf3
3
+ metadata.gz: 6d6b32ba5bcfa9553687bb70a01ba4e1b9211ab50336e62f3f7eae7033c3f689
4
+ data.tar.gz: e53cc11ffec79936ec74319fb1ec6d47cd74d67f4f56ff0c2d234abf8800b1f3
5
5
  SHA512:
6
- metadata.gz: f8799f782713ae01ca312506c39ef2bed3a56448fb0b4e78d4c75a441b4a3fbac731a5bcf3f23421f9b240e84346f80028bd4545c8edbc14b4ed67769e92e3ef
7
- data.tar.gz: 23ce3eab9360b27dd23732589dc3794bb6c6c8cfe6ca9bf289830265ad8697ce0a2779836c0b4ff37c2a5c3211dffaaf74e817e63d0bc96417ad62af8048e444
6
+ metadata.gz: 87cb708335cfae465b142633dc829c92ac16c4d9d958807cbb682caf455ca18e2b13a24ad30b1bb3020f3493b6dd924151269eb37ba2eaeb09508d5b350a9572
7
+ data.tar.gz: 04fcd5259c6fe2b1d06f289254817fc1baee73e5d7c7d2aee3476a2155e02e2cb4c40c02deab4285a602bbe9c51806a3e90c5f15ed2b3de928ca9843b0f9fd14
data/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ ### 0.2.0
2
+
3
+ * Added passkeys/debug_login functionality.
4
+
1
5
  ### 0.1.7
2
6
 
3
7
  * Added IntegrationHelpers to support client testing.
data/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  # PasskeysRails
6
6
 
7
- Devise is awesome, but we don't need all that UI/UX for PassKeys.
7
+ Devise is awesome, but we don't need all that UI/UX for PassKeys, especially for an API back end.
8
8
 
9
9
  The purpose of this gem is to make it easy to provide a rails back end API that supports PassKey authentication. It uses the [`webauthn`](https://github.com/w3c/webauthn) gem to do the cryptographic work and presents a simple API interface for passkey registration and authentication.
10
10
 
@@ -177,12 +177,237 @@ end
177
177
 
178
178
  ### Mobile Application Integration
179
179
 
180
- **TODO**: Describe the APIs and point to the soon-to-be-created reference mobile applications for how to use **passkeys-rails** for passkey authentication.
180
+ There are n groups of API endpoints that your mobile application may consume.
181
+
182
+ 1. Unauthenticated (public) endpoints
183
+ 1. Authenticated (private) endpoints
184
+ 1. Passey endpoints (for supporting authentication)
185
+
186
+ **Unauthenticated endpoints** can be consumed without and authentication.
187
+
188
+ **Authenticated endpoints** are protected by `authenticate_passkey!` or `PasskeysRails.authenticate!(request)`. Those methods check for and validate the `X-Auth` header, which must be set to the auth token returned in the `AuthResponse`, described below.
189
+
190
+ **Passkey endpoints** are supplied by this gem and allow you to register a user, authenticate (login) a user, and refresh the token. This section describes these endpoints.
191
+
192
+ All Passkey endpoints accept and respond with JSON.
193
+
194
+ On **success**, they will respond with a 200 or 201 response code and relevant JSON.
195
+
196
+ On **error**, they will respond with a status code of `422` (Unprocessable Entity) and a JSON `ErrorResponse` structure:
197
+
198
+ ```JSON
199
+ {
200
+ "error": {
201
+ "context": "authentication",
202
+ "code": "Specific text code",
203
+ "message": "Some human readable message"
204
+ }
205
+ }
206
+ ```
207
+
208
+ Some endpoints return an `AuthResponse`, which has this JSON structure:
209
+
210
+ ```JSON
211
+ {
212
+ "username": String, # the username used during registration
213
+ "auth_token": String # an expiring token to use to authenticate with the back end (X-Auth header)
214
+ }
215
+ ```
216
+
217
+ #### POST /passkeys/challenge
218
+
219
+ Submit this to begin registration or authentication.
220
+
221
+ Supply a `{ "username": "unique username" } ` to register a new credential.
222
+ If all goes well, the JSON response will be the `options_for_create` from webauthn.
223
+ If the username is already in use, or anything else goes wrong, an error with code `validation_errors` will be returned.
224
+
225
+ Omit the `username` when authenticating (logging in).
226
+ The JSON response will be the `options_for_get` from webauthn.
227
+
228
+ #### POST /passkeys/register
229
+
230
+ After calling the `challenge` endpoint with a `username`, and handling its response, finish registering by calling this endpoint.
231
+
232
+ Supply the following JSON structure:
233
+
234
+ ```JSON
235
+ # POST body
236
+ {
237
+ # NOTE: credential will likely come directly from the PassKeys class/library on the platform
238
+ "credential": {
239
+ "id": String,
240
+ "rawId": String,
241
+ "type": String,
242
+ "response": {
243
+ "attestationObject": String,
244
+ "clientDataJSON": String
245
+ }
246
+ },
247
+ # authenticatable is optional and is informas PasskeysRails how to build your "user" model
248
+ "authenticatable": { # optional
249
+ "class": "User", # whatever class to which you want this credential to apply (as described earlier)
250
+ "params": { } # Any params you want passed as a hash to the registering_with method on that class
251
+ }
252
+ }
253
+ ```
254
+
255
+ On **success**, the response is an `AuthResponse`.
256
+
257
+ Possible **failure codes** (using the `ErrorResponse` structure) are:
258
+
259
+ - webauthn_error - something is wrong with the credential
260
+ - error - something else went wrong during credentail validation - see the `message` in the `ErrorResponse`
261
+ - passkey_error - unable to persiste the passkey
262
+ - invalid_authenticatable_class - the supplied authenticatable class can't be created/found (check spelling & capitalization)
263
+ - invalid_class_whitelist - the whitelist in the passkeys_rails.rb configuration is invalid - be sure it's nil or an array
264
+ - invalid_authenticatable_class - the supplied authenticatable class is not allowed - maybe it's not in the whitelist
265
+ - record_invalid - the object of the supplied authenticatable class cannot be saved due to validation errors
266
+ - agent_not_found - the agent referenced in the credential cannot be found in the database
267
+
268
+ #### POST /passkeys/authenticate
269
+
270
+ After calling the `challenge` endpoint without a `username`, and handling its response, finish authenticating by calling this endpoint.
271
+
272
+ Supply the following JSON structure:
273
+
274
+ ```JSON
275
+ # POST body
276
+ {
277
+ # NOTE: all of this will likely come directly from the PassKeys class/library on the platform
278
+ "id": String, # Base64 encoded assertion.credentialID
279
+ "rawId": String, # Base64 encoded assertion.credentialID
280
+ "type": "public-key",
281
+ "response": {
282
+ "authenticatorData": String, # Base64 encoded assertion.rawAuthenticatorData
283
+ "clientDataJSON": String, # Base64 encoded assertion.rawClientDataJSON
284
+ "signature": String, # Base64 encoded signature
285
+ "userHandle":String # Base64 encoded assertion.userID
286
+ }
287
+ }
288
+ ```
289
+ On **success**, the response is an `AuthResponse`.
290
+
291
+ Possible **failure codes** (using the `ErrorResponse` structure) are:
292
+
293
+ - webauthn_error - something is wrong with the credential
294
+ - passkey_not_found - the passkey referenced in the credential cannot be found in the database
295
+
296
+ #### POST /passkeys/refresh
297
+
298
+ The token will expire after some time (configurable in passkeys_rails.rb). Before that happens, refresh it using this API. Once it's expired, to get a new token, use the /authentication API.
299
+
300
+ Supply the following JSON structure:
301
+
302
+ ```JSON
303
+ # POST body
304
+ {
305
+ token: String
306
+ }
307
+ ```
308
+
309
+ On **success**, the response is an `AuthResponse` with a new, refreshed token.
310
+
311
+ Possible **failure codes** (using the `ErrorResponse` structure) are:
312
+
313
+ - invalid_token - the token data is invalid
314
+ - expired_token - the token is expired
315
+ - token_error - some other error ocurred when decoding the token
316
+
317
+ #### POST /passkeys/debug_login
318
+
319
+ As it may not be possible to acess Passkey functionality in mobile simulators, this endpoint may be called to login (authenticate) a username while bypassing the normal challenge/response sequence.
320
+
321
+ This endpoint only responds if DEBUG_LOGIN_REGEX is set in the server environment. It is very insecure to set this variable in a production environment as it bypasses all Passkey checks. It is only intended to be used during mobile application development.
322
+
323
+ To use this endpoint:
324
+
325
+ 1. Manually create one or more PasskeysRails::Agent records in the database. A unique username is required for each.
326
+
327
+ 1. Set DEBUG_LOGIN_REGEX to a regex that matches any username you want to use during development - for example `^test(-\d+)?$` will match `test`, `test-1`, `test-123`, etc.
328
+
329
+ 1. In the mobile application, call this endpoint in stead of the /passkeys/challenge and /passkeys/authenticate. The response is identicial to that of /passkeys/authenticate.
330
+
331
+ 1. Use the response as if it was from /passkeys/authenticate.
332
+
333
+ If you supply a username that doesn't match the DEBUG_LOGIN_REGEX, the endpoint will respond with an error.
334
+
335
+ ```JSON
336
+ # POST body
337
+ {
338
+ "username": String
339
+ }
340
+ ```
341
+ On **success**, the response is an `AuthResponse`.
342
+
343
+ Possible **failure codes** (using the `ErrorResponse` structure) are:
344
+
345
+ - not_allowed - Invalid username (the username doesn't match the regex)
346
+ - agent_not_found - No agent found with that username
347
+
348
+ ## Reference/Example Mobile Applications
349
+
350
+ **TODO**: Point to the soon-to-be-created reference mobile applications for how to use **passkeys-rails** for passkey authentication.
181
351
 
182
352
  ## Contributing
183
353
 
184
- Contact me if you'd like to contribute time, energy, etc. to this project.
354
+ ### Contributing Guidelines
355
+
356
+ Thank you for considering contributing to PasskeysRails! We welcome your help to improve and enhance this project. Whether it's a bug fix, documentation update, or a new feature, your contributions are valuable to the community.
357
+
358
+ To ensure a smooth collaboration, please follow the guidelines below when submitting your contributions:
359
+
360
+ #### Code of Conduct
361
+
362
+ Please note that this project follows the [Code of Conduct](https://github.com/alliedcode/passkeys-rails/blob/main/CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. If you encounter any behavior that violates the code, please report it to the project maintainers.
363
+
364
+ #### How to Contribute
365
+
366
+ 1. Fork the repository on GitHub.
367
+
368
+ 2. Create a new branch for your contribution. Use a descriptive name that reflects the purpose of your changes.
369
+
370
+ 3. Make your changes and commit them with clear and concise messages. Remember to follow the project's coding style and guidelines.
371
+
372
+ 4. Before submitting a pull request, ensure that your changes pass all existing tests and add relevant tests if applicable.
373
+
374
+ 5. Update the documentation if your changes introduce new features, modify existing behavior, or require user instructions.
375
+
376
+ 6. Squash your commits into a single logical commit if needed. Keep your commit history clean and focused.
377
+
378
+ 7. Submit a pull request against the `main` branch of the original repository.
379
+
380
+ 8. Add a comment at the top of the CHANGELOG.md describing the change.
381
+
382
+ #### Pull Request Guidelines
383
+
384
+ When submitting a pull request, please include the following details:
385
+
386
+ - A clear description of the changes you made and the problem it solves.
387
+
388
+ - Any relevant issue numbers that your pull request addresses or fixes.
389
+
390
+ - The steps to test your changes, so the project maintainers can verify them.
391
+
392
+ - Ensure that your pull request title and description are descriptive and informative.
393
+
394
+ #### Code Review Process
395
+
396
+ All pull requests will undergo a code review process by the project maintainers. We appreciate your patience during this review process. Constructive feedback may be provided, and further changes might be requested.
397
+
398
+ #### Contributor License Agreement
399
+
400
+ By submitting a pull request, you acknowledge that your contributions will be licensed under the project's [MIT License](https://github.com/alliedcode/passkeys-rails/blob/main/MIT-LICENSE).
401
+
402
+ #### Reporting Issues
403
+
404
+ If you encounter any bugs, problems, or have suggestions for improvement, please create an issue on the GitHub repository. Provide clear and detailed information about the issue to help us address it efficiently.
405
+
406
+ #### Thank You
407
+
408
+ Your contributions are valuable, and we sincerely appreciate your efforts to improve PasskeysRails. Together, we can build a better software ecosystem for the community. Thank you for your support and happy contributing!
409
+
185
410
 
186
411
  ## License
187
412
 
188
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
413
+ The gem is available as open source under the terms of the [MIT License](https://github.com/alliedcode/passkeys-rails/blob/main/MIT-LICENSE).
@@ -30,6 +30,15 @@ module PasskeysRails
30
30
  render json: { username: result.username, auth_token: result.auth_token }
31
31
  end
32
32
 
33
+ # This action exists to allow easier mobile app debugging as it may not
34
+ # be possible to acess Passkey functionality in mobile simulators.
35
+ # It is only routable if DEBUG_LOGIN_REGEX is set in the server environment.
36
+ # CAUTION: It is very insecure to set DEBUG_LOGIN_REGEX in a production environment.
37
+ def debug_login
38
+ result = PasskeysRails::DebugLogin.call!(username: debug_login_params[:username])
39
+ render json: { username: result.username, auth_token: result.auth_token }
40
+ end
41
+
33
42
  protected
34
43
 
35
44
  def challenge_params
@@ -57,5 +66,10 @@ module PasskeysRails
57
66
  params.require(:auth_token)
58
67
  params.permit(:auth_token)
59
68
  end
69
+
70
+ def debug_login_params
71
+ params.require(:username)
72
+ params.permit(:username)
73
+ end
60
74
  end
61
75
  end
@@ -0,0 +1,43 @@
1
+ # This functionality exists to allow easier mobile app debugging as it may not
2
+ # be possible to acess Passkey functionality in mobile simulators.
3
+ # It is only operational if DEBUG_LOGIN_REGEX is set in the server environment.
4
+ # CAUTION: It is very insecure to set DEBUG_LOGIN_REGEX in a production environment.
5
+ module PasskeysRails
6
+ class DebugLogin
7
+ include Interactor
8
+
9
+ delegate :username, to: :context
10
+
11
+ def call
12
+ ensure_debug_mode
13
+ ensure_regex_match
14
+
15
+ context.username = agent.username
16
+ context.auth_token = GenerateAuthToken.call!(agent:).auth_token
17
+ rescue Interactor::Failure => e
18
+ context.fail! code: e.context.code, message: e.context.message
19
+ end
20
+
21
+ private
22
+
23
+ def ensure_debug_mode
24
+ context.fail!(code: :not_allowed, message: 'Action not allowed') if username_regex.blank?
25
+ end
26
+
27
+ def ensure_regex_match
28
+ context.fail!(code: :not_allowed, message: 'Invalid username') unless username&.match?(username_regex)
29
+ end
30
+
31
+ def username_regex
32
+ PasskeysRails.debug_login_regex
33
+ end
34
+
35
+ def agent
36
+ @agent ||= begin
37
+ agent = Agent.find_by(username:)
38
+ context.fail!(code: :agent_not_found, message: "No agent found with that username") if agent.blank?
39
+ agent
40
+ end
41
+ end
42
+ end
43
+ end
data/config/routes.rb CHANGED
@@ -3,4 +3,11 @@ PasskeysRails::Engine.routes.draw do
3
3
  post 'passkeys/register'
4
4
  post 'passkeys/authenticate'
5
5
  post 'passkeys/refresh'
6
+
7
+ # This route exists to allow easier mobile app debugging as it may not
8
+ # be possible to acess Passkey functionality in mobile simulators.
9
+ # CAUTION: It is very insecure to set DEBUG_LOGIN_REGEX in a production environment.
10
+ constraints(->(_request) { PasskeysRails.debug_login_regex.present? }) do
11
+ post 'passkeys/debug_login'
12
+ end
6
13
  end
@@ -45,6 +45,12 @@ module PasskeysRails
45
45
  # for example: %w[User AdminUser]
46
46
  mattr_accessor :class_whitelist, default: nil
47
47
 
48
+ # This is only used by the debug_login endpoint.
49
+ # CAUTION: It is very insecure to set DEBUG_LOGIN_REGEX in a production environment.
50
+ def self.debug_login_regex
51
+ ENV['DEBUG_LOGIN_REGEX'].present? ? Regexp.new(ENV['DEBUG_LOGIN_REGEX']) : nil
52
+ end
53
+
48
54
  # Returns an Interactor::Context that indicates if the request is authentic.
49
55
  #
50
56
  # .success? is true if authentic
@@ -1,3 +1,3 @@
1
1
  module PasskeysRails
2
- VERSION = "0.1.7".freeze
2
+ VERSION = "0.2.0".freeze
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: passkeys-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.7
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Troy Anderson
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-07-26 00:00:00.000000000 Z
11
+ date: 2023-07-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -357,6 +357,7 @@ files:
357
357
  - app/interactors/passkeys_rails/begin_authentication.rb
358
358
  - app/interactors/passkeys_rails/begin_challenge.rb
359
359
  - app/interactors/passkeys_rails/begin_registration.rb
360
+ - app/interactors/passkeys_rails/debug_login.rb
360
361
  - app/interactors/passkeys_rails/finish_authentication.rb
361
362
  - app/interactors/passkeys_rails/finish_registration.rb
362
363
  - app/interactors/passkeys_rails/generate_auth_token.rb