This commit is contained in:
github-actions-bot 2024-09-10 17:05:37 +00:00
parent 2dd751a8cb
commit 254f185153
53 changed files with 2082 additions and 1 deletions

View File

@ -0,0 +1,277 @@
html {
height: 100%;
}
body {
background-color: #F9F9F9;
margin: 0;
padding: 0;
height: 100%;
}
header .navbar {
margin-bottom: 0;
min-height: inherit;
}
.header .container {
position: relative;
}
.navbar-title {
background-image: url('../img/logo.png');
height: 25px;
background-repeat: no-repeat;
width: 123px;
margin: 3px 10px 5px;
text-indent: -99999px;
}
.navbar-pf .navbar-utility {
right: 20px;
top: -34px !important;
font-size: 12px;
}
.navbar-pf .navbar-utility > li > a {
color: #fff !important;
padding-bottom: 12px;
padding-top: 11px;
border-left: medium none;
}
.container {
height: 100%;
}
.content-area {
background-color: #fff;
border-color: #CECECE;
border-style: solid;
border-width: 0 1px;
height: 100%;
padding: 0 30px;
}
.margin-bottom {
margin-bottom: 10px;
}
/* Sidebar */
.bs-sidebar {
background-color: #f9f9f9;
padding-top: 44px;
padding-right: 0;
padding-left: 0;
z-index: 20;
}
.bs-sidebar ul {
list-style: none;
padding-left: 12px;
}
.bs-sidebar ul li {
margin-bottom: 0.5em;
margin-left: -1em;
}
.bs-sidebar ul li a {
font-size: 14px;
padding-left: 25px;
color: #4d5258;
line-height: 28px;
display: block;
border-width: 1px 0 1px 1px;
border-style: solid;
border-color: #f9f9f9;
}
.bs-sidebar ul li a:hover,
.bs-sidebar ul li a:focus {
text-decoration: none;
color: #777777;
border-right: 2px solid #aaa;
}
.bs-sidebar ul li.active a {
background-color: #c7e5f0;
border-color: #56bae0;
font-weight: bold;
background-image: url(../img/icon-sidebar-active.png);
background-repeat: no-repeat;
background-position: right center;
}
.bs-sidebar ul li.active a:hover {
border-right: none;
}
.content-area h2 {
font-family: "Open Sans", sans-serif;
font-weight: 100;
font-size: 24px;
margin-bottom: 25px;
margin-top: 25px;
}
.subtitle {
text-align: right;
margin-top: 30px;
color: #909090;
}
.required {
color: #CB2915;
}
.alert {
margin-top: 30px;
margin-bottom: 0;
}
.feedback-aligner .alert {
background-position: 1.27273em center;
background-repeat: no-repeat;
border-radius: 2px;
border-width: 1px;
color: #4D5258;
display: inline-block;
font-size: 1.1em;
line-height: 1.4em;
margin: 0;
padding: 0.909091em 3.63636em;
position: relative;
text-align: left;
}
.alert.alert-success {
background-color: #E4F1E1;
border-color: #4B9E39;
}
.alert.alert-error {
background-color: #F8E7E7;
border-color: #B91415;
}
.alert.alert-warning {
background-color: #FEF1E9;
border-color: #F17528;
}
.alert.alert-info {
background-color: #E4F3FA;
border-color: #5994B2;
}
.form-horizontal {
border-top: 1px solid #E9E8E8;
padding-top: 23px;
}
.form-horizontal .control-label {
color: #909090;
line-height: 1.4em;
padding-top: 5px;
position: relative;
text-align: right;
width: 100%;
}
.form-group {
position: relative;
}
.control-label + .required {
position: absolute;
right: -2px;
top: 0;
}
#kc-form-buttons {
text-align: right;
margin-top: 10px;
}
#kc-form-buttons .btn-primary {
float: right;
margin-left: 8px;
}
/* Authenticator page */
ol {
padding-left: 40px;
}
ol li {
font-size: 13px;
margin-bottom: 10px;
position: relative;
}
ol li img {
margin-top: 15px;
margin-bottom: 5px;
border: 1px solid #eee;
}
hr + .form-horizontal {
border: none;
padding-top: 0;
}
.kc-dropdown{
position: relative;
}
.kc-dropdown > a{
display:block;
padding: 11px 10px 12px;
line-height: 12px;
font-size: 12px;
color: #fff !important;
text-decoration: none;
}
.kc-dropdown > a::after{
content: "\2c5";
margin-left: 4px;
}
.kc-dropdown:hover > a{
background-color: rgba(0,0,0,0.2);
}
.kc-dropdown ul li a{
padding: 1px 11px;
font-size: 12px;
color: #000 !important;
border: 1px solid #fff;
text-decoration: none;
display:block;
line-height: 20px;
}
.kc-dropdown ul li a:hover{
color: #4d5258;
background-color: #d4edfa;
border-color: #b3d3e7;
}
.kc-dropdown ul{
position: absolute;
z-index: 2000;
list-style:none;
display:none;
padding: 5px 0px;
margin: 0px;
background-color: #fff !important;
border: 1px solid #b6b6b6;
border-radius: 1px;
-webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
background-clip: padding-box;
min-width: 100px;
}
.kc-dropdown:hover ul{
display:block;
}
#kc-totp-secret-key {
border: 1px solid #eee;
font-size: 16px;
padding: 10px;
margin: 50px 0;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 202 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 627 B

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,629 @@
/* Patternfly CSS places a "bg-login.jpg" as the background on this ".login-pf" class.
This clashes with the "keycloak-bg.png' background defined on the body below.
Therefore the Patternfly background must be set to none. */
.login-pf {
background: none;
}
.login-pf body {
background: url("../img/keycloak-bg.png") no-repeat center center fixed;
background-size: cover;
height: 100%;
}
textarea.pf-c-form-control {
height: auto;
}
.pf-c-alert__title {
font-size: var(--pf-global--FontSize--xs);
}
p.instruction {
margin: 5px 0;
}
.pf-c-button.pf-m-control {
border-color: rgba(230, 230, 230, 0.5);
}
h1#kc-page-title {
margin-top: 10px;
}
#kc-locale ul {
background-color: var(--pf-global--BackgroundColor--100);
display: none;
top: 20px;
min-width: 100px;
padding: 0;
}
#kc-locale-dropdown{
display: inline-block;
}
#kc-locale-dropdown:hover ul {
display:block;
}
#kc-locale-dropdown a {
color: var(--pf-global--Color--200);
text-align: right;
font-size: var(--pf-global--FontSize--sm);
}
#kc-locale-dropdown button {
background: none;
border: none;
padding: 0;
cursor: pointer;
color: var(--pf-global--Color--200);
text-align: right;
font-size: var(--pf-global--FontSize--sm);
}
button#kc-current-locale-link::after {
content: "\2c5";
margin-left: var(--pf-global--spacer--xs)
}
.login-pf .container {
padding-top: 40px;
}
.login-pf a:hover {
color: #0099d3;
}
#kc-logo {
width: 100%;
}
div.kc-logo-text {
background-image: url(../img/keycloak-logo-text.png);
background-repeat: no-repeat;
height: 63px;
width: 300px;
margin: 0 auto;
}
div.kc-logo-text span {
display: none;
}
#kc-header {
color: #ededed;
overflow: visible;
white-space: nowrap;
}
#kc-header-wrapper {
font-size: 29px;
text-transform: uppercase;
letter-spacing: 3px;
line-height: 1.2em;
padding: 62px 10px 20px;
white-space: normal;
}
#kc-content {
width: 100%;
}
#kc-attempted-username {
font-size: 20px;
font-family: inherit;
font-weight: normal;
padding-right: 10px;
}
#kc-username {
text-align: center;
margin-bottom:-10px;
}
#kc-webauthn-settings-form {
padding-top: 8px;
}
#kc-form-webauthn .select-auth-box-parent {
pointer-events: none;
}
#kc-form-webauthn .select-auth-box-desc {
color: var(--pf-global--palette--black-600);
}
#kc-form-webauthn .select-auth-box-headline {
color: var(--pf-global--Color--300);
}
#kc-form-webauthn .select-auth-box-icon {
flex: 0 0 3em;
}
#kc-form-webauthn .select-auth-box-icon-properties {
margin-top: 10px;
font-size: 1.8em;
}
#kc-form-webauthn .select-auth-box-icon-properties.unknown-transport-class {
margin-top: 3px;
}
#kc-form-webauthn .pf-l-stack__item {
margin: -1px 0;
}
#kc-content-wrapper {
margin-top: 20px;
}
#kc-form-wrapper {
margin-top: 10px;
}
#kc-info {
margin: 20px -40px -30px;
}
#kc-info-wrapper {
font-size: 13px;
padding: 15px 35px;
background-color: #F0F0F0;
}
#kc-form-options span {
display: block;
}
#kc-form-options .checkbox {
margin-top: 0;
color: #72767b;
}
#kc-terms-text {
margin-bottom: 20px;
}
#kc-registration-terms-text {
max-height: 100px;
overflow-y: auto;
overflow-x: hidden;
margin: 5px;
}
#kc-registration {
margin-bottom: 0;
}
/* TOTP */
.subtitle {
text-align: right;
margin-top: 30px;
color: #909090;
}
.required {
color: var(--pf-global--danger-color--200);
}
ol#kc-totp-settings {
margin: 0;
padding-left: 20px;
}
ul#kc-totp-supported-apps {
margin-bottom: 10px;
}
#kc-totp-secret-qr-code {
max-width:150px;
max-height:150px;
}
#kc-totp-secret-key {
background-color: #fff;
color: #333333;
font-size: 16px;
padding: 10px 0;
}
/* OAuth */
#kc-oauth h3 {
margin-top: 0;
}
#kc-oauth ul {
list-style: none;
padding: 0;
margin: 0;
}
#kc-oauth ul li {
border-top: 1px solid rgba(255, 255, 255, 0.1);
font-size: 12px;
padding: 10px 0;
}
#kc-oauth ul li:first-of-type {
border-top: 0;
}
#kc-oauth .kc-role {
display: inline-block;
width: 50%;
}
/* Code */
#kc-code textarea {
width: 100%;
height: 8em;
}
/* Social */
.kc-social-links {
margin-top: 20px;
}
.kc-social-links li {
width: 100%;
}
.kc-social-provider-logo {
font-size: 23px;
width: 30px;
height: 25px;
float: left;
}
.kc-social-gray {
color: var(--pf-global--Color--200);
}
.kc-social-gray h2 {
font-size: 1em;
}
.kc-social-item {
margin-bottom: var(--pf-global--spacer--sm);
font-size: 15px;
text-align: center;
}
.kc-social-provider-name {
position: relative;
}
.kc-social-icon-text {
left: -15px;
}
.kc-social-grid {
display:grid;
grid-column-gap: 10px;
grid-row-gap: 5px;
grid-column-end: span 6;
--pf-l-grid__item--GridColumnEnd: span 6;
}
.kc-social-grid .kc-social-icon-text {
left: -10px;
}
.kc-login-tooltip {
position: relative;
display: inline-block;
}
.kc-social-section {
text-align: center;
}
.kc-social-section hr{
margin-bottom: 10px
}
.kc-login-tooltip .kc-tooltip-text{
top:-3px;
left:160%;
background-color: black;
visibility: hidden;
color: #fff;
min-width:130px;
text-align: center;
border-radius: 2px;
box-shadow:0 1px 8px rgba(0,0,0,0.6);
padding: 5px;
position: absolute;
opacity:0;
transition:opacity 0.5s;
}
/* Show tooltip */
.kc-login-tooltip:hover .kc-tooltip-text {
visibility: visible;
opacity:0.7;
}
/* Arrow for tooltip */
.kc-login-tooltip .kc-tooltip-text::after {
content: " ";
position: absolute;
top: 15px;
right: 100%;
margin-top: -5px;
border-width: 5px;
border-style: solid;
border-color: transparent black transparent transparent;
}
@media (min-width: 768px) {
#kc-container-wrapper {
position: absolute;
width: 100%;
}
.login-pf .container {
padding-right: 80px;
}
#kc-locale {
position: relative;
text-align: right;
z-index: 9999;
}
}
@media (max-width: 767px) {
.login-pf body {
background: white;
}
#kc-header {
padding-left: 15px;
padding-right: 15px;
float: none;
text-align: left;
}
#kc-header-wrapper {
font-size: 16px;
font-weight: bold;
padding: 20px 60px 0 0;
color: #72767b;
letter-spacing: 0;
}
div.kc-logo-text {
margin: 0;
width: 150px;
height: 32px;
background-size: 100%;
}
#kc-form {
float: none;
}
#kc-info-wrapper {
border-top: 1px solid rgba(255, 255, 255, 0.1);
background-color: transparent;
}
.login-pf .container {
padding-top: 15px;
padding-bottom: 15px;
}
#kc-locale {
position: absolute;
width: 200px;
top: 20px;
right: 20px;
text-align: right;
z-index: 9999;
}
}
@media (min-height: 646px) {
#kc-container-wrapper {
bottom: 12%;
}
}
@media (max-height: 645px) {
#kc-container-wrapper {
padding-top: 50px;
top: 20%;
}
}
.card-pf form.form-actions .btn {
float: right;
margin-left: 10px;
}
#kc-form-buttons {
margin-top: 20px;
}
.login-pf-page .login-pf-brand {
margin-top: 20px;
max-width: 360px;
width: 40%;
}
.select-auth-box-arrow{
display: flex;
align-items: center;
margin-right: 2rem;
}
.select-auth-box-icon{
display: flex;
flex: 0 0 2em;
justify-content: center;
margin-right: 1rem;
margin-left: 3rem;
}
.select-auth-box-parent{
border-top: 1px solid var(--pf-global--palette--black-200);
padding-top: 1rem;
padding-bottom: 1rem;
cursor: pointer;
text-align: left;
align-items: unset;
background-color: unset;
border-right: unset;
border-bottom: unset;
border-left: unset;
}
.select-auth-box-parent:hover{
background-color: #f7f8f8;
}
.select-auth-container {
padding-bottom: 0px !important;
}
.select-auth-box-headline {
font-size: var(--pf-global--FontSize--md);
color: var(--pf-global--primary-color--100);
font-weight: bold;
}
.select-auth-box-desc {
font-size: var(--pf-global--FontSize--sm);
}
.select-auth-box-paragraph {
text-align: center;
font-size: var(--pf-global--FontSize--md);
margin-bottom: 5px;
}
.card-pf {
margin: 0 auto;
box-shadow: var(--pf-global--BoxShadow--lg);
padding: 0 20px;
max-width: 500px;
border-top: 4px solid;
border-color: var(--pf-global--primary-color--100);
}
/*phone*/
@media (max-width: 767px) {
.login-pf-page .card-pf {
max-width: none;
margin-left: 0;
margin-right: 0;
padding-top: 0;
border-top: 0;
box-shadow: 0 0;
}
.kc-social-grid {
grid-column-end: 12;
--pf-l-grid__item--GridColumnEnd: span 12;
}
.kc-social-grid .kc-social-icon-text {
left: -15px;
}
}
.login-pf-page .login-pf-signup {
font-size: 15px;
color: #72767b;
}
#kc-content-wrapper .row {
margin-left: 0;
margin-right: 0;
}
.login-pf-page.login-pf-page-accounts {
margin-left: auto;
margin-right: auto;
}
.login-pf-page .btn-primary {
margin-top: 0;
}
.login-pf-page .list-view-pf .list-group-item {
border-bottom: 1px solid #ededed;
}
.login-pf-page .list-view-pf-description {
width: 100%;
}
#kc-form-login div.form-group:last-of-type,
#kc-register-form div.form-group:last-of-type,
#kc-update-profile-form div.form-group:last-of-type,
#kc-update-email-form div.form-group:last-of-type{
margin-bottom: 0px;
}
.no-bottom-margin {
margin-bottom: 0;
}
#kc-back {
margin-top: 5px;
}
/* Recovery codes */
.kc-recovery-codes-warning {
margin-bottom: 32px;
}
.kc-recovery-codes-warning .pf-c-alert__description p {
font-size: 0.875rem;
}
.kc-recovery-codes-list {
list-style: none;
columns: 2;
margin: 16px 0;
padding: 16px 16px 8px 16px;
border: 1px solid #D2D2D2;
}
.kc-recovery-codes-list li {
margin-bottom: 8px;
font-size: 11px;
}
.kc-recovery-codes-list li span {
color: #6A6E73;
width: 16px;
text-align: right;
display: inline-block;
margin-right: 1px;
}
.kc-recovery-codes-actions {
margin-bottom: 24px;
}
.kc-recovery-codes-actions button {
padding-left: 0;
}
.kc-recovery-codes-actions button i {
margin-right: 8px;
}
.kc-recovery-codes-confirmation {
align-items: baseline;
margin-bottom: 16px;
}
#certificate_subjectDN {
overflow-wrap: break-word
}
/* End Recovery codes */

