Added impersonation for admins

This commit is contained in:
Tom
2024-01-04 21:57:32 +00:00
parent 320c826684
commit 8f7f18e069
25 changed files with 494 additions and 131 deletions

View File

@ -0,0 +1,91 @@
import { db } from "@/lib/db"
import { NextResponse } from "next/server";
import fetchUser from "@/lib/fetch-user";
export async function GET(req: Request) {
try {
const user = await fetchUser(req)
if (!user || user.role != "ADMIN") {
return new NextResponse("Unauthorized", { status: 401 });
}
const impersonation = await db.impersonation.findFirst({
where: {
sourceId: user.id
}
});
return NextResponse.json(impersonation);
} catch (error) {
console.log("[AUTH/ACCOUNT/IMPERSONATION]", error);
return new NextResponse("Internal Error", { status: 500 });
}
}
export async function POST(req: Request) {
try {
const user = await fetchUser(req)
if (!user || user.role != "ADMIN") {
return new NextResponse("Unauthorized", { status: 401 });
}
const { targetId } = await req.json();
const impersonation = await db.impersonation.create({
data: {
sourceId: user.id,
targetId
}
});
return NextResponse.json(impersonation);
} catch (error) {
console.log("[AUTH/ACCOUNT/IMPERSONATION]", error);
return new NextResponse("Internal Error", { status: 500 });
}
}
export async function PUT(req: Request) {
try {
const user = await fetchUser(req)
if (!user || user.role != "ADMIN") {
return new NextResponse("Unauthorized", { status: 401 });
}
const { targetId } = await req.json();
const impersonation = await db.impersonation.update({
where: {
sourceId: user.id,
},
data: {
targetId
}
});
return NextResponse.json(impersonation);
} catch (error) {
console.log("[AUTH/ACCOUNT/IMPERSONATION]", error);
return new NextResponse("Internal Error", { status: 500 });
}
}
export async function DELETE(req: Request) {
try {
const user = await fetchUser(req)
if (!user || user.role != "ADMIN") {
return new NextResponse("Unauthorized", { status: 401 });
}
const impersonation = await db.impersonation.delete({
where: {
sourceId: user.id
}
});
return NextResponse.json(impersonation)
} catch (error) {
console.log("[AUTH/ACCOUNT/IMPERSONATION]", error);
return new NextResponse("Internal Error" + error, { status: 500 });
}
}

View File

