Created
April 9, 2025 08:35
-
-
Save bkyerv/a73ea2c49cc89c6261e19ff07004dd6d to your computer and use it in GitHub Desktop.
telegram plugin
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import crypto from "node:crypto"; | |
import { createAuthEndpoint } from "better-auth/api"; | |
import { account, user } from "src/db/auth-schema"; | |
import type { BetterAuthPlugin } from "better-auth/types"; | |
// const TELEGRAM_BOT_TOKEN = import.meta.env.TELEGRAM_BOT_TOKEN; | |
// The session expiration time (30 days by default) | |
const SESSION_MAX_AGE = 60 * 60 * 24 * 30; // 30 days in seconds | |
// Helper to generate placeholder emails | |
const createPlaceholderEmail = (telegramUserId: string): string => { | |
return `tg_${telegramUserId}@placeholder.telegram.user`; | |
}; | |
export const telegramPlugin = ({ botToken }: { botToken: string }) => { | |
return { | |
id: "telegram", | |
endpoints: { | |
telegramCallback: createAuthEndpoint( | |
"/telegram/callback", | |
{ method: "GET", requireRequest: true }, | |
async ({ | |
request, | |
context: { adapter, internalAdapter, createAuthCookie }, | |
redirect, | |
setCookie, | |
setSignedCookie, | |
}) => { | |
console.log("from inside telegram callback - bot token", botToken); | |
if (!botToken) { | |
console.error("Telegram Bot Token not configured!"); | |
return new Response("Server configuration error", { status: 500 }); | |
} | |
const url = new URL(request?.url); | |
const params = url.searchParams; | |
const hash = params.get("hash"); | |
const telegramUserData = Object.fromEntries(params.entries()); | |
if (!hash) { | |
return redirect("/signin?error=telegram_missing_hash"); | |
} | |
// --- 1. Verify Telegram Hash --- | |
const dataCheckArr: string[] = []; | |
Object.keys(telegramUserData) | |
.filter((key) => key !== "hash") | |
.sort() | |
.forEach((key) => { | |
dataCheckArr.push(`${key}=${telegramUserData[key]}`); | |
}); | |
const dataCheckString = dataCheckArr.join("\n"); | |
console.log( | |
"from inside telegram callback - datacheckstring", | |
dataCheckString | |
); | |
const secretKey = crypto | |
.createHash("sha256") | |
.update(botToken) | |
.digest(); | |
const calculatedHash = crypto | |
.createHmac("sha256", secretKey) | |
.update(dataCheckString) | |
.digest("hex"); | |
if (calculatedHash !== hash) { | |
console.warn("Telegram hash verification failed!"); | |
return redirect("/signin?error=telegram_auth_failed"); | |
} | |
try { | |
// --- Check auth_date --- | |
const authDate = params.get("auth_date"); | |
if (authDate) { | |
const timestamp = parseInt(authDate, 10); | |
const now = Math.floor(Date.now() / 1000); | |
const timeDiff = now - timestamp; | |
if (timeDiff > 300) { | |
// 5 minute validity window | |
console.warn("Telegram auth_date is too old!"); | |
return redirect("/signin?error=telegram_auth_expired"); | |
} | |
} else { | |
return redirect("/signin?error=telegram_auth_timestamp_missing"); | |
} | |
// --- Hash is valid --- | |
const telegramUserId = params.get("id"); // This is the providerAccountId | |
if (!telegramUserId) { | |
return redirect("/signin?error=telegram_id_missing"); | |
} | |
const providerId = "telegram"; | |
// Step 1: Check for an existing Telegram account | |
const existingAccount = (await adapter.findOne({ | |
model: "account", | |
where: [ | |
{ field: "providerId", value: providerId }, | |
{ field: "accountId", value: telegramUserId }, | |
], | |
select: ["userId"], | |
})) as typeof account.$inferSelect; | |
let userId: string | null = null; | |
let userJustCreated = false; | |
if (existingAccount) { | |
// Step 2a: Account exists, use the linked user | |
userId = existingAccount.userId; | |
console.log( | |
`Found existing user yo yo yo (${userId}) via Telegram account.` | |
); | |
} else { | |
// Step 2b: No account found, create a new user and link the account | |
console.log( | |
`No existing Telegram account found for ${telegramUserId}. Creating new user.` | |
); | |
userJustCreated = true; | |
// Construct user data | |
const userName = | |
`${params.get("first_name") || ""} ${ | |
params.get("last_name") || "" | |
}`.trim() || | |
params.get("username") || | |
`Telegram User ${telegramUserId}`; | |
// Create the user | |
const newUser = (await adapter.create({ | |
model: "user", | |
data: { | |
name: userName, | |
email: createPlaceholderEmail(telegramUserId), | |
emailVerified: false, | |
image: params.get("photo_url") || null, | |
role: "regular_user", | |
}, | |
})) as typeof user.$inferInsert; | |
userId = newUser.id; | |
console.log(`Created new user with ID: ${userId}`); | |
// Link the Telegram account to the new user | |
await adapter.create({ | |
model: "account", | |
data: { | |
userId: userId, | |
providerId: providerId, | |
accountId: telegramUserId, | |
}, | |
}); | |
console.log( | |
`Linked Telegram account ${telegramUserId} to user ${userId}` | |
); | |
} | |
const session = await internalAdapter.createSession( | |
userId, | |
request | |
); | |
const cookie = createAuthCookie("session_token", { | |
maxAge: SESSION_MAX_AGE, | |
}); | |
console.log("Cookie attributes:", cookie.attributes); | |
// setSignedCookie( | |
// cookie.name, | |
// session.token, | |
// process.env.BETTER_AUTH_SECRET!, | |
// cookie.attributes | |
// ); | |
setCookie(cookie.name, session.token, cookie.attributes); | |
console.log("Signed cookie set with name:", cookie.name); | |
console.log("Session token:", session.token); | |
// Redirect based on user status | |
const redirectTo = userJustCreated | |
? "/protected/new" | |
: "/protected/images/results"; | |
return redirect(redirectTo); | |
} catch (error) { | |
console.error("Error during Telegram auth callback:", error); | |
if (error instanceof Error) { | |
console.error(error.message); | |
console.error(error.stack); | |
} | |
return redirect("/signin?error=server_error"); | |
} | |
} | |
), | |
}, | |
} satisfies BetterAuthPlugin; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment