From 89b6deeaa1cc36670e37d1d531e68378d039586c Mon Sep 17 00:00:00 2001 From: Maharshi Mishra Date: Mon, 21 Oct 2024 21:24:10 +0530 Subject: [PATCH] Added Telegram server code, Mini app code --- .gitignore | 1 + .../{src => }/.env | 0 .../server/api/index.js | 78 +++++-- .../sfa-web-ton-telegram-example/src/App.css | 80 +++++++- .../sfa-web-ton-telegram-example/src/App.tsx | 194 ++++++++++-------- .../src/TelegramAppWrapper.tsx | 32 --- .../src/assets/Logo.svg | 16 ++ .../src/hooks/useClientOnce.ts | 9 + .../src/hooks/useMockTelegramInitData.ts | 90 +++++--- .../src/index.tsx | 7 +- .../src/tonRpc.ts | 11 +- .../tsconfig.json | 2 +- .../vite.config.ts | 9 + 13 files changed, 350 insertions(+), 179 deletions(-) rename single-factor-auth-web/sfa-web-ton-telegram-example/{src => }/.env (100%) delete mode 100644 single-factor-auth-web/sfa-web-ton-telegram-example/src/TelegramAppWrapper.tsx create mode 100644 single-factor-auth-web/sfa-web-ton-telegram-example/src/assets/Logo.svg create mode 100644 single-factor-auth-web/sfa-web-ton-telegram-example/src/hooks/useClientOnce.ts diff --git a/.gitignore b/.gitignore index 3ebb0791..22a1485d 100644 --- a/.gitignore +++ b/.gitignore @@ -102,3 +102,4 @@ tests/cases/user/prettier/prettier .expo build single-factor-auth-web/sfa-web-ton-telegram-example/src/.env +single-factor-auth-web/sfa-web-ton-telegram-example/src/.env diff --git a/single-factor-auth-web/sfa-web-ton-telegram-example/src/.env b/single-factor-auth-web/sfa-web-ton-telegram-example/.env similarity index 100% rename from single-factor-auth-web/sfa-web-ton-telegram-example/src/.env rename to single-factor-auth-web/sfa-web-ton-telegram-example/.env diff --git a/single-factor-auth-web/sfa-web-ton-telegram-example/server/api/index.js b/single-factor-auth-web/sfa-web-ton-telegram-example/server/api/index.js index d1078dc4..ebc8f808 100644 --- a/single-factor-auth-web/sfa-web-ton-telegram-example/server/api/index.js +++ b/single-factor-auth-web/sfa-web-ton-telegram-example/server/api/index.js @@ -6,29 +6,35 @@ const path = require("path"); const { AuthDataValidator } = require("@telegram-auth/server"); const { objectToAuthDataMap } = require("@telegram-auth/server/utils"); const RateLimit = require("express-rate-limit"); -const cors = require('cors'); dotenv.config(); const app = express(); app.use(express.json()); // Middleware to parse JSON requests -const { TELEGRAM_BOT_TOKEN, SERVER_URL, JWT_KEY_ID } = process.env; +const { TELEGRAM_BOT_TOKEN, JWT_KEY_ID, APP_URL } = process.env; const privateKey = fs.readFileSync(path.resolve(__dirname, "privateKey.pem"), "utf8"); -// CORS configuration -const corsOptions = { - origin: SERVER_URL || 'http://localhost:3000', // Use the SERVER_URL from .env or fallback to localhost during dev - credentials: true, // This allows cookies/credentials to be sent across domains - methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], // Allowed HTTP methods - allowedHeaders: ['Origin', 'X-Requested-With', 'Content-Type', 'Accept', 'Authorization'], // Allowed headers -}; +// Define allowed origins +const allowedOrigins = [APP_URL]; // Add more origins if needed -// Apply CORS middleware to all routes -app.use(cors(corsOptions)); +// CORS configuration +app.use((req, res, next) => { + const origin = req.headers.origin; + if (allowedOrigins.includes(origin)) { + res.setHeader('Access-Control-Allow-Origin', origin); // Allow only the allowed origins + res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS'); + res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Requested-With, Accept'); + res.setHeader('Access-Control-Allow-Credentials', 'true'); // Allow credentials like cookies + } -// Handle preflight requests (OPTIONS method) for all routes -app.options('*', cors(corsOptions)); + // Handle preflight requests (OPTIONS method) + if (req.method === 'OPTIONS') { + res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Requested-With, Accept'); + return res.sendStatus(204); // Send no content status for OPTIONS requests + } + next(); // Pass control to the next middleware +}); // Rate limiter configuration const limiter = RateLimit({ @@ -41,33 +47,66 @@ app.use(limiter); // Helper function to generate JWT token const generateJwtToken = (userData) => { + console.log("id", userData.id); const payload = { telegram_id: userData.id, username: userData.username, - avatar_url: userData.photo_url, + avatar_url: userData.photo_url || "https://www.gravatar.com/avatar", // Default photo URL if not available sub: userData.id.toString(), name: userData.first_name, iss: "https://api.telegram.org", iat: Math.floor(Date.now() / 1000), - exp: Math.floor(Date.now() / 1000) + 60 * 60, + exp: Math.floor(Date.now() / 1000) + 60 * 60, // Token valid for 1 hour }; return jwt.sign(payload, privateKey, { algorithm: "RS256", keyid: JWT_KEY_ID }); }; -// Validate Telegram data and generate JWT -app.post("/auth/telegram", async (req, res) => { - const { initDataRaw } = req.body; +// Route 1: Test route to check if the server is running +app.get("/test", (req, res) => { + res.json({ message: "Connection successful. Server is running!" }); +}); +// Route 2: Telegram authentication route +app.post("/auth/telegram", async (req, res) => { + const { initDataRaw, isMocked } = req.body; + console.log("Received initDataRaw:", initDataRaw); + console.log("isMocked:", isMocked); if (!initDataRaw) { return res.status(400).json({ error: "initDataRaw is required" }); } + if (isMocked) { + // Directly parse the mocked initDataRaw without validation + const data = new URLSearchParams(initDataRaw); + const user = JSON.parse(decodeURIComponent(data.get("user"))); // Decode the 'user' parameter from initDataRaw + + const mockUser = { + id: user.id, + username: user.username, + photo_url: user.photo_url || "https://www.gravatar.com/avatar", // Default photo URL for mocked user + first_name: user.first_name, + }; + + console.log("Parsed mock user data:", mockUser); + + const JWTtoken = generateJwtToken(mockUser); + return res.json({ token: JWTtoken }); + } + + // For real scenarios, proceed with validation const validator = new AuthDataValidator({ botToken: TELEGRAM_BOT_TOKEN }); const data = objectToAuthDataMap(new URLSearchParams(initDataRaw)); try { const user = await validator.validate(data); - const JWTtoken = generateJwtToken(user); + + // Ensure a photo URL is available or use a default one + const validatedUser = { + ...user, + photo_url: user.photo_url || "https://www.gravatar.com/avatar", // Fallback photo URL if missing + }; + + const JWTtoken = generateJwtToken(validatedUser); res.json({ token: JWTtoken }); } catch (error) { console.error("Error validating Telegram data:", error); @@ -75,4 +114,5 @@ app.post("/auth/telegram", async (req, res) => { } }); +// Start the server app.listen(3000, () => console.log("Server ready on port 3000.")); diff --git a/single-factor-auth-web/sfa-web-ton-telegram-example/src/App.css b/single-factor-auth-web/sfa-web-ton-telegram-example/src/App.css index 243c0dd1..340c540a 100644 --- a/single-factor-auth-web/sfa-web-ton-telegram-example/src/App.css +++ b/single-factor-auth-web/sfa-web-ton-telegram-example/src/App.css @@ -1,3 +1,4 @@ +/* Main Container */ .container { width: 60%; margin: auto; @@ -28,8 +29,8 @@ .grid { display: flex; - align-items: center; flex-direction: column; + align-items: center; } .card { @@ -101,3 +102,80 @@ font-size: 16px; font-family: monospace; } + +/* Updated box sizes */ +.user-info-box, .info-box { + padding: 15px; + margin: 15px 0; + border: 1px solid #ccc; + border-radius: 10px; + background-color: #f9f9f9; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); + width: 350px; + height: auto; + display: flex; + align-items: center; + justify-content: center; + text-align: center; + cursor: pointer; + transition: background-color 0.3s ease; +} + +.info-box:hover { + background-color: #eef; +} + +.loader-container { + display: flex; + justify-content: center; + align-items: center; + height: 100%; +} + +/* Ellipsis for long strings */ +p { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 100%; /* Ensure ellipsis kicks in at full box width */ +} + +/* Adjust alignment and spacing of ID and Logo */ +.id-with-logo { + display: inline-flex; + align-items: center; /* Vertically align the text and logo */ + gap: 4px; /* Reduce the gap between the ID and the logo */ + margin-bottom: 0; /* Remove unnecessary margin below the ID */ + max-width: fit-content; /* Adjust width to fit content only */ +} + +/* Style the logo */ +.telegram-logo { + width: 20px; + height: 20px; + margin-left: 4px; /* Minimal space between the ID and logo */ +} + +/* Adjust overall spacing for user-info-box */ +.user-info-box { + padding: 15px; + margin: 10px 0; /* Adjust the margin for better spacing */ + border: 1px solid #ccc; + border-radius: 10px; + background-color: #f9f9f9; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); + width: 350px; + height: auto; + display: flex; + align-items: center; + text-align: center; + cursor: pointer; + transition: background-color 0.3s ease; +} + +.user-avatar { + width: 50px; + height: 50px; + border-radius: 50%; + margin-right: 10px; +} diff --git a/single-factor-auth-web/sfa-web-ton-telegram-example/src/App.tsx b/single-factor-auth-web/sfa-web-ton-telegram-example/src/App.tsx index 4d5e26d3..5e060e03 100644 --- a/single-factor-auth-web/sfa-web-ton-telegram-example/src/App.tsx +++ b/single-factor-auth-web/sfa-web-ton-telegram-example/src/App.tsx @@ -3,10 +3,11 @@ import { Web3Auth, decodeToken } from "@web3auth/single-factor-auth"; import { CHAIN_NAMESPACES, WEB3AUTH_NETWORK } from "@web3auth/base"; import { CommonPrivateKeyProvider } from "@web3auth/base-provider"; import { getHttpEndpoint } from "@orbs-network/ton-access"; -import { useLaunchParams, isTMA } from "@telegram-apps/sdk-react"; -import { mockTelegramEnvironment } from "./hooks/useMockTelegramInitData"; -// import TonRPC from "./tonRpc"; +import TonRPC from "./tonRpc"; // Import the TonRPC class +import { useLaunchParams } from "@telegram-apps/sdk-react"; +import { useTelegramMock } from "./hooks/useMockTelegramInitData"; import Loading from "./Loading"; +import TelegramLogo from "./assets/Logo.svg"; // Assuming the logo is in the assets folder import "./App.css"; const verifier = "w3a-telegram-demo"; @@ -14,38 +15,16 @@ const clientId = "BPi5PB_UiIZ-cPz1GtV5i1I2iOSOHuimiXBI0e-Oe_u6X3oVAbCiAZOTEBtTXw function App() { const [isLoggingIn, setIsLoggingIn] = useState(false); - const [loggedIn, setLoggedIn] = useState(false); - const [provider, setProvider] = useState(null); - const [initDataRaw, setInitDataRaw] = useState(null); const [web3authSfa, setWeb3authSfa] = useState(null); const [web3AuthInitialized, setWeb3AuthInitialized] = useState(false); + const [userData, setUserData] = useState(null); // State to hold parsed user info + const [tonAccountAddress, setTonAccountAddress] = useState(null); + const [signedMessage, setSignedMessage] = useState(null); + const [isLoggedIn, setIsLoggedIn] = useState(false); - const { initDataRaw: launchData } = useLaunchParams() || {}; + const { initDataRaw } = useLaunchParams() || {}; - useEffect(() => { - const checkTelegramEnvironment = async () => { - if (await isTMA()) { - console.log("Running inside Telegram Mini App."); - } else { - console.warn("Not running inside Telegram Mini App. Mocking environment for development..."); - if (process.env.NODE_ENV === "development") { - mockTelegramEnvironment(); - } - } - - if (launchData) { - console.log("initDataRaw found: ", launchData); - setInitDataRaw(launchData); - } else { - console.warn("initDataRaw not found or undefined. Using mock data."); - if (process.env.NODE_ENV === "development") { - mockTelegramEnvironment(); - } - } - }; - - checkTelegramEnvironment(); - }, [launchData]); + useTelegramMock(); // Initialize the Telegram mock data useEffect(() => { const initializeWeb3Auth = async () => { @@ -79,12 +58,13 @@ function App() { privateKeyProvider, }); - console.log("Web3Auth initialized."); setWeb3authSfa(web3authInstance); + console.log("Initializing Web3Auth..."); await web3authInstance.init(); // Ensure Web3Auth is initialized + console.log("Web3Auth initialized."); + setWeb3AuthInitialized(true); - console.log("Web3Auth is now ready."); } catch (error) { console.error("Error fetching TON Testnet RPC endpoint: ", error); } @@ -95,12 +75,22 @@ function App() { useEffect(() => { const connectWeb3Auth = async () => { - if (web3authSfa && web3AuthInitialized && !loggedIn && initDataRaw) { + if (web3authSfa && web3AuthInitialized && initDataRaw) { setIsLoggingIn(true); try { - console.log("Starting Web3Auth connection..."); + console.log("Checking Web3Auth connection status..."); + + if (web3authSfa.status === "connected") { + await web3authSfa.logout(); + console.log("Logged out successfully."); + } - const idToken = await getIdTokenFromServer(); + if (web3authSfa.status === "not_ready") { + await web3authSfa.init(); + console.log("Web3Auth initialized."); + } + + const idToken = await getIdTokenFromServer(initDataRaw); if (!idToken) { console.error("No ID token found."); setIsLoggingIn(false); @@ -116,79 +106,113 @@ function App() { idToken: idToken, }); - setProvider(web3authSfa.provider); - setLoggedIn(true); console.log("Successfully logged in."); + setUserData(payload); + setIsLoggedIn(true); + + // Initialize TonRPC and fetch the account address + const tonRpc = new TonRPC(web3authSfa.provider); + const tonAddress = await tonRpc.getAccounts(); + setTonAccountAddress(tonAddress); // Set the TON address dynamically + + // Sign a message and set it + const messageToSign = "Hello, TON!"; + const signedMsg = await tonRpc.signMessage(messageToSign); + setSignedMessage(signedMsg); // Set the signed message } catch (error) { console.error("Error during Web3Auth connection:", error); } finally { setIsLoggingIn(false); } - } else { - console.warn("Web3Auth is not ready or already logged in."); } }; if (web3AuthInitialized && initDataRaw) { connectWeb3Auth(); } - }, [initDataRaw, loggedIn, web3authSfa, web3AuthInitialized]); - - const getIdTokenFromServer = async () => { - console.log("Requesting ID token from server with initDataRaw: ", initDataRaw); - try { - const response = await fetch(`${process.env.REACT_APP_SERVER_URL}/auth/telegram`, { - method: "POST", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, - body: JSON.stringify({ initDataRaw }), - credentials: "include", - mode: "cors", - }); - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - const data = await response.json(); - console.log("ID token received: ", data.token); - return data.token; - } catch (error) { - console.error("Error fetching ID token from server: ", error); - return null; - } + }, [initDataRaw, web3authSfa, web3AuthInitialized]); + + const getIdTokenFromServer = async (initDataRaw: string) => { + const isMocked = !!sessionStorage.getItem("____mocked"); + + const response = await fetch(`${process.env.SERVER_URL}/auth/telegram`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ initDataRaw, isMocked }), + }); + + const data = await response.json(); + console.log("Received ID token from server:", data.token); + return data.token; }; - const getUserInfo = async () => { - if (!provider) { - console.log("Web3Auth Single Factor Auth SDK not initialized yet"); - return; - } - const userInfo = await web3authSfa?.getUserInfo(); - console.log(userInfo); + const copyToClipboard = (text: string) => { + navigator.clipboard.writeText(text); + alert("Copied to clipboard!"); }; - const loginView = ( - <> -
- - {/* Add other actions here */} + const userInfoBox = ( +
+ User avatar +
+
+

+ ID: {userData?.telegram_id} +

+ Telegram Logo +
+

+ Username: {userData?.username} +

+

+ Name: {userData?.name} +

- +
+ ); + + const tonAccountBox = ( +
copyToClipboard(tonAccountAddress || "")}> +

+ TON Account: {tonAccountAddress} +

+
+ ); + + const signedMessageBox = ( +
copyToClipboard(signedMessage || "")}> +

+ Signed Message: {signedMessage} +

+
); const logoutView = ( - +
+ +
); return (
-

Web3Auth SFA React TON Example

- {isLoggingIn ? :
{provider ? (loggedIn ? loginView : logoutView) : null}
} +

Web3Auth TON Telegram MiniApp

+ {isLoggingIn ? ( + + ) : ( +
+ {isLoggedIn ? ( + <> + {userInfoBox} + {tonAccountBox} + {signedMessageBox} + + ) : ( + logoutView + )} +
+ )}