wildflock/src/services/aliases/aliases.class.ts

144 lines
4.8 KiB
TypeScript

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 WildDuckAddress {
success: boolean;
id: string;
address: string;
main: boolean;
user: string;
tags: string[];
created: string;
}
interface GetWildDuckAddressInfoResponse {
success: boolean;
results: WildDuckAddress[];
}
interface AliasApiResponse {
id: string | null;
address: string;
tags: string[];
created: string;
}
interface CreateWildDuckAddressResponse {
success: boolean;
id: string;
}
type AliasesData = { tags?: string[] }
type AliasesPatch = any;
type AliasesQuery = any;
export type { WildDuckAddress as Aliases, AliasesData, AliasesPatch, AliasesQuery };
export interface AliasesServiceOptions {
app: Application;
}
export interface AliasesParams extends Params<AliasesQuery> {
session?: any;
}
export class AliasesService<ServiceParams extends AliasesParams = AliasesParams>
implements ServiceInterface<AliasApiResponse, AliasesData, ServiceParams, AliasesPatch>
{
constructor(public options: AliasesServiceOptions) { }
async find(params: ServiceParams): Promise<AliasApiResponse[]> {
const userId = await this.getUserIdByEmailAddress(params);
return this.getUserAddresses(userId);
}
async create(data: AliasesData, params: ServiceParams): Promise<AliasApiResponse>;
async create(data: AliasesData, params: ServiceParams): Promise<AliasApiResponse | AliasApiResponse[]> {
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<CreateWildDuckAddressResponse>(`/users/${userId}/addresses`, {
address: `${alias}@${emailDomain}`,
tags: data.tags,
});
if (!createResult.data.success) {
throw new BadRequest('Failed to create alias');
}
return this.getUserAddresses(userId);
}
private async getUserIdByEmailAddress(params: ServiceParams): Promise<string> {
const emails = params.session?.user?.emails;
const preferredDomain = config.get('wildDuck.preferredDomain');
if (!emails.length || !preferredDomain) {
throw new BadRequest('Unable to find user');
}
const addressInfoResponse = await Promise.any(
emails
.filter((email: string) => email.endsWith(config.get('wildDuck.preferredDomain')))
.map((email: string) => wildDuckClient.get<WildDuckAddress>(`addresses/resolve/${email}`))
);
return addressInfoResponse.data.user;
}
private async getUserAddresses(userId: string): Promise<AliasApiResponse[]> {
const { data: userAddressesResponse } = await wildDuckClient.get<GetWildDuckAddressInfoResponse>(
`/users/${userId}/addresses`
);
return userAddressesResponse.results.map(this.sanitizeAliasResponse);
}
async remove(id: NullableId, params: ServiceParams): Promise<AliasApiResponse[]> {
const { data: addressInfoResponse } = await wildDuckClient.get<WildDuckAddress>(`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');
}
const userId = await this.getUserIdByEmailAddress(params);
await wildDuckClient.delete<WildDuckAddress>(`users/${userId}/addresses/${id}`);
return this.getUserAddresses(userId);
}
sanitizeAliasResponse(alias: WildDuckAddress): AliasApiResponse {
// Prevent the user from deleting their main address or any address that does not end with the preferred domain
const isRemovable = !alias.main && alias.address.endsWith(config.get('wildDuck.domain'));
// Hide the id if the alias is not removable
return {
id: isRemovable ? alias.id : null,
address: alias.address,
tags: alias.tags,
created: alias.created,
};
}
}
export const getOptions = (app: Application) => {
return { app };
};