attempted upgrade

This commit is contained in:
yohlo
2025-09-24 00:13:41 -05:00
parent 94ea44c66e
commit e4164cbc71
26 changed files with 1390 additions and 1273 deletions

View File

@@ -1,127 +1,146 @@
import { createServerFileRoute } from '@tanstack/react-start/server'
import { SpotifyAuth } from '@/lib/spotify/auth'
import { createFileRoute } from "@tanstack/react-router";
import { SpotifyAuth } from "@/lib/spotify/auth";
const SPOTIFY_CLIENT_ID = import.meta.env.VITE_SPOTIFY_CLIENT_ID!
const SPOTIFY_CLIENT_SECRET = process.env.SPOTIFY_CLIENT_SECRET!
const SPOTIFY_REDIRECT_URI = import.meta.env.VITE_SPOTIFY_REDIRECT_URI!
const SPOTIFY_CLIENT_ID = import.meta.env.VITE_SPOTIFY_CLIENT_ID!;
const SPOTIFY_CLIENT_SECRET = import.meta.env.SPOTIFY_CLIENT_SECRET!;
const SPOTIFY_REDIRECT_URI = import.meta.env.VITE_SPOTIFY_REDIRECT_URI!;
export const ServerRoute = createServerFileRoute('/api/spotify/callback').methods({
GET: async ({ request }: { request: Request }) => {
const getReturnPath = (state: string | null): string => {
if (!state) return '/';
try {
const decodedState = JSON.parse(atob(state));
return decodedState.returnPath || '/';
} catch {
return '/';
}
};
export const Route = createFileRoute("/api/spotify/callback")({
server: {
handlers: {
GET: async ({ request }: { request: Request }) => {
const getReturnPath = (state: string | null): string => {
if (!state) return "/";
try {
const decodedState = JSON.parse(atob(state));
return decodedState.returnPath || "/";
} catch {
return "/";
}
};
try {
const url = new URL(request.url)
const code = url.searchParams.get('code')
const state = url.searchParams.get('state')
const error = url.searchParams.get('error')
try {
const url = new URL(request.url);
const code = url.searchParams.get("code");
const state = url.searchParams.get("state");
const error = url.searchParams.get("error");
const returnPath = getReturnPath(state);
const returnPath = getReturnPath(state);
if (error) {
console.error('Spotify OAuth error:', error)
return new Response(null, {
status: 302,
headers: {
'Location': returnPath + '?spotify_error=' + encodeURIComponent(error),
},
})
}
if (error) {
console.error("Spotify OAuth error:", error);
return new Response(null, {
status: 302,
headers: {
Location:
returnPath + "?spotify_error=" + encodeURIComponent(error),
},
});
}
if (!code || !state) {
console.error('Missing code or state:', { code: !!code, state: !!state })
return new Response(null, {
status: 302,
headers: {
'Location': returnPath + '?spotify_error=missing_code_or_state',
},
})
}
if (!code || !state) {
console.error("Missing code or state:", {
code: !!code,
state: !!state,
});
return new Response(null, {
status: 302,
headers: {
Location: returnPath + "?spotify_error=missing_code_or_state",
},
});
}
console.log('Token exchange attempt:', {
client_id: SPOTIFY_CLIENT_ID,
redirect_uri: SPOTIFY_REDIRECT_URI,
has_code: !!code,
has_state: !!state,
})
console.log("Token exchange attempt:", {
client_id: SPOTIFY_CLIENT_ID,
redirect_uri: SPOTIFY_REDIRECT_URI,
has_code: !!code,
has_state: !!state,
});
const tokenResponse = await fetch('https://accounts.spotify.com/api/token', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': `Basic ${Buffer.from(`${SPOTIFY_CLIENT_ID}:${SPOTIFY_CLIENT_SECRET}`).toString('base64')}`,
},
body: new URLSearchParams({
grant_type: 'authorization_code',
code,
redirect_uri: SPOTIFY_REDIRECT_URI,
}),
})
const tokenResponse = await fetch(
"https://accounts.spotify.com/api/token",
{
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
Authorization: `Basic ${Buffer.from(`${SPOTIFY_CLIENT_ID}:${SPOTIFY_CLIENT_SECRET}`).toString("base64")}`,
},
body: new URLSearchParams({
grant_type: "authorization_code",
code,
redirect_uri: SPOTIFY_REDIRECT_URI,
}),
}
);
if (!tokenResponse.ok) {
const errorText = await tokenResponse.text()
console.error('Token exchange error:', {
status: tokenResponse.status,
statusText: tokenResponse.statusText,
body: errorText,
redirect_uri: SPOTIFY_REDIRECT_URI,
})
const errorParam = encodeURIComponent(`${tokenResponse.status}: ${errorText}`)
return new Response(null, {
status: 302,
headers: {
'Location': `${returnPath}?spotify_error=token_exchange_failed&details=${errorParam}`,
},
})
}
if (!tokenResponse.ok) {
const errorText = await tokenResponse.text();
console.error("Token exchange error:", {
status: tokenResponse.status,
statusText: tokenResponse.statusText,
body: errorText,
redirect_uri: SPOTIFY_REDIRECT_URI,
});
const tokens = await tokenResponse.json()
const errorParam = encodeURIComponent(
`${tokenResponse.status}: ${errorText}`
);
return new Response(null, {
status: 302,
headers: {
Location: `${returnPath}?spotify_error=token_exchange_failed&details=${errorParam}`,
},
});
}
console.log('Token exchange successful:', {
has_access_token: !!tokens.access_token,
has_refresh_token: !!tokens.refresh_token,
expires_in: tokens.expires_in,
})
const tokens = await tokenResponse.json();
console.log('Decoded return path:', returnPath);
console.log("Token exchange successful:", {
has_access_token: !!tokens.access_token,
has_refresh_token: !!tokens.refresh_token,
expires_in: tokens.expires_in,
});
const response = new Response(null, {
status: 302,
headers: {
'Location': returnPath + '?spotify_auth=success',
},
})
console.log("Decoded return path:", returnPath);
const isSecure = process.env.NODE_ENV === 'production'
const cookieOptions = `HttpOnly; Secure=${isSecure}; SameSite=Strict; Path=/; Max-Age=${tokens.expires_in}`
const response = new Response(null, {
status: 302,
headers: {
Location: returnPath + "?spotify_auth=success",
},
});
response.headers.append('Set-Cookie', `spotify_access_token=${tokens.access_token}; ${cookieOptions}`)
if (tokens.refresh_token) {
const refreshCookieOptions = `HttpOnly; Secure=${isSecure}; SameSite=Strict; Path=/; Max-Age=${60 * 60 * 24 * 30}` // 30 days
response.headers.append('Set-Cookie', `spotify_refresh_token=${tokens.refresh_token}; ${refreshCookieOptions}`)
}
const isSecure = import.meta.env.NODE_ENV === "production";
const cookieOptions = `HttpOnly; Secure=${isSecure}; SameSite=Strict; Path=/; Max-Age=${tokens.expires_in}`;
return response
} catch (error) {
console.error('Spotify callback error:', error)
const url = new URL(request.url);
const state = url.searchParams.get('state');
const returnPath = getReturnPath(state);
return new Response(null, {
status: 302,
headers: {
'Location': returnPath + '?spotify_error=callback_failed',
},
})
}
response.headers.append(
"Set-Cookie",
`spotify_access_token=${tokens.access_token}; ${cookieOptions}`
);
if (tokens.refresh_token) {
const refreshCookieOptions = `HttpOnly; Secure=${isSecure}; SameSite=Strict; Path=/; Max-Age=${60 * 60 * 24 * 30}`; // 30 days
response.headers.append(
"Set-Cookie",
`spotify_refresh_token=${tokens.refresh_token}; ${refreshCookieOptions}`
);
}
return response;
} catch (error) {
console.error("Spotify callback error:", error);
const url = new URL(request.url);
const state = url.searchParams.get("state");
const returnPath = getReturnPath(state);
return new Response(null, {
status: 302,
headers: {
Location: returnPath + "?spotify_error=callback_failed",
},
});
}
},
},
},
})
});