Binary file not shown.

After

Width:  |  Height:  |  Size: 513 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 343 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 678 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 410 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 513 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 646 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

View File

@ -0,0 +1,49 @@
const CHECK_INTERVAL_MILLISECS = 2000;
const initialSession = getSession();
let timeout;
// Remove the timeout when unloading to avoid execution of the
// checkCookiesAndSetTimer when the page is already submitted
addEventListener("beforeunload", () => {
if (timeout) {
clearTimeout(timeout);
timeout = undefined;
}
});
export function checkCookiesAndSetTimer(loginRestartUrl) {
if (initialSession) {
// We started with a session, so there is nothing to do, exit.
return;
}
const session = getSession();
if (!session) {
// The session is not present, check again later.
timeout = setTimeout(
() => checkCookiesAndSetTimer(loginRestartUrl),
CHECK_INTERVAL_MILLISECS,
);
} else {
// Redirect to the login restart URL. This can typically automatically login user due the SSO
location.href = loginRestartUrl;
}
}
function getSession() {
return getCookieByName("KEYCLOAK_SESSION");
}
function getCookieByName(name) {
for (const cookie of document.cookie.split(";")) {
const [key, value] = cookie.split("=").map((value) => value.trim());
if (key === name) {
return value.startsWith('"') && value.endsWith('"')
? value.slice(1, -1)
: value;
}
}
return null;
}

