import type { NullableId, Params, ServiceInterface, } from "@feathersjs/feathers"; import type { Application } from "../../declarations"; import wildDuckClient from "../../clients/wildduck.client"; import { faker } from "@faker-js/faker"; import { BadRequest } from "@feathersjs/errors"; import config from "config"; interface Alias { success: boolean; id: string; address: string; main: boolean; user: string; tags: string[]; created: string; } interface GetAddressInfoResponse { success: boolean; results: Alias[]; } interface CreateAddressResponse { success: boolean; id: string; } type AliasesData = any; type AliasesPatch = any; type AliasesQuery = any; export type { Alias as Aliases, AliasesData, AliasesPatch, AliasesQuery }; export interface AliasesServiceOptions { app: Application; } export interface AliasesParams extends Params { session?: any; } export class AliasesService implements ServiceInterface { constructor(public options: AliasesServiceOptions) {} async find(params: ServiceParams): Promise { const userId = await this.getUserIdByEmailAddress(params); return this.getUserAddresses(userId); } async create(data: AliasesData, params: ServiceParams): Promise; async create( data: AliasesData, params: ServiceParams, ): Promise { const userId = await this.getUserIdByEmailAddress(params); const randomString = faker.git.commitSha({ length: 4 }); // Replace all non-alphanumeric characters with nothing and spaces with dashes const alias = `${faker.color.human()}-${faker.animal.snake()}-${randomString}` .replace(/\s+/g, "-") .replace(/[^a-zA-Z0-9-]/g, "") .toLowerCase(); const emailDomain = config.get("wildDuck.domain"); const createResult = await wildDuckClient.post( `/users/${userId}/addresses`, { address: `${alias}@${emailDomain}`, }, ); if (!createResult.data.success) { throw new BadRequest("Failed to create alias"); } return this.getUserAddresses(userId); } private async getUserIdByEmailAddress( params: ServiceParams, ): Promise { const emails = params.session?.user?.emails; const addressInfoResponse = await Promise.any( emails.map((email: string) => wildDuckClient.get(`addresses/resolve/${email}`), ), ); return addressInfoResponse.data.user; } private async getUserAddresses(userId: string): Promise { const { data: userAddressesResponse } = await wildDuckClient.get( `/users/${userId}/addresses`, ); return userAddressesResponse.results; } async remove(id: NullableId, params: ServiceParams): Promise { const { data: addressInfoResponse } = await wildDuckClient.get( `addresses/resolve/${id}`, ); const allowedDomain: string = config.get("wildDuck.domain"); // If address does not match the allowed domain, throw an error if ( !allowedDomain || !addressInfoResponse.address.endsWith(allowedDomain) ) { throw new BadRequest("Unable to delete address"); } await wildDuckClient.delete(`/addresses/${id}`); const userId = await this.getUserIdByEmailAddress(params); return this.getUserAddresses(userId); } } export const getOptions = (app: Application) => { return { app }; };