diff --git a/deployment.yaml b/deployment.yaml index d54729f..7ef2aea 100644 --- a/deployment.yaml +++ b/deployment.yaml @@ -103,13 +103,3 @@ spec: envFrom: - secretRef: name: oidc-client-walias-owner-secrets - ---- -apiVersion: codemowers.cloud/v1beta1 -kind: RedisClaim -metadata: - name: walias-cache - namespace: msergo-bwybg -spec: - capacity: 100Mi - class: cache diff --git a/public/index.js b/public/index.js index 86770f0..fe1c00a 100644 --- a/public/index.js +++ b/public/index.js @@ -31,29 +31,17 @@ function renderAliases(aliases) { const tdCreated = document.createElement('td') tdCreated.innerText = alias.created const tdActions = document.createElement('td') - const aDelete = document.createElement('a') - aDelete.addEventListener('click', async () => { - const res = await fetch(`/aliases/${alias.id}`, { - method: 'DELETE' - }) + const deleteButton = createDeleteButton(alias) - if (res.ok) { - const data = await res.json() - renderAliases(data) - return - } - }) - - aDelete.classList.add('btn', 'btn-danger') - aDelete.innerText = 'Delete' - tdActions.appendChild(aDelete) + tdActions.appendChild(deleteButton) tr.appendChild(tdAddress) tr.appendChild(tdCreated) tr.appendChild(tdActions) tbody.appendChild(tr) }) + table.appendChild(tbody) dataContainer.innerHTML = '' @@ -62,6 +50,32 @@ function renderAliases(aliases) { renderCreateButton(); } +function createDeleteButton(alias) { + const deleteButton = document.createElement('a') + deleteButton.classList.add('btn', 'btn-danger') + + if (!alias.id) { + deleteButton.classList.add('disabled') + } else { + + deleteButton.addEventListener('click', async () => { + const res = await fetch(`/aliases/${alias.id}`, { + method: 'DELETE' + }) + + if (res.ok) { + const data = await res.json() + renderAliases(data) + return + } + }) + } + + deleteButton.innerText = 'Delete' + + return deleteButton +} + function renderCreateButton() { const dataContainer = document.getElementById('container') const button = document.createElement('a') diff --git a/src/app.ts b/src/app.ts index 0030c77..65bb717 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1,3 +1,4 @@ +import { randomUUID } from "crypto"; import { feathers } from "@feathersjs/feathers"; import express, { rest, @@ -16,12 +17,11 @@ import RedisStore from "connect-redis"; import { createClient } from "redis"; import config from "config"; import type { Application } from "./declarations"; - import { logger } from "./logger"; import { logError } from "./hooks/log-error"; import { services } from "./services/index"; import { channels } from "./channels"; -import { randomUUID } from "crypto"; +import { Env, getEnv } from "./helpers/get-env"; const app: Application = express(feathers()); @@ -35,14 +35,20 @@ app.use( ); app.use(cookieParser()); + +const sessionStore = + getEnv() === Env.prod + ? new RedisStore({ + prefix: "walias:", + client: createClient({ + url: config.get("redis.url"), + }), + }) + : undefined; + app.use( session({ - store: new RedisStore({ - prefix: "walias:", - client: createClient({ - url: config.get("redis.url"), - }), - }), + store: sessionStore, secret: randomUUID(), resave: false, saveUninitialized: false, diff --git a/src/helpers/get-env.ts b/src/helpers/get-env.ts new file mode 100644 index 0000000..4198918 --- /dev/null +++ b/src/helpers/get-env.ts @@ -0,0 +1,16 @@ +export enum Env { + dev = "dev", + prod = "prod", + test = "test", +} + +export const getEnv = (): Env => { + const env = process.env.NODE_ENV; + if (env === "prod") { + return Env.prod; + } else if (env === "test") { + return Env.test; + } else { + return Env.dev; + } +}; diff --git a/src/services/aliases/aliases.class.ts b/src/services/aliases/aliases.class.ts index 7260b27..a089feb 100644 --- a/src/services/aliases/aliases.class.ts +++ b/src/services/aliases/aliases.class.ts @@ -6,11 +6,11 @@ import type { import type { Application } from "../../declarations"; import wildDuckClient from "../../clients/wildduck.client"; -import { faker } from "@faker-js/faker"; +import { faker, th } from "@faker-js/faker"; import { BadRequest } from "@feathersjs/errors"; import config from "config"; -interface Alias { +interface WildDuckAddress { success: boolean; id: string; address: string; @@ -20,12 +20,19 @@ interface Alias { created: string; } -interface GetAddressInfoResponse { +interface GetWildDuckAddressInfoResponse { success: boolean; - results: Alias[]; + results: WildDuckAddress[]; } -interface CreateAddressResponse { +interface AliasApiResponse { + id: string | null; + address: string; + tags: string[]; + created: string; +} + +interface CreateWildDuckAddressResponse { success: boolean; id: string; } @@ -34,7 +41,12 @@ type AliasesData = any; type AliasesPatch = any; type AliasesQuery = any; -export type { Alias as Aliases, AliasesData, AliasesPatch, AliasesQuery }; +export type { + WildDuckAddress as Aliases, + AliasesData, + AliasesPatch, + AliasesQuery, +}; export interface AliasesServiceOptions { app: Application; @@ -45,21 +57,30 @@ export interface AliasesParams extends Params { } export class AliasesService - implements ServiceInterface + implements + ServiceInterface< + AliasApiResponse, + AliasesData, + ServiceParams, + AliasesPatch + > { constructor(public options: AliasesServiceOptions) {} - async find(params: ServiceParams): Promise { + 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 { + ): Promise; + async create( + data: AliasesData, + params: ServiceParams, + ): Promise { const userId = await this.getUserIdByEmailAddress(params); const randomString = faker.git.commitSha({ length: 4 }); @@ -73,12 +94,13 @@ export class AliasesService const emailDomain = config.get("wildDuck.domain"); - const createResult = await wildDuckClient.post( - `/users/${userId}/addresses`, - { - address: `${alias}@${emailDomain}`, - }, - ); + const createResult = + await wildDuckClient.post( + `/users/${userId}/addresses`, + { + address: `${alias}@${emailDomain}`, + }, + ); if (!createResult.data.success) { throw new BadRequest("Failed to create alias"); @@ -92,32 +114,40 @@ export class AliasesService ): Promise { 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(`addresses/resolve/${email}`), + wildDuckClient.get(`addresses/resolve/${email}`), ), ); return addressInfoResponse.data.user; } - private async getUserAddresses(userId: string): Promise { + private async getUserAddresses(userId: string): Promise { const { data: userAddressesResponse } = - await wildDuckClient.get( + await wildDuckClient.get( `/users/${userId}/addresses`, ); - return userAddressesResponse.results; + return userAddressesResponse.results.map(this.sanitizeAliasResponse); } - async remove(id: NullableId, params: ServiceParams): Promise { - const { data: addressInfoResponse } = await wildDuckClient.get( - `addresses/resolve/${id}`, - ); + 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 @@ -129,10 +159,26 @@ export class AliasesService } const userId = await this.getUserIdByEmailAddress(params); - await wildDuckClient.delete(`users/${userId}/addresses/${id}`); + await wildDuckClient.delete( + `users/${userId}/addresses/${id}`, + ); return this.getUserAddresses(userId); } + + sanitizeAliasResponse(alias: WildDuckAddress): AliasApiResponse { + // Hide the id if the alias is not removable + const isRemovable = + alias.main || + !alias.address.endsWith(config.get("wildDuck.preferredDomain")); + + return { + id: isRemovable ? null : alias.id, + address: alias.address, + tags: alias.tags, + created: alias.created, + }; + } } export const getOptions = (app: Application) => {