diff --git a/CHANGELOG.md b/CHANGELOG.md index 95343f7f..6e9c35c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## main branch - telegram mini app +- telegram mini 增加 `ubind`, `delete` 指令 ## v0.4.3 diff --git a/frontend/package.json b/frontend/package.json index 55a8c54e..a09b8d15 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "cloudflare_temp_email", - "version": "0.4.3", + "version": "0.4.4", "private": true, "type": "module", "scripts": { diff --git a/worker/src/admin_api/webhook_settings.ts b/worker/src/admin_api/webhook_settings.ts index 1e84d1a5..8cc7b1e0 100644 --- a/worker/src/admin_api/webhook_settings.ts +++ b/worker/src/admin_api/webhook_settings.ts @@ -1,16 +1,14 @@ import { Context } from "hono"; -import { Bindings } from "../types"; +import { HonoCustomType } from "../types"; import { CONSTANTS } from "../constants"; import { AdminWebhookSettings } from "../models/models"; -// @ts-ignore -import { getBooleanValue } from "../utils"; -async function getWebhookSettings(c: Context<{ Bindings: Bindings }>): Promise { +async function getWebhookSettings(c: Context): Promise { const settings = await c.env.KV.get(CONSTANTS.WEBHOOK_KV_SETTINGS_KEY, "json"); return c.json(settings || new AdminWebhookSettings([])); } -async function saveWebhookSettings(c: Context<{ Bindings: Bindings }>): Promise { +async function saveWebhookSettings(c: Context): Promise { const settings = await c.req.json(); await c.env.KV.put(CONSTANTS.WEBHOOK_KV_SETTINGS_KEY, JSON.stringify(settings)); return c.json({ success: true }) diff --git a/worker/src/commom_api.ts b/worker/src/commom_api.ts index 36fd2864..0a598399 100644 --- a/worker/src/commom_api.ts +++ b/worker/src/commom_api.ts @@ -1,11 +1,10 @@ import { Hono } from 'hono' -// @ts-ignore import { getDomains, getPasswords, getBooleanValue } from './utils'; import { CONSTANTS } from './constants'; -import { Bindings } from './types'; +import { HonoCustomType } from './types'; -const api = new Hono<{ Bindings: Bindings }> +const api = new Hono api.get('/open_api/settings', async (c) => { // check header x-custom-auth @@ -13,7 +12,7 @@ api.get('/open_api/settings', async (c) => { const passwords = getPasswords(c); if (passwords && passwords.length > 0) { const auth = c.req.raw.headers.get("x-custom-auth"); - needAuth = !passwords.includes(auth); + needAuth = !auth || !passwords.includes(auth); } return c.json({ "prefix": c.env.PREFIX, diff --git a/worker/src/common.js b/worker/src/common.ts similarity index 54% rename from worker/src/common.js rename to worker/src/common.ts index 68bc9d2d..994bb503 100644 --- a/worker/src/common.js +++ b/worker/src/common.ts @@ -1,8 +1,16 @@ +import { Context } from 'hono'; import { Jwt } from 'hono/utils/jwt' import { getDomains, getStringValue } from './utils'; +import { HonoCustomType } from './types'; +import { CONSTANTS } from './constants'; +import { unbindTelegramByAddress } from './telegram_api/common'; -export const newAddress = async (c, name, domain, enablePrefix) => { +export const newAddress = async ( + c: Context, + name: string, domain: string | undefined | null, + enablePrefix: boolean +): Promise<{ address: string, jwt: string }> => { // remove special characters name = name.replace(/[^a-zA-Z0-9.]/g, '') // check name length @@ -36,10 +44,9 @@ export const newAddress = async (c, name, domain, enablePrefix) => { } throw new Error("Failed to create address") } - let address_id = 0; - address_id = await c.env.DB.prepare( + const address_id = await c.env.DB.prepare( `SELECT id FROM address where name = ?` - ).bind(name).first("id"); + ).bind(name).first("id"); // create jwt const jwt = await Jwt.sign({ address: name, @@ -51,7 +58,11 @@ export const newAddress = async (c, name, domain, enablePrefix) => { } } -export const cleanup = async (c, cleanType, cleanDays) => { +export const cleanup = async ( + c: Context, + cleanType: string | undefined | null, + cleanDays: number | undefined | null +): Promise => { if (!cleanType || !cleanDays || cleanDays < 0 || cleanDays > 30) { throw new Error("Invalid cleanType or cleanDays") } @@ -80,17 +91,57 @@ export const cleanup = async (c, cleanType, cleanDays) => { } /** - * - * @param {*} c context - * @param {*} query @type {string} query - * @param {*} countQuery @type {string} countQuery - * @param {*} limit @type {number} limit - * @param {*} offset @type {number} offset - * @returns {Promise} Promise + * TODO: need senbox delete? */ +export const deleteAddressWithData = async ( + c: Context, + address: string | undefined | null, + address_id: number | undefined | null +): Promise => { + if (!address && !address_id) { + throw new Error("Address or address_id required") + } + // get address_id or address + if (!address_id) { + address_id = await c.env.DB.prepare( + `SELECT id FROM address where name = ?` + ).bind(address).first("id"); + } else if (!address) { + address = await c.env.DB.prepare( + `SELECT name FROM address where id = ?` + ).bind(address_id).first("name"); + } + // check address again + if (!address || !address_id) { + throw new Error("Can't find address"); + } + // unbind telegram + await unbindTelegramByAddress(c, address); + // delete address and related data + const { success: mailSuccess } = await c.env.DB.prepare( + `DELETE FROM raw_mails WHERE address = ? ` + ).bind(address).run(); + const { success: sendAccess } = await c.env.DB.prepare( + `DELETE FROM address_sender WHERE address = ? ` + ).bind(address).run(); + const { success: addressSuccess } = await c.env.DB.prepare( + `DELETE FROM users_address WHERE address_id = ? ` + ).bind(address_id).run(); + const { success } = await c.env.DB.prepare( + `DELETE FROM address WHERE name = ? ` + ).bind(address).run(); + if (!success || !mailSuccess || !addressSuccess || !sendAccess) { + throw new Error("Failed to delete address") + } + return true; +} + export const handleListQuery = async ( - c, query, countQuery, params, limit, offset -) => { + c: Context, + query: string, countQuery: string, params: string[], + limit: number | undefined | null, + offset: number | undefined | null +): Promise => { if (!limit || limit < 0 || limit > 100) { return c.text("Invalid limit", 400) } diff --git a/worker/src/constants.ts b/worker/src/constants.ts index 5ea7cc47..cdb2e635 100644 --- a/worker/src/constants.ts +++ b/worker/src/constants.ts @@ -1,5 +1,5 @@ export const CONSTANTS = { - VERSION: 'v0.4.3', + VERSION: 'v0.4.4', // DB settings ADDRESS_BLOCK_LIST_KEY: 'address_block_list', diff --git a/worker/src/email/auto_reply.ts b/worker/src/email/auto_reply.ts index 336f75e7..a4abf8e5 100644 --- a/worker/src/email/auto_reply.ts +++ b/worker/src/email/auto_reply.ts @@ -1,5 +1,4 @@ import { createMimeMessage } from "mimetext"; -// @ts-ignore import { getBooleanValue } from "../utils"; import { Bindings } from "../types"; diff --git a/worker/src/email/index.ts b/worker/src/email/index.ts index f2f508b7..de320720 100644 --- a/worker/src/email/index.ts +++ b/worker/src/email/index.ts @@ -1,7 +1,7 @@ import { Context } from "hono"; import { sendMailToTelegram } from "../telegram_api"; -import { Bindings } from "../types"; +import { Bindings, HonoCustomType } from "../types"; import { auto_reply } from "./auto_reply"; import { trigerWebhook } from "../mails_api/webhook_settings"; @@ -28,7 +28,7 @@ async function email(message: ForwardableEmailMessage, env: Bindings, ctx: Execu // send email to telegram try { await sendMailToTelegram( - { env: env } as Context<{ Bindings: Bindings }>, + { env: env } as Context, message.to, rawEmail); } catch (error) { console.log("send mail to telegram error", error); @@ -37,7 +37,7 @@ async function email(message: ForwardableEmailMessage, env: Bindings, ctx: Execu // send webhook try { await trigerWebhook( - { env: env } as Context<{ Bindings: Bindings }>, + { env: env } as Context, message.to, rawEmail ); } catch (error) { diff --git a/worker/src/mails_api/webhook_settings.ts b/worker/src/mails_api/webhook_settings.ts index cf46362f..d33289a9 100644 --- a/worker/src/mails_api/webhook_settings.ts +++ b/worker/src/mails_api/webhook_settings.ts @@ -1,8 +1,7 @@ import { Context } from "hono"; -import { Bindings, Variables } from "../types"; +import { HonoCustomType } from "../types"; import { CONSTANTS } from "../constants"; import { AdminWebhookSettings, WebhookMail } from "../models/models"; -// @ts-ignore import { getBooleanValue } from "../utils"; import PostalMime from 'postal-mime'; @@ -24,9 +23,7 @@ class WebhookSettings { } -async function getWebhookSettings( - c: Context<{ Bindings: Bindings, Variables: Variables }> -): Promise { +async function getWebhookSettings(c: Context): Promise { if (!c.env.KV) { return c.text("KV is not available", 400); } @@ -45,9 +42,7 @@ async function getWebhookSettings( } -async function saveWebhookSettings( - c: Context<{ Bindings: Bindings, Variables: Variables }> -): Promise { +async function saveWebhookSettings(c: Context): Promise { const { address } = c.get("jwtPayload") const adminSettings = await c.env.KV.get(CONSTANTS.WEBHOOK_KV_SETTINGS_KEY, "json"); if (!adminSettings?.allowList.includes(address)) { @@ -79,7 +74,7 @@ async function sendWebhook(settings: WebhookSettings, formatMap: WebhookMail): P } export async function trigerWebhook( - c: Context<{ Bindings: Bindings }>, + c: Context, address: string, raw_mail: string ): Promise { @@ -110,9 +105,7 @@ export async function trigerWebhook( } } -async function testWebhookSettings( - c: Context<{ Bindings: Bindings, Variables: Variables }> -): Promise { +async function testWebhookSettings(c: Context): Promise { const settings = await c.req.json(); const res = await sendWebhook(settings, { from: "from@test.com", diff --git a/worker/src/models/models.ts b/worker/src/models/models.ts index a9eb1918..5fc60f92 100644 --- a/worker/src/models/models.ts +++ b/worker/src/models/models.ts @@ -14,3 +14,27 @@ export type WebhookMail = { raw: string; parsedText: string; } + +export class CleanupSettings { + + enableMailsAutoCleanup: boolean | undefined; + cleanMailsDays: number; + enableUnknowMailsAutoCleanup: boolean | undefined; + cleanUnknowMailsDays: number; + enableSendBoxAutoCleanup: boolean | undefined; + cleanSendBoxDays: number; + + constructor(data: CleanupSettings | undefined | null) { + const { + enableMailsAutoCleanup, cleanMailsDays, + enableUnknowMailsAutoCleanup, cleanUnknowMailsDays, + enableSendBoxAutoCleanup, cleanSendBoxDays + } = data || {}; + this.enableMailsAutoCleanup = enableMailsAutoCleanup; + this.cleanMailsDays = cleanMailsDays || 0; + this.enableUnknowMailsAutoCleanup = enableUnknowMailsAutoCleanup; + this.cleanUnknowMailsDays = cleanUnknowMailsDays || 0; + this.enableSendBoxAutoCleanup = enableSendBoxAutoCleanup; + this.cleanSendBoxDays = cleanSendBoxDays || 0; + } +} diff --git a/worker/src/scheduled.js b/worker/src/scheduled.ts similarity index 70% rename from worker/src/scheduled.js rename to worker/src/scheduled.ts index 0dfb4eb7..92bb6bf6 100644 --- a/worker/src/scheduled.js +++ b/worker/src/scheduled.ts @@ -1,33 +1,35 @@ +import { Context } from 'hono'; import { cleanup } from './common' import { CONSTANTS } from './constants' import { getJsonSetting } from './utils'; -import { CleanupSettings } from './models'; +import { CleanupSettings } from './models/models'; +import { Bindings, HonoCustomType } from './types'; -export async function scheduled(event, env, ctx) { +export async function scheduled(event: ScheduledEvent, env: Bindings, ctx: any) { console.log("Scheduled event: ", event); const value = await getJsonSetting( - { env: env, }, + { env: env, } as Context, CONSTANTS.AUTO_CLEANUP_KEY ); const autoCleanupSetting = new CleanupSettings(value); console.log("autoCleanupSetting:", JSON.stringify(autoCleanupSetting)); if (autoCleanupSetting.enableMailsAutoCleanup && autoCleanupSetting.cleanMailsDays > 0) { await cleanup( - { env: env, }, + { env: env, } as Context, "mails", autoCleanupSetting.cleanMailsDays ); } if (autoCleanupSetting.enableUnknowMailsAutoCleanup && autoCleanupSetting.cleanUnknowMailsDays > 0) { await cleanup( - { env: env, }, + { env: env, } as Context, "mails_unknow", autoCleanupSetting.cleanUnknowMailsDays ); } if (autoCleanupSetting.enableSendBoxAutoCleanup && autoCleanupSetting.cleanSendBoxDays > 0) { await cleanup( - { env: env, }, + { env: env, } as Context, "sendbox", autoCleanupSetting.cleanSendBoxDays ); diff --git a/worker/src/telegram_api/common.ts b/worker/src/telegram_api/common.ts new file mode 100644 index 00000000..578d566f --- /dev/null +++ b/worker/src/telegram_api/common.ts @@ -0,0 +1,35 @@ +import { Context } from "hono"; +import { Jwt } from "hono/utils/jwt"; +import { CONSTANTS } from "../constants"; +import { HonoCustomType } from "../types"; + +export const unbindTelegramAddress = async ( + c: Context, userId: string, address: string +): Promise => { + const jwtList = await c.env.KV.get(`${CONSTANTS.TG_KV_PREFIX}:${userId}`, 'json') || []; + const newJwtList = []; + for (const jwt of jwtList) { + try { + const { address: kvAddress } = await Jwt.verify(jwt, c.env.JWT_SECRET, "HS256"); + if (kvAddress == address) { + continue; + } + } catch (e) { + console.log(`解绑失败: ${(e as Error).message}`); + } + newJwtList.push(jwt); + } + await c.env.KV.put(`${CONSTANTS.TG_KV_PREFIX}:${userId}`, JSON.stringify(newJwtList)); + await c.env.KV.delete(`${CONSTANTS.TG_KV_PREFIX}:${address}`); + return true; +} + +export const unbindTelegramByAddress = async ( + c: Context, address: string +): Promise => { + const userId = await c.env.KV.get(`${CONSTANTS.TG_KV_PREFIX}:${address}`) + if (userId) { + return await unbindTelegramAddress(c, userId, address); + } + return true; +} diff --git a/worker/src/telegram_api/index.ts b/worker/src/telegram_api/index.ts index 0cf95bc9..743f3866 100644 --- a/worker/src/telegram_api/index.ts +++ b/worker/src/telegram_api/index.ts @@ -2,12 +2,12 @@ import { Hono } from 'hono' import { ServerResponse } from 'node:http' import { Writable } from 'node:stream' -import { Bindings } from '../types' +import { HonoCustomType } from '../types' import { newTelegramBot, initTelegramBotCommands, sendMailToTelegram } from './telegram' import settings from './settings' import miniapp from './miniapp' -export const api = new Hono<{ Bindings: Bindings }>(); +export const api = new Hono(); export { sendMailToTelegram } api.use("/telegram/*", async (c, next) => { diff --git a/worker/src/telegram_api/miniapp.ts b/worker/src/telegram_api/miniapp.ts index 4e4dc934..1b137142 100644 --- a/worker/src/telegram_api/miniapp.ts +++ b/worker/src/telegram_api/miniapp.ts @@ -1,12 +1,12 @@ import { Context } from "hono"; import { Jwt } from 'hono/utils/jwt' -import { Bindings } from "../types"; +import { HonoCustomType } from "../types"; import { CONSTANTS } from "../constants"; const encoder = new TextEncoder(); -async function getTelegramBindAddress(c: Context<{ Bindings: Bindings }>): Promise { +async function getTelegramBindAddress(c: Context): Promise { // check if the request is from telegram const { initData } = await c.req.json(); const initDataObj = new URLSearchParams(initData); diff --git a/worker/src/telegram_api/settings.ts b/worker/src/telegram_api/settings.ts index 8b83278d..3c02dcd4 100644 --- a/worker/src/telegram_api/settings.ts +++ b/worker/src/telegram_api/settings.ts @@ -1,5 +1,5 @@ import { Context } from "hono"; -import { Bindings } from "../types"; +import { HonoCustomType } from "../types"; import { CONSTANTS } from "../constants"; export class TelegramSettings { @@ -12,13 +12,13 @@ export class TelegramSettings { } } -async function getTelegramSettings(c: Context<{ Bindings: Bindings }>): Promise { +async function getTelegramSettings(c: Context): Promise { const settings = await c.env.KV.get(CONSTANTS.TG_KV_SETTINGS_KEY, "json"); return c.json(settings || new TelegramSettings(false, [])); } -async function saveTelegramSettings(c: Context<{ Bindings: Bindings }>): Promise { +async function saveTelegramSettings(c: Context): Promise { const settings = await c.req.json(); await c.env.KV.put(CONSTANTS.TG_KV_SETTINGS_KEY, JSON.stringify(settings)); return c.json({ success: true }) diff --git a/worker/src/telegram_api/telegram.ts b/worker/src/telegram_api/telegram.ts index 8a629b23..fd9d4126 100644 --- a/worker/src/telegram_api/telegram.ts +++ b/worker/src/telegram_api/telegram.ts @@ -6,12 +6,12 @@ import { callbackQuery } from "telegraf/filters"; import PostalMime from 'postal-mime'; import { CONSTANTS } from "../constants"; -// @ts-ignore import { getIntValue, getDomains, getStringValue } from '../utils'; // @ts-ignore -import { newAddress } from '../common' -import { Bindings } from "../types"; +import { deleteAddressWithData, newAddress } from '../common' +import { HonoCustomType } from "../types"; import { TelegramSettings } from "./settings"; +import { unbindTelegramAddress } from "./common"; const COMMANDS = [ { @@ -30,13 +30,21 @@ const COMMANDS = [ command: "bind", description: "绑定邮箱地址, 请输入 /bind <邮箱地址凭证>" }, + { + command: "unbind", + description: "解绑邮箱地址, 请输入 /unbind <邮箱地址>" + }, + { + command: "delete", + description: "删除邮箱地址, 请输入 /delete <邮箱地址>" + }, { command: "mails", description: "查看邮件, 请输入 /mails <邮箱地址>, 不输入地址默认查看第一个地址" }, ] -export function newTelegramBot(c: Context<{ Bindings: Bindings }>, token: string): Telegraf { +export function newTelegramBot(c: Context, token: string): Telegraf { const bot = new Telegraf(token); bot.use(async (ctx, next) => { @@ -55,8 +63,12 @@ export function newTelegramBot(c: Context<{ Bindings: Bindings }>, token: string ) { return await ctx.reply("您没有权限使用此机器人"); } - - await next(); + try { + await next(); + } catch (error) { + console.error(`Error: ${error}`); + return await ctx.reply(`Error: ${error}`); + } }) bot.command("start", async (ctx: TgContext) => { @@ -65,8 +77,6 @@ export function newTelegramBot(c: Context<{ Bindings: Bindings }>, token: string return await ctx.reply( "欢迎使用本机器人, 您可以点击左下角打开 mini app \n\n" + (prefix ? `当前已启用前缀: ${prefix}\n` : '') - + "新建邮箱地址, 如果要自定义邮箱地址, " - + "请输入 /new @, name [a-zA-Z0-9.] 有效\n" + `当前可用域名: ${JSON.stringify(domains)}\n` + "请使用以下命令:\n" + COMMANDS.map(c => `/${c.command}: ${c.description}`).join("\n") @@ -137,6 +147,40 @@ export function newTelegramBot(c: Context<{ Bindings: Bindings }>, token: string } }); + bot.command("unbind", async (ctx: TgContext) => { + const userId = ctx?.message?.from?.id; + if (!userId) { + return await ctx.reply("无法获取用户信息"); + } + try { + // @ts-ignore + const address = ctx?.message?.text.slice("/unbind".length).trim(); + if (!address) { + return await ctx.reply("请输入地址"); + } + await unbindTelegramAddress(c, userId.toString(), address); + return await ctx.reply(`解绑成功:\n地址: ${address}` + ); + } + catch (e) { + return await ctx.reply(`解绑失败: ${(e as Error).message}`); + } + }) + + bot.command("delete", async (ctx: TgContext) => { + try { + // @ts-ignore + const address = ctx?.message?.text.slice("/unbind".length).trim(); + if (!address) { + return await ctx.reply("请输入地址"); + } + await deleteAddressWithData(c, address, null) + return await ctx.reply(`删除成功: ${address}`); + } catch (e) { + return await ctx.reply(`删除失败: ${(e as Error).message}`); + } + }); + bot.command("address", async (ctx) => { const userId = ctx?.message?.from?.id; if (!userId) { @@ -251,7 +295,7 @@ const parseMail = async (raw_mail: string | undefined | null) => { try { const parsedEmail = await PostalMime.parse(raw_mail); if (parsedEmail?.text?.length && parsedEmail?.text?.length > 1000) { - parsedEmail.text = parsedEmail.text.substring(0, 1000) + "..."; + parsedEmail.text = parsedEmail.text.substring(0, 1000) + "...消息过长请到miniapp查看"; } return { isHtml: false, @@ -270,7 +314,7 @@ const parseMail = async (raw_mail: string | undefined | null) => { } -export async function sendMailToTelegram(c: Context<{ Bindings: Bindings }>, address: string, raw_mail: string) { +export async function sendMailToTelegram(c: Context, address: string, raw_mail: string) { if (!c.env.TELEGRAM_BOT_TOKEN || !c.env.KV) { return; } diff --git a/worker/src/types.d.ts b/worker/src/types.d.ts index 3af39be1..796c4d47 100644 --- a/worker/src/types.d.ts +++ b/worker/src/types.d.ts @@ -6,6 +6,9 @@ export type Bindings = { // config PREFIX: string | undefined + DOMAINS: string | string[] | undefined + PASSWORDS: string | string[] | undefined + ADMIN_PASSWORDS: string | string[] | undefined JWT_SECRET: string BLACK_LIST: string | undefined ENABLE_AUTO_REPLY: string | boolean | undefined @@ -18,6 +21,7 @@ export type Bindings = { // cf turnstile CF_TURNSTILE_SITE_KEY: string | undefined + CF_TURNSTILE_SECRET_KEY: string | undefined // telegram config TELEGRAM_BOT_TOKEN: string @@ -40,3 +44,8 @@ type Variables = { userPayload: UserPayload, jwtPayload: JwtPayload } + +type HonoCustomType = { + "Bindings": Bindings; + "Variables": Variables; +} diff --git a/worker/src/utils.js b/worker/src/utils.ts similarity index 69% rename from worker/src/utils.js rename to worker/src/utils.ts index e0e445c7..55e0c617 100644 --- a/worker/src/utils.js +++ b/worker/src/utils.ts @@ -1,6 +1,10 @@ +import { Context } from "hono"; import { createMimeMessage } from "mimetext"; +import { HonoCustomType } from "./types"; -export const getJsonSetting = async (c, key) => { +export const getJsonSetting = async ( + c: Context, key: string +): Promise => { const value = await getSetting(c, key); if (!value) { return null; @@ -13,11 +17,13 @@ export const getJsonSetting = async (c, key) => { return null; } -export const getSetting = async (c, key) => { +export const getSetting = async ( + c: Context, key: string +): Promise => { try { const value = await c.env.DB.prepare( `SELECT value FROM settings where key = ?` - ).bind(key).first("value"); + ).bind(key).first("value"); return value; } catch (error) { console.error(`GetSetting: Failed to get ${key}`, error); @@ -25,7 +31,10 @@ export const getSetting = async (c, key) => { return null; } -export const saveSetting = async (c, key, value) => { +export const saveSetting = async ( + c: Context, + key: string, value: string +) => { await c.env.DB.prepare( `INSERT or REPLACE INTO settings (key, value) VALUES (?, ?)` + ` ON CONFLICT(key) DO UPDATE SET value = ?, updated_at = datetime('now')` @@ -33,14 +42,16 @@ export const saveSetting = async (c, key, value) => { return true; } -export const getStringValue = (value) => { +export const getStringValue = (value: any): string => { if (typeof value === "string") { return value; } return ""; } -export const getBooleanValue = (value) => { +export const getBooleanValue = ( + value: boolean | string | any +): boolean => { if (typeof value === "boolean") { return value; } @@ -51,7 +62,10 @@ export const getBooleanValue = (value) => { return false; } -export const getIntValue = (value, defaultValue = 0) => { +export const getIntValue = ( + value: number | string | any, + defaultValue: number = 0 +): number => { if (typeof value === "number") { return value; } @@ -65,7 +79,7 @@ export const getIntValue = (value, defaultValue = 0) => { return defaultValue; } -export const getDomains = (c) => { +export const getDomains = (c: Context): string[] => { if (!c.env.DOMAINS) { return []; } @@ -81,14 +95,14 @@ export const getDomains = (c) => { return c.env.DOMAINS; } -export const getPasswords = (c) => { +export const getPasswords = (c: Context): string[] => { if (!c.env.PASSWORDS) { return []; } // check if PASSWORDS is an array, if not use json.parse if (!Array.isArray(c.env.PASSWORDS)) { try { - let res = JSON.parse(c.env.PASSWORDS); + const res = JSON.parse(c.env.PASSWORDS) as string[]; return res.filter((item) => item.length > 0); } catch (e) { console.error("Failed to parse PASSWORDS", e); @@ -98,23 +112,26 @@ export const getPasswords = (c) => { return c.env.PASSWORDS.filter((item) => item.length > 0); } -export const getAdminPasswords = (c) => { +export const getAdminPasswords = (c: Context): string[] => { if (!c.env.ADMIN_PASSWORDS) { return []; } // check if ADMIN_PASSWORDS is an array, if not use json.parse if (!Array.isArray(c.env.ADMIN_PASSWORDS)) { try { - return JSON.parse(c.env.ADMIN_PASSWORDS); + const res = JSON.parse(c.env.ADMIN_PASSWORDS) as string[]; + return res.filter((item) => item.length > 0); } catch (e) { console.error("Failed to parse ADMIN_PASSWORDS", e); return []; } } - return c.env.ADMIN_PASSWORDS; + return c.env.ADMIN_PASSWORDS.filter((item) => item.length > 0); } -export const sendAdminInternalMail = async (c, toMail, subject, text) => { +export const sendAdminInternalMail = async ( + c: Context, toMail: string, subject: string, text: string +): Promise => { try { const msg = createMimeMessage(); @@ -144,31 +161,33 @@ export const sendAdminInternalMail = async (c, toMail, subject, text) => { } }; -export const checkCfTurnstile = async (c, token) => { - if (!c.env.CF_TURNSTILE_SITE_KEY) { +export const checkCfTurnstile = async ( + c: Context, token: string | undefined | null +): Promise => { + if (!c.env.CF_TURNSTILE_SITE_KEY || !c.env.CF_TURNSTILE_SECRET_KEY) { return; } if (!token) { throw new Error("Captcha token is required"); } - const reqIp = c.req.raw.headers.get("cf-connecting-ip") + const reqIp = c.req.raw.headers.get("cf-connecting-ip"); let formData = new FormData(); formData.append('secret', c.env.CF_TURNSTILE_SECRET_KEY); formData.append('response', token); - formData.append('remoteip', reqIp); + if (reqIp) formData.append('remoteip', reqIp); const url = 'https://challenges.cloudflare.com/turnstile/v0/siteverify'; const result = await fetch(url, { body: formData, method: 'POST', }); - const captchaRes = await result.json(); + const captchaRes: any = await result.json(); if (!captchaRes.success) { console.log("Captcha failed", captchaRes); throw new Error("Captcha failed"); } } -export const checkUserPassword = (password) => { +export const checkUserPassword = (password: string) => { if (!password || password.length < 1 || password.length > 100) { throw new Error("Invalid password") } diff --git a/worker/src/worker.ts b/worker/src/worker.ts index d70b61fd..2bff7bab 100644 --- a/worker/src/worker.ts +++ b/worker/src/worker.ts @@ -3,7 +3,6 @@ import { cors } from 'hono/cors'; import { jwt } from 'hono/jwt' import { Jwt } from 'hono/utils/jwt' -// @ts-ignore import { api as commonApi } from './commom_api'; // @ts-ignore import { api as mailsApi } from './mails_api' @@ -18,13 +17,11 @@ import { api as apiSendMail } from './mails_api/send_mail_api' import { api as telegramApi } from './telegram_api' import { email } from './email'; -// @ts-ignore import { scheduled } from './scheduled'; -// @ts-ignore import { getAdminPasswords, getPasswords, getBooleanValue } from './utils'; -import { Bindings } from './types'; +import { HonoCustomType } from './types'; -const app = new Hono<{ Bindings: Bindings }>() +const app = new Hono() //cors app.use('/*', cors()); // rate limit @@ -88,6 +85,7 @@ app.use('/user_api/*', async (c, next) => { } try { const token = c.req.raw.headers.get("x-user-token"); + if (!token) return c.text("Need User Token", 401) const payload = await Jwt.verify(token, c.env.JWT_SECRET, "HS256"); // check expired if (!payload.exp) return c.text("Invalid Token", 401);