Docs - Exemples d'API
Des exemples complets et fonctionnels pour les tâches API les plus courantes. Tous les exemples utilisent du JavaScript pur et l'API Web Crypto intégrée, aucune bibliothèque externe n'est donc nécessaire. Consultez la Docs - API pour la référence complète des points de terminaison.
Créer un secret
Un exemple de chiffrement d'un secret et de son envoi à l'API password.link en JavaScript. Enregistrez-le comme fichier HTML, définissez la clé API et ouvrez-le dans un navigateur.
Un script comme celui-ci peut être utilisé sur un ordinateur local ou un réseau local pour créer facilement des secrets sur notre service, mais ne le placez jamais dans un emplacement public sans authentification appropriée, car il contient votre clé API privée.
<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>
Créer un secret avec pièce jointe
Cet exemple montre les parties propres à la pièce jointe. Il suppose que vous avez déjà créé le texte chiffré du secret et les parties du mot de passe comme indiqué dans l'exemple Créer un secret ci-dessus.
// 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;
}
Voir le secret
Un exemple de récupération, de déchiffrement et d'affichage d'un secret en JavaScript. Un script comme celui-ci peut être utilisé pour créer une page d'affichage du secret auto-hébergée.
Le texte chiffré peut être en Web Crypto ou en ancien SJCL. L'exemple ci-dessous gère les deux cas : les charges utiles SJCL possèdent une clé ct. Pour le déchiffrement SJCL, incluez sjcl.js dans la page.
<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>