Esempi di API documentazione

Esempi completi e funzionanti per le attività API più comuni. Tutti gli esempi utilizzano JavaScript puro e la Web Crypto API integrata, quindi non sono necessarie librerie esterne. Consultate la Documentazione API per il riferimento completo degli endpoint.

Creare un segreto

POST /api/secrets Chiave API privata

Un esempio di crittografia di un segreto e del suo invio all'API di password.link usando JavaScript. Salvatelo come file HTML, impostate la chiave API e apritelo in un browser.

Uno script come questo può essere utilizzato su un computer locale o in una rete locale per creare comodamente segreti sul nostro servizio; tuttavia non mettetelo mai in una posizione pubblica senza un'autenticazione adeguata, perché contiene la vostra chiave API privata.

HTML
<html>
  <head>
    <meta content="text/html;charset=utf-8" http-equiv="Content-Type">
    <meta content="utf-8" http-equiv="encoding">

    <script>
      // Set the base URL for the generated link
      const LINK_BASE_URL = "https://some.site/secret.html";

      // Set the API key here - a private API key is required
      const PRIVATE_API_KEY = "private_key_abcd...";

      // ----------------------------------- //

      // A function for sending the secret to password.link API
      async function sendToPasswordlinkApi(secret) {
        // Create the public and private password parts
        const passwordPartPublic = generateString(18);
        const passwordPartPrivate = generateString(18);

        // Create a Web Crypto compatible Base64 encoded ciphertext
        const ciphertext = await encryptSecret(passwordPartPublic, passwordPartPrivate, secret);

        // Send a request to the password.link API and process the result
        const response = await fetch("https://password.link/api/secrets", {
          method: "POST",
          headers: {
            "Authorization": "ApiKey " + PRIVATE_API_KEY,
            "Content-Type": "application/json"
          },
          body: JSON.stringify({
            "ciphertext": ciphertext,
            "password_part_private": btoa(passwordPartPrivate)
          })
        });

        const data = await response.json();

        if (!response.ok) {
          throw new Error(data.error.message);
        }

        const secretUrl = LINK_BASE_URL + "?" + data.data.id + "#" + btoa(passwordPartPublic);
        const meta = data.metadata;

        document.getElementById("secret").textContent = "URL to secret: " + secretUrl;
        document.getElementById("secret-meta").textContent =
          "Total secrets: " + meta.secrets_total +
          " | Usage: " + meta.secrets_usage +
          " | Allowance: " + meta.secrets_allowance;
      }

      // A function for encrypting a secret with the Web Crypto API (AES-GCM),
      // returns a Base64 encoded ciphertext JSON string
      async function encryptSecret(passwordPartPublic, passwordPartPrivate, secret) {
        const iterations = 10000;
        const salt = crypto.getRandomValues(new Uint8Array(16));
        const iv = crypto.getRandomValues(new Uint8Array(12));

        // The encryption key is derived from the concatenated password parts
        const passwordKey = await crypto.subtle.importKey(
          "raw",
          new TextEncoder().encode(passwordPartPrivate + passwordPartPublic),
          "PBKDF2",
          false,
          ["deriveKey"]
        );

        const key = await crypto.subtle.deriveKey(
          { name: "PBKDF2", salt: salt, iterations: iterations, hash: "SHA-256" },
          passwordKey,
          { name: "AES-GCM", length: 256 },
          false,
          ["encrypt"]
        );

        const cipher = await crypto.subtle.encrypt(
          { name: "AES-GCM", iv: iv },
          key,
          new TextEncoder().encode(secret)
        );

        return btoa(JSON.stringify({
          "cipher": bufferToBase64(cipher),
          "iv": bufferToBase64(iv),
          "salt": bufferToBase64(salt),
          "iter": iterations
        }));
      }

      // Base64 encode the bytes of an ArrayBuffer
      function bufferToBase64(buffer) {
        const bytes = new Uint8Array(buffer);
        let binary = "";

        for (let i = 0; i < bytes.length; i++) {
          binary += String.fromCharCode(bytes[i]);
        }

        return btoa(binary);
      }

      // A function for generating a random string. Rejection sampling keeps
      // the character distribution unbiased.
      function generateString(length) {
        const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789$|/!_+,.-?()[]{}<>&#^*=@";
        const maxAcceptable = Math.floor(256 / chars.length) * chars.length;

        let str = "";

        while (str.length < length) {
          const randomValues = new Uint8Array(length - str.length);
          crypto.getRandomValues(randomValues);

          for (let i = 0; i < randomValues.length && str.length < length; i++) {
            if (randomValues[i] < maxAcceptable) {
              str += chars.charAt(randomValues[i] % chars.length);
            }
          }
        }

        return str;
      }

      // Execute the send function when the button is clicked
      document.addEventListener("DOMContentLoaded", function() {
        document.getElementById("secret-button").addEventListener("click", function(e) {
          e.preventDefault();

          const secret = document.getElementById("secret-input").value;

          sendToPasswordlinkApi(secret).catch(function(error) {
            document.getElementById("secret").textContent = "Error: " + error.message;
          });
        });
      });
    </script>
    <style>
      body {
        font: 12px Arial;
      }
      .container {
        max-width: 960px;
        margin: 0 auto;
        text-align: center;
        margin-top: 60px;
      }
      #secret {
        margin-top: 30px;
        font-size: 18px;
      }
      #secret-meta {
        margin-top: 40px;
      }
      .secret-form {
        display: flex;
        flex-flow: row wrap;
        align-items: center;
        flex-direction: vertical;
        justify-content: center;
      }
      .secret-form input {
        vertical-align: middle;
        margin: 5px 10px 5px 0;
        padding: 10px;
        border: 1px solid #ddd;
        width: 200px;
      }
      .secret-form button {
        padding: 10px 20px;
        border: 1px solid #ddd;
      }
      .secret-form button:hover {
        cursor: pointer;
      }
    </style>
  </head>
  <body>
    <div class="container">
      <h2>Encrypt and create a secret on password.link</h2>
      <form class="secret-form">
        <input id="secret-input" type="text" name="secret">
        <button id="secret-button">Encrypt and create link</button>
      </form>

      <!-- This div will contain the link to the secret (or error) -->
      <div id="secret"></div>
      <div id="secret-meta"></div>
    </div>
  </body>
