mirror of
https://github.com/System-End/identity-vault.git
synced 2026-04-19 18:35:13 +00:00
less scary consent screen for HQ stuff! (#208)
This commit is contained in:
parent
e007096005
commit
aba5b912e3
11 changed files with 193 additions and 22 deletions
|
|
@ -153,6 +153,10 @@ class DeveloperAppsController < ApplicationController
|
|||
permitted << :trust_level
|
||||
end
|
||||
|
||||
if policy(@app || Program.new).update_byline?
|
||||
permitted << :byline
|
||||
end
|
||||
|
||||
if policy(@app || Program.new).update_onboarding_scenario?
|
||||
permitted << :onboarding_scenario
|
||||
end
|
||||
|
|
|
|||
|
|
@ -3,12 +3,18 @@
|
|||
.oauth-consent {
|
||||
max-width: 580px;
|
||||
|
||||
.oauth-official-byline {
|
||||
font-size: 0.875rem;
|
||||
color: var(--text-muted-strong);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.oauth-permissions {
|
||||
margin: $space-6 0;
|
||||
|
||||
|
||||
fieldset {
|
||||
margin: 0;
|
||||
|
||||
|
||||
legend {
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
|
|
@ -18,6 +24,43 @@
|
|||
margin-bottom: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
// collapsed variant for hq official apps
|
||||
&[open] summary {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
> summary {
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 500;
|
||||
color: var(--text-muted-strong);
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.375rem;
|
||||
list-style-type: none;
|
||||
|
||||
&::-webkit-details-marker,
|
||||
&::marker {
|
||||
display: none;
|
||||
content: none;
|
||||
}
|
||||
|
||||
&::after {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
&::before {
|
||||
content: "›";
|
||||
font-size: 1rem;
|
||||
line-height: 1;
|
||||
transition: transform 0.15s ease;
|
||||
}
|
||||
}
|
||||
|
||||
&[open] summary::before {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
}
|
||||
|
||||
.permissions-list {
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ class Program < ApplicationRecord
|
|||
audit_field :scopes_array, type: :array, label: "scopes"
|
||||
audit_field :redirect_uris, type: :array, label: "redirect URIs"
|
||||
audit_field :active, type: :boolean
|
||||
audit_field :byline
|
||||
audit_field :onboarding_scenario, transform: ->(v) { v&.titleize }
|
||||
|
||||
COLLABORATOR_ACTIVITY_KEYS = %w[
|
||||
|
|
|
|||
|
|
@ -56,6 +56,10 @@ class ProgramPolicy < ApplicationPolicy
|
|||
end
|
||||
end
|
||||
|
||||
def update_byline?
|
||||
user.can_hq_officialize? || admin?
|
||||
end
|
||||
|
||||
def update_onboarding_scenario?
|
||||
super_admin?
|
||||
end
|
||||
|
|
|
|||
|
|
@ -114,9 +114,16 @@
|
|||
</div>
|
||||
|
||||
<%# === Card 4: Admin Settings (if applicable) === %>
|
||||
<% if policy(@app).update_onboarding_scenario? || policy(@app).update_active? %>
|
||||
<% if policy(@app).update_byline? || policy(@app).update_onboarding_scenario? || policy(@app).update_active? %>
|
||||
<section class="section-card">
|
||||
<h3><%= t(".admin_settings_heading", default: "Admin Settings") %></h3>
|
||||
<% if policy(@app).update_byline? %>
|
||||
<div class="field-group">
|
||||
<%= f.label :byline, t(".byline") %>
|
||||
<%= f.text_field :byline, placeholder: t(".byline_placeholder") %>
|
||||
<small class="usn"><%= t(".byline_hint") %></small>
|
||||
</div>
|
||||
<% end %>
|
||||
<% if policy(@app).update_onboarding_scenario? %>
|
||||
<div class="field-group">
|
||||
<%= f.label :onboarding_scenario, t(".onboarding_scenario") %>
|
||||
|
|
|
|||
|
|
@ -22,6 +22,12 @@
|
|||
</small>
|
||||
</header>
|
||||
|
||||
<% if application&.hq_official? %>
|
||||
<div class="oauth-official-byline">
|
||||
<%= t('.official_byline', byline: application.byline.presence || t('.official_byline_default')) %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<% if @pre_auth.scopes.count > 0 %>
|
||||
<%
|
||||
scopes = @pre_auth.scopes.map(&:to_s)
|
||||
|
|
@ -31,7 +37,7 @@
|
|||
%>
|
||||
|
||||
<% if scope_data.any? || unknown_scopes.any? %>
|
||||
<div class="oauth-permissions">
|
||||
<% permissions_content = capture do %>
|
||||
<fieldset>
|
||||
<legend><%= t('.able_to') %></legend>
|
||||
<ul class="permissions-list">
|
||||
|
|
@ -60,11 +66,22 @@
|
|||
<% end %>
|
||||
</ul>
|
||||
</fieldset>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<% if application&.hq_official? %>
|
||||
<details class="oauth-permissions">
|
||||
<summary><%= t('.permissions_details') %></summary>
|
||||
<%= permissions_content %>
|
||||
</details>
|
||||
<% else %>
|
||||
<div class="oauth-permissions">
|
||||
<%= permissions_content %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<% auth_label = t('.authorize') %>
|
||||
<% auth_label = application&.hq_official? ? t('.authorize_official') : t('.authorize') %>
|
||||
<div class="oauth-actions" x-data="{ countdown: <%= application&.hq_official? ? 0 : 3 %>, ready: <%= application&.hq_official? ? true : false %>, authLabel: '<%= auth_label.gsub("'", "\\'") %>' }" x-init="if (countdown > 0) { let interval = setInterval(() => { countdown--; if (countdown <= 0) { ready = true; clearInterval(interval); } }, 1000); }">
|
||||
<%= form_tag oauth_authorization_path, method: :delete do %>
|
||||
<%= hidden_field_tag :client_id, @pre_auth.client.uid, id: nil %>
|
||||
|
|
|
|||
|
|
@ -260,6 +260,9 @@ en:
|
|||
scopes_not_valid_for_trust_level: not valid for this trust level.
|
||||
scopes_locked_by_higher_permission: "Managed by a higher-permission user:"
|
||||
admin_settings_heading: Admin Settings
|
||||
byline: Consent Screen Byline
|
||||
byline_hint: Shown on the consent screen for HQ official apps. Leave blank to default to "Hack Club HQ".
|
||||
byline_placeholder: "@nora, @msw, @zrl"
|
||||
onboarding_scenario: Onboarding Scenario
|
||||
onboarding_default: "(default)"
|
||||
onboarding_scenario_hint: Users signing up through this OAuth app will use this onboarding flow
|
||||
|
|
@ -329,8 +332,12 @@ en:
|
|||
new:
|
||||
prompt: Authorize %{client_name} to use your account?
|
||||
able_to: "This application will be able to:"
|
||||
official_byline: "This is an official Hack Club program run by %{byline}."
|
||||
official_byline_default: Hack Club HQ
|
||||
permissions_details: What data will be shared?
|
||||
deny: Deny
|
||||
authorize: Authorize →
|
||||
authorize_official: Okay! →
|
||||
banners:
|
||||
community_untrusted:
|
||||
title: Community Application
|
||||
|
|
|
|||
52
db/analytics_schema.rb
Normal file
52
db/analytics_schema.rb
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
# This file is auto-generated from the current state of the database. Instead
|
||||
# of editing this file, please use the migrations feature of Active Record to
|
||||
# incrementally modify your database, and then regenerate this schema definition.
|
||||
#
|
||||
# This file is the source Rails uses to define your schema when running `bin/rails
|
||||
# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
|
||||
# be faster and is potentially less error prone than running all of your
|
||||
# migrations from scratch. Old migrations may fail to apply correctly if those
|
||||
# migrations use external dependencies or application code.
|
||||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema[8.0].define(version: 2026_01_12_000002) do
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "pg_catalog.plpgsql"
|
||||
|
||||
create_table "ahoy_events", force: :cascade do |t|
|
||||
t.bigint "visit_id"
|
||||
t.string "name"
|
||||
t.jsonb "properties"
|
||||
t.datetime "time"
|
||||
t.index ["name", "time"], name: "index_ahoy_events_on_name_and_time"
|
||||
t.index ["name"], name: "index_ahoy_events_on_name"
|
||||
t.index ["properties"], name: "index_ahoy_events_on_properties", using: :gin
|
||||
t.index ["time"], name: "index_ahoy_events_on_time"
|
||||
t.index ["visit_id"], name: "index_ahoy_events_on_visit_id"
|
||||
end
|
||||
|
||||
create_table "ahoy_visits", force: :cascade do |t|
|
||||
t.string "visit_token"
|
||||
t.string "visitor_token"
|
||||
t.string "ip"
|
||||
t.text "user_agent"
|
||||
t.text "referrer"
|
||||
t.string "referring_domain"
|
||||
t.text "landing_page"
|
||||
t.string "browser"
|
||||
t.string "os"
|
||||
t.string "device_type"
|
||||
t.string "utm_source"
|
||||
t.string "utm_medium"
|
||||
t.string "utm_campaign"
|
||||
t.string "utm_term"
|
||||
t.string "utm_content"
|
||||
t.datetime "started_at"
|
||||
t.index ["started_at"], name: "index_ahoy_visits_on_started_at"
|
||||
t.index ["visit_token"], name: "index_ahoy_visits_on_visit_token", unique: true
|
||||
t.index ["visitor_token"], name: "index_ahoy_visits_on_visitor_token"
|
||||
end
|
||||
|
||||
add_foreign_key "ahoy_events", "ahoy_visits", column: "visit_id"
|
||||
end
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
class AddBylineToOAuthApplications < ActiveRecord::Migration[8.0]
|
||||
def change
|
||||
add_column :oauth_applications, :byline, :string
|
||||
end
|
||||
end
|
||||
19
db/schema.rb
generated
19
db/schema.rb
generated
|
|
@ -10,9 +10,10 @@
|
|||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema[8.0].define(version: 2026_03_02_000002) do
|
||||
ActiveRecord::Schema[8.0].define(version: 2026_03_24_000001) do
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "pg_catalog.plpgsql"
|
||||
enable_extension "pg_trgm"
|
||||
enable_extension "pgcrypto"
|
||||
|
||||
create_table "active_storage_attachments", force: :cascade do |t|
|
||||
|
|
@ -301,7 +302,6 @@ ActiveRecord::Schema[8.0].define(version: 2026_03_02_000002) do
|
|||
t.boolean "saml_debug"
|
||||
t.boolean "is_in_workspace", default: false, null: false
|
||||
t.string "slack_dm_channel_id"
|
||||
t.string "webauthn_id"
|
||||
t.boolean "is_alum", default: false
|
||||
t.boolean "can_hq_officialize", default: false, null: false
|
||||
t.index "lower((primary_email)::text)", name: "idx_identities_unique_primary_email", unique: true, where: "(deleted_at IS NULL)"
|
||||
|
|
@ -447,6 +447,7 @@ ActiveRecord::Schema[8.0].define(version: 2026_03_02_000002) do
|
|||
t.integer "sign_count"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.datetime "compromised_at"
|
||||
t.index ["external_id"], name: "index_identity_webauthn_credentials_on_external_id", unique: true
|
||||
t.index ["identity_id"], name: "index_identity_webauthn_credentials_on_identity_id"
|
||||
end
|
||||
|
|
@ -516,6 +517,7 @@ ActiveRecord::Schema[8.0].define(version: 2026_03_02_000002) do
|
|||
t.integer "trust_level", default: 0, null: false
|
||||
t.bigint "owner_identity_id"
|
||||
t.string "onboarding_scenario"
|
||||
t.string "byline"
|
||||
t.index ["owner_identity_id"], name: "index_oauth_applications_on_owner_identity_id"
|
||||
t.index ["program_key_bidx"], name: "index_oauth_applications_on_program_key_bidx", unique: true
|
||||
t.index ["uid"], name: "index_oauth_applications_on_uid", unique: true
|
||||
|
|
@ -602,18 +604,6 @@ ActiveRecord::Schema[8.0].define(version: 2026_03_02_000002) do
|
|||
t.index ["item_type", "item_id"], name: "index_versions_on_item_type_and_item_id"
|
||||
end
|
||||
|
||||
create_table "webauthn_credentials", force: :cascade do |t|
|
||||
t.bigint "identity_id", null: false
|
||||
t.string "external_id", null: false
|
||||
t.string "public_key", null: false
|
||||
t.string "nickname", null: false
|
||||
t.integer "sign_count", default: 0, null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["external_id"], name: "index_webauthn_credentials_on_external_id", unique: true
|
||||
t.index ["identity_id"], name: "index_webauthn_credentials_on_identity_id"
|
||||
end
|
||||
|
||||
add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id"
|
||||
add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id"
|
||||
add_foreign_key "addresses", "identities"
|
||||
|
|
@ -648,5 +638,4 @@ ActiveRecord::Schema[8.0].define(version: 2026_03_02_000002) do
|
|||
add_foreign_key "verifications", "identities"
|
||||
add_foreign_key "verifications", "identity_aadhaar_records", column: "aadhaar_record_id"
|
||||
add_foreign_key "verifications", "identity_documents"
|
||||
add_foreign_key "webauthn_credentials", "identities"
|
||||
end
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ RSpec.describe "doorkeeper/authorizations/new", type: :view do
|
|||
it "shows verification status for verification_status scope" do
|
||||
allow(pre_auth).to receive(:scopes).and_return(Doorkeeper::OAuth::Scopes.from_string("verification_status"))
|
||||
render
|
||||
expect(rendered).to include(identity.verification_status)
|
||||
expect(rendered).to include(identity.verification_status.humanize)
|
||||
expect(rendered).to include("YSWS eligible")
|
||||
end
|
||||
|
||||
|
|
@ -86,6 +86,48 @@ RSpec.describe "doorkeeper/authorizations/new", type: :view do
|
|||
end
|
||||
end
|
||||
|
||||
describe "hq official byline" do
|
||||
it "shows default byline for hq_official apps without a custom byline" do
|
||||
allow(program).to receive(:hq_official?).and_return(true)
|
||||
allow(program).to receive(:byline).and_return(nil)
|
||||
render
|
||||
expect(rendered).to include("official Hack Club program")
|
||||
expect(rendered).to include("Hack Club HQ")
|
||||
end
|
||||
|
||||
it "shows custom byline when set" do
|
||||
allow(program).to receive(:hq_official?).and_return(true)
|
||||
allow(program).to receive(:byline).and_return("@nora, @msw, @zrl")
|
||||
render
|
||||
expect(rendered).to include("@nora, @msw, @zrl")
|
||||
end
|
||||
|
||||
it "does not show byline for community apps" do
|
||||
allow(program).to receive(:hq_official?).and_return(false)
|
||||
allow(program).to receive(:trust_level).and_return("community_untrusted")
|
||||
render
|
||||
expect(rendered).not_to include("official Hack Club program")
|
||||
end
|
||||
end
|
||||
|
||||
describe "permissions display" do
|
||||
it "collapses permissions behind details for hq_official apps" do
|
||||
allow(program).to receive(:hq_official?).and_return(true)
|
||||
allow(program).to receive(:byline).and_return(nil)
|
||||
render
|
||||
expect(rendered).to include("<details")
|
||||
expect(rendered).to include("What data will be shared?")
|
||||
end
|
||||
|
||||
it "shows permissions directly for non-official apps" do
|
||||
allow(program).to receive(:hq_official?).and_return(false)
|
||||
allow(program).to receive(:trust_level).and_return("community_untrusted")
|
||||
render
|
||||
expect(rendered).not_to include("<details")
|
||||
expect(rendered).to include("oauth-permissions")
|
||||
end
|
||||
end
|
||||
|
||||
describe "trust level banners" do
|
||||
it "shows warning banner for community_untrusted apps" do
|
||||
allow(program).to receive(:trust_level).and_return("community_untrusted")
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue