Compare commits

...

2 commits

Author SHA1 Message Date
Lea c90bfd4b47
yeah 2024-01-22 23:20:59 +01:00
Lea be4aaf0dd4
temp commit 2024-01-22 22:35:39 +01:00
13 changed files with 504 additions and 38 deletions

View file

@ -37,5 +37,6 @@ Check out our [Next.js deployment documentation](https://nextjs.org/docs/deploym
### notes to add later
```sql
CREATE TABLE aliases (id INTEGER PRIMARY KEY AUTOINCREMENT, address TEXT NOT NULL, alias TEXT NOT NULL, pending INTEGER DEFAULT 0);
CREATE TABLE aliases (id INTEGER PRIMARY KEY AUTOINCREMENT, address TEXT NOT NULL, alias TEXT NOT NULL, pending INTEGER DEFAULT 0, temporary INTEGER DEFAULT 0);
CREATE TABLE temp_alias_requests (key TEXT PRIMARY KEY, address TEXT NOT NULL, alias TEXT NOT NULL, expires INTEGER NOT NULL);
```

View file

@ -17,6 +17,7 @@
"lucide-react": "^0.311.0",
"next": "14.0.4",
"next-auth": "^4.24.5",
"random-words": "^2.0.0",
"react": "^18",
"react-dom": "^18",
"sqlite3": "^5.1.7"

View file

@ -29,6 +29,9 @@ dependencies:
next-auth:
specifier: ^4.24.5
version: 4.24.5(next@14.0.4)(react-dom@18.2.0)(react@18.2.0)
random-words:
specifier: ^2.0.0
version: 2.0.0
react:
specifier: ^18
version: 18.2.0
@ -4029,6 +4032,12 @@ packages:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
dev: true
/random-words@2.0.0:
resolution: {integrity: sha512-uqpnDqFnYrZajgmvgjmBrSZL2V1UA/9bNPGrilo12CmBeBszoff/avElutUlwWxG12gvmCk/8dUhvHefYxzYjw==}
dependencies:
seedrandom: 3.0.5
dev: false
/rc@1.2.8:
resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==}
hasBin: true
@ -4244,6 +4253,10 @@ packages:
loose-envify: 1.4.0
dev: false
/seedrandom@3.0.5:
resolution: {integrity: sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==}
dev: false
/semver@6.3.1:
resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
hasBin: true

View file