</html>

Creare un segreto con allegato

POST /api/secrets Chiave API privata

Questo esempio mostra le parti specifiche dell'allegato. Presuppone che tu abbia già creato il testo cifrato del segreto e le parti della password come mostrato nell'esempio Creare un segreto sopra.

JavaScript
// Set these first
const PASSWORDLINK_BASE_URL = "https://password.link";
const PRIVATE_API_KEY = "private_key_abcd...";

// These values come from the Create Secret example above
const ciphertext = "...";
const passwordPartPrivate = "...";
const passwordPartPublic = "...";
const passwordPartPrivateBase64 = btoa(passwordPartPrivate);

async function createSecretWithAttachment(file) {
  const createResponse = await fetch(`${PASSWORDLINK_BASE_URL}/api/secrets`, {
    method: "POST",
    headers: {
      "Authorization": "ApiKey " + PRIVATE_API_KEY,
      "Content-Type": "application/json"
    },
    body: JSON.stringify({
      ciphertext: ciphertext,
      password_part_private: passwordPartPrivateBase64,
      attachment: {
        file_name: file.name,
        file_type: file.type || "application/octet-stream",
        file_size: file.size
      }
    })
  });

  const createData = await createResponse.json();

  if (!createResponse.ok) {
    throw new Error(createData.error?.message || "Failed to create secret");
  }

  const attachmentUpload = createData.data.attachment.upload;
  const fileDataUrl = await fileToDataUrl(file);
  const encryptedFile = await encryptAttachment(
    fileDataUrl,
    passwordPartPrivate + passwordPartPublic
  );

  const formData = new FormData();
  appendFormFields(formData, attachmentUpload.metadata);
  appendFormFields(formData, attachmentUpload.fields);
  formData.append("file", new Blob([encryptedFile], { type: "text/plain" }), "file.txt");

  const uploadUrl = new URL(attachmentUpload.url, PASSWORDLINK_BASE_URL);
  const uploadOptions = {
    method: "POST",
    body: formData
  };

  if (uploadUrl.origin === new URL(PASSWORDLINK_BASE_URL).origin) {
    uploadOptions.headers = {
      "Authorization": "ApiKey " + PRIVATE_API_KEY
    };
  } else {
    uploadOptions.mode = "no-cors";
  }

  const uploadResponse = await fetch(uploadUrl.toString(), uploadOptions);

  if (uploadResponse.status !== 0 && !uploadResponse.ok) {
    throw new Error("Failed to upload attachment");
  }

  return createData.data.id;
}

function appendFormFields(formData, fields) {
  if (!fields) {
    return;
  }

  Object.entries(fields).forEach(([key, value]) => {
    formData.append(key, value);
  });
}

function fileToDataUrl(file) {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onload = () => resolve(reader.result);
    reader.onerror = reject;
    reader.readAsDataURL(file);
  });
}

