POST /api/aliases/temporary

This commit is contained in:
Lea 2024-01-23 23:39:48 +01:00
parent aa7c2b12eb
commit 54cefc8360
Signed by: Lea
GPG key ID: 1BAFFE8347019C42
6 changed files with 127 additions and 38 deletions

View file

@ -0,0 +1,78 @@
import apiAuth from "@/lib/apiAuth";
import { auditLog } from "@/lib/audit";
import { AliasEntry, createAliasEntry } from "@/lib/db";
import { generateAliasEmail } from "@/lib/util";
import { NextResponse } from "next/server";
/**
* @swagger
* /api/aliases/temporary:
* post:
* description: Creates a new temporary alias
* requestBody:
* description: The options for the alias
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* label:
* type: string
* example: "reddit"
* style:
* type: string
* enum: [words, random]
* labelAtEnd:
* type: boolean
* security:
* - api_key: []
* responses:
* 200:
* description: The created alias
* content:
* application/json:
* schema:
* type: object
* properties:
* id:
* type: number
* address:
* type: string
* example: "your@primary.email"
* alias:
* type: string
* example: "reddit-whypark@t.amogus.cloud"
* pending:
* type: boolean
* temporary:
* type: boolean
*/
export async function POST(request: Request) {
return await apiAuth(request, async (request, user) => {
const body = await request.json();
if (typeof body.label != "string") return new Response('"label" not provided', { status: 400 });
if (!body.label.length || body.label.length > 16) return new Response('"label" must be between 1 and 16 characters long', { status: 400 });
if (typeof body.style != "string") return new Response('"style" not provided', { status: 400 });
if (typeof body.labelAtEnd != 'boolean') return new Response('"labelAtEnd" not provided', { status: 400 });
const email = await generateAliasEmail(body.label, body.style, body.labelAtEnd);
const id = await createAliasEntry(
user,
email,
false,
true,
);
const alias: AliasEntry = {
id: id,
address: user,
alias: email,
pending: false,
temporary: true,
};
auditLog("createTempAlias", alias);
return NextResponse.json(alias);
});
}

View file

@ -1,15 +0,0 @@
/**
* @swagger
* /api/test:
* get:
* description: Returns the hello world
* responses:
* 200:
* description: Hello World!
*/
export async function GET(_request: Request) {
// Do whatever you want
return new Response('Hello World!', {
status: 200,
});
}

View file

@ -3,11 +3,9 @@
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, ApiKeyEntry, approveAliasEntry, createAliasEntry, createApiKeyEntry, createTempAliasRequestEntry, createUserEntry, database, deleteAliasEntry, deleteApiKey, deleteTempAliasRequestEntry, getAlias, getAllAliases, getApiKeyById, getTempAliasRequestEntry, getUserAliases, getUserApiKeys, setUserPassword } from "./db";
import { aliasesNeedApproval, anonymizeApiKey, isAdmin } from "./util";
import { TEMP_EMAIL_DOMAIN } from "./constants";
import { aliasesNeedApproval, anonymizeApiKey, generateAliasEmail, isAdmin } from "./util";
export async function fetchAllUsers(): Promise<string[]> {
return new Promise(async (resolve, reject) => {
@ -135,24 +133,7 @@ export async function requestTemporaryAlias(
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));
let email = await generateAliasEmail(label, style, labelAtEnd);
const request: AliasRequestEntry = {
key: crypto.randomBytes(12).toString('base64'),

19
src/lib/apiAuth.ts Normal file
View file

@ -0,0 +1,19 @@
import { getApiKeyByToken } from "./db";
// Returns the user's email address or throws an error if unauthenticated
export default async function apiAuth(request: Request, callback: (request: Request, user: string) => Response | Promise<Response>) {
let authToken = request.headers.get("authorization");
if (!authToken) return new Response("Unauthorized", { status: 401 });
const token = authToken.replace(/^Bearer /, ""); // the annoying prefix should be optional
const key = await getApiKeyByToken(token);
if (!key) return new Response("Unauthorized", { status: 401 });
const res = callback(request, key.address);;
if (res instanceof Promise) {
return await res;
} else {
return res;
}
};

View file

@ -11,10 +11,10 @@ export const getApiDocs = async () => {
},
components: {
securitySchemes: {
BearerAuth: {
api_key: {
type: 'http',
scheme: 'bearer',
bearerFormat: 'JWT',
name: 'API key',
},
},
},

View file

@ -1,6 +1,9 @@
import { Session } from 'next-auth';
import crypto from 'crypto';
import { ApiKeyEntry } from './db';
import * as random_words from "random-words";
import { TEMP_EMAIL_DOMAIN } from './constants';
import { aliasAvailable } from './actions';
export function sha256sum(input: any) {
const hash = crypto.createHash('sha256');
@ -23,3 +26,26 @@ export function anonymizeApiKey(key: ApiKeyEntry): ApiKeyEntry {
token: key.token.substring(0, 6) + "********",
};
}
export async function generateAliasEmail(label: string, style: 'words' | 'random', labelAtEnd: boolean): Promise<string> {
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));
return email;
}