spotify/server/dist/routes/auth.js
2025-10-16 13:07:44 +02:00

92 lines
4.3 KiB
JavaScript

import { Router } from 'express';
import axios from 'axios';
import jwt from 'jsonwebtoken';
import { db } from '../lib/db.js';
export const authRouter = Router();
const SPOTIFY_CLIENT_ID = process.env.SPOTIFY_CLIENT_ID || '';
const SPOTIFY_CLIENT_SECRET = process.env.SPOTIFY_CLIENT_SECRET || '';
const REDIRECT_URI = process.env.REDIRECT_URI || 'http://localhost:3000/callback';
const JWT_SECRET = process.env.JWT_SECRET || 'dev-secret-change';
authRouter.post('/exchange', async (req, res) => {
try {
const { code } = req.body;
if (!code)
return res.status(400).json({ error: 'Missing code' });
const tokenResp = await axios.post('https://accounts.spotify.com/api/token', new URLSearchParams({
grant_type: 'authorization_code',
code,
redirect_uri: REDIRECT_URI,
}), {
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
Authorization: `Basic ${Buffer.from(`${SPOTIFY_CLIENT_ID}:${SPOTIFY_CLIENT_SECRET}`).toString('base64')}`,
},
});
const { access_token, refresh_token, expires_in } = tokenResp.data;
const meResp = await axios.get('https://api.spotify.com/v1/me', {
headers: { Authorization: `Bearer ${access_token}` },
});
const profile = meResp.data;
const now = Date.now();
const tokenExpiresAt = now + expires_in * 1000 - 60000; // refresh a bit early
db.db
.prepare(`INSERT INTO users (id, display_name, email, avatar_url, country, product, created_at, updated_at, access_token, refresh_token, token_expires_at)
VALUES (@id, @display_name, @email, @avatar_url, @country, @product, @created_at, @updated_at, @access_token, @refresh_token, @token_expires_at)
ON CONFLICT(id) DO UPDATE SET
display_name=excluded.display_name,
email=excluded.email,
avatar_url=excluded.avatar_url,
country=excluded.country,
product=excluded.product,
updated_at=excluded.updated_at,
access_token=excluded.access_token,
refresh_token=excluded.refresh_token,
token_expires_at=excluded.token_expires_at`)
.run({
id: profile.id,
display_name: profile.display_name,
email: profile.email,
avatar_url: profile.images?.[0]?.url || null,
country: profile.country,
product: profile.product,
created_at: now,
updated_at: now,
access_token,
refresh_token,
token_expires_at: tokenExpiresAt,
});
const jwtToken = jwt.sign({ uid: profile.id }, JWT_SECRET, { expiresIn: '30d' });
// Return Spotify tokens to enable client-side playback controls
res.json({ token: jwtToken, uid: profile.id, access_token, refresh_token, expires_in });
}
catch (err) {
const detail = err?.response?.data || err?.message || 'Unknown error';
console.error('Auth exchange error:', detail);
res.status(500).json({ error: 'Auth exchange failed', detail });
}
});
authRouter.post('/refresh', async (req, res) => {
try {
const { uid } = req.body;
if (!uid)
return res.status(400).json({ error: 'Missing uid' });
const row = db.db.prepare('SELECT refresh_token FROM users WHERE id = ?').get(uid);
if (!row?.refresh_token)
return res.status(404).json({ error: 'User not found' });
const tokenResp = await axios.post('https://accounts.spotify.com/api/token', new URLSearchParams({ grant_type: 'refresh_token', refresh_token: row.refresh_token }), {
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
Authorization: `Basic ${Buffer.from(`${SPOTIFY_CLIENT_ID}:${SPOTIFY_CLIENT_SECRET}`).toString('base64')}`,
},
});
const { access_token, expires_in } = tokenResp.data;
const tokenExpiresAt = Date.now() + expires_in * 1000 - 60000;
db.db.prepare('UPDATE users SET access_token=?, token_expires_at=?, updated_at=? WHERE id=?').run(access_token, tokenExpiresAt, Date.now(), uid);
res.json({ ok: true });
}
catch (err) {
console.error(err.response?.data || err.message);
res.status(500).json({ error: 'Refresh failed' });
}
});