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
23 changes: 18 additions & 5 deletions backend/.env.example
Original file line number Diff line number Diff line change
@@ -1,6 +1,19 @@

# Environment
NODE_ENV=development
MONGODB_URI=
JWT_SECRET=
STARKNET_MAINNET_RPC_API_URL=
STARKNET_SEPOLIA_RPC_API_URL=

# Database
MONGODB_URI=mongodb://localhost:27017/musicstrk

# JWT Secret (generate a secure random string)
JWT_SECRET=your_super_secret_jwt_key_here_make_it_long_and_random

# Starknet RPC URLs
STARKNET_MAINNET_RPC_API_URL=https://starknet-mainnet.public.blastapi.io
STARKNET_SEPOLIA_RPC_API_URL=https://starknet-sepolia.public.blastapi.io

# TikTok OAuth
TIKTOK_CLIENT_ID=your_tiktok_client_id_here
TIKTOK_CLIENT_SECRET=your_tiktok_client_secret_here

# Frontend URL
FRONTEND_URL=http://localhost:3000
3 changes: 3 additions & 0 deletions backend/src/routes/v1/auth.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Router, Request } from "express";
import { BigNumberish, constants, ec, Provider } from "starknet";
import TikTokAuthRoutes from "./auth/tiktok";

import UserModel, { createUser, findUserByaddress } from "models/UserModel";
import { AUTHENTICATION_SNIP12_MESSAGE } from "constants/index";
Expand All @@ -24,6 +25,8 @@ type ReqBody_Authenticate = {
signature: string[];
};

AuthRoutes.use("/tiktok", TikTokAuthRoutes)

