-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Generate invitations to enrol new admin users
* Adds new endpoint to handle invitations * Enables generating invitation link to share from admin user show page * Adds a signup page using token details * updates validations on password, so that password is optional
- Loading branch information
1 parent
2f05ae1
commit fc2f263
Showing
15 changed files
with
319 additions
and
8 deletions.
There are no files selected for viewing
15 changes: 15 additions & 0 deletions
15
app/assets/javascripts/koi/controllers/clipboard_controller.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import { Controller } from "@hotwired/stimulus"; | ||
|
||
export default class ClipboardController extends Controller { | ||
static targets = [ "source" ] | ||
|
||
copy(event) { | ||
event.preventDefault() | ||
navigator.clipboard.writeText(this.sourceTarget.value); | ||
|
||
this.element.classList.add("copied"); | ||
setTimeout(() => { | ||
this.element.classList.remove("copied"); | ||
}, 2000) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
# frozen_string_literal: true | ||
|
||
module Admin | ||
class TokensController < ApplicationController | ||
include Koi::Controller::JsonWebToken | ||
|
||
skip_before_action :authenticate_admin, only: %i[show update] | ||
before_action :set_token, only: %i[show update] | ||
|
||
def create | ||
admin = Admin::User.find(params[:id]) | ||
token = encode_token(admin_id: admin.id, exp: 24.hours.from_now.to_i, iat: Time.now.to_i) | ||
|
||
render locals: { token: } | ||
end | ||
|
||
def update | ||
return redirect_to admin_dashboard_path, status: :see_other if admin_signed_in? | ||
|
||
return redirect_to new_admin_session_path, status: :see_other, notice: "invalid token" if @token.blank? | ||
|
||
admin = Admin::User.find(@token[:admin_id]) | ||
sign_in_admin(admin) | ||
|
||
redirect_to admin_admin_user_path(admin) | ||
end | ||
|
||
def show | ||
return redirect_to new_admin_session_path, notice: "invalid token" if @token.blank? | ||
|
||
admin = Admin::User.find(@token[:admin_id]) | ||
|
||
return redirect_to new_admin_session_path, notice: "token already used" if token_utilised?(admin, @token) | ||
|
||
render locals: { admin:, token: params[:token] }, layout: "koi/login" | ||
end | ||
|
||
private | ||
|
||
def set_token | ||
@token = decode_token(params[:token]) | ||
end | ||
|
||
def token_utilised?(admin, token) | ||
admin.current_sign_in_at.present? || (admin.last_sign_in_at.present? && admin.last_sign_in_at.to_i > token[:iat]) | ||
end | ||
|
||
def sign_in_admin(admin) | ||
admin.current_sign_in_at = Time.current | ||
admin.current_sign_in_ip = request.remote_ip | ||
admin.sign_in_count = 1 | ||
|
||
# disable validations to allow saving without password or passkey credentials | ||
admin.save!(validate: false) | ||
session[:admin_user_id] = admin.id | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
# frozen_string_literal: true | ||
|
||
module Koi | ||
module Controller | ||
module JsonWebToken | ||
extend ActiveSupport::Concern | ||
|
||
SECRET_KEY = Rails.application.secret_key_base | ||
|
||
def encode_token(**payload) | ||
JWT.encode(payload, SECRET_KEY) | ||
end | ||
|
||
def decode_token(token) | ||
payload = JWT.decode(token, SECRET_KEY)[0] | ||
HashWithIndifferentAccess.new(payload) | ||
rescue JWT::DecodeError | ||
nil | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
<%= turbo_stream.replace "invite" do %> | ||
<div class="copy-to-clipboard-wrapper govuk-input__wrapper" data-controller="clipboard"> | ||
<%= text_field_tag :invite_link, new_admin_session_url(token: url_encode(token)), readonly: true, data: { clipboard_target: "source" } %> | ||
<button class="govuk-input__suffix" aria-hidden="true" data-action="clipboard#copy"> | ||
Copy link | ||
</button> | ||
<div class="copy-to-clipboard--feedback" role="alert" data-clipboard-target="feedback"></div> | ||
</div> | ||
<% end %> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
<%= render "layouts/koi/navigation_header" %> | ||
|
||
<%= form_with(model: admin, url: accept_admin_admin_user_path(admin), method: :post) do |form| %> | ||
<%= tag.input name: :token, type: :hidden, value: token %> | ||
<p>Welcome to Admin Terminal</p> | ||
<%= render Koi::SummaryListComponent.new(model: admin, class: "item-table") do |builder| %> | ||
<%= builder.text :name %> | ||
<%= builder.text :email %> | ||
<% end %> | ||
<div class="actions-group"> | ||
<%= form.admin_save "Sign up" %> | ||
</div> | ||
<% end %> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
# frozen_string_literal: true | ||
|
||
require "rails_helper" | ||
|
||
RSpec.describe Admin::TokensController do | ||
subject { action && response } | ||
|
||
def jwt_token(**payload) | ||
JWT.encode(payload, Rails.application.secret_key_base) | ||
end | ||
|
||
describe "GET /admin/token/:token" do | ||
let(:action) { get admin_token_path(token) } | ||
let(:admin) { create(:admin, password: "") } | ||
let(:token) { jwt_token(admin_id: admin.id, exp: 5.seconds.from_now.to_i, iat: Time.now.to_i) } | ||
|
||
it { is_expected.to be_successful } | ||
|
||
context "with used token" do | ||
let(:admin) { create(:admin, last_sign_in_at: Time.now) } | ||
let(:token) { jwt_token(admin_id: admin.id, exp: 5.seconds.from_now.to_i, iat: 1.hour.ago.to_i) } | ||
|
||
it { is_expected.to redirect_to(new_admin_session_path) } | ||
|
||
it "shows a flash message" do | ||
action | ||
expect(flash[:notice]).to match(/token already used/) | ||
end | ||
end | ||
|
||
context "with invalid token" do | ||
let(:token) { "token" } | ||
|
||
it { is_expected.to redirect_to(new_admin_session_path) } | ||
|
||
it "shows a flash message" do | ||
action | ||
expect(flash[:notice]).to match(/invalid token/) | ||
end | ||
end | ||
end | ||
|
||
describe "POST /admin/admin_users/:id/invite" do | ||
let(:action) { post invite_admin_admin_user_path(admin), as: :turbo_stream } | ||
let(:admin) { create(:admin) } | ||
|
||
include_context "with admin session" | ||
|
||
it_behaves_like "requires admin" | ||
|
||
it { is_expected.to be_successful } | ||
|
||
it "renders the token" do | ||
action | ||
expect(response.body).to have_css("turbo-stream[action=replace][target='invite']") | ||
end | ||
end | ||
|
||
describe "POST /admin/admin_users/:id/accept" do | ||
let(:action) { post accept_admin_admin_user_path(admin), params: { token: } } | ||
let(:admin) { create(:admin, password: "") } | ||
let(:token) { jwt_token(admin_id: admin.id, exp: 5.seconds.from_now.to_i, iat: Time.now.to_i) } | ||
|
||
it { is_expected.to redirect_to(admin_admin_user_path(admin)) } | ||
|
||
it "updates the admin login details" do | ||
expect { action }.to change { admin.reload.current_sign_in_at }.from(nil).to be_present | ||
end | ||
|
||
context "when admin is signed in" do | ||
let(:admin) { create(:admin) } | ||
|
||
include_context "with admin session" | ||
|
||
it { is_expected.to redirect_to(admin_dashboard_path) } | ||
end | ||
end | ||
end |
Oops, something went wrong.