store express sessions in redis, delete only aliases from preferred domain #3
@ -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
|
||||
|
@ -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')
|
||||
|
22
src/app.ts
22
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,
|
||||
|
16
src/helpers/get-env.ts
Normal file
16
src/helpers/get-env.ts
Normal file
@ -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;
|
||||
}
|
||||
};
|
@ -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<AliasesQuery> {
|
||||
}
|
||||
|
||||
export class AliasesService<ServiceParams extends AliasesParams = AliasesParams>
|
||||
implements ServiceInterface<Alias, AliasesData, ServiceParams, AliasesPatch>
|
||||
implements
|
||||
ServiceInterface<
|
||||
AliasApiResponse,
|
||||
AliasesData,
|
||||
ServiceParams,
|
||||
AliasesPatch
|
||||
>
|
||||
{
|
||||
constructor(public options: AliasesServiceOptions) {}
|
||||
|
||||
async find(params: ServiceParams): Promise<Alias[]> {
|
||||
async find(params: ServiceParams): Promise<AliasApiResponse[]> {
|
||||
const userId = await this.getUserIdByEmailAddress(params);
|
||||
|
||||
return this.getUserAddresses(userId);
|
||||
}
|
||||
|
||||
async create(data: AliasesData, params: ServiceParams): Promise<Alias>;
|
||||
async create(
|
||||
data: AliasesData,
|
||||
params: ServiceParams,
|
||||
): Promise<Alias | Alias[]> {
|
||||
): Promise<AliasApiResponse>;
|
||||
async create(
|
||||
data: AliasesData,
|
||||
params: ServiceParams,
|
||||
): Promise<AliasApiResponse | AliasApiResponse[]> {
|
||||
const userId = await this.getUserIdByEmailAddress(params);
|
||||
|
||||
const randomString = faker.git.commitSha({ length: 4 });
|
||||
@ -73,12 +94,13 @@ export class AliasesService<ServiceParams extends AliasesParams = AliasesParams>
|
||||
|
||||
const emailDomain = config.get("wildDuck.domain");
|
||||
|
||||
const createResult = await wildDuckClient.post<CreateAddressResponse>(
|
||||
`/users/${userId}/addresses`,
|
||||
{
|
||||
address: `${alias}@${emailDomain}`,
|
||||
},
|
||||
);
|
||||
const createResult =
|
||||
await wildDuckClient.post<CreateWildDuckAddressResponse>(
|
||||
`/users/${userId}/addresses`,
|
||||
{
|
||||
address: `${alias}@${emailDomain}`,
|
||||
},
|
||||
);
|
||||
|
||||
if (!createResult.data.success) {
|
||||
throw new BadRequest("Failed to create alias");
|
||||
@ -92,32 +114,40 @@ export class AliasesService<ServiceParams extends AliasesParams = AliasesParams>
|
||||
): 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<Alias>(`addresses/resolve/${email}`),
|
||||
wildDuckClient.get<WildDuckAddress>(`addresses/resolve/${email}`),
|
||||
),
|
||||
);
|
||||
|
||||
return addressInfoResponse.data.user;
|
||||
}
|
||||
|
||||
private async getUserAddresses(userId: string): Promise<Alias[]> {
|
||||
private async getUserAddresses(userId: string): Promise<AliasApiResponse[]> {
|
||||
const { data: userAddressesResponse } =
|
||||
await wildDuckClient.get<GetAddressInfoResponse>(
|
||||
await wildDuckClient.get<GetWildDuckAddressInfoResponse>(
|
||||
`/users/${userId}/addresses`,
|
||||
);
|
||||
|
||||
return userAddressesResponse.results;
|
||||
return userAddressesResponse.results.map(this.sanitizeAliasResponse);
|
||||
}
|
||||
|
||||
async remove(id: NullableId, params: ServiceParams): Promise<Alias[]> {
|
||||
const { data: addressInfoResponse } = await wildDuckClient.get<Alias>(
|
||||
`addresses/resolve/${id}`,
|
||||
);
|
||||
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
|
||||
@ -129,10 +159,26 @@ export class AliasesService<ServiceParams extends AliasesParams = AliasesParams>
|
||||
}
|
||||
const userId = await this.getUserIdByEmailAddress(params);
|
||||
|
||||
await wildDuckClient.delete<Alias>(`users/${userId}/addresses/${id}`);
|
||||
await wildDuckClient.delete<WildDuckAddress>(
|
||||
`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) => {
|
||||
|
Loading…
Reference in New Issue
Block a user