Skip to content

The most advanced Spotify Car Thing mod - Restore functionality to your Spotify Car Thing!

License

Notifications You must be signed in to change notification settings

usenocturne/nocturne-ui

Repository files navigation


Nocturne
nocturne

A web application for nocturne-image and the Spotify Car Thing.

How To UseSpotify Developer SetupLocal Development SetupCreditsRelatedLicense

screenshot screenshot screenshot

How To Use

Custom Credentials

  1. First, follow the steps in Spotify Developer Setup
  2. Follow the steps for your operating system in the image's repo
  3. Once running on your Car Thing, press "Use Custom Credentials" and enter your Spotify Client ID and Client Secret using VNC Viewer
  4. Your credentials will be encrypted and stored securely
  5. Authorize with Spotify
  6. Start using the app

Default Credentials (Beta)

It is not recommended to use default credentials at this point in time. Unless you know what you're doing, please use custom credentials

  1. First, follow the steps in Spotify Developer Setup
  2. Follow the steps for your operating system in the image's repo.
  3. Once running on your Car Thing, hold the back button (under the knob) until the "Use Default Credentials (Beta)" button appears
  4. Press the "Use Default Credentials (Beta)" butto
  5. Authorize with Spotify
  6. Start using the app

Button Mapping and Button Usage

  • Hold one of the top hardware preset buttons while on a playlist page to map it to the button
  • Press the mapped buttons to quickly play playlists
  • Hold the right-most top hardware button to access settings
  • Triple-press the right-most top hardware button to access brightness control
  • Press the hardware button underneath of the knob to go back in the application

Spotify Developer Setup

  1. Go to the Spotify Developer Dashboard

  2. Log in with your Spotify account (create one if needed)

  3. Click "Create App"

    • App name: Choose a name (e.g., "Nocturne")
    • App description: Brief description of your app
    • Redirect URI: Add https://nocturne.brandons.place for non-development usage, or https://your.local.ip.address:port for development usage
    • Select "Web API" as the API you will be using
  4. After creating the app, you'll see your:

    • Client ID (shown on the dashboard)
    • Client Secret (click "Show Client Secret")
  5. Required Scopes: The app needs these Spotify scopes:

    user-read-recently-played
    user-read-private
    user-top-read
    user-read-playback-state
    user-modify-playback-state
    user-read-currently-playing
    user-library-read
    user-library-modify
    playlist-read-private
    playlist-read-collaborative
    playlist-modify-public
    playlist-modify-private
    

Local Development Setup

  1. Clone the repository:
git clone https://github.com/yourusername/nocturne-ui.git
cd nocturne
  1. Install dependencies:
npm install
# or
yarn install
# or
bun install
  1. Copy the environment example file:
cp .env.example .env.local
  1. Spotify authentication requires HTTPS, follow these steps to set up HTTPS locally for development:
# macOS
brew install mkcert

# Windows (using chocolatey)
choco install mkcert

# Linux
apt install mkcert
# Create and install local CA
mkcert -install

# Generate certificates for localhost and your local IP
mkcert localhost 127.0.0.1 ::1 your.local.ip.address
# Rename the generated certificates
mv localhost+3.pem cert.crt
mv localhost+3-key.pem cert.key

# Copy CA certificates (locations vary by OS)
# macOS
cp "$(mkcert -CAROOT)/rootCA.pem" ca.crt
cp "$(mkcert -CAROOT)/rootCA-key.pem" ca.key

# Windows
copy "%LOCALAPPDATA%\mkcert\rootCA.pem" ca.crt
copy "%LOCALAPPDATA%\mkcert\rootCA-key.pem" ca.key

# Linux
cp "$(mkcert -CAROOT)/rootCA.pem" ca.crt
cp "$(mkcert -CAROOT)/rootCA-key.pem" ca.key
# Add to .gitignore
*.crt
*.key
*.pem

Create custom server file:

const { createServer } = require('https');
const { parse } = require('url');
const next = require('next');
const fs = require('fs');

const dev = process.env.NODE_ENV !== 'production';
const app = next({ dev });
const handle = app.getRequestHandler();

const httpsOptions = {
  key: fs.readFileSync('./cert.key'),
  cert: fs.readFileSync('./cert.crt')
};

app.prepare().then(() => {
  createServer(httpsOptions, (req, res) => {
    const parsedUrl = parse(req.url, true);
    handle(req, res, parsedUrl);
  }).listen(3000, (err) => {
    if (err) throw err;
    console.log('> Ready on https://localhost:3000');
    console.log('> Also available on https://your.local.ip.address:3000');
  });
});
  1. Set up your Supabase project:
    • Create a new project at Supabase
    • Create a new table named spotify_credentials with the following schema:
create table spotify_credentials (
  id uuid default uuid_generate_v4() primary key,
  temp_id text not null,
  client_id text not null,
  encrypted_client_secret text not null,
  refresh_token text,
  created_at timestamp with time zone default timezone('utc'::text, now()) not null,
  expires_at timestamp with time zone,
  last_used timestamp with time zone,
  first_used_at timestamp with time zone,
  token_refresh_count integer default 0,
  user_agent text
);

