mirror of
https://github.com/kanidm/kanidm.git
synced 2025-02-22 20:26:30 +01:00
Javascript linting (#3329)
* feat(ci/dev): adding npm/eslint config for javascript linting * feat(ci/dev): adding js-prettier config for consistency in formatting * fix(css): linting * fix(js): linting the js things
This commit is contained in:
parent
3430a1c31d
commit
b74883ae0d
|
@ -2,9 +2,21 @@
|
|||
|
||||
root = true
|
||||
|
||||
|
||||
|
||||
[*.md]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_size = 2
|
||||
max_line_length = 100
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.js]
|
||||
tab_width = 4
|
||||
max_line_length = 120
|
||||
print_width = 120
|
||||
|
||||
[*.mjs]
|
||||
tab_width = 4
|
||||
max_line_length = 120
|
||||
print_width = 120
|
21
.github/workflows/javascript_lint.yml
vendored
Normal file
21
.github/workflows/javascript_lint.yml
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
---
|
||||
name: Javascript Linting
|
||||
"on":
|
||||
- push
|
||||
- pull_request
|
||||
jobs:
|
||||
javascript_lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Run ESLint to check Javascript files
|
||||
run:
|
||||
make eslint
|
||||
javascript_fmt:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Run Prettier to check Javascript files
|
||||
run:
|
||||
make prettier
|
||||
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -45,3 +45,6 @@ scripts/oauth_proxy/envfile
|
|||
# IDEs
|
||||
.idea/
|
||||
.vscode/
|
||||
|
||||
# javascript test things
|
||||
node_modules/
|
31
Makefile
31
Makefile
|
@ -325,4 +325,33 @@ coverage: ## Run the coverage tests using cargo-tarpaulin
|
|||
coveralls: ## Run cargo tarpaulin and upload to coveralls
|
||||
coveralls:
|
||||
cargo tarpaulin --coveralls $(COVERALLS_REPO_TOKEN)
|
||||
@echo "Coveralls repo information is at https://coveralls.io/github/kanidm/kanidm"
|
||||
@echo "Coveralls repo information is at https://coveralls.io/github/kanidm/kanidm"
|
||||
|
||||
|
||||
.PHONY: eslint
|
||||
eslint: ## Run eslint on the UI javascript things
|
||||
eslint: eslint/setup
|
||||
@echo "################################"
|
||||
@echo " Running eslint..."
|
||||
@echo "################################"
|
||||
cd server/core && find ./static -name '*js' -not -path '*/external/*' -exec eslint "{}" \;
|
||||
@echo "################################"
|
||||
@echo "Done!"
|
||||
|
||||
.PHONY: eslint/setup
|
||||
eslint/setup: ## Install eslint for the UI javascript things
|
||||
cd server/core && npm ci
|
||||
|
||||
.PHONY: prettier
|
||||
prettier: ## Run prettier on the UI javascript things
|
||||
prettier: eslint/setup
|
||||
@echo " Running prettier..."
|
||||
cd server/core && npm run prettier
|
||||
@echo "Done!"
|
||||
|
||||
.PHONY: prettier/fix
|
||||
prettier/fix: ## Run prettier on the UI javascript things and write back changes
|
||||
prettier/fix: eslint/setup
|
||||
@echo " Running prettier..."
|
||||
cd server/core && npm run prettier:fix
|
||||
@echo "Done!"
|
16
server/core/eslint.config.mjs
Normal file
16
server/core/eslint.config.mjs
Normal file
|
@ -0,0 +1,16 @@
|
|||
import globals from "globals";
|
||||
import pluginJs from "@eslint/js";
|
||||
|
||||
|
||||
/** @type {import('eslint').Linter.Config[]} */
|
||||
export default [
|
||||
{
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.browser,
|
||||
Base64 : "writeable" // to feed the Base64 class into the global scope
|
||||
}
|
||||
}
|
||||
},
|
||||
pluginJs.configs.recommended,
|
||||
];
|
1104
server/core/package-lock.json
generated
Normal file
1104
server/core/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
27
server/core/package.json
Normal file
27
server/core/package.json
Normal file
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"name": "kanidm",
|
||||
"version": "0.0.1-dev",
|
||||
"description": "Kanidm UI Javascript - not a publishable package, just for development",
|
||||
"type": "module",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/kanidm/kanidm.git"
|
||||
},
|
||||
"author": "",
|
||||
"license": "MPL-2.0",
|
||||
"bugs": {
|
||||
"url": "https://github.com/kanidm/kanidm/issues"
|
||||
},
|
||||
"homepage": "https://kanidm.com",
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.17.0",
|
||||
"eslint": "^9.17.0",
|
||||
"globals": "^15.14.0",
|
||||
"prettier": "^3.4.2"
|
||||
},
|
||||
"scripts" : {
|
||||
"prettier": "npx prettier --ignore-path 'prettier-ignore' ./static --check",
|
||||
"prettier:fix": "npx prettier --ignore-path 'prettier-ignore' ./static --write"
|
||||
}
|
||||
|
||||
}
|
1
server/core/prettier-ignore
Normal file
1
server/core/prettier-ignore
Normal file
|
@ -0,0 +1 @@
|
|||
static/external/
|
|
@ -1,4 +1,4 @@
|
|||
console.debug('credupdate: loaded');
|
||||
console.debug("credupdate: loaded");
|
||||
|
||||
// Makes the password form interactive (e.g. shows when passwords don't match)
|
||||
function setupInteractivePwdFormListeners() {
|
||||
|
@ -18,10 +18,10 @@ function setupInteractivePwdFormListeners() {
|
|||
pwd_submit.disabled = true;
|
||||
}
|
||||
|
||||
new_pwd.addEventListener("input", (_) => {
|
||||
new_pwd.addEventListener("input", () => {
|
||||
// Don't mark invalid if user didn't fill in the confirmation box yet
|
||||
// Also my password manager (keepassxc with autocomplete)
|
||||
// likes to fire off input events when both inputs were empty.
|
||||
// Also KeepassXC with autocomplete likes to fire off input events when
|
||||
// both inputs are empty.
|
||||
if (new_pwd_check.value !== "") {
|
||||
if (new_pwd.value === new_pwd_check.value) {
|
||||
markPwdCheckValid();
|
||||
|
@ -32,7 +32,7 @@ function setupInteractivePwdFormListeners() {
|
|||
new_pwd.classList.remove("is-invalid");
|
||||
});
|
||||
|
||||
new_pwd_check.addEventListener("input", (_) => {
|
||||
new_pwd_check.addEventListener("input", () => {
|
||||
// No point in updating the status if confirmation box is empty
|
||||
if (new_pwd_check.value === "") return;
|
||||
if (new_pwd_check.value === new_pwd.value) {
|
||||
|
@ -43,38 +43,44 @@ function setupInteractivePwdFormListeners() {
|
|||
});
|
||||
}
|
||||
|
||||
window.stillSwapFailureResponse = function(event) {
|
||||
window.stillSwapFailureResponse = function (event) {
|
||||
if (event.detail.xhr.status === 422 || event.detail.xhr.status === 500) {
|
||||
console.log("Still swapping failure response")
|
||||
console.debug(`Got HTTP/${event.detail.xhr.status}, still swapping failure response`);
|
||||
event.detail.shouldSwap = true;
|
||||
event.detail.isError = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function onPasskeyCreated(assertion) {
|
||||
try {
|
||||
console.log(assertion)
|
||||
console.log(assertion);
|
||||
let creationData = {};
|
||||
|
||||
creationData.id = assertion.id;
|
||||
creationData.rawId = Base64.fromUint8Array(new Uint8Array(assertion.rawId))
|
||||
creationData.rawId = Base64.fromUint8Array(new Uint8Array(assertion.rawId));
|
||||
creationData.response = {};
|
||||
creationData.response.attestationObject = Base64.fromUint8Array(new Uint8Array(assertion.response.attestationObject))
|
||||
creationData.response.clientDataJSON = Base64.fromUint8Array(new Uint8Array(assertion.response.clientDataJSON))
|
||||
creationData.type = assertion.type
|
||||
creationData.extensions = assertion.getClientExtensionResults()
|
||||
creationData.extensions.uvm = undefined
|
||||
creationData.response.attestationObject = Base64.fromUint8Array(
|
||||
new Uint8Array(assertion.response.attestationObject),
|
||||
);
|
||||
creationData.response.clientDataJSON = Base64.fromUint8Array(new Uint8Array(assertion.response.clientDataJSON));
|
||||
creationData.type = assertion.type;
|
||||
creationData.extensions = assertion.getClientExtensionResults();
|
||||
creationData.extensions.uvm = undefined;
|
||||
|
||||
// Put the passkey creation data into the form for submission
|
||||
document.getElementById("passkey-create-data").value = JSON.stringify(creationData)
|
||||
document.getElementById("passkey-create-data").value = JSON.stringify(creationData);
|
||||
|
||||
// Make the name input visible and hide the "Begin Passkey Enrollment" button
|
||||
document.getElementById("passkeyNamingSafariPre").classList.add("d-none")
|
||||
document.getElementById("passkeyNamingForm").classList.remove("d-none")
|
||||
document.getElementById("passkeyNamingSubmitBtn").classList.remove("d-none")
|
||||
document.getElementById("passkeyNamingSafariPre").classList.add("d-none");
|
||||
document.getElementById("passkeyNamingForm").classList.remove("d-none");
|
||||
document.getElementById("passkeyNamingSubmitBtn").classList.remove("d-none");
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
if (confirm("Failed to encode your new passkey's data for transmission, confirm to reload this page.\nReport this issue if it keeps occurring.")) {
|
||||
console.log(e);
|
||||
if (
|
||||
confirm(
|
||||
"Failed to encode your new passkey's data for transmission, confirm to reload this page.\nReport this issue if it keeps occurring.",
|
||||
)
|
||||
) {
|
||||
window.location.reload();
|
||||
}
|
||||
}
|
||||
|
@ -82,36 +88,41 @@ function onPasskeyCreated(assertion) {
|
|||
|
||||
function startPasskeyEnrollment() {
|
||||
try {
|
||||
const data_elem = document.getElementById('data');
|
||||
const data_elem = document.getElementById("data");
|
||||
const credentialRequestOptions = JSON.parse(data_elem.textContent);
|
||||
credentialRequestOptions.publicKey.challenge = Base64.toUint8Array(credentialRequestOptions.publicKey.challenge);
|
||||
credentialRequestOptions.publicKey.challenge = Base64.toUint8Array(
|
||||
credentialRequestOptions.publicKey.challenge,
|
||||
);
|
||||
credentialRequestOptions.publicKey.user.id = Base64.toUint8Array(credentialRequestOptions.publicKey.user.id);
|
||||
|
||||
console.log(credentialRequestOptions)
|
||||
navigator.credentials
|
||||
.create({publicKey: credentialRequestOptions.publicKey})
|
||||
.then((assertion) => {
|
||||
console.log(credentialRequestOptions);
|
||||
navigator.credentials.create({ publicKey: credentialRequestOptions.publicKey }).then(
|
||||
(assertion) => {
|
||||
onPasskeyCreated(assertion);
|
||||
}, (reason) => {
|
||||
alert("Passkey creation failed " + reason.toString())
|
||||
console.log("Passkey creation failed: " + reason.toString())
|
||||
});
|
||||
},
|
||||
(reason) => {
|
||||
alert(`Passkey creation failed ${reason.toString()}`);
|
||||
console.log(`Passkey creation failed: ${reason.toString()}`);
|
||||
},
|
||||
);
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
if (confirm("Failed to initialize passkey creation, confirm to reload this page.\nReport this issue if it keeps occurring.")) {
|
||||
console.log(`Failed to initialize passkey creation: ${e}`);
|
||||
if (
|
||||
confirm(
|
||||
"Failed to initialize passkey creation, confirm to reload this page.\nReport this issue if it keeps occurring.",
|
||||
)
|
||||
) {
|
||||
window.location.reload();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setupPasskeyNamingSafariButton() {
|
||||
document.getElementById("passkeyNamingSafariBtn")
|
||||
.addEventListener("click", startPasskeyEnrollment)
|
||||
document.getElementById("passkeyNamingSafariBtn").addEventListener("click", startPasskeyEnrollment);
|
||||
}
|
||||
|
||||
function setupSubmitBtnVisibility() {
|
||||
document.getElementById("passkey-label")
|
||||
?.addEventListener("input", updateSubmitButtonVisibility)
|
||||
document.getElementById("passkey-label")?.addEventListener("input", updateSubmitButtonVisibility);
|
||||
}
|
||||
|
||||
function updateSubmitButtonVisibility(event) {
|
||||
|
@ -119,12 +130,14 @@ function updateSubmitButtonVisibility(event) {
|
|||
submitButton.disabled = event.value === "";
|
||||
}
|
||||
|
||||
(function() {
|
||||
console.debug('credupdate: init');
|
||||
document.body.addEventListener("addPasswordSwapped", () => { setupInteractivePwdFormListeners() });
|
||||
(function () {
|
||||
console.debug("credupdate: init");
|
||||
document.body.addEventListener("addPasswordSwapped", () => {
|
||||
setupInteractivePwdFormListeners();
|
||||
});
|
||||
document.body.addEventListener("addPasskeySwapped", () => {
|
||||
setupPasskeyNamingSafariButton();
|
||||
startPasskeyEnrollment();
|
||||
setupSubmitBtnVisibility();
|
||||
});
|
||||
})()
|
||||
})();
|
||||
|
|
|
@ -1,29 +1,45 @@
|
|||
/**
|
||||
* Initiates the passkey login process by requesting credentials from the user.
|
||||
*
|
||||
* This function retrieves the credential request options from the DOM, converts
|
||||
* necessary fields from Base64 to Uint8Array, and then uses the Web Authentication API
|
||||
* to get the user's credentials. Upon successful retrieval, it encodes the assertion
|
||||
* response back to Base64 and submits the form with the credential data.
|
||||
*
|
||||
* @function asskey_login
|
||||
* @throws {Error} If the passkey authentication process fails.
|
||||
*/
|
||||
|
||||
function asskey_login() {
|
||||
let credentialRequestOptions = JSON.parse(document.getElementById('data').textContent);
|
||||
let credentialRequestOptions = JSON.parse(document.getElementById("data").textContent);
|
||||
credentialRequestOptions.publicKey.challenge = Base64.toUint8Array(credentialRequestOptions.publicKey.challenge);
|
||||
credentialRequestOptions.publicKey.allowCredentials?.forEach(function (listItem) {
|
||||
listItem.id = Base64.toUint8Array(listItem.id)
|
||||
listItem.id = Base64.toUint8Array(listItem.id);
|
||||
});
|
||||
|
||||
navigator.credentials.get({ publicKey: credentialRequestOptions.publicKey })
|
||||
.then((assertion) => {
|
||||
document.getElementById("cred").value = JSON.stringify({
|
||||
id: assertion.id,
|
||||
rawId: Base64.fromUint8Array(new Uint8Array(assertion.rawId), true),
|
||||
type: assertion.type,
|
||||
response: {
|
||||
authenticatorData: Base64.fromUint8Array(new Uint8Array(assertion.response.authenticatorData), true),
|
||||
clientDataJSON: Base64.fromUint8Array(new Uint8Array(assertion.response.clientDataJSON), true),
|
||||
signature: Base64.fromUint8Array(new Uint8Array(assertion.response.signature), true),
|
||||
userHandle: Base64.fromUint8Array(new Uint8Array(assertion.response.userHandle), true)
|
||||
},
|
||||
navigator.credentials
|
||||
.get({ publicKey: credentialRequestOptions.publicKey })
|
||||
.then((assertion) => {
|
||||
document.getElementById("cred").value = JSON.stringify({
|
||||
id: assertion.id,
|
||||
rawId: Base64.fromUint8Array(new Uint8Array(assertion.rawId), true),
|
||||
type: assertion.type,
|
||||
response: {
|
||||
authenticatorData: Base64.fromUint8Array(
|
||||
new Uint8Array(assertion.response.authenticatorData),
|
||||
true,
|
||||
),
|
||||
clientDataJSON: Base64.fromUint8Array(new Uint8Array(assertion.response.clientDataJSON), true),
|
||||
signature: Base64.fromUint8Array(new Uint8Array(assertion.response.signature), true),
|
||||
userHandle: Base64.fromUint8Array(new Uint8Array(assertion.response.userHandle), true),
|
||||
},
|
||||
});
|
||||
document.getElementById("cred-form").submit();
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(`Failed to complete passkey authentication: ${error}`);
|
||||
throw error;
|
||||
});
|
||||
document.getElementById("cred-form").submit();
|
||||
}).catch((error) => {
|
||||
console.error(`Failed to complete passkey authentication: ${error}`);
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
|
@ -31,17 +47,23 @@ try {
|
|||
myButton.addEventListener("click", () => {
|
||||
asskey_login();
|
||||
});
|
||||
} catch (_error) {};
|
||||
} catch (error) {
|
||||
console.error(`Failed to add button event listener for passkey authentication: ${error}`);
|
||||
}
|
||||
|
||||
try {
|
||||
const myButton = document.getElementById("start-seckey-button");
|
||||
myButton.addEventListener("click", () => {
|
||||
asskey_login();
|
||||
});
|
||||
} catch (_error) {};
|
||||
} catch (error) {
|
||||
console.error(`Failed to add button event listener for security key authentication: ${error}`);
|
||||
}
|
||||
|
||||
try {
|
||||
window.addEventListener("load", (event) => {
|
||||
asskey_login()
|
||||
addEventListener("load", () => {
|
||||
asskey_login();
|
||||
});
|
||||
} catch (_error) {};
|
||||
} catch (error) {
|
||||
console.error(`Failed to add load-time event listener for passkey authentication: ${error}`);
|
||||
}
|
||||
|
|
|
@ -30,14 +30,15 @@ body {
|
|||
|
||||
.side-menu-item {
|
||||
--icon-size: 24px;
|
||||
padding: .4rem .7rem;
|
||||
padding: 0.4rem 0.7rem;
|
||||
text-decoration: none;
|
||||
|
||||
&.active {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
&:hover, &.active {
|
||||
&:hover,
|
||||
&.active {
|
||||
background-color: var(--bs-gray-300);
|
||||
}
|
||||
|
||||
|
@ -53,7 +54,6 @@ body {
|
|||
* Personal Settings sidemenu
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
* Navbar
|
||||
*/
|
||||
|
@ -80,7 +80,9 @@ body {
|
|||
margin: auto;
|
||||
border-radius: 15px;
|
||||
background: #21252915;
|
||||
box-shadow: -5px -5px 11px #ededed, 5px 5px 11px #ffffff;
|
||||
box-shadow:
|
||||
-5px -5px 11px #ededed,
|
||||
5px 5px 11px #ffffff;
|
||||
margin: 15px;
|
||||
}
|
||||
|
||||
|
@ -150,9 +152,9 @@ body {
|
|||
}
|
||||
|
||||
.btn-tiny {
|
||||
--bs-btn-padding-y: .05rem;
|
||||
--bs-btn-padding-x: .4rem;
|
||||
--bs-btn-font-size: .75rem;
|
||||
--bs-btn-padding-y: 0.05rem;
|
||||
--bs-btn-padding-x: 0.4rem;
|
||||
--bs-btn-font-size: 0.75rem;
|
||||
}
|
||||
|
||||
#cred-update-commit-bar {
|
||||
|
|
Loading…
Reference in a new issue