katalyst-koi 4.14.0 → 4.14.2
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 +4 -4
- data/app/assets/builds/koi/admin.css +1 -1
- data/app/assets/javascripts/koi/controllers/webauthn_registration_controller.js +15 -7
- data/app/assets/stylesheets/koi/base/_flow.scss +8 -0
- data/app/assets/stylesheets/koi/base/_index.scss +2 -0
- data/app/assets/stylesheets/koi/base/_repel.scss +23 -0
- data/app/controllers/admin/admin_users_controller.rb +2 -0
- data/app/controllers/admin/credentials_controller.rb +5 -1
- data/app/controllers/admin/sessions_controller.rb +1 -1
- data/app/controllers/concerns/koi/controller/has_webauthn.rb +4 -1
- data/app/views/admin/admin_users/_fields.html+self.erb +3 -0
- data/app/views/admin/admin_users/_fields.html.erb +0 -1
- data/app/views/admin/admin_users/index.html.erb +3 -0
- data/app/views/admin/admin_users/show.html+self.erb +19 -0
- data/app/views/admin/admin_users/show.html.erb +8 -12
- data/app/views/admin/credentials/_credentials.html+self.erb +10 -0
- data/app/views/admin/credentials/_credentials.html.erb +4 -5
- data/app/views/admin/credentials/new.html.erb +27 -4
- data/app/views/layouts/koi/_navigation.html.erb +9 -0
- data/lib/koi/menu.rb +0 -1
- metadata +7 -2
@@ -6,8 +6,11 @@ import {
|
|
6
6
|
} from "@github/webauthn-json/browser-ponyfill";
|
7
7
|
|
8
8
|
export default class WebauthnRegistrationController extends Controller {
|
9
|
-
static values = {
|
10
|
-
|
9
|
+
static values = {
|
10
|
+
options: Object,
|
11
|
+
response: String,
|
12
|
+
};
|
13
|
+
static targets = ["intro", "nickname", "response"];
|
11
14
|
|
12
15
|
submit(e) {
|
13
16
|
if (this.responseTarget.value === "") {
|
@@ -16,12 +19,17 @@ export default class WebauthnRegistrationController extends Controller {
|
|
16
19
|
}
|
17
20
|
}
|
18
21
|
|
19
|
-
createCredential() {
|
20
|
-
create(this.options)
|
21
|
-
this.responseTarget.value = JSON.stringify(response);
|
22
|
+
async createCredential() {
|
23
|
+
const response = await create(this.options);
|
22
24
|
|
23
|
-
|
24
|
-
|
25
|
+
this.responseValue = JSON.stringify(response);
|
26
|
+
this.responseTarget.value = JSON.stringify(response);
|
27
|
+
}
|
28
|
+
|
29
|
+
responseValueChanged(response) {
|
30
|
+
const responsePresent = response !== "";
|
31
|
+
this.introTarget.toggleAttribute("hidden", responsePresent);
|
32
|
+
this.nicknameTarget.toggleAttribute("hidden", !responsePresent);
|
25
33
|
}
|
26
34
|
|
27
35
|
get options() {
|
@@ -0,0 +1,23 @@
|
|
1
|
+
/*
|
2
|
+
REPEL
|
3
|
+
A little layout that pushes items away from each other where
|
4
|
+
there is space in the viewport and stacks on small viewports
|
5
|
+
|
6
|
+
CUSTOM PROPERTIES AND CONFIGURATION
|
7
|
+
--gutter (var(--space-s-m)): This defines the space
|
8
|
+
between each item.
|
9
|
+
|
10
|
+
--repel-vertical-alignment How items should align vertically.
|
11
|
+
Can be any acceptable flexbox alignment value.
|
12
|
+
*/
|
13
|
+
.repel {
|
14
|
+
display: flex;
|
15
|
+
flex-wrap: wrap;
|
16
|
+
justify-content: space-between;
|
17
|
+
align-items: var(--repel-vertical-alignment, center);
|
18
|
+
gap: var(--gutter, var(--space-s-m));
|
19
|
+
}
|
20
|
+
|
21
|
+
.repel[data-nowrap] {
|
22
|
+
flex-wrap: nowrap;
|
23
|
+
}
|
@@ -70,7 +70,11 @@ module Admin
|
|
70
70
|
def set_admin_user
|
71
71
|
@admin_user = Admin::User.find(params[:admin_user_id])
|
72
72
|
|
73
|
-
|
73
|
+
if current_admin == @admin_user
|
74
|
+
request.variant = :self
|
75
|
+
else
|
76
|
+
head(:forbidden)
|
77
|
+
end
|
74
78
|
end
|
75
79
|
end
|
76
80
|
end
|
@@ -19,7 +19,7 @@ module Admin
|
|
19
19
|
|
20
20
|
session[:admin_user_id] = admin_user.id
|
21
21
|
|
22
|
-
redirect_to(params[:redirect].presence || admin_dashboard_path, status: :see_other)
|
22
|
+
redirect_to(url_from(params[:redirect].presence) || admin_dashboard_path, status: :see_other)
|
23
23
|
else
|
24
24
|
admin_user = Admin::User.new(session_params.slice(:email, :password))
|
25
25
|
admin_user.errors.add(:email, "Invalid email or password")
|
@@ -36,7 +36,10 @@ module Koi
|
|
36
36
|
Admin::Credential.find_by!(external_id: credential.id)
|
37
37
|
end
|
38
38
|
|
39
|
-
stored_credential.update
|
39
|
+
stored_credential.update(
|
40
|
+
sign_count: webauthn_credential.sign_count,
|
41
|
+
updated_at: DateTime.current,
|
42
|
+
)
|
40
43
|
|
41
44
|
stored_credential.admin
|
42
45
|
end
|
@@ -15,6 +15,9 @@
|
|
15
15
|
<% row.select %>
|
16
16
|
<% row.link :name, url: :admin_admin_user_path %>
|
17
17
|
<% row.text :email %>
|
18
|
+
<% row.boolean :credentials, label: "Passkey" do |cell| %>
|
19
|
+
<%= cell.value.any? ? "Yes" : "No" %>
|
20
|
+
<% end %>
|
18
21
|
<% end %>
|
19
22
|
|
20
23
|
<%= table_pagination_with(collection:) %>
|
@@ -0,0 +1,19 @@
|
|
1
|
+
<%# locals: (admin:) %>
|
2
|
+
|
3
|
+
<% content_for :header do %>
|
4
|
+
<%= render Koi::Header::ShowComponent.new(resource: admin) %>
|
5
|
+
<% end %>
|
6
|
+
|
7
|
+
<%= render Koi::SummaryListComponent.new(model: admin, class: "item-table") do |builder| %>
|
8
|
+
<%= builder.text :name %>
|
9
|
+
<%= builder.text :email %>
|
10
|
+
<%= builder.date :created_at %>
|
11
|
+
<%= builder.date :last_sign_in_at, label: { text: "Last sign in" } %>
|
12
|
+
<% end %>
|
13
|
+
|
14
|
+
<div class="repel">
|
15
|
+
<h3>Passkeys</h3>
|
16
|
+
<%= kpop_link_to "New passkey", new_admin_admin_user_credential_path(admin), class: "button button--primary" %>
|
17
|
+
</div>
|
18
|
+
|
19
|
+
<%= render "admin/credentials/credentials", admin: %>
|
@@ -1,3 +1,5 @@
|
|
1
|
+
<%# locals: (admin:) %>
|
2
|
+
|
1
3
|
<% content_for :header do %>
|
2
4
|
<%= render Koi::Header::ShowComponent.new(resource: admin) %>
|
3
5
|
<% end %>
|
@@ -5,11 +7,15 @@
|
|
5
7
|
<%= render Koi::SummaryListComponent.new(model: admin, class: "item-table") do |builder| %>
|
6
8
|
<%= builder.text :name %>
|
7
9
|
<%= builder.text :email %>
|
8
|
-
<%= builder.
|
9
|
-
<%= builder.
|
10
|
+
<%= builder.date :created_at %>
|
11
|
+
<%= builder.date :last_sign_in_at, label: { text: "Last sign in" } %>
|
10
12
|
<%= builder.boolean :archived? %>
|
11
13
|
<% end %>
|
12
14
|
|
15
|
+
<h3>Passkeys</h3>
|
16
|
+
|
17
|
+
<%= render "admin/credentials/credentials", admin: %>
|
18
|
+
|
13
19
|
<div class="actions">
|
14
20
|
<% if admin.archived? %>
|
15
21
|
<%= button_to "Delete", admin_admin_user_path(admin),
|
@@ -19,13 +25,3 @@
|
|
19
25
|
<% end %>
|
20
26
|
<%= button_to "Generate login link", admin_admin_user_tokens_path(admin), class: "button button--primary", form: { id: "invite" } %>
|
21
27
|
</div>
|
22
|
-
|
23
|
-
<h2>Authentication</h2>
|
24
|
-
|
25
|
-
<%= render "admin/credentials/credentials", admin: %>
|
26
|
-
|
27
|
-
<% if admin == current_admin %>
|
28
|
-
<div class="actions-group">
|
29
|
-
<%= kpop_link_to "Add this device", new_admin_admin_user_credential_path(admin), class: "button button--primary" %>
|
30
|
-
</div>
|
31
|
-
<% end %>
|
@@ -0,0 +1,10 @@
|
|
1
|
+
<%= table_with(id: dom_id(admin, :credentials), collection: admin.credentials) do |t, c| %>
|
2
|
+
<% t.text :nickname, label: "Name" %>
|
3
|
+
<% t.date :updated_at, label: "Last use" do |date| %>
|
4
|
+
<%= date unless c.created_at == c.updated_at %>
|
5
|
+
<% end %>
|
6
|
+
<% t.cell :actions, label: "" do %>
|
7
|
+
<%= link_to("Remove passkey", admin_admin_user_credential_path(admin, c),
|
8
|
+
data: { turbo_method: :delete }) %>
|
9
|
+
<% end %>
|
10
|
+
<% end %>
|
@@ -1,7 +1,6 @@
|
|
1
1
|
<%= table_with(id: dom_id(admin, :credentials), collection: admin.credentials) do |t, c| %>
|
2
|
-
<% t.text :nickname %>
|
3
|
-
<% t.
|
4
|
-
|
5
|
-
|
6
|
-
<% end if admin == current_admin %>
|
2
|
+
<% t.text :nickname, label: "Name" %>
|
3
|
+
<% t.date :updated_at, label: "Last use" do |date| %>
|
4
|
+
<%= date unless c.created_at == c.updated_at %>
|
5
|
+
<% end %>
|
7
6
|
<% end %>
|
@@ -1,14 +1,37 @@
|
|
1
|
-
<%= render Kpop::ModalComponent.new(title: "
|
1
|
+
<%= render Kpop::ModalComponent.new(title: "New passkey") do %>
|
2
2
|
<%= form_with model: admin.credentials.new,
|
3
3
|
url: admin_admin_user_credentials_path(admin),
|
4
|
+
class: "flow prose",
|
4
5
|
data: {
|
5
6
|
controller: "webauthn-registration",
|
6
7
|
action: "submit->webauthn-registration#submit",
|
7
8
|
webauthn_registration_options_value: { publicKey: options },
|
8
9
|
} do |form| %>
|
9
|
-
<%= form.govuk_text_field :nickname %>
|
10
10
|
<%= form.hidden_field :response, data: { webauthn_registration_target: "response" } %>
|
11
|
-
|
12
|
-
|
11
|
+
<section class="flow prose" data-webauthn-registration-target="intro">
|
12
|
+
<p>
|
13
|
+
Passkeys are secure secrets that are stored by your device.
|
14
|
+
You will need the device where your passkey is stored to log in.
|
15
|
+
</p>
|
16
|
+
<p>
|
17
|
+
Unlike a password, your password doesn't get sent to the server when you log
|
18
|
+
in and can't be stolen in a data breach. When you log in with a passkey,
|
19
|
+
your operating system will prompt you for permission to use the passkey
|
20
|
+
secret to authenticate the login attempt.
|
21
|
+
</p>
|
22
|
+
<p>
|
23
|
+
We recommend that you store your passkey on your phone or cloud account.
|
24
|
+
Depending on your browser, you may need to choose "more options" to see
|
25
|
+
a QR code that you can scan with your phone.
|
26
|
+
</p>
|
27
|
+
</section>
|
28
|
+
<section class="flow" data-webauthn-registration-target="nickname" hidden>
|
29
|
+
<%= form.govuk_text_field :nickname, label: { text: "Passkey name" } do %>
|
30
|
+
Enter a name for this passkey to help you distinguish it from other passkeys you may have for this site.
|
31
|
+
<br>
|
32
|
+
Example: My Phone, Chrome, iCloud, 1Password
|
33
|
+
<% end %>
|
34
|
+
</section>
|
35
|
+
<%= form.admin_save("Next") %>
|
13
36
|
<% end %>
|
14
37
|
<% end %>
|
@@ -9,6 +9,15 @@
|
|
9
9
|
data-action="input->navigation#filter change->navigation#filter
|
10
10
|
keydown.enter->navigation#go keydown.esc->navigation#clear">
|
11
11
|
</div>
|
12
|
+
<ul role="list">
|
13
|
+
<li>
|
14
|
+
<span><%= current_admin_user.name %></span>
|
15
|
+
<ul role="list">
|
16
|
+
<li><%= link_to("Profile", main_app.admin_admin_user_path(current_admin_user)) %></li>
|
17
|
+
<li><%= link_to("Log out", main_app.admin_session_path, data: { turbo_method: :delete }) %></li>
|
18
|
+
</ul>
|
19
|
+
</li>
|
20
|
+
</ul>
|
12
21
|
<%= navigation_menu_with(menu: Koi::Menu.admin_menu(self)) %>
|
13
22
|
</nav>
|
14
23
|
<%= render "layouts/koi/navigation_collapse" %>
|
data/lib/koi/menu.rb
CHANGED
@@ -17,7 +17,6 @@ module Koi
|
|
17
17
|
b.add_link(title: "View site", url: "/", target: :blank)
|
18
18
|
b.add_link(title: "Dashboard", url: context.main_app.admin_dashboard_path)
|
19
19
|
b.add_items(priority)
|
20
|
-
b.add_button(title: "Logout", url: context.main_app.admin_session_path, http_method: :delete)
|
21
20
|
end
|
22
21
|
builder.add_menu(title: "Modules") do |b|
|
23
22
|
b.add_items(modules)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: katalyst-koi
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 4.14.
|
4
|
+
version: 4.14.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Katalyst Interactive
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-11-
|
11
|
+
date: 2024-11-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -269,11 +269,13 @@ files:
|
|
269
269
|
- app/assets/javascripts/koi/utils/transition.js
|
270
270
|
- app/assets/stylesheets/koi/admin.scss
|
271
271
|
- app/assets/stylesheets/koi/base/_button.scss
|
272
|
+
- app/assets/stylesheets/koi/base/_flow.scss
|
272
273
|
- app/assets/stylesheets/koi/base/_icon.scss
|
273
274
|
- app/assets/stylesheets/koi/base/_index.scss
|
274
275
|
- app/assets/stylesheets/koi/base/_input.scss
|
275
276
|
- app/assets/stylesheets/koi/base/_link.scss
|
276
277
|
- app/assets/stylesheets/koi/base/_list.scss
|
278
|
+
- app/assets/stylesheets/koi/base/_repel.scss
|
277
279
|
- app/assets/stylesheets/koi/base/_tables.scss
|
278
280
|
- app/assets/stylesheets/koi/base/_typography.scss
|
279
281
|
- app/assets/stylesheets/koi/components/_actions-group.scss
|
@@ -362,12 +364,15 @@ files:
|
|
362
364
|
- app/models/application_record.rb
|
363
365
|
- app/models/concerns/koi/model/archivable.rb
|
364
366
|
- app/models/url_rewrite.rb
|
367
|
+
- app/views/admin/admin_users/_fields.html+self.erb
|
365
368
|
- app/views/admin/admin_users/_fields.html.erb
|
366
369
|
- app/views/admin/admin_users/archived.html.erb
|
367
370
|
- app/views/admin/admin_users/edit.html.erb
|
368
371
|
- app/views/admin/admin_users/index.html.erb
|
369
372
|
- app/views/admin/admin_users/new.html.erb
|
373
|
+
- app/views/admin/admin_users/show.html+self.erb
|
370
374
|
- app/views/admin/admin_users/show.html.erb
|
375
|
+
- app/views/admin/credentials/_credentials.html+self.erb
|
371
376
|
- app/views/admin/credentials/_credentials.html.erb
|
372
377
|
- app/views/admin/credentials/create.turbo_stream.erb
|
373
378
|
- app/views/admin/credentials/destroy.turbo_stream.erb
|