@ -1,12 +1,12 @@
import { db } from "@/lib/db"
import { NextResponse } from "next/server";
import { auth } from "@/auth";
import fetchUserUsingAPI from "@/lib/validate-api";
import fetchUser from "@/lib/fetch-user";
export async function GET(req: Request) {
try {
return NextResponse.json(await fetchUserUsingAPI(req))
return NextResponse.json(await fetchUser(req))
} catch (error) {
console.log("[ACCOUNT]", error);
return new NextResponse("Internal Error", { status: 500 });

View File

@ -1,10 +1,10 @@
import { db } from "@/lib/db"
import fetchUserUsingAPI from "@/lib/validate-api";
import fetchUserWithImpersonation from "@/lib/fetch-user-impersonation";
import { NextResponse } from "next/server";
export async function POST(req: Request) {
try {
const user = await fetchUserUsingAPI(req)
const user = await fetchUserWithImpersonation(req)
if (!user) {
return new NextResponse("Unauthorized", { status: 401 });
}

View File

@ -1,10 +1,10 @@
import { db } from "@/lib/db"
import { NextResponse } from "next/server";
import fetchUserUsingAPI from "@/lib/validate-api";
import fetchUserWithImpersonation from "@/lib/fetch-user-impersonation";
export async function GET(req: Request) {
try {
const user = await fetchUserUsingAPI(req)
const user = await fetchUserWithImpersonation(req)
if (!user) {
return new NextResponse("Unauthorized", { status: 401 });
}

View File

@ -1,11 +1,11 @@
import { db } from "@/lib/db"
import { NextResponse } from "next/server";
import fetchUserUsingAPI from "@/lib/validate-api";
import fetchUserWithImpersonation from "@/lib/fetch-user-impersonation";
import voices from "@/data/tts";
export async function GET(req: Request) {
try {
const user = await fetchUserUsingAPI(req)
const user = await fetchUserWithImpersonation(req)
if (!user) {
return new NextResponse("Unauthorized", { status: 401 });
}
@ -26,7 +26,7 @@ export async function GET(req: Request) {
export async function POST(req: Request) {
try {
const user = await fetchUserUsingAPI(req)
const user = await fetchUserWithImpersonation(req)
if (!user) {
return new NextResponse("Unauthorized", { status: 401 });
}

View File

@ -1,10 +1,10 @@
import { db } from "@/lib/db"
import { NextResponse } from "next/server";
import fetchUserUsingAPI from "@/lib/validate-api";
import fetchUserWithImpersonation from "@/lib/fetch-user-impersonation";
export async function GET(req: Request) {
try {
const user = await fetchUserUsingAPI(req)
const user = await fetchUserWithImpersonation(req)
if (!user) {
return new NextResponse("Unauthorized", { status: 401 });
}
@ -24,7 +24,7 @@ export async function GET(req: Request) {
export async function POST(req: Request) {
try {
const user = await fetchUserUsingAPI(req)
const user = await fetchUserWithImpersonation(req)
if (!user) {
return new NextResponse("Unauthorized", { status: 401 });
}
@ -57,7 +57,7 @@ export async function POST(req: Request) {
export async function DELETE(req: Request) {
try {
const user = await fetchUserUsingAPI(req)
const user = await fetchUserWithImpersonation(req)
if (!user) {
return new NextResponse("Unauthorized", { status: 401 });
}

View File

@ -1,10 +1,10 @@
import { db } from "@/lib/db"
import { NextResponse } from "next/server";
import fetchUserUsingAPI from "@/lib/validate-api";
import fetchUserWithImpersonation from "@/lib/fetch-user-impersonation";
export async function GET(req: Request) {
try {
const user = await fetchUserUsingAPI(req)
const user = await fetchUserWithImpersonation(req)
if (!user) {
return new NextResponse("Unauthorized", { status: 401 });
}
@ -24,7 +24,7 @@ export async function GET(req: Request) {
export async function POST(req: Request) {
try {
const user = await fetchUserUsingAPI(req)
const user = await fetchUserWithImpersonation(req)
if (!user) {
return new NextResponse("Unauthorized", { status: 401 });
}
@ -35,7 +35,7 @@ export async function POST(req: Request) {
data: {
search,
replace,
userId: user.id as string
userId: user.id
}
});
@ -48,7 +48,7 @@ export async function POST(req: Request) {
export async function PUT(req: Request) {
try {
const user = await fetchUserUsingAPI(req)
const user = await fetchUserWithImpersonation(req)
if (!user) {
return new NextResponse("Unauthorized", { status: 401 });
}
@ -61,7 +61,8 @@ export async function PUT(req: Request) {
},
data: {
search,
replace
replace,
userId: user.id
}
});
@ -74,7 +75,7 @@ export async function PUT(req: Request) {
export async function DELETE(req: Request) {
try {
const user = await fetchUserUsingAPI(req)
const user = await fetchUserWithImpersonation(req)
if (!user) {
return new NextResponse("Unauthorized", { status: 401 });
}
@ -87,7 +88,7 @@ export async function DELETE(req: Request) {
const filter = await db.ttsWordFilter.delete({
where: {
userId_search: {
userId: user.id as string,
userId: user.id,
search
}
}

View File

@ -1,11 +1,11 @@
import { db } from "@/lib/db"
import { NextResponse } from "next/server";
import fetchUserUsingAPI from "@/lib/validate-api";
import fetchUserWithImpersonation from "@/lib/fetch-user-impersonation";
import voices from "@/data/tts";
export async function GET(req: Request) {
try {
const user = await fetchUserUsingAPI(req)
const user = await fetchUserWithImpersonation(req)
if (!user) {
return new NextResponse("Unauthorized", { status: 401 });
}
@ -40,7 +40,7 @@ export async function GET(req: Request) {
export async function POST(req: Request) {
try {
const user = await fetchUserUsingAPI(req)
const user = await fetchUserWithImpersonation(req)
if (!user) {
return new NextResponse("Unauthorized", { status: 401 });
}

View File

@ -1,12 +1,17 @@
import { db } from "@/lib/db"
import fetchUserUsingAPI from "@/lib/validate-api";
import fetchUserWithImpersonation from "@/lib/fetch-user-impersonation";
import { NextResponse } from "next/server";
export async function GET(req: Request, { params } : { params: { id: string } }) {
try {
const user = await fetchUserWithImpersonation(req)
if (!user) {
return new NextResponse("Unauthorized", { status: 401 });
}
let id = req.headers?.get('x-api-key')
if (id == null) {
return NextResponse.json(null);
return NextResponse.json(null);
}
const tokens = await db.apiKey.findFirst({
@ -18,15 +23,19 @@ export async function GET(req: Request, { params } : { params: { id: string } })
return NextResponse.json(tokens);
} catch (error) {
console.log("[TOKEN/GET]", error);
return new NextResponse("Internal Error", { status: 500});
return new NextResponse("Internal Error", { status: 500 });
}
}
export async function DELETE(req: Request, { params } : { params: { id: string } }) {
try {
const { id } = params
const user = await fetchUserUsingAPI(req)
const user = await fetchUserWithImpersonation(req)
if (!user) {
return new NextResponse("Unauthorized", { status: 401 });
}
const { id } = params
const token = await db.apiKey.delete({
where: {
id,
@ -37,6 +46,6 @@ export async function DELETE(req: Request, { params } : { params: { id: string }
return NextResponse.json(token);
} catch (error) {
console.log("[TOKEN/DELETE]", error);
return new NextResponse("Internal Error", { status: 500});
return new NextResponse("Internal Error", { status: 500 });
}
}

View File

@ -1,10 +1,10 @@
import { db } from "@/lib/db"
import fetchUserUsingAPI from "@/lib/validate-api";
import fetchUserWithImpersonation from "@/lib/fetch-user-impersonation";
import { NextResponse } from "next/server";
export async function GET(req: Request) {
try {
const user = await fetchUserUsingAPI(req);
const user = await fetchUserWithImpersonation(req);
if (!user) {
return new NextResponse("Unauthorized", { status: 401 });
}

View File

@ -1,13 +1,18 @@
import fetchUserUsingAPI from "@/lib/validate-api";
import fetchUserWithImpersonation from "@/lib/fetch-user-impersonation";
import { db } from "@/lib/db"
import { NextResponse } from "next/server";
export async function POST(req: Request) {
try {
const user = await fetchUserWithImpersonation(req)
if (!user) {
return new NextResponse("Unauthorized", { status: 401 });
}
let { userId, label } = await req.json();
if (userId == null) {
const user = await fetchUserUsingAPI(req);
const user = await fetchUserWithImpersonation(req);
if (user != null) {
userId = user.id;
}
@ -31,9 +36,13 @@ export async function POST(req: Request) {
export async function DELETE(req: Request) {
try {
const user = await fetchUserWithImpersonation(req)
if (!user) {
return new NextResponse("Unauthorized", { status: 401 });
}
let { id } = await req.json();
const user = await fetchUserUsingAPI(req);
if (!id || !user) {
if (!id) {
return NextResponse.json(null)
}

View File

@ -1,4 +1,4 @@
import fetchUserUsingAPI from "@/lib/validate-api";
import fetchUser from "@/lib/fetch-user";
import { db } from "@/lib/db"
import { NextResponse } from "next/server";
@ -8,7 +8,7 @@ export async function GET(req: Request) {
let userId = searchParams.get('userId')
if (userId == null) {
const user = await fetchUserUsingAPI(req);
const user = await fetchUser(req);
if (user != null) {
userId = user.id as string;
}

41
app/api/users/route.ts Normal file
View File

@ -0,0 +1,41 @@
import { db } from "@/lib/db"
import { NextResponse } from "next/server";
import fetchUser from "@/lib/fetch-user";
export async function GET(req: Request) {
try {
const user = await fetchUser(req)
if (!user || user.role != "ADMIN") {
return new NextResponse("Unauthorized", { status: 401 });
}
const { searchParams } = new URL(req.url)
const qn = searchParams.get('qn') as string
const id = searchParams.get('id') as string
if (qn) {
const users = await db.user.findMany({
where: {
name: {
contains: qn
}
}
})
return NextResponse.json(users)
}
if (id) {
const users = await db.user.findUnique({
where: {
id: id
}
})
return NextResponse.json(users)
}
const users = await db.user.findMany();
return NextResponse.json(users)
} catch (error) {
console.log("[AUTH/ACCOUNT/IMPERSONATION]", error);
return new NextResponse("Internal Error", { status: 500 });
}
}

View File

@ -20,7 +20,6 @@ const SettingsPage = () => {
try {
const keys = (await axios.get("/api/tokens")).data ?? {};
setApiKeys(keys)
console.log(keys);
} catch (error) {
console.log("ERROR", error)
}
@ -49,20 +48,6 @@ const SettingsPage = () => {
}
}
useEffect(() => {
const fetchData = async () => {
try {
const keys = (await axios.get("/api/tokens")).data;
setApiKeys(keys)
console.log(keys);
} catch (error) {
console.log("ERROR", error)
}
};
fetchData().catch(console.error);
}, [apiKeyViewable]);
return (
<div>
<div className="px-10 py-5 mx-5 my-10">

View File

@ -13,8 +13,7 @@ import { useForm } from "react-hook-form";
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
import * as z from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
import { DropdownMenu, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger } from "@/components/ui/dropdown-menu";
import { DropdownMenuContent, DropdownMenuTrigger } from "@radix-ui/react-dropdown-menu";
import { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
import { Label } from "@/components/ui/label";
@ -58,7 +57,6 @@ const TTSFiltersPage = () => {
try {
const replacementData = await axios.get("/api/settings/tts/filter/words")
console.log(replacementData.data)
setReplacements(replacementData.data ?? [])
} catch (e) {
console.log("ERROR", e)
@ -112,8 +110,9 @@ const TTSFiltersPage = () => {
const onReplaceAdd = async () => {
await axios.post("/api/settings/tts/filter/words", { search, replace })
.then(d => {
replacements.push(d.data)
replacements.push({ id: d.data.id, search: d.data.search, replace: d.data.replace, userId: d.data.userId })
setReplacements(replacements)
setSearch("")
}).catch(e => {
// TODO: handle already exist.
console.log("LOGGED:", e)
@ -135,7 +134,9 @@ const TTSFiltersPage = () => {
const onReplaceDelete = async (id: string) => {
await axios.delete("/api/settings/tts/filter/words?id=" + id)
.then(d => {
setReplacements(replacements.filter(r => r.id != id))
const r = replacements.filter(r => r.id != d.data.id)
setReplacements(r)
console.log(r)
}).catch(e => {
// TODO: handle does not exist.
console.log("LOGGED:", e)
@ -166,9 +167,9 @@ const TTSFiltersPage = () => {
<div>
<div className="text-2xl text-center pt-[50px]">TTS Filters</div>
<div className="px-10 py-5 w-full h-full flex-grow inset-y-1/2">
<div className="">
<div>
{userTags.map((user, index) => (
<div className="flex w-full items-start justify-between rounded-md border px-4 py-1">
<div key={user.username + "-tags"} className="flex w-full items-start justify-between rounded-md border px-4 py-1">
<p className="text-base font-medium">
<span className="mr-2 rounded-lg bg-primary px-2 py-1 text-xs text-primary-foreground">
{user.tag}
@ -200,7 +201,7 @@ const TTSFiltersPage = () => {
<CommandGroup>
{tags.map((tag) => (
<CommandItem
key={tag}
key={user.username + "-tag"}
value={tag}
onSelect={(value) => {
onAddExtended({ username: userTags[index].username, tag: value}, false)
@ -216,7 +217,7 @@ const TTSFiltersPage = () => {
</DropdownMenuSubContent>
</DropdownMenuSub>
<DropdownMenuSeparator />
<DropdownMenuItem onClick={onDelete} className="text-red-600">
<DropdownMenuItem key={user.username + "-delete"} onClick={onDelete} className="text-red-600">
<Trash className="mr-2 h-4 w-4" />
Delete
</DropdownMenuItem>
@ -235,7 +236,7 @@ const TTSFiltersPage = () => {
control={usernameFilteredForm.control}
name="username"
render={({ field }) => (
<FormItem className="flex-grow">
<FormItem key={"new-username"} className="flex-grow">
<FormControl>
<Input id="username" placeholder="Enter a twitch username" {...field} />
</FormControl>
@ -272,6 +273,7 @@ const TTSFiltersPage = () => {
{tags.map((tag) => (
<CommandItem
value={tag}
key={tag + "-tag"}
onSelect={(value) => {
setTag(value)
setOpen(false)
@ -297,7 +299,7 @@ const TTSFiltersPage = () => {
<p className="text-center text-2xl text-white pt-[80px]">Regex Replacement</p>
<div>
{replacements.map((term: { id: string, search: string, replace: string, userId: string }) => (
<div className="flex flex-row w-full items-start justify-between rounded-lg border px-4 py-3 gap-3 mt-[15px]">
<div key={term.id} className="flex flex-row w-full items-start justify-between rounded-lg border px-4 py-3 gap-3 mt-[15px]">
<Input id="search" placeholder={term.search} className="flex" onChange={e => term.search = e.target.value } defaultValue={term.search} />
<Input id="replace" placeholder={term.replace} className="flex" onChange={e => term.replace = e.target.value } defaultValue={term.replace} />
<Button className="bg-blue-500 hover:bg-blue-600 items-center align-middle" onClick={_ => onReplaceUpdate(term)}>

View File

@ -14,7 +14,7 @@ import { Label } from "@/components/ui/label";
import { Checkbox } from "@/components/ui/checkbox";
import voices from "@/data/tts";
const TTSFiltersPage = () => {
const TTSVoiceFiltersPage = () => {
const { data: session, status } = useSession();
const [loading, setLoading] = useState<boolean>(true)
@ -88,7 +88,7 @@ const TTSFiltersPage = () => {
<CommandGroup>
{voices.map((voice) => (
<CommandItem
key={voice.value}
key={voice.value + "-" + voice.label}
value={voice.value}
onSelect={(currentValue) => {
setValue(Number.parseInt(currentValue))
@ -115,7 +115,7 @@ const TTSFiltersPage = () => {
<p className="text-xl text-center justify-center">Voices Enabled</p>
<div className="grid grid-cols-4 grid-flow-row gap-4 pt-[20px]">
{voices.map((v, i) => (
<div className="h-[30px] row-span-1 col-span-1 align-middle flex items-center justify-center">
<div key={v.label + "-enabled"} className="h-[30px] row-span-1 col-span-1 align-middle flex items-center justify-center">
<Checkbox onClick={() => {
const newVal = enabled ^ (1 << (Number.parseInt(v.value) - 1))
setEnabled(newVal)
@ -132,4 +132,4 @@ const TTSFiltersPage = () => {
);
}
export default TTSFiltersPage;
export default TTSVoiceFiltersPage;