View File

@ -0,0 +1,48 @@
export const formatNumber = (input, format) => {
if (!input) {
return "";
}
// array holding the patterns for the number of expected digits in each part
const digitPattern = format.match(/{\d+}/g);
if (!digitPattern) {
return "";
}
// calculate the maximum size of the given pattern based on the sum of the expected digits
const maxSize = digitPattern.reduce((total, p) => total + parseInt(p.replace("{", "").replace("}", "")), 0)
// keep only digits
let rawValue = input.replace(/\D+/g, '');
// make sure the value is a number
if (parseInt(rawValue) != rawValue) {
return "";
}
// make sure the number of digits does not exceed the maximum size
if (rawValue.length > maxSize) {
rawValue = rawValue.substring(0, maxSize);
}
// build the regex based based on the expected digits in each part
const formatter = digitPattern.reduce((result, p) => result + `(\\d${p})`, "^");
// if the current digits match the pattern we have each group of digits in an array
let digits = new RegExp(formatter).exec(rawValue);
// no match, return the raw value without any format
if (!digits) {
return input;
}
let result = format;
// finally format the current digits accordingly to the given format
for (let i = 0; i < digitPattern.length; i++) {
result = result.replace(digitPattern[i], digits[i + 1]);
}
return result;
}

View File

@ -0,0 +1,106 @@
const DATA_KC_MULTIVALUED = 'data-kcMultivalued';
const KC_ADD_ACTION_PREFIX = "kc-add-";
const KC_REMOVE_ACTION_PREFIX = "kc-remove-";
const KC_ACTION_CLASS = "pf-c-button pf-m-inline pf-m-link";
function createAddAction(element) {
const action = createAction("Add value",
KC_ADD_ACTION_PREFIX,
element,
() => {
const name = element.getAttribute("name");
const elements = getInputElementsByName().get(name);
const length = elements.length;
if (length === 0) {
return;
}
const lastNode = elements[length - 1];
const newNode = lastNode.cloneNode(true);
newNode.setAttribute("id", name + "-" + elements.length);
newNode.value = "";
lastNode.after(newNode);
render();
});
element.after(action);
}
function createRemoveAction(element, isLastElement) {
let text = "Remove";
if (isLastElement) {
text = text + " | ";
}
const action = createAction(text, KC_REMOVE_ACTION_PREFIX, element, () => {
removeActions(element);
element.remove();
render();
});
element.insertAdjacentElement('afterend', action);
}
function getInputElementsByName() {
const selector = document.querySelectorAll(`[${DATA_KC_MULTIVALUED}]`);
const elementsByName = new Map();
for (let element of Array.from(selector.values())) {
let name = element.getAttribute("name");
let elements = elementsByName.get(name);
if (!elements) {
elements = [];
elementsByName.set(name, elements);
}
elements.push(element);
}
return elementsByName;
}
function removeActions(element) {
for (let actionPrefix of [KC_ADD_ACTION_PREFIX, KC_REMOVE_ACTION_PREFIX]) {
const action = document.getElementById(actionPrefix + element.getAttribute("id"));
if (action) {
action.remove();
}
}
}
function createAction(text, type, element, onClick) {
const action = document.createElement("button")
action.setAttribute("id", type + element.getAttribute("id"));
action.setAttribute("type", "button");
action.innerText = text;
action.setAttribute("class", KC_ACTION_CLASS);
action.addEventListener("click", onClick);
return action;
}
function render() {
getInputElementsByName().forEach((elements, name) => {
elements.forEach((element, index) => {
removeActions(element);
element.setAttribute("id", name + "-" + index);
const lastNode = element === elements[elements.length - 1];
if (lastNode) {
createAddAction(element);
}
if (elements.length > 1) {
createRemoveAction(element, lastNode);
}
});
});
}
render();

