finished access token creation feature
This commit is contained in:
parent
d91ebb3fa2
commit
05563134b4
|
@ -12,6 +12,8 @@ type Props = {
|
|||
export default function NewKeyModal({ onClose }: Props) {
|
||||
const { data } = useSession();
|
||||
|
||||
const [newToken, setNewToken] = useState("");
|
||||
|
||||
const initial = {
|
||||
name: "",
|
||||
expires: 0 as KeyExpiry,
|
||||
|
@ -43,7 +45,7 @@ export default function NewKeyModal({ onClose }: Props) {
|
|||
|
||||
if (response.ok) {
|
||||
toast.success(`Created!`);
|
||||
onClose();
|
||||
setNewToken(data.response);
|
||||
} else toast.error(data.response as string);
|
||||
|
||||
setSubmitLoader(false);
|
||||
|
@ -54,6 +56,31 @@ export default function NewKeyModal({ onClose }: Props) {
|
|||
|
||||
return (
|
||||
<Modal toggleModal={onClose}>
|
||||
{newToken ? (
|
||||
<div className="flex flex-col justify-center space-y-4">
|
||||
<p className="text-xl font-thin">Access Token Created</p>
|
||||
<p>
|
||||
Your new token has been created. Please copy it and store it
|
||||
somewhere safe. You will not be able to see it again.
|
||||
</p>
|
||||
<TextInput
|
||||
spellCheck={false}
|
||||
value={newToken}
|
||||
onChange={() => {}}
|
||||
className="w-full"
|
||||
/>
|
||||
<button
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(newToken);
|
||||
toast.success("Copied to clipboard!");
|
||||
}}
|
||||
className="btn btn-primary w-fit mx-auto"
|
||||
>
|
||||
Copy to Clipboard
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<p className="text-xl font-thin">Create an Access Token</p>
|
||||
|
||||
<div className="divider mb-3 mt-1"></div>
|
||||
|
@ -65,13 +92,13 @@ export default function NewKeyModal({ onClose }: Props) {
|
|||
<TextInput
|
||||
value={key.name}
|
||||
onChange={(e) => setKey({ ...key, name: e.target.value })}
|
||||
placeholder="e.g. For the Mobile App"
|
||||
placeholder="e.g. For the iOS shortcut"
|
||||
className="bg-base-200"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p className="mb-2">Date of Expiry</p>
|
||||
<p className="mb-2">Expires in</p>
|
||||
|
||||
<div className="dropdown dropdown-bottom dropdown-end">
|
||||
<div
|
||||
|
@ -194,6 +221,8 @@ export default function NewKeyModal({ onClose }: Props) {
|
|||
Create Access Token
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ type Props = {
|
|||
onChange: ChangeEventHandler<HTMLInputElement>;
|
||||
onKeyDown?: KeyboardEventHandler<HTMLInputElement> | undefined;
|
||||
className?: string;
|
||||
spellCheck?: boolean;
|
||||
};
|
||||
|
||||
export default function TextInput({
|
||||
|
@ -18,9 +19,11 @@ export default function TextInput({
|
|||
onChange,
|
||||
onKeyDown,
|
||||
className,
|
||||
spellCheck,
|
||||
}: Props) {
|
||||
return (
|
||||
<input
|
||||
spellCheck={spellCheck}
|
||||
autoFocus={autoFocus}
|
||||
type={type ? type : "text"}
|
||||
placeholder={placeholder}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { prisma } from "@/lib/api/db";
|
||||
import { KeyExpiry } from "@/types/global";
|
||||
import bcrypt from "bcrypt";
|
||||
import crypto from "crypto";
|
||||
import { decode, encode, getToken } from "next-auth/jwt";
|
||||
|
||||
export default async function postToken(
|
||||
body: {
|
||||
|
@ -34,44 +34,55 @@ export default async function postToken(
|
|||
};
|
||||
}
|
||||
|
||||
const now = Date.now();
|
||||
let expiryDate = new Date();
|
||||
const oneDayInSeconds = 86400;
|
||||
let expiryDateSecond = 7 * oneDayInSeconds;
|
||||
|
||||
switch (body.expires) {
|
||||
case KeyExpiry.sevenDays:
|
||||
expiryDate.setDate(expiryDate.getDate() + 7);
|
||||
break;
|
||||
case KeyExpiry.oneMonth:
|
||||
if (body.expires === KeyExpiry.oneMonth) {
|
||||
expiryDate.setDate(expiryDate.getDate() + 30);
|
||||
break;
|
||||
case KeyExpiry.twoMonths:
|
||||
expiryDateSecond = 30 * oneDayInSeconds;
|
||||
} else if (body.expires === KeyExpiry.twoMonths) {
|
||||
expiryDate.setDate(expiryDate.getDate() + 60);
|
||||
break;
|
||||
case KeyExpiry.threeMonths:
|
||||
expiryDateSecond = 60 * oneDayInSeconds;
|
||||
} else if (body.expires === KeyExpiry.threeMonths) {
|
||||
expiryDate.setDate(expiryDate.getDate() + 90);
|
||||
break;
|
||||
case KeyExpiry.never:
|
||||
expiryDateSecond = 90 * oneDayInSeconds;
|
||||
} else if (body.expires === KeyExpiry.never) {
|
||||
expiryDate.setDate(expiryDate.getDate() + 73000); // 200 years (not really never)
|
||||
break;
|
||||
default:
|
||||
expiryDateSecond = 73050 * oneDayInSeconds;
|
||||
} else {
|
||||
expiryDate.setDate(expiryDate.getDate() + 7);
|
||||
break;
|
||||
expiryDateSecond = 7 * oneDayInSeconds;
|
||||
}
|
||||
|
||||
const saltRounds = 10;
|
||||
const token = await encode({
|
||||
token: {
|
||||
id: userId,
|
||||
iat: now / 1000,
|
||||
exp: (expiryDate as any) / 1000,
|
||||
jti: crypto.randomUUID(),
|
||||
},
|
||||
maxAge: expiryDateSecond || 604800,
|
||||
secret: process.env.NEXTAUTH_SECRET,
|
||||
});
|
||||
|
||||
const hashedKey = bcrypt.hashSync(crypto.randomUUID(), saltRounds);
|
||||
const tokenBody = await decode({
|
||||
token,
|
||||
secret: process.env.NEXTAUTH_SECRET,
|
||||
});
|
||||
|
||||
const createToken = await prisma.apiKey.create({
|
||||
data: {
|
||||
name: body.name,
|
||||
userId,
|
||||
token: hashedKey,
|
||||
token: tokenBody?.jti as string,
|
||||
expires: expiryDate,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
response: createToken.token,
|
||||
response: token,
|
||||
status: 200,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -23,6 +23,13 @@ export default async function verifyUser({
|
|||
return null;
|
||||
}
|
||||
|
||||
if (token.exp < Date.now() / 1000) {
|
||||
res
|
||||
.status(401)
|
||||
.json({ response: "Your session has expired, please log in again." });
|
||||
return null;
|
||||
}
|
||||
|
||||
const user = await prisma.user.findUnique({
|
||||
where: {
|
||||
id: userId,
|
||||
|
|
|
@ -65,6 +65,7 @@ import ZitadelProvider from "next-auth/providers/zitadel";
|
|||
import ZohoProvider from "next-auth/providers/zoho";
|
||||
import ZoomProvider from "next-auth/providers/zoom";
|
||||
import * as process from "process";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
const emailEnabled =
|
||||
process.env.EMAIL_FROM && process.env.EMAIL_SERVER ? true : false;
|
||||
|
@ -1059,7 +1060,8 @@ if (process.env.NEXT_PUBLIC_ZOOM_ENABLED_ENABLED === "true") {
|
|||
};
|
||||
}
|
||||
|
||||
export const authOptions: AuthOptions = {
|
||||
export default async function auth(req: NextApiRequest, res: NextApiResponse) {
|
||||
return await NextAuth(req, res, {
|
||||
adapter: adapter as Adapter,
|
||||
session: {
|
||||
strategy: "jwt",
|
||||
|
@ -1113,6 +1115,5 @@ export const authOptions: AuthOptions = {
|
|||
return session;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default NextAuth(authOptions);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -16,6 +16,12 @@ export default async function users(req: NextApiRequest, res: NextApiResponse) {
|
|||
return res.status(401).json({ response: "You must be logged in." });
|
||||
}
|
||||
|
||||
if (token.exp < Date.now() / 1000) {
|
||||
return res
|
||||
.status(401)
|
||||
.json({ response: "Your session has expired, please log in again." });
|
||||
}
|
||||
|
||||
if (userId !== Number(req.query.id))
|
||||
return res.status(401).json({ response: "Permission denied." });
|
||||
|
||||
|
|
Ŝarĝante…
Reference in New Issue