diff --git a/README.md b/README.md index 43ca9bf..dec003f 100755 --- a/README.md +++ b/README.md @@ -150,7 +150,7 @@ they can add a Passkey to their account. The Passkey could be an iCloud Keychain key like a Yubikey, or a mobile device. If enabled and configured, the user will be prompted to use their Passkey after they log in. -### Configuration +#### Configuration The migrations are already copied over to your application when you run `bin/rails action_auth:install:migrations`. There are only two steps that you have to take to enable @@ -160,13 +160,13 @@ The reason why you need to add the gem is because it's not added to the gemspec intentional as not all users will want to add this functionality. This will help minimize the number of gems that your application relies on unless if they are features that you want to use. -#### Add the gem +##### Add the gem ``` bundle add webauthn ``` -### Configure the WebAuthn settings +#### Configure the WebAuthn settings **Note:** that the origin name does not have a trailing / or a port number. @@ -179,12 +179,67 @@ ActionAuth.configure do |config| end ``` -### Demo +#### Demo Here's a view of the experience with WebAuthn ![action_auth](https://github.com/kobaltz/action_auth/assets/635114/fa88d83c-5af5-471b-a094-ec9785ea2f87) +### Within Your Application + +It can be cumbersome to have to reference ActionAuth::User within the application as well as in the +relationships between models. Luckily, we can use ActiveSupport::CurrentAttributes to make this +process easier as well as inheritance of our models. + +#### Setting up the User model + +```ruby +# app/models/user.rb +class User < ActionAuth::User + has_many :posts, dependent: :destroy +end +``` + +#### Setting up the Current model + +```ruby +# app/models/current.rb +class Current < ActiveSupport::CurrentAttributes + def user + User.find_by(id: ActionAuth::Current.user&.id) + end +end +``` + +#### Generating an association + +There's one little gotcha when generating the associations. We are using `user:belongs_to` instead of +`action_auth_user:belongs_to`. However, when the foreign key is generated, it will look for the users table +instead of the action_auth_users table. To get around this, we'll need to modify the migration. + +```bash +bin/rails g scaffold posts user:belongs_to title +``` + +We can update the `foreign_key` from `true` to `{ to_table: :action_auth_users }` to get around this. + +```ruby +# db/migrate/XXXXXXXXXXX_create_posts.rb +class CreatePosts < ActiveRecord::Migration[7.1] + def change + create_table :posts do |t| + t.belongs_to :user, null: false, foreign_key: { to_table: :action_auth_users } + t.string :title + + t.timestamps + end + end +end +``` + +#### Using the Current model + +Now, you'll be able to do things like `Current.user` and `Current.user.posts` within your application. ## License The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). diff --git a/test/dummy/app/controllers/posts_controller.rb b/test/dummy/app/controllers/posts_controller.rb new file mode 100644 index 0000000..3d3b2f8 --- /dev/null +++ b/test/dummy/app/controllers/posts_controller.rb @@ -0,0 +1,58 @@ +class PostsController < ApplicationController + before_action :set_post, only: %i[ show edit update destroy ] + + # GET /posts + def index + @posts = Post.all + end + + # GET /posts/1 + def show + end + + # GET /posts/new + def new + @post = Post.new + end + + # GET /posts/1/edit + def edit + end + + # POST /posts + def create + @post = Post.new(post_params) + + if @post.save + redirect_to @post, notice: "Post was successfully created." + else + render :new, status: :unprocessable_entity + end + end + + # PATCH/PUT /posts/1 + def update + if @post.update(post_params) + redirect_to @post, notice: "Post was successfully updated.", status: :see_other + else + render :edit, status: :unprocessable_entity + end + end + + # DELETE /posts/1 + def destroy + @post.destroy! + redirect_to posts_url, notice: "Post was successfully destroyed.", status: :see_other + end + + private + # Use callbacks to share common setup or constraints between actions. + def set_post + @post = Post.find(params[:id]) + end + + # Only allow a list of trusted parameters through. + def post_params + params.require(:post).permit(:user_id, :title) + end +end diff --git a/test/dummy/app/helpers/posts_helper.rb b/test/dummy/app/helpers/posts_helper.rb new file mode 100644 index 0000000..a7b8cec --- /dev/null +++ b/test/dummy/app/helpers/posts_helper.rb @@ -0,0 +1,2 @@ +module PostsHelper +end diff --git a/test/dummy/app/models/current.rb b/test/dummy/app/models/current.rb new file mode 100644 index 0000000..3b716c1 --- /dev/null +++ b/test/dummy/app/models/current.rb @@ -0,0 +1,5 @@ +class Current < ActiveSupport::CurrentAttributes + def user + User.find_by(id: ActionAuth::Current.user&.id) + end +end diff --git a/test/dummy/app/models/post.rb b/test/dummy/app/models/post.rb new file mode 100644 index 0000000..95d45da --- /dev/null +++ b/test/dummy/app/models/post.rb @@ -0,0 +1,3 @@ +class Post < ApplicationRecord + belongs_to :user +end diff --git a/test/dummy/app/models/user.rb b/test/dummy/app/models/user.rb new file mode 100644 index 0000000..7ae1cda --- /dev/null +++ b/test/dummy/app/models/user.rb @@ -0,0 +1,3 @@ +class User < ActionAuth::User + has_many :posts, dependent: :destroy +end diff --git a/test/dummy/app/views/posts/_form.html.erb b/test/dummy/app/views/posts/_form.html.erb new file mode 100644 index 0000000..4691a19 --- /dev/null +++ b/test/dummy/app/views/posts/_form.html.erb @@ -0,0 +1,27 @@ +<%= form_with(model: post) do |form| %> + <% if post.errors.any? %> +
+ User: + <%= post.user_id %> +
+ ++ Title: + <%= post.title %> +
+ +<%= notice %>
+ ++ <%= link_to "Show this post", post %> +
+ <% end %> +<%= notice %>
+ +<%= render @post %> + +<%= File.read(Rails.root.join('app', 'models', 'current.rb')) %>+Current.user: <%= Current.user.inspect %> + +
<%= File.read(Rails.root.join('app', 'models', 'user.rb')) %>+ +
bin/rails g scaffold post user:belongs_to title+
<%= File.read(Rails.root.join('db', 'migrate', '20240114051355_create_posts.rb')) %>diff --git a/test/dummy/config/routes.rb b/test/dummy/config/routes.rb index b7b5a2b..c64d237 100755 --- a/test/dummy/config/routes.rb +++ b/test/dummy/config/routes.rb @@ -1,4 +1,5 @@ Rails.application.routes.draw do + resources :posts mount ActionAuth::Engine => 'action_auth' root 'welcome#index' end diff --git a/test/dummy/db/migrate/20240114051355_create_posts.rb b/test/dummy/db/migrate/20240114051355_create_posts.rb new file mode 100644 index 0000000..ecb723b --- /dev/null +++ b/test/dummy/db/migrate/20240114051355_create_posts.rb @@ -0,0 +1,10 @@ +class CreatePosts < ActiveRecord::Migration[7.1] + def change + create_table :posts do |t| + t.belongs_to :user, null: false, foreign_key: { to_table: :action_auth_users } + t.string :title + + t.timestamps + end + end +end diff --git a/test/dummy/db/schema.rb b/test/dummy/db/schema.rb index c062763..a38bd37 100755 --- a/test/dummy/db/schema.rb +++ b/test/dummy/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.1].define(version: 2024_01_11_142545) do +ActiveRecord::Schema[7.1].define(version: 2024_01_14_051355) do create_table "action_auth_sessions", force: :cascade do |t| t.integer "action_auth_user_id", null: false t.string "user_agent" @@ -42,6 +42,15 @@ t.index ["external_id"], name: "index_action_auth_webauthn_credentials_on_external_id", unique: true end + create_table "posts", force: :cascade do |t| + t.integer "user_id", null: false + t.string "title" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["user_id"], name: "index_posts_on_user_id" + end + add_foreign_key "action_auth_sessions", "action_auth_users" add_foreign_key "action_auth_webauthn_credentials", "action_auth_users" + add_foreign_key "posts", "action_auth_users", column: "user_id" end diff --git a/test/dummy/test/controllers/posts_controller_test.rb b/test/dummy/test/controllers/posts_controller_test.rb new file mode 100644 index 0000000..736964a --- /dev/null +++ b/test/dummy/test/controllers/posts_controller_test.rb @@ -0,0 +1,48 @@ +require "test_helper" + +class PostsControllerTest < ActionDispatch::IntegrationTest + setup do + @post = posts(:one) + end + + test "should get index" do + get posts_url + assert_response :success + end + + test "should get new" do + get new_post_url + assert_response :success + end + + test "should create post" do + assert_difference("Post.count") do + post posts_url, params: { post: { title: @post.title, user_id: @post.user_id } } + end + + assert_redirected_to post_url(Post.last) + end + + test "should show post" do + get post_url(@post) + assert_response :success + end + + test "should get edit" do + get edit_post_url(@post) + assert_response :success + end + + test "should update post" do + patch post_url(@post), params: { post: { title: @post.title, user_id: @post.user_id } } + assert_redirected_to post_url(@post) + end + + test "should destroy post" do + assert_difference("Post.count", -1) do + delete post_url(@post) + end + + assert_redirected_to posts_url + end +end diff --git a/test/dummy/test/fixtures/posts.yml b/test/dummy/test/fixtures/posts.yml new file mode 100644 index 0000000..7c8fd40 --- /dev/null +++ b/test/dummy/test/fixtures/posts.yml @@ -0,0 +1,9 @@ +# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +one: + user: one + title: MyString + +two: + user: two + title: MyString diff --git a/test/dummy/test/models/post_test.rb b/test/dummy/test/models/post_test.rb new file mode 100644 index 0000000..ff155c4 --- /dev/null +++ b/test/dummy/test/models/post_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +class PostTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end diff --git a/test/dummy/test/system/posts_test.rb b/test/dummy/test/system/posts_test.rb new file mode 100644 index 0000000..6603c19 --- /dev/null +++ b/test/dummy/test/system/posts_test.rb @@ -0,0 +1,43 @@ +require "application_system_test_case" + +class PostsTest < ApplicationSystemTestCase + setup do + @post = posts(:one) + end + + test "visiting the index" do + visit posts_url + assert_selector "h1", text: "Posts" + end + + test "should create post" do + visit posts_url + click_on "New post" + + fill_in "Title", with: @post.title + fill_in "User", with: @post.user_id + click_on "Create Post" + + assert_text "Post was successfully created" + click_on "Back" + end + + test "should update Post" do + visit post_url(@post) + click_on "Edit this post", match: :first + + fill_in "Title", with: @post.title + fill_in "User", with: @post.user_id + click_on "Update Post" + + assert_text "Post was successfully updated" + click_on "Back" + end + + test "should destroy Post" do + visit post_url(@post) + click_on "Destroy this post", match: :first + + assert_text "Post was successfully destroyed" + end +end