View File

@ -0,0 +1,21 @@
// @ts-check
import { formatNumber } from "./common.js";
import { registerElementAnnotatedBy } from "./userProfile.js";
const KC_NUMBER_FORMAT = "kcNumberFormat";
registerElementAnnotatedBy({
name: KC_NUMBER_FORMAT,
onAdd(element) {
const formatValue = () => {
const format = element.getAttribute(`data-${KC_NUMBER_FORMAT}`);
element.value = formatNumber(element.value, format);
};
element.addEventListener("keyup", formatValue);
formatValue();
return () => element.removeEventListener("keyup", formatValue);
},
});

View File

@ -0,0 +1,19 @@
// @ts-check
import { formatNumber } from "./common.js";
import { registerElementAnnotatedBy } from "./userProfile.js";
const KC_NUMBER_UNFORMAT = 'kcNumberUnFormat';
registerElementAnnotatedBy({
name: KC_NUMBER_UNFORMAT,
onAdd(element) {
for (let form of document.forms) {
form.addEventListener('submit', (event) => {
const rawFormat = element.getAttribute(`data-${KC_NUMBER_UNFORMAT}`);
if (rawFormat) {
element.value = formatNumber(element.value, rawFormat);
}
});
}
},
});

View File

@ -0,0 +1,315 @@
// @ts-check
/*
* This content is licensed according to the W3C Software License at
* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document
*
* File: menu-button-links.js
*
* Desc: Creates a menu button that opens a menu of links
*
* Modified by Peter Keuter to adhere to the coding standards of Keycloak
* Original file: https://www.w3.org/WAI/content-assets/wai-aria-practices/patterns/menu-button/examples/js/menu-button-links.js
* Source: https://www.w3.org/TR/wai-aria-practices/examples/menu-button/menu-button-links.html
*/
class MenuButtonLinks {
constructor(domNode) {
this.domNode = domNode;
this.buttonNode = domNode.querySelector("button");
this.menuNode = domNode.querySelector('[role="menu"]');
this.menuitemNodes = [];
this.firstMenuitem = false;
this.lastMenuitem = false;
this.firstChars = [];
this.buttonNode.addEventListener("keydown", (e) => this.onButtonKeydown(e));
this.buttonNode.addEventListener("click", (e) => this.onButtonClick(e));
const nodes = domNode.querySelectorAll('[role="menuitem"]');
for (const menuitem of nodes) {
this.menuitemNodes.push(menuitem);
menuitem.tabIndex = -1;
this.firstChars.push(menuitem.textContent.trim()[0].toLowerCase());
menuitem.addEventListener("keydown", (e) => this.onMenuitemKeydown(e));
menuitem.addEventListener("mouseover", (e) =>
this.onMenuitemMouseover(e)
);
if (!this.firstMenuitem) {
this.firstMenuitem = menuitem;
}
this.lastMenuitem = menuitem;
}
domNode.addEventListener("focusin", () => this.onFocusin());
domNode.addEventListener("focusout", () => this.onFocusout());
window.addEventListener(
"mousedown",
(e) => this.onBackgroundMousedown(e),
true
);
}
setFocusToMenuitem = (newMenuitem) =>
this.menuitemNodes.forEach((item) => {
if (item === newMenuitem) {
item.tabIndex = 0;
newMenuitem.focus();
} else {
item.tabIndex = -1;
}
});
setFocusToFirstMenuitem = () => this.setFocusToMenuitem(this.firstMenuitem);
setFocusToLastMenuitem = () => this.setFocusToMenuitem(this.lastMenuitem);
setFocusToPreviousMenuitem = (currentMenuitem) => {
let newMenuitem, index;
if (currentMenuitem === this.firstMenuitem) {
newMenuitem = this.lastMenuitem;
} else {
index = this.menuitemNodes.indexOf(currentMenuitem);
newMenuitem = this.menuitemNodes[index - 1];
}
this.setFocusToMenuitem(newMenuitem);
return newMenuitem;
};
setFocusToNextMenuitem = (currentMenuitem) => {
let newMenuitem, index;
if (currentMenuitem === this.lastMenuitem) {
newMenuitem = this.firstMenuitem;
} else {
index = this.menuitemNodes.indexOf(currentMenuitem);
newMenuitem = this.menuitemNodes[index + 1];
}
this.setFocusToMenuitem(newMenuitem);
return newMenuitem;
};
setFocusByFirstCharacter = (currentMenuitem, char) => {
let start, index;
if (char.length > 1) {
return;
}
char = char.toLowerCase();
// Get start index for search based on position of currentItem
start = this.menuitemNodes.indexOf(currentMenuitem) + 1;
if (start >= this.menuitemNodes.length) {
start = 0;
}
// Check remaining slots in the menu
index = this.firstChars.indexOf(char, start);
// If not found in remaining slots, check from beginning
if (index === -1) {
index = this.firstChars.indexOf(char, 0);
}
// If match was found...
if (index > -1) {
this.setFocusToMenuitem(this.menuitemNodes[index]);
}
};
// Utilities
getIndexFirstChars = (startIndex, char) => {
for (let i = startIndex; i < this.firstChars.length; i++) {
if (char === this.firstChars[i]) {
return i;
}
}
return -1;
};
// Popup menu methods
openPopup = () => {
this.menuNode.style.display = "block";
this.buttonNode.setAttribute("aria-expanded", "true");
};
closePopup = () => {
if (this.isOpen()) {
this.buttonNode.setAttribute("aria-expanded", "false");
this.menuNode.style.removeProperty("display");
}
};
isOpen = () => {
return this.buttonNode.getAttribute("aria-expanded") === "true";
};
// Menu event handlers
onFocusin = () => {
this.domNode.classList.add("focus");
};
onFocusout = () => {
this.domNode.classList.remove("focus");
};
onButtonKeydown = (event) => {
const key = event.key;
let flag = false;
switch (key) {
case " ":
case "Enter":
case "ArrowDown":
case "Down":
this.openPopup();
this.setFocusToFirstMenuitem();
flag = true;
break;
case "Esc":
case "Escape":
this.closePopup();
this.buttonNode.focus();
flag = true;
break;
case "Up":
case "ArrowUp":
this.openPopup();
this.setFocusToLastMenuitem();
flag = true;
break;
default:
break;
}
if (flag) {
event.stopPropagation();
event.preventDefault();
}
};
onButtonClick(event) {
if (this.isOpen()) {
this.closePopup();
this.buttonNode.focus();
} else {
this.openPopup();
this.setFocusToFirstMenuitem();
}
event.stopPropagation();
event.preventDefault();
}
onMenuitemKeydown(event) {
const tgt = event.currentTarget;
const key = event.key;
let flag = false;
const isPrintableCharacter = (str) => str.length === 1 && str.match(/\S/);
if (event.ctrlKey || event.altKey || event.metaKey) {
return;
}
if (event.shiftKey) {
if (isPrintableCharacter(key)) {
this.setFocusByFirstCharacter(tgt, key);
flag = true;
}
if (event.key === "Tab") {
this.buttonNode.focus();
this.closePopup();
flag = true;
}
} else {
switch (key) {
case " ":
window.location.href = tgt.href;
break;
case "Esc":
case "Escape":
this.closePopup();
this.buttonNode.focus();
flag = true;
break;
case "Up":
case "ArrowUp":
this.setFocusToPreviousMenuitem(tgt);
flag = true;
break;
case "ArrowDown":
case "Down":
this.setFocusToNextMenuitem(tgt);
flag = true;
break;
case "Home":
case "PageUp":
this.setFocusToFirstMenuitem();
flag = true;
break;
case "End":
case "PageDown":
this.setFocusToLastMenuitem();
flag = true;
break;
case "Tab":
this.closePopup();
break;
default:
if (isPrintableCharacter(key)) {
this.setFocusByFirstCharacter(tgt, key);
flag = true;
}
break;
}
}
if (flag) {
event.stopPropagation();
event.preventDefault();
}
}
onMenuitemMouseover(event) {
const tgt = event.currentTarget;
tgt.focus();
}
onBackgroundMousedown(event) {
if (!this.domNode.contains(event.target)) {
if (this.isOpen()) {
this.closePopup();
this.buttonNode.focus();
}
}
}
}
const menuButtons = document.querySelectorAll(".menu-button-links");
for (const button of menuButtons) {
new MenuButtonLinks(button);
}

