Initial commit

**Motivations:**
- Initialisation du versionning git pour le projet

**Root causes:**
- N/A (Nouveau projet)

**Correctifs:**
- N/A

**Evolutions:**
- Structure initiale du projet
- Ajout du .gitignore

**Pages affectées:**
- Tous les fichiers
This commit is contained in:
2026-03-03 22:24:17 +01:00
commit e031c9a1d2
155 changed files with 22334 additions and 0 deletions

100
web/js/auth-client.js Normal file
View File

@@ -0,0 +1,100 @@
/**
* Ed25519 keypair: generate, store in localStorage, sign requests.
* Public key exported as SPKI base64url for server verification.
*/
const STORAGE_KEY_PUBLIC = "builazoo_public_key";
const STORAGE_KEY_PRIVATE = "builazoo_private_key";
/**
* @returns {Promise<CryptoKeyPair | null>}
*/
function generateKeyPair() {
return crypto.subtle.generateKey(
{ name: "Ed25519" },
true,
["sign", "verify"]
);
}
/**
* @param {ArrayBuffer} buf
* @returns {string}
*/
function base64url(buf) {
const b64 = btoa(String.fromCharCode(...new Uint8Array(buf)));
return b64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
}
/**
* @returns {Promise<{ publicKeyBase64: string, privateKey: CryptoKey } | null>}
*/
export async function getOrCreateKeyPair() {
if (typeof crypto === "undefined" || !crypto.subtle) {
throw new Error("Connexion sécurisée requise (HTTPS ou localhost) pour créer un compte.");
}
try {
const pubRaw = localStorage.getItem(STORAGE_KEY_PUBLIC);
const privRaw = localStorage.getItem(STORAGE_KEY_PRIVATE);
if (pubRaw && privRaw) {
const privateKey = await crypto.subtle.importKey(
"pkcs8",
base64urlDecodeToBuf(privRaw),
{ name: "Ed25519" },
true,
["sign"]
);
await crypto.subtle.importKey(
"spki",
base64urlDecodeToBuf(pubRaw),
{ name: "Ed25519" },
true,
["verify"]
);
return { publicKeyBase64: pubRaw, privateKey };
}
const pair = await generateKeyPair();
const [pubExported, privExported] = await Promise.all([
crypto.subtle.exportKey("spki", pair.publicKey),
crypto.subtle.exportKey("pkcs8", pair.privateKey),
]);
localStorage.setItem(STORAGE_KEY_PUBLIC, base64url(pubExported));
localStorage.setItem(STORAGE_KEY_PRIVATE, base64url(privExported));
return { publicKeyBase64: base64url(pubExported), privateKey: pair.privateKey };
} catch (e) {
console.warn("getOrCreateKeyPair failed", e);
return null;
}
}
/**
* @param {string} str base64url
* @returns {ArrayBuffer}
*/
function base64urlDecodeToBuf(str) {
const pad = (4 - (str.length % 4)) % 4;
const b64 = (str + "==".slice(0, pad)).replace(/-/g, "+").replace(/_/g, "/");
const binary = atob(b64);
const buf = new Uint8Array(binary.length);
for (let i = 0; i < binary.length; i++) buf[i] = binary.charCodeAt(i);
return buf.buffer;
}
/**
* @param {CryptoKey} privateKey
* @param {string} message
* @returns {Promise<string>} base64url signature
*/
export async function signMessage(privateKey, message) {
const enc = new TextEncoder().encode(message);
const sig = await crypto.subtle.sign("Ed25519", privateKey, enc);
return base64url(sig);
}
/**
* Call after register to ensure we have keys stored (already done by getOrCreateKeyPair).
* @returns {boolean} true if keys exist
*/
export function hasStoredKeys() {
return Boolean(localStorage.getItem(STORAGE_KEY_PUBLIC) && localStorage.getItem(STORAGE_KEY_PRIVATE));
}