Files
metadata-agregator/docs/research/bedrock-api/analysis/API.md
T
Alexander a1f6701bac feat: initial implementation of metadata aggregator
- gRPC service with MusicBrainz provider
- PostgreSQL schema with migrations
- Service layer with database-first caching
- Repository pattern for data access
- YAML configuration support
- Research documentation for 17 music metadata projects
2026-04-28 16:28:53 +02:00

25 KiB

Bedrock-API API Reference

Protocol Buffer Definition

File: proto/bedrock_service.proto
Lines: 622
Package: bedrock
Go Package: github.com/feralbureau/bedrock-api/proto

Service Definition

service BedrockService {
    // Search operations
    rpc SearchTracks(SearchRequest) returns (SearchTracksResponse);
    rpc SearchAlbums(SearchRequest) returns (SearchAlbumsResponse);
    rpc SearchArtists(SearchRequest) returns (SearchArtistsResponse);
    rpc SearchPlaylists(SearchRequest) returns (SearchPlaylistsResponse);
    
    // Retrieval operations
    rpc GetTrack(GetRequest) returns (Track);
    rpc GetAlbum(GetRequest) returns (Album);
    rpc GetArtist(GetRequest) returns (Artist);
    rpc GetPlaylist(GetRequest) returns (Playlist);
    
    // Streaming
    rpc GetStreamURL(GetRequest) returns (StreamURLResponse);
    
    // Recommendations
    rpc GetSimilarTracks(SimilarTracksRequest) returns (SearchTracksResponse);
    
    // Lyrics
    rpc GetLyrics(LyricsRequest) returns (LyricsResponse);
    rpc GetSyncedLyrics(LyricsRequest) returns (SyncedLyricsResponse);
    
    // Statistics
    rpc GetTopTracks(TopRequest) returns (SearchTracksResponse);
    rpc GetTopAlbums(TopRequest) returns (SearchAlbumsResponse);
    rpc GetTopArtists(TopRequest) returns (SearchArtistsResponse);
    
    // Import
    rpc ImportPlaylist(ImportPlaylistRequest) returns (Playlist);
    
    // Service status
    rpc GetServiceStatus(Empty) returns (ServiceStatusResponse);
    
    // Authentication
    rpc Register(AuthRequest) returns (AuthResponse);
    rpc Login(AuthRequest) returns (AuthResponse);
    rpc RefreshToken(RefreshTokenRequest) returns (AuthResponse);
}

Total Methods: 23

Method Categories

Category Methods Authentication Required
Search 4 Yes
Retrieval 4 Yes
Streaming 1 Yes
Recommendations 1 Yes
Lyrics 2 Yes
Statistics 3 Yes
Import 1 Yes
Service Status 1 No
Authentication 3 No (except RefreshToken)

Search Operations

SearchTracks

Request:

message SearchRequest {
    string query = 1;
    int32 limit = 2;  // Default: 20, Max: 50
    Platform platform = 3;  // Optional: filter by platform
}

Response:

message SearchTracksResponse {
    repeated Track tracks = 1;
    ResponseStatus status = 2;
    repeated ProviderError errors = 3;
}

Behavior:

  • Queries all enabled providers in parallel
  • Aggregates results from all platforms
  • Returns partial results if some providers fail
  • Results are not deduplicated (same track from multiple platforms appears multiple times)

Example:

resp, err := client.SearchTracks(ctx, &pb.SearchRequest{
    Query: "Bohemian Rhapsody",
    Limit: 10,
})

// resp.Tracks contains results from Spotify, SoundCloud, Deezer, YouTube Music
// Each track has platform-namespaced ID (e.g., "spotify:track:abc123")

SearchAlbums

Request: Same as SearchTracks

Response:

message SearchAlbumsResponse {
    repeated Album albums = 1;
    ResponseStatus status = 2;
    repeated ProviderError errors = 3;
}

Behavior: Same parallel fan-out pattern as SearchTracks

SearchArtists

Request: Same as SearchTracks

Response:

message SearchArtistsResponse {
    repeated Artist artists = 1;
    ResponseStatus status = 2;
    repeated ProviderError errors = 3;
}

Behavior: Same parallel fan-out pattern as SearchTracks

SearchPlaylists

Request: Same as SearchTracks

Response:

message SearchPlaylistsResponse {
    repeated Playlist playlists = 1;
    ResponseStatus status = 2;
    repeated ProviderError errors = 3;
}