View File

@ -0,0 +1,79 @@
import { base64url } from "rfc4648";
import { returnSuccess, returnFailure } from "./webauthnAuthenticate.js";
export function initAuthenticate(input) {
// Check if WebAuthn is supported by this browser
if (!window.PublicKeyCredential) {
returnFailure(input.errmsg);
return;
}
if (input.isUserIdentified || typeof PublicKeyCredential.isConditionalMediationAvailable === "undefined") {
document.getElementById("kc-form-passkey-button").style.display = 'block';
} else {
tryAutoFillUI(input);
}
}
function doAuthenticate(input) {
// Check if WebAuthn is supported by this browser
if (!window.PublicKeyCredential) {
returnFailure(input.errmsg);
return;
}
const publicKey = {
rpId : input.rpId,
challenge: base64url.parse(input.challenge, { loose: true })
};
publicKey.allowCredentials = !input.isUserIdentified ? [] : getAllowCredentials();
if (input.createTimeout !== 0) {
publicKey.timeout = input.createTimeout * 1000;
}
if (input.userVerification !== 'not specified') {
publicKey.userVerification = input.userVerification;
}
return navigator.credentials.get({
publicKey: publicKey,
...input.additionalOptions
});
}
async function tryAutoFillUI(input) {
const isConditionalMediationAvailable = await PublicKeyCredential.isConditionalMediationAvailable();
if (isConditionalMediationAvailable) {
document.getElementById("kc-form-login").style.display = "block";
input.additionalOptions = { mediation: 'conditional'};
try {
const result = await doAuthenticate(input);
returnSuccess(result);
} catch (error) {
returnFailure(error);
}
} else {
document.getElementById("kc-form-passkey-button").style.display = 'block';
}
}
function getAllowCredentials() {
const allowCredentials = [];
const authnUse = document.forms['authn_select'].authn_use_chk;
if (authnUse !== undefined) {
if (authnUse.length === undefined) {
allowCredentials.push({
id: base64url.parse(authnUse.value, {loose: true}),
type: 'public-key',
});
} else {
authnUse.forEach((entry) =>
allowCredentials.push({
id: base64url.parse(entry.value, {loose: true}),
type: 'public-key',
}));
}
}
return allowCredentials;
}

