From f97685a665116538a6e2d48a487bb367c5d3c64a Mon Sep 17 00:00:00 2001 From: mjoshionemind Date: Wed, 10 Jul 2024 23:11:36 +0530 Subject: [PATCH] Revert "NB-12 remove cyphertext field from graphql and reformat the code" This reverts commit 35d69de31bb39ac30e3a1b775ae20741244976cf. --- netbox_secrets/constants.py | 1 + netbox_secrets/forms/model_forms.py | 2 +- netbox_secrets/graphql/filters.py | 4 +- netbox_secrets/graphql/schema.py | 2 +- netbox_secrets/graphql/types.py | 5 +- netbox_secrets/models/secrets.py | 2 +- netbox_secrets/project-static/.eslintrc | 15 +- netbox_secrets/project-static/bundle.js | 46 +- netbox_secrets/project-static/src/bs.ts | 110 ++--- netbox_secrets/project-static/src/global.d.ts | 22 +- netbox_secrets/project-static/src/index.ts | 6 +- netbox_secrets/project-static/src/secrets.ts | 411 +++++++++--------- netbox_secrets/project-static/src/types.ts | 32 +- netbox_secrets/project-static/src/util.ts | 74 ++-- .../static/netbox_secrets/secrets.js | 334 +------------- .../netbox_secrets/inc/private_key_modal.html | 6 +- .../netbox_secrets/inc/secret_actions.html | 3 +- .../templates/netbox_secrets/secret.html | 136 +++--- .../netbox_secrets/userkey_list.html | 2 +- 19 files changed, 440 insertions(+), 773 deletions(-) diff --git a/netbox_secrets/constants.py b/netbox_secrets/constants.py index b673e83..c57a3f4 100644 --- a/netbox_secrets/constants.py +++ b/netbox_secrets/constants.py @@ -18,6 +18,7 @@ CENSOR_MASTER_KEY = '********' CENSOR_MASTER_KEY_CHANGED = '***CHANGED***' + # # Session Keys # diff --git a/netbox_secrets/forms/model_forms.py b/netbox_secrets/forms/model_forms.py index 87c609b..d98439b 100644 --- a/netbox_secrets/forms/model_forms.py +++ b/netbox_secrets/forms/model_forms.py @@ -120,7 +120,7 @@ class UserKeyForm(forms.ModelForm): ), label='Public Key (PEM format)', help_text='Enter your public RSA key. Keep the private one with you; you will need it for decryption. Please ' - 'note that passphrase-protected keys are not supported.', + 'note that passphrase-protected keys are not supported.', ) class Meta: diff --git a/netbox_secrets/graphql/filters.py b/netbox_secrets/graphql/filters.py index e0dc303..117c54d 100644 --- a/netbox_secrets/graphql/filters.py +++ b/netbox_secrets/graphql/filters.py @@ -1,8 +1,8 @@ import strawberry_django - from netbox.graphql.filter_mixins import autotype_decorator, BaseFilterMixin -from ..filtersets import * + from ..models import * +from ..filtersets import * __all__ = [ 'SecretFilter', diff --git a/netbox_secrets/graphql/schema.py b/netbox_secrets/graphql/schema.py index 2594cf6..952f53e 100644 --- a/netbox_secrets/graphql/schema.py +++ b/netbox_secrets/graphql/schema.py @@ -3,8 +3,8 @@ import strawberry import strawberry_django -from .types import * from ..models import * +from .types import * @strawberry.type diff --git a/netbox_secrets/graphql/types.py b/netbox_secrets/graphql/types.py index 6bd45ac..7ea49f0 100644 --- a/netbox_secrets/graphql/types.py +++ b/netbox_secrets/graphql/types.py @@ -1,11 +1,11 @@ -from typing import Annotated +from typing import Annotated, List import strawberry import strawberry_django from netbox.graphql.types import NetBoxObjectType -from .filters import * from ..models import * +from .filters import * __all__ = [ 'SecretRoleType', @@ -24,4 +24,5 @@ class SecretRoleType(NetBoxObjectType): class SecretType(NetBoxObjectType): role: Annotated['SecretRoleType', strawberry.lazy('netbox_secrets.graphql.types')] name: str + ciphertext: bool description: str diff --git a/netbox_secrets/models/secrets.py b/netbox_secrets/models/secrets.py index f2c453e..75b4594 100644 --- a/netbox_secrets/models/secrets.py +++ b/netbox_secrets/models/secrets.py @@ -346,7 +346,7 @@ def _unpad(self, s): plaintext_length = (ord(s[0]) << 8) + ord(s[1]) else: plaintext_length = (s[0] << 8) + s[1] - return s[2: plaintext_length + 2].decode('utf8') + return s[2 : plaintext_length + 2].decode('utf8') def encrypt(self, secret_key): """ diff --git a/netbox_secrets/project-static/.eslintrc b/netbox_secrets/project-static/.eslintrc index 0afeaef..41d4750 100644 --- a/netbox_secrets/project-static/.eslintrc +++ b/netbox_secrets/project-static/.eslintrc @@ -21,16 +21,10 @@ "arrowFunctions": true } }, - "plugins": [ - "@typescript-eslint", - "prettier" - ], + "plugins": ["@typescript-eslint", "prettier"], "settings": { "import/parsers": { - "@typescript-eslint/parser": [ - ".ts", - ".tsx" - ] + "@typescript-eslint/parser": [".ts", ".tsx"] }, "import/resolver": { "typescript": {} @@ -40,10 +34,7 @@ "@typescript-eslint/no-unused-vars": "off", "no-unused-vars": "off", "no-inner-declarations": "off", - "comma-dangle": [ - "error", - "always-multiline" - ], + "comma-dangle": ["error", "always-multiline"], "global-require": "off", "import/no-dynamic-require": "off", "import/prefer-default-export": "off", diff --git a/netbox_secrets/project-static/bundle.js b/netbox_secrets/project-static/bundle.js index 211477b..0490bb6 100644 --- a/netbox_secrets/project-static/bundle.js +++ b/netbox_secrets/project-static/bundle.js @@ -2,36 +2,36 @@ const esbuild = require('esbuild'); // Bundler options common to all bundle jobs. const options = { - outdir: './dist', - bundle: true, - minify: true, - sourcemap: true, - logLevel: 'error', - publicPath: '/static/netbox_secrets', + outdir: './dist', + bundle: true, + minify: true, + sourcemap: true, + logLevel: 'error', + publicPath: '/static/netbox_secrets', }; /** * Run script bundle jobs. */ async function bundleScripts() { - const entryPoints = { - secrets: 'src/index.ts', - }; - try { - let result = await esbuild.build({ - ...options, - entryPoints, - target: 'es2016', - }); - if (result.errors.length === 0) { - for (const [targetName, sourceName] of Object.entries(entryPoints)) { - const source = sourceName.split('/')[1]; - console.log(`✅ Bundled source file '${source}' to '${targetName}.js'`); - } - } - } catch (err) { - console.error(err); + const entryPoints = { + secrets: 'src/index.ts', + }; + try { + let result = await esbuild.build({ + ...options, + entryPoints, + target: 'es2016', + }); + if (result.errors.length === 0) { + for (const [targetName, sourceName] of Object.entries(entryPoints)) { + const source = sourceName.split('/')[1]; + console.log(`✅ Bundled source file '${source}' to '${targetName}.js'`); + } } + } catch (err) { + console.error(err); + } } bundleScripts(); diff --git a/netbox_secrets/project-static/src/bs.ts b/netbox_secrets/project-static/src/bs.ts index 873a09a..48de832 100644 --- a/netbox_secrets/project-static/src/bs.ts +++ b/netbox_secrets/project-static/src/bs.ts @@ -1,73 +1,73 @@ type ToastLevel = 'danger' | 'warning' | 'success' | 'info'; export function createToast( - level: ToastLevel, - title: string, - message: string, - extra?: string, + level: ToastLevel, + title: string, + message: string, + extra?: string, ): InstanceType { - let iconName = 'mdi-alert'; - switch (level) { - case 'warning': - iconName = 'mdi-alert'; - break; - case 'success': - iconName = 'mdi-check-circle'; - break; - case 'info': - iconName = 'mdi-information'; - break; - case 'danger': - iconName = 'mdi-alert'; - break; - } + let iconName = 'mdi-alert'; + switch (level) { + case 'warning': + iconName = 'mdi-alert'; + break; + case 'success': + iconName = 'mdi-check-circle'; + break; + case 'info': + iconName = 'mdi-information'; + break; + case 'danger': + iconName = 'mdi-alert'; + break; + } - const container = document.createElement('div'); - container.setAttribute('class', 'toast-container position-fixed bottom-0 end-0 m-3'); + const container = document.createElement('div'); + container.setAttribute('class', 'toast-container position-fixed bottom-0 end-0 m-3'); - const main = document.createElement('div'); - main.setAttribute('class', `toast bg-${level}`); - main.setAttribute('role', 'alert'); - main.setAttribute('aria-live', 'assertive'); - main.setAttribute('aria-atomic', 'true'); + const main = document.createElement('div'); + main.setAttribute('class', `toast bg-${level}`); + main.setAttribute('role', 'alert'); + main.setAttribute('aria-live', 'assertive'); + main.setAttribute('aria-atomic', 'true'); - const header = document.createElement('div'); - header.setAttribute('class', `toast-header bg-${level} text-body`); + const header = document.createElement('div'); + header.setAttribute('class', `toast-header bg-${level} text-body`); - const icon = document.createElement('i'); - icon.setAttribute('class', `mdi ${iconName}`); + const icon = document.createElement('i'); + icon.setAttribute('class', `mdi ${iconName}`); - const titleElement = document.createElement('strong'); - titleElement.setAttribute('class', 'me-auto ms-1'); - titleElement.innerText = title; + const titleElement = document.createElement('strong'); + titleElement.setAttribute('class', 'me-auto ms-1'); + titleElement.innerText = title; - const button = document.createElement('button'); - button.setAttribute('type', 'button'); - button.setAttribute('class', 'btn-close'); - button.setAttribute('data-bs-dismiss', 'toast'); - button.setAttribute('aria-label', 'Close'); + const button = document.createElement('button'); + button.setAttribute('type', 'button'); + button.setAttribute('class', 'btn-close'); + button.setAttribute('data-bs-dismiss', 'toast'); + button.setAttribute('aria-label', 'Close'); - const body = document.createElement('div'); - body.setAttribute('class', 'toast-body'); + const body = document.createElement('div'); + body.setAttribute('class', 'toast-body'); - header.appendChild(icon); - header.appendChild(titleElement); + header.appendChild(icon); + header.appendChild(titleElement); - if (typeof extra !== 'undefined') { - const extraElement = document.createElement('small'); - extraElement.setAttribute('class', 'text-muted'); - header.appendChild(extraElement); - } + if (typeof extra !== 'undefined') { + const extraElement = document.createElement('small'); + extraElement.setAttribute('class', 'text-muted'); + header.appendChild(extraElement); + } - header.appendChild(button); + header.appendChild(button); - body.innerText = message.trim(); + body.innerText = message.trim(); - main.appendChild(header); - main.appendChild(body); - container.appendChild(main); - document.body.appendChild(container); + main.appendChild(header); + main.appendChild(body); + container.appendChild(main); + document.body.appendChild(container); - const toast = new window.Toast(main); - return toast; + const toast = new window.Toast(main); + return toast; } diff --git a/netbox_secrets/project-static/src/global.d.ts b/netbox_secrets/project-static/src/global.d.ts index 9e0ce12..4b1c5db 100644 --- a/netbox_secrets/project-static/src/global.d.ts +++ b/netbox_secrets/project-static/src/global.d.ts @@ -8,30 +8,30 @@ type JSONAble = Primitives | Primitives[] | { [k: string]: JSONAble } | JSONAble * Base NetBox API Error. */ type ErrorBase = { - error: string; + error: string; }; /** * NetBox API error with details. */ type APIError = { - exception: string; - netbox_version: string; - python_version: string; + exception: string; + netbox_version: string; + python_version: string; } & ErrorBase; /** * NetBox API Object. */ type APIObjectBase = { - id: number; - display: string; - name?: string | null; - url: string; - [k: string]: JSONAble; + id: number; + display: string; + name?: string | null; + url: string; + [k: string]: JSONAble; }; interface Window { - Modal: typeof import('bootstrap').Modal; - Toast: typeof import('bootstrap').Toast; + Modal: typeof import('bootstrap').Modal; + Toast: typeof import('bootstrap').Toast; } diff --git a/netbox_secrets/project-static/src/index.ts b/netbox_secrets/project-static/src/index.ts index 333fa11..1b88ece 100644 --- a/netbox_secrets/project-static/src/index.ts +++ b/netbox_secrets/project-static/src/index.ts @@ -1,7 +1,7 @@ -import {initSecrets} from './secrets'; +import { initSecrets } from './secrets'; if (document.readyState !== 'loading') { - initSecrets(); + initSecrets(); } else { - document.addEventListener('DOMContentLoaded', initSecrets); + document.addEventListener('DOMContentLoaded', initSecrets); } diff --git a/netbox_secrets/project-static/src/secrets.ts b/netbox_secrets/project-static/src/secrets.ts index 1d3026a..c206ed2 100644 --- a/netbox_secrets/project-static/src/secrets.ts +++ b/netbox_secrets/project-static/src/secrets.ts @@ -1,86 +1,86 @@ -import {createToast} from './bs'; -import {apiGetBase, apiPostForm, hasError, isApiError, isInputElement} from './util'; +import { createToast } from './bs'; +import { apiGetBase, apiPostForm, hasError, isApiError, isInputElement } from './util'; -import type {APIKeyPair, APISecret} from './types'; +import type { APIKeyPair, APISecret } from './types'; /** * Initialize Generate Private Key Pair Elements. */ function initGenerateKeyPair() { - const element = document.getElementById('new_keypair_modal') as HTMLDivElement; - const accept = document.getElementById('use_new_pubkey') as HTMLButtonElement; - const copyBtn = document.getElementById('copy_prikey') as HTMLButtonElement; - const exportBtn = document.getElementById('export_key') as HTMLButtonElement; - // If the elements are not loaded, stop. - if (element === null || accept === null || copyBtn === null || exportBtn === null) { - return; - } - const publicElem = element.querySelector('textarea#new_pubkey'); - const privateElem = element.querySelector('textarea#new_privkey'); + const element = document.getElementById('new_keypair_modal') as HTMLDivElement; + const accept = document.getElementById('use_new_pubkey') as HTMLButtonElement; + const copyBtn = document.getElementById('copy_prikey') as HTMLButtonElement; + const exportBtn = document.getElementById('export_key') as HTMLButtonElement; + // If the elements are not loaded, stop. + if (element === null || accept === null || copyBtn === null || exportBtn === null) { + return; + } + const publicElem = element.querySelector('textarea#new_pubkey'); + const privateElem = element.querySelector('textarea#new_privkey'); - /** - * Handle Generate Private Key Pair Modal opening. - */ - function handleOpen() { - // When the modal opens, set the `readonly` attribute on the textarea elements. - for (const elem of [publicElem, privateElem]) { - if (elem !== null) { - elem.setAttribute('readonly', ''); - } - } - // Fetch the key pair from the API. - apiGetBase('/api/plugins/secrets/generate-rsa-key-pair/').then(data => { - if (!hasError(data)) { - // If key pair generation was successful, set the textarea elements' value to the generated - // values. - const {private_key: priv, public_key: pub} = data; - if (publicElem !== null && privateElem !== null) { - publicElem.value = pub; - privateElem.value = priv; - } - } else { - // Otherwise, show an error. - const toast = createToast('danger', 'Error', data.error); - toast.show(); - } - }); + /** + * Handle Generate Private Key Pair Modal opening. + */ + function handleOpen() { + // When the modal opens, set the `readonly` attribute on the textarea elements. + for (const elem of [publicElem, privateElem]) { + if (elem !== null) { + elem.setAttribute('readonly', ''); + } } - - /** - * Set the public key form field's value to the generated public key. - */ - function handleAccept() { - const publicKeyField = document.getElementById('id_public_key') as HTMLTextAreaElement; - if (publicElem !== null) { - publicKeyField.value = publicElem.value; - publicKeyField.innerText = publicElem.value; + // Fetch the key pair from the API. + apiGetBase('/api/plugins/secrets/generate-rsa-key-pair/').then(data => { + if (!hasError(data)) { + // If key pair generation was successful, set the textarea elements' value to the generated + // values. + const { private_key: priv, public_key: pub } = data; + if (publicElem !== null && privateElem !== null) { + publicElem.value = pub; + privateElem.value = priv; } + } else { + // Otherwise, show an error. + const toast = createToast('danger', 'Error', data.error); + toast.show(); + } + }); + } + + /** + * Set the public key form field's value to the generated public key. + */ + function handleAccept() { + const publicKeyField = document.getElementById('id_public_key') as HTMLTextAreaElement; + if (publicElem !== null) { + publicKeyField.value = publicElem.value; + publicKeyField.innerText = publicElem.value; } + } - /** - * Handles file download functionality. - */ - function handleExport() { - const content = `Public Key\n\n${publicElem?.value}\n\nPrivate Key\n\n${privateElem?.value}`; + /** + * Handles file download functionality. + */ + function handleExport() { + const content = `Public Key\n\n${publicElem?.value}\n\nPrivate Key\n\n${privateElem?.value}`; - const blob = new Blob([content], {type: 'text/plain'}); + const blob = new Blob([content], { type: 'text/plain' }); - const a = document.createElement('a'); - a.style.display = 'none'; - a.href = window.URL.createObjectURL(blob); - a.download = 'key.txt'; - document.body.appendChild(a); + const a = document.createElement('a'); + a.style.display = 'none'; + a.href = window.URL.createObjectURL(blob); + a.download = 'key.txt'; + document.body.appendChild(a); - a.click(); + a.click(); - window.URL.revokeObjectURL(a.href); - document.body.removeChild(a); - } + window.URL.revokeObjectURL(a.href); + document.body.removeChild(a); + } - element.addEventListener('shown.bs.modal', () => handleOpen()); - accept.addEventListener('click', () => handleAccept()); - copyBtn.addEventListener('click', () => navigator.clipboard.writeText(privateElem?.value || '')); - exportBtn.addEventListener('click', () => handleExport()); + element.addEventListener('shown.bs.modal', () => handleOpen()); + accept.addEventListener('click', () => handleAccept()); + copyBtn.addEventListener('click', () => navigator.clipboard.writeText(privateElem?.value || '')); + exportBtn.addEventListener('click', () => handleExport()); } /** @@ -89,104 +89,104 @@ function initGenerateKeyPair() { * @param action Lock or Unlock, so we know which buttons to display. */ function toggleSecretButtons(id: string, action: 'lock' | 'unlock') { - const unlockButton = document.querySelector(`button.unlock-secret[secret-id='${id}']`); - const lockButton = document.querySelector(`button.lock-secret[secret-id='${id}']`); - const copyButton = document.querySelector(`span[secret-id='${id}']`); - // If we're unlocking, hide the unlock button. Otherwise, show it. - if (unlockButton !== null) { - if (action === 'unlock') unlockButton.classList.add('d-none'); - if (action === 'lock') unlockButton.classList.remove('d-none'); - } - // If we're unlocking, show the lock button. Otherwise, hide it. - if (lockButton !== null) { - if (action === 'unlock') lockButton.classList.remove('d-none'); - if (action === 'lock') lockButton.classList.add('d-none'); - } - // If we're unlocking, show the copy button. Otherwise, hide it. - if (copyButton !== null) { - if (action === 'unlock') copyButton.classList.remove('d-none'); - if (action === 'lock') copyButton.classList.add('d-none'); - } + const unlockButton = document.querySelector(`button.unlock-secret[secret-id='${id}']`); + const lockButton = document.querySelector(`button.lock-secret[secret-id='${id}']`); + const copyButton = document.querySelector(`span[secret-id='${id}']`); + // If we're unlocking, hide the unlock button. Otherwise, show it. + if (unlockButton !== null) { + if (action === 'unlock') unlockButton.classList.add('d-none'); + if (action === 'lock') unlockButton.classList.remove('d-none'); + } + // If we're unlocking, show the lock button. Otherwise, hide it. + if (lockButton !== null) { + if (action === 'unlock') lockButton.classList.remove('d-none'); + if (action === 'lock') lockButton.classList.add('d-none'); + } + // If we're unlocking, show the copy button. Otherwise, hide it. + if (copyButton !== null) { + if (action === 'unlock') copyButton.classList.remove('d-none'); + if (action === 'lock') copyButton.classList.add('d-none'); + } } /** * Initialize Lock & Unlock button event listeners & callbacks. */ function initLockUnlock() { - const privateKeyModal = new window.Modal('#privkey_modal'); - - /** - * Unlock a secret, or prompt the user for their private key, if a session key is not available. - * - * @param id Secret ID - */ - function unlock(id: string | null) { - const target = document.getElementById(`secret_${id}`) as HTMLDivElement | HTMLInputElement; - if (typeof id === 'string' && id !== '') { - apiGetBase(`/api/plugins/secrets/secrets/${id}/`).then(data => { - if (!hasError(data)) { - const {plaintext} = data; - // `plaintext` is the plain text value of the secret. If it is null, it has not been - // decrypted, likely due to a mission session key. + const privateKeyModal = new window.Modal('#privkey_modal'); - if (target !== null && plaintext !== null) { - // If `plaintext` is not null, we have the decrypted value. Set the target element's - // inner text to the decrypted value and toggle copy/lock button visibility. - if (isInputElement(target)) { - target.value = plaintext; - } else { - target.innerText = plaintext; - } - - toggleSecretButtons(id, 'unlock'); - } else { - // Otherwise, we do _not_ have the decrypted value and need to prompt the user for - // their private RSA key, in order to get a session key. The session key is then sent - // as a cookie in future requests. - privateKeyModal.show(); - } - } else { - if (data.error.toLowerCase().includes('invalid session key')) { - // If, for some reason, a request was made but resulted in an API error that complains - // of a missing session key, prompt the user for their session key. - privateKeyModal.show(); - } else { - // If we received an API error but it doesn't contain 'invalid session key', show the - // user an error message. - const toast = createToast('danger', 'Error', data.error); - toast.show(); - } - } - }); - } - } - - /** - * Lock a secret and toggle visibility of the unlock button. - * @param id Secret ID - */ - function lock(id: string | null) { - if (typeof id === 'string' && id !== '') { - const target = document.getElementById(`secret_${id}`) as HTMLDivElement | HTMLInputElement; + /** + * Unlock a secret, or prompt the user for their private key, if a session key is not available. + * + * @param id Secret ID + */ + function unlock(id: string | null) { + const target = document.getElementById(`secret_${id}`) as HTMLDivElement | HTMLInputElement; + if (typeof id === 'string' && id !== '') { + apiGetBase(`/api/plugins/secrets/secrets/${id}/`).then(data => { + if (!hasError(data)) { + const { plaintext } = data; + // `plaintext` is the plain text value of the secret. If it is null, it has not been + // decrypted, likely due to a mission session key. - // Obscure the inner text of the secret element. + if (target !== null && plaintext !== null) { + // If `plaintext` is not null, we have the decrypted value. Set the target element's + // inner text to the decrypted value and toggle copy/lock button visibility. if (isInputElement(target)) { - target.value = '********'; + target.value = plaintext; } else { - target.innerText = '********'; + target.innerText = plaintext; } - // Toggle visibility of the copy/lock/unlock buttons. - toggleSecretButtons(id, 'lock'); + toggleSecretButtons(id, 'unlock'); + } else { + // Otherwise, we do _not_ have the decrypted value and need to prompt the user for + // their private RSA key, in order to get a session key. The session key is then sent + // as a cookie in future requests. + privateKeyModal.show(); + } + } else { + if (data.error.toLowerCase().includes('invalid session key')) { + // If, for some reason, a request was made but resulted in an API error that complains + // of a missing session key, prompt the user for their session key. + privateKeyModal.show(); + } else { + // If we received an API error but it doesn't contain 'invalid session key', show the + // user an error message. + const toast = createToast('danger', 'Error', data.error); + toast.show(); + } } + }); } + } - for (const element of document.querySelectorAll('button.unlock-secret')) { - element.addEventListener('click', () => unlock(element.getAttribute('secret-id'))); - } - for (const element of document.querySelectorAll('button.lock-secret')) { - element.addEventListener('click', () => lock(element.getAttribute('secret-id'))); + /** + * Lock a secret and toggle visibility of the unlock button. + * @param id Secret ID + */ + function lock(id: string | null) { + if (typeof id === 'string' && id !== '') { + const target = document.getElementById(`secret_${id}`) as HTMLDivElement | HTMLInputElement; + + // Obscure the inner text of the secret element. + if (isInputElement(target)) { + target.value = '********'; + } else { + target.innerText = '********'; + } + + // Toggle visibility of the copy/lock/unlock buttons. + toggleSecretButtons(id, 'lock'); } + } + + for (const element of document.querySelectorAll('button.unlock-secret')) { + element.addEventListener('click', () => unlock(element.getAttribute('secret-id'))); + } + for (const element of document.querySelectorAll('button.lock-secret')) { + element.addEventListener('click', () => lock(element.getAttribute('secret-id'))); + } } /** @@ -194,81 +194,80 @@ function initLockUnlock() { * @param privateKey RSA Private Key (valid JSON string) */ function requestSessionKey(privateKey: string) { - apiPostForm('/api/plugins/secrets/session-keys/', { - private_key: privateKey, - preserve_key: true, - }).then(res => { - if (!hasError(res)) { - // If the session key has been added from the user key page, reload the page. - if (window.location.pathname === '/plugins/secrets/user-key/') { - window.location.reload(); - } else { - // If the response received was not an error, show the user a success message. - const toast = createToast('success', 'Session Key Received', 'You may now unlock secrets.'); - toast.show(); - } - } else { - // Otherwise, show the user an error message. - let message = res.error; - if (isApiError(res)) { - // If the error received was a standard API error containing a Python exception message, - // append it to the error. - message += `\n${res.exception}`; - } - const toast = createToast('danger', 'Failed to Retrieve Session Key', message); - toast.show(); - } - }); + apiPostForm('/api/plugins/secrets/session-keys/', { + private_key: privateKey, + preserve_key: true, + }).then(res => { + if (!hasError(res)) { + // If the session key has been added from the user key page, reload the page. + if (window.location.pathname === '/plugins/secrets/user-key/') { + window.location.reload(); + } else { + // If the response received was not an error, show the user a success message. + const toast = createToast('success', 'Session Key Received', 'You may now unlock secrets.'); + toast.show(); + } + } else { + // Otherwise, show the user an error message. + let message = res.error; + if (isApiError(res)) { + // If the error received was a standard API error containing a Python exception message, + // append it to the error. + message += `\n${res.exception}`; + } + const toast = createToast('danger', 'Failed to Retrieve Session Key', message); + toast.show(); + } + }); } /** * Initialize Request Session Key Elements. */ function initGetSessionKey() { - for (const element of document.querySelectorAll('#request_session_key')) { - /** - * Send the user's input private key to the API to get a session key, which will be stored as - * a cookie for future requests. - */ - function handleClick() { - for (const pk of document.querySelectorAll('#user_privkey')) { - requestSessionKey(pk.value); - // Clear the private key form field value. - pk.value = ''; - } - } - - element.addEventListener('click', handleClick); + for (const element of document.querySelectorAll('#request_session_key')) { + /** + * Send the user's input private key to the API to get a session key, which will be stored as + * a cookie for future requests. + */ + function handleClick() { + for (const pk of document.querySelectorAll('#user_privkey')) { + requestSessionKey(pk.value); + // Clear the private key form field value. + pk.value = ''; + } } + element.addEventListener('click', handleClick); + } } /** * Initialize Secret Edit Form Handler. */ function initSecretsEdit() { - const privateKeyModal = new window.Modal('#privkey_modal'); + const privateKeyModal = new window.Modal('#privkey_modal'); - /** - * Check the cookie store for a `netbox_secrets_sessionid`. If not present, prompt the user to submit their - * private key. - */ - function handleSubmit(event: Event): void { - if (document.cookie.indexOf('netbox_secrets_sessionid') === -1) { - event.preventDefault(); - privateKeyModal.show(); - } + /** + * Check the cookie store for a `netbox_secrets_sessionid`. If not present, prompt the user to submit their + * private key. + */ + function handleSubmit(event: Event): void { + if (document.cookie.indexOf('netbox_secrets_sessionid') === -1) { + event.preventDefault(); + privateKeyModal.show(); } + } - for (const element of document.querySelectorAll('.requires-session-key')) { - const form = element.closest('form'); - if (form !== null) { - form.addEventListener('submit', handleSubmit); - } + for (const element of document.querySelectorAll('.requires-session-key')) { + const form = element.closest('form'); + if (form !== null) { + form.addEventListener('submit', handleSubmit); } + } } export function initSecrets() { - for (const func of [initGenerateKeyPair, initLockUnlock, initGetSessionKey, initSecretsEdit]) { - func(); - } + for (const func of [initGenerateKeyPair, initLockUnlock, initGetSessionKey, initSecretsEdit]) { + func(); + } } diff --git a/netbox_secrets/project-static/src/types.ts b/netbox_secrets/project-static/src/types.ts index fbbf5a0..37e0c6a 100644 --- a/netbox_secrets/project-static/src/types.ts +++ b/netbox_secrets/project-static/src/types.ts @@ -1,21 +1,21 @@ export type APIKeyPair = { - public_key: string; - private_key: string; + public_key: string; + private_key: string; }; export type APISecret = { - assigned_object: APIObjectBase; - assigned_object_id: number; - assigned_object_type: string; - created: string; - custom_fields: Record; - display: string; - hash: string; - id: number; - last_updated: string; - name: string; - plaintext: string | null; - role: APIObjectBase; - tags: number[]; - url: string; + assigned_object: APIObjectBase; + assigned_object_id: number; + assigned_object_type: string; + created: string; + custom_fields: Record; + display: string; + hash: string; + id: number; + last_updated: string; + name: string; + plaintext: string | null; + role: APIObjectBase; + tags: number[]; + url: string; }; diff --git a/netbox_secrets/project-static/src/util.ts b/netbox_secrets/project-static/src/util.ts index 6ea67cd..ba9c782 100644 --- a/netbox_secrets/project-static/src/util.ts +++ b/netbox_secrets/project-static/src/util.ts @@ -11,7 +11,7 @@ type ReqData = URLSearchParams | Dict | undefined | unknown; * @returns Type guard for `data`. */ export function isApiError(data: Record): data is APIError { - return 'error' in data && 'exception' in data; + return 'error' in data && 'exception' in data; } /** @@ -21,7 +21,7 @@ export function isApiError(data: Record): data is APIError { * @returns Type guard for `data`. */ export function hasError(data: Record): data is ErrorBase { - return 'error' in data; + return 'error' in data; } /** @@ -30,18 +30,18 @@ export function hasError(data: Record): data is ErrorBase { * @param element HTML Element. */ export function isInputElement(element: HTMLElement): element is HTMLInputElement { - return 'value' in element && 'required' in element; + return 'value' in element && 'required' in element; } /** * Retrieve the CSRF token from cookie storage. */ export function getCsrfToken(): string { - const {csrftoken: csrfToken} = Cookie.parse(document.cookie); - if (typeof csrfToken === 'undefined') { - throw new Error('Invalid or missing CSRF token'); - } - return csrfToken; + const { csrftoken: csrfToken } = Cookie.parse(document.cookie); + if (typeof csrfToken === 'undefined') { + throw new Error('Invalid or missing CSRF token'); + } + return csrfToken; } /** @@ -53,34 +53,34 @@ export function getCsrfToken(): string { * @returns JSON Response */ export async function apiRequest( - url: string, - method: Method, - data?: D, + url: string, + method: Method, + data?: D, ): Promise> { - const token = getCsrfToken(); - const headers = new Headers({'X-CSRFToken': token}); + const token = getCsrfToken(); + const headers = new Headers({ 'X-CSRFToken': token }); - let body; - if (typeof data !== 'undefined') { - body = JSON.stringify(data); - headers.set('content-type', 'application/json'); - headers.set('Accept', 'application/json'); - } + let body; + if (typeof data !== 'undefined') { + body = JSON.stringify(data); + headers.set('content-type', 'application/json'); + headers.set('Accept', 'application/json'); + } - const res = await fetch(url, {method, body, headers, credentials: 'same-origin'}); - const contentType = res.headers.get('Content-Type'); - if (typeof contentType === 'string' && contentType.includes('text')) { - const error = await res.text(); - return {error} as ErrorBase; - } - const json = (await res.json()) as R | APIError; - if (!res.ok && Array.isArray(json)) { - const error = json.join('\n'); - return {error} as ErrorBase; - } else if (!res.ok && 'detail' in json) { - return {error: json.detail} as ErrorBase; - } - return json; + const res = await fetch(url, { method, body, headers, credentials: 'same-origin' }); + const contentType = res.headers.get('Content-Type'); + if (typeof contentType === 'string' && contentType.includes('text')) { + const error = await res.text(); + return { error } as ErrorBase; + } + const json = (await res.json()) as R | APIError; + if (!res.ok && Array.isArray(json)) { + const error = json.join('\n'); + return { error } as ErrorBase; + } else if (!res.ok && 'detail' in json) { + return { error: json.detail } as ErrorBase; + } + return json; } /** @@ -91,10 +91,10 @@ export async function apiRequest( * @returns JSON Response */ export async function apiPostForm( - url: string, - data: D, + url: string, + data: D, ): Promise> { - return await apiRequest(url, 'POST', data); + return await apiRequest(url, 'POST', data); } /** @@ -104,5 +104,5 @@ export async function apiPostForm( * @returns JSON Response */ export async function apiGetBase(url: string): Promise> { - return await apiRequest(url, 'GET'); + return await apiRequest(url, 'GET'); } diff --git a/netbox_secrets/static/netbox_secrets/secrets.js b/netbox_secrets/static/netbox_secrets/secrets.js index 71033ba..1c5540c 100644 --- a/netbox_secrets/static/netbox_secrets/secrets.js +++ b/netbox_secrets/static/netbox_secrets/secrets.js @@ -1,336 +1,12 @@ -"use strict"; -(() => { - var P = Object.create; - var g = Object.defineProperty; - var R = Object.getOwnPropertyDescriptor; - var M = Object.getOwnPropertyNames; - var B = Object.getPrototypeOf, C = Object.prototype.hasOwnProperty; - var _ = (e, n) => () => (n || e((n = {exports: {}}).exports, n), n.exports); - var D = (e, n, i, t) => { - if (n && typeof n == "object" || typeof n == "function") for (let o of M(n)) !C.call(e, o) && o !== i && g(e, o, { - get: () => n[o], - enumerable: !(t = R(n, o)) || t.enumerable - }); - return e - }; - var H = (e, n, i) => (i = e != null ? P(B(e)) : {}, D(n || !e || !e.__esModule ? g(i, "default", { - value: e, - enumerable: !0 - }) : i, e)); - var m = (e, n, i) => new Promise((t, o) => { - var s = l => { - try { - c(i.next(l)) - } catch (a) { - o(a) - } - }, r = l => { - try { - c(i.throw(l)) - } catch (a) { - o(a) - } - }, c = l => l.done ? t(l.value) : Promise.resolve(l.value).then(s, r); - c((i = i.apply(e, n)).next()) - }); - var h = _(E => { - "use strict"; - E.parse = O; - E.serialize = q; - var I = Object.prototype.toString, f = /^[\u0009\u0020-\u007e\u0080-\u00ff]+$/; +"use strict";(()=>{var P=Object.create;var g=Object.defineProperty;var R=Object.getOwnPropertyDescriptor;var M=Object.getOwnPropertyNames;var B=Object.getPrototypeOf,C=Object.prototype.hasOwnProperty;var _=(e,n)=>()=>(n||e((n={exports:{}}).exports,n),n.exports);var D=(e,n,i,t)=>{if(n&&typeof n=="object"||typeof n=="function")for(let o of M(n))!C.call(e,o)&&o!==i&&g(e,o,{get:()=>n[o],enumerable:!(t=R(n,o))||t.enumerable});return e};var H=(e,n,i)=>(i=e!=null?P(B(e)):{},D(n||!e||!e.__esModule?g(i,"default",{value:e,enumerable:!0}):i,e));var m=(e,n,i)=>new Promise((t,o)=>{var s=l=>{try{c(i.next(l))}catch(a){o(a)}},r=l=>{try{c(i.throw(l))}catch(a){o(a)}},c=l=>l.done?t(l.value):Promise.resolve(l.value).then(s,r);c((i=i.apply(e,n)).next())});var h=_(E=>{"use strict";E.parse=O;E.serialize=q;var I=Object.prototype.toString,f=/^[\u0009\u0020-\u007e\u0080-\u00ff]+$/;function O(e,n){if(typeof e!="string")throw new TypeError("argument str must be a string");for(var i={},t=n||{},o=t.decode||$,s=0;s{if(y(a))p("danger","Error",a.error).show();else{let{private_key:d,public_key:u}=a;o!==null&&s!==null&&(o.value=u,s.value=d)}})}function c(){let a=document.getElementById("id_public_key");o!==null&&(a.value=o.value,a.innerText=o.value)}function l(){let a=`Public Key - function O(e, n) { - if (typeof e != "string") throw new TypeError("argument str must be a string"); - for (var i = {}, t = n || {}, o = t.decode || $, s = 0; s < e.length;) { - var r = e.indexOf("=", s); - if (r === -1) break; - var c = e.indexOf(";", s); - if (c === -1) c = e.length; else if (c < r) { - s = e.lastIndexOf(";", r - 1) + 1; - continue - } - var l = e.slice(s, r).trim(); - if (i[l] === void 0) { - var a = e.slice(r + 1, c).trim(); - a.charCodeAt(0) === 34 && (a = a.slice(1, -1)), i[l] = U(a, o) - } - s = c + 1 - } - return i - } - - function q(e, n, i) { - var t = i || {}, o = t.encode || K; - if (typeof o != "function") throw new TypeError("option encode is invalid"); - if (!f.test(e)) throw new TypeError("argument name is invalid"); - var s = o(n); - if (s && !f.test(s)) throw new TypeError("argument val is invalid"); - var r = e + "=" + s; - if (t.maxAge != null) { - var c = t.maxAge - 0; - if (isNaN(c) || !isFinite(c)) throw new TypeError("option maxAge is invalid"); - r += "; Max-Age=" + Math.floor(c) - } - if (t.domain) { - if (!f.test(t.domain)) throw new TypeError("option domain is invalid"); - r += "; Domain=" + t.domain - } - if (t.path) { - if (!f.test(t.path)) throw new TypeError("option path is invalid"); - r += "; Path=" + t.path - } - if (t.expires) { - var l = t.expires; - if (!j(l) || isNaN(l.valueOf())) throw new TypeError("option expires is invalid"); - r += "; Expires=" + l.toUTCString() - } - if (t.httpOnly && (r += "; HttpOnly"), t.secure && (r += "; Secure"), t.priority) { - var a = typeof t.priority == "string" ? t.priority.toLowerCase() : t.priority; - switch (a) { - case"low": - r += "; Priority=Low"; - break; - case"medium": - r += "; Priority=Medium"; - break; - case"high": - r += "; Priority=High"; - break; - default: - throw new TypeError("option priority is invalid") - } - } - if (t.sameSite) { - var d = typeof t.sameSite == "string" ? t.sameSite.toLowerCase() : t.sameSite; - switch (d) { - case!0: - r += "; SameSite=Strict"; - break; - case"lax": - r += "; SameSite=Lax"; - break; - case"strict": - r += "; SameSite=Strict"; - break; - case"none": - r += "; SameSite=None"; - break; - default: - throw new TypeError("option sameSite is invalid") - } - } - return r - } - - function $(e) { - return e.indexOf("%") !== -1 ? decodeURIComponent(e) : e - } - - function K(e) { - return encodeURIComponent(e) - } - - function j(e) { - return I.call(e) === "[object Date]" || e instanceof Date - } - - function U(e, n) { - try { - return n(e) - } catch (i) { - return e - } - } - }); - - function p(e, n, i, t) { - let o = "mdi-alert"; - switch (e) { - case"warning": - o = "mdi-alert"; - break; - case"success": - o = "mdi-check-circle"; - break; - case"info": - o = "mdi-information"; - break; - case"danger": - o = "mdi-alert"; - break - } - let s = document.createElement("div"); - s.setAttribute("class", "toast-container position-fixed bottom-0 end-0 m-3"); - let r = document.createElement("div"); - r.setAttribute("class", `toast bg-${e}`), r.setAttribute("role", "alert"), r.setAttribute("aria-live", "assertive"), r.setAttribute("aria-atomic", "true"); - let c = document.createElement("div"); - c.setAttribute("class", `toast-header bg-${e} text-body`); - let l = document.createElement("i"); - l.setAttribute("class", `mdi ${o}`); - let a = document.createElement("strong"); - a.setAttribute("class", "me-auto ms-1"), a.innerText = n; - let d = document.createElement("button"); - d.setAttribute("type", "button"), d.setAttribute("class", "btn-close"), d.setAttribute("data-bs-dismiss", "toast"), d.setAttribute("aria-label", "Close"); - let u = document.createElement("div"); - if (u.setAttribute("class", "toast-body"), c.appendChild(l), c.appendChild(a), typeof t != "undefined") { - let b = document.createElement("small"); - b.setAttribute("class", "text-muted"), c.appendChild(b) - } - return c.appendChild(d), u.innerText = i.trim(), r.appendChild(c), r.appendChild(u), s.appendChild(r), document.body.appendChild(s), new window.Toast(r) - } - - var T = H(h()); - - function L(e) { - return "error" in e && "exception" in e - } - - function y(e) { - return "error" in e - } - - function v(e) { - return "value" in e && "required" in e - } - - function F() { - let {csrftoken: e} = T.default.parse(document.cookie); - if (typeof e == "undefined") throw new Error("Invalid or missing CSRF token"); - return e - } - - function x(e, n, i) { - return m(this, null, function* () { - let t = F(), o = new Headers({"X-CSRFToken": t}), s; - typeof i != "undefined" && (s = JSON.stringify(i), o.set("content-type", "application/json"), o.set("Accept", "application/json")); - let r = yield fetch(e, {method: n, body: s, headers: o, credentials: "same-origin"}), - c = r.headers.get("Content-Type"); - if (typeof c == "string" && c.includes("text")) return {error: yield r.text()}; - let l = yield r.json(); - return !r.ok && Array.isArray(l) ? { - error: l.join(` -`) - } : !r.ok && "detail" in l ? {error: l.detail} : l - }) - } - - function A(e, n) { - return m(this, null, function* () { - return yield x(e, "POST", n) - }) - } - - function k(e) { - return m(this, null, function* () { - return yield x(e, "GET") - }) - } - - function N() { - let e = document.getElementById("new_keypair_modal"), n = document.getElementById("use_new_pubkey"), - i = document.getElementById("copy_prikey"), t = document.getElementById("export_key"); - if (e === null || n === null || i === null || t === null) return; - let o = e.querySelector("textarea#new_pubkey"), s = e.querySelector("textarea#new_privkey"); - - function r() { - for (let a of [o, s]) a !== null && a.setAttribute("readonly", ""); - k("/api/plugins/secrets/generate-rsa-key-pair/").then(a => { - if (y(a)) p("danger", "Error", a.error).show(); else { - let {private_key: d, public_key: u} = a; - o !== null && s !== null && (o.value = u, s.value = d) - } - }) - } - - function c() { - let a = document.getElementById("id_public_key"); - o !== null && (a.value = o.value, a.innerText = o.value) - } - - function l() { - let a = `Public Key - -${o == null ? void 0 : o.value} +${o==null?void 0:o.value} Private Key -${s == null ? void 0 : s.value}`, d = new Blob([a], {type: "text/plain"}), u = document.createElement("a"); - u.style.display = "none", u.href = window.URL.createObjectURL(d), u.download = "key.txt", document.body.appendChild(u), u.click(), window.URL.revokeObjectURL(u.href), document.body.removeChild(u) - } - - e.addEventListener("shown.bs.modal", () => r()), n.addEventListener("click", () => c()), i.addEventListener("click", () => navigator.clipboard.writeText((s == null ? void 0 : s.value) || "")), t.addEventListener("click", () => l()) - } - - function S(e, n) { - let i = document.querySelector(`button.unlock-secret[secret-id='${e}']`), - t = document.querySelector(`button.lock-secret[secret-id='${e}']`), - o = document.querySelector(`span[secret-id='${e}']`); - i !== null && (n === "unlock" && i.classList.add("d-none"), n === "lock" && i.classList.remove("d-none")), t !== null && (n === "unlock" && t.classList.remove("d-none"), n === "lock" && t.classList.add("d-none")), o !== null && (n === "unlock" && o.classList.remove("d-none"), n === "lock" && o.classList.add("d-none")) - } - - function G() { - let e = new window.Modal("#privkey_modal"); - - function n(t) { - let o = document.getElementById(`secret_${t}`); - typeof t == "string" && t !== "" && k(`/api/plugins/secrets/secrets/${t}/`).then(s => { - if (y(s)) s.error.toLowerCase().includes("invalid session key") ? e.show() : p("danger", "Error", s.error).show(); else { - let {plaintext: r} = s; - o !== null && r !== null ? (v(o) ? o.value = r : o.innerText = r, S(t, "unlock")) : e.show() - } - }) - } - - function i(t) { - if (typeof t == "string" && t !== "") { - let o = document.getElementById(`secret_${t}`); - v(o) ? o.value = "********" : o.innerText = "********", S(t, "lock") - } - } - - for (let t of document.querySelectorAll("button.unlock-secret")) t.addEventListener("click", () => n(t.getAttribute("secret-id"))); - for (let t of document.querySelectorAll("button.lock-secret")) t.addEventListener("click", () => i(t.getAttribute("secret-id"))) - } - - function z(e) { - A("/api/plugins/secrets/session-keys/", {private_key: e, preserve_key: !0}).then(n => { - if (!y(n)) window.location.pathname === "/plugins/secrets/user-key/" ? window.location.reload() : p("success", "Session Key Received", "You may now unlock secrets.").show(); else { - let i = n.error; - L(n) && (i += ` -${n.exception}`), p("danger", "Failed to Retrieve Session Key", i).show() - } - }) - } - - function J() { - for (let n of document.querySelectorAll("#request_session_key")) { - let i = function () { - for (let t of document.querySelectorAll("#user_privkey")) z(t.value), t.value = "" - }; - var e = i; - n.addEventListener("click", i) - } - } - - function X() { - let e = new window.Modal("#privkey_modal"); - - function n(i) { - document.cookie.indexOf("netbox_secrets_sessionid") === -1 && (i.preventDefault(), e.show()) - } - - for (let i of document.querySelectorAll(".requires-session-key")) { - let t = i.closest("form"); - t !== null && t.addEventListener("submit", n) - } - } - - function w() { - for (let e of [N, G, J, X]) e() - } - - document.readyState !== "loading" ? w() : document.addEventListener("DOMContentLoaded", w); -})(); +${s==null?void 0:s.value}`,d=new Blob([a],{type:"text/plain"}),u=document.createElement("a");u.style.display="none",u.href=window.URL.createObjectURL(d),u.download="key.txt",document.body.appendChild(u),u.click(),window.URL.revokeObjectURL(u.href),document.body.removeChild(u)}e.addEventListener("shown.bs.modal",()=>r()),n.addEventListener("click",()=>c()),i.addEventListener("click",()=>navigator.clipboard.writeText((s==null?void 0:s.value)||"")),t.addEventListener("click",()=>l())}function S(e,n){let i=document.querySelector(`button.unlock-secret[secret-id='${e}']`),t=document.querySelector(`button.lock-secret[secret-id='${e}']`),o=document.querySelector(`span[secret-id='${e}']`);i!==null&&(n==="unlock"&&i.classList.add("d-none"),n==="lock"&&i.classList.remove("d-none")),t!==null&&(n==="unlock"&&t.classList.remove("d-none"),n==="lock"&&t.classList.add("d-none")),o!==null&&(n==="unlock"&&o.classList.remove("d-none"),n==="lock"&&o.classList.add("d-none"))}function G(){let e=new window.Modal("#privkey_modal");function n(t){let o=document.getElementById(`secret_${t}`);typeof t=="string"&&t!==""&&k(`/api/plugins/secrets/secrets/${t}/`).then(s=>{if(y(s))s.error.toLowerCase().includes("invalid session key")?e.show():p("danger","Error",s.error).show();else{let{plaintext:r}=s;o!==null&&r!==null?(v(o)?o.value=r:o.innerText=r,S(t,"unlock")):e.show()}})}function i(t){if(typeof t=="string"&&t!==""){let o=document.getElementById(`secret_${t}`);v(o)?o.value="********":o.innerText="********",S(t,"lock")}}for(let t of document.querySelectorAll("button.unlock-secret"))t.addEventListener("click",()=>n(t.getAttribute("secret-id")));for(let t of document.querySelectorAll("button.lock-secret"))t.addEventListener("click",()=>i(t.getAttribute("secret-id")))}function z(e){A("/api/plugins/secrets/session-keys/",{private_key:e,preserve_key:!0}).then(n=>{if(!y(n))window.location.pathname==="/plugins/secrets/user-key/"?window.location.reload():p("success","Session Key Received","You may now unlock secrets.").show();else{let i=n.error;L(n)&&(i+=` +${n.exception}`),p("danger","Failed to Retrieve Session Key",i).show()}})}function J(){for(let n of document.querySelectorAll("#request_session_key")){let i=function(){for(let t of document.querySelectorAll("#user_privkey"))z(t.value),t.value=""};var e=i;n.addEventListener("click",i)}}function X(){let e=new window.Modal("#privkey_modal");function n(i){document.cookie.indexOf("netbox_secrets_sessionid")===-1&&(i.preventDefault(),e.show())}for(let i of document.querySelectorAll(".requires-session-key")){let t=i.closest("form");t!==null&&t.addEventListener("submit",n)}}function w(){for(let e of[N,G,J,X])e()}document.readyState!=="loading"?w():document.addEventListener("DOMContentLoaded",w);})(); /*! Bundled license information: cookie/index.js: diff --git a/netbox_secrets/templates/netbox_secrets/inc/private_key_modal.html b/netbox_secrets/templates/netbox_secrets/inc/private_key_modal.html index abc01c5..8043c29 100644 --- a/netbox_secrets/templates/netbox_secrets/inc/private_key_modal.html +++ b/netbox_secrets/templates/netbox_secrets/inc/private_key_modal.html @@ -29,8 +29,8 @@