Behavior: Same parallel fan-out pattern as SearchTracks

Retrieval Operations

GetTrack

Request:

message GetRequest {
    string id = 1;  // Namespaced ID (e.g., "spotify:track:abc123")
}

Response:

message Track {
    string id = 1;
    string title = 2;
    string artist = 3;
    string artist_id = 4;
    string album = 5;
    string album_id = 6;
    int32 duration = 7;  // Seconds
    string cover_url = 8;
    int32 year = 9;
    string genre = 10;
    int64 play_count = 11;
    bool explicit = 12;
    string isrc = 13;
    Platform platform = 14;
}

Behavior:

  • Parses namespaced ID to determine platform
  • Routes request to specific provider
  • Returns single track or error

Example:

track, err := client.GetTrack(ctx, &pb.GetRequest{
    Id: "spotify:track:3n3Ppam7vgaVa1iaRUc9Lp",
})

GetAlbum

Request: Same as GetTrack

Response:

message Album {
    string id = 1;
    string title = 2;
    string artist = 3;
    string artist_id = 4;
    int32 year = 5;
    string cover_url = 6;
    int32 track_count = 7;
    repeated Track tracks = 8;
    string genre = 9;
    string label = 10;
    Platform platform = 11;
}

Behavior:

  • Returns album metadata
  • Includes full track list in tracks field
  • Track IDs are namespaced to same platform as album

GetArtist

Request: Same as GetTrack

Response:

message Artist {
    string id = 1;
    string name = 2;
    string image_url = 3;
    repeated string genres = 4;
    int64 followers = 5;
    repeated Album albums = 6;  // Artist discography
    Platform platform = 7;
}

Behavior:

  • Returns artist metadata
  • Includes full discography in albums field (can be large)
  • Deezer provider fetches albums concurrently

GetPlaylist

Request: Same as GetTrack

Response:

message Playlist {
    string id = 1;
    string name = 2;
    string description = 3;
    string owner = 4;
    string cover_url = 5;
    int32 track_count = 6;
    repeated Track tracks = 7;
    bool public = 8;
    Platform platform = 9;
}

Behavior:

  • Returns playlist metadata
  • Includes full track list in tracks field
  • SoundCloud uses batch hydration for track details (30 IDs per request)

Streaming Operations

GetStreamURL

Request:

message GetRequest {
    string id = 1;  // Track ID (namespaced)
}

Response:

message StreamURLResponse {
    string url = 1;
    int32 bitrate = 2;  // kbps
    string format = 3;  // mp3, opus, aac, etc.
    int32 expires_at = 4;  // Unix timestamp
}

Behavior:

  • For SoundCloud/YouTube Music: Returns direct stream URL
  • For Spotify/Deezer: Searches SoundCloud/YouTube Music for matching track, returns bridged stream URL
  • Stream URLs are temporary (expire after 1-6 hours depending on provider)

Stream Resolution Algorithm:

1. Parse platform from namespaced ID
2. If platform is SoundCloud or YouTube Music:
   - Call platform's GetStreamURL directly
3. If platform is Spotify or Deezer:
   - Get track metadata (artist, title)
   - Search SoundCloud for "{artist} - {title}"
   - If found, return SoundCloud stream URL
   - If not found, search YouTube Music
   - If found, return YouTube Music stream URL
   - If not found, return error

Example:

// Direct streaming platform
resp, err := client.GetStreamURL(ctx, &pb.GetRequest{
    Id: "soundcloud:track:1234567890",
})
// resp.Url = "https://cf-media.sndcdn.com/..."

// Non-streaming platform (bridged)
resp, err := client.GetStreamURL(ctx, &pb.GetRequest{
    Id: "spotify:track:3n3Ppam7vgaVa1iaRUc9Lp",
})
// resp.Url = "https://cf-media.sndcdn.com/..." (SoundCloud match)

YouTube Music Stream Selection:

  • Tries 7 different client types sequentially
  • Prefers itag 251 (opus) > 140 (aac)
  • Skips ciphered streams (encrypted, requires decryption)
  • Falls back to SoundCloud if all YouTube clients fail

Recommendation Operations

GetSimilarTracks

Request:

message SimilarTracksRequest {
    string track_id = 1;  // Namespaced track ID
    int32 limit = 2;  // Default: 20
}

Response: Same as SearchTracksResponse