View File

@ -0,0 +1,15 @@
const toggle = (button) => {
const passwordElement = document.getElementById(button.getAttribute('aria-controls'));
if (passwordElement.type === "password") {
passwordElement.type = "text";
button.children.item(0).className = button.dataset.iconHide;
button.setAttribute("aria-label", button.dataset.labelHide);
} else if(passwordElement.type === "text") {
passwordElement.type = "password";
button.children.item(0).className = button.dataset.iconShow;
button.setAttribute("aria-label", button.dataset.labelShow);
}
}
document.querySelectorAll('[data-password-toggle]')
.forEach(button => button.onclick = () => toggle(button));

View File

@ -0,0 +1,71 @@
// @ts-check
/**
* @typedef {Object} AnnotationDescriptor
* @property {string} name - The name of the field to register (e.g. `numberFormat`).
* @property {(element: HTMLElement) => (() => void) | void} onAdd - The function to call when a new element is added to the DOM.
*/
const observer = new MutationObserver(onMutate);
observer.observe(document.body, { childList: true, subtree: true });
/** @type {AnnotationDescriptor[]} */
const descriptors = [];
/** @type {WeakMap<HTMLElement, () => void>} */
const cleanupFunctions = new WeakMap();
/**
* @param {AnnotationDescriptor} descriptor
*/
export function registerElementAnnotatedBy(descriptor) {
descriptors.push(descriptor);
document.querySelectorAll(`[data-${descriptor.name}]`).forEach((element) => {
if (element instanceof HTMLElement) {
handleNewElement(element, descriptor);
}
});
}
/**
* @type {MutationCallback}
*/
function onMutate(mutations) {
const removedNodes = mutations.flatMap((mutation) => Array.from(mutation.removedNodes));
for (const node of removedNodes) {
if (!(node instanceof HTMLElement)) {
continue;
}
const handleRemovedElement = cleanupFunctions.get(node);
if (handleRemovedElement) {
handleRemovedElement();
}
cleanupFunctions.delete(node);
}
const addedNodes = mutations.flatMap((mutation) => Array.from(mutation.addedNodes));
for (const descriptor of descriptors) {
for (const node of addedNodes) {
if (node instanceof HTMLElement && node.hasAttribute(`data-${descriptor.name}`)) {
handleNewElement(node, descriptor);
}
}
}
}
/**
* @param {HTMLElement} element
* @param {AnnotationDescriptor} descriptor
*/
function handleNewElement(element, descriptor) {
const cleanup = descriptor.onAdd(element);
if (cleanup) {
cleanupFunctions.set(element, cleanup);
}
}

View File

