mirror of
https://github.com/dreamhunter2333/cloudflare_temp_email.git
synced 2026-06-07 00:19:56 +08:00
feat: add address source tracking (source_meta field) (#794)
- Add source_meta field to address table for tracking creation source
- Web: records client IP address (with fallback to 'web:unknown')
- Telegram: records 'tg:{userId}'
- Admin: records 'admin'
- Add database migration with field existence check
- Add frontend display in admin Account page
- Backward compatible: fallback if field doesn't exist
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -21,6 +21,7 @@ CREATE TABLE IF NOT EXISTS address (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT UNIQUE,
|
||||
password TEXT,
|
||||
source_meta TEXT,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
@@ -31,6 +32,8 @@ CREATE INDEX IF NOT EXISTS idx_address_created_at ON address(created_at);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_address_updated_at ON address(updated_at);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_address_source_meta ON address(source_meta);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS auto_reply_mails (
|
||||
id INTEGER PRIMARY KEY,
|
||||
source_prefix TEXT,
|
||||
@@ -138,14 +141,42 @@ export default {
|
||||
},
|
||||
migrate: async (c: Context<HonoCustomType>) => {
|
||||
const version = await utils.getSetting(c, CONSTANTS.DB_VERSION_KEY);
|
||||
if (version == "v0.0.2") {
|
||||
// example migration from v0.0.2 to v0.0.3
|
||||
const query = `ALTER TABLE address ADD password TEXT;`
|
||||
await c.env.DB.exec(query);
|
||||
if (version && version <= "v0.0.2") {
|
||||
// migration to v0.0.3: add password column
|
||||
const tableInfo = await c.env.DB.prepare(
|
||||
`PRAGMA table_info(address)`
|
||||
).all();
|
||||
const hasPassword = tableInfo.results?.some(
|
||||
(col: any) => col.name === 'password'
|
||||
);
|
||||
if (!hasPassword) {
|
||||
await c.env.DB.exec(`ALTER TABLE address ADD COLUMN password TEXT;`);
|
||||
}
|
||||
}
|
||||
if (version == "v0.0.3") {
|
||||
// migration from v0.0.3 to v0.0.4
|
||||
await c.env.DB.exec(`ALTER TABLE raw_mails ADD COLUMN metadata TEXT;`);
|
||||
if (version && version <= "v0.0.3") {
|
||||
// migration to v0.0.4: add metadata column
|
||||
const tableInfo = await c.env.DB.prepare(
|
||||
`PRAGMA table_info(raw_mails)`
|
||||
).all();
|
||||
const hasMetadata = tableInfo.results?.some(
|
||||
(col: any) => col.name === 'metadata'
|
||||
);
|
||||
if (!hasMetadata) {
|
||||
await c.env.DB.exec(`ALTER TABLE raw_mails ADD COLUMN metadata TEXT;`);
|
||||
}
|
||||
}
|
||||
if (version && version <= "v0.0.4") {
|
||||
// migration to v0.0.5: add source_meta column
|
||||
const tableInfo = await c.env.DB.prepare(
|
||||
`PRAGMA table_info(address)`
|
||||
).all();
|
||||
const hasSourceMeta = tableInfo.results?.some(
|
||||
(col: any) => col.name === 'source_meta'
|
||||
);
|
||||
if (!hasSourceMeta) {
|
||||
await c.env.DB.exec(`ALTER TABLE address ADD COLUMN source_meta TEXT;`);
|
||||
await c.env.DB.exec(`CREATE INDEX IF NOT EXISTS idx_address_source_meta ON address(source_meta);`);
|
||||
}
|
||||
}
|
||||
if (version != CONSTANTS.DB_VERSION) {
|
||||
// remove all \r and \n characters from the query string
|
||||
|
||||
@@ -57,6 +57,7 @@ api.post('/admin/new_address', async (c) => {
|
||||
addressPrefix: null,
|
||||
checkAllowDomains: false,
|
||||
enableCheckNameRegex: false,
|
||||
sourceMeta: 'admin'
|
||||
});
|
||||
|
||||
return c.json(res);
|
||||
|
||||
@@ -123,6 +123,7 @@ export const newAddress = async (
|
||||
addressPrefix = null,
|
||||
checkAllowDomains = true,
|
||||
enableCheckNameRegex = true,
|
||||
sourceMeta = null,
|
||||
}: {
|
||||
name: string, domain: string | undefined | null,
|
||||
enablePrefix: boolean,
|
||||
@@ -130,6 +131,7 @@ export const newAddress = async (
|
||||
addressPrefix?: string | undefined | null,
|
||||
checkAllowDomains?: boolean,
|
||||
enableCheckNameRegex?: boolean,
|
||||
sourceMeta?: string | undefined | null,
|
||||
}
|
||||
): Promise<{ address: string, jwt: string, password?: string | null }> => {
|
||||
// trim whitespace and remove special characters
|
||||
@@ -180,19 +182,30 @@ export const newAddress = async (
|
||||
// create address
|
||||
name = name + "@" + domain;
|
||||
try {
|
||||
const { success } = await c.env.DB.prepare(
|
||||
`INSERT INTO address(name) VALUES(?)`
|
||||
).bind(name).run();
|
||||
if (!success) {
|
||||
// Try insert with source_meta field first
|
||||
const result = await c.env.DB.prepare(
|
||||
`INSERT INTO address(name, source_meta) VALUES(?, ?)`
|
||||
).bind(name, sourceMeta).run();
|
||||
if (!result.success) {
|
||||
throw new Error("Failed to create address")
|
||||
}
|
||||
await updateAddressUpdatedAt(c, name);
|
||||
} catch (e) {
|
||||
const message = (e as Error).message;
|
||||
if (message && message.includes("UNIQUE")) {
|
||||
// Fallback: source_meta field may not exist, try without it
|
||||
if (message && message.includes("source_meta")) {
|
||||
const result = await c.env.DB.prepare(
|
||||
`INSERT INTO address(name) VALUES(?)`
|
||||
).bind(name).run();
|
||||
if (!result.success) {
|
||||
throw new Error("Failed to create address")
|
||||
}
|
||||
await updateAddressUpdatedAt(c, name);
|
||||
} else if (message && message.includes("UNIQUE")) {
|
||||
throw new Error("Address already exists")
|
||||
} else {
|
||||
throw new Error("Failed to create address")
|
||||
}
|
||||
throw new Error("Failed to create address")
|
||||
}
|
||||
const address_id = await c.env.DB.prepare(
|
||||
`SELECT id FROM address where name = ?`
|
||||
|
||||
@@ -3,7 +3,7 @@ export const CONSTANTS = {
|
||||
|
||||
// DB Version
|
||||
DB_VERSION_KEY: 'db_version',
|
||||
DB_VERSION: "v0.0.4",
|
||||
DB_VERSION: "v0.0.5",
|
||||
|
||||
// DB settings
|
||||
ADDRESS_BLOCK_LIST_KEY: 'address_block_list',
|
||||
|
||||
@@ -144,11 +144,17 @@ api.post('/api/new_address', async (c) => {
|
||||
}
|
||||
try {
|
||||
const addressPrefix = await getAddressPrefix(c);
|
||||
// Get client IP for source tracking
|
||||
const sourceMeta = c.req.header('CF-Connecting-IP')
|
||||
|| c.req.header('X-Forwarded-For')?.split(',')[0]?.trim()
|
||||
|| c.req.header('X-Real-IP')
|
||||
|| 'web:unknown';
|
||||
const res = await newAddress(c, {
|
||||
name, domain,
|
||||
enablePrefix: true,
|
||||
checkLengthByConfig: true,
|
||||
addressPrefix
|
||||
addressPrefix,
|
||||
sourceMeta
|
||||
});
|
||||
return c.json(res);
|
||||
} catch (e) {
|
||||
|
||||
@@ -38,7 +38,8 @@ export const tgUserNewAddress = async (
|
||||
const res = await newAddress(c, {
|
||||
name: finalName,
|
||||
domain,
|
||||
enablePrefix: true
|
||||
enablePrefix: true,
|
||||
sourceMeta: `tg:${userId}`
|
||||
});
|
||||
// for mail push to telegram
|
||||
await c.env.KV.put(`${CONSTANTS.TG_KV_PREFIX}:${userId}`, JSON.stringify([...jwtList, res.jwt]));
|
||||
|
||||
Reference in New Issue
Block a user