Behavior:

  • Queries provider's recommendation API
  • Spotify: Uses "Get Recommendations" endpoint with seed track
  • YouTube Music: Uses "Get Watch Playlist" (radio)
  • SoundCloud: Uses "Related Tracks" endpoint
  • Deezer: No similar tracks API (returns empty)

Example:

resp, err := client.GetSimilarTracks(ctx, &pb.SimilarTracksRequest{
    TrackId: "spotify:track:3n3Ppam7vgaVa1iaRUc9Lp",
    Limit:   10,
})
// Returns 10 similar tracks from Spotify's recommendation engine

Lyrics Operations

GetLyrics

Request:

message LyricsRequest {
    string artist = 1;
    string title = 2;
    string album = 3;  // Optional
    int32 duration = 4;  // Optional, seconds
}

Response:

message LyricsResponse {
    string lyrics = 1;  // Plain text
    string source = 2;  // "genius"
    repeated Annotation annotations = 3;  // Genius annotations
}

message Annotation {
    string fragment = 1;  // Lyric fragment
    string annotation = 2;  // Explanation/context
}

Behavior:

  • Queries Genius API
  • Returns plain text lyrics
  • Includes annotations (explanations of lyric meanings)
  • Requires GENIUS_ACCESS_TOKEN environment variable

Example:

resp, err := client.GetLyrics(ctx, &pb.LyricsRequest{
    Artist: "Queen",
    Title:  "Bohemian Rhapsody",
})
// resp.Lyrics = "Is this the real life?\nIs this just fantasy?..."
// resp.Annotations contains explanations of lyric meanings

GetSyncedLyrics

Request: Same as GetLyrics

Response:

message SyncedLyricsResponse {
    repeated LyricLine lines = 1;
    string source = 2;  // "lrclib"
}

message LyricLine {
    int32 timestamp = 1;  // Milliseconds
    string text = 2;
}

Behavior:

  • Queries LrcLib API
  • Returns timestamped lyrics (LRC format)
  • Matches by artist, title, album, duration
  • 5 second timeout
  • No authentication required

Example:

resp, err := client.GetSyncedLyrics(ctx, &pb.LyricsRequest{
    Artist:   "Queen",
    Title:    "Bohemian Rhapsody",
    Album:    "A Night at the Opera",
    Duration: 354,
})
// resp.Lines = [
//   {Timestamp: 0, Text: "Is this the real life?"},
//   {Timestamp: 3500, Text: "Is this just fantasy?"},
//   ...
// ]

Statistics Operations

GetTopTracks

Request:

message TopRequest {
    Platform platform = 1;  // Required
    string region = 2;  // ISO country code (e.g., "US", "GB")
    int32 limit = 3;  // Default: 20
}

Response: Same as SearchTracksResponse

Behavior:

  • Queries platform's charts/top tracks API
  • Spotify: Uses "Get Playlist" on regional top 50 playlists
  • YouTube Music: Uses trending charts
  • SoundCloud: Uses "Trending" endpoint
  • Deezer: Uses "Chart" endpoint

Example:

resp, err := client.GetTopTracks(ctx, &pb.TopRequest{
    Platform: pb.Platform_SPOTIFY,
    Region:   "US",
    Limit:    10,
})
// Returns top 10 tracks in US Spotify charts

GetTopAlbums

Request: Same as GetTopTracks

Response: Same as SearchAlbumsResponse

Behavior: Similar to GetTopTracks, queries platform-specific album charts

GetTopArtists

Request: Same as GetTopTracks

Response: Same as SearchArtistsResponse

Behavior: Similar to GetTopTracks, queries platform-specific artist charts

Import Operations

ImportPlaylist

Request:

message ImportPlaylistRequest {
    string url = 1;  // Playlist URL from any supported platform
    Platform target_platform = 2;  // Platform to import to
}

Response: Same as Playlist message

Behavior:

  • Parses playlist URL to determine source platform
  • Fetches playlist tracks from source
  • Creates new playlist on target platform
  • Matches tracks across platforms (by ISRC or artist+title search)
  • Returns created playlist

Example:

resp, err := client.ImportPlaylist(ctx, &pb.ImportPlaylistRequest{
    Url:            "https://open.spotify.com/playlist/37i9dQZF1DXcBWIGoYBM5M",
    TargetPlatform: pb.Platform_SOUNDCLOUD,
})
// Creates SoundCloud playlist with matched tracks from Spotify playlist

