fix: support admin auth for Telegram MiniApp mail viewing (#875)

* fix: support admin auth for Telegram MiniApp mail viewing (#852)

Admin users configured in miniAppUrl can now view emails via the
MiniApp without needing Telegram initData auth. The getMail endpoint
reads the x-admin-auth header (already sent by the frontend) to
bypass Telegram auth and address permission checks for admin users.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: extract isAdmin() as shared utility function

Reuse the x-admin-auth header check logic across worker.ts and
miniapp.ts via a common isAdmin() helper in utils.ts.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: rename isAdmin to checkIsAdmin for consistency

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: address PR review comments

- Remove unused getAdminPasswords import from worker.ts
- Return 404 when admin queries a non-existent mail

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dream Hunter
2026-03-06 14:06:49 +08:00
committed by GitHub
parent 635e0f4456
commit 8341cae28f
5 changed files with 24 additions and 9 deletions

View File

@@ -2,7 +2,7 @@ import { Context } from "hono";
import { Jwt } from 'hono/utils/jwt'
import { CONSTANTS } from "../constants";
import { bindTelegramAddress, jwtListToAddressData, tgUserNewAddress, unbindTelegramAddress } from "./common";
import { checkCfTurnstile } from "../utils";
import { checkCfTurnstile, checkIsAdmin } from "../utils";
import { TelegramSettings } from "./settings";
import i18n from "../i18n";
@@ -131,6 +131,15 @@ async function getMail(c: Context<HonoCustomType>): Promise<Response> {
const { initData, mailId } = await c.req.json();
const msgs = i18n.getMessagesbyContext(c);
try {
if (checkIsAdmin(c)) {
const result = await c.env.DB.prepare(
`SELECT * FROM raw_mails where id = ?`
).bind(mailId).first();
if (!result) {
return c.text("Mail not found", 404);
}
return c.json(result);
}
const userId = await checkTelegramAuth(c, initData);
const jwtList = await c.env.KV.get<string[]>(`${CONSTANTS.TG_KV_PREFIX}:${userId}`, 'json') || [];
const { addressList, addressIdMap } = await jwtListToAddressData(c, jwtList, msgs);

View File

@@ -216,6 +216,13 @@ export const getAdminPasswords = (c: Context<HonoCustomType>): string[] => {
return c.env.ADMIN_PASSWORDS.filter((item) => item.length > 0);
}
export const checkIsAdmin = (c: Context<HonoCustomType>): boolean => {
const adminPasswords = getAdminPasswords(c);
if (!adminPasswords.length) return false;
const adminAuth = c.req.raw.headers.get("x-admin-auth");
return !!adminAuth && adminPasswords.includes(adminAuth);
}
export const getEnvStringList = (value: string | string[] | undefined): string[] => {
if (!value) {
return [];
@@ -359,6 +366,7 @@ export default {
getAnotherWorkerList,
getPasswords,
getAdminPasswords,
checkIsAdmin,
getEnvStringList,
sendAdminInternalMail,
checkCfTurnstile,

View File

@@ -13,7 +13,7 @@ import { api as telegramApi } from './telegram_api'
import i18n from './i18n';
import { email } from './email';
import { scheduled } from './scheduled';
import { getAdminPasswords, getPasswords, getBooleanValue, getStringArray } from './utils';
import { getPasswords, getBooleanValue, getStringArray, checkIsAdmin } from './utils';
import { checkAccessControl } from './ip_blacklist';
const API_PATHS = [
@@ -215,13 +215,9 @@ app.use('/user_api/*', async (c, next) => {
app.use('/admin/*', async (c, next) => {
// check header x-admin-auth
const adminPasswords = getAdminPasswords(c);
if (adminPasswords && adminPasswords.length > 0) {
const adminAuth = c.req.raw.headers.get("x-admin-auth");
if (adminAuth && adminPasswords.includes(adminAuth)) {
await next();
return;
}
if (checkIsAdmin(c)) {
await next();
return;
}
const lang = c.req.raw.headers.get("x-lang") || c.env.DEFAULT_LANG;
const msgs = i18n.getMessages(lang);