Izstrādātājiem & AI

Vairogs API & MCP

Piekļūstiet sava seifa saturam programmatiski ar zero-knowledge šifrēšanu.

Failu Upload & Download

Šifrētu attēlu un dokumentu augšupielāde un lejupielāde caur Tag API v2

Zero-Knowledge Šifrēšana

Visi faili tiek šifrēti klienta pusē pirms augšupielādes ar AES-256-GCM. Serveris nekad nesaņem atšifrētus failus vai šifrēšanas atslēgas.

IMAGE

Attēli ar automātisku thumbnail ģenerāciju (200x200px JPEG)

DOCUMENT

Jebkuri faili (PDF, DOCX, ZIP, utt.) līdz 100 MB

Augšupielādes Process

Failu augšupielāde notiek 4 soļos ar parakstītiem Supabase URL:

1

Pieprasīt parakstītus URL

POST /api/v2/tag/files/upload - Saņem URL augšupielādei ar 1h derīgumu

2

Augšupielādēt šifrēto failu

PUT uz fileUploadUrl - Tieši uz Supabase storage

3

Augšupielādēt thumbnail (attēliem)

PUT uz thumbnailUploadUrl - 200x200px JPEG thumbnail

4

Apstiprināt un izveidot item

POST /api/v2/tag/files/confirm, tad POST /api/v2/tag/items

Pilns Augšupielādes Piemērs

javascript
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 Endpoints

POST/api/v2/tag/files/upload

Pieprasīt parakstītus URL šifrēta faila augšupielādei

Request Body

ParametrsTipsApraksts
itemId*stringUUID jaunajam item (ģenerē client-side)
fileType*string"image" vai "document"
fileSize*numberŠifrētā faila izmērs baitos
thumbnailSizenumberŠifrētā thumbnail izmērs (tikai attēliem)

Response

json
{
  "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

Parakstītie URL ir derīgi 1 stundu. Pēc tam nepieciešams pieprasīt jaunus.
POST/api/v2/tag/files/confirm

Apstiprināt veiksmīgu augšupielādi un atjaunināt storage uzskaiti

Request Body

ParametrsTipsApraksts
itemId*stringItem UUID no upload pieprasījuma
fileSize*numberŠifrētā faila izmērs baitos
thumbnailSizenumberŠifrētā thumbnail izmērs

Response

json
{
  "success": true,
  "data": {
    "confirmed": true,
    "itemId": "abc-123",
    "bytesAdded": 14345
  }
}
GET/api/v2/tag/files/:itemId/download

Lejupielādēt šifrētu failu vai thumbnail

Query Parameters

ParametrsTipsApraksts
typestring"file" (default) vai "thumbnail"

Response

Atgriež raw šifrētu binary (application/octet-stream) ar headeriem:

  • Content-Type: application/octet-stream
  • Content-Length: {bytes}
  • X-Item-Id: {itemId}
  • X-File-Type: file|thumbnail

Atšifrēšana

javascript
// 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);

Thumbnail Ģenerācija

Attēliem automātiski jāģenerē thumbnail client-side pirms augšupielādes:

Ieteikumi

  • Izmērs: 200x200px (fit: 'inside')
  • Formāts: JPEG ar 70% kvalitāti
  • Auto-rotate pēc EXIF metadatiem
  • Atsevišķs DEK thumbnail šifrēšanai
javascript
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
);

fileMetadata Struktūra

IMAGE un DOCUMENT item encryptedData iekšienē:

typescript
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ā.

Storage Limiti & Drošība

FREE Tier

0 GB

Failu augšupielāde pieejama tikai PRO kontiem

PRO Tier

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.

Kļūdu Kodi

CodeHTTPDescription
STORAGE_QUOTA_EXCEEDED403Pārsniegts storage limits (1 GB PRO)
PRO_REQUIRED403Failu augšupielāde pieejama tikai PRO
FILE_TOO_LARGE400Fails lielāks par 100 MB
INVALID_FILE_TYPE400fileType jābūt "image" vai "document"
ITEM_NOT_FOUND404Item nav atrasts (download operācijā)
FILE_NOT_FOUND404Fails nav atrasts Supabase storage