Limitations:

  • Requires authentication on target platform (not implemented)
  • Track matching is best-effort (some tracks may not match)
  • No progress reporting for large playlists

Service Status Operations

GetServiceStatus

Request:

message Empty {}

Response:

message ServiceStatusResponse {
    ServiceStatus status = 1;
    repeated DependencyStatus dependencies = 2;
}

enum ServiceStatus {
    HEALTHY = 0;
    DEGRADED = 1;
    UNHEALTHY = 2;
}

message DependencyStatus {
    string name = 1;  // Provider name
    HealthStatus health = 2;
    int32 latency = 3;  // Milliseconds
}

enum HealthStatus {
    HEALTHY = 0;
    UNHEALTHY = 1;
    UNKNOWN = 2;
}

Behavior:

  • Current: Stub implementation, always returns HEALTHY
  • Planned: Ping each provider, measure latency, return actual health status

Example:

resp, err := client.GetServiceStatus(ctx, &pb.Empty{})
// resp.Status = HEALTHY
// resp.Dependencies = [
//   {Name: "spotify", Health: HEALTHY, Latency: 0},
//   {Name: "soundcloud", Health: HEALTHY, Latency: 0},
//   ...
// ]

Authentication Operations

Register

Request:

message AuthRequest {
    string email = 1;
    string password = 2;
}

Response:

message AuthResponse {
    string access_token = 1;  // JWT, 15 minute expiry
    string refresh_token = 2;  // JWT, 7 day expiry
    User user = 3;
}

message User {
    string id = 1;  // UUID
    string email = 2;
    string role = 3;  // "user" or "admin"
    bool is_verified = 4;
}

Behavior:

  • Validates email format
  • Hashes password with bcrypt (cost 10)
  • Stores user in PostgreSQL
  • Generates JWT access and refresh tokens
  • Returns tokens and user info

Validation:

  • Email must be valid format
  • Email must be unique (returns error if exists)
  • No password requirements (any length, no complexity rules)

Example:

resp, err := client.Register(ctx, &pb.AuthRequest{
    Email:    "user@example.com",
    Password: "password123",
})
// resp.AccessToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
// resp.RefreshToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

Login

Request: Same as Register

Response: Same as Register

Behavior:

  • Fetches user by email from PostgreSQL
  • Verifies password with bcrypt
  • Generates new JWT access and refresh tokens
  • Returns tokens and user info

Security:

  • No rate limiting (brute force possible)
  • No account lockout after failed attempts
  • No login attempt logging

Example:

resp, err := client.Login(ctx, &pb.AuthRequest{
    Email:    "user@example.com",
    Password: "password123",
})

RefreshToken

Request:

message RefreshTokenRequest {
    string refresh_token = 1;
}

Response: Same as AuthResponse

Behavior:

  • Validates refresh token signature and expiration
  • Extracts user ID and email from token claims
  • Generates new access and refresh tokens
  • Returns new tokens

Token Rotation: Both access and refresh tokens are regenerated (refresh token rotation).

Example:

resp, err := client.RefreshToken(ctx, &pb.RefreshTokenRequest{
    RefreshToken: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
})
// resp.AccessToken = new 15-minute token
// resp.RefreshToken = new 7-day token

HTTP Proxy Endpoints

Stream Proxy

Endpoint: GET /stream/{service}/{id}

Parameters:

  • service: Platform name (spotify, soundcloud, deezer, youtube)
  • id: Native track ID (not namespaced)

Headers:

  • Range: Optional, for seeking (e.g., "bytes=0-1023")

Response:

  • 200 OK: Full stream
  • 206 Partial Content: Range response
  • 400 Bad Request: Invalid service or ID
  • 404 Not Found: Stream not found
  • 500 Internal Server Error: Upstream failure

Behavior:

  • Constructs namespaced ID from service and ID
  • Calls GetStreamURL gRPC method
  • Proxies stream from provider
  • Forwards range requests to upstream
  • Streams response to client

Example:

curl http://localhost:8080/stream/soundcloud/1234567890 \
  -H "Range: bytes=0-1023" \
  -o audio.mp3

Cover Proxy

Endpoint: GET /cover/{service}/{id}

Parameters:

  • service: Platform name
  • id: Album or track ID

Response: Same status codes as stream proxy

