action_auth 0.1.10 → 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: 5bea0149d86872b2b1c33c027547c89053a8cd4519459e320049f578e2f27bc3
4
- data.tar.gz: ab18c246998b4b22c8c319a64b67ee4b9975d87cbf42c9358cfb219de73ce3d3
3
+ metadata.gz: 2da21219355f4b3f9fedb9ebd753a39468ca5e5d4f818a704acf67c99ec1f595
4
+ data.tar.gz: cebdf0acc073b632b4c393b923ef4e76580f52383503ee0702d46b752099fe6a
5
5
  SHA512:
6
- metadata.gz: da7f5ba2158c2129dd24f318b60dc17ccd6a489607e9f0d54af5c874f62d9dbe267e6f13e8c24f8a8d07254a84f09df6adb3a8cf5ae38b438fc16b405d4e70e5
7
- data.tar.gz: 2ca16c8b8b5a9866f0c5b99bbe29021a1ff0fb0108f933647ee6a0054741201eb0f8bfd4f52342785ad2b49a0a8a027cbc81a91402f626c08ee7e37c6239c060
6
+ metadata.gz: 5eb15aaec06bcf5779e690e70acac48046a1fcbd17efc52270a4a542b2913f489c7ff8da42632ffe86f5b9860652de9aae8a76f6a54b4261923bc441d6d6ba42
7
+ data.tar.gz: 405ae5545bf0d919317d4944f0c85b99dba7e5e7f7ea70d391a516838c75ff1780633da9672cf2da2474ede30e01ae5d9bd883977f18c3fbb9d036b5a0285120
data/README.md CHANGED
@@ -15,7 +15,7 @@ bundle add action_auth
15
15
  bin/rails action_auth:install:migrations
16
16
  ```
17
17
 
18
- Modify config/routes.rb to include the following:
18
+ Modify config/routes.rb to include the following (note that the path can be anything you want):
19
19
 
20
20
  ```ruby
21
21
  mount ActionAuth::Engine => 'action_auth'
@@ -25,7 +25,7 @@ In your view layout
25
25
 
26
26
  ```ruby
27
27
  <% if user_signed_in? %>
28
- <li><%= link_to "Sessions", user_sessions_path %></li>
28
+ <li><%= link_to "Security", user_sessions_path %></li>
29
29
  <li><%= button_to "Sign Out", user_session_path(current_session), method: :delete %></li>
30
30
  <% else %>
31
31
  <li><%= link_to "Sign In", new_user_session_path %></li>
@@ -33,6 +33,8 @@ In your view layout
33
33
  <% end %>
34
34
  ```
35
35
 
