diff --git a/frontend/src/components/MailBox.vue b/frontend/src/components/MailBox.vue
index 9508109c..be7b0461 100644
--- a/frontend/src/components/MailBox.vue
+++ b/frontend/src/components/MailBox.vue
@@ -371,8 +371,15 @@ onBeforeUnmount(() => {
{{ t('reply') }}
+
{{ curMail.text }}
+
+
diff --git a/worker/src/admin_api/admin_user_api.js b/worker/src/admin_api/admin_user_api.js
index 9412704b..8a8ba644 100644
--- a/worker/src/admin_api/admin_user_api.js
+++ b/worker/src/admin_api/admin_user_api.js
@@ -1,6 +1,7 @@
import { CONSTANTS } from '../constants';
import { getJsonSetting, saveSetting, checkUserPassword, getDomains } from '../utils';
import { UserSettings, GeoData, UserInfo } from "../models";
+import { handleListQuery } from '../common'
export default {
getSetting: async (c) => {
@@ -29,49 +30,23 @@ export default {
},
getUsers: async (c) => {
const { limit, offset, query } = c.req.query();
- if (!limit || limit < 0 || limit > 100) {
- return c.text("Invalid limit", 400)
- }
- if (!offset || offset < 0) {
- return c.text("Invalid offset", 400)
- }
if (query) {
- const { results } = await c.env.DB.prepare(
+ return await handleListQuery(c,
`SELECT u.id, u.user_email, u.created_at, u.updated_at,`
+ ` (SELECT COUNT(*) FROM users_address WHERE user_id = u.id) AS address_count`
+ ` FROM users u`
- + ` where u.user_email like ?`
- + ` order by u.id desc limit ? offset ?`
- ).bind(`%${query}%`, limit, offset).all();
- let count = 0;
- if (offset == 0) {
- const { count: userCount } = await c.env.DB.prepare(
- `SELECT count(*) as count FROM users where user_email like ?`
- ).bind(`%${query}%`).first();
- count = userCount;
- }
- return c.json({
- results: results,
- count: count
- })
+ + ` where u.user_email like ?`,
+ `SELECT count(*) as count FROM users where user_email like ?`,
+ [`%${query}%`], limit, offset
+ );
}
- const { results } = await c.env.DB.prepare(
+ return await handleListQuery(c,
`SELECT u.id, u.user_email, u.created_at, u.updated_at,`
+ ` (SELECT COUNT(*) FROM users_address WHERE user_id = u.id) AS address_count`
- + ` FROM users u`
- + ` order by u.id desc limit ? offset ?`
- ).bind(limit, offset).all();
- let count = 0;
- if (offset == 0) {
- const { count: userCount } = await c.env.DB.prepare(
- `SELECT count(*) as count FROM users`
- ).first();
- count = userCount;
- }
- return c.json({
- results: results,
- count: count
- })
+ + ` FROM users u`,
+ `SELECT count(*) as count FROM users`,
+ [], limit, offset
+ );
},
createUser: async (c) => {
const { email, password } = await c.req.json();
diff --git a/worker/src/admin_api/index.js b/worker/src/admin_api/index.js
index 643dcb26..d1bb7401 100644
--- a/worker/src/admin_api/index.js
+++ b/worker/src/admin_api/index.js
@@ -10,51 +10,25 @@ const api = new Hono()
api.get('/admin/address', async (c) => {
const { limit, offset, query } = c.req.query();
- if (!limit || limit < 0 || limit > 100) {
- return c.text("Invalid limit", 400)
- }
- if (!offset || offset < 0) {
- return c.text("Invalid offset", 400)
- }
if (query) {
- const { results } = await c.env.DB.prepare(
+ return await handleListQuery(c,
`SELECT a.*,`
+ ` (SELECT COUNT(*) FROM raw_mails WHERE address = a.name) AS mail_count,`
+ ` (SELECT COUNT(*) FROM sendbox WHERE address = a.name) AS send_count`
+ ` FROM address a`
- + ` where name like ?`
- + ` order by id desc limit ? offset ?`
- ).bind(`%${query}%`, limit, offset).all();
- let count = 0;
- if (offset == 0) {
- const { count: addressCount } = await c.env.DB.prepare(
- `SELECT count(*) as count FROM address where name like ?`
- ).bind(`%${query}%`).first();
- count = addressCount;
- }
- return c.json({
- results: results,
- count: count
- })
+ + ` where name like ?`,
+ `SELECT count(*) as count FROM address where name like ?`,
+ [`%${query}%`], limit, offset
+ );
}
- const { results } = await c.env.DB.prepare(
+ return await handleListQuery(c,
`SELECT a.*,`
+ ` (SELECT COUNT(*) FROM raw_mails WHERE address = a.name) AS mail_count,`
+ ` (SELECT COUNT(*) FROM sendbox WHERE address = a.name) AS send_count`
- + ` FROM address a`
- + ` order by id desc limit ? offset ?`
- ).bind(limit, offset).all();
- let count = 0;
- if (offset == 0) {
- const { count: addressCount } = await c.env.DB.prepare(
- `SELECT count(*) as count FROM address`
- ).first();
- count = addressCount;
- }
- return c.json({
- results: results,
- count: count
- })
+ + ` FROM address a`,
+ `SELECT count(*) as count FROM address`,
+ [], limit, offset
+ );
})
api.post('/admin/new_address', async (c) => {
@@ -100,7 +74,7 @@ api.get('/admin/show_password/:id', async (c) => {
const jwt = await Jwt.sign({
address: name,
address_id: id
- }, c.env.JWT_SECRET)
+ }, c.env.JWT_SECRET, "HS256")
return c.json({
jwt: jwt
})
@@ -184,42 +158,18 @@ api.post('/admin/address_sender', async (c) => {
api.get('/admin/sendbox', async (c) => {
const { address, limit, offset } = c.req.query();
- if (!limit || limit < 0 || limit > 100) {
- return c.text("Invalid limit", 400)
+ if (address) {
+ return await handleListQuery(c,
+ `SELECT * FROM sendbox where address = ? `,
+ `SELECT count(*) as count FROM sendbox where address = ? `,
+ [address], limit, offset
+ );
}
- if (!offset || offset < 0) {
- return c.text("Invalid offset", 400)
- }
- if (!address) {
- const { results } = await c.env.DB.prepare(
- `SELECT * FROM sendbox order by id desc limit ? offset ?`
- ).bind(limit, offset).all();
- let count = 0;
- if (offset == 0) {
- const { count: mailCount } = await c.env.DB.prepare(
- `SELECT count(*) as count FROM sendbox`
- ).first();
- count = mailCount;
- }
- return c.json({
- results: results,
- count: count
- })
- }
- const { results } = await c.env.DB.prepare(
- `SELECT * FROM sendbox where address = ? order by id desc limit ? offset ?`
- ).bind(address, limit, offset).all();
- let count = 0;
- if (offset == 0) {
- const { count: mailCount } = await c.env.DB.prepare(
- `SELECT count(*) as count FROM sendbox where address = ?`
- ).bind(address).first();
- count = mailCount;
- }
- return c.json({
- results: results,
- count: count
- })
+ return await handleListQuery(c,
+ `SELECT * FROM sendbox `,
+ `SELECT count(*) as count FROM sendbox `,
+ [], limit, offset
+ );
})
api.get('/admin/statistics', async (c) => {
diff --git a/worker/src/commom_api.js b/worker/src/commom_api.js
new file mode 100644
index 00000000..99472b8d
--- /dev/null
+++ b/worker/src/commom_api.js
@@ -0,0 +1,28 @@
+import { Hono } from 'hono'
+
+import { getDomains, getPasswords, getBooleanValue } from './utils';
+
+const api = new Hono()
+
+api.get('/open_api/settings', async (c) => {
+ // check header x-custom-auth
+ let needAuth = false;
+ const passwords = getPasswords(c);
+ if (passwords && passwords.length > 0) {
+ const auth = c.req.raw.headers.get("x-custom-auth");
+ needAuth = !passwords.includes(auth);
+ }
+ return c.json({
+ "prefix": c.env.PREFIX,
+ "domains": getDomains(c),
+ "needAuth": needAuth,
+ "adminContact": c.env.ADMIN_CONTACT,
+ "enableUserCreateEmail": getBooleanValue(c.env.ENABLE_USER_CREATE_EMAIL),
+ "enableUserDeleteEmail": getBooleanValue(c.env.ENABLE_USER_DELETE_EMAIL),
+ "enableAutoReply": getBooleanValue(c.env.ENABLE_AUTO_REPLY),
+ "copyright": c.env.COPYRIGHT,
+ "cfTurnstileSiteKey": c.env.CF_TURNSTILE_SITE_KEY,
+ });
+})
+
+export { api }
diff --git a/worker/src/common.js b/worker/src/common.js
index 7bdad7a3..2c785a90 100644
--- a/worker/src/common.js
+++ b/worker/src/common.js
@@ -48,7 +48,7 @@ export const newAddress = async (c, name, domain, enablePrefix) => {
const jwt = await Jwt.sign({
address: name,
address_id: address_id
- }, c.env.JWT_SECRET)
+ }, c.env.JWT_SECRET, "HS256")
return c.json({
jwt: jwt
})
diff --git a/worker/src/mails_api/index.js b/worker/src/mails_api/index.js
index b5ebac5a..f8a41c06 100644
--- a/worker/src/mails_api/index.js
+++ b/worker/src/mails_api/index.js
@@ -1,5 +1,8 @@
import { Hono } from 'hono'
+import { getBooleanValue, getJsonSetting, checkCfTurnstile } from '../utils';
+import { newAddress, handleListQuery } from '../common'
+import { CONSTANTS } from '../constants'
import auto_reply from './auto_reply'
const api = new Hono()
@@ -7,4 +10,137 @@ const api = new Hono()
api.get('/api/auto_reply', auto_reply.getAutoReply)
api.post('/api/auto_reply', auto_reply.saveAutoReply)
+api.get('/api/mails', async (c) => {
+ const { address } = c.get("jwtPayload")
+ if (!address) {
+ return c.json({ "error": "No address" }, 400)
+ }
+ const { limit, offset } = c.req.query();
+ return await handleListQuery(c,
+ `SELECT * FROM raw_mails where address = ?`,
+ `SELECT count(*) as count FROM raw_mails where address = ?`,
+ [address], limit, offset
+ );
+})
+
+api.delete('/api/mails/:id', async (c) => {
+ if (!getBooleanValue(c.env.ENABLE_USER_DELETE_EMAIL)) {
+ return c.text("User delete email is disabled", 403)
+ }
+ const { address } = c.get("jwtPayload")
+ const { id } = c.req.param();
+ const { success } = await c.env.DB.prepare(
+ `DELETE FROM raw_mails WHERE address = ? and id = ? `
+ ).bind(address, id).run();
+ return c.json({
+ success: success
+ })
+})
+
+api.get('/api/settings', async (c) => {
+ const { address, address_id } = c.get("jwtPayload")
+ if (address_id && address_id > 0) {
+ try {
+ const db_address_id = await c.env.DB.prepare(
+ `SELECT id FROM address where id = ? `
+ ).bind(address_id).first("id");
+ if (!db_address_id) {
+ return c.text("Invalid address", 400)
+ }
+ } catch (error) {
+ return c.text("Invalid address", 400)
+ }
+ }
+ // check address id
+ try {
+ if (!address_id) {
+ const db_address_id = await c.env.DB.prepare(
+ `SELECT id FROM address where name = ? `
+ ).bind(address).first("id");
+ if (!db_address_id) {
+ return c.text("Invalid address", 400)
+ }
+ }
+ } catch (error) {
+ return c.text("Invalid address", 400)
+ }
+ // update address updated_at
+ try {
+ c.env.DB.prepare(
+ `UPDATE address SET updated_at = datetime('now') where name = ?`
+ ).bind(address).run();
+ } catch (e) {
+ console.warn("Failed to update address")
+ }
+ const { count: mailCountV1 } = await c.env.DB.prepare(
+ `SELECT count(*) as count FROM mails where address = ?`
+ ).bind(address).first();
+ const balance = await c.env.DB.prepare(
+ `SELECT balance FROM address_sender
+ where address = ? and enabled = 1`
+ ).bind(address).first("balance");
+ return c.json({
+ address: address,
+ has_v1_mails: mailCountV1 && mailCountV1 > 0,
+ send_balance: balance || 0,
+ });
+})
+
+api.post('/api/new_address', async (c) => {
+ if (!getBooleanValue(c.env.ENABLE_USER_CREATE_EMAIL)) {
+ return c.text("New address is disabled", 403)
+ }
+ let { name, domain, cf_token } = await c.req.json();
+ // check cf turnstile
+ try {
+ await checkCfTurnstile(c, cf_token);
+ } catch (error) {
+ return c.text("Failed to check cf turnstile", 500)
+ }
+ // if no name, generate random name
+ if (!name) {
+ name = Math.random().toString(36).substring(2, 15);
+ }
+ // check name block list
+ try {
+ const value = await getJsonSetting(c, CONSTANTS.ADDRESS_BLOCK_LIST_KEY);
+ const blockList = value || [];
+ if (blockList.some((item) => name.includes(item))) {
+ return c.text(`Name[${name}]is blocked`, 400)
+ }
+ } catch (error) {
+ console.error(error);
+ }
+ return newAddress(c, name, domain, true);
+})
+
+api.delete('/api/delete_address', async (c) => {
+ if (!getBooleanValue(c.env.ENABLE_USER_DELETE_EMAIL)) {
+ return c.text("User delete email is disabled", 403)
+ }
+ const { address, address_id } = c.get("jwtPayload")
+ let name = address;
+ const { success } = await c.env.DB.prepare(
+ `DELETE FROM address WHERE name = ? `
+ ).bind(name).run();
+ if (!success) {
+ return c.text("Failed to delete address", 500)
+ }
+ const { success: mailSuccess } = await c.env.DB.prepare(
+ `DELETE FROM raw_mails WHERE address = ? `
+ ).bind(address).run();
+ if (!mailSuccess) {
+ return c.text("Failed to delete mails", 500)
+ }
+ 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();
+ return c.json({
+ success: success && mailSuccess && sendAccess && addressSuccess
+ })
+})
+
export { api }
diff --git a/worker/src/mails_api/send_mail_api.js b/worker/src/mails_api/send_mail_api.js
index 9388996b..6ed3ea51 100644
--- a/worker/src/mails_api/send_mail_api.js
+++ b/worker/src/mails_api/send_mail_api.js
@@ -3,6 +3,8 @@ import { Jwt } from 'hono/utils/jwt'
import { CONSTANTS } from '../constants'
import { getJsonSetting, getDomains } from '../utils';
import { GeoData } from '../models'
+import { handleListQuery } from '../common'
+
const api = new Hono()
@@ -112,8 +114,7 @@ export const sendMail = async (c, address, reqJson) => {
// update balance
try {
const { success } = await c.env.DB.prepare(
- `UPDATE address_sender SET balance = balance - 1
- where address = ?`
+ `UPDATE address_sender SET balance = balance - 1 where address = ?`
).bind(address).run();
if (!success) {
console.warn(`Failed to update balance for ${address}`);
@@ -171,27 +172,11 @@ const getSendbox = async (c, address, limit, offset) => {
if (!address) {
return c.json({ "error": "No address" }, 400)
}
- if (!limit || limit < 0 || limit > 100) {
- return c.text("Invalid limit", 400)
- }
- if (!offset || offset < 0) {
- return c.text("Invalid offset", 400)
- }
- const { results } = await c.env.DB.prepare(
- `SELECT * FROM sendbox where address = ?
- order by id desc limit ? offset ?`
- ).bind(address, limit, offset).all();
- let count = 0;
- if (offset == 0) {
- const { count: mailCount } = await c.env.DB.prepare(
- `SELECT count(*) as count FROM sendbox where address = ?`
- ).bind(address).first();
- count = mailCount;
- }
- return c.json({
- results: results,
- count: count
- })
+ return await handleListQuery(c,
+ `SELECT * FROM sendbox where address = ? `,
+ `SELECT count(*) as count FROM sendbox where address = ? `,
+ [address], limit, offset
+ );
}
api.get('/api/sendbox', async (c) => {
diff --git a/worker/src/router.js b/worker/src/router.js
deleted file mode 100644
index df07bf90..00000000
--- a/worker/src/router.js
+++ /dev/null
@@ -1,181 +0,0 @@
-import { Hono } from 'hono'
-
-import {
- getDomains, getPasswords, getBooleanValue, getJsonSetting,
- checkCfTurnstile
-} from './utils';
-import { newAddress } from './common'
-import { CONSTANTS } from './constants'
-
-const api = new Hono()
-
-api.get('/api/mails', async (c) => {
- const { address } = c.get("jwtPayload")
- if (!address) {
- return c.json({ "error": "No address" }, 400)
- }
- const { limit, offset } = c.req.query();
- if (!limit || limit < 0 || limit > 100) {
- return c.text("Invalid limit", 400)
- }
- if (!offset || offset < 0) {
- return c.text("Invalid offset", 400)
- }
- const { results } = await c.env.DB.prepare(
- `SELECT * FROM raw_mails where address = ? order by id desc limit ? offset ?`
- ).bind(address, limit, offset).all();
- let count = 0;
- if (offset == 0) {
- const { count: mailCount } = await c.env.DB.prepare(
- `SELECT count(*) as count FROM raw_mails where address = ?`
- ).bind(address).first();
- count = mailCount;
- }
- return c.json({
- results: results,
- count: count
- })
-})
-
-api.delete('/api/mails/:id', async (c) => {
- if (!getBooleanValue(c.env.ENABLE_USER_DELETE_EMAIL)) {
- return c.text("User delete email is disabled", 403)
- }
- const { address } = c.get("jwtPayload")
- const { id } = c.req.param();
- const { success } = await c.env.DB.prepare(
- `DELETE FROM raw_mails WHERE address = ? and id = ?`
- ).bind(address, id).run();
- return c.json({
- success: success
- })
-})
-
-api.get('/api/settings', async (c) => {
- const { address, address_id } = c.get("jwtPayload")
- if (address_id && address_id > 0) {
- try {
- const db_address_id = await c.env.DB.prepare(
- `SELECT id FROM address where id = ?`
- ).bind(address_id).first("id");
- if (!db_address_id) {
- return c.text("Invalid address", 400)
- }
- } catch (error) {
- return c.text("Invalid address", 400)
- }
- }
- // check address id
- try {
- if (!address_id) {
- const db_address_id = await c.env.DB.prepare(
- `SELECT id FROM address where name = ?`
- ).bind(address).first("id");
- if (!db_address_id) {
- return c.text("Invalid address", 400)
- }
- }
- } catch (error) {
- return c.text("Invalid address", 400)
- }
- // update address updated_at
- try {
- c.env.DB.prepare(
- `UPDATE address SET updated_at = datetime('now') where name = ?`
- ).bind(address).run();
- } catch (e) {
- console.warn("Failed to update address")
- }
- const { count: mailCountV1 } = await c.env.DB.prepare(
- `SELECT count(*) as count FROM mails where address = ?`
- ).bind(address).first();
- const balance = await c.env.DB.prepare(
- `SELECT balance FROM address_sender
- where address = ? and enabled = 1`
- ).bind(address).first("balance");
- return c.json({
- address: address,
- has_v1_mails: mailCountV1 && mailCountV1 > 0,
- send_balance: balance || 0,
- });
-})
-
-api.get('/open_api/settings', async (c) => {
- // check header x-custom-auth
- let needAuth = false;
- const passwords = getPasswords(c);
- if (passwords && passwords.length > 0) {
- const auth = c.req.raw.headers.get("x-custom-auth");
- needAuth = !passwords.includes(auth);
- }
- return c.json({
- "prefix": c.env.PREFIX,
- "domains": getDomains(c),
- "needAuth": needAuth,
- "adminContact": c.env.ADMIN_CONTACT,
- "enableUserCreateEmail": getBooleanValue(c.env.ENABLE_USER_CREATE_EMAIL),
- "enableUserDeleteEmail": getBooleanValue(c.env.ENABLE_USER_DELETE_EMAIL),
- "enableAutoReply": getBooleanValue(c.env.ENABLE_AUTO_REPLY),
- "copyright": c.env.COPYRIGHT,
- "cfTurnstileSiteKey": c.env.CF_TURNSTILE_SITE_KEY,
- });
-})
-
-api.post('/api/new_address', async (c) => {
- if (!getBooleanValue(c.env.ENABLE_USER_CREATE_EMAIL)) {
- return c.text("New address is disabled", 403)
- }
- let { name, domain, cf_token } = await c.req.json();
- // check cf turnstile
- try {
- await checkCfTurnstile(c, cf_token);
- } catch (error) {
- return c.text("Failed to check cf turnstile", 500)
- }
- // if no name, generate random name
- if (!name) {
- name = Math.random().toString(36).substring(2, 15);
- }
- // check name block list
- try {
- const value = await getJsonSetting(c, CONSTANTS.ADDRESS_BLOCK_LIST_KEY);
- const blockList = value || [];
- if (blockList.some((item) => name.includes(item))) {
- return c.text(`Name [${name}] is blocked`, 400)
- }
- } catch (error) {
- console.error(error);
- }
- return newAddress(c, name, domain, true);
-})
-
-api.delete('/api/delete_address', async (c) => {
- if (!getBooleanValue(c.env.ENABLE_USER_DELETE_EMAIL)) {
- return c.text("User delete email is disabled", 403)
- }
- const { address, address_id } = c.get("jwtPayload")
- let name = address;
- const { success } = await c.env.DB.prepare(
- `DELETE FROM address WHERE name = ? `
- ).bind(name).run();
- if (!success) {
- return c.text("Failed to delete address", 500)
- }
- const { success: mailSuccess } = await c.env.DB.prepare(
- `DELETE FROM raw_mails WHERE address = ? `
- ).bind(address).run();
- if (!mailSuccess) {
- return c.text("Failed to delete mails", 500)
- }
- 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();
- return c.json({
- success: success && mailSuccess && sendAccess && addressSuccess
- })
-})
-
-export { api }
diff --git a/worker/src/user_api/bind_address.js b/worker/src/user_api/bind_address.js
index 985e4531..46585ccb 100644
--- a/worker/src/user_api/bind_address.js
+++ b/worker/src/user_api/bind_address.js
@@ -131,7 +131,7 @@ export default {
const jwt = await Jwt.sign({
address: name,
address_id: address_id
- }, c.env.JWT_SECRET)
+ }, c.env.JWT_SECRET, "HS256")
return c.json({
jwt: jwt
})
diff --git a/worker/src/user_api/user.js b/worker/src/user_api/user.js
index 066a9341..500d8f1c 100644
--- a/worker/src/user_api/user.js
+++ b/worker/src/user_api/user.js
@@ -136,7 +136,7 @@ export default {
// 30 days expire in seconds
exp: Math.floor(Date.now() / 1000) + 30 * 24 * 60 * 60,
iat: Math.floor(Date.now() / 1000),
- }, c.env.JWT_SECRET)
+ }, c.env.JWT_SECRET, "HS256")
return c.json({
jwt: jwt
})
diff --git a/worker/src/worker.js b/worker/src/worker.js
index bad78dc9..c04d9c0b 100644
--- a/worker/src/worker.js
+++ b/worker/src/worker.js
@@ -3,8 +3,8 @@ import { cors } from 'hono/cors';
import { jwt } from 'hono/jwt'
import { Jwt } from 'hono/utils/jwt'
-import { api } from './router';
-import { api as MailsApi } from './mails_api'
+import { api as commonApi } from './commom_api';
+import { api as mailsApi } from './mails_api'
import { api as userApi } from './user_api';
import { api as adminApi } from './admin_api';
import { api as apiV1 } from './deprecated';
@@ -101,8 +101,8 @@ app.use('/admin/*', async (c, next) => {
});
-app.route('/', api)
-app.route('/', MailsApi)
+app.route('/', commonApi)
+app.route('/', mailsApi)
app.route('/', userApi)
app.route('/', adminApi)
app.route('/', apiV1)