Behavior:

  • Fetches album/track metadata
  • Extracts cover URL
  • Proxies image from provider
  • Supports range requests

Example:

curl http://localhost:8080/cover/spotify/3n3Ppam7vgaVa1iaRUc9Lp \
  -o cover.jpg

Platform Enum

enum Platform {
    SPOTIFY = 0;
    YANDEX = 1;
    VK = 2;
    DEEZER = 3;
    SOUNDCLOUD = 4;
    YOUTUBE = 5;
}

Active Platforms: SPOTIFY, DEEZER, SOUNDCLOUD, YOUTUBE
Stub Platforms: YANDEX, VK

Response Status Enum

enum ResponseStatus {
    OK = 0;       // All providers succeeded
    PARTIAL = 1;  // Some providers failed, some succeeded
    ERROR = 2;    // All providers failed
}

Usage: All search and multi-provider operations return this status

Client Handling:

switch resp.Status {
case pb.ResponseStatus_OK:
    // Use resp.Tracks/Albums/Artists
case pb.ResponseStatus_PARTIAL:
    // Use resp.Tracks/Albums/Artists (partial results)
    // Log resp.Errors for debugging
case pb.ResponseStatus_ERROR:
    // No results available
    // Check resp.Errors for failure reasons
}

Error Handling

gRPC Status Codes

Code Scenario
OK Successful operation
Unauthenticated Missing or invalid JWT token
InvalidArgument Invalid request parameters
NotFound Entity not found
Internal Server error

Provider Errors

message ProviderError {
    string provider = 1;  // "spotify", "soundcloud", etc.
    string message = 2;   // Error description
}

Included In: All search and multi-provider responses

Example:

resp, err := client.SearchTracks(ctx, &pb.SearchRequest{Query: "test"})
if err != nil {
    // gRPC-level error
    return err
}

if resp.Status == pb.ResponseStatus_PARTIAL {
    for _, providerErr := range resp.Errors {
        log.Printf("Provider %s failed: %s", providerErr.Provider, providerErr.Message)
    }
}

Authentication Flow

Initial Registration

Client                          Server
  |                               |
  |-- Register(email, password)-->|
  |                               |
  |                               |-- Hash password (bcrypt)
  |                               |-- Store in PostgreSQL
  |                               |-- Generate access token (15min)
  |                               |-- Generate refresh token (7 days)
  |                               |
  |<-- access_token, refresh_token|
  |                               |

Authenticated Request

Client                          Server
  |                               |
  |-- SearchTracks(query) ------->|
  |   + metadata:                 |
  |     authorization: Bearer <token>
  |                               |
  |                               |-- authInterceptor validates JWT
  |                               |-- Extract user claims
  |                               |-- Execute search
  |                               |
  |<-- SearchTracksResponse ------|
  |                               |

Token Refresh

Client                          Server
  |                               |
  |-- RefreshToken(refresh_token)|
  |                               |
  |                               |-- Validate refresh token
  |                               |-- Extract user claims
  |                               |-- Generate new access token
  |                               |-- Generate new refresh token
  |                               |
  |<-- new access_token, refresh_token
  |                               |

Client Implementation Examples

Go Client

package main

import (
    "context"
    "log"
    
    "google.golang.org/grpc"
    "google.golang.org/grpc/metadata"
    pb "github.com/feralbureau/bedrock-api/proto"
)

func main() {
    conn, err := grpc.Dial("localhost:50052", grpc.WithInsecure())
    if err != nil {
        log.Fatal(err)
    }
    defer conn.Close()
    
    client := pb.NewBedrockServiceClient(conn)
    
    // Register
    authResp, err := client.Register(context.Background(), &pb.AuthRequest{
        Email:    "user@example.com",
        Password: "password123",
    })
    if err != nil {
        log.Fatal(err)
    }
    
    accessToken := authResp.AccessToken
    
    // Authenticated request
    ctx := metadata.AppendToOutgoingContext(context.Background(), "authorization", "Bearer "+accessToken)
    
    searchResp, err := client.SearchTracks(ctx, &pb.SearchRequest{
        Query: "Bohemian Rhapsody",
        Limit: 10,
    })
    if err != nil {
        log.Fatal(err)
    }
    
    for _, track := range searchResp.Tracks {
        log.Printf("%s - %s (%s)", track.Artist, track.Title, track.Platform)
    }
}

Python Client