@ -5,7 +5,7 @@ import GenericConfirmationDialog from "@/lib/components/ui/GenericConfirmationDi
import GhostMessage from "@/lib/components/ui/GhostMessage";
import LoadingSpinner from "@/lib/components/ui/LoadingSpinner";
import { AliasEntry } from "@/lib/db";
import { Button, Card, Code, Dialog, Flex, Heading, Table } from "@radix-ui/themes";
import { Badge, Button, Card, Code, Dialog, Flex, Heading, Table } from "@radix-ui/themes";
import { ListChecksIcon } from "lucide-react";
import Image from "next/image";
import { useEffect, useState } from "react";
@ -96,7 +96,10 @@ export default function Aliases() {
{active.map((alias) => (
<Table.Row key={alias.id}>
<Table.Cell justify='start'>{alias.address}</Table.Cell>
<Table.Cell justify='start'>{alias.alias}</Table.Cell>
<Table.Cell justify='start'>
{alias.alias}
{alias.temporary && <Badge variant="soft" ml="3" color="gray">Temporary</Badge>}
</Table.Cell>
<Table.Cell justify='end'>
<Flex gap='3' justify="end">
<Dialog.Root>

View file

@ -9,13 +9,14 @@ import { sha256sum } from "@/lib/util";
import { Avatar, Card, Code, Flex, Grid, Heading, IconButton, Text } from "@radix-ui/themes";
import dayjs from "dayjs";
import relativeTime from "dayjs/plugin/relativeTime";
import { ArrowLeftIcon, ArrowRightIcon, AsteriskSquareIcon, GhostIcon, LogInIcon, UserCheckIcon, UserPlusIcon, UsersIcon, XIcon } from "lucide-react";
import { ArrowLeftIcon, ArrowRightIcon, AsteriskSquareIcon, GhostIcon, LogInIcon, UserCheckIcon, UserPlusIcon, UsersIcon, UsersRoundIcon, XIcon } from "lucide-react";
import { useEffect, useState } from "react";
dayjs.extend(relativeTime);
const LOG_ACTION_HUMAN_READABLE: { [key in AuditLogAction]?: string } = {
requestAlias: "Requested an alias",
createTempAlias: "Generated a temporary alias",
createAlias: "Created an alias",
approveAlias: "Approved an alias",
deleteAlias: "Deleted an alias",
@ -42,6 +43,7 @@ export default function Audit() {
const logActionIcons: { [key in AuditLogAction]?: React.ReactNode } = {
requestAlias: <UsersIcon />,
createTempAlias: <UsersRoundIcon />,
createAlias: <UsersIcon />,
approveAlias: <UserCheckIcon />,
deleteAlias: <XIcon />,

View file

@ -3,21 +3,23 @@
import ConnectionDetailsCard from "@/lib/components/ui/user/ConnectionDetailsCard";
import OwnAliasesCard from "@/lib/components/ui/user/OwnAliasesCard";
import OwnCredentialsCard from "@/lib/components/ui/user/OwnCredentialsCard";
import TempAliasesCard from "@/lib/components/ui/user/TempAliasesCard";
import useWindowDimensions from "@/lib/hooks/useWindowDimensions";
import { Grid, Heading } from "@radix-ui/themes";
export default function SelfService() {
const dimensions = useWindowDimensions();
const mobileUi = dimensions.width < 1200;
const mobileUi = dimensions.width < 1450;
return (
<>
<Heading className="pb-4">Account settings</Heading>
<Grid display="inline-grid" columns={mobileUi ? "1" : "3"} gap="4" width={mobileUi ? "100%" : "auto"}>
<Grid display="inline-grid" columns={mobileUi ? "1" : "4"} gap="4" width={mobileUi ? "100%" : "auto"}>
<OwnCredentialsCard />
<ConnectionDetailsCard />
<OwnAliasesCard />
<TempAliasesCard />
</Grid>
</>
);

View file

@ -1,10 +1,13 @@
"use server";
import { getServerSession } from "next-auth";
import { AliasEntry, approveAliasEntry, createAliasEntry, createUserEntry, database, deleteAliasEntry, getAlias, getAllAliases, getUserAliases, setUserPassword } from "./db";
import { aliasesNeedApproval, isAdmin } from "./util";
import { AuditLog, auditLog } from "./audit";
import crypto from "crypto";
import fs from "fs/promises";
import { getServerSession } from "next-auth";
import * as random_words from "random-words";
import { AuditLog, auditLog } from "./audit";
import { AliasEntry, AliasRequestEntry, approveAliasEntry, createAliasEntry, createTempAliasRequestEntry, createUserEntry, database, deleteAliasEntry, deleteTempAliasRequestEntry, getAlias, getAllAliases, getTempAliasRequestEntry, getUserAliases, setUserPassword } from "./db";
import { aliasesNeedApproval, isAdmin } from "./util";
import { TEMP_EMAIL_DOMAIN } from "./constants";
export async function fetchAllUsers(): Promise<string[]> {
return new Promise(async (resolve, reject) => {
@ -28,10 +31,10 @@ export async function changeOwnPassword(newPass: string) {
auditLog("changeOwnPassword");
}
export async function fetchOwnAliases() {
export async function fetchOwnAliases(tempAliases?: boolean) {
const session = await getServerSession();
if (!session?.user?.email) throw new Error("Unauthenticated");
return await getUserAliases(session.user.email);
return await getUserAliases(session.user.email, tempAliases);
}
export async function fetchUserAliases(email: string) {
@ -50,14 +53,14 @@ export async function fetchAllAliases() {
return await getAllAliases();
}
export async function aliasAvailable(email: string) {
export async function aliasAvailable(email: string, searchTempRequests: boolean = false) {
const session = await getServerSession();
if (!session?.user) throw new Error("Unauthenticated");
return new Promise<boolean>((resolve, reject) => {
const db = database('aliases');
db.get('SELECT id FROM aliases WHERE alias = ?', email.toLowerCase(), (err, res) => {
db.close();
if (!searchTempRequests || err) db.close();
if (err) return reject(err);
if (res != undefined) return resolve(false);
@ -65,7 +68,17 @@ export async function aliasAvailable(email: string) {
authDb.get('SELECT key FROM passwords WHERE key = ?', email.toLowerCase(), (err, res) => {
authDb.close();
if (err) return reject(err);
resolve(res == undefined);
if (!searchTempRequests) {
return resolve(res == undefined);
} else {
db.get('SELECT key FROM temp_alias_requests WHERE alias = ?', email.toLowerCase(), (err, res) => {
db.close();
if (err) return reject(err);
return resolve(res == undefined);
});
}
});
});
});
@ -78,11 +91,13 @@ export async function createAlias(user: string, alias: string): Promise<AliasEnt
if (!await aliasAvailable(alias)) throw new Error("Alias unavailable");
const id = await createAliasEntry(user, alias.toLowerCase(), false);
const res = {
id: id,
address: user,
alias: alias,
pending: false,
temporary: false,
};
auditLog('createAlias', res);
@ -101,13 +116,101 @@ export async function createAliasSelf(alias: string): Promise<AliasEntry> {
id: id,
address: session.user.email,
alias: alias,
pending: pending
pending: pending,
temporary: false,
};
auditLog('requestAlias', res);
return res;
}
export async function requestTemporaryAlias(
label: string,
labelAtEnd: boolean,
style: 'words' | 'random',
oldToken?: string,
): Promise<AliasRequestEntry> {
const session = await getServerSession();
if (!session?.user?.email) throw new Error("Unauthenticated");
if (!label.length || label.length > 16) throw new Error("Malformed request");
let email: string;
do {
let randomString: string;
switch (style) {
case 'words':
randomString = random_words.generate(2).join('');
break;
case 'random':
randomString = crypto
.randomBytes(8)
.toString('base64')
.replace(/\W/, ''); // Delete special characters
break;
default:
throw new Error("Invalid style");
}
email = `${labelAtEnd ? `${randomString}-${label}` : `${label}-${randomString}`}@${TEMP_EMAIL_DOMAIN}`;
} while (!await aliasAvailable(email, true));
const request: AliasRequestEntry = {
key: crypto.randomBytes(12).toString('base64'),
address: session.user.email,
alias: email,
expires: Math.floor(Date.now() / 1000) + (60 * 60 * 4), // In 4 hours
};
await createTempAliasRequestEntry(
request.key,
request.address,
request.alias,
request.expires,
);
if (oldToken) {
await deleteTempAliasRequestEntry(oldToken);
}
return request;
}
export async function claimTemporaryAlias(key: string): Promise<AliasEntry> {
const session = await getServerSession();
if (!session?.user?.email) throw new Error("Unauthenticated");
const data = await getTempAliasRequestEntry(key);
if (!data || data.address != session.user.email) throw new Error("Unknown alias key");
await deleteTempAliasRequestEntry(key);
if (data.expires < Math.floor(Date.now() / 1000)) throw new Error("Alias request expired");
const id = await createAliasEntry(data.address, data.alias, false, true);
const alias: AliasEntry = {
id: id,
address: data.address,
alias: data.alias,
pending: false,
temporary: true,
};
auditLog('createTempAlias', alias);
return alias;
}
export async function disposeTempAliasRequest(key: string) {
const session = await getServerSession();
if (!session?.user?.email) throw new Error("Unauthenticated");
const data = await getTempAliasRequestEntry(key);
if (!data) return;
if (data.address != session.user.email) throw new Error("Unauthorized");
await deleteTempAliasRequestEntry(key);
}
export async function deleteAlias(alias: string) {
const session = await getServerSession();
if (!session?.user?.email) throw new Error("Unauthenticated");
@ -161,7 +264,7 @@ export async function fetchAuditLog(page: number): Promise<{ page: number, perPa
.split("\n")
.reverse();
if (page > Math.floor(lines.length / itemsPerPage)) {
if (page > Math.floor(lines.length / itemsPerPage)) {
page = Math.ceil(lines.length / itemsPerPage) - 1;
} else if (page < 0) {
page = 0;

View file

@ -1,7 +1,7 @@
import { getServerSession } from "next-auth";
import fs from "fs/promises";
export type AuditLogAction = 'login' | 'changeOwnPassword' | 'requestAlias' | 'deleteAlias' // Unprivileged
export type AuditLogAction = 'login' | 'changeOwnPassword' | 'requestAlias' | 'deleteAlias' | 'createTempAlias' // Unprivileged
| 'createUser' | 'createAlias' | 'approveAlias' | 'deleteAlias'; // Privileged
export type AuditLog = {

View file

@ -68,7 +68,9 @@ export default function ManageUserButton({ email }: { email: string }) {
aliases.map((alias) => (
<Table.Row key={alias.id}>
<Table.Cell justify="start">
{alias.alias} {alias.pending && <Badge variant="soft" ml="3">Pending</Badge>}
{alias.alias}
{alias.pending && <Badge variant="soft" ml="3">Pending</Badge>}
{alias.temporary && <Badge variant="soft" ml="3" color="gray">Temporary</Badge>}
</Table.Cell>
<Table.Cell justify="end">
<Flex gap="3" justify="end">

View file

@ -21,7 +21,7 @@ export default function OwnAliasesCard() {
const toast = useContext(ToastContext);
useEffect(() => {
fetchOwnAliases().then(setAliases);
fetchOwnAliases(false).then(setAliases);
}, []);
useEffect(() => {

View file

@ -0,0 +1,275 @@
import { disposeTempAliasRequest, fetchOwnAliases, requestTemporaryAlias, claimTemporaryAlias, deleteAlias } from "@/lib/actions";
import { GRAVATAR_DEFAULT } from "@/lib/constants";
import { AliasEntry, AliasRequestEntry } from "@/lib/db";
import { ToastContext } from "@/lib/providers/ToastProvider";
import { sha256sum } from "@/lib/util";
import { Avatar, Badge, Box, Button, Card, Code, Dialog, Flex, Heading, IconButton, ScrollArea, Select, Table, Text, TextField } from "@radix-ui/themes";
import { CircleUserIcon, CopyIcon, RefreshCcwIcon, UsersRoundIcon } from "lucide-react";
import { useSession } from "next-auth/react";
import { useContext, useEffect, useState } from "react";
import GhostMessage from "../GhostMessage";
import LoadingSpinner from "../LoadingSpinner";
export default function TempAliasesCard() {
const session = useSession().data;
const [aliases, setAliases] = useState<AliasEntry[] | null>(null);
const [aliasPreview, setAliasPreview] = useState<AliasRequestEntry | undefined>(undefined);
const [aliasLabel, setAliasLabel] = useState("");
const [labelAtEnd, setLabelAtEnd] = useState(false);
const [aliasStyle, setAliasStyle] = useState<"words" | "random">("words");
const [open, setOpen] = useState(false);
const [state, setState] = useState<'input' | 'loading' | 'created'>('input');
const toast = useContext(ToastContext);
useEffect(() => {
fetchOwnAliases(true).then(setAliases);
}, []);
const refreshAlias = () => {
if (aliasPreview?.key) disposeTempAliasRequest(aliasPreview.key); // It's fine if this errors
if (!aliasLabel) {
setAliasPreview(undefined);
return;
}
requestTemporaryAlias(
aliasLabel,
labelAtEnd,
aliasStyle,
aliasPreview?.key,
).then(setAliasPreview);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
useEffect(refreshAlias, [aliasLabel, aliasStyle, labelAtEnd]);
return (
<Card className="h-fit">
<Flex direction="row" justify="between" mb="2">
<Heading size="3">Temporary aliases</Heading>
<Dialog.Root open={open} onOpenChange={(open) => {
setOpen(open);
if (!open) {
setAliasLabel("");
setLabelAtEnd(false);
setAliasStyle("words");
setState('input');
}
}}>
<Dialog.Trigger>
<Button variant="outline" size="1">Generate alias</Button>
</Dialog.Trigger>
<Dialog.Content>
<Dialog.Title>Generate alias</Dialog.Title>
{
state == "input"
? <>
<Dialog.Description>
Temporary aliases can be used to sign up with services without exposing your primary email address.
If an alias starts receiving spam or you no longer use the service, you can delete the alias.
</Dialog.Description>
<Flex direction="row" gap="3" mt="4">
<Flex direction="column" gap="3" grow="1">
<Text ml="1" size="1" color="gray" mb="-1">Alias label</Text>
<TextField.Root>
<TextField.Slot>
<CircleUserIcon size="16" />
</TextField.Slot>
<TextField.Input
placeholder="Alias label"
value={aliasLabel}
onChange={(e) => setAliasLabel(e.currentTarget.value.substring(0, 16))}
/>
</TextField.Root>
</Flex>
<Flex direction="column" gap="3">
<Text ml="1" size="1" color="gray" mb="-1">Alias style</Text>
<Select.Root value={aliasStyle} onValueChange={(value) => setAliasStyle(value as any)}>
<Select.Trigger />
<Select.Content>
<Select.Group>
<Select.Label>Alias style</Select.Label>
<Select.Item value="words">Words</Select.Item>
<Select.Item value="random">Random</Select.Item>
</Select.Group>
</Select.Content>
</Select.Root>
</Flex>
<Flex direction="column" gap="3">
<Text ml="1" size="1" color="gray" mb="-1">Label position</Text>
<Select.Root value={labelAtEnd ? "after" : "before"} onValueChange={(value) => setLabelAtEnd(value == "after")}>
<Select.Trigger />
<Select.Content>
<Select.Group>
<Select.Label>Alias style</Select.Label>
<Select.Item value="before">Label first</Select.Item>
<Select.Item value="after">Label at end</Select.Item>
</Select.Group>
</Select.Content>
</Select.Root>
</Flex>
</Flex>
<Card mt="4">
<Flex direction="row" justify="between" align="center">
<Flex direction="row" align="center" gap="3">
<Avatar
size="3"
src={`https://gravatar.com/avatar/${sha256sum(aliasPreview?.alias || "")}?d=${GRAVATAR_DEFAULT}`}
fallback={"@"}
/>
<Flex direction="column" gap="0">
<Text size="3" weight="medium">Generated alias</Text>
<Text size="2" weight="light">{aliasPreview?.alias || "Enter a label first"}</Text>
</Flex>
</Flex>
<IconButton onClick={() => refreshAlias()} disabled={!aliasLabel} variant="surface" size="2">
<RefreshCcwIcon size="16" />
</IconButton>
</Flex>
</Card>
<Flex gap="3" mt="4" justify="end">
<Dialog.Close>
<Button variant="outline">Close</Button>
</Dialog.Close>
<Button
disabled={!aliasPreview}
variant="soft"
onClick={async () => {
try {
setState('loading');
const alias = await claimTemporaryAlias(aliasPreview!.key);
setAliases([...(aliases ?? []), alias]);
setState('created');
} catch (e) {
setOpen(false);
toast({
title: "Failed to create alias",
description: `${e}`,
variant: "error",
});
}
}}
>
Create
</Button>
</Flex>
</>
: state == "loading"
? <>
<GhostMessage icon={<LoadingSpinner />} header="Creating your alias..." />
</>
: <>
<Card mt="4">
<Flex direction="row" justify="between" align="center">
<Flex direction="row" align="center" gap="3">
<Avatar
size="3"
src={`https://gravatar.com/avatar/${sha256sum(aliasPreview!.alias)}?d=${GRAVATAR_DEFAULT}`}
fallback={"@"}
/>
<Flex direction="column" gap="0">
<Text size="3" weight="medium">Alias created</Text>
<Text size="2" weight="light">{aliasPreview!.alias}</Text>
</Flex>
</Flex>
<IconButton onClick={() => navigator.clipboard.writeText(aliasPreview!.alias)} variant="surface" size="2">
<CopyIcon size="16" />
</IconButton>
</Flex>
</Card>
<Flex gap="3" mt="4" justify="end">
<Dialog.Close>
<Button variant="outline">Close</Button>
</Dialog.Close>
</Flex>
</>
}
</Dialog.Content>
</Dialog.Root>
</Flex>
<Text weight="light" size="2">Freely generate and dispose of randomized aliases.</Text>
{
aliases == null
? <GhostMessage icon={<LoadingSpinner />} header="Loading..." />
: aliases.length
? <Box className="h-52">
<ScrollArea>
<Table.Root>
<Table.Header>
<Table.Row>
<Table.ColumnHeaderCell justify='start'>Alias</Table.ColumnHeaderCell>
<Table.ColumnHeaderCell justify='end'>Manage</Table.ColumnHeaderCell>
</Table.Row>
</Table.Header>
<Table.Body>
{
aliases.map((alias => <Table.Row key={alias.id}>
<Table.Cell justify='start'>
{alias.alias}
</Table.Cell>
<Table.Cell justify='end'>
<Dialog.Root>
<Dialog.Trigger>
<Button variant="soft" size="1" mr="2">Delete</Button>
</Dialog.Trigger>
<Dialog.Content>
<Dialog.Title>Delete alias</Dialog.Title>
<Dialog.Description>
Are you sure you want to delete the alias <Code className="whitespace-nowrap">{alias.alias}</Code>?
You will not be able to recreate it afterwards.
</Dialog.Description>
<Flex gap="3" mt="4" justify="end">
<Dialog.Close>
<Button variant="outline">Cancel</Button>
</Dialog.Close>
<Dialog.Close>
<Button
variant="soft"
onClick={async () => {
try {
await deleteAlias(alias.alias);
setAliases(aliases.filter((a) => a.id != alias.id));
toast({
title: "Temporary alias deleted",
description: alias.alias,
variant: "success",
});
} catch (e) {
console.error(e);
toast({
title: "Failed to delete alias",
description: `${e}`,
variant: "error",
});
}
}}
>
Delete
</Button>
</Dialog.Close>
</Flex>
</Dialog.Content>
</Dialog.Root>
</Table.Cell>
</Table.Row>))
}
</Table.Body>
</Table.Root>
</ScrollArea>
</Box>
: <GhostMessage icon={<UsersRoundIcon />} header="No aliases" message="Generate an alias to begin" />
}
</Card>
);
}

View file

@ -8,4 +8,5 @@ export const SMTP_SECURITY = "SSL/TLS";
export const IMAP_SECURITY = "SSL/TLS";
export const WEBMAIL_URL = "https://webmail.amogus.cloud";
export const ALIAS_DOMAINS = ["amogus.cloud", "lea.pet", "futacockinside.me"];
export const GRAVATAR_DEFAULT = "retro";
export const GRAVATAR_DEFAULT = "retro";
export const TEMP_EMAIL_DOMAIN = "t.amogus.cloud";

View file

@ -2,6 +2,9 @@ import sqlite from "sqlite3";
import bcrypt from "bcryptjs";
import { PHASE_PRODUCTION_BUILD } from "next/dist/shared/lib/constants";
export type AliasEntry = { id: number, address: string, alias: string, pending: boolean, temporary: boolean };
export type AliasRequestEntry = { key: string, address: string, alias: string, expires: number };
export const database = (type: 'credentials' | 'aliases') => {
if (process.env.NEXT_PHASE != PHASE_PRODUCTION_BUILD) {
for (const v of ["CREDENTIALS_DB_PATH", "ALIASES_DB_PATH"]) {
@ -75,33 +78,41 @@ export function setUserPassword(email: string, newPass: string) {
});
}
export type AliasEntry = { id: number, address: string, alias: string, pending: boolean };
export function getAllAliases() {
return new Promise<AliasEntry[]>(async (resolve, reject) => {
const db = database('aliases');
db.all("SELECT id, address, alias, pending FROM aliases", (err, res: any[]) => {
db.all("SELECT id, address, alias, pending, temporary FROM aliases", (err, res: any[]) => {
db.close();
if (err) return reject(err);
resolve(res.map((data) => ({
...data,
pending: !!data.pending,
temporary: !!data.temporary,
})));
});
});
}
export function getUserAliases(email: string) {
export function getUserAliases(email: string, tempAliases?: boolean) {
return new Promise<AliasEntry[]>(async (resolve, reject) => {
const db = database('aliases');
db.all("SELECT id, address, alias, pending FROM aliases WHERE address = ?", email, (err, res: any[]) => {
db.close();
if (err) return reject(err);
resolve(res.map((data) => ({
...data,
pending: !!data.pending,
})));
});
db.all(
"SELECT id, address, alias, pending, temporary FROM aliases WHERE address = ?1 " + (typeof tempAliases != 'undefined' ? "AND temporary = ?2" : ""),
{
1: email,
2: typeof tempAliases == "boolean" ? (tempAliases ? 1 : 0) : undefined,
},
(err, res: any[]) => {
db.close();
if (err) return reject(err);
resolve(res.map((data) => ({
...data,
pending: !!data.pending,
temporary: !!data.temporary,
})));
});
});
}
@ -109,30 +120,32 @@ export function getAlias(alias: string) {
return new Promise<AliasEntry | undefined>(async (resolve, reject) => {
const db = database('aliases');
db.get("SELECT id, address, alias, pending FROM aliases WHERE alias = ?", alias, (err, res: AliasEntry) => {
db.get("SELECT id, address, alias, pending, temporary FROM aliases WHERE alias = ?", alias, (err, res: AliasEntry) => {
db.close();
if (err) return reject(err);
if (!res) return resolve(undefined);
resolve({
...res,
pending: !!res.pending,
temporary: !!res.temporary,
});
});
});
}
export function createAliasEntry(user: string, alias: string, pending: boolean) {
export function createAliasEntry(user: string, alias: string, pending: boolean, temporary: boolean = false) {
return new Promise<number>(async (resolve, reject) => {
const db = database('aliases');
db.run(
"INSERT INTO aliases (address, alias, pending) VALUES (?1, ?2, ?3)",
"INSERT INTO aliases (address, alias, pending, temporary) VALUES (?1, ?2, ?3, ?4)",
{
1: user,
2: alias,
3: pending ? 1 : 0,
4: temporary ? 1 : 0,
},
function(err: any) {
function (err: any) {
db.close();
if (err) return reject(err);
resolve(this.lastID);
@ -140,6 +153,56 @@ export function createAliasEntry(user: string, alias: string, pending: boolean)
});
}
export function createTempAliasRequestEntry(key: string, account: string, alias: string, expires: number) {
return new Promise<number>(async (resolve, reject) => {
const db = database('aliases');
db.run(
"INSERT INTO temp_alias_requests (key, address, alias, expires) VALUES (?1, ?2, ?3, ?4)",
{
1: key,
2: account,
3: alias,
4: expires,
},
function (err: any) {
db.close();
if (err) return reject(err);
resolve(this.lastID);
});
});
}
export function getTempAliasRequestEntry(key: string) {
return new Promise<AliasRequestEntry | undefined>(async (resolve, reject) => {
const db = database('aliases');
db.get(
"SELECT key, address, alias, expires FROM temp_alias_requests WHERE key = ?",
key,
function (err, res: any) {
db.close();
if (err) return reject(err);
resolve(res);
});
});
}
export function deleteTempAliasRequestEntry(key: string) {
return new Promise<void>(async (resolve, reject) => {
const db = database('aliases');
db.run(
"DELETE FROM temp_alias_requests WHERE key = ?",
key,
function (err: any) {
db.close();
if (err) return reject(err);
resolve();
});
});
}
export function approveAliasEntry(alias: string) {
return new Promise<void>(async (resolve, reject) => {
const db = database('aliases');
@ -147,7 +210,7 @@ export function approveAliasEntry(alias: string) {
db.run(
"UPDATE aliases SET pending = 0 WHERE alias = ?",
alias,
function(err: any) {
function (err: any) {
db.close();
if (err) return reject(err);
resolve();
@ -162,7 +225,7 @@ export function deleteAliasEntry(alias: string) {
db.run(
"DELETE FROM aliases WHERE alias = ?",
alias,
function(err: any) {
function (err: any) {
db.close();
if (err) return reject(err);
resolve();