AuthRoutes.post(
"/authenticate",
async (req: Request<{}, {}, ReqBody_Authenticate>, res: any) => {
Expand Down
108 changes: 108 additions & 0 deletions backend/src/routes/v1/auth/tiktok.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import { Router, Request, Response } from "express";

const TikTokAuthRoutes = Router();

interface TikTokTokenResponse {
access_token: string;
open_id: string;
scope: string;
expires_in: number;
}

interface TikTokUserInfo {
open_id: string;
username: string;
display_name: string;
avatar_url: string;
}

// Exchange authorization code for access token
TikTokAuthRoutes.post("/token", async (req: Request, res: Response): Promise<void> => {
try {
const { code } = req.body;

if (!code) {
res.status(400).json({ success: false, error: "Authorization code is required" });
return;
}

const clientId = process.env.TIKTOK_CLIENT_ID;
const clientSecret = process.env.TIKTOK_CLIENT_SECRET;
const redirectUri = `${process.env.FRONTEND_URL || 'http://localhost:3000'}/auth/tiktok/callback`;

if (!clientId || !clientSecret) {
res.status(500).json({ success: false, error: "TikTok configuration missing" });
return;
}

console.log("Exchanging TikTok authorization code for token...");

// Exchange code for access token
const tokenResponse = await fetch("https://open-api.tiktok.com/oauth/access_token/", {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
body: new URLSearchParams({
client_key: clientId,
client_secret: clientSecret,
code: code,
grant_type: "authorization_code",
redirect_uri: redirectUri,
}),
});

const tokenData = await tokenResponse.json() as any;

if (!tokenResponse.ok || tokenData.error) {
console.error("Token exchange failed:", tokenData);
res.status(400).json({
success: false,
error: tokenData.error_description || "Failed to exchange authorization code"
});
return;
}

const { access_token, open_id } = tokenData.data as TikTokTokenResponse;

console.log("Token exchange successful, fetching user info...");

// Get user information
const userResponse = await fetch(`https://open-api.tiktok.com/user/info/?access_token=${access_token}&open_id=${open_id}`);
const userData = await userResponse.json() as any;

if (!userResponse.ok || userData.error) {
console.error("User info fetch failed:", userData);
res.status(400).json({
success: false,
error: "Failed to fetch user information"
});
return;
}

const userInfo = userData.data.user;

const authResult = {
accessToken: access_token,
openId: open_id,
userInfo: {
openId: open_id,
username: userInfo.username,
displayName: userInfo.display_name,
avatarUrl: userInfo.avatar_url,
},
};

console.log("TikTok authentication successful for user:", userInfo.username);

res.json({ success: true, authResult });
} catch (error) {
console.error("TikTok token exchange error:", error);
res.status(500).json({
success: false,
error: "Internal server error during token exchange"
});
}
});

export default TikTokAuthRoutes;
11 changes: 0 additions & 11 deletions dashboard/src/components/layout/data/sidebar-data.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,13 @@
import {
IconBarrierBlock,
IconBrowserCheck,
IconBug,
IconChecklist,
IconError404,
IconHelp,
IconLayoutDashboard,
IconLock,
IconLockAccess,
IconMessages,
IconNotification,
IconPackages,
IconPalette,
IconServerOff,
IconSettings,
IconTool,
IconUserCog,
IconUserOff,
IconUsers,
IconUser,
} from '@tabler/icons-react'
import {
AudioWaveform,
Expand Down
1 change: 0 additions & 1 deletion dashboard/src/components/layout/nav-group.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {
} from '@/components/ui/collapsible'
import {
SidebarGroup,
SidebarGroupLabel,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
Expand Down
6 changes: 2 additions & 4 deletions dashboard/src/components/layout/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { LinkProps } from '@tanstack/react-router'

interface User {
name: string
email: string
Expand All @@ -19,12 +17,12 @@ interface BaseNavItem {
}

type NavLink = BaseNavItem & {
url: LinkProps['to']
url: string
items?: never
}

type NavCollapsible = BaseNavItem & {
items: (BaseNavItem & { url: LinkProps['to'] })[]
items: (BaseNavItem & { url: string })[]
url?: never
}

Expand Down
44 changes: 22 additions & 22 deletions dashboard/src/context/auth-context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
useContext,
useEffect,
useState,
useCallback,
ReactNode,
} from 'react'
import { useAccount, useConnect, useDisconnect } from '@starknet-react/core'
Expand Down Expand Up @@ -43,23 +44,7 @@
}
}, [isConnected, status])

// Handle the completion or failure of a wallet connection
useEffect(() => {
if (connectingWallet) {
if (status === 'connected' && account && address) {
// Proceed with authentication
completeAuthentication()
} else if (status === 'disconnected' && !isLoading) {
// Connection failed or cancelled
console.log('Connection failed or cancelled')
setConnectingWallet(false)
setError('Wallet connection was cancelled or failed')
setIsLoading(false)
}
}
}, [status, connectingWallet, account, address])

const completeAuthentication = async () => {
const completeAuthentication = useCallback(async () => {
try {
setConnectingWallet(false)
if (isConnected && account && address) {
Expand All @@ -72,14 +57,29 @@
setError(null)
}
} catch (err) {
console.error('Authentication error:', err)
// Removed console.error for lint compliance
const errorMessage =
err instanceof Error ? err.message : 'Authentication failed'
setError(errorMessage)
} finally {
setIsLoading(false)
}
}
}, [isConnected, account, address])

// Handle the completion or failure of a wallet connection
useEffect(() => {
if (connectingWallet) {
if (status === 'connected' && account && address) {
// Proceed with authentication
completeAuthentication()
} else if (status === 'disconnected' && !isLoading) {
// Connection failed or cancelled
setConnectingWallet(false)
setError('Wallet connection was cancelled or failed')
setIsLoading(false)
}
}
}, [status, connectingWallet, account, address, completeAuthentication, isLoading])

const signIn = async (connectorId: string) => {
try {
Expand All @@ -91,7 +91,7 @@
throw new Error('Wallet not found')
}

if (typeof window === 'undefined' || !(window as any).starknet) {
if (typeof window === 'undefined' || !(window as { starknet?: unknown }).starknet) {
throw new Error(
'Wallet provider not detected. Please install Argent X or Braavos.'
)
Expand All @@ -101,7 +101,7 @@
setConnectingWallet(true)
connect({ connector })
} catch (err) {
console.error('Connection error:', err)
// Removed console.error for lint compliance
setConnectingWallet(false)
setIsLoading(false)
const errorMessage =
Expand All @@ -118,7 +118,7 @@
setIsAuthenticated(false)
setError(null)
} catch (err) {
console.error('Sign out error:', err)
// Removed console.error for lint compliance
const errorMessage =
err instanceof Error ? err.message : 'Failed to sign out'
setError(errorMessage)
Expand All @@ -142,7 +142,7 @@
)
}

export function useAuth() {

Check warning on line 145 in dashboard/src/context/auth-context.tsx

View workflow job for this annotation

GitHub Actions / build-dashboard

Fast refresh only works when a file only exports components. Use a new file to share constants or functions between components
const context = useContext(AuthContext)
if (context === undefined) {
throw new Error('useAuth must be used within an AuthProvider')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ export function WalletModal({ isOpen, onClose }: WalletModalProps) {
setError(null);
await signIn(connectorId);
} catch (err) {
console.error('Sign in error caught in modal:', err);
const errorMessage =
err instanceof Error && err.message === 'Wallet connection was cancelled'
? 'Connection cancelled. Please try again.'
Expand Down
2 changes: 1 addition & 1 deletion dashboard/src/features/dashboard/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { ProfileDropdown } from '@/components/profile-dropdown'
import { ThemeSwitch } from '@/components/theme-switch'
import { ConnectWalletButton } from './components/connect-wallet-button'
import { WalletModal } from './components/wallet-modal'
import { Check, ChevronLeft, Copy, RotateCcw } from "lucide-react";
import { Check, Copy } from "lucide-react";
import { useAccount } from '@starknet-react/core'

function CopyAddressButton() {
Expand Down
11 changes: 11 additions & 0 deletions webapp/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# TikTok OAuth Configuration
NEXT_PUBLIC_TIKTOK_CLIENT_ID=your_tiktok_client_id_here
NEXT_PUBLIC_TIKTOK_REDIRECT_URI=http://localhost:3000/auth/tiktok/callback

# Server-side TikTok Config (for API routes)
TIKTOK_CLIENT_ID=your_tiktok_client_id_here
TIKTOK_CLIENT_SECRET=your_tiktok_client_secret_here

# Backend URLs
BACKEND_URL=http://localhost:8080
NEXT_PUBLIC_BACKEND_URL=http://localhost:8080
1 change: 1 addition & 0 deletions webapp/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ yarn-debug.log*
yarn-error.log*

# local env files
.env
.env*.local

# vercel
Expand Down
Loading