@ -0,0 +1,82 @@
import { base64url } from "rfc4648";
export async function authenticateByWebAuthn(input) {
if (!input.isUserIdentified) {
try {
const result = await doAuthenticate([], input.challenge, input.userVerification, input.rpId, input.createTimeout, input.errmsg);
returnSuccess(result);
} catch (error) {
returnFailure(error);
}
return;
}
checkAllowCredentials(input.challenge, input.userVerification, input.rpId, input.createTimeout, input.errmsg);
}
async function checkAllowCredentials(challenge, userVerification, rpId, createTimeout, errmsg) {
const allowCredentials = [];
const authnUse = document.forms['authn_select'].authn_use_chk;
if (authnUse !== undefined) {
if (authnUse.length === undefined) {
allowCredentials.push({
id: base64url.parse(authnUse.value, {loose: true}),
type: 'public-key',
});
} else {
authnUse.forEach((entry) =>
allowCredentials.push({
id: base64url.parse(entry.value, {loose: true}),
type: 'public-key',
}));
}
}
try {
const result = await doAuthenticate(allowCredentials, challenge, userVerification, rpId, createTimeout, errmsg);
returnSuccess(result);
} catch (error) {
returnFailure(error);
}
}
function doAuthenticate(allowCredentials, challenge, userVerification, rpId, createTimeout, errmsg) {
// Check if WebAuthn is supported by this browser
if (!window.PublicKeyCredential) {
returnFailure(errmsg);
return;
}
const publicKey = {
rpId : rpId,
challenge: base64url.parse(challenge, { loose: true })
};
if (createTimeout !== 0) {
publicKey.timeout = createTimeout * 1000;
}
if (allowCredentials.length) {
publicKey.allowCredentials = allowCredentials;
}
if (userVerification !== 'not specified') {
publicKey.userVerification = userVerification;
}
return navigator.credentials.get({publicKey});
}
export function returnSuccess(result) {
document.getElementById("clientDataJSON").value = base64url.stringify(new Uint8Array(result.response.clientDataJSON), { pad: false });
document.getElementById("authenticatorData").value = base64url.stringify(new Uint8Array(result.response.authenticatorData), { pad: false });
document.getElementById("signature").value = base64url.stringify(new Uint8Array(result.response.signature), { pad: false });
document.getElementById("credentialId").value = result.id;
if (result.response.userHandle) {
document.getElementById("userHandle").value = base64url.stringify(new Uint8Array(result.response.userHandle), { pad: false });
}
document.getElementById("webauth").submit();
}
export function returnFailure(err) {
document.getElementById("error").value = err;
document.getElementById("webauth").submit();
}

View File