import grpc
import bedrock_pb2
import bedrock_pb2_grpc

channel = grpc.insecure_channel('localhost:50052')
client = bedrock_pb2_grpc.BedrockServiceStub(channel)

# Register
auth_resp = client.Register(bedrock_pb2.AuthRequest(
    email='user@example.com',
    password='password123'
))

access_token = auth_resp.access_token

# Authenticated request
metadata = [('authorization', f'Bearer {access_token}')]

search_resp = client.SearchTracks(
    bedrock_pb2.SearchRequest(query='Bohemian Rhapsody', limit=10),
    metadata=metadata
)

for track in search_resp.tracks:
    print(f"{track.artist} - {track.title} ({track.platform})")

JavaScript Client (Node.js)

const grpc = require('@grpc/grpc-js');
const protoLoader = require('@grpc/proto-loader');

const packageDefinition = protoLoader.loadSync('bedrock_service.proto');
const bedrock = grpc.loadPackageDefinition(packageDefinition).bedrock;

const client = new bedrock.BedrockService('localhost:50052', grpc.credentials.createInsecure());

// Register
client.Register({ email: 'user@example.com', password: 'password123' }, (err, authResp) => {
    if (err) throw err;
    
    const accessToken = authResp.access_token;
    
    // Authenticated request
    const metadata = new grpc.Metadata();
    metadata.add('authorization', `Bearer ${accessToken}`);
    
    client.SearchTracks(
        { query: 'Bohemian Rhapsody', limit: 10 },
        metadata,
        (err, searchResp) => {
            if (err) throw err;
            
            searchResp.tracks.forEach(track => {
                console.log(`${track.artist} - ${track.title} (${track.platform})`);
            });
        }
    );
});

Rate Limiting

Current: No rate limiting implemented

Risks:

  • Provider API rate limits can be exceeded
  • No protection against abuse
  • No per-user quotas

Recommendations:

  • Implement per-user rate limiting (e.g., 100 requests/minute)
  • Implement per-IP rate limiting for unauthenticated endpoints
  • Cache responses to reduce provider API calls
  • Implement circuit breakers for failing providers

Pagination

Current: No pagination support

Limitations:

  • Search results limited by limit parameter (max 50)
  • No cursor or offset-based pagination
  • Large result sets cannot be retrieved incrementally

Workarounds:

  • Increase limit parameter (up to 50)
  • Make multiple searches with different queries

Recommendations:

  • Add cursor-based pagination for search results
  • Add offset/limit pagination for playlists and albums
  • Return total result count in responses

Versioning

Current: No API versioning

Implications:

  • Breaking changes affect all clients
  • No backward compatibility guarantees
  • No deprecation path for old endpoints

Recommendations:

  • Add version to package name (e.g., bedrock.v1)
  • Support multiple versions simultaneously
  • Document breaking changes and migration paths

Performance Characteristics

Response Times (Typical)

Operation Latency Notes
SearchTracks 200-500ms Parallel provider queries
GetTrack 100-300ms Single provider query
GetStreamURL 200-800ms Includes bridge resolution
GetLyrics 1-3s Genius API can be slow
GetSyncedLyrics 100-500ms LrcLib is fast
Register/Login 100-200ms bcrypt hashing overhead

Payload Sizes (Typical)

Operation Response Size Notes
SearchTracks (10 results) 5-10 KB Depends on metadata richness
GetAlbum (with tracks) 20-100 KB Depends on track count
GetArtist (with discography) 50-500 KB Can be very large
GetPlaylist (100 tracks) 50-100 KB Includes full track metadata
GetLyrics 2-10 KB Plain text
GetSyncedLyrics 5-20 KB Timestamped lines

Security Considerations

Authentication

  • JWT tokens transmitted in gRPC metadata
  • No TLS by default (tokens sent in plaintext)
  • No token revocation mechanism
  • No refresh token rotation (fixed 7-day expiry)

Authorization

  • No role-based access control (RBAC)
  • All authenticated users have same permissions
  • No resource ownership checks
  • No admin-only endpoints

Input Validation

  • No query sanitization (SQL injection risk if queries touch DB)
  • No limit enforcement on request parameters
  • No URL validation for ImportPlaylist

Recommendations

  • Deploy behind TLS-terminating reverse proxy
  • Implement token revocation list (Redis)
  • Add RBAC for admin operations
  • Validate and sanitize all inputs
  • Add request size limits