diff --git a/src/app/admin/aliases/page.tsx b/src/app/admin/aliases/page.tsx
index 4e25ee3..c5ca9d3 100644
--- a/src/app/admin/aliases/page.tsx
+++ b/src/app/admin/aliases/page.tsx
@@ -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) => (
{alias.address}
- {alias.alias}
+
+ {alias.alias}
+ {alias.temporary && Temporary}
+
diff --git a/src/app/admin/audit/page.tsx b/src/app/admin/audit/page.tsx
index f59b3d4..cc5d9f6 100644
--- a/src/app/admin/audit/page.tsx
+++ b/src/app/admin/audit/page.tsx
@@ -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: ,
+ createTempAlias: ,
createAlias: ,
approveAlias: ,
deleteAlias: ,
diff --git a/src/app/self-service/page.tsx b/src/app/self-service/page.tsx
index 048529a..78d2f3a 100644
--- a/src/app/self-service/page.tsx
+++ b/src/app/self-service/page.tsx
@@ -9,7 +9,7 @@ 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 (
<>
diff --git a/src/lib/actions.ts b/src/lib/actions.ts
index 8a0220e..da5292f 100644
--- a/src/lib/actions.ts
+++ b/src/lib/actions.ts
@@ -144,7 +144,7 @@ export async function requestTemporaryAlias(
break;
case 'random':
randomString = crypto
- .randomBytes(12)
+ .randomBytes(8)
.toString('base64')
.replace(/\W/, ''); // Delete special characters
break;
@@ -195,6 +195,8 @@ export async function claimTemporaryAlias(key: string): Promise {
temporary: true,
};
+ auditLog('createTempAlias', alias);
+
return alias;
}
diff --git a/src/lib/audit.ts b/src/lib/audit.ts
index 4cf893e..7eb1792 100644
--- a/src/lib/audit.ts
+++ b/src/lib/audit.ts
@@ -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 = {
diff --git a/src/lib/components/ui/admin/ManageUserButton.tsx b/src/lib/components/ui/admin/ManageUserButton.tsx
index ae250fe..f6c55f6 100644
--- a/src/lib/components/ui/admin/ManageUserButton.tsx
+++ b/src/lib/components/ui/admin/ManageUserButton.tsx
@@ -68,7 +68,9 @@ export default function ManageUserButton({ email }: { email: string }) {
aliases.map((alias) => (
- {alias.alias} {alias.pending && Pending}
+ {alias.alias}
+ {alias.pending && Pending}
+ {alias.temporary && Temporary}
diff --git a/src/lib/components/ui/user/TempAliasesCard.tsx b/src/lib/components/ui/user/TempAliasesCard.tsx
index ec080b5..0ba5db7 100644
--- a/src/lib/components/ui/user/TempAliasesCard.tsx
+++ b/src/lib/components/ui/user/TempAliasesCard.tsx
@@ -1,11 +1,14 @@
-import { disposeTempAliasRequest, fetchOwnAliases, requestTemporaryAlias, claimTemporaryAlias } from "@/lib/actions";
+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, Button, Card, Dialog, Flex, Heading, IconButton, Select, Text, TextField } from "@radix-ui/themes";
-import { CircleUserIcon, RefreshCcwIcon } from "lucide-react";
+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 { useEffect, useState } from "react";
+import { useContext, useEffect, useState } from "react";
+import GhostMessage from "../GhostMessage";
+import LoadingSpinner from "../LoadingSpinner";
export default function TempAliasesCard() {
const session = useSession().data;
@@ -15,7 +18,8 @@ export default function TempAliasesCard() {
const [labelAtEnd, setLabelAtEnd] = useState(false);
const [aliasStyle, setAliasStyle] = useState<"words" | "random">("words");
const [open, setOpen] = useState(false);
- const [created, setCreated] = useState(false);
+ const [state, setState] = useState<'input' | 'loading' | 'created'>('input');
+ const toast = useContext(ToastContext);
useEffect(() => {
fetchOwnAliases(true).then(setAliases);
@@ -50,7 +54,7 @@ export default function TempAliasesCard() {
setAliasLabel("");
setLabelAtEnd(false);
setAliasStyle("words");
- setCreated(false);
+ setState('input');
}
}}>
@@ -58,96 +62,214 @@ export default function TempAliasesCard() {
Generate alias
-
- 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.
-
-
-
- Alias label
-
-
-
-
- setAliasLabel(e.currentTarget.value.substring(0, 16))}
- />
-
-
+ {
+ state == "input"
+ ? <>
+
+ 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.
+
-
- Alias style
- setAliasStyle(value as any)}>
-
-
-
- Alias style
- Words
- Random
-
-
-
-
+
+
+ Alias label
+
+
+
+
+ setAliasLabel(e.currentTarget.value.substring(0, 16))}
+ />
+
+
-
- Label position
- setLabelAtEnd(value == "after")}>
-
-
-
- Alias style
- Label first
- Label at end
-
-
-
-
-
+
+ Alias style
+ setAliasStyle(value as any)}>
+
+
+
+ Alias style
+ Words
+ Random
+
+
+
+
-
-
-
-
-
- Generated alias
- {aliasPreview?.alias || "Enter a label first"}
+
+ Label position
+ setLabelAtEnd(value == "after")}>
+
+
+
+ Alias style
+ Label first
+ Label at end
+
+
+
+
-
- refreshAlias()} disabled={!aliasLabel} variant="surface" size="2">
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+ Generated alias
+ {aliasPreview?.alias || "Enter a label first"}
+
+
+ refreshAlias()} disabled={!aliasLabel} variant="surface" size="2">
+
+
+
+
+
+
+
+
+
+
+
+ >
+ : state == "loading"
+ ? <>
+ } header="Creating your alias..." />
+ >
+ : <>
+
+
+
+
+
+ Alias created
+ {aliasPreview!.alias}
+
+
+ navigator.clipboard.writeText(aliasPreview!.alias)} variant="surface" size="2">
+
+
+
+
+
+
+
+
+
+ >
+ }
Freely generate and dispose of randomized aliases.
+
+ {
+ aliases == null
+ ? } header="Loading..." />
+ : aliases.length
+ ?
+
+
+
+
+ Alias
+ Manage
+
+
+
+
+ {
+ aliases.map((alias =>
+
+ {alias.alias}
+
+
+
+
+
+
+
+
+ Delete alias
+
+ Are you sure you want to delete the alias {alias.alias}
?
+ You will not be able to recreate it afterwards.
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ))
+ }
+
+
+
+
+ : } header="No aliases" message="Generate an alias to begin" />
+ }
);
}
diff --git a/src/lib/db.ts b/src/lib/db.ts
index 3a44131..bc51921 100644
--- a/src/lib/db.ts
+++ b/src/lib/db.ts
@@ -82,7 +82,7 @@ export function getAllAliases() {
return new Promise(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) => ({
@@ -99,10 +99,10 @@ export function getUserAliases(email: string, tempAliases?: boolean) {
const db = database('aliases');
db.all(
- "SELECT id, address, alias, pending FROM aliases WHERE address = ?1 " + (typeof tempAliases != 'undefined' ? "AND temporary = ?2" : ""),
+ "SELECT id, address, alias, pending, temporary FROM aliases WHERE address = ?1 " + (typeof tempAliases != 'undefined' ? "AND temporary = ?2" : ""),
{
1: email,
- 2: tempAliases ? 1 : 0,
+ 2: typeof tempAliases == "boolean" ? (tempAliases ? 1 : 0) : undefined,
},
(err, res: any[]) => {
db.close();
@@ -120,7 +120,7 @@ export function getAlias(alias: string) {
return new Promise(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);