async function encryptAttachment(plaintext, password) {
  const salt = crypto.getRandomValues(new Uint8Array(16));
  const iv = crypto.getRandomValues(new Uint8Array(12));
  const passwordKey = await crypto.subtle.importKey(
    "raw",
    new TextEncoder().encode(password),
    "PBKDF2",
    false,
    ["deriveKey"]
  );

  const key = await crypto.subtle.deriveKey(
    { name: "PBKDF2", salt: salt, iterations: 10000, hash: "SHA-256" },
    passwordKey,
    { name: "AES-GCM", length: 256 },
    false,
    ["encrypt"]
  );

  const cipher = await crypto.subtle.encrypt(
    { name: "AES-GCM", iv: iv },
    key,
    new TextEncoder().encode(plaintext)
  );

  return btoa(JSON.stringify({
    cipher: bufferToString(cipher),
    iv: bufferToString(iv),
    salt: bufferToString(salt)
  }));
}

function bufferToString(buffer) {
  let binary = "";
  const bytes = new Uint8Array(buffer);

  for (let i = 0; i < bytes.byteLength; i++) {
    binary += String.fromCharCode(bytes[i]);
  }

  return binary;
}

Visualizza il Segreto

GET /api/secrets/<id> Chiave API pubblica

Un esempio di recupero, decrittografia e visualizzazione di un segreto usando JavaScript. Uno script come questo può essere utilizzato per creare una pagina self-hosted di visualizzazione del segreto.

Il testo cifrato può essere Web Crypto o SJCL legacy. L'esempio seguente gestisce entrambi i casi: i payload SJCL hanno una chiave ct. Per la decrittografia SJCL, includere sjcl.js nella pagina.

HTML
<html>
  <head>
    <style>
      body {
        font: 12px Arial;
      }
      .container {
        max-width: 960px;
        margin: 0 auto;
        text-align: center;
        margin-top: 60px;
      }
    </style>
  </head>
  <body>
    <div class="container">
      <h2>Here's the secret</h2>

      <!-- This div will contain the decrypted secret (or error) -->
      <div id="secret"></div>
    </div>

    <script>
      (function() {
        // Set the API key here - a public API key is required
        const PUBLIC_API_KEY = "public_key_abcd...";

        // ----------------------------------- //

        // Get the secret ID from the query string part of the URL
        // E.g. https://some.site/secret.html?secret_id
        const secretId = location.search.substr(1);

        // Get the public password (encryption key) part from the hash part of the URL, in Base64 format
        // E.g. https://some.site/secret.html?secret_id#password_part_public
        const passwordPartPublic = location.hash.substr(1);

        // Fetch the secret from the password.link API and decrypt it
        async function fetchAndDecryptSecret() {
          const response = await fetch("https://password.link/api/secrets/" + secretId, {
            headers: {
              "Authorization": "ApiKey " + PUBLIC_API_KEY
            }
          });

          const data = await response.json();

          if (!response.ok) {
            throw new Error(data.error.message);
          }

          const secret = data.data;
          const decryptedSecret = await decryptSecret(
            passwordPartPublic,
            secret.password_part_private,
            secret.ciphertext
          );

          // Set the content of the element with id "secret" to the decrypted secret
          // Use .textContent to avoid XSS
          document.getElementById("secret").textContent = decryptedSecret;
        }

        // Decrypts a ciphertext received from the password.link API.
        // All parameters are in Base64 format.
        //
        // Legacy SJCL payloads contain a "ct" key and need sjcl.js:
        // https://github.com/bitwiseshiftleft/sjcl
        async function decryptSecret(passwordPartPublic, passwordPartPrivate, ciphertext) {
          const payload = JSON.parse(atob(ciphertext));
          const password = atob(passwordPartPrivate) + atob(passwordPartPublic);

          if (Object.prototype.hasOwnProperty.call(payload, "ct")) {
            return sjcl.decrypt(password, atob(ciphertext));
          }

          const passwordKey = await crypto.subtle.importKey(
            "raw",
            new TextEncoder().encode(password),
            "PBKDF2",
            false,
            ["deriveKey"]
          );

          const key = await crypto.subtle.deriveKey(
            {
              name: "PBKDF2",
              salt: base64ToBytes(payload.salt),
              iterations: payload.iter || 10000,
              hash: "SHA-256"
            },
            passwordKey,
            { name: "AES-GCM", length: 256 },
            false,
            ["decrypt"]
          );

          const plaintext = await crypto.subtle.decrypt(
            { name: "AES-GCM", iv: base64ToBytes(payload.iv) },
            key,
            base64ToBytes(payload.cipher)
          );

          return new TextDecoder().decode(plaintext);
        }

        // Decode a Base64 string into a byte array
        function base64ToBytes(base64) {
          const binary = atob(base64);
          const bytes = new Uint8Array(binary.length);

          for (let i = 0; i < binary.length; i++) {
            bytes[i] = binary.charCodeAt(i);
          }

          return bytes;
        }

        fetchAndDecryptSecret().catch(function(error) {
          document.getElementById("secret").textContent = "Error: " + error.message;
        });
      })();
    </script>
  </body>
</html>