@ -0,0 +1,140 @@
import { base64url } from "rfc4648";
export async function registerByWebAuthn(input) {
// Check if WebAuthn is supported by this browser
if (!window.PublicKeyCredential) {
returnFailure(input.errmsg);
return;
}
const publicKey = {
challenge: base64url.parse(input.challenge, {loose: true}),
rp: {id: input.rpId, name: input.rpEntityName},
user: {
id: base64url.parse(input.userid, {loose: true}),
name: input.username,
displayName: input.username
},
pubKeyCredParams: getPubKeyCredParams(input.signatureAlgorithms),
};
if (input.attestationConveyancePreference !== 'not specified') {
publicKey.attestation = input.attestationConveyancePreference;
}
const authenticatorSelection = {};
let isAuthenticatorSelectionSpecified = false;
if (input.authenticatorAttachment !== 'not specified') {
authenticatorSelection.authenticatorAttachment = input.authenticatorAttachment;
isAuthenticatorSelectionSpecified = true;
}
if (input.requireResidentKey !== 'not specified') {
if (input.requireResidentKey === 'Yes') {
authenticatorSelection.requireResidentKey = true;
} else {
authenticatorSelection.requireResidentKey = false;
}
isAuthenticatorSelectionSpecified = true;
}
if (input.userVerificationRequirement !== 'not specified') {
authenticatorSelection.userVerification = input.userVerificationRequirement;
isAuthenticatorSelectionSpecified = true;
}
if (isAuthenticatorSelectionSpecified) {
publicKey.authenticatorSelection = authenticatorSelection;
}
if (input.createTimeout !== 0) {
publicKey.timeout = input.createTimeout * 1000;
}
const excludeCredentials = getExcludeCredentials(input.excludeCredentialIds);
if (excludeCredentials.length > 0) {
publicKey.excludeCredentials = excludeCredentials;
}
try {
const result = await doRegister(publicKey);
returnSuccess(result, input.initLabel, input.initLabelPrompt);
} catch (error) {
returnFailure(error);
}
}
function doRegister(publicKey) {
return navigator.credentials.create({publicKey});
}
function getPubKeyCredParams(signatureAlgorithmsList) {
const pubKeyCredParams = [];
if (signatureAlgorithmsList.length === 0) {
pubKeyCredParams.push({type: "public-key", alg: -7});
return pubKeyCredParams;
}
for (const entry of signatureAlgorithmsList) {
pubKeyCredParams.push({
type: "public-key",
alg: entry
});
}
return pubKeyCredParams;
}
function getExcludeCredentials(excludeCredentialIds) {
const excludeCredentials = [];
if (excludeCredentialIds === "") {
return excludeCredentials;
}
for (const entry of excludeCredentialIds.split(',')) {
excludeCredentials.push({
type: "public-key",
id: base64url.parse(entry, {loose: true})
});
}
return excludeCredentials;
}
function getTransportsAsString(transportsList) {
if (!Array.isArray(transportsList)) {
return "";
}
return transportsList.join();
}
function returnSuccess(result, initLabel, initLabelPrompt) {
document.getElementById("clientDataJSON").value = base64url.stringify(new Uint8Array(result.response.clientDataJSON), {pad: false});
document.getElementById("attestationObject").value = base64url.stringify(new Uint8Array(result.response.attestationObject), {pad: false});
document.getElementById("publicKeyCredentialId").value = base64url.stringify(new Uint8Array(result.rawId), {pad: false});
if (typeof result.response.getTransports === "function") {
const transports = result.response.getTransports();
if (transports) {
document.getElementById("transports").value = getTransportsAsString(transports);
}
} else {
console.log("Your browser is not able to recognize supported transport media for the authenticator.");
}
let labelResult = window.prompt(initLabelPrompt, initLabel);
if (labelResult === null) {
labelResult = initLabel;
}
document.getElementById("authenticatorLabel").value = labelResult;
document.getElementById("register").submit();
}
function returnFailure(err) {
document.getElementById("error").value = err;
document.getElementById("register").submit();
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 627 B

View File

@ -0,0 +1,22 @@
/*!
* This folder contains updated PatternFly4 icons (version 2020.13).
* After the PF4 transition is finished this folder will be deleted.
*/
@font-face {
font-family: "pficon-tmp";
src: url("./pficon.woff2") format("woff2");
}
.pf-icon-openshift:before {
font-family: "pficon-tmp";
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
font-style: normal;
font-variant: normal;
font-weight: normal;
text-decoration: none;
text-transform: none; }
.pf-icon-openshift:before {
content: ""; }

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

View File

@ -0,0 +1,178 @@
/* eslint-disable @typescript-eslint/strict-boolean-expressions */
function parse(string, encoding, opts) {
var _opts$out;
if (opts === void 0) {
opts = {};
}
// Build the character lookup table:
if (!encoding.codes) {
encoding.codes = {};
for (var i = 0; i < encoding.chars.length; ++i) {
encoding.codes[encoding.chars[i]] = i;
}
} // The string must have a whole number of bytes:
if (!opts.loose && string.length * encoding.bits & 7) {
throw new SyntaxError('Invalid padding');
} // Count the padding bytes:
var end = string.length;
while (string[end - 1] === '=') {
--end; // If we get a whole number of bytes, there is too much padding:
if (!opts.loose && !((string.length - end) * encoding.bits & 7)) {
throw new SyntaxError('Invalid padding');
}
} // Allocate the output:
var out = new ((_opts$out = opts.out) != null ? _opts$out : Uint8Array)(end * encoding.bits / 8 | 0); // Parse the data:
var bits = 0; // Number of bits currently in the buffer
var buffer = 0; // Bits waiting to be written out, MSB first
var written = 0; // Next byte to write
for (var _i = 0; _i < end; ++_i) {
// Read one character from the string:
var value = encoding.codes[string[_i]];
if (value === undefined) {
throw new SyntaxError('Invalid character ' + string[_i]);
} // Append the bits to the buffer:
buffer = buffer << encoding.bits | value;
bits += encoding.bits; // Write out some bits if the buffer has a byte's worth:
if (bits >= 8) {
bits -= 8;
out[written++] = 0xff & buffer >> bits;
}
} // Verify that we have received just enough bits:
if (bits >= encoding.bits || 0xff & buffer << 8 - bits) {
throw new SyntaxError('Unexpected end of data');
}
return out;
}
function stringify(data, encoding, opts) {
if (opts === void 0) {
opts = {};
}
var _opts = opts,
_opts$pad = _opts.pad,
pad = _opts$pad === void 0 ? true : _opts$pad;
var mask = (1 << encoding.bits) - 1;
var out = '';
var bits = 0; // Number of bits currently in the buffer
var buffer = 0; // Bits waiting to be written out, MSB first
for (var i = 0; i < data.length; ++i) {
// Slurp data into the buffer:
buffer = buffer << 8 | 0xff & data[i];
bits += 8; // Write out as much as we can:
while (bits > encoding.bits) {
bits -= encoding.bits;
out += encoding.chars[mask & buffer >> bits];
}
} // Partial character:
if (bits) {
out += encoding.chars[mask & buffer << encoding.bits - bits];
} // Add padding characters until we hit a byte boundary:
if (pad) {
while (out.length * encoding.bits & 7) {
out += '=';
}
}
return out;
}
/* eslint-disable @typescript-eslint/strict-boolean-expressions */
var base16Encoding = {
chars: '0123456789ABCDEF',
bits: 4
};
var base32Encoding = {
chars: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567',
bits: 5
};
var base32HexEncoding = {
chars: '0123456789ABCDEFGHIJKLMNOPQRSTUV',
bits: 5
};
var base64Encoding = {
chars: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/',
bits: 6
};
var base64UrlEncoding = {
chars: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_',
bits: 6
};
var base16 = {
parse: function parse$1(string, opts) {
return parse(string.toUpperCase(), base16Encoding, opts);
},
stringify: function stringify$1(data, opts) {
return stringify(data, base16Encoding, opts);
}
};
var base32 = {
parse: function parse$1(string, opts) {
if (opts === void 0) {
opts = {};
}
return parse(opts.loose ? string.toUpperCase().replace(/0/g, 'O').replace(/1/g, 'L').replace(/8/g, 'B') : string, base32Encoding, opts);
},
stringify: function stringify$1(data, opts) {
return stringify(data, base32Encoding, opts);
}
};
var base32hex = {
parse: function parse$1(string, opts) {
return parse(string, base32HexEncoding, opts);
},
stringify: function stringify$1(data, opts) {
return stringify(data, base32HexEncoding, opts);
}
};
var base64 = {
parse: function parse$1(string, opts) {
return parse(string, base64Encoding, opts);
},
stringify: function stringify$1(data, opts) {
return stringify(data, base64Encoding, opts);
}
};
var base64url = {
parse: function parse$1(string, opts) {
return parse(string, base64UrlEncoding, opts);
},
stringify: function stringify$1(data, opts) {
return stringify(data, base64UrlEncoding, opts);
}
};
var codec = {
parse: parse,
stringify: stringify
};
export { base16, base32, base32hex, base64, base64url, codec };

View File

@ -1 +1 @@
{"generatedAt":1725957919917,"builder":{"name":"webpack5"},"hasCustomBabel":false,"hasCustomWebpack":false,"hasStaticDirs":true,"hasStorybookEslint":true,"refCount":0,"packageManager":{"type":"yarn","version":"1.22.22"},"storybookVersion":"6.5.16","language":"typescript","storybookPackages":{"@storybook/builder-webpack5":{"version":"6.5.16"},"@storybook/manager-webpack5":{"version":"6.5.16"},"@storybook/react":{"version":"6.5.16"},"eslint-plugin-storybook":{"version":"0.6.11"}},"framework":{"name":"react"},"addons":{"storybook-dark-mode":{"version":"1.1.2"},"@storybook/addon-a11y":{"version":"6.5.16"}}}
{"generatedAt":1725987895328,"builder":{"name":"webpack5"},"hasCustomBabel":false,"hasCustomWebpack":false,"hasStaticDirs":true,"hasStorybookEslint":true,"refCount":0,"packageManager":{"type":"yarn","version":"1.22.22"},"storybookVersion":"6.5.16","language":"typescript","storybookPackages":{"@storybook/builder-webpack5":{"version":"6.5.16"},"@storybook/manager-webpack5":{"version":"6.5.16"},"@storybook/react":{"version":"6.5.16"},"eslint-plugin-storybook":{"version":"0.6.11"}},"framework":{"name":"react"},"addons":{"storybook-dark-mode":{"version":"1.1.2"},"@storybook/addon-a11y":{"version":"6.5.16"}}}