mirror of
https://github.com/Nezumi-2711/next-gdrive-index.git
synced 2026-09-22 13:38:38 +00:00
Update protected query for folder and files.
This commit is contained in:
@@ -34,3 +34,7 @@ yarn-error.log*
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
!/src/pages/api/test.ts
|
||||
|
||||
# personal docs
|
||||
/docs
|
||||
+4
-4
@@ -9,12 +9,8 @@
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tailwindcss/typography": "^0.5.9",
|
||||
"@types/mime-types": "^2.1.1",
|
||||
"@types/node": "18.15.11",
|
||||
"@types/react": "18.0.35",
|
||||
"@types/react-dom": "18.0.11",
|
||||
"@types/react-pdf": "^6.2.0",
|
||||
"autoprefixer": "10.4.14",
|
||||
"axios": "^1.3.5",
|
||||
"googleapis": "^118.0.0",
|
||||
@@ -42,6 +38,10 @@
|
||||
"tailwindcss": "3.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/typography": "^0.5.9",
|
||||
"@types/jsonwebtoken": "^9.0.1",
|
||||
"@types/mime-types": "^2.1.1",
|
||||
"@types/node": "18.15.11",
|
||||
"eslint": "8.38.0",
|
||||
"eslint-config-next": "13.3.0",
|
||||
"prettier": "^2.8.7",
|
||||
|
||||
@@ -11,6 +11,8 @@ import rehypePrism from "rehype-prism-plus";
|
||||
import { SpecialComponents } from "react-markdown/lib/ast-to-react";
|
||||
import { NormalComponents } from "react-markdown/lib/complex-types";
|
||||
import config from "@/config/site.config";
|
||||
import useCopyText from "@hooks/useCopyText";
|
||||
import { MdContentCopy } from "react-icons/md";
|
||||
|
||||
type Props = {
|
||||
content: string;
|
||||
@@ -21,7 +23,7 @@ const customComponents: Partial<
|
||||
> = {
|
||||
img({ alt, src, ...props }: any) {
|
||||
return (
|
||||
<div className='flex flex-col gap-0'>
|
||||
<div className='my-4 flex flex-col gap-0'>
|
||||
<img
|
||||
src={src}
|
||||
alt={alt}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
module.exports = {
|
||||
client_id:
|
||||
"740683322791-5glqp7unsscric9g51ccdsctu5nenkgd.apps.googleusercontent.com",
|
||||
redirect_uri: "http://localhost:5000/auth/google/callback",
|
||||
client_secret:
|
||||
"1b9a24512e48c83cf7e520a7b9442298:6b8c0dc1e9cb0c28c673ebb222f9f03f425f8566b0d9ef4a7265582a99e57499b6b215ff7d038a477c2797195a5ccf65",
|
||||
refresh_token:
|
||||
"fd0a8add6d49a1cbba6211eaa1e64e4e:e0a974bf57af247cf147237dea17c38e78fc6aa88e244f57ca3e373974e95dd6120949b9d320bb6c0dfaad026a2fdaac633a5c515683d1cd2eb468e6e97ac0ddf69068a5aa611fc264b9a998e8e85d20ef13a3c9ecfb527df34455f69fa5458e00e598e3441e20f8a5958c27da911a9a",
|
||||
};
|
||||
|
||||
@@ -46,11 +46,12 @@ const config = {
|
||||
// Starting point of the drive
|
||||
// Use 'root' to use My Drive as starting point
|
||||
// Or use folder id to use a specific folder as starting point
|
||||
rootFolder: "1KgPV6QB1GYT8fmn2uTfbtr9rDXqcRR0j",
|
||||
// rootFolder: "1KgPV6QB1GYT8fmn2uTfbtr9rDXqcRR0j",
|
||||
rootFolder: "1p6znx1BKPsqFnyOPw49uhoc8FNglfYnD",
|
||||
// If this set to true, any user can download or view protected files.
|
||||
// If this set to false, only authorized users can download or view protected files.
|
||||
// The authorized users URL will have a token in it that valid for 1 hour.
|
||||
allowDownloadProtectedFiles: true, // If this set to true, any user can download protected files, but can't see the details of the file or folder.
|
||||
allowDownloadProtectedFiles: false, // If this set to true, any user can download protected files, but can't see the details of the file or folder.
|
||||
},
|
||||
/* Config for readme file render */
|
||||
readme: {
|
||||
|
||||
@@ -1,33 +1,82 @@
|
||||
import { ErrorResponse } from "@/types/googleapis";
|
||||
import { ErrorResponse, FileResponse, TFileParent } from "@/types/googleapis";
|
||||
import drive from "@utils/driveClient";
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
import config from "@config/site.config";
|
||||
import { validateProtected } from "@utils/driveHelper";
|
||||
import { ExtendedError } from "@/types/default";
|
||||
|
||||
export default async function handler(
|
||||
request: NextApiRequest,
|
||||
response: NextApiResponse,
|
||||
) {
|
||||
try {
|
||||
const { id } = request.query;
|
||||
const { id, hash } = request.query;
|
||||
const { authorization } = request.headers;
|
||||
const headerHash = authorization?.split(" ")[1] || null;
|
||||
|
||||
const fetchFileMetadata = await drive.files.get({
|
||||
fileId: id as string,
|
||||
fields: "id, name, mimeType, size",
|
||||
fields: "id, name, mimeType, size, exportLinks, parents",
|
||||
});
|
||||
|
||||
if (!config.files.allowDownloadProtectedFiles) {
|
||||
const parentsArray: TFileParent[] = [];
|
||||
|
||||
// Fetch parents
|
||||
if (
|
||||
fetchFileMetadata.data.mimeType === "application/vnd.google-apps.folder"
|
||||
) {
|
||||
parentsArray.push({
|
||||
id: fetchFileMetadata.data.id as string,
|
||||
name: fetchFileMetadata.data.name as string,
|
||||
});
|
||||
}
|
||||
let parents = fetchFileMetadata.data.parents || [];
|
||||
while (parents.length > 0) {
|
||||
const fetchParents = await drive.files.get({
|
||||
fileId: parents[0],
|
||||
fields: "id, name, parents",
|
||||
});
|
||||
if (fetchParents.data.id === config.files.rootFolder) {
|
||||
parentsArray.push({
|
||||
id: fetchParents.data.id as string,
|
||||
name: fetchParents.data.name as string,
|
||||
});
|
||||
break;
|
||||
}
|
||||
parents = fetchParents.data.parents || [];
|
||||
if (!parents.length) break;
|
||||
|
||||
parentsArray.push({
|
||||
id: fetchParents.data.id as string,
|
||||
name: fetchParents.data.name as string,
|
||||
});
|
||||
}
|
||||
|
||||
// Check for password file
|
||||
const validatePassword = await validateProtected(
|
||||
parentsArray[0].id,
|
||||
(headerHash as string) || (hash as string),
|
||||
);
|
||||
if (validatePassword.isProtected && !validatePassword.valid) {
|
||||
return response.status(200).json({
|
||||
success: true,
|
||||
timestamp: new Date().toISOString(),
|
||||
passwordRequired: true,
|
||||
passwordValidated: false,
|
||||
parents: [],
|
||||
file: {},
|
||||
} as FileResponse);
|
||||
}
|
||||
}
|
||||
|
||||
const { name, mimeType, size } = fetchFileMetadata.data;
|
||||
|
||||
if (mimeType === "application/vnd.google-apps.folder") {
|
||||
const payload: ErrorResponse = {
|
||||
success: false,
|
||||
timestamp: new Date().toISOString(),
|
||||
code: 400,
|
||||
errors: {
|
||||
message: "Cannot download folder",
|
||||
reason: "badRequest",
|
||||
},
|
||||
};
|
||||
|
||||
return response.status(400).json(payload);
|
||||
const error = new Error("Folder cannot be downloaded") as ExtendedError;
|
||||
error.cause = "badRequest";
|
||||
error.code = 400;
|
||||
throw error;
|
||||
}
|
||||
|
||||
response.setHeader(
|
||||
@@ -47,25 +96,17 @@ export default async function handler(
|
||||
},
|
||||
);
|
||||
|
||||
streamFile.data.on("error", (error: any) => {
|
||||
throw error;
|
||||
});
|
||||
streamFile.data.on("data", (chunk: Buffer) => {
|
||||
response.write(chunk);
|
||||
});
|
||||
streamFile.data.on("end", () => {
|
||||
response.end();
|
||||
});
|
||||
return response.status(200);
|
||||
return response.send(streamFile.data);
|
||||
} catch (error: any) {
|
||||
if (error satisfies ErrorResponse) {
|
||||
const payload: ErrorResponse = {
|
||||
success: false,
|
||||
timestamp: new Date().toISOString(),
|
||||
code: error.code,
|
||||
code: error.code || 500,
|
||||
errors: {
|
||||
message: error.errors?.[0].message || error.message,
|
||||
reason: error.errors?.[0].reason || "internalError",
|
||||
message:
|
||||
error.errors?.[0].message || error.message || "Unknown error",
|
||||
reason: error.errors?.[0].reason || error.cause || "internalError",
|
||||
},
|
||||
};
|
||||
|
||||
@@ -75,10 +116,10 @@ export default async function handler(
|
||||
const payload: ErrorResponse = {
|
||||
success: false,
|
||||
timestamp: new Date().toISOString(),
|
||||
code: 500,
|
||||
code: error.code || 500,
|
||||
errors: {
|
||||
message: error.message,
|
||||
reason: "internalError",
|
||||
message: error.message || "Unknown error",
|
||||
reason: error.cause || "internalError",
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
TFileParent,
|
||||
} from "@/types/googleapis";
|
||||
import drive from "@/utils/driveClient";
|
||||
import { buildQuery } from "@/utils/driveHelper";
|
||||
import { buildQuery, validateProtected } from "@/utils/driveHelper";
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
import config from "@config/site.config";
|
||||
|
||||
@@ -15,6 +15,8 @@ export default async function handler(
|
||||
) {
|
||||
try {
|
||||
const { id } = request.query;
|
||||
const { authorization } = request.headers;
|
||||
const hash = authorization?.split(" ")[1] || null;
|
||||
|
||||
const parentsArray: TFileParent[] = [];
|
||||
|
||||
@@ -22,7 +24,6 @@ export default async function handler(
|
||||
fileId: id as string,
|
||||
fields:
|
||||
"id, name, mimeType, parents, thumbnailLink, fileExtension, createdTime, modifiedTime, size, imageMediaMetadata, videoMediaMetadata, exportLinks",
|
||||
// "*",
|
||||
});
|
||||
|
||||
// Fetch parents
|
||||
@@ -38,7 +39,13 @@ export default async function handler(
|
||||
fileId: parents[0],
|
||||
fields: "id, name, parents",
|
||||
});
|
||||
if (fetchParents.data.id === config.files.rootFolder) break;
|
||||
if (fetchParents.data.id === config.files.rootFolder) {
|
||||
parentsArray.push({
|
||||
id: fetchParents.data.id as string,
|
||||
name: fetchParents.data.name as string,
|
||||
});
|
||||
break;
|
||||
}
|
||||
parents = fetchParents.data.parents || [];
|
||||
if (!parents.length) break;
|
||||
|
||||
@@ -49,59 +56,19 @@ export default async function handler(
|
||||
}
|
||||
|
||||
// Check for password file
|
||||
// Password checking takes too long.
|
||||
// Try to find a better way or just keep this as it is.
|
||||
const passwordQuery = [
|
||||
"name = '.password'",
|
||||
"'me' in owners",
|
||||
"trashed = false",
|
||||
];
|
||||
const fetchPassword = await drive.files.list({
|
||||
q: passwordQuery.join(" and "),
|
||||
fields: "files(id, name, parents)",
|
||||
pageSize: 1000,
|
||||
});
|
||||
const passwordFile = fetchPassword.data.files?.find((file) => {
|
||||
if (file.parents?.[0] === id) return true;
|
||||
return parentsArray.some((parent) => parent.id === file.parents?.[0]);
|
||||
});
|
||||
|
||||
if (passwordFile) {
|
||||
// const {authorization} = request.headers;
|
||||
// const userPassword = authorization?.split(" ")[1];
|
||||
// For dev purpose, get password from query
|
||||
const userPassword = request.query.password as string;
|
||||
if (!userPassword)
|
||||
return response.status(401).json({
|
||||
success: false,
|
||||
timestamp: new Date().toISOString(),
|
||||
passwordRequired: true,
|
||||
code: 401,
|
||||
errors: {
|
||||
message: "Unauthorized",
|
||||
reason: "passwordRequired",
|
||||
},
|
||||
});
|
||||
|
||||
const getPassword = await drive.files.get(
|
||||
{
|
||||
fileId: passwordFile.id as string,
|
||||
alt: "media",
|
||||
},
|
||||
{ responseType: "text" },
|
||||
);
|
||||
|
||||
if (getPassword.data !== userPassword)
|
||||
return response.status(401).json({
|
||||
success: false,
|
||||
timestamp: new Date().toISOString(),
|
||||
passwordRequired: true,
|
||||
code: 401,
|
||||
errors: {
|
||||
message: "Unauthorized",
|
||||
reason: "passwordWrong",
|
||||
},
|
||||
});
|
||||
const validatePassword = await validateProtected(
|
||||
parentsArray || (id as string),
|
||||
hash as string,
|
||||
);
|
||||
if (validatePassword.isProtected && !validatePassword.valid) {
|
||||
return response.status(200).json({
|
||||
success: true,
|
||||
timestamp: new Date().toISOString(),
|
||||
passwordRequired: true,
|
||||
passwordValidated: false,
|
||||
parents: [],
|
||||
file: {},
|
||||
} as FileResponse);
|
||||
}
|
||||
|
||||
// Check if file is folder
|
||||
@@ -148,8 +115,8 @@ export default async function handler(
|
||||
success: true,
|
||||
timestamp: new Date().toISOString(),
|
||||
parents: parentsArray,
|
||||
passwordRequired: !!passwordFile,
|
||||
passwordValidated: true,
|
||||
passwordRequired: validatePassword.isProtected,
|
||||
passwordValidated: validatePassword.valid,
|
||||
folders,
|
||||
files,
|
||||
nextPageToken: fetchFiles.data.nextPageToken || undefined,
|
||||
@@ -163,8 +130,8 @@ export default async function handler(
|
||||
success: true,
|
||||
timestamp: new Date().toISOString(),
|
||||
parents: parentsArray,
|
||||
passwordRequired: !!passwordFile,
|
||||
passwordValidated: true,
|
||||
passwordRequired: validatePassword.isProtected,
|
||||
passwordValidated: validatePassword.valid,
|
||||
file: fetchFile.data,
|
||||
};
|
||||
|
||||
@@ -174,10 +141,11 @@ export default async function handler(
|
||||
const payload: ErrorResponse = {
|
||||
success: false,
|
||||
timestamp: new Date().toISOString(),
|
||||
code: error.code,
|
||||
code: error.code || 500,
|
||||
errors: {
|
||||
message: error.errors[0].message,
|
||||
reason: error.errors[0].reason,
|
||||
message:
|
||||
error.errors?.[0].message || error.message || "Unknown error",
|
||||
reason: error.errors?.[0].reason || error.cause || "internalError",
|
||||
},
|
||||
};
|
||||
|
||||
@@ -187,10 +155,10 @@ export default async function handler(
|
||||
const payload: ErrorResponse = {
|
||||
success: false,
|
||||
timestamp: new Date().toISOString(),
|
||||
code: 500,
|
||||
code: error.code || 500,
|
||||
errors: {
|
||||
message: error.message,
|
||||
reason: "internalError",
|
||||
message: error.message || "Unknown error",
|
||||
reason: error.cause || "internalError",
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
import { ErrorResponse, TFileParent } from "@/types/googleapis";
|
||||
import { ErrorResponse, FileResponse, TFileParent } from "@/types/googleapis";
|
||||
import drive from "@utils/driveClient";
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
import config from "@config/site.config";
|
||||
import { validateProtected } from "@utils/driveHelper";
|
||||
import { ExtendedError } from "@/types/default";
|
||||
|
||||
export default async function handler(
|
||||
request: NextApiRequest,
|
||||
response: NextApiResponse,
|
||||
) {
|
||||
try {
|
||||
const { id } = request.query;
|
||||
const { id, hash } = request.query;
|
||||
const { authorization } = request.headers;
|
||||
const headerHash = authorization?.split(" ")[1] || null;
|
||||
|
||||
const fetchFileMetadata = await drive.files.get({
|
||||
fileId: id as string,
|
||||
@@ -33,7 +37,13 @@ export default async function handler(
|
||||
fileId: parents[0],
|
||||
fields: "id, name, parents",
|
||||
});
|
||||
if (fetchParents.data.id === config.files.rootFolder) break;
|
||||
if (fetchParents.data.id === config.files.rootFolder) {
|
||||
parentsArray.push({
|
||||
id: fetchParents.data.id as string,
|
||||
name: fetchParents.data.name as string,
|
||||
});
|
||||
break;
|
||||
}
|
||||
parents = fetchParents.data.parents || [];
|
||||
if (!parents.length) break;
|
||||
|
||||
@@ -44,77 +54,29 @@ export default async function handler(
|
||||
}
|
||||
|
||||
// Check for password file
|
||||
// Password checking takes too long.
|
||||
// Try to find a better way or just keep this as it is.
|
||||
const passwordQuery = [
|
||||
"name = '.password'",
|
||||
"'me' in owners",
|
||||
"trashed = false",
|
||||
];
|
||||
const fetchPassword = await drive.files.list({
|
||||
q: passwordQuery.join(" and "),
|
||||
fields: "files(id, name, parents)",
|
||||
pageSize: 1000,
|
||||
});
|
||||
const passwordFile = fetchPassword.data.files?.find((file) => {
|
||||
if (file.parents?.[0] === id) return true;
|
||||
return parentsArray.some((parent) => parent.id === file.parents?.[0]);
|
||||
});
|
||||
|
||||
if (passwordFile) {
|
||||
// const {authorization} = request.headers;
|
||||
// const userPassword = authorization?.split(" ")[1];
|
||||
// For dev purpose, get password from query
|
||||
const userPassword = request.query.password as string;
|
||||
if (!userPassword)
|
||||
return response.status(401).json({
|
||||
success: false,
|
||||
timestamp: new Date().toISOString(),
|
||||
passwordRequired: true,
|
||||
code: 401,
|
||||
errors: {
|
||||
message: "Unauthorized",
|
||||
reason: "passwordRequired",
|
||||
},
|
||||
});
|
||||
|
||||
const getPassword = await drive.files.get(
|
||||
{
|
||||
fileId: passwordFile.id as string,
|
||||
alt: "media",
|
||||
},
|
||||
{ responseType: "text" },
|
||||
);
|
||||
|
||||
if (getPassword.data !== userPassword)
|
||||
return response.status(401).json({
|
||||
success: false,
|
||||
timestamp: new Date().toISOString(),
|
||||
passwordRequired: true,
|
||||
code: 401,
|
||||
errors: {
|
||||
message: "Unauthorized",
|
||||
reason: "passwordWrong",
|
||||
},
|
||||
});
|
||||
const validatePassword = await validateProtected(
|
||||
parentsArray[0].id,
|
||||
(headerHash as string) || (hash as string),
|
||||
);
|
||||
if (validatePassword.isProtected && !validatePassword.valid) {
|
||||
return response.status(200).json({
|
||||
success: true,
|
||||
timestamp: new Date().toISOString(),
|
||||
passwordRequired: true,
|
||||
passwordValidated: false,
|
||||
parents: [],
|
||||
file: {},
|
||||
} as FileResponse);
|
||||
}
|
||||
}
|
||||
|
||||
console.log("Serve file.");
|
||||
const { name, mimeType, size, exportLinks } = fetchFileMetadata.data;
|
||||
const { name, mimeType, size } = fetchFileMetadata.data;
|
||||
|
||||
if (mimeType === "application/vnd.google-apps.folder") {
|
||||
const payload: ErrorResponse = {
|
||||
success: false,
|
||||
timestamp: new Date().toISOString(),
|
||||
code: 400,
|
||||
errors: {
|
||||
message: "Cannot view folder",
|
||||
reason: "badRequest",
|
||||
},
|
||||
};
|
||||
|
||||
return response.status(400).json(payload);
|
||||
const error = new Error("Folder cannot be downloaded") as ExtendedError;
|
||||
error.cause = "badRequest";
|
||||
error.code = 400;
|
||||
throw error;
|
||||
}
|
||||
|
||||
response.setHeader(
|
||||
@@ -134,25 +96,17 @@ export default async function handler(
|
||||
},
|
||||
);
|
||||
|
||||
streamFile.data.on("error", (error: any) => {
|
||||
throw error;
|
||||
});
|
||||
streamFile.data.on("data", (chunk: Buffer) => {
|
||||
response.write(chunk);
|
||||
});
|
||||
streamFile.data.on("end", () => {
|
||||
response.end();
|
||||
});
|
||||
return response.status(200);
|
||||
return response.send(streamFile.data);
|
||||
} catch (error: any) {
|
||||
if (error satisfies ErrorResponse) {
|
||||
const payload: ErrorResponse = {
|
||||
success: false,
|
||||
timestamp: new Date().toISOString(),
|
||||
code: error.code,
|
||||
code: error.code || 500,
|
||||
errors: {
|
||||
message: error.errors?.[0].message || error.message,
|
||||
reason: error.errors?.[0].reason || "internalError",
|
||||
message:
|
||||
error.errors?.[0].message || error.message || "Unknown error",
|
||||
reason: error.errors?.[0].reason || error.cause || "internalError",
|
||||
},
|
||||
};
|
||||
|
||||
@@ -162,10 +116,10 @@ export default async function handler(
|
||||
const payload: ErrorResponse = {
|
||||
success: false,
|
||||
timestamp: new Date().toISOString(),
|
||||
code: 500,
|
||||
code: error.code || 500,
|
||||
errors: {
|
||||
message: error.message,
|
||||
reason: "internalError",
|
||||
message: error.message || "Unknown error",
|
||||
reason: error.cause || "internalError",
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ErrorResponse, FilesResponse } from "@/types/googleapis";
|
||||
import drive from "@/utils/driveClient";
|
||||
import { buildQuery } from "@/utils/driveHelper";
|
||||
import { buildQuery, validateProtected } from "@/utils/driveHelper";
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
import config from "@config/site.config";
|
||||
|
||||
@@ -10,6 +10,27 @@ export default async function handler(
|
||||
) {
|
||||
try {
|
||||
const { pageToken } = request.query;
|
||||
const { authorization } = request.headers;
|
||||
const hash = authorization?.split(" ")[1] || null;
|
||||
|
||||
// Check for password file
|
||||
const validatePassword = await validateProtected(
|
||||
config.files.rootFolder,
|
||||
hash as string,
|
||||
);
|
||||
if (validatePassword.isProtected && !validatePassword.valid) {
|
||||
return response.status(200).json({
|
||||
success: true,
|
||||
timestamp: new Date().toISOString(),
|
||||
passwordRequired: true,
|
||||
passwordValidated: false,
|
||||
parents: [],
|
||||
files: [],
|
||||
folders: [],
|
||||
nextPageToken: undefined,
|
||||
readmeExists: false,
|
||||
});
|
||||
}
|
||||
|
||||
const fetchFiles = await drive.files.list({
|
||||
q: buildQuery({
|
||||
@@ -47,6 +68,8 @@ export default async function handler(
|
||||
const payload: FilesResponse = {
|
||||
success: true,
|
||||
timestamp: new Date().toISOString(),
|
||||
passwordRequired: validatePassword.isProtected,
|
||||
passwordValidated: validatePassword.valid,
|
||||
folders,
|
||||
files,
|
||||
nextPageToken: fetchFiles.data.nextPageToken || undefined,
|
||||
@@ -59,10 +82,11 @@ export default async function handler(
|
||||
const payload: ErrorResponse = {
|
||||
success: false,
|
||||
timestamp: new Date().toISOString(),
|
||||
code: error.code,
|
||||
code: error.code || 500,
|
||||
errors: {
|
||||
message: error.errors[0].message,
|
||||
reason: error.errors[0].reason,
|
||||
message:
|
||||
error.errors?.[0].message || error.message || "Unknown error",
|
||||
reason: error.errors?.[0].reason || error.cause || "internalError",
|
||||
},
|
||||
};
|
||||
|
||||
@@ -72,10 +96,10 @@ export default async function handler(
|
||||
const payload: ErrorResponse = {
|
||||
success: false,
|
||||
timestamp: new Date().toISOString(),
|
||||
code: 500,
|
||||
code: error.code || 500,
|
||||
errors: {
|
||||
message: error.message,
|
||||
reason: "internalError",
|
||||
message: error.message || "Unknown error",
|
||||
reason: error.cause || "internalError",
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -47,24 +47,17 @@ export default async function handler(
|
||||
},
|
||||
);
|
||||
|
||||
streamFile.data.on("error", (error: any) => {
|
||||
throw error;
|
||||
});
|
||||
streamFile.data.on("data", (chunk: Buffer) => {
|
||||
response.write(chunk);
|
||||
});
|
||||
streamFile.data.on("end", () => {
|
||||
response.end();
|
||||
});
|
||||
return response.send(streamFile.data);
|
||||
} catch (error: any) {
|
||||
if (error satisfies ErrorResponse) {
|
||||
const payload: ErrorResponse = {
|
||||
success: false,
|
||||
timestamp: new Date().toISOString(),
|
||||
code: error.code,
|
||||
code: error.code || 500,
|
||||
errors: {
|
||||
message: error.errors[0].message,
|
||||
reason: error.errors[0].reason,
|
||||
message:
|
||||
error.errors?.[0].message || error.message || "Unknown error",
|
||||
reason: error.errors?.[0].reason || error.cause || "internalError",
|
||||
},
|
||||
};
|
||||
|
||||
@@ -74,10 +67,10 @@ export default async function handler(
|
||||
const payload: ErrorResponse = {
|
||||
success: false,
|
||||
timestamp: new Date().toISOString(),
|
||||
code: 500,
|
||||
code: error.code || 500,
|
||||
errors: {
|
||||
message: error.message,
|
||||
reason: "internalError",
|
||||
message: error.message || "Unknown error",
|
||||
reason: error.cause || "internalError",
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import { ErrorResponse, ReadmeResponse } from "@/types/googleapis";
|
||||
import { ErrorResponse } from "@/types/googleapis";
|
||||
import { buildQuery } from "@/utils/driveHelper";
|
||||
import drive from "@/utils/driveClient";
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
import { drive_v3 } from "googleapis";
|
||||
import axios from "axios";
|
||||
|
||||
export default async function handler(
|
||||
request: NextApiRequest,
|
||||
@@ -49,26 +47,17 @@ export default async function handler(
|
||||
},
|
||||
);
|
||||
|
||||
streamFile.data.on("error", (error: any) => {
|
||||
throw error;
|
||||
});
|
||||
streamFile.data.on("data", (chunk: Buffer) => {
|
||||
response.write(chunk);
|
||||
});
|
||||
streamFile.data.on("end", () => {
|
||||
response.end();
|
||||
});
|
||||
|
||||
return response.status(200);
|
||||
return response.send(streamFile.data);
|
||||
} catch (error: any) {
|
||||
if (error satisfies ErrorResponse) {
|
||||
const payload: ErrorResponse = {
|
||||
success: false,
|
||||
timestamp: new Date().toISOString(),
|
||||
code: error.code,
|
||||
code: error.code || 500,
|
||||
errors: {
|
||||
message: error.errors?.[0]?.message || "",
|
||||
reason: error.errors?.[0]?.reason || "",
|
||||
message:
|
||||
error.errors?.[0].message || error.message || "Unknown error",
|
||||
reason: error.errors?.[0].reason || error.cause || "internalError",
|
||||
},
|
||||
};
|
||||
|
||||
@@ -78,10 +67,10 @@ export default async function handler(
|
||||
const payload: ErrorResponse = {
|
||||
success: false,
|
||||
timestamp: new Date().toISOString(),
|
||||
code: 500,
|
||||
code: error.code || 500,
|
||||
errors: {
|
||||
message: error.message,
|
||||
reason: "internalError",
|
||||
message: error.message || "Unknown error",
|
||||
reason: error.cause || "internalError",
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
+8
-11
@@ -1,8 +1,4 @@
|
||||
import {
|
||||
FilesResponse,
|
||||
ErrorResponse,
|
||||
SearchResponse,
|
||||
} from "@/types/googleapis";
|
||||
import { ErrorResponse, SearchResponse } from "@/types/googleapis";
|
||||
import drive from "@/utils/driveClient";
|
||||
import { buildQuery } from "@/utils/driveHelper";
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
@@ -46,10 +42,11 @@ export default async function handler(
|
||||
const payload: ErrorResponse = {
|
||||
success: false,
|
||||
timestamp: new Date().toISOString(),
|
||||
code: error.code,
|
||||
code: error.code || 500,
|
||||
errors: {
|
||||
message: error.errors[0].message,
|
||||
reason: error.errors[0].reason,
|
||||
message:
|
||||
error.errors?.[0].message || error.message || "Unknown error",
|
||||
reason: error.errors?.[0].reason || error.cause || "internalError",
|
||||
},
|
||||
};
|
||||
|
||||
@@ -59,10 +56,10 @@ export default async function handler(
|
||||
const payload: ErrorResponse = {
|
||||
success: false,
|
||||
timestamp: new Date().toISOString(),
|
||||
code: 500,
|
||||
code: error.code || 500,
|
||||
errors: {
|
||||
message: error.message,
|
||||
reason: "internalError",
|
||||
message: error.message || "Unknown error",
|
||||
reason: error.cause || "internalError",
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import { NextApiHandler, NextApiRequest, NextApiResponse } from "next";
|
||||
import drive from "@utils/driveClient";
|
||||
import { decrypt } from "@utils/encryptionHelper";
|
||||
import { createJWTToken, verifyJWTToken } from "@utils/jwtHelper";
|
||||
import { hashToken, verifyHash } from "@utils/hashHelper";
|
||||
import { buildQuery } from "@utils/driveHelper";
|
||||
|
||||
export default async function handler(
|
||||
request: NextApiRequest,
|
||||
response: NextApiResponse,
|
||||
) {
|
||||
const files = await drive.files.list({
|
||||
q: "'1VMU0sQOkuI06icRRJFof-6V-NLyBWlp5' in parents",
|
||||
fields: "files(id, name, mimeType, size)",
|
||||
});
|
||||
const { password } = request.query;
|
||||
return response.status(200).json({
|
||||
hash: hashToken(password as string),
|
||||
file: files.data.files,
|
||||
});
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
import { MdContentCopy } from "react-icons/md";
|
||||
import { useState } from "react";
|
||||
import { generateRandomEncryptionKey } from "@utils/encryptionHelper";
|
||||
import useCopyText from "@hooks/useCopyText";
|
||||
import { toast } from "react-toastify";
|
||||
|
||||
export default function Setup() {
|
||||
const [key, setKey] = useState<string>("");
|
||||
const copyText = useCopyText();
|
||||
|
||||
return (
|
||||
<div className='mx-auto flex max-w-screen-xl flex-col gap-4'>
|
||||
<div className={"card"}>
|
||||
<div className='flex w-full items-center justify-between rounded-lg px-4'>
|
||||
<span className='font-bold'>Generate random encryption key</span>
|
||||
</div>
|
||||
|
||||
<div className={"divider-horizontal"} />
|
||||
|
||||
<p className={"mx-auto max-w-screen-md px-4 py-4 text-center"}>
|
||||
On this page you can generate a random encryption key.
|
||||
<br />
|
||||
This key will be used to encrypt your files.
|
||||
<br />
|
||||
You can also copy the key to your clipboard and save it somewhere
|
||||
safe.
|
||||
</p>
|
||||
|
||||
<div
|
||||
className={"mx-auto flex w-full max-w-screen-md flex-col gap-2 px-4"}
|
||||
>
|
||||
<div className={"flex flex-col gap-2"}>
|
||||
<span className='font-bold'>Encryption key</span>
|
||||
<input
|
||||
type={"text"}
|
||||
disabled
|
||||
className={"pr-4"}
|
||||
value={key}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className={
|
||||
"mx-auto mt-4 flex w-full max-w-md flex-col items-center justify-center gap-2 tablet:flex-row tablet:gap-4"
|
||||
}
|
||||
>
|
||||
<button
|
||||
className={"flex w-full items-center justify-center"}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const generated = generateRandomEncryptionKey()
|
||||
.then((key) => {
|
||||
setKey(key);
|
||||
})
|
||||
.catch((err) => {
|
||||
toast.error("Failed to generate encryption key");
|
||||
console.error(err);
|
||||
});
|
||||
}}
|
||||
>
|
||||
Generate
|
||||
</button>
|
||||
<button
|
||||
className={"flex w-full items-center justify-center"}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
copyText(key);
|
||||
}}
|
||||
>
|
||||
Copy
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
import { MdContentCopy, MdWarning } from "react-icons/md";
|
||||
import { useEffect, useState } from "react";
|
||||
import {
|
||||
createEncryptionKey,
|
||||
generateRandomEncryptionKey,
|
||||
} from "@utils/encryptionHelper";
|
||||
import useCopyText from "@hooks/useCopyText";
|
||||
import { toast } from "react-toastify";
|
||||
import Link from "next/link";
|
||||
import useLocalStorage from "@hooks/useLocalStorage";
|
||||
|
||||
export default function Encryption() {
|
||||
const [settingJson, setSettingJson] = useLocalStorage("tempEncryption", "");
|
||||
const [key, setKey] = useState<string>("");
|
||||
const [allowNext, setAllowNext] = useState<boolean>(false);
|
||||
const copyText = useCopyText();
|
||||
|
||||
useEffect(() => {
|
||||
if (settingJson) {
|
||||
setKey(settingJson);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (key) {
|
||||
setAllowNext(true);
|
||||
} else {
|
||||
setAllowNext(false);
|
||||
}
|
||||
}, [key]);
|
||||
|
||||
return (
|
||||
<div className='mx-auto flex max-w-screen-xl flex-col gap-4'>
|
||||
<div className={"card"}>
|
||||
<div className='flex w-full items-center justify-between rounded-lg px-4'>
|
||||
<span className='font-bold'>Encryption key</span>
|
||||
</div>
|
||||
|
||||
<div className={"divider-horizontal"} />
|
||||
|
||||
<div className={"banner warning"}>
|
||||
<MdWarning className={"h-6 w-6 text-red-500"} />
|
||||
<span>
|
||||
Make sure you don't share your encryption key with anyone.
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<p className={"mx-auto max-w-screen-md px-4 py-4 text-center"}>
|
||||
On this page you can generate a random encryption key, or you can
|
||||
define your own.
|
||||
<br />
|
||||
This key will be used to encrypt your files.
|
||||
<br />
|
||||
<br />
|
||||
You can also copy the key to your clipboard and save it somewhere
|
||||
safe.
|
||||
</p>
|
||||
|
||||
<div
|
||||
className={"mx-auto flex w-full max-w-screen-md flex-col gap-4 px-4"}
|
||||
>
|
||||
<div className={"flex flex-col gap-2"}>
|
||||
<span className='font-bold'>Encryption key</span>
|
||||
<input
|
||||
type={"text"}
|
||||
className={"pr-4"}
|
||||
value={key}
|
||||
onChange={(e) => setKey(e.target.value)}
|
||||
placeholder={"Enter your encryption key here..."}
|
||||
/>
|
||||
</div>
|
||||
<div className={"mx-auto grid w-full max-w-md grid-cols-2 gap-2"}>
|
||||
<button
|
||||
className={"primary flex w-full items-center justify-center"}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const generated = generateRandomEncryptionKey()
|
||||
.then((key) => {
|
||||
setKey(key);
|
||||
})
|
||||
.catch((err) => {
|
||||
toast.error("Failed to generate encryption key");
|
||||
console.error(err);
|
||||
});
|
||||
}}
|
||||
>
|
||||
Generate
|
||||
</button>
|
||||
<button
|
||||
className={"danger flex w-full items-center justify-center"}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
|
||||
setKey("");
|
||||
}}
|
||||
>
|
||||
Reset
|
||||
</button>
|
||||
<button
|
||||
className={
|
||||
"secondary col-span-full flex w-full items-center justify-center"
|
||||
}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
copyText(key);
|
||||
}}
|
||||
>
|
||||
Copy
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={"divider-horizontal"} />
|
||||
|
||||
<div
|
||||
className={
|
||||
"mx-auto flex w-full items-center justify-end gap-2 tablet:gap-4"
|
||||
}
|
||||
>
|
||||
<Link href={"/setup"}>
|
||||
<button className={"secondary"}>Previous Page</button>
|
||||
</Link>
|
||||
<Link
|
||||
href={allowNext ? "/setup/google-cloud" : ""}
|
||||
onClick={async (e) => {
|
||||
if (!allowNext) return;
|
||||
if (!key) return;
|
||||
createEncryptionKey(key).then((encryptionKey) => {
|
||||
setSettingJson(encryptionKey);
|
||||
});
|
||||
}}
|
||||
>
|
||||
<button
|
||||
className={"primary"}
|
||||
disabled={!allowNext}
|
||||
>
|
||||
Next Page
|
||||
</button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -33,6 +33,9 @@
|
||||
.markdown ul {
|
||||
@apply list-disc ml-8 leading-loose;
|
||||
}
|
||||
.markdown ol {
|
||||
@apply list-decimal leading-loose;
|
||||
}
|
||||
|
||||
.markdown table {
|
||||
@apply block w-full overflow-auto border-collapse mb-4 border-spacing-0;
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
export interface ExtendedError extends Error {
|
||||
code?: number;
|
||||
}
|
||||
@@ -96,6 +96,11 @@ export interface ReadmeResponse extends APIResponse {
|
||||
file: TFile | drive_v3.Schema$File;
|
||||
}
|
||||
|
||||
export interface PasswordResponse extends APIResponse {
|
||||
passwordRequired: boolean;
|
||||
passwordValidated: boolean;
|
||||
}
|
||||
|
||||
export interface ErrorResponse extends APIResponse {
|
||||
code: number;
|
||||
errors: {
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
export interface JWTPayload<T> {
|
||||
payload: T;
|
||||
exp: number;
|
||||
iat: number;
|
||||
isExpired: boolean;
|
||||
}
|
||||
export interface ExpiredJWTPayload {
|
||||
iat: number;
|
||||
exp: number;
|
||||
isExpired: boolean;
|
||||
}
|
||||
|
||||
export interface ProtectionPayload {
|
||||
fileId: string;
|
||||
password: string;
|
||||
}
|
||||
+22
-13
@@ -1,29 +1,38 @@
|
||||
import apiConfig from "@config/api.config";
|
||||
import { google, drive_v3 } from "googleapis";
|
||||
import { decrypt } from "@utils/encryptionHelper";
|
||||
|
||||
class DriveClient {
|
||||
private instance: drive_v3.Drive;
|
||||
instance: drive_v3.Drive;
|
||||
private decryptedSecret: string = decrypt(
|
||||
apiConfig.client_secret,
|
||||
process.env.ENCRYPTION_KEY as string,
|
||||
);
|
||||
private decryptedRefreshToken: string = decrypt(
|
||||
apiConfig.refresh_token,
|
||||
process.env.ENCRYPTION_KEY as string,
|
||||
);
|
||||
|
||||
constructor() {
|
||||
const oauth2Client = new google.auth.OAuth2(
|
||||
apiConfig.client_id,
|
||||
process.env.CLIENT_SECRET,
|
||||
apiConfig.redirect_uri,
|
||||
this.decryptedSecret,
|
||||
);
|
||||
oauth2Client.setCredentials({ refresh_token: process.env.REFRESH_TOKEN });
|
||||
oauth2Client.setCredentials({ refresh_token: this.decryptedRefreshToken });
|
||||
this.instance = google.drive({ version: "v3", auth: oauth2Client });
|
||||
}
|
||||
|
||||
getInstance() {
|
||||
if (!this.instance) {
|
||||
const oauth2Client = new google.auth.OAuth2(
|
||||
apiConfig.client_id,
|
||||
process.env.CLIENT_SECRET,
|
||||
apiConfig.redirect_uri,
|
||||
);
|
||||
oauth2Client.setCredentials({ refresh_token: process.env.REFRESH_TOKEN });
|
||||
this.instance = google.drive({ version: "v3", auth: oauth2Client });
|
||||
}
|
||||
// if (!this.instance) {
|
||||
// const oauth2Client = new google.auth.OAuth2(
|
||||
// apiConfig.client_id,
|
||||
// this.decryptedSecret,
|
||||
// );
|
||||
// oauth2Client.setCredentials({
|
||||
// refresh_token: process.env.REFRESH_TOKEN,
|
||||
// });
|
||||
// this.instance = google.drive({ version: "v3", auth: oauth2Client });
|
||||
// }
|
||||
return this.instance;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import drive from "@utils/driveClient";
|
||||
import config from "@config/site.config";
|
||||
import { verifyHash } from "@utils/hashHelper";
|
||||
import { TFileParent } from "@/types/googleapis";
|
||||
|
||||
export function buildQuery({
|
||||
id,
|
||||
@@ -31,7 +33,7 @@ export function buildQuery({
|
||||
return query.join(" and ");
|
||||
}
|
||||
|
||||
export async function checkProtected(id: string) {
|
||||
export async function _checkProtected(id: string) {
|
||||
try {
|
||||
const files = await drive.files.list({
|
||||
q: id ? buildQuery({ id }) : buildQuery({}),
|
||||
@@ -50,7 +52,7 @@ export async function checkProtected(id: string) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function validateFolderPassword(
|
||||
export async function _validateFolderPassword(
|
||||
passwordFileId: string,
|
||||
password: string,
|
||||
) {
|
||||
@@ -68,3 +70,49 @@ export async function validateFolderPassword(
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export async function validateProtected(
|
||||
fileId: string | TFileParent[],
|
||||
passwordHash: string,
|
||||
): Promise<{ isProtected: boolean; valid?: boolean }> {
|
||||
const fetchPassword = await drive.files.list({
|
||||
q: `name = '.password' and 'me' in owners and trashed = false`,
|
||||
fields: "files(id, name, parents)",
|
||||
pageSize: 1000,
|
||||
});
|
||||
let passwordFile;
|
||||
if (typeof fileId === "string") {
|
||||
passwordFile = fetchPassword.data.files?.find(
|
||||
(file) => file.parents?.[0] === fileId,
|
||||
);
|
||||
}
|
||||
if (Array.isArray(fileId)) {
|
||||
const parentsIdMap = fileId.map((parent) => parent.id);
|
||||
passwordFile = fetchPassword.data.files?.find((file) =>
|
||||
parentsIdMap.includes(file.parents?.[0] as string),
|
||||
);
|
||||
}
|
||||
|
||||
console.log(fetchPassword.data.files);
|
||||
|
||||
if (!passwordFile) return { isProtected: false };
|
||||
|
||||
const getPassword = await drive.files.get(
|
||||
{
|
||||
fileId: passwordFile.id as string,
|
||||
alt: "media",
|
||||
},
|
||||
{ responseType: "text" },
|
||||
);
|
||||
|
||||
if (!passwordHash)
|
||||
return {
|
||||
isProtected: true,
|
||||
valid: false,
|
||||
};
|
||||
|
||||
return {
|
||||
isProtected: true,
|
||||
valid: verifyHash(getPassword.data as string, passwordHash),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -10,3 +10,50 @@ export function generateRandomEncryptionKey(): Promise<string> {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function createEncryptionKey(passphrase: string): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const salt = crypto.randomBytes(16); // generate a random salt
|
||||
const iterations = 100000; // number of PBKDF2 iterations
|
||||
const keyLength = 32; // desired key length in bytes
|
||||
|
||||
crypto.pbkdf2(
|
||||
passphrase,
|
||||
salt,
|
||||
iterations,
|
||||
keyLength,
|
||||
"sha256",
|
||||
(err, derivedKey) => {
|
||||
if (err) throw reject(err);
|
||||
const encryptionKey = derivedKey.toString("hex");
|
||||
resolve(encryptionKey);
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export function encrypt(data: string, encryptionKey: string) {
|
||||
const iv = crypto.randomBytes(16);
|
||||
const cipher = crypto.createCipheriv(
|
||||
"aes-256-cbc",
|
||||
Buffer.from(encryptionKey, "hex"),
|
||||
iv,
|
||||
);
|
||||
let encrypted = cipher.update(data);
|
||||
encrypted = Buffer.concat([encrypted, cipher.final()]);
|
||||
return iv.toString("hex") + ":" + encrypted.toString("hex");
|
||||
}
|
||||
|
||||
export function decrypt(encryptedData: string, encryptionKey: string) {
|
||||
const [ivString, encryptedString] = encryptedData.split(":");
|
||||
const iv = Buffer.from(ivString, "hex");
|
||||
const encrypted = Buffer.from(encryptedString, "hex");
|
||||
const decipher = crypto.createDecipheriv(
|
||||
"aes-256-cbc",
|
||||
Buffer.from(encryptionKey, "hex"),
|
||||
iv,
|
||||
);
|
||||
let decrypted = decipher.update(encrypted);
|
||||
decrypted = Buffer.concat([decrypted, decipher.final()]);
|
||||
return decrypted.toString();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import { createHash } from "crypto";
|
||||
|
||||
export function hashToken(text: string): string {
|
||||
return createHash("sha256").update(text).digest("hex");
|
||||
}
|
||||
|
||||
export function verifyHash(text: string, hash: string): boolean {
|
||||
return hashToken(text) === hash;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import JWT from "jsonwebtoken";
|
||||
import { decrypt, encrypt } from "@utils/encryptionHelper";
|
||||
import { ExpiredJWTPayload, JWTPayload } from "@/types/jwt";
|
||||
|
||||
export function createJWTToken(payload: any, expiresIn: string = "3h") {
|
||||
return encrypt(
|
||||
JWT.sign({ payload }, process.env.JWT_KEY as string, { expiresIn }),
|
||||
process.env.ENCRYPTION_KEY as string,
|
||||
);
|
||||
}
|
||||
|
||||
export function verifyJWTToken<T = any>(
|
||||
token: string,
|
||||
): JWTPayload<T> | ExpiredJWTPayload {
|
||||
try {
|
||||
return {
|
||||
...(JWT.verify(
|
||||
decrypt(token, process.env.ENCRYPTION_KEY as string),
|
||||
process.env.JWT_KEY as string,
|
||||
) as JWTPayload<T>),
|
||||
isExpired: false,
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
iat: 0,
|
||||
exp: 0,
|
||||
isExpired: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -233,6 +233,13 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
|
||||
integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==
|
||||
|
||||
"@types/jsonwebtoken@^9.0.1":
|
||||
version "9.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-9.0.1.tgz#29b1369c4774200d6d6f63135bf3d1ba3ef997a4"
|
||||
integrity sha512-c5ltxazpWabia/4UzhIoaDcIza4KViOQhdbjRlfcIGVnsE3c3brkz9Z+F/EeJIECOQP7W7US2hNE930cWWkPiw==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/katex@^0.11.0":
|
||||
version "0.11.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/katex/-/katex-0.11.1.tgz#34de04477dcf79e2ef6c8d23b41a3d81f9ebeaf5"
|
||||
@@ -260,6 +267,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.31.tgz#31b7ca6407128a3d2bbc27fe2d21b345397f6197"
|
||||
integrity sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==
|
||||
|
||||
"@types/node@*":
|
||||
version "18.16.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.16.0.tgz#4668bc392bb6938637b47e98b1f2ed5426f33316"
|
||||
integrity sha512-BsAaKhB+7X+H4GnSjGhJG9Qi8Tw+inU9nJDwmD5CgOmBLEI6ArdhikpLX7DjbjDRDTbqZzU2LSQNZg8WGPiSZQ==
|
||||
|
||||
"@types/node@18.15.11":
|
||||
version "18.15.11"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.15.11.tgz#b3b790f09cb1696cffcec605de025b088fa4225f"
|
||||
@@ -292,14 +304,6 @@
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
|
||||
"@types/react-pdf@^6.2.0":
|
||||
version "6.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-pdf/-/react-pdf-6.2.0.tgz#e99d498f8f76704d15e4553197c89e254cc17278"
|
||||
integrity sha512-OSCYmrfaJvpXkM5V4seUMAhUDOAOqbGQf9kwv14INyTf7AjDs2ukfkkQrLWRQ8OjWrDklbXYWh5l7pT7l0N76g==
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
pdfjs-dist "^2.16.105"
|
||||
|
||||
"@types/react@*", "@types/react@18.0.35":
|
||||
version "18.0.35"
|
||||
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.35.tgz#192061cb1044fe01f2d3a94272cd35dd50502741"
|
||||
@@ -849,11 +853,6 @@ doctrine@^3.0.0:
|
||||
dependencies:
|
||||
esutils "^2.0.2"
|
||||
|
||||
dommatrix@^1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/dommatrix/-/dommatrix-1.0.3.tgz#e7c18e8d6f3abdd1fef3dd4aa74c4d2e620a0525"
|
||||
integrity sha512-l32Xp/TLgWb8ReqbVJAFIvXmY7go4nTxxlWiAFyhoQw9RKEOHBZNnyGvJWqDVSPmq3Y9HlM4npqF/T6VMOXhww==
|
||||
|
||||
ecdsa-sig-formatter@1.0.11, ecdsa-sig-formatter@^1.0.11:
|
||||
version "1.0.11"
|
||||
resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf"
|
||||
@@ -2898,14 +2897,6 @@ path-type@^4.0.0:
|
||||
resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b"
|
||||
integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==
|
||||
|
||||
pdfjs-dist@^2.16.105:
|
||||
version "2.16.105"
|
||||
resolved "https://registry.yarnpkg.com/pdfjs-dist/-/pdfjs-dist-2.16.105.tgz#937b9c4a918f03f3979c88209d84c1ce90122c2a"
|
||||
integrity sha512-J4dn41spsAwUxCpEoVf6GVoz908IAA3mYiLmNxg8J9kfRXc2jxpbUepcP0ocp0alVNLFthTAM8DZ1RaHh8sU0A==
|
||||
dependencies:
|
||||
dommatrix "^1.0.3"
|
||||
web-streams-polyfill "^3.2.1"
|
||||
|
||||
picocolors@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"
|
||||
@@ -3839,11 +3830,6 @@ web-namespaces@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/web-namespaces/-/web-namespaces-2.0.1.tgz#1010ff7c650eccb2592cebeeaf9a1b253fd40692"
|
||||
integrity sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==
|
||||
|
||||
web-streams-polyfill@^3.2.1:
|
||||
version "3.2.1"
|
||||
resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz#71c2718c52b45fd49dbeee88634b3a60ceab42a6"
|
||||
integrity sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==
|
||||
|
||||
webidl-conversions@^3.0.0:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"
|
||||
|
||||
Reference in New Issue
Block a user