Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 13 additions & 40 deletions docs/how-to/integrate-blob-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ This guide shows how to integrate the blob download REST API into your frontend

- Access to the GraphQL API (for getting blob hashes)
- API base URL (e.g., `https://api.oswatcher.io`)
- Optional: Auth0 JWT token for authenticated requests

## Step 1: Update Configuration

Expand Down Expand Up @@ -41,22 +40,15 @@ export class BlobService {
* Download a blob by hash
* @param hash SHA-1 hash of the blob
* @param filename Optional filename for download
* @param authToken Optional JWT token
*/
async downloadBlob(
hash: string,
filename?: string,
authToken?: string
filename?: string
): Promise<void> {
const url = `${this.blobBaseUrl}/${hash}`;

const headers: Record<string, string> = {};
if (authToken) {
headers['Authorization'] = `Bearer ${authToken}`;
}

try {
const response = await fetch(url, { headers });
const response = await fetch(url);

if (!response.ok) {
await this.handleError(response);
Expand Down Expand Up @@ -123,17 +115,11 @@ export class BlobService {
async downloadBlobWithProgress(
hash: string,
onProgress: (percent: number) => void,
filename?: string,
authToken?: string
filename?: string
): Promise<void> {
const url = `${this.blobBaseUrl}/${hash}`;

const headers: Record<string, string> = {};
if (authToken) {
headers['Authorization'] = `Bearer ${authToken}`;
}

const response = await fetch(url, { headers });
const response = await fetch(url);

if (!response.ok) {
await this.handleError(response);
Expand Down Expand Up @@ -204,19 +190,14 @@ export const LIST_ENTRIES_FOR_TREE = gql`

import { BlobService } from '../services/blobService';
import { API_CONFIG } from '../config';
import { useAuth0 } from '@auth0/auth0-react';

export function FileExplorer() {
const blobService = new BlobService(API_CONFIG.blobUrl);
const { getAccessTokenSilently } = useAuth0();

const handleDownload = async (blobHash: string, filename: string) => {
try {
// Get auth token if available
const token = await getAccessTokenSilently().catch(() => undefined);

// Download blob
await blobService.downloadBlob(blobHash, filename, token);
await blobService.downloadBlob(blobHash, filename);

// Success notification
toast.success(`Downloaded ${filename}`);
Expand Down Expand Up @@ -255,10 +236,9 @@ import { BlobService } from '../services/blobService';
interface DownloadButtonProps {
hash: string;
filename: string;
authToken?: string;
}

export function DownloadButton({ hash, filename, authToken }: DownloadButtonProps) {
export function DownloadButton({ hash, filename }: DownloadButtonProps) {
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const blobService = new BlobService(API_CONFIG.blobUrl);
Expand All @@ -268,7 +248,7 @@ export function DownloadButton({ hash, filename, authToken }: DownloadButtonProp
setError(null);

try {
await blobService.downloadBlob(hash, filename, authToken);
await blobService.downloadBlob(hash, filename);
} catch (err) {
if (err instanceof Error) {
setError(err.message);
Expand Down Expand Up @@ -312,18 +292,11 @@ export class BlobService {
/**
* Check if a blob is accessible
*/
async isAccessible(hash: string, authToken?: string): Promise<boolean> {
async isAccessible(hash: string): Promise<boolean> {
const url = `${this.blobBaseUrl}/${hash}`;
const headers: Record<string, string> = {};
if (authToken) {
headers['Authorization'] = `Bearer ${authToken}`;
}

try {
const response = await fetch(url, {
method: 'HEAD',
headers
});
const response = await fetch(url, { method: 'HEAD' });
return response.ok;
} catch {
return false;
Expand All @@ -332,7 +305,7 @@ export class BlobService {
}

// Usage:
const accessible = await blobService.isAccessible(hash, token);
const accessible = await blobService.isAccessible(hash);
if (!accessible) {
showWarning('This file is restricted');
}
Expand All @@ -355,7 +328,7 @@ window.open(s3Url, '_blank');
```typescript
// ✅ New approach
const apiUrl = `https://api.oswatcher.io/blob/${blobHash}`;
await blobService.downloadBlob(blobHash, filename, authToken);
await blobService.downloadBlob(blobHash, filename);
```

## Testing
Expand Down Expand Up @@ -411,15 +384,15 @@ The API doesn't cache authorization results, so each download checks the databas
const accessCache = new Map<string, { accessible: boolean; timestamp: number }>();
const CACHE_TTL = 60000; // 1 minute

async function isCachedAccessible(hash: string, token?: string): Promise<boolean> {
async function isCachedAccessible(hash: string): Promise<boolean> {
const cached = accessCache.get(hash);
const now = Date.now();

if (cached && now - cached.timestamp < CACHE_TTL) {
return cached.accessible;
}

const accessible = await blobService.isAccessible(hash, token);
const accessible = await blobService.isAccessible(hash);
accessCache.set(hash, { accessible, timestamp: now });
return accessible;
}
Expand Down
4 changes: 2 additions & 2 deletions docs/reference/access-restrictions.md
Original file line number Diff line number Diff line change
Expand Up @@ -473,8 +473,8 @@ describe('Access Restriction', () => {
- Different restrictions for filesystem vs. symbol blobs

2. **Role-Based Access Control (RBAC)**
- Use Auth0 permissions for fine-grained access
- Example: `download:windows` permission for licensed users
- Would require reintroducing an identity provider, removed when the project
went fully open source

3. **Rate Limiting Per User**
- Higher limits for authenticated users
Expand Down
11 changes: 4 additions & 7 deletions docs/reference/blob-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,7 @@ Downloads a blob by its SHA-1 hash with authorization checks.

## Request Headers

| Header | Required | Description |
|--------|----------|-------------|
| `Authorization` | No | Bearer token for Auth0 JWT authentication |
The endpoint is unauthenticated: no headers are required.

## Response

Expand Down Expand Up @@ -102,10 +100,9 @@ Error communicating with object storage.

## Authorization

The API performs two levels of authorization:

1. **Authentication**: Optional Auth0 JWT token validation
2. **Blob Restriction**: Neo4j query to check if blob belongs exclusively to restricted branches
Access control is limited to blob restriction: a Neo4j query checks whether the
blob belongs exclusively to restricted branches. There is no per-user
authentication.

### Restriction Logic

Expand Down
Loading