gebeya-telegram-otp
Version:
Reusable Telegram phone verification components for React applications
514 lines (398 loc) • 14.5 kB
Markdown
This guide will walk you through installing and setting up the `gebeya-telegram-otp` package in your React application.
```bash
npm install gebeya-telegram-otp
```
The package requires several peer dependencies. Install them all at once:
```bash
npm install @supabase/supabase-js lucide-react input-otp sonner qrcode react react-dom
```
If you prefer to install them individually or need specific versions:
```bash
npm install @supabase/supabase-js
npm install lucide-react
npm install input-otp
npm install sonner
npm install qrcode
npm install react react-dom
```
**Note:** This package uses flexible version ranges (`>=`) to ensure compatibility with both older and newer versions of dependencies. Your existing versions should work fine if they meet the minimum requirements.
## Step 3: Set Up Tailwind CSS
The components use Tailwind CSS for styling. If you don't have Tailwind CSS set up, follow these steps:
### Install Tailwind CSS
```bash
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
```
### Configure Tailwind CSS
Update your `tailwind.config.js` to include the package components:
```javascript
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./index.html",
"./src/**/*.{js,ts,jsx,tsx}",
"./node_modules/gebeya-telegram-verify/dist/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {},
},
plugins: [],
};
```
Add these directives to your main CSS file (e.g., `src/index.css`):
```css
@tailwind base;
@tailwind components;
@tailwind utilities;
```
1. Go to [https://supabase.com](https://supabase.com)
2. Create a new project
3. Note your project URL and anon key
Run this SQL in your Supabase SQL editor:
```sql
-- Create verification sessions table
CREATE TABLE public.verification_sessions (
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
phone_number TEXT NOT NULL,
telegram_user_id BIGINT,
telegram_chat_id BIGINT,
otp_code TEXT,
verified BOOLEAN NOT NULL DEFAULT false,
expires_at TIMESTAMP WITH TIME ZONE NOT NULL,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()
);
-- Enable RLS
ALTER TABLE public.verification_sessions ENABLE ROW LEVEL SECURITY;
-- Create policies
CREATE POLICY "Anyone can insert verification sessions"
ON public.verification_sessions FOR INSERT WITH CHECK (true);
CREATE POLICY "Anyone can read verification sessions for verification process"
ON public.verification_sessions FOR SELECT USING (true);
CREATE POLICY "Anyone can update verification sessions"
ON public.verification_sessions FOR UPDATE USING (true);
```
Create a file `src/lib/supabase.js` (or `.ts` for TypeScript):
```javascript
import { createClient } from "@supabase/supabase-js";
const supabaseUrl = "YOUR_SUPABASE_URL";
const supabaseKey = "YOUR_SUPABASE_ANON_KEY";
export const supabase = createClient(supabaseUrl, supabaseKey);
```
1. Message [@BotFather](https://t.me/botfather) on Telegram
2. Create a new bot with `/newbot`
3. Get your bot token
4. Add the bot token to your Supabase secrets as `TELEGRAM_BOT_TOKEN`
In your `supabase/config.toml`, add:
```toml
[]
verify_jwt = false
[]
verify_jwt = false
```
Create these edge functions in your `supabase/functions/` directory:
```typescript
import { serve } from "https://deno.land/std@0.168.0/http/server.ts";
import { createClient } from "https://esm.sh/@supabase/supabase-js@2";
const corsHeaders = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers":
"authorization, x-client-info, apikey, content-type",
};
serve(async (req) => {
if (req.method === "OPTIONS") {
return new Response(null, { headers: corsHeaders });
}
try {
const requestBody = await req.json();
const botToken = Deno.env.get("TELEGRAM_BOT_TOKEN");
const supabaseUrl = Deno.env.get("SUPABASE_URL");
const supabaseKey = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY");
if (!botToken || !supabaseUrl || !supabaseKey) {
return new Response(
JSON.stringify({ error: "Missing required environment variables" }),
{ status: 500, headers: corsHeaders }
);
}
const supabase = createClient(supabaseUrl, supabaseKey);
// Handle webhook setup
if (requestBody.action === "setup_webhook") {
const webhookUrl = `${supabaseUrl}/functions/v1/telegram-webhook`;
const telegramApiUrl = `https://api.telegram.org/bot${botToken}/setWebhook`;
const response = await fetch(telegramApiUrl, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ url: webhookUrl }),
});
const result = await response.json();
return new Response(JSON.stringify(result), {
headers: { ...corsHeaders, "Content-Type": "application/json" },
});
}
// Handle verification session creation
if (requestBody.phone_number && !requestBody.message) {
const { data: session, error } = await supabase
.from("verification_sessions")
.insert({
phone_number: requestBody.phone_number,
expires_at: new Date(Date.now() + 10 * 60 * 1000).toISOString(),
})
.select()
.single();
if (error) {
return new Response(
JSON.stringify({ error: "Failed to create verification session" }),
{ status: 500, headers: corsHeaders }
);
}
return new Response(JSON.stringify({ session_id: session.id }), {
headers: corsHeaders,
});
}
// Handle Telegram updates (messages, contacts, etc.)
const update = requestBody;
if (update.message?.contact) {
const contact = update.message.contact;
const chatId = update.message.chat.id;
const userId = update.message.from.id;
const phoneNumber = contact.phone_number.startsWith("+")
? contact.phone_number
: "+" + contact.phone_number;
// Generate OTP
const otpCode = Math.floor(100000 + Math.random() * 900000).toString();
// Update verification session
await supabase
.from("verification_sessions")
.update({
telegram_user_id: userId,
telegram_chat_id: chatId,
otp_code: otpCode,
expires_at: new Date(Date.now() + 5 * 60 * 1000).toISOString(),
})
.eq("phone_number", phoneNumber);
// Send OTP to user
await fetch(`https://api.telegram.org/bot${botToken}/sendMessage`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
chat_id: chatId,
text: `Your OTP code is: ${otpCode}`,
reply_markup: { remove_keyboard: true },
}),
});
}
return new Response("OK", { headers: corsHeaders });
} catch (error) {
console.error("Webhook error:", error);
return new Response("Internal Server Error", {
status: 500,
headers: corsHeaders,
});
}
});
```
```typescript
import { serve } from "https://deno.land/std@0.168.0/http/server.ts";
import { createClient } from "https://esm.sh/@supabase/supabase-js@2";
const corsHeaders = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers":
"authorization, x-client-info, apikey, content-type",
};
serve(async (req) => {
if (req.method === "OPTIONS") {
return new Response(null, { headers: corsHeaders });
}
try {
const { phone_number, otp_code } = await req.json();
if (!phone_number || !otp_code) {
return new Response(
JSON.stringify({ error: "Phone number and OTP are required" }),
{ status: 400, headers: corsHeaders }
);
}
const supabaseUrl = Deno.env.get("SUPABASE_URL");
const supabaseKey = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY");
if (!supabaseUrl || !supabaseKey) {
return new Response(
JSON.stringify({ error: "Server configuration error" }),
{ status: 500, headers: corsHeaders }
);
}
const supabase = createClient(supabaseUrl, supabaseKey);
// Verify OTP
const { data: session, error } = await supabase
.from("verification_sessions")
.select("*")
.eq("phone_number", phone_number)
.eq("otp_code", otp_code)
.eq("verified", false)
.gt("expires_at", new Date().toISOString())
.single();
if (error || !session) {
return new Response(JSON.stringify({ error: "Invalid or expired OTP" }), {
status: 400,
headers: corsHeaders,
});
}
// Mark as verified
await supabase
.from("verification_sessions")
.update({ verified: true })
.eq("id", session.id);
// Create or update user
const { data: user, error: userError } =
await supabase.auth.admin.createUser({
phone: phone_number,
phone_confirm: true,
user_metadata: {
phone_verified: true,
telegram_user_id: session.telegram_user_id,
},
});
return new Response(
JSON.stringify({
success: true,
message: "Phone number verified successfully",
user: user?.user,
}),
{ headers: corsHeaders }
);
} catch (error) {
console.error("Verification error:", error);
return new Response(JSON.stringify({ error: "Internal server error" }), {
status: 500,
headers: corsHeaders,
});
}
});
```
In your Supabase Dashboard > Settings > Edge Functions, add:
- `TELEGRAM_BOT_TOKEN`: Your bot token from Step 5
Set your bot webhook by calling your edge function:
```bash
curl -X POST "https://YOUR_PROJECT_ID.supabase.co/functions/v1/telegram-webhook" \
-H "Content-Type: application/json" \
-d '{"action": "setup_webhook"}'
```
```tsx
// In your main App.tsx or layout file
import { TelegramVerificationProvider } from "gebeya-telegram-otp";
import { supabase } from "./lib/supabase";
function App() {
return (
<TelegramVerificationProvider
supabaseClient={supabase}
telegramConfig={{ botName: "@your_bot_name" }}
onSuccess={(userData) => {
console.log("Verification successful:", userData);
}}
onError={(error) => {
console.error("Verification error:", error);
}}
>
<YourAppContent />
</TelegramVerificationProvider>
);
}
export default App;
```
```tsx
import { TelegramVerifyButton } from "gebeya-telegram-otp";
function LoginPage() {
return (
<div className="flex items-center justify-center min-h-screen">
<div className="p-8 max-w-md mx-auto">
<h1 className="text-2xl font-bold mb-6 text-center">Sign In</h1>
<TelegramVerifyButton
buttonText="Verify with Telegram"
variant="default"
size="lg"
className="w-full"
onVerificationComplete={(userData) => {
console.log("User verified:", userData);
// Redirect to dashboard or update app state
}}
/>
</div>
</div>
);
}
export default LoginPage;
```
The package includes full TypeScript definitions:
```tsx
import {
TelegramVerifyButton,
TelegramVerificationProvider,
type TelegramVerificationData,
type TelegramConfig,
} from "gebeya-telegram-otp";
// Types are automatically available
const config: TelegramConfig = {
botName: "@your_bot_name",
};
const handleSuccess = (userData: TelegramVerificationData) => {
// userData is fully typed
console.log("User ID:", userData.user.id);
console.log("Phone:", userData.user.phone);
};
```
Your application should now be ready to use Telegram verification. The package will handle:
- Phone number input with country selection
- QR code generation for Telegram verification
- OTP verification process
- Success handling
1. **Missing styles**: Ensure Tailwind CSS is properly configured with the package path
2. **Supabase errors**: Check your database setup and RLS policies
3. **Telegram bot issues**: Verify your bot token and webhook setup
4. **Import errors**: Make sure all peer dependencies are installed
5. **Edge function errors**: Check your Supabase function logs for detailed error messages
1. Check Supabase edge function logs in your dashboard
2. Test webhook manually with curl
3. Verify database entries in verification_sessions table
4. Check Telegram bot settings with [@BotFather](https://t.me/botfather)
5. Ensure all environment variables are properly set
1. Never expose bot token in client-side code
2. Implement rate limiting for verification attempts
3. Set appropriate session expiry times
4. Use HTTPS for all webhook URLs
5. Validate phone numbers on both client and server
- Customize styling to match your app
- Add additional security measures
- Implement user profiles
- Add multi-language support
- Set up monitoring and analytics
For more detailed setup instructions, see the [setup-guide.md](./setup-guide.md) file.