mirror of
https://github.com/dreamhunter2333/cloudflare_temp_email.git
synced 2026-08-28 19:48:01 +08:00
feat: telegram bot global push (#269)
This commit is contained in:
@@ -1,6 +1,12 @@
|
|||||||
<!-- markdownlint-disable-file MD004 MD024 MD034 MD036 -->
|
<!-- markdownlint-disable-file MD004 MD024 MD034 MD036 -->
|
||||||
# CHANGE LOG
|
# CHANGE LOG
|
||||||
|
|
||||||
|
## main branch
|
||||||
|
|
||||||
|
- UI lazy load
|
||||||
|
- telegram bot 添加用户全局推送功能
|
||||||
|
- 增加对 cloudflare verified 用户发送邮件
|
||||||
|
|
||||||
## v0.4.4
|
## v0.4.4
|
||||||
|
|
||||||
- 增加 telegram mini app
|
- 增加 telegram mini app
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ const { t } = useI18n({
|
|||||||
address_block_list: 'Address Block Keywords for Users(Admin can skip)',
|
address_block_list: 'Address Block Keywords for Users(Admin can skip)',
|
||||||
address_block_list_placeholder: 'Please enter the keywords you want to block',
|
address_block_list_placeholder: 'Please enter the keywords you want to block',
|
||||||
send_address_block_list: 'Address Block Keywords for send email',
|
send_address_block_list: 'Address Block Keywords for send email',
|
||||||
|
verified_address_list: 'Verified Address List(Can send email by cf internal api)',
|
||||||
},
|
},
|
||||||
zh: {
|
zh: {
|
||||||
save: '保存',
|
save: '保存',
|
||||||
@@ -24,18 +25,21 @@ const { t } = useI18n({
|
|||||||
address_block_list: '邮件地址屏蔽关键词(管理员可跳过检查)',
|
address_block_list: '邮件地址屏蔽关键词(管理员可跳过检查)',
|
||||||
address_block_list_placeholder: '请输入您想要屏蔽的关键词',
|
address_block_list_placeholder: '请输入您想要屏蔽的关键词',
|
||||||
send_address_block_list: '发送邮件地址屏蔽关键词',
|
send_address_block_list: '发送邮件地址屏蔽关键词',
|
||||||
|
verified_address_list: '已验证地址列表(可通过 cf 内部 api 发送邮件)',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const addressBlockList = ref([])
|
const addressBlockList = ref([])
|
||||||
const sendAddressBlockList = ref([])
|
const sendAddressBlockList = ref([])
|
||||||
|
const verifiedAddressList = ref([])
|
||||||
|
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
try {
|
try {
|
||||||
const res = await api.fetch(`/admin/account_settings`)
|
const res = await api.fetch(`/admin/account_settings`)
|
||||||
addressBlockList.value = res.blockList || []
|
addressBlockList.value = res.blockList || []
|
||||||
sendAddressBlockList.value = res.sendBlockList || []
|
sendAddressBlockList.value = res.sendBlockList || []
|
||||||
|
verifiedAddressList.value = res.verifiedAddressList || []
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
message.error(error.message || "error");
|
message.error(error.message || "error");
|
||||||
}
|
}
|
||||||
@@ -47,7 +51,8 @@ const save = async () => {
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
blockList: addressBlockList.value || [],
|
blockList: addressBlockList.value || [],
|
||||||
sendBlockList: sendAddressBlockList.value || []
|
sendBlockList: sendAddressBlockList.value || [],
|
||||||
|
verifiedAddressList: verifiedAddressList.value || []
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
message.success(t('successTip'))
|
message.success(t('successTip'))
|
||||||
@@ -73,6 +78,10 @@ onMounted(async () => {
|
|||||||
<n-select v-model:value="sendAddressBlockList" filterable multiple tag
|
<n-select v-model:value="sendAddressBlockList" filterable multiple tag
|
||||||
:placeholder="t('address_block_list_placeholder')" />
|
:placeholder="t('address_block_list_placeholder')" />
|
||||||
</n-form-item-row>
|
</n-form-item-row>
|
||||||
|
<n-form-item-row :label="t('verified_address_list')">
|
||||||
|
<n-select v-model:value="verifiedAddressList" filterable multiple tag
|
||||||
|
:placeholder="t('verified_address_list')" />
|
||||||
|
</n-form-item-row>
|
||||||
<n-button @click="save" type="primary" block :loading="loading">
|
<n-button @click="save" type="primary" block :loading="loading">
|
||||||
{{ t('save') }}
|
{{ t('save') }}
|
||||||
</n-button>
|
</n-button>
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ const { t } = useI18n({
|
|||||||
telegramAllowList: 'Telegram Allow List',
|
telegramAllowList: 'Telegram Allow List',
|
||||||
save: 'Save',
|
save: 'Save',
|
||||||
miniAppUrl: 'Telegram Mini App URL',
|
miniAppUrl: 'Telegram Mini App URL',
|
||||||
|
enableGlobalMailPush: 'Enable Global Mail Push(Manually input telegram user ID)',
|
||||||
|
globalMailPushList: 'Global Mail Push List',
|
||||||
},
|
},
|
||||||
zh: {
|
zh: {
|
||||||
init: '初始化',
|
init: '初始化',
|
||||||
@@ -33,6 +35,8 @@ const { t } = useI18n({
|
|||||||
telegramAllowList: 'Telegram 白名单',
|
telegramAllowList: 'Telegram 白名单',
|
||||||
save: '保存',
|
save: '保存',
|
||||||
miniAppUrl: '电报小程序 URL(请输入你部署的电报小程序网页地址)',
|
miniAppUrl: '电报小程序 URL(请输入你部署的电报小程序网页地址)',
|
||||||
|
enableGlobalMailPush: '启用全局邮件推送(手动输入 telegram 用户 ID)',
|
||||||
|
globalMailPushList: '全局邮件推送用户列表',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -66,15 +70,22 @@ class TelegramSettings {
|
|||||||
enableAllowList: boolean;
|
enableAllowList: boolean;
|
||||||
allowList: string[];
|
allowList: string[];
|
||||||
miniAppUrl: string;
|
miniAppUrl: string;
|
||||||
|
enableGlobalMailPush: boolean;
|
||||||
|
globalMailPushList: string[];
|
||||||
|
|
||||||
constructor(enableAllowList: boolean, allowList: string[], miniAppUrl: string) {
|
constructor(
|
||||||
|
enableAllowList: boolean, allowList: string[], miniAppUrl: string,
|
||||||
|
enableGlobalMailPush: boolean, globalMailPushList: string[]
|
||||||
|
) {
|
||||||
this.enableAllowList = enableAllowList;
|
this.enableAllowList = enableAllowList;
|
||||||
this.allowList = allowList;
|
this.allowList = allowList;
|
||||||
this.miniAppUrl = miniAppUrl;
|
this.miniAppUrl = miniAppUrl;
|
||||||
|
this.enableGlobalMailPush = enableGlobalMailPush;
|
||||||
|
this.globalMailPushList = globalMailPushList;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const settings = ref(new TelegramSettings(false, [], ''))
|
const settings = ref(new TelegramSettings(false, [], '', false, []))
|
||||||
|
|
||||||
const getSettings = async () => {
|
const getSettings = async () => {
|
||||||
try {
|
try {
|
||||||
@@ -115,6 +126,15 @@ onMounted(async () => {
|
|||||||
:placeholder="t('telegramAllowList')" />
|
:placeholder="t('telegramAllowList')" />
|
||||||
</n-input-group>
|
</n-input-group>
|
||||||
</n-form-item-row>
|
</n-form-item-row>
|
||||||
|
<n-form-item-row :label="t('enableGlobalMailPush')">
|
||||||
|
<n-input-group>
|
||||||
|
<n-checkbox v-model:checked="settings.enableGlobalMailPush" style="width: 20%;">
|
||||||
|
{{ t('enable') }}
|
||||||
|
</n-checkbox>
|
||||||
|
<n-select v-model:value="settings.globalMailPushList" filterable multiple tag
|
||||||
|
style="width: 80%;" :placeholder="t('globalMailPushList')" />
|
||||||
|
</n-input-group>
|
||||||
|
</n-form-item-row>
|
||||||
<n-form-item-row :label="t('miniAppUrl')">
|
<n-form-item-row :label="t('miniAppUrl')">
|
||||||
<n-input v-model:value="settings.miniAppUrl"></n-input>
|
<n-input v-model:value="settings.miniAppUrl"></n-input>
|
||||||
</n-form-item-row>
|
</n-form-item-row>
|
||||||
|
|||||||
@@ -68,6 +68,11 @@ node_compat = true
|
|||||||
# [triggers]
|
# [triggers]
|
||||||
# crons = [ "0 0 * * *" ]
|
# crons = [ "0 0 * * *" ]
|
||||||
|
|
||||||
|
# send mail by cf mail
|
||||||
|
# send_email = [
|
||||||
|
# { name = "SEND_MAIL" },
|
||||||
|
# ]
|
||||||
|
|
||||||
[vars]
|
[vars]
|
||||||
PREFIX = "tmp" # The mailbox name prefix to be processed
|
PREFIX = "tmp" # The mailbox name prefix to be processed
|
||||||
# If you want your site to be private, uncomment below and change your password
|
# If you want your site to be private, uncomment below and change your password
|
||||||
|
|||||||
@@ -36,6 +36,11 @@ node_compat = true
|
|||||||
# [triggers]
|
# [triggers]
|
||||||
# crons = [ "0 0 * * *" ]
|
# crons = [ "0 0 * * *" ]
|
||||||
|
|
||||||
|
# 通过 Cloudflare 发送邮件
|
||||||
|
# send_email = [
|
||||||
|
# { name = "SEND_MAIL" },
|
||||||
|
# ]
|
||||||
|
|
||||||
[vars]
|
[vars]
|
||||||
PREFIX = "tmp" # 要处理的邮箱名称前缀,不需要后缀可配置为空字符串
|
PREFIX = "tmp" # 要处理的邮箱名称前缀,不需要后缀可配置为空字符串
|
||||||
# 如果你想要你的网站私有,取消下面的注释,并修改密码
|
# 如果你想要你的网站私有,取消下面的注释,并修改密码
|
||||||
|
|||||||
@@ -1,21 +1,27 @@
|
|||||||
|
import { Context } from 'hono';
|
||||||
|
|
||||||
import { CONSTANTS } from '../constants';
|
import { CONSTANTS } from '../constants';
|
||||||
import { getJsonSetting, saveSetting, checkUserPassword, getDomains } from '../utils';
|
import { getJsonSetting, saveSetting, checkUserPassword, getDomains } from '../utils';
|
||||||
import { UserSettings, GeoData, UserInfo } from "../models";
|
import { UserSettings, GeoData, UserInfo } from "../models";
|
||||||
import { handleListQuery } from '../common'
|
import { handleListQuery } from '../common'
|
||||||
|
import { HonoCustomType } from '../types';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
getSetting: async (c) => {
|
getSetting: async (c: Context<HonoCustomType>) => {
|
||||||
const value = await getJsonSetting(c, CONSTANTS.USER_SETTINGS_KEY);
|
const value = await getJsonSetting(c, CONSTANTS.USER_SETTINGS_KEY);
|
||||||
const settings = new UserSettings(value);
|
const settings = new UserSettings(value);
|
||||||
return c.json(settings)
|
return c.json(settings)
|
||||||
},
|
},
|
||||||
saveSetting: async (c) => {
|
saveSetting: async (c: Context<HonoCustomType>) => {
|
||||||
const value = await c.req.json();
|
const value = await c.req.json();
|
||||||
const settings = new UserSettings(value);
|
const settings = new UserSettings(value);
|
||||||
if (settings.enableMailVerify && !c.env.KV) {
|
if (settings.enableMailVerify && !c.env.KV) {
|
||||||
return c.text("Please enable KV first if you want to enable mail verify", 403)
|
return c.text("Please enable KV first if you want to enable mail verify", 403)
|
||||||
}
|
}
|
||||||
if (settings.enableMailVerify) {
|
if (settings.enableMailVerify && !settings.verifyMailSender) {
|
||||||
|
return c.text("Please provide verifyMailSender", 400)
|
||||||
|
}
|
||||||
|
if (settings.enableMailVerify && settings.verifyMailSender) {
|
||||||
const mailDomain = settings.verifyMailSender.split("@")[1];
|
const mailDomain = settings.verifyMailSender.split("@")[1];
|
||||||
const domains = getDomains(c);
|
const domains = getDomains(c);
|
||||||
if (!domains.includes(mailDomain)) {
|
if (!domains.includes(mailDomain)) {
|
||||||
@@ -28,7 +34,7 @@ export default {
|
|||||||
await saveSetting(c, CONSTANTS.USER_SETTINGS_KEY, JSON.stringify(settings));
|
await saveSetting(c, CONSTANTS.USER_SETTINGS_KEY, JSON.stringify(settings));
|
||||||
return c.json({ success: true })
|
return c.json({ success: true })
|
||||||
},
|
},
|
||||||
getUsers: async (c) => {
|
getUsers: async (c: Context<HonoCustomType>) => {
|
||||||
const { limit, offset, query } = c.req.query();
|
const { limit, offset, query } = c.req.query();
|
||||||
if (query) {
|
if (query) {
|
||||||
return await handleListQuery(c,
|
return await handleListQuery(c,
|
||||||
@@ -48,15 +54,15 @@ export default {
|
|||||||
[], limit, offset
|
[], limit, offset
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
createUser: async (c) => {
|
createUser: async (c: Context<HonoCustomType>) => {
|
||||||
const { email, password } = await c.req.json();
|
const { email, password } = await c.req.json();
|
||||||
if (!email || !password) {
|
if (!email || !password) {
|
||||||
return c.text("Invalid email or password", 400)
|
return c.text("Invalid email or password", 400)
|
||||||
}
|
}
|
||||||
// geo data
|
// geo data
|
||||||
const reqIp = c.req.raw.headers.get("cf-connecting-ip")
|
const reqIp = c.req.raw.headers.get("cf-connecting-ip")
|
||||||
const geoData = new GeoData(reqIp, c.req.raw.cf);
|
const geoData = new GeoData(reqIp, c.req.raw.cf as any);
|
||||||
const userInfo = new UserInfo(geoData);
|
const userInfo = new UserInfo(geoData, email);
|
||||||
try {
|
try {
|
||||||
checkUserPassword(password);
|
checkUserPassword(password);
|
||||||
const { success } = await c.env.DB.prepare(
|
const { success } = await c.env.DB.prepare(
|
||||||
@@ -69,14 +75,15 @@ export default {
|
|||||||
return c.text("Failed to register", 500)
|
return c.text("Failed to register", 500)
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e.message && e.message.includes("UNIQUE")) {
|
const errorMsg = (e as Error).message;
|
||||||
|
if (errorMsg && errorMsg.includes("UNIQUE")) {
|
||||||
return c.text("User already exists", 400)
|
return c.text("User already exists", 400)
|
||||||
}
|
}
|
||||||
return c.text(`Failed to register: ${e.message}`, 500)
|
return c.text(`Failed to register: ${errorMsg}`, 500)
|
||||||
}
|
}
|
||||||
return c.json({ success: true })
|
return c.json({ success: true })
|
||||||
},
|
},
|
||||||
deleteUser: async (c) => {
|
deleteUser: async (c: Context<HonoCustomType>) => {
|
||||||
const { user_id } = c.req.param();
|
const { user_id } = c.req.param();
|
||||||
if (!user_id) return c.text("Invalid user_id", 400);
|
if (!user_id) return c.text("Invalid user_id", 400);
|
||||||
const { success } = await c.env.DB.prepare(
|
const { success } = await c.env.DB.prepare(
|
||||||
@@ -90,7 +97,7 @@ export default {
|
|||||||
}
|
}
|
||||||
return c.json({ success: true })
|
return c.json({ success: true })
|
||||||
},
|
},
|
||||||
resetPassword: async (c) => {
|
resetPassword: async (c: Context<HonoCustomType>) => {
|
||||||
const { user_id } = c.req.param();
|
const { user_id } = c.req.param();
|
||||||
const { password } = await c.req.json();
|
const { password } = await c.req.json();
|
||||||
if (!user_id) return c.text("Invalid user_id", 400);
|
if (!user_id) return c.text("Invalid user_id", 400);
|
||||||
@@ -103,7 +110,7 @@ export default {
|
|||||||
return c.text("Failed to reset password", 500)
|
return c.text("Failed to reset password", 500)
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return c.text(`Failed to reset password: ${e.message}`, 500)
|
return c.text(`Failed to reset password: ${(e as Error).message}`, 500)
|
||||||
}
|
}
|
||||||
return c.json({ success: true });
|
return c.json({ success: true });
|
||||||
},
|
},
|
||||||
@@ -1,25 +1,28 @@
|
|||||||
|
import { Context } from 'hono';
|
||||||
|
|
||||||
import { cleanup } from '../common';
|
import { cleanup } from '../common';
|
||||||
import { CONSTANTS } from '../constants';
|
import { CONSTANTS } from '../constants';
|
||||||
import { getJsonSetting, saveSetting } from '../utils';
|
import { getJsonSetting, saveSetting } from '../utils';
|
||||||
import { CleanupSettings } from '../models';
|
import { CleanupSettings } from '../models';
|
||||||
|
import { HonoCustomType } from '../types';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
cleanup: async (c) => {
|
cleanup: async (c: Context<HonoCustomType>) => {
|
||||||
const { cleanType, cleanDays } = await c.req.json();
|
const { cleanType, cleanDays } = await c.req.json();
|
||||||
try {
|
try {
|
||||||
await cleanup(c, cleanType, cleanDays);
|
await cleanup(c, cleanType, cleanDays);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
return c.text(`Failed to cleanup ${error.message}`, 500)
|
return c.text(`Failed to cleanup ${(error as Error).message}`, 500)
|
||||||
}
|
}
|
||||||
return c.json({ success: true })
|
return c.json({ success: true })
|
||||||
},
|
},
|
||||||
getCleanup: async (c) => {
|
getCleanup: async (c: Context<HonoCustomType>) => {
|
||||||
const value = await getJsonSetting(c, CONSTANTS.AUTO_CLEANUP_KEY);
|
const value = await getJsonSetting(c, CONSTANTS.AUTO_CLEANUP_KEY);
|
||||||
const cleanupSetting = new CleanupSettings(value);
|
const cleanupSetting = new CleanupSettings(value);
|
||||||
return c.json(cleanupSetting)
|
return c.json(cleanupSetting)
|
||||||
},
|
},
|
||||||
saveCleanup: async (c) => {
|
saveCleanup: async (c: Context<HonoCustomType>) => {
|
||||||
const value = await c.req.json();
|
const value = await c.req.json();
|
||||||
const cleanupSetting = new CleanupSettings(value);
|
const cleanupSetting = new CleanupSettings(value);
|
||||||
await saveSetting(c, CONSTANTS.AUTO_CLEANUP_KEY, JSON.stringify(cleanupSetting));
|
await saveSetting(c, CONSTANTS.AUTO_CLEANUP_KEY, JSON.stringify(cleanupSetting));
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
import { Hono } from 'hono'
|
import { Hono } from 'hono'
|
||||||
import { Jwt } from 'hono/utils/jwt'
|
import { Jwt } from 'hono/utils/jwt'
|
||||||
|
|
||||||
|
import { HonoCustomType } from '../types'
|
||||||
import { sendAdminInternalMail, getJsonSetting, saveSetting } from '../utils'
|
import { sendAdminInternalMail, getJsonSetting, saveSetting } from '../utils'
|
||||||
import { newAddress, handleListQuery } from '../common'
|
import { newAddress, handleListQuery } from '../common'
|
||||||
import { CONSTANTS } from '../constants'
|
import { CONSTANTS } from '../constants'
|
||||||
@@ -7,7 +9,7 @@ import cleanup_api from './cleanup_api'
|
|||||||
import admin_user_api from './admin_user_api'
|
import admin_user_api from './admin_user_api'
|
||||||
import webhook_settings from './webhook_settings'
|
import webhook_settings from './webhook_settings'
|
||||||
|
|
||||||
const api = new Hono()
|
export const api = new Hono<HonoCustomType>()
|
||||||
|
|
||||||
api.get('/admin/address', async (c) => {
|
api.get('/admin/address', async (c) => {
|
||||||
const { limit, offset, query } = c.req.query();
|
const { limit, offset, query } = c.req.query();
|
||||||
@@ -41,7 +43,7 @@ api.post('/admin/new_address', async (c) => {
|
|||||||
const res = await newAddress(c, name, domain, enablePrefix);
|
const res = await newAddress(c, name, domain, enablePrefix);
|
||||||
return c.json(res);
|
return c.json(res);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return c.text(`Failed create address: ${e.message}`, 400)
|
return c.text(`Failed create address: ${(e as Error).message}`, 400)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -181,16 +183,16 @@ api.get('/admin/sendbox', async (c) => {
|
|||||||
api.get('/admin/statistics', async (c) => {
|
api.get('/admin/statistics', async (c) => {
|
||||||
const { count: mailCount } = await c.env.DB.prepare(
|
const { count: mailCount } = await c.env.DB.prepare(
|
||||||
`SELECT count(*) as count FROM raw_mails`
|
`SELECT count(*) as count FROM raw_mails`
|
||||||
).first();
|
).first<{ count: number }>() || {};
|
||||||
const { count: addressCount } = await c.env.DB.prepare(
|
const { count: addressCount } = await c.env.DB.prepare(
|
||||||
`SELECT count(*) as count FROM address`
|
`SELECT count(*) as count FROM address`
|
||||||
).first();
|
).first<{ count: number }>() || {};
|
||||||
const { count: activeUserCount7days } = await c.env.DB.prepare(
|
const { count: activeUserCount7days } = await c.env.DB.prepare(
|
||||||
`SELECT count(*) as count FROM address where updated_at > datetime('now', '-7 day')`
|
`SELECT count(*) as count FROM address where updated_at > datetime('now', '-7 day')`
|
||||||
).first();
|
).first<{ count: number }>() || {};
|
||||||
const { count: sendMailCount } = await c.env.DB.prepare(
|
const { count: sendMailCount } = await c.env.DB.prepare(
|
||||||
`SELECT count(*) as count FROM sendbox`
|
`SELECT count(*) as count FROM sendbox`
|
||||||
).first();
|
).first<{ count: number }>() || {};
|
||||||
return c.json({
|
return c.json({
|
||||||
mailCount: mailCount,
|
mailCount: mailCount,
|
||||||
userCount: addressCount,
|
userCount: addressCount,
|
||||||
@@ -201,13 +203,13 @@ api.get('/admin/statistics', async (c) => {
|
|||||||
|
|
||||||
api.get('/admin/account_settings', async (c) => {
|
api.get('/admin/account_settings', async (c) => {
|
||||||
try {
|
try {
|
||||||
/** @type {Array<string>|undefined|null} */
|
|
||||||
const blockList = await getJsonSetting(c, CONSTANTS.ADDRESS_BLOCK_LIST_KEY);
|
const blockList = await getJsonSetting(c, CONSTANTS.ADDRESS_BLOCK_LIST_KEY);
|
||||||
/** @type {Array<string>|undefined|null} */
|
|
||||||
const sendBlockList = await getJsonSetting(c, CONSTANTS.SEND_BLOCK_LIST_KEY);
|
const sendBlockList = await getJsonSetting(c, CONSTANTS.SEND_BLOCK_LIST_KEY);
|
||||||
|
const verifiedAddressList = await getJsonSetting(c, CONSTANTS.VERIFIED_ADDRESS_LIST_KEY);
|
||||||
return c.json({
|
return c.json({
|
||||||
blockList: blockList || [],
|
blockList: blockList || [],
|
||||||
sendBlockList: sendBlockList || []
|
sendBlockList: sendBlockList || [],
|
||||||
|
verifiedAddressList: verifiedAddressList || []
|
||||||
})
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
@@ -217,10 +219,13 @@ api.get('/admin/account_settings', async (c) => {
|
|||||||
|
|
||||||
api.post('/admin/account_settings', async (c) => {
|
api.post('/admin/account_settings', async (c) => {
|
||||||
/** @type {{ blockList: Array<string>, sendBlockList: Array<string> }} */
|
/** @type {{ blockList: Array<string>, sendBlockList: Array<string> }} */
|
||||||
const { blockList, sendBlockList } = await c.req.json();
|
const { blockList, sendBlockList, verifiedAddressList } = await c.req.json();
|
||||||
if (!blockList || !sendBlockList) {
|
if (!blockList || !sendBlockList || !verifiedAddressList) {
|
||||||
return c.text("Invalid blockList or sendBlockList", 400)
|
return c.text("Invalid blockList or sendBlockList", 400)
|
||||||
}
|
}
|
||||||
|
if (!c.env.SEND_MAIL && verifiedAddressList.length > 0) {
|
||||||
|
return c.text("Please enable SEND_MAIL to use verifiedAddressList", 400)
|
||||||
|
}
|
||||||
await saveSetting(
|
await saveSetting(
|
||||||
c, CONSTANTS.ADDRESS_BLOCK_LIST_KEY,
|
c, CONSTANTS.ADDRESS_BLOCK_LIST_KEY,
|
||||||
JSON.stringify(blockList)
|
JSON.stringify(blockList)
|
||||||
@@ -229,6 +234,10 @@ api.post('/admin/account_settings', async (c) => {
|
|||||||
c, CONSTANTS.SEND_BLOCK_LIST_KEY,
|
c, CONSTANTS.SEND_BLOCK_LIST_KEY,
|
||||||
JSON.stringify(sendBlockList)
|
JSON.stringify(sendBlockList)
|
||||||
);
|
);
|
||||||
|
await saveSetting(
|
||||||
|
c, CONSTANTS.VERIFIED_ADDRESS_LIST_KEY,
|
||||||
|
JSON.stringify(verifiedAddressList)
|
||||||
|
)
|
||||||
return c.json({
|
return c.json({
|
||||||
success: true
|
success: true
|
||||||
})
|
})
|
||||||
@@ -245,5 +254,3 @@ api.post('/admin/users', admin_user_api.createUser)
|
|||||||
api.post('/admin/users/:user_id/reset_password', admin_user_api.resetPassword)
|
api.post('/admin/users/:user_id/reset_password', admin_user_api.resetPassword)
|
||||||
api.get("/admin/webhook/settings", webhook_settings.getWebhookSettings);
|
api.get("/admin/webhook/settings", webhook_settings.getWebhookSettings);
|
||||||
api.post("/admin/webhook/settings", webhook_settings.saveWebhookSettings);
|
api.post("/admin/webhook/settings", webhook_settings.saveWebhookSettings);
|
||||||
|
|
||||||
export { api }
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Context } from "hono";
|
import { Context } from "hono";
|
||||||
import { HonoCustomType } from "../types";
|
import { HonoCustomType } from "../types";
|
||||||
import { CONSTANTS } from "../constants";
|
import { CONSTANTS } from "../constants";
|
||||||
import { AdminWebhookSettings } from "../models/models";
|
import { AdminWebhookSettings } from "../models";
|
||||||
|
|
||||||
async function getWebhookSettings(c: Context<HonoCustomType>): Promise<Response> {
|
async function getWebhookSettings(c: Context<HonoCustomType>): Promise<Response> {
|
||||||
const settings = await c.env.KV.get<AdminWebhookSettings>(CONSTANTS.WEBHOOK_KV_SETTINGS_KEY, "json");
|
const settings = await c.env.KV.get<AdminWebhookSettings>(CONSTANTS.WEBHOOK_KV_SETTINGS_KEY, "json");
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ export const CONSTANTS = {
|
|||||||
SEND_BLOCK_LIST_KEY: 'send_block_list',
|
SEND_BLOCK_LIST_KEY: 'send_block_list',
|
||||||
AUTO_CLEANUP_KEY: 'auto_cleanup',
|
AUTO_CLEANUP_KEY: 'auto_cleanup',
|
||||||
USER_SETTINGS_KEY: 'user_settings',
|
USER_SETTINGS_KEY: 'user_settings',
|
||||||
|
VERIFIED_ADDRESS_LIST_KEY: 'verified_address_list',
|
||||||
|
|
||||||
// KV
|
// KV
|
||||||
TG_KV_PREFIX: "temp-mail-telegram",
|
TG_KV_PREFIX: "temp-mail-telegram",
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import { Context, Hono } from 'hono'
|
import { Context, Hono } from 'hono'
|
||||||
import { Jwt } from 'hono/utils/jwt'
|
import { Jwt } from 'hono/utils/jwt'
|
||||||
|
import { createMimeMessage } from 'mimetext';
|
||||||
|
|
||||||
import { CONSTANTS } from '../constants'
|
import { CONSTANTS } from '../constants'
|
||||||
import { getJsonSetting, getDomains, getIntValue } from '../utils';
|
import { getJsonSetting, getDomains, getIntValue } from '../utils';
|
||||||
import { GeoData } from '../models/models'
|
import { GeoData } from '../models'
|
||||||
import { handleListQuery } from '../common'
|
import { handleListQuery } from '../common'
|
||||||
import { HonoCustomType } from '../types';
|
import { HonoCustomType } from '../types';
|
||||||
|
|
||||||
@@ -34,6 +36,30 @@ api.post('/api/requset_send_mail_access', async (c) => {
|
|||||||
return c.json({ status: "ok" })
|
return c.json({ status: "ok" })
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const sendMailToVerifyAddress = async (
|
||||||
|
c: Context<HonoCustomType>, address: string,
|
||||||
|
reqJson: {
|
||||||
|
from_name: string, to_mail: string, to_name: string,
|
||||||
|
subject: string, content: string, is_html: boolean
|
||||||
|
}
|
||||||
|
) => {
|
||||||
|
const {
|
||||||
|
from_name, to_mail, to_name,
|
||||||
|
subject, content, is_html
|
||||||
|
} = reqJson;
|
||||||
|
const msg = createMimeMessage();
|
||||||
|
msg.setSender({ name: from_name, addr: address });
|
||||||
|
msg.setRecipient({ name: to_name, addr: to_mail });
|
||||||
|
msg.setSubject(subject);
|
||||||
|
msg.addMessage({
|
||||||
|
contentType: is_html ? 'text/html' : 'text/plain',
|
||||||
|
data: content
|
||||||
|
});
|
||||||
|
const { EmailMessage } = await import('cloudflare:email');
|
||||||
|
const message = new EmailMessage(address, to_mail, msg.asRaw());
|
||||||
|
await c.env.SEND_MAIL.send(message);
|
||||||
|
}
|
||||||
|
|
||||||
export const sendMail = async (
|
export const sendMail = async (
|
||||||
c: Context<HonoCustomType>, address: string,
|
c: Context<HonoCustomType>, address: string,
|
||||||
reqJson: {
|
reqJson: {
|
||||||
@@ -78,6 +104,15 @@ export const sendMail = async (
|
|||||||
if (!content) {
|
if (!content) {
|
||||||
throw new Error("Invalid content")
|
throw new Error("Invalid content")
|
||||||
}
|
}
|
||||||
|
// send to verified address list, do not update balance
|
||||||
|
if (c.env.SEND_MAIL) {
|
||||||
|
const verifiedAddressList = await getJsonSetting(c, CONSTANTS.VERIFIED_ADDRESS_LIST_KEY) || [];
|
||||||
|
if (verifiedAddressList.includes(to_mail)) {
|
||||||
|
return sendMailToVerifyAddress(c, address, {
|
||||||
|
from_name, to_mail, to_name, subject, content, is_html
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
let dmikBody = {}
|
let dmikBody = {}
|
||||||
if (c.env.DKIM_SELECTOR && c.env.DKIM_PRIVATE_KEY && address.includes("@")) {
|
if (c.env.DKIM_SELECTOR && c.env.DKIM_PRIVATE_KEY && address.includes("@")) {
|
||||||
dmikBody = {
|
dmikBody = {
|
||||||
@@ -169,7 +204,7 @@ api.post('/external/api/send_mail', async (c) => {
|
|||||||
return c.text("No address", 400)
|
return c.text("No address", 400)
|
||||||
}
|
}
|
||||||
const reqJson = await c.req.json();
|
const reqJson = await c.req.json();
|
||||||
await sendMail(c, address, reqJson);
|
await sendMail(c, address as string, reqJson);
|
||||||
return c.json({ status: "ok" })
|
return c.json({ status: "ok" })
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Failed to send mail", e);
|
console.error("Failed to send mail", e);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Context } from "hono";
|
import { Context } from "hono";
|
||||||
import { HonoCustomType } from "../types";
|
import { HonoCustomType } from "../types";
|
||||||
import { CONSTANTS } from "../constants";
|
import { CONSTANTS } from "../constants";
|
||||||
import { AdminWebhookSettings, WebhookMail } from "../models/models";
|
import { AdminWebhookSettings, WebhookMail } from "../models";
|
||||||
import { getBooleanValue } from "../utils";
|
import { getBooleanValue } from "../utils";
|
||||||
import PostalMime from 'postal-mime';
|
import PostalMime from 'postal-mime';
|
||||||
|
|
||||||
|
|||||||
@@ -1,87 +0,0 @@
|
|||||||
export class UserSettings {
|
|
||||||
/** @param {UserSettings|undefined|null} data */
|
|
||||||
constructor(data) {
|
|
||||||
if (data === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const {
|
|
||||||
enable, enableMailVerify, verifyMailSender,
|
|
||||||
enableMailAllowList, mailAllowList, maxAddressCount
|
|
||||||
} = data || {};
|
|
||||||
/** @type {boolean|undefined} */
|
|
||||||
this.enable = enable;
|
|
||||||
/** @type {boolean|undefined} */
|
|
||||||
this.enableMailVerify = enableMailVerify;
|
|
||||||
/** @type {string|undefined} */
|
|
||||||
this.verifyMailSender = verifyMailSender;
|
|
||||||
/** @type {boolean|undefined} */
|
|
||||||
this.enableMailAllowList = enableMailAllowList;
|
|
||||||
/** @type {Array<string>|undefined} */
|
|
||||||
this.mailAllowList = mailAllowList;
|
|
||||||
/** @type {number|undefined} */
|
|
||||||
this.maxAddressCount = maxAddressCount || 5;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class CleanupSettings {
|
|
||||||
/** @param {CleanupSettings|undefined|null} data */
|
|
||||||
constructor(data) {
|
|
||||||
const {
|
|
||||||
enableMailsAutoCleanup, cleanMailsDays,
|
|
||||||
enableUnknowMailsAutoCleanup, cleanUnknowMailsDays,
|
|
||||||
enableSendBoxAutoCleanup, cleanSendBoxDays
|
|
||||||
} = data || {};
|
|
||||||
/** @type {boolean|undefined} */
|
|
||||||
this.enableMailsAutoCleanup = enableMailsAutoCleanup;
|
|
||||||
/** @type {number|undefined} */
|
|
||||||
this.cleanMailsDays = cleanMailsDays;
|
|
||||||
/** @type {boolean|undefined} */
|
|
||||||
this.enableUnknowMailsAutoCleanup = enableUnknowMailsAutoCleanup;
|
|
||||||
/** @type {number|undefined} */
|
|
||||||
this.cleanUnknowMailsDays = cleanUnknowMailsDays;
|
|
||||||
/** @type {boolean|undefined} */
|
|
||||||
this.enableSendBoxAutoCleanup = enableSendBoxAutoCleanup;
|
|
||||||
/** @type {number|undefined} */
|
|
||||||
this.cleanSendBoxDays = cleanSendBoxDays;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class GeoData {
|
|
||||||
/** @param {string} ip @param {GeoData|undefined|null} data */
|
|
||||||
constructor(ip, data) {
|
|
||||||
const {
|
|
||||||
country, city, timezone, postalCode, region,
|
|
||||||
latitude, longitude, regionCode, asOrganization
|
|
||||||
} = data || {};
|
|
||||||
/** @type {string} */
|
|
||||||
this.ip = ip;
|
|
||||||
/** @type {string|undefined} */
|
|
||||||
this.country = country;
|
|
||||||
/** @type {string|undefined} */
|
|
||||||
this.city = city;
|
|
||||||
/** @type {string|undefined} */
|
|
||||||
this.timezone = timezone;
|
|
||||||
/** @type {string|undefined} */
|
|
||||||
this.postalCode = postalCode;
|
|
||||||
/** @type {string|undefined} */
|
|
||||||
this.region = region;
|
|
||||||
/** @type {number|undefined} */
|
|
||||||
this.latitude = latitude;
|
|
||||||
/** @type {number|undefined} */
|
|
||||||
this.longitude = longitude;
|
|
||||||
/** @type {string|undefined} */
|
|
||||||
this.regionCode = regionCode;
|
|
||||||
/** @type {string|undefined} */
|
|
||||||
this.asOrganization = asOrganization;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class UserInfo {
|
|
||||||
/** @param {GeoData} geoData @param {string} userEmail */
|
|
||||||
constructor(geoData, userEmail) {
|
|
||||||
/** @type {geoData} */
|
|
||||||
this.geoData = geoData;
|
|
||||||
/** @type {string} */
|
|
||||||
this.userEmail = userEmail;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -70,3 +70,37 @@ export class GeoData {
|
|||||||
this.asOrganization = asOrganization;
|
this.asOrganization = asOrganization;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class UserSettings {
|
||||||
|
|
||||||
|
enable: boolean | undefined;
|
||||||
|
enableMailVerify: boolean | undefined;
|
||||||
|
verifyMailSender: string | undefined;
|
||||||
|
enableMailAllowList: boolean | undefined;
|
||||||
|
mailAllowList: string[] | undefined;
|
||||||
|
maxAddressCount: number;
|
||||||
|
|
||||||
|
constructor(data: UserSettings | undefined | null) {
|
||||||
|
const {
|
||||||
|
enable, enableMailVerify, verifyMailSender,
|
||||||
|
enableMailAllowList, mailAllowList, maxAddressCount
|
||||||
|
} = data || {};
|
||||||
|
this.enable = enable;
|
||||||
|
this.enableMailVerify = enableMailVerify;
|
||||||
|
this.verifyMailSender = verifyMailSender;
|
||||||
|
this.enableMailAllowList = enableMailAllowList;
|
||||||
|
this.mailAllowList = mailAllowList;
|
||||||
|
this.maxAddressCount = maxAddressCount || 5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UserInfo {
|
||||||
|
|
||||||
|
geoData: GeoData;
|
||||||
|
userEmail: string;
|
||||||
|
|
||||||
|
constructor(geoData: GeoData, userEmail: string) {
|
||||||
|
this.geoData = geoData;
|
||||||
|
this.userEmail = userEmail;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@ import { Context } from 'hono';
|
|||||||
import { cleanup } from './common'
|
import { cleanup } from './common'
|
||||||
import { CONSTANTS } from './constants'
|
import { CONSTANTS } from './constants'
|
||||||
import { getJsonSetting } from './utils';
|
import { getJsonSetting } from './utils';
|
||||||
import { CleanupSettings } from './models/models';
|
import { CleanupSettings } from './models';
|
||||||
import { Bindings, HonoCustomType } from './types';
|
import { Bindings, HonoCustomType } from './types';
|
||||||
|
|
||||||
export async function scheduled(event: ScheduledEvent, env: Bindings, ctx: any) {
|
export async function scheduled(event: ScheduledEvent, env: Bindings, ctx: any) {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { HonoCustomType } from "../types";
|
|||||||
import { CONSTANTS } from "../constants";
|
import { CONSTANTS } from "../constants";
|
||||||
import { bindTelegramAddress, jwtListToAddressData, tgUserNewAddress, unbindTelegramAddress } from "./common";
|
import { bindTelegramAddress, jwtListToAddressData, tgUserNewAddress, unbindTelegramAddress } from "./common";
|
||||||
import { checkCfTurnstile } from "../utils";
|
import { checkCfTurnstile } from "../utils";
|
||||||
|
import { TelegramSettings } from "./settings";
|
||||||
|
|
||||||
const encoder = new TextEncoder();
|
const encoder = new TextEncoder();
|
||||||
const TG_AUTH_TIMEOUT = 300;
|
const TG_AUTH_TIMEOUT = 300;
|
||||||
@@ -130,7 +131,12 @@ async function getMail(c: Context<HonoCustomType>): Promise<Response> {
|
|||||||
const result = await c.env.DB.prepare(
|
const result = await c.env.DB.prepare(
|
||||||
`SELECT * FROM raw_mails where id = ?`
|
`SELECT * FROM raw_mails where id = ?`
|
||||||
).bind(mailId).first();
|
).bind(mailId).first();
|
||||||
if (result?.address && !(result.address as string in addressIdMap)) {
|
const settings = await c.env.KV.get<TelegramSettings>(CONSTANTS.TG_KV_SETTINGS_KEY, "json");
|
||||||
|
const superUser = settings?.enableGlobalMailPush && settings?.globalMailPushList.includes(userId);
|
||||||
|
if (
|
||||||
|
!superUser && result?.address &&
|
||||||
|
!(result.address as string in addressIdMap)
|
||||||
|
) {
|
||||||
return c.text("无权查看此邮件", 403);
|
return c.text("无权查看此邮件", 403);
|
||||||
}
|
}
|
||||||
const address_id = addressIdMap[result?.address as string];
|
const address_id = addressIdMap[result?.address as string];
|
||||||
|
|||||||
@@ -6,17 +6,24 @@ export class TelegramSettings {
|
|||||||
enableAllowList: boolean;
|
enableAllowList: boolean;
|
||||||
allowList: string[];
|
allowList: string[];
|
||||||
miniAppUrl: string;
|
miniAppUrl: string;
|
||||||
|
enableGlobalMailPush: boolean;
|
||||||
|
globalMailPushList: string[];
|
||||||
|
|
||||||
constructor(enableAllowList: boolean, allowList: string[], miniAppUrl: string) {
|
constructor(
|
||||||
|
enableAllowList: boolean, allowList: string[], miniAppUrl: string,
|
||||||
|
enableGlobalMailPush: boolean, globalMailPushList: string[]
|
||||||
|
) {
|
||||||
this.enableAllowList = enableAllowList;
|
this.enableAllowList = enableAllowList;
|
||||||
this.allowList = allowList;
|
this.allowList = allowList;
|
||||||
this.miniAppUrl = miniAppUrl;
|
this.miniAppUrl = miniAppUrl;
|
||||||
|
this.enableGlobalMailPush = enableGlobalMailPush;
|
||||||
|
this.globalMailPushList = globalMailPushList;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getTelegramSettings(c: Context<HonoCustomType>): Promise<Response> {
|
async function getTelegramSettings(c: Context<HonoCustomType>): Promise<Response> {
|
||||||
const settings = await c.env.KV.get<TelegramSettings>(CONSTANTS.TG_KV_SETTINGS_KEY, "json");
|
const settings = await c.env.KV.get<TelegramSettings>(CONSTANTS.TG_KV_SETTINGS_KEY, "json");
|
||||||
return c.json(settings || new TelegramSettings(false, [], ""));
|
return c.json(settings || new TelegramSettings(false, [], "", false, []));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -318,6 +318,15 @@ export async function sendMailToTelegram(
|
|||||||
url.searchParams.set("mail_id", mailId);
|
url.searchParams.set("mail_id", mailId);
|
||||||
miniAppButtons.push(Markup.button.webApp("查看邮件", url.toString()));
|
miniAppButtons.push(Markup.button.webApp("查看邮件", url.toString()));
|
||||||
}
|
}
|
||||||
|
if (settings?.enableGlobalMailPush && settings?.globalMailPushList) {
|
||||||
|
for (const pushId of settings.globalMailPushList) {
|
||||||
|
await bot.telegram.sendMessage(pushId, mail, {
|
||||||
|
...Markup.inlineKeyboard([
|
||||||
|
...miniAppButtons,
|
||||||
|
])
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
await bot.telegram.sendMessage(userId, mail, {
|
await bot.telegram.sendMessage(userId, mail, {
|
||||||
...Markup.inlineKeyboard([
|
...Markup.inlineKeyboard([
|
||||||
...miniAppButtons,
|
...miniAppButtons,
|
||||||
|
|||||||
Vendored
+1
@@ -3,6 +3,7 @@ export type Bindings = {
|
|||||||
DB: D1Database
|
DB: D1Database
|
||||||
KV: KVNamespace
|
KV: KVNamespace
|
||||||
RATE_LIMITER: any
|
RATE_LIMITER: any
|
||||||
|
SEND_MAIL: any
|
||||||
|
|
||||||
// config
|
// config
|
||||||
PREFIX: string | undefined
|
PREFIX: string | undefined
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
|
import { Context } from 'hono';
|
||||||
import { Jwt } from 'hono/utils/jwt'
|
import { Jwt } from 'hono/utils/jwt'
|
||||||
|
|
||||||
|
import { HonoCustomType } from '../types';
|
||||||
import { UserSettings } from "../models";
|
import { UserSettings } from "../models";
|
||||||
import { getJsonSetting } from "../utils"
|
import { getJsonSetting } from "../utils"
|
||||||
import { CONSTANTS } from "../constants";
|
import { CONSTANTS } from "../constants";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
bind: async (c) => {
|
bind: async (c: Context<HonoCustomType>) => {
|
||||||
const { user_id } = c.get("userPayload");
|
const { user_id } = c.get("userPayload");
|
||||||
const { address_id } = c.get("jwtPayload");
|
const { address_id } = c.get("jwtPayload");
|
||||||
if (!address_id || !user_id) {
|
if (!address_id || !user_id) {
|
||||||
@@ -36,7 +38,7 @@ export default {
|
|||||||
if (settings.maxAddressCount > 0) {
|
if (settings.maxAddressCount > 0) {
|
||||||
const { count } = await c.env.DB.prepare(
|
const { count } = await c.env.DB.prepare(
|
||||||
`SELECT COUNT(*) as count FROM users_address where user_id = ?`
|
`SELECT COUNT(*) as count FROM users_address where user_id = ?`
|
||||||
).bind(user_id).first();
|
).bind(user_id).first<{ count: number }>() || { count: 0 };
|
||||||
if (count >= settings.maxAddressCount) {
|
if (count >= settings.maxAddressCount) {
|
||||||
return c.text("Max address count reached", 400)
|
return c.text("Max address count reached", 400)
|
||||||
}
|
}
|
||||||
@@ -50,14 +52,15 @@ export default {
|
|||||||
return c.text("Failed to bind", 500)
|
return c.text("Failed to bind", 500)
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e.message && e.message.includes("UNIQUE")) {
|
const error = e as Error;
|
||||||
|
if (error.message && error.message.includes("UNIQUE")) {
|
||||||
return c.text("Address already binded, please unbind first", 400)
|
return c.text("Address already binded, please unbind first", 400)
|
||||||
}
|
}
|
||||||
return c.text("Failed to bind", 500)
|
return c.text("Failed to bind", 500)
|
||||||
}
|
}
|
||||||
return c.json({ success: true })
|
return c.json({ success: true })
|
||||||
},
|
},
|
||||||
unbind: async (c) => {
|
unbind: async (c: Context<HonoCustomType>) => {
|
||||||
const { user_id } = c.get("userPayload");
|
const { user_id } = c.get("userPayload");
|
||||||
const { address_id } = await c.req.json();
|
const { address_id } = await c.req.json();
|
||||||
if (!address_id || !user_id) {
|
if (!address_id || !user_id) {
|
||||||
@@ -90,7 +93,7 @@ export default {
|
|||||||
}
|
}
|
||||||
return c.json({ success: true })
|
return c.json({ success: true })
|
||||||
},
|
},
|
||||||
getBindedAddresses: async (c) => {
|
getBindedAddresses: async (c: Context<HonoCustomType>) => {
|
||||||
const { user_id } = c.get("userPayload");
|
const { user_id } = c.get("userPayload");
|
||||||
if (!user_id) {
|
if (!user_id) {
|
||||||
return c.text("No user token", 400)
|
return c.text("No user token", 400)
|
||||||
@@ -110,7 +113,7 @@ export default {
|
|||||||
results: results,
|
results: results,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
getBindedAddressJwt: async (c) => {
|
getBindedAddressJwt: async (c: Context<HonoCustomType>) => {
|
||||||
const { address_id } = c.req.param();
|
const { address_id } = c.req.param();
|
||||||
// check binded
|
// check binded
|
||||||
const { user_id } = c.get("userPayload");
|
const { user_id } = c.get("userPayload");
|
||||||
@@ -1,11 +1,8 @@
|
|||||||
import { Hono } from 'hono';
|
import { Hono } from 'hono';
|
||||||
|
|
||||||
import { HonoCustomType } from '../types';
|
import { HonoCustomType } from '../types';
|
||||||
// @ts-ignore
|
|
||||||
import settings from './settings';
|
import settings from './settings';
|
||||||
// @ts-ignore
|
|
||||||
import user from './user';
|
import user from './user';
|
||||||
// @ts-ignore
|
|
||||||
import bind_address from './bind_address';
|
import bind_address from './bind_address';
|
||||||
|
|
||||||
export const api = new Hono<HonoCustomType>();
|
export const api = new Hono<HonoCustomType>();
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
|
import { Context } from "hono";
|
||||||
|
|
||||||
|
import { HonoCustomType } from "../types";
|
||||||
import { UserSettings } from "../models";
|
import { UserSettings } from "../models";
|
||||||
import { getJsonSetting } from "../utils"
|
import { getJsonSetting } from "../utils"
|
||||||
import { CONSTANTS } from "../constants";
|
import { CONSTANTS } from "../constants";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
openSettings: async (c) => {
|
openSettings: async (c: Context<HonoCustomType>) => {
|
||||||
const value = await getJsonSetting(c, CONSTANTS.USER_SETTINGS_KEY);
|
const value = await getJsonSetting(c, CONSTANTS.USER_SETTINGS_KEY);
|
||||||
const settings = new UserSettings(value);
|
const settings = new UserSettings(value);
|
||||||
return c.json({
|
return c.json({
|
||||||
@@ -11,7 +14,7 @@ export default {
|
|||||||
enableMailVerify: settings.enableMailVerify,
|
enableMailVerify: settings.enableMailVerify,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
settings: async (c) => {
|
settings: async (c: Context<HonoCustomType>) => {
|
||||||
const user = c.get("userPayload");
|
const user = c.get("userPayload");
|
||||||
// check if user exists
|
// check if user exists
|
||||||
const db_user_id = await c.env.DB.prepare(
|
const db_user_id = await c.env.DB.prepare(
|
||||||
@@ -1,12 +1,14 @@
|
|||||||
|
import { Context } from 'hono';
|
||||||
import { Jwt } from 'hono/utils/jwt'
|
import { Jwt } from 'hono/utils/jwt'
|
||||||
|
|
||||||
|
import { HonoCustomType } from '../types';
|
||||||
import { checkCfTurnstile, getJsonSetting, checkUserPassword } from "../utils"
|
import { checkCfTurnstile, getJsonSetting, checkUserPassword } from "../utils"
|
||||||
import { CONSTANTS } from "../constants";
|
import { CONSTANTS } from "../constants";
|
||||||
import { GeoData, UserInfo, UserSettings } from "../models";
|
import { GeoData, UserInfo, UserSettings } from "../models";
|
||||||
import { sendMail } from "../mails_api/send_mail_api";
|
import { sendMail } from "../mails_api/send_mail_api";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
verifyCode: async (c) => {
|
verifyCode: async (c: Context<HonoCustomType>) => {
|
||||||
const { email, cf_token } = await c.req.json();
|
const { email, cf_token } = await c.req.json();
|
||||||
// check cf turnstile
|
// check cf turnstile
|
||||||
try {
|
try {
|
||||||
@@ -24,6 +26,9 @@ export default {
|
|||||||
) {
|
) {
|
||||||
return c.text(`Mail domain must in ${JSON.stringify(settings.mailAllowList, null, 2)}`, 400)
|
return c.text(`Mail domain must in ${JSON.stringify(settings.mailAllowList, null, 2)}`, 400)
|
||||||
}
|
}
|
||||||
|
if (!settings.verifyMailSender) {
|
||||||
|
return c.text("Verify mail sender not set", 400)
|
||||||
|
}
|
||||||
// check if code exists in KV
|
// check if code exists in KV
|
||||||
const tmpcode = await c.env.KV.get(`temp-mail:${email}`)
|
const tmpcode = await c.env.KV.get(`temp-mail:${email}`)
|
||||||
if (tmpcode) {
|
if (tmpcode) {
|
||||||
@@ -34,12 +39,15 @@ export default {
|
|||||||
// send code to email
|
// send code to email
|
||||||
try {
|
try {
|
||||||
await sendMail(c, settings.verifyMailSender, {
|
await sendMail(c, settings.verifyMailSender, {
|
||||||
to_mail: email,
|
from_name: "Temp Mail Verify",
|
||||||
|
to_name: '',
|
||||||
|
to_mail: email as string,
|
||||||
subject: "Temp Mail Verify code",
|
subject: "Temp Mail Verify code",
|
||||||
content: `Your verify code is ${code}`,
|
content: `Your verify code is ${code}`,
|
||||||
|
is_html: false,
|
||||||
})
|
})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return c.text(`Failed to send verify code: ${e.message}`, 500)
|
return c.text(`Failed to send verify code: ${(e as Error).message}`, 500)
|
||||||
}
|
}
|
||||||
// save to KV
|
// save to KV
|
||||||
await c.env.KV.put(`temp-mail:${email}`, code, { expirationTtl: 300 });
|
await c.env.KV.put(`temp-mail:${email}`, code, { expirationTtl: 300 });
|
||||||
@@ -48,7 +56,7 @@ export default {
|
|||||||
expirationTtl: 300
|
expirationTtl: 300
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
register: async (c) => {
|
register: async (c: Context<HonoCustomType>) => {
|
||||||
const value = await getJsonSetting(c, CONSTANTS.USER_SETTINGS_KEY);
|
const value = await getJsonSetting(c, CONSTANTS.USER_SETTINGS_KEY);
|
||||||
const settings = new UserSettings(value)
|
const settings = new UserSettings(value)
|
||||||
// check enable
|
// check enable
|
||||||
@@ -67,6 +75,7 @@ export default {
|
|||||||
// check mail domain allow list
|
// check mail domain allow list
|
||||||
const mailDomain = email.split("@")[1];
|
const mailDomain = email.split("@")[1];
|
||||||
if (settings.enableMailAllowList
|
if (settings.enableMailAllowList
|
||||||
|
&& settings.mailAllowList
|
||||||
&& !settings.mailAllowList.includes(mailDomain)
|
&& !settings.mailAllowList.includes(mailDomain)
|
||||||
) {
|
) {
|
||||||
return c.text(`Mail domain must in ${JSON.stringify(settings.mailAllowList, null, 2)}`, 400)
|
return c.text(`Mail domain must in ${JSON.stringify(settings.mailAllowList, null, 2)}`, 400)
|
||||||
@@ -80,8 +89,8 @@ export default {
|
|||||||
}
|
}
|
||||||
// geo data
|
// geo data
|
||||||
const reqIp = c.req.raw.headers.get("cf-connecting-ip")
|
const reqIp = c.req.raw.headers.get("cf-connecting-ip")
|
||||||
const geoData = new GeoData(reqIp, c.req.raw.cf);
|
const geoData = new GeoData(reqIp, c.req.raw.cf as any);
|
||||||
const userInfo = new UserInfo(geoData);
|
const userInfo = new UserInfo(geoData, email);
|
||||||
// if not enable mail verify, do not on conflict update
|
// if not enable mail verify, do not on conflict update
|
||||||
if (!settings.enableMailVerify) {
|
if (!settings.enableMailVerify) {
|
||||||
try {
|
try {
|
||||||
@@ -95,10 +104,11 @@ export default {
|
|||||||
return c.text("Failed to register", 500)
|
return c.text("Failed to register", 500)
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e.message && e.message.includes("UNIQUE")) {
|
const error = e as Error;
|
||||||
|
if (error.message && error.message.includes("UNIQUE")) {
|
||||||
return c.text("User already exists, please login", 400)
|
return c.text("User already exists, please login", 400)
|
||||||
}
|
}
|
||||||
return c.text(`Failed to register: ${e.message}`, 500)
|
return c.text(`Failed to register: ${error.message}`, 500)
|
||||||
}
|
}
|
||||||
return c.json({ success: true })
|
return c.json({ success: true })
|
||||||
}
|
}
|
||||||
@@ -116,7 +126,7 @@ export default {
|
|||||||
}
|
}
|
||||||
return c.json({ success: true })
|
return c.json({ success: true })
|
||||||
},
|
},
|
||||||
login: async (c) => {
|
login: async (c: Context<HonoCustomType>) => {
|
||||||
const { email, password } = await c.req.json();
|
const { email, password } = await c.req.json();
|
||||||
if (!email || !password) return c.text("Invalid email or password", 400);
|
if (!email || !password) return c.text("Invalid email or password", 400);
|
||||||
const { id: user_id, password: dbPassword } = await c.env.DB.prepare(
|
const { id: user_id, password: dbPassword } = await c.env.DB.prepare(
|
||||||
@@ -11,6 +11,10 @@ node_compat = true
|
|||||||
# [triggers]
|
# [triggers]
|
||||||
# crons = [ "0 0 * * *" ]
|
# crons = [ "0 0 * * *" ]
|
||||||
|
|
||||||
|
# send_email = [
|
||||||
|
# { name = "SEND_MAIL" },
|
||||||
|
# ]
|
||||||
|
|
||||||
[vars]
|
[vars]
|
||||||
PREFIX = "tmp"
|
PREFIX = "tmp"
|
||||||
# IF YOU WANT TO MAKE YOUR SITE PRIVATE, UNCOMMENT THE FOLLOWING LINES
|
# IF YOU WANT TO MAKE YOUR SITE PRIVATE, UNCOMMENT THE FOLLOWING LINES
|
||||||
|
|||||||
Reference in New Issue
Block a user