-- Add policies for Row Level Security (RLS)
alter table spotify_credentials enable row level security;

-- Cleanup expired records
create policy "Cleanup expired records"
on spotify_credentials for all
using (expires_at < current_timestamp or refresh_token = current_setting('app.current_refresh_token'::text, true));

-- Cleanup unused temp IDs
create policy "cleanup_unused_temp_ids"
on spotify_credentials for all
using (created_at < current_timestamp - interval '1 hour' and refresh_token is null);

-- Enforce expiration
create policy "Enforce expiration"
on spotify_credentials for all
using (expires_at is null or expires_at > current_timestamp);

-- Update restrictions
create policy "Restricted updates"
on spotify_credentials for update
using (temp_id is not null)
with check (
  client_id = (select client_id from spotify_credentials where id = id)
  and encrypted_client_secret = (select encrypted_client_secret from spotify_credentials where id = id)
  and temp_id = (select temp_id from spotify_credentials where id = id)
  and created_at = (select created_at from spotify_credentials where id = id)
);

-- Insert validation
create policy "Validated inserts"
on spotify_credentials for insert
with check (
  temp_id is not null 
  and client_id is not null 
  and encrypted_client_secret is not null 
  and length(temp_id) >= 7 
  and created_at <= current_timestamp 
  and (expires_at is null or expires_at > current_timestamp)
  and not exists (
    select 1 from spotify_credentials sc 
    where sc.temp_id = spotify_credentials.temp_id
  )
);
  1. Generate encryption keys:
# Generate 32-byte key for AES-256
openssl rand -hex 32

# Generate 16-byte IV
openssl rand -hex 16
  1. Update your .env.local with the required values:
# Supabase
NEXT_PUBLIC_SUPABASE_URL=your_supabase_url
SUPABASE_ANON_KEY=your_anon_key

# Spotify
NEXT_PUBLIC_SPOTIFY_CLIENT_ID=your_client_id
SPOTIFY_CLIENT_SECRET=your_client_secret
NEXT_PUBLIC_REDIRECT_URI=https://your.local.ip.address:port

# Encryption (from step 5)
ENCRYPTION_KEY=your_32_byte_hex_key
ENCRYPTION_IV=your_16_byte_hex_key
  1. Start the development server:
npm run dev
# or
yarn dev
# or
bun dev

Cloudflare Deployment Setup

  1. Install Wrangler CLI:
npm install -g wrangler
  1. Login to Cloudflare:
wrangler login
  1. Create a KV namespace for encryption keys:
cd workers/key-rotation
npx wrangler kv:namespace create ENCRYPTION_KEYS
  1. Update the KV namespace ID in your root wrangler.toml:
name = "nocturne"
compatibility_date = "2024-09-23"
compatibility_flags = ["nodejs_compat"]
pages_build_output_dir = ".vercel/output/static"

kv_namespaces = [
  { binding = "ENCRYPTION_KEYS", id = "your_namespace_id_here" }
]

[vars]
NEXT_PUBLIC_REDIRECT_URI = "your_production_url"
NEXT_PUBLIC_SUPABASE_URL = "your_supabase_url"
NEXT_PUBLIC_SPOTIFY_CLIENT_ID = "your_client_id"
  1. Set up environment secrets:
# For the main app
npx wrangler secret put SUPABASE_ANON_KEY
npx wrangler secret put SPOTIFY_CLIENT_SECRET
npx wrangler secret put ENCRYPTION_KEY
npx wrangler secret put ENCRYPTION_IV

# For the key rotation worker
cd workers/key-rotation
npx wrangler secret put ENCRYPTION_KEY
npx wrangler secret put ENCRYPTION_IV
  1. Deploy the key rotation worker:
cd workers/key-rotation
npx wrangler deploy

Key Rotation

The project includes automatic key rotation for encrypted credentials. The rotation worker runs every 7 days and:

  • Generates new encryption keys
  • Re-encrypts existing credentials
  • Maintains backups during rotation
  • Handles failures gracefully

The rotation schedule can be modified in workers/key-rotation/wrangler.toml.

Contributing

  1. Fork the repository
  2. Create a feature branch: git checkout -b feature/new-feature
  3. Commit your changes: git commit -m 'Add new feature'
  4. Push to the branch: git push origin feature/new-feature
  5. Open a Pull Request

Security

  • Client secrets are encrypted using AES-256-CBC
  • Keys are automatically rotated every 7 days
  • Credentials are stored with expiration dates
  • Unused credentials are automatically cleaned up
  • All sensitive operations happen server-side

Credits

This software was made possible only through the following individuals and open source programs:

Related

nocturne-image - The Debian image that runs this web application

License

This project is licensed under the GPL-3.0 license.

We kindly ask that any modifications or distributions made outside of direct forks from this repository include attribution to the original project in the README, as we have worked hard on this. :)


brandons.place  ·  GitHub @brandonsaldan  ·  Twitter @brandonsaldan