firefox-email-mailcow-tempe.../Data/src/utils.ts
2025-02-20 00:29:13 +01:00

251 lines
6.4 KiB
TypeScript

import { randAlphaNumeric, randEmail } from "@ngneat/falso"
// import { createHash } from "crypto";
// change the number depending on the version of the extension, this is to prevent conflicts with breaking changes
const PREFIX = "aliasaddon_2"
export interface Alias {
// id that mailcow assigns alias
id: number
// domain of alias
domain: string
targetAddress: string
address: string
active: boolean
created: number
// modified: Date
// hash of site
siteHash: string
}
enum GenerationMethod {
RandomCharacters = 0,
RandomName = 1
// WebsiteURL = 2
}
export interface Settings {
host?: string
apiKey?: string
forwardAddress?: string
aliasDomain: string | null
generationMethod: GenerationMethod
}
interface FetchAliasData {
id: number
domain: string
// private comment includes the site hash and will start with aliasextension_ if it was generated by this extension
private_comment: string | null
// target address
goto: string
// alias address
address: string
active: number
active_int: number
created: string
// modified will be null if the alias has never been modified
modified: string | null
}
export async function fetchDomains(
settings: Required<Settings>
): Promise<string[]> {
const controller = new AbortController()
const id = setTimeout(() => controller.abort(), 5000)
const data: {
domain_name: string
aliases_left: number
active: number
active_int: number
}[] = await (
await fetch(`${settings.host}/api/v1/get/domain/all`, {
headers: {
"X-API-Key": settings.apiKey
},
signal: controller.signal
})
).json()
clearTimeout(id)
return data
.filter((domain) => domain.active === 1 && domain.aliases_left > 0)
.map((domain) => domain.domain_name)
}
export async function fetchAliases(
settings: Required<Settings>
): Promise<Alias[]> {
const data: FetchAliasData[] = await (
await fetch(`${settings.host}/api/v1/get/alias/all`, {
headers: {
"X-API-Key": settings.apiKey
}
})
).json()
return data
.filter(
// make sure alias is active and was generated by this extension
(alias) =>
alias.private_comment && alias.private_comment.startsWith(PREFIX)
)
.map((alias) => {
// format: prefix, version, hash, timestamp
const info = alias.private_comment!.split("_")
return {
id: alias.id,
domain: alias.domain,
targetAddress: alias.goto,
address: alias.address,
active: alias.active === 1,
created: parseInt(info[3]),
// modified: alias.modified ? new Date(`${alias.modified}.000Z`) : null,
siteHash: info[2]
}
})
}
export async function generateAlias(
settings: Required<Settings>,
hostname?: string
): Promise<Alias> {
const address = generateEmail(settings, hostname)
// const hash = await generateHash(hostname ?? "no")
const hash = hostname ?? "nohostname"
// although mailcow has its own date, the format they use sucks
const createdAt = Date.now()
const controller = new AbortController()
const timeoutId = setTimeout(() => controller.abort(), 5000)
// first in msg array should be "alias_added", second is the address, third is the id as a string
const data = await (
await fetch(`${settings.host}/api/v1/add/alias`, {
headers: {
"Content-Type": "application/json",
"X-API-Key": settings.apiKey
},
method: "POST",
body: JSON.stringify({
address,
goto: settings.forwardAddress,
active: "1",
private_comment: `${PREFIX}_${hash}_${createdAt}`
}),
signal: controller.signal
})
).json()
clearTimeout(timeoutId)
if (data[0].type !== "success") {
throw new Error("Failed to generate alias")
}
return {
id: parseInt(data[0].msg[2]),
domain: settings.aliasDomain!,
targetAddress: settings.apiKey,
address,
active: true,
// mailcow returns dates in weird format so not using them (https://github.com/mailcow/mailcow-dockerized/issues/4876)
created: createdAt,
// modified: null,
siteHash: hash
}
}
export async function updateAlias(
id: number,
settings: Settings,
active: 0 | 1
): Promise<boolean> {
const controller = new AbortController()
const timeoutId = setTimeout(() => controller.abort(), 5000)
const data = await (
await fetch(`${settings.host}/api/v1/edit/alias/${id}`, {
headers: {
"Content-Type": "application/json",
"X-API-Key": settings.apiKey!
},
method: "POST",
body: JSON.stringify({
attr: { active: active.toString() },
items: [id]
}),
signal: controller.signal
})
).json()
clearTimeout(timeoutId)
return data[0].type === "success"
}
export async function deleteAlias(
id: number,
settings: Settings
): Promise<boolean> {
const controller = new AbortController()
const timeoutId = setTimeout(() => controller.abort(), 5000)
const data = await (
await fetch(`${settings.host}/api/v1/delete/alias`, {
headers: {
"Content-Type": "application/json",
"X-API-Key": settings.apiKey!
},
method: "POST",
body: JSON.stringify([id]),
signal: controller.signal
})
).json()
clearTimeout(timeoutId)
return data[0].type === "success"
}
// export function generateHash(data: string) {
// return createHash("sha256").update(data).digest("hex");
// }
export async function generateHash(data: string) {
return Array.from(
new Uint8Array(
await crypto.subtle.digest("SHA-256", new TextEncoder().encode(data))
)
)
.map((bytes) => bytes.toString(16).padStart(2, "0"))
.join("")
}
export function generateEmail(
settings: Required<Settings>,
hostname?: string
): string {
switch (settings.generationMethod) {
case GenerationMethod.RandomCharacters:
return `${randAlphaNumeric({ length: 16 }).join("")}@${
settings.aliasDomain
}`
case GenerationMethod.RandomName:
console.log(settings)
if (!settings.aliasDomain) {
return randEmail({ provider: "example", suffix: "com" })
}
const domain = settings.aliasDomain.split(".")
const suffix = domain.pop()
const provider = domain.join(".")
return randEmail({ provider, suffix })
// case GenerationMethod.WebsiteURL:
// return `${hostname.replace(".", "_")}_${faker.random.numeric(3)}@${
// settings.aliasDomain
// }`
}
}