36
+ See [WebAuthn](#webauthn) for additional configuration.
37
+
36
38
  ## Features
37
39
 
38
40
  These are the planned features for ActionAuth. The ones that are checked off are currently implemented. The ones that are not checked off are planned for future releases.
@@ -45,9 +47,11 @@ These are the planned features for ActionAuth. The ones that are checked off are
45
47
 
46
48
  ✅ - Cookie-based sessions
47
49
 
48
- - Multifactor Authentication
50
+ - Device Session Management
51
+
52
+ ✅ - Multifactor Authentication (through Passkeys)
49
53
 
50
- - Passkeys/Hardware Security Keys
54
+ - Passkeys/Hardware Security Keys
51
55
 
52
56
  ⏳ - Magic Links
53
57
 
@@ -127,6 +131,41 @@ versus a user that is not logged in.
127
131
  end
128
132
  root to: 'welcome#index'
129
133
 
134
+ ## WebAuthn
135
+
136
+ ActionAuth's approach for WebAuthn is simplicity. It is used as a multifactor authentication step,
137
+ so users will still need to register their email address and password. Once the user is registered,
138
+ they can add a Passkey to their account. The Passkey could be an iCloud Keychain, a hardware security
139
+ key like a Yubikey, or a mobile device. If enabled and configured, the user will be prompted to use
140
+ their Passkey after they log in.
141
+
142
+ ### Configuration
143
+
144
+ The migrations are already copied over to your application when you run
145
+ `bin/rails action_auth:install:migrations`. There are only two steps that you have to take to enable
146
+ WebAuthn for your application.
147
+
148
+ The reason why you need to add the gem is because it's not added to the gemspec of ActionAuth. This is
149
+ intentional as not all users will want to add this functionality. This will help minimize
150
+ the number of gems that your application relies on unless if they are features that you want to use.
151
+
152
+ #### Add the gem
153
+
154
+ ```
155
+ bundle add webauthn
156
+ ```
157
+
158
+ ### Configure the WebAuthn settings
159
+
160
+ **Note:** that the origin name does not have a trailing / or a port number.
161
+
162
+ ```
163
+ ActionAuth.configure do |config|
164
+ config.webauthn_enabled = true
165
+ config.webauthn_origin = "http://localhost:3000" # or "https://example.com"
166
+ config.webauthn_rp_name = Rails.application.class.to_s.deconstantize
167
+ end
168
+ ```
130
169
 
131
170
  ## License
132
171
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -134,5 +173,6 @@ The gem is available as open source under the terms of the [MIT License](https:/
134
173
 
135
174
  ## Credits
136
175
 
137
- Heavily inspired by [Drifting Ruby #300](https://www.driftingruby.com/episodes/authentication-from-scratch)
138
- and [Authentication Zero](https://github.com/lazaronixon/authentication-zero).
176
+ ❤️ Heavily inspired by [Drifting Ruby #300](https://www.driftingruby.com/episodes/authentication-from-scratch)
177
+ and [Authentication Zero](https://github.com/lazaronixon/authentication-zero) and WebAuthn work from
178
+ [cedarcode](https://www.cedarcode.com/).
@@ -1 +1,2 @@
1
1
  //= link_directory ../stylesheets/action_auth .css
2
+ //= link_directory ../javascripts/action_auth .js
@@ -0,0 +1,96 @@
1
+ import { Application, Controller } from "https://unpkg.com/@hotwired/stimulus/dist/stimulus.js";
2
+ window.Stimulus = Application.start();
3
+
4
+ import * as WebAuthnJSON from 'https://unpkg.com/@github/webauthn-json@2.1.1/dist/esm/webauthn-json.js';
5
+ const Credential = {
6
+ getCRFSToken: function () {
7
+ const CSRFSelector = document.querySelector('meta[name="csrf-token"]');
8
+ if (CSRFSelector) {
9
+ return CSRFSelector.getAttribute("content");
10
+ } else {
11
+ return null;
12
+ }
13
+ },
14
+
15
+ callback: function (url, body) {
16
+ const token = this.getCRFSToken();
17
+ fetch(url, {
18
+ method: "POST",
19
+ body: JSON.stringify(body),
20
+ headers: {
21
+ "Content-Type": "application/json",
22
+ "Accept": "application/json",
23
+ "X-CSRF-Token": token
24
+ },
25
+ credentials: 'same-origin'
26
+ }).then(function (response) {
27
+ if (response.ok) {
28
+ window.location.replace("/");
29
+ } else if (response.status < 500) {
30
+ response.text();
31
+ }
32
+ });
33
+ },
34
+
35
+ create: function (callbackUrl, credentialOptions) {
36
+ const self = this;
37
+ WebAuthnJSON.create({ "publicKey": credentialOptions }).then(function (credential) {
38
+ self.callback(callbackUrl, credential);
39
+ });
40
+ },
41
+
42
+ get: function (credentialOptions) {
43
+ const self = this;
44
+ const webauthnUrl = document.querySelector('meta[name="webauthn_auth_url"]').getAttribute("content");
45
+ WebAuthnJSON.get({ "publicKey": credentialOptions }).then(function (credential) {
46
+ self.callback(webauthnUrl, credential);
47
+ });
48
+ }
49
+ };
50
+
51
+
52
+ Stimulus.register(
53
+ "add-credential",
54
+ class extends Controller {
55
+ create(event) {
56
+ const webauthnUrl = document.querySelector('meta[name="webauthn_cred_url"]').getAttribute("content");
57
+ const credentialOptions = event.detail;
58
+ const credential_nickname = event.target.querySelector("input[name='webauthn_credential[nickname]']").value;
59
+ const callback_url = `${webauthnUrl}/?credential_nickname=${credential_nickname}`
60
+ Credential.create(encodeURI(callback_url), credentialOptions);
61
+ }
62
+ }
63
+ );
64
+
65
+ Stimulus.register(
66
+ "credential-authenticator",
67
+ class extends Controller {
68
+ verifyKey(event) {
69
+ const credentialOptions = event.detail;
70
+ console.log(credentialOptions);
71
+ Credential.get(credentialOptions);
72
+ }
73
+ }
74
+ );
75
+
76
+ document.addEventListener('DOMContentLoaded', function () {
77
+ const form = document.getElementById('webauthn_credential_form');
78
+ if (form) {
79
+ form.addEventListener('submit', function (event) {
80
+ event.preventDefault();
81
+ const formData = new FormData(form);
82
+ fetch(form.action, {
83
+ method: 'POST',
84
+ body: formData,
85
+ headers: {
86
+ 'X-CSRF-Token': document.querySelector('[name="csrf-token"]').content
87
+ },
88
+ credentials: 'same-origin'
89
+ }).then(response => {
90
+ return response.json();
91
+ }).then(data => {
92
+ form.dispatchEvent(new CustomEvent('ajax:success', { detail: data }));
93
+ });
94
+ });
95
+ }
96
+ });
@@ -1,7 +1,9 @@
1
1
  module ActionAuth
2
2
  class SessionsController < ApplicationController
3
3
  before_action :set_current_request_details
4
+ before_action :authenticate_user!, only: [:index, :destroy]
4
5
  layout "action_auth/application-full-width", only: :index
6
+
5
7
  def index
6
8
  @sessions = Current.user.action_auth_sessions.order(created_at: :desc)
7
9
  end
@@ -11,9 +13,14 @@ module ActionAuth
11
13
 
12
14
  def create
13
15
  if user = User.authenticate_by(email: params[:email], password: params[:password])
14
- @session = user.action_auth_sessions.create
15
- cookies.signed.permanent[:session_token] = { value: @session.id, httponly: true }
16
- redirect_to main_app.root_path, notice: "Signed in successfully"
16
+ if user.second_factor_enabled?
17
+ session[:webauthn_user_id] = user.id
18
+ redirect_to new_webauthn_credential_authentications_path
19
+ else
20
+ @session = user.action_auth_sessions.create
21
+ cookies.signed.permanent[:session_token] = { value: @session.id, httponly: true }
22
+ redirect_to main_app.root_path, notice: "Signed in successfully"
23
+ end
17
24
  else
18
25
  redirect_to sign_in_path(email_hint: params[:email]), alert: "That email or password is incorrect"
19
26
  end
@@ -22,7 +29,7 @@ module ActionAuth
22
29
  def destroy
23
30
  session = Current.user.action_auth_sessions.find(params[:id])
24
31
  session.destroy
25
- redirect_to(main_app.root_path, notice: "That session has been logged out")
32
+ redirect_to main_app.root_path, notice: "That session has been logged out"
26
33
  end
27
34
  end
28
35
  end
@@ -0,0 +1,57 @@
1
+ class ActionAuth::WebauthnCredentialAuthenticationsController < ApplicationController
2
+ before_action :ensure_user_not_authenticated
3
+ before_action :ensure_login_initiated
4
+ layout "action_auth/application-full-width"
5
+
6
+ def new
7
+ end
8
+
9
+ def options
10
+ get_options = WebAuthn::Credential.options_for_get(allow: user.action_auth_webauthn_credentials.pluck(:external_id))
11
+
12
+ session[:current_challenge] = get_options.challenge
13
+
14
+ respond_to do |format|
15
+ format.json { render json: get_options }
16
+ end
17
+ end
18
+
19
+ def create
20
+ webauthn_credential = WebAuthn::Credential.from_get(params)
21
+
22
+ credential = user.action_auth_webauthn_credentials.find_by(external_id: webauthn_credential.id)
23
+
24
+ begin
25
+ webauthn_credential.verify(
26
+ session[:current_challenge],
27
+ public_key: credential.public_key,
28
+ sign_count: credential.sign_count
29
+ )
30
+
31
+ credential.update!(sign_count: webauthn_credential.sign_count)
32
+ session.delete(:webauthn_user_id)
33
+ session = user.action_auth_sessions.create
34
+ cookies.signed.permanent[:session_token] = { value: session.id, httponly: true }
35
+ render json: { status: "ok" }, status: :ok
36
+ rescue WebAuthn::Error => e
37
+ Rails.logger.error "❌ Verification failed: #{e.message}"
38
+ render json: "Verification failed: #{e.message}", status: :unprocessable_entity
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ def user
45
+ @user ||= ActionAuth::User.find_by(id: session[:webauthn_user_id])
46
+ end
47
+
48
+ def ensure_login_initiated
49
+ return unless session[:webauthn_user_id].blank?
50
+ redirect_to sign_in_path
51
+ end
52
+
53
+ def ensure_user_not_authenticated
54
+ return unless current_user
55
+ redirect_to main_app.root_path
56
+ end
57
+ end
@@ -0,0 +1,57 @@
1
+ class ActionAuth::WebauthnCredentialsController < ApplicationController
2
+ before_action :authenticate_user!
3
+ layout "action_auth/application-full-width"
4
+
5
+ def new
6
+ end
7
+
8
+ def options
9
+ if current_user.webauthn_id.blank?
10
+ current_user.update!(webauthn_id: WebAuthn.generate_user_id)
11
+ end
12
+
13
+ create_options = WebAuthn::Credential.options_for_create(
14
+ user: {
15
+ id: current_user.webauthn_id,
16
+ name: current_user.email
17
+ },
18
+ exclude: current_user.action_auth_webauthn_credentials.pluck(:external_id)
19
+ )
20
+
21
+ session[:current_challenge] = create_options.challenge
22
+
23
+ respond_to do |format|
24
+ format.json { render json: create_options }
25
+ end
26
+ end
27
+
28
+ def create
29
+ webauthn_credential = WebAuthn::Credential.from_create(params)
30
+
31
+ begin
32
+ webauthn_credential.verify(session[:current_challenge])
33
+
34
+ credential = current_user.action_auth_webauthn_credentials.build(
35
+ external_id: webauthn_credential.id,
36
+ nickname: params[:credential_nickname],
37
+ public_key: webauthn_credential.public_key,
38
+ sign_count: webauthn_credential.sign_count
39
+ )
40
+
41
+ if credential.save
42
+ render json: { status: "ok" }, status: :ok
43
+ else
44
+ render json: "Couldn't add your Security Key", status: :unprocessable_entity
45
+ end
46
+ rescue WebAuthn::Error => e
47
+ Rails.logger.error "❌ Verification failed: #{e.message}"
48
+ render json: "Verification failed: #{e.message}", status: :unprocessable_entity
49
+ end
50
+ end
51
+
52
+ def destroy
53
+ current_user.action_auth_webauthn_credentials.destroy(params[:id])
54
+
55
+ redirect_to sessions_path
56
+ end
57
+ end
@@ -2,6 +2,14 @@ module ActionAuth
2
2
  class User < ApplicationRecord
3
3
  has_secure_password
4
4
 
5
+ has_many :action_auth_sessions, dependent: :destroy,
6
+ class_name: "ActionAuth::Session", foreign_key: "action_auth_user_id"
7
+
8
+ if ActionAuth.configuration.webauthn_enabled?
9
+ has_many :action_auth_webauthn_credentials, dependent: :destroy,
10
+ class_name: "ActionAuth::WebauthnCredential", foreign_key: "action_auth_user_id"
11
+ end
12
+
5
13
  generates_token_for :email_verification, expires_in: 2.days do
6
14
  email
7
15
  end
@@ -10,8 +18,6 @@ module ActionAuth
10
18
  password_salt.last(10)
11
19
  end
12
20
 
13
- has_many :action_auth_sessions, dependent: :destroy, class_name: "ActionAuth::Session", foreign_key: "action_auth_user_id"
14
-
15
21
  validates :email, presence: true, uniqueness: true, format: { with: URI::MailTo::EMAIL_REGEXP }
16
22
  validates :password, allow_nil: true, length: { minimum: 12 }
17
23
 
@@ -24,5 +30,10 @@ module ActionAuth
24
30
  after_update if: :password_digest_previously_changed? do
25
31
  action_auth_sessions.where.not(id: Current.session).delete_all
26
32
  end
33
+
34
+ def second_factor_enabled?
35
+ return false unless ActionAuth.configuration.webauthn_enabled?
36
+ action_auth_webauthn_credentials.any?
37
+ end
27
38
  end
28
39
  end
@@ -0,0 +1,12 @@
1
+ module ActionAuth
2
+ class WebauthnCredential < ApplicationRecord
3
+ validates :external_id, :public_key, :nickname, :sign_count, presence: true
4
+ validates :external_id, uniqueness: true
5
+ validates :sign_count,
6
+ numericality: {
7
+ only_integer: true,
8
+ greater_than_or_equal_to: 0,
9
+ less_than_or_equal_to: 2**32 - 1
10
+ }
11
+ end
12
+ end
@@ -5,6 +5,7 @@
5
5
  </h1>
6
6
  <small><%= link_to "back to app", main_app.root_path %></small>
7
7
 
8
+ <h2>Sessions</h2>
8
9
  <div id="sessions">
9
10
  <table class="action-auth--table">
10
11
  <thead>
@@ -28,3 +29,38 @@
28
29
  </table>
29
30
  </div>
30
31
 
32
+ <% if ActionAuth.configuration.webauthn_enabled? %>
33
+ <% if current_user.second_factor_enabled? %>
34
+ <h3>Your Security Keys:</h3>
35
+ <table class="action-auth--table">
36
+ <thead>
37
+ <tr>
38
+ <th>Key</th>
39
+ <th nowrap>Registered On</th>
40
+ <th nowrap></th>
41
+ </tr>
42
+ </thead>
43
+ <tbody>
44
+ <% current_user.action_auth_webauthn_credentials.each do |credential| %>
45
+ <%= content_tag :tr, id: dom_id(credential) do %>
46
+ <td><%= credential.nickname %></td>
47
+ <td nowrap><%= credential.created_at.strftime('%B %d, %Y') %></td>
48
+ <td nowrap><%= button_to "Delete", credential, method: :delete, class: "btn btn-primary" %></td>
49
+ <% end %>
50
+ <% end %>
51
+ </tbody>
52
+ </table>
53
+
54
+ <div>
55
+ <%= button_to("Register new Security Key",
56
+ new_webauthn_credential_path,
57
+ method: :get,
58
+ class: "btn btn-primary") %>
59
+ </div>
60
+ <% else %>
61
+ <h2>Enable Two-Factor Authentication</h2>
62
+ <div class="">
63
+ <%= link_to "Add a passkey", new_webauthn_credential_path, class: "btn btn-primary" %>
64
+ </div>
65
+ <% end %>
66
+ <% end %>
@@ -0,0 +1,16 @@
1
+ <h2 class="">Use one of your security keys to sign in</h2>
2
+
3
+ <%= form_with scope: :webauthn_credential_authentication,
4
+ url: options_for_webauthn_credential_authentications_path,
5
+ id: "webauthn_credential_form",
6
+ data: { controller: "credential-authenticator", action: "ajax:success->credential-authenticator#verifyKey" } do |form| %>
7
+
8
+ <div class="mb-3">
9
+ <%= form.submit "Use Security Key", class: "btn btn-primary" %>
10
+ </div>
11
+
12
+ <div class="mb-3">
13
+ If it's an USB key be sure to insert it and, if necessary, tap it.
14
+ </div>
15
+ <% end %>
16
+
@@ -0,0 +1,21 @@
1
+ <h2 class="">Add a security key:</h2>
2
+ <%= form_with scope: :webauthn_credential,
3
+ url: options_for_webauthn_credentials_path,
4
+ id: "webauthn_credential_form",
5
+ data: {
6
+ controller: "add-credential",
7
+ action: "ajax:success->add-credential#create",
8
+ } do |form| %>
9
+
10
+ <div class="mb-3">
11
+ <%= form.text_field :nickname, autofocus: true, placeholder: "New Security Key nickname", required: true %>
12
+ </div>
13
+
14
+ <div class="mb-3">
15
+ <%= form.submit "Add Security Key", class: "btn btn-primary" %>
16
+ </div>
17
+
18
+ <div class="">
19
+ <span class="">If it's an USB key be sure to insert it and, if necessary, tap it.</span>
20
+ </div>
21
+ <% end %>
@@ -5,6 +5,11 @@
5
5
  <%= csrf_meta_tags %>
6
6
  <%= csp_meta_tag %>
7
7
  <%= stylesheet_link_tag "action_auth/application", media: "all" %>
8
+ <%= javascript_include_tag "action_auth/application", "data-turbo-track": "reload", type: "module" %>
9
+ <% if ActionAuth.configuration.webauthn_enabled? %>
10
+ <%= tag :meta, name: :webauthn_auth_url, content: action_auth.webauthn_credential_authentications_url %>
11
+ <%= tag :meta, name: :webauthn_cred_url, content: action_auth.webauthn_credentials_url %>
12
+ <% end %>
8
13
  </head>
9
14
  <body class="bg-light">
10
15
  <div class="container-fluid bg-white border pb-3">
@@ -5,6 +5,7 @@
5
5
  <%= csrf_meta_tags %>
6
6
  <%= csp_meta_tag %>
7
7
  <%= stylesheet_link_tag "action_auth/application", media: "all" %>
8
+ <%= javascript_include_tag "action_auth/application", "data-turbo-track": "reload", type: "module" %>
8
9
  </head>
9
10
  <body class="bg-light">
10
11
  <div class="container bg-white border pb-3">
data/config/routes.rb CHANGED
@@ -10,4 +10,14 @@ ActionAuth::Engine.routes.draw do
10
10
  resource :email_verification, only: [:show, :create]
11
11
  resource :password_reset, only: [:new, :edit, :create, :update]
12
12
  end
13
+
14
+ if ActionAuth.configuration.webauthn_enabled?
15
+ resources :webauthn_credentials, only: [:new, :create, :destroy] do
16
+ post :options, on: :collection, as: 'options_for'
17
+ end
18
+
19
+ resource :webauthn_credential_authentications, only: [:new, :create] do
20
+ post :options, on: :collection, as: 'options_for'
21
+ end
22
+ end
13
23
  end
@@ -0,0 +1,16 @@
1
+ class AddWebauthnCredentials < ActiveRecord::Migration[7.1]
2
+ def change
3
+ create_table :action_auth_webauthn_credentials do |t|
4
+ t.string :external_id, null: false
5
+ t.string :public_key, null: false
6
+ t.string :nickname, null: false
7
+ t.bigint :sign_count, null: false, default: 0
8
+
9
+ t.index :external_id, unique: true
10
+
11
+ t.references :action_auth_user, foreign_key: true
12
+
13
+ t.timestamps
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,5 @@
1
+ class AddWebauthnIdToUsers < ActiveRecord::Migration[7.1]
2
+ def change
3
+ add_column :action_auth_users, :webauthn_id, :string
4
+ end
5
+ end
@@ -0,0 +1,19 @@
1
+ module ActionAuth
2
+ class Configuration
3
+
4
+ attr_accessor :webauthn_enabled
5
+ attr_accessor :webauthn_origin
6
+ attr_accessor :webauthn_rp_name
7
+
8
+ def initialize
9
+ @webauthn_enabled = defined?(WebAuthn)
10
+ @webauthn_origin = "http://localhost:3000"
11
+ @webauthn_rp_name = Rails.application.class.to_s.deconstantize
12
+ end
13
+
14
+ def webauthn_enabled?
15
+ @webauthn_enabled.respond_to?(:call) ? @webauthn_enabled.call : @webauthn_enabled
16
+ end
17
+
18
+ end
19
+ end
@@ -14,6 +14,12 @@ module ActionAuth
14
14
 
15
15
  def user_signed_in?; Current.user.present?; end
16
16
  helper_method :user_signed_in?
17
+
18
+ def authenticate_user!
19
+ return if user_signed_in?
20
+ redirect_to new_user_session_path
21
+ end
22
+ helper_method :authenticate_user!
17
23
  end
18
24
 
19
25
  private
@@ -1,3 +1,3 @@
1
1
  module ActionAuth
2
- VERSION = "0.1.10"
2
+ VERSION = "0.2.0"
3
3
  end
data/lib/action_auth.rb CHANGED
@@ -1,7 +1,23 @@
1
1
  require "action_auth/version"
2
2
  require "action_auth/engine"
3
+ require "action_auth/configuration"
3
4
 
4
5
  module ActionAuth
5
- module Controllers
6
+ class << self
7
+ attr_accessor :configuration
8
+
9
+ def configure
10
+ self.configuration ||= Configuration.new
11
+ yield(configuration)
12
+ configure_webauthn
13
+ end
14
+
15
+ def configure_webauthn
16
+ return unless configuration.webauthn_enabled?
17
+ WebAuthn.configure do |config|
18
+ config.origin = configuration.webauthn_origin
19
+ config.rp_name = configuration.webauthn_rp_name
20
+ end
21
+ end
6
22
  end
7
23
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: action_auth
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.10
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dave Kimura
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-01-11 00:00:00.000000000 Z
11
+ date: 2024-01-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -50,6 +50,7 @@ files:
50
50
  - README.md
51
51
  - Rakefile
52
52
  - app/assets/config/action_auth_manifest.js
53
+ - app/assets/javascripts/action_auth/application.js
53
54
  - app/assets/stylesheets/action_auth/application.css
54
55
  - app/controllers/action_auth/application_controller.rb
55
56
  - app/controllers/action_auth/identity/email_verifications_controller.rb
@@ -58,6 +59,8 @@ files:
58
59
  - app/controllers/action_auth/passwords_controller.rb
59
60
  - app/controllers/action_auth/registrations_controller.rb
60
61
  - app/controllers/action_auth/sessions_controller.rb
62
+ - app/controllers/action_auth/webauthn_credential_authentications_controller.rb
63
+ - app/controllers/action_auth/webauthn_credentials_controller.rb
61
64
  - app/helpers/action_auth/application_helper.rb
62
65
  - app/jobs/action_auth/application_job.rb
63
66
  - app/mailers/action_auth/application_mailer.rb
@@ -66,6 +69,7 @@ files:
66
69
  - app/models/action_auth/current.rb
67
70
  - app/models/action_auth/session.rb
68
71
  - app/models/action_auth/user.rb
72
+ - app/models/action_auth/webauthn_credential.rb
69
73
  - app/views/action_auth/identity/emails/edit.html.erb
70
74
  - app/views/action_auth/identity/password_resets/edit.html.erb
71
75
  - app/views/action_auth/identity/password_resets/new.html.erb
@@ -77,6 +81,8 @@ files:
77
81
  - app/views/action_auth/user_mailer/email_verification.text.erb
78
82
  - app/views/action_auth/user_mailer/password_reset.html.erb
79
83
  - app/views/action_auth/user_mailer/password_reset.text.erb
84
+ - app/views/action_auth/webauthn_credential_authentications/new.html.erb
85
+ - app/views/action_auth/webauthn_credentials/new.html.erb
80
86
  - app/views/layouts/action_auth/application-full-width.html.erb
81
87
  - app/views/layouts/action_auth/application.html.erb
82
88
  - app/views/layouts/action_auth/mailer.html.erb
@@ -84,7 +90,10 @@ files:
84
90
  - config/routes.rb
85
91
  - db/migrate/20231107165548_create_action_auth_users.rb
86
92
  - db/migrate/20231107170349_create_action_auth_sessions.rb
93
+ - db/migrate/20240111125859_add_webauthn_credentials.rb
94
+ - db/migrate/20240111142545_add_webauthn_id_to_users.rb
87
95
  - lib/action_auth.rb
96
+ - lib/action_auth/configuration.rb
88
97
  - lib/action_auth/controllers/helpers.rb
89
98
  - lib/action_auth/engine.rb
90
99
  - lib/action_auth/routing/helpers.rb