Piekļūstiet sava seifa saturam programmatiski ar zero-knowledge šifrēšanu.
Šifrētu attēlu un dokumentu augšupielāde un lejupielāde caur Tag API v2
Zero-Knowledge Šifrēšana
Attēli ar automātisku thumbnail ģenerāciju (200x200px JPEG)
Jebkuri faili (PDF, DOCX, ZIP, utt.) līdz 100 MB
Failu augšupielāde notiek 4 soļos ar parakstītiem Supabase URL:
POST /api/v2/tag/files/upload - Saņem URL augšupielādei ar 1h derīgumu
PUT uz fileUploadUrl - Tieši uz Supabase storage
PUT uz thumbnailUploadUrl - 200x200px JPEG thumbnail
POST /api/v2/tag/files/confirm, tad POST /api/v2/tag/items
import { randomBytes, createCipheriv } from 'crypto';
import sharp from 'sharp';
// 1. Ģenerē unikālu ID un DEK faila šifrēšanai
const itemId = crypto.randomUUID();
const fileDEK = randomBytes(32); // 256-bit AES key
const fileIv = randomBytes(12); // 96-bit IV for GCM
const thumbnailDEK = randomBytes(32);
const thumbnailIv = randomBytes(12);
// 2. Šifrē failu ar AES-256-GCM
const fileCipher = createCipheriv('aes-256-gcm', fileDEK, fileIv);
const encryptedFile = Buffer.concat([
fileCipher.update(originalFileBuffer),
fileCipher.final(),
fileCipher.getAuthTag() // GCM auth tag (16 bytes)
]);
// 3. Ģenerē un šifrē thumbnail (tikai attēliem)
const THUMBNAIL_SIZE = 200;
const thumbnailBuffer = await sharp(originalFileBuffer)
.resize(THUMBNAIL_SIZE, THUMBNAIL_SIZE, {
fit: 'inside',
withoutEnlargement: true
})
.rotate() // Auto-rotate based on EXIF
.jpeg({ quality: 70, mozjpeg: true })
.toBuffer();
const thumbCipher = createCipheriv('aes-256-gcm', thumbnailDEK, thumbnailIv);
const encryptedThumbnail = Buffer.concat([
thumbCipher.update(thumbnailBuffer),
thumbCipher.final(),
thumbCipher.getAuthTag()
]);
// 4. Pieprasīt parakstītus upload URL
const uploadRes = await fetch('https://vairogs.lv/api/v2/tag/files/upload', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': ACCESS_KEY // vgtk_a_...
},
body: JSON.stringify({
itemId,
fileType: 'image',
fileSize: encryptedFile.length,
thumbnailSize: encryptedThumbnail.length
})
});
const { data: urls } = await uploadRes.json();
// 5. Augšupielādēt failu uz Supabase (tiešs PUT)
await fetch(urls.fileUploadUrl, {
method: 'PUT',
body: encryptedFile,
headers: { 'Content-Type': 'application/octet-stream' }
});
// 6. Augšupielādēt thumbnail
await fetch(urls.thumbnailUploadUrl, {
method: 'PUT',
body: encryptedThumbnail,
headers: { 'Content-Type': 'application/octet-stream' }
});
// 7. Apstiprināt augšupielādi
await fetch('https://vairogs.lv/api/v2/tag/files/confirm', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': ACCESS_KEY
},
body: JSON.stringify({
itemId,
fileSize: encryptedFile.length,
thumbnailSize: encryptedThumbnail.length
})
});
// 8. Izveidot item ar fileMetadata (SVARĪGI: iekļaut DEK!)
const itemData = {
fileMetadata: {
storagePath: urls.filePath,
thumbnailPath: urls.thumbnailPath,
originalFileName: 'photo.jpg',
mimeType: 'image/jpeg',
fileSize: originalFileBuffer.length,
encryptedSize: encryptedFile.length,
fileIv: fileIv.toString('base64'),
thumbnailIv: thumbnailIv.toString('base64'),
fileDEK: fileDEK.toString('base64'), // OBLIGĀTI MCP failiem!
thumbnailDEK: thumbnailDEK.toString('base64')
},
notes: 'Augšupielādēts caur MCP'
};
// 9. Šifrē metadata un izveido item (sk. Write operāciju piemērus)
// ... (DEK ģenerācija, key slots izveidošana, POST /api/v2/tag/items)/api/v2/tag/files/uploadPieprasīt parakstītus URL šifrēta faila augšupielādei
| Parametrs | Tips | Apraksts |
|---|---|---|
itemId* | string | UUID jaunajam item (ģenerē client-side) |
fileType* | string | "image" vai "document" |
fileSize* | number | Šifrētā faila izmērs baitos |
thumbnailSize | number | Šifrētā thumbnail izmērs (tikai attēliem) |
{
"success": true,
"data": {
"fileUploadUrl": "https://supabase.co/.../signed-url",
"filePath": "{emailHash}/files/{itemId}.enc",
"thumbnailUploadUrl": "https://supabase.co/.../signed-url",
"thumbnailPath": "{emailHash}/thumbnails/{itemId}_thumb.enc",
"expiresAt": "2025-12-11T18:00:00.000Z"
}
}URL Derīgums
/api/v2/tag/files/confirmApstiprināt veiksmīgu augšupielādi un atjaunināt storage uzskaiti
| Parametrs | Tips | Apraksts |
|---|---|---|
itemId* | string | Item UUID no upload pieprasījuma |
fileSize* | number | Šifrētā faila izmērs baitos |
thumbnailSize | number | Šifrētā thumbnail izmērs |
{
"success": true,
"data": {
"confirmed": true,
"itemId": "abc-123",
"bytesAdded": 14345
}
}/api/v2/tag/files/:itemId/downloadLejupielādēt šifrētu failu vai thumbnail
| Parametrs | Tips | Apraksts |
|---|---|---|
type | string | "file" (default) vai "thumbnail" |
Atgriež raw šifrētu binary (application/octet-stream) ar headeriem:
Content-Type: application/octet-streamContent-Length: {bytes}X-Item-Id: {itemId}X-File-Type: file|thumbnail// 1. Vispirms atšifrē item metadata, lai iegūtu fileIv un fileDEK
const itemData = await decryptItemData(item.encryptedData, item.iv, dek);
const { fileMetadata } = itemData;
// 2. Lejupielādē šifrēto failu
const encryptedFile = await fetch(
`https://vairogs.lv/api/v2/tag/files/${item.id}/download`,
{ headers: { 'X-API-Key': ACCESS_KEY } } // vgtk_a_...
).then(r => r.arrayBuffer());
// 3. Atšifrē ar fileDEK un fileIv (no metadata)
const fileIv = base64ToUint8Array(fileMetadata.fileIv);
const fileDEK = base64ToUint8Array(fileMetadata.fileDEK); // Tikai MCP failiem!
// Ja nav fileDEK (user-uploaded), izmanto master key:
const decryptionKey = fileMetadata.fileDEK
? fileDEK // MCP-uploaded
: await derivedKeyFromMasterPassword(masterKey, salt); // User-uploaded
const aesKey = await crypto.subtle.importKey(
'raw', decryptionKey, { name: 'AES-GCM' }, false, ['decrypt']
);
const decryptedFile = await crypto.subtle.decrypt(
{ name: 'AES-GCM', iv: fileIv },
aesKey,
encryptedFile
);
// 4. Saglabā vai parāda failu
const blob = new Blob([decryptedFile], { type: fileMetadata.mimeType });
const url = URL.createObjectURL(blob);Attēliem automātiski jāģenerē thumbnail client-side pirms augšupielādes:
Ieteikumi
import sharp from 'sharp';
const THUMBNAIL_SIZE = 200;
async function generateThumbnail(imageBuffer) {
return await sharp(imageBuffer)
.resize(THUMBNAIL_SIZE, THUMBNAIL_SIZE, {
fit: 'inside', // Saglabā aspekta attiecību
withoutEnlargement: true // Nepalielina mazus attēlus
})
.rotate() // Auto-rotate pēc EXIF
.jpeg({
quality: 70, // Optimizē izmēru
mozjpeg: true // Labāka kompresija
})
.toBuffer();
}
// Šifrē thumbnail ar atsevišķu DEK
const thumbnailDEK = randomBytes(32);
const thumbnailIv = randomBytes(12);
const thumbnailBuffer = await generateThumbnail(originalImage);
const encryptedThumbnail = await encryptWithAES(
thumbnailBuffer,
thumbnailDEK,
thumbnailIv
);IMAGE un DOCUMENT item encryptedData iekšienē:
interface EncryptedFileMetadata {
// Storage ceļi (Supabase)
storagePath: string; // {emailHash}/files/{itemId}.enc
thumbnailPath?: string; // {emailHash}/thumbnails/{itemId}_thumb.enc
// Faila informācija
originalFileName: string; // "photo.jpg"
mimeType: string; // "image/jpeg"
fileSize: number; // Oriģinālais izmērs baitos
encryptedSize: number; // Šifrētais izmērs (storage tracking)
// Šifrēšanas parametri
fileIv: string; // Base64 IV faila atšifrēšanai
thumbnailIv?: string; // Base64 IV thumbnail atšifrēšanai
// DEK (OBLIGĀTI MCP-augšupielādētiem failiem!)
fileDEK?: string; // Base64 DEK failam (random, NE no master key!)
thumbnailDEK?: string; // Base64 DEK thumbnail
}
// Item encryptedData saturs IMAGE/DOCUMENT tipiem:
{
fileMetadata: EncryptedFileMetadata,
notes?: string // Papildus piezīmes
}Kritisks: DEK Glabāšana MCP Failiem
MCP-augšupielādēti faili izmanto random DEK šifrēšanai, nevis atslēgas, kas atvasinātas no lietotāja master password.
Bez fileDEK un thumbnailDEK laukiem metadata, web klients NEVAR atšifrēt MCP-augšupielādētus failus!
Obligāti jāiekļauj fileDEK un thumbnailDEK Base64 formātā fileMetadata objektā.
0 GB
Failu augšupielāde pieejama tikai PRO kontiem
1 GB
Ietver šifrētus failus + thumbnail
Zero-Knowledge Šifrēšana
Visi faili šifrēti ar AES-256-GCM pirms augšupielādes. Serveris nekad nesaņem atšifrētus datus.
Atsevišķs DEK Katram Failam
Katrs fails izmanto unikālu 256-bit DEK (Data Encryption Key) ar NaCl box encryption.
Parakstīti URL ar Ierobežotu Derīgumu
Supabase parakstītie URL derīgi 1 stundu. Pēc tam nepieciešams ģenerēt jaunus.
Automātiska Storage Uzskaite
Faila izmērs automātiski pieskaitīts lietotāja storage quota pēc confirm pieprasījuma.
| Code | HTTP | Description |
|---|---|---|
STORAGE_QUOTA_EXCEEDED | 403 | Pārsniegts storage limits (1 GB PRO) |
PRO_REQUIRED | 403 | Failu augšupielāde pieejama tikai PRO |
FILE_TOO_LARGE | 400 | Fails lielāks par 100 MB |
INVALID_FILE_TYPE | 400 | fileType jābūt "image" vai "document" |
ITEM_NOT_FOUND | 404 | Item nav atrasts (download operācijā) |
FILE_NOT_FOUND | 404 | Fails nav atrasts Supabase storage |