diff --git a/server/my_sql/create_tables.sql b/server/my_sql/create_tables.sql index 0aa4227..0577ae7 100644 --- a/server/my_sql/create_tables.sql +++ b/server/my_sql/create_tables.sql @@ -110,19 +110,22 @@ CREATE TABLE users ( user_name TEXT, user_email TEXT, user_type INT, - user_api_keys JSON, user_last_login DATETIME, user_image: TEXT, - user_image: TEXT, user_restricted_access BOOLEAN, PRIMARY KEY (id) ); --- User Auth -CREATE TABLE auth ( - id SERIAL PRIMARY KEY, - auth_entry TEXT, - auth_type TEXT, - auth_nickname TEXT, - auth_permissions TEXT -); \ No newline at end of file +-- Api Keys +CREATE TABLE apikey ( + id INT NOT NULL AUTO_INCREMENT, + user_id INT, + api_key_name TEXT, + api_key_value TEXT, + api_key_last_used DATETIME, + api_key_permissions TEXT, + api_key_logs JSON, + PRIMARY KEY (id), + FOREIGN KEY (user_id) REFERENCES users(id) +); + diff --git a/website/package-lock.json b/website/package-lock.json index dfe75f9..0de1850 100644 --- a/website/package-lock.json +++ b/website/package-lock.json @@ -1343,9 +1343,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001489", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001489.tgz", - "integrity": "sha512-x1mgZEXK8jHIfAxm+xgdpHpk50IN3z3q3zP261/WS+uvePxW8izXuCu6AHz0lkuYTlATDehiZ/tNyYBdSQsOUQ==", + "version": "1.0.30001562", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001562.tgz", + "integrity": "sha512-kfte3Hym//51EdX4239i+Rmp20EsLIYGdPkERegTgU19hQWCRhsRFGKHTliUlsry53tv17K7n077Kqa0WJU4ng==", "funding": [ { "type": "opencollective", diff --git a/website/src/lib/api_tools.ts b/website/src/lib/api_tools.ts index 18784e0..226f073 100644 --- a/website/src/lib/api_tools.ts +++ b/website/src/lib/api_tools.ts @@ -2,17 +2,32 @@ import {NextApiRequest, NextApiResponse} from "next"; import {checkPermissions, getUserPermissions, RongoaUser, UserPermissions} from "@/lib/users"; import axios, {AxiosRequestConfig, AxiosResponse} from "axios"; import {jwtVerify, SignJWT} from "jose"; +import {getFromCache, saveToCache} from "@/lib/cache"; - -export async function checkApiPermissions(request: NextApiRequest, response: NextApiResponse, session: any, client: any, permission: string) { +export async function checkApiPermissions(request: NextApiRequest, response: NextApiResponse, session: any, client: any, makeQuery: any, permission: string) { let permissions : UserPermissions | null = null + let api_key_data = null + // Check if there is an API key let {api_key} = request.query; if(api_key){ - // TODO: Get the api key from the database and check its permissions + console.log("API key: " + api_key) + + // Make the query + const query = `SELECT api_key_permissions, api_key_logs FROM apikey WHERE api_key_value = '${api_key}'` + const result = await makeQuery(query, client) + + // Check if the API key exists + if(result.length == 0) return false + + // Get the permissions + permissions = JSON.parse(result[0].api_key_permissions) + + // Get the api key data + api_key_data = result[0] }else{ @@ -57,6 +72,21 @@ export async function checkApiPermissions(request: NextApiRequest, response: Nex // Get the permissions of the user const isAllowed = checkPermissions(permissions, permissionToCheck) console.log(permissionToCheck + ": " + isAllowed) + + // If the api key was used then store the action in the log + if(api_key && api_key_data) { + + // Parse the log + let log = JSON.parse(api_key_data.api_key_logs) + + // Add the action to the log + log.push({time: new Date().toISOString(), action: "Attempt to access " + permissionToCheck + " on " + request.url + ": " + (isAllowed ? "Allowed" : "Denied")}) + + // Update the log + const query = `UPDATE apikey SET api_key_logs = '${JSON.stringify(log)}', api_key_last_used = NOW() WHERE api_key_value = '${api_key}'` + await makeQuery(query, client) + } + return isAllowed } @@ -120,4 +150,19 @@ export async function makeRequestWithToken ( console.error('Request failed:', error.message); throw error; } -}; \ No newline at end of file +}; + + +export async function makeCachedRequest(key: string, url: string){ + + let cache = getFromCache(key) + if(cache){ + return cache + } + cache = await makeRequestWithToken("get",url) + if(!cache.data.error){ + + saveToCache(key, cache.data.data) + } + return cache.data.data +} \ No newline at end of file diff --git a/website/src/lib/databse.ts b/website/src/lib/databse.ts index f92eb49..59e649e 100644 --- a/website/src/lib/databse.ts +++ b/website/src/lib/databse.ts @@ -78,12 +78,13 @@ export class SQLDatabase { user_image: string; user_restricted_access: string; - - // Auth Table - auth_entry: string; - auth_type: string; - auth_nickname: string; - auth_permissions: string; + // Api Keys Table + user_id: string; + api_key_name: string; + api_key_value: string; + api_key_last_used: string; + api_key_permissions: string; + api_key_logs: string; constructor() { this.database = "rongoa8jwons3_rongoadb" @@ -155,13 +156,13 @@ export class SQLDatabase { this.user_image = "user_image"; this.user_restricted_access = "user_restricted_access"; - - // Auth Table - this.auth_entry = "auth_entry"; - this.auth_type = "auth_type"; - this.auth_nickname = "auth_nickname"; - this.auth_permissions = "auth_permissions"; - + // Api Keys Table + this.user_id = "user_id"; + this.api_key_name = "api_key_name"; + this.api_key_value = "api_key_value"; + this.api_key_last_used = "api_key_last_used"; + this.api_key_permissions = "api_key_permissions"; + this.api_key_logs = "api_key_logs"; } } @@ -225,12 +226,6 @@ export class PostgresSQL extends SQLDatabase{ this.months_event = "event"; this.months_start_month = "start_month"; this.months_end_month = "end_month"; - - // Auth Table - this.auth_entry = "entry"; - this.auth_type = "type"; - this.auth_nickname = "nickname"; - this.auth_permissions = "permissions"; } } diff --git a/website/src/lib/plant_data.ts b/website/src/lib/plant_data.ts index dc7a7a4..bf64657 100644 --- a/website/src/lib/plant_data.ts +++ b/website/src/lib/plant_data.ts @@ -795,7 +795,7 @@ export async function fetchPlant (id: number) { console.log(e) continue } - const authorsData = authorData.data.user + const authorsData = authorData.data.data if(authorsData){ authors.push(authorsData as UserDatabaseDetails) } @@ -1111,6 +1111,9 @@ export function dateToString(date: Date | string): string return "Invalid Date" } + // Add an hour to the date because of timezone issues + date.setHours(date.getHours() + 1); + dateString = date.toISOString(); } diff --git a/website/src/lib/users.ts b/website/src/lib/users.ts index 19757ac..2d3fdf0 100644 --- a/website/src/lib/users.ts +++ b/website/src/lib/users.ts @@ -19,11 +19,8 @@ export interface UserDatabaseDetails { user_email: string, user_type: number, user_last_login: string, - user_api_keys: object, user_image: string, user_restricted_access: boolean, - - } export interface UserPermissions { @@ -96,6 +93,7 @@ export interface UserPermissions { add: boolean; remove: boolean; edit: boolean; + fetch: boolean; }; data: { publicAccess: boolean; @@ -144,9 +142,8 @@ export interface UserPermissions { } } -export function getUserPermissions(user: RongoaUser | null) { - - let permissions : UserPermissions = { +export const getDefaultPermissions = () : UserPermissions => { + return { api: { auth: { edit_auth: { @@ -221,11 +218,12 @@ export function getUserPermissions(user: RongoaUser | null) { user: { api_keys: { - publicAccess: true, + publicAccess: false, internalAccess: true, add: true, remove: true, edit: true, + fetch: true, }, data: { @@ -271,7 +269,7 @@ export function getUserPermissions(user: RongoaUser | null) { }, }, - data:{ + data: { account: { viewPrivateDetails: false, }, @@ -281,6 +279,11 @@ export function getUserPermissions(user: RongoaUser | null) { }, } } +} + +export function getUserPermissions(user: RongoaUser | null) { + + let permissions : UserPermissions = getDefaultPermissions(); // If there is no user logged in it must be a guest if(user == null) @@ -291,7 +294,6 @@ export function getUserPermissions(user: RongoaUser | null) { // Check if they are allowed to view restricted data permissions.data.plants.viewRestrictedSections = user.database.user_restricted_access; - console.log("User is allowed to view restricted data: " + permissions.data.plants.viewRestrictedSections); // If they are a member allow them to use parts of the api non-internally if(user.database.user_type >= MEMBER_USER_TYPE) { @@ -393,4 +395,39 @@ export function checkPermissions(permissions: UserPermissions, permission: strin } +export function getStrings(permissions: UserPermissions) { + + let strings: string[] = [] + + // Loop through the permissions + for (let key in permissions) { + + if(!permissions.hasOwnProperty(key)) continue; + + // @ts-ignore + let value = permissions[key] as any + // If the value is an object, then loop through it + if (typeof value === "object") { + + // Add the sub values to the strings + let subStrings = getStrings(value) + for (let subString of subStrings) { + strings.push(key + ":" + subString) + } + + } else { + + // Check if the permission is true + if (value === true && key !== "internalAccess") { + if (key === "publicAccess") { + strings.push("access") + }else{ + strings.push(key) + } + + } + } + } + return strings +} diff --git a/website/src/pages/account/edit.tsx b/website/src/pages/account/edit.tsx index d1f6363..0a0a93a 100644 --- a/website/src/pages/account/edit.tsx +++ b/website/src/pages/account/edit.tsx @@ -5,9 +5,7 @@ import Section from "@/components/section"; import Footer from "@/components/footer"; import PageHeader from "@/components/page_header"; import styles from "@/styles/pages/account/index.module.css" -import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; -import {faPerson} from "@fortawesome/free-solid-svg-icons"; -import {signIn, useSession} from "next-auth/react"; +import {useSession} from "next-auth/react"; import {ADMIN_USER_TYPE, EDITOR_USER_TYPE, MEMBER_USER_TYPE, RongoaUser, UserDatabaseDetails} from "@/lib/users"; import {globalStyles} from "@/lib/global_css"; import {useRouter} from "next/router"; @@ -16,6 +14,7 @@ import {FileInput, SmallInput, ValidationState} from "@/components/input_section import {dateToString} from "@/lib/plant_data"; import {Loading} from "@/components/loading"; import {makeRequestWithToken} from "@/lib/api_tools"; +import {loginSection} from "@/pages/account/index"; export default function EditAccount() { const pageName = "Account"; @@ -122,7 +121,7 @@ export default function EditAccount() { try { const response = await makeRequestWithToken("get","/api/user/email?email=" + userEmail) - if (response.data.user) { + if (response.data.data) { setValidUserEmail(["error", "Email is already in use"]) return false } @@ -222,7 +221,6 @@ export default function EditAccount() { user_name: userName, user_email: userEmail, user_image: userLocalImage ? process.env.NEXT_PUBLIC_FTP_PUBLIC_URL + "/users/" + userID + "/" + userLocalImage?.name : (session?.user as RongoaUser).database.user_image, - user_api_keys: (session?.user as RongoaUser).database.user_api_keys, user_type: (session?.user as RongoaUser).database.user_type, user_last_login: (session?.user as RongoaUser).database.user_last_login, user_restricted_access: (session?.user as RongoaUser).database.user_restricted_access @@ -234,16 +232,6 @@ export default function EditAccount() { await router.push("/account") } - const loginSection = () => { - return ( - <> -
- -
- - ) - } - const editSection = () => { return ( <> diff --git a/website/src/pages/account/index.tsx b/website/src/pages/account/index.tsx index 467eed4..a06f68e 100644 --- a/website/src/pages/account/index.tsx +++ b/website/src/pages/account/index.tsx @@ -13,7 +13,7 @@ import { ADMIN_USER_TYPE, checkUserPermissions, EDITOR_USER_TYPE, - getUserPermissions, + getStrings, MEMBER_USER_TYPE, RongoaUser, UserDatabaseDetails @@ -25,7 +25,8 @@ import {useRouter} from "next/router"; import Link from "next/link"; import {dateToString, getNamesInPreference, macronCodeToChar, numberDictionary, PlantData} from "@/lib/plant_data"; import {Error} from "@/components/error"; -import {makeRequestWithToken} from "@/lib/api_tools"; +import {makeCachedRequest, makeRequestWithToken} from "@/lib/api_tools"; +import {Loading} from "@/components/loading"; export default function Account() { @@ -55,17 +56,21 @@ export function AccountPage({dataID}: AccountPageProps){ const [userPlants, setUserPlants] = React.useState("") const [userPosts, setUserPosts] = React.useState("") const [userPlantsData, setUserPlantsData] = React.useState([]) + const [userPostsData, setUserPostsData] = React.useState([]) + const [userApiKeysData, setUserApiKeysData] = React.useState([]) + const [userApiKeysShown, setUserApiKeysShown] = React.useState([]) const [userID, setUserID] = React.useState(0) - // States + const [loading, setLoading] = React.useState(false) + const [loadingMessage, setLoadingMessage] = React.useState("Loading...") + const[error, setError] = useState("") const [editor, setEditor] = React.useState(false) const [myAccount, setMyAccount] = React.useState(false) const [hidePrivate, setHidePrivate] = React.useState(true) // Don't fetch the data again if it has already been fetched const dataFetch = useRef("-1") - const[error, setError] = useState("") useEffect(() => { @@ -75,8 +80,11 @@ export function AccountPage({dataID}: AccountPageProps){ // Check if it is to be used if(dataID != "0"){ - if(!checkUserPermissions(session?.user as RongoaUser, "pages:account:publicAccess")) + if(!checkUserPermissions(session?.user as RongoaUser, "pages:account:publicAccess")){ setError("You must be logged in to view other users") + setLoading(false) + return + } // Try converting the id to a number let localId = parseInt(dataID as string) @@ -84,6 +92,8 @@ export function AccountPage({dataID}: AccountPageProps){ // If it is not a number then there is a problem if(isNaN(localId)){ console.log("Not a number") + setError("User not found") + setLoading(false) return } @@ -115,11 +125,10 @@ export function AccountPage({dataID}: AccountPageProps){ setHidePrivate(false) let user = session.user as RongoaUser - if(!user) return - - // Log their permissions - console.log("User permissions: ") - console.log(getUserPermissions(user)) + if(!user){ + setLoading(false) + return + } // Load the user data loadUserData(user.database) @@ -170,47 +179,97 @@ export function AccountPage({dataID}: AccountPageProps){ const fetchData = async (localId: number = 0) => { + setLoading(true) + console.log("Fetching data") // If we are viewing a user then we need to get their data if(localId != 0) { try { + setLoadingMessage("Fetching user data...") - const user = await makeRequestWithToken("get", "/api/user/data?id=" + localId) - if(user.data.error){ + // Get the data + const user = await makeCachedRequest("userData_" + localId, "/api/user/data?id=" + localId) + if(!user){ setError("User not found") + setLoading(false) + return } - loadUserData(user.data.user) + loadUserData(user) // Override the editor state setEditor(false) } catch (e) { console.log(e) + setLoading(false) setError("User not found") + return } } // Get the users plants try { - let url = "/api/user/plants" + setLoadingMessage("Fetching plants...") + // Create the url + let url = "/api/user/plants" if(localId != 0) { url += "?id=" + localId } - const plants = await makeRequestWithToken("get",url) - if(!plants.data.error){ - setUserPlants(plants.data.data.length.toString()) + // Get the data + let plants = await makeCachedRequest("userPlantsData_" + localId, url) + + // Update the state variables + if(plants){ + setUserPlants(plants.length.toString()) + setUserPlantsData(plants) + }else{ + setUserPlants("0") + setUserPlantsData([]) } - setUserPlantsData(plants.data.data) + } catch (e) { // User has no plants console.log(e) setUserPlants("0") } + + // Get the users posts + + // Get the users api keys + try { + + setLoadingMessage("Fetching API keys...") + + + // Create the url + let apiUrl = "/api/user/api_keys?operation=fetch" + if(localId != 0 && checkUserPermissions(session?.user as RongoaUser, "data:account:viewPrivateDetails")) { + console.log("Can view private details") + apiUrl += "&publicUserID=" + localId + } + + // Get the data + let apikeys = await makeCachedRequest("userApiKeysData_" + localId, apiUrl) + if(!apikeys) + apikeys = [] + + // Update the state variables + setUserApiKeysData(apikeys) + + } catch (e) { + + // User has no plants + console.log(e) + setUserPlants("0") + } + + console.log("Finished fetching data") + setLoading(false) } const signOutUser = async () => { @@ -222,16 +281,6 @@ export function AccountPage({dataID}: AccountPageProps){ await signOutUser() } - const loginSection = () => { - return ( - <> -
- -
- - ) - } - const accountSection = () => { return ( <> @@ -363,12 +412,13 @@ export function AccountPage({dataID}: AccountPageProps){
- +
- + + @@ -376,11 +426,34 @@ export function AccountPage({dataID}: AccountPageProps){ - - - + {userApiKeysData.length > 0 ? userApiKeysData.map((apiKey: any, index) => ( + + + + + + + + + + {editor && } + + )) : + + }
ID NameKeyValueLast used Permissions Divider View

No API Keys Found

{apiKey.id}{apiKey.api_key_name}{apiKey.api_key_value}{dateToString(new Date(apiKey.api_key_last_used))}{getStrings(JSON.parse(apiKey.api_key_permissions)).join(", ")}Divider + + + + + + + +

No Api Keys Found

+ + {editor && } +
@@ -425,6 +498,9 @@ export function AccountPage({dataID}: AccountPageProps){ + {/* Loading Message */} + {loading && } + {/* Error Message */} {error ? @@ -453,4 +529,14 @@ export function AccountPage({dataID}: AccountPageProps){ ) +} + +export const loginSection = () => { + return ( + <> +
+ +
+ + ) } \ No newline at end of file diff --git a/website/src/pages/account/keys/create.tsx b/website/src/pages/account/keys/create.tsx new file mode 100644 index 0000000..59a118e --- /dev/null +++ b/website/src/pages/account/keys/create.tsx @@ -0,0 +1,224 @@ +import HtmlHeader from "@/components/html_header"; +import Navbar from "@/components/navbar"; +import React, {useEffect} from "react"; +import Section from "@/components/section"; +import Footer from "@/components/footer"; +import PageHeader from "@/components/page_header"; +import styles from "@/styles/pages/account/index.module.css" +import {useSession} from "next-auth/react"; +import {useRouter} from "next/router"; +import {loginSection} from "@/pages/account"; +import {SmallInput, ValidationState} from "@/components/input_sections"; +import {getDefaultPermissions, getStrings, getUserPermissions, RongoaUser, UserPermissions} from "@/lib/users"; +import {makeRequestWithToken} from "@/lib/api_tools"; +import {Loading} from "@/components/loading"; +import {Error} from "@/components/error"; + +export default function Account() { + + return( + + ) + +} + +interface AccountPageProps { + dataID: string | string[] +} + +export function AccountPage({dataID}: AccountPageProps){ + const pageName = "Account"; + + const { data: session } = useSession() + + const router = useRouter() + + // Data states + const [keyName, setKeyName] = React.useState("") + const [keyNameState, setKeyNameState] = React.useState<[ValidationState, string]>(["normal", ""]) + const [keyPermissions, setKeyPermissions] = React.useState([]) + + const [loading, setLoading] = React.useState(false) + const [error, setError] = React.useState("") + + // Check if the user is logged in + useEffect(() => { + if (session?.user) { + generateKeyPermissions() + } + }, [session]) + + + // Generate the key permissions + const generateKeyPermissions = () => { + + const permissions = getUserPermissions(session?.user as RongoaUser) + setKeyPermissions(getStrings(permissions)) + } + + const getPermissions = () => { + + let permissions: UserPermissions = getDefaultPermissions(); + + function updatePermissions(permissions: any, keys: string[], permissionValue: boolean) { + let currentLevel = permissions; + + for (let i = 0; i < keys.length - 1; i++) { + currentLevel = currentLevel[keys[i]] = currentLevel[keys[i]] || {}; + } + + currentLevel[keys[keys.length - 1]] = permissionValue; + } + + // Loop through the permissions + for (let permission of keyPermissions) { + + // Get the value of the permission + let permissionValue = (document.getElementById(permission) as HTMLInputElement)?.checked + + // Check if the permission is undefined or null + if (permissionValue === undefined || permissionValue === null) continue; + + // Get the keys + let keys = permission.split(":") + + // Replace access with publicAccess + for (let i = 0; i < keys.length; i++) { + if (keys[i] === "access") keys[i] = "publicAccess" + } + + // Update the permissions + updatePermissions(permissions, keys, permissionValue) + + } + + console.log(permissions) + return permissions + + } + + const uploadKey = async () => { + + setLoading(true) + + // Check if the key name is valid + if (keyName === "") { + setKeyNameState(["error", "Key name cannot be empty"]) + setLoading(false) + return + }else { + setKeyNameState(["success", ""]) + } + + // Get the permissions + const permissions = getPermissions() + + // Check if there is atleast one permission set to true + let hasPermission = false + for (let permission of keyPermissions) { + + // Get the value of the permission + let permissionValue = (document.getElementById(permission) as HTMLInputElement)?.checked + + if(permissionValue) hasPermission = true + } + + if(!hasPermission){ + setError("You must have atleast one permission set to true") + setLoading(false) + return + } + + // Upload the key + try{ + const response = await makeRequestWithToken("post", "/api/user/api_keys?operation=new&keyName=" + keyName + "&permissions=" + JSON.stringify(permissions)) + } catch (e) { + console.log(e) + setError((e as Error).message) + } + + // Delete the cached user keys + localStorage.removeItem("userApiKeysData_0") + + setLoading(false) + // Go to the account page + router.push("/account/") + + } + + const keysCreator = () => { + return ( + <> +
+ +
+ +
+

Key Name

+ +
+ +
+

Key Permissions

+
+ {keyPermissions.map((permission, index) => { + return ( +
+ {/* Check box for the permission */} + + +
+ ) + })} +
+
+ +
+ +
+ +
+
+ + + ) + } + + + return( + <> + + {/* Set up the page header and navbar */} + + + + + {/* Header for the page */} +
+ +
+

Create New Key

+
+
+
+ + {/* Loading Message */} + {loading && } + + {/* Error Message */} + {error &&
} + + {!session? + loginSection() + : + keysCreator() + } + + {/* Footer */} +
+
+ + + ) +} \ No newline at end of file diff --git a/website/src/pages/api/auth/edit_auth.ts b/website/src/pages/api/auth/edit_auth.ts deleted file mode 100644 index 2fcefd6..0000000 --- a/website/src/pages/api/auth/edit_auth.ts +++ /dev/null @@ -1,88 +0,0 @@ -import {NextApiRequest, NextApiResponse} from 'next'; -import {getClient, getTables, makeQuery} from "@/lib/databse"; - -export default async function handler( - request: NextApiRequest, - response: NextApiResponse, -) { - - - return response.status(200).json({ data: "Under Development" }); - - - - // Get the client - const client = await getClient() - - // Get the tables - const tables = getTables(); - - // Try uploading the data to the database - try { - - - // Get the data from the request - let { - operation, - entry, - type, - nickname, - permissions - } = request.query; - - console.log(operation) - - if(!operation){ - return response.status(400).json({ error: 'Missing operation'}); - } - - // Check if the entry is valid - if(operation != "fetch") - if(!entry || !type || !nickname || !permissions){ - return response.status(400).json({ error: 'Missing entry, type, nickname or permissions', entry: entry, type: type, nickname: nickname, permissions: permissions}); - } - - // Convert entry to string - if(entry) - entry = Buffer.from(entry as string, 'base64').toString('ascii'); - - let query = ""; - switch (operation) { - case "add": - // Make the query - query = `INSERT INTO auth (${tables.auth_entry}, ${tables.auth_type}, ${tables.auth_nickname}, ${tables.auth_permissions}) VALUES ('${entry}', '${type}', '${nickname}', '${permissions}')`; - const new_auths = await makeQuery(query, client) - return response.status(200).json({ data: new_auths }); - - case "remove": - // Make the query - query = `DELETE FROM auth WHERE ${tables.auth_entry} = '${entry}' AND ${tables.auth_type} = '${type}' AND ${tables.auth_nickname} = '${nickname}' AND ${tables.auth_permissions} = '${permissions}'`; - console.log(query) - const remove_auths = await makeQuery(query, client) - return response.status(200).json({ data: remove_auths }); - - case "fetch": - // Make the query - query = `SELECT * FROM auth`; - - // Get the auth entries - const auths = await makeQuery(query, client) - - // If there are no auths, return an error - if (!auths) { - return response.status(404).json({ error: 'No auths found' }); - } - - // Return the plant ids - return response.status(200).json({ data: auths }); - } - - - } catch (error) { - console.log("Error"); - console.log(error); - - // If there is an error, return the error - return response.status(500).json({ error: error }); - } -} \ No newline at end of file diff --git a/website/src/pages/api/files/backup_database.ts b/website/src/pages/api/files/backup_database.ts index 2ce116f..07861d0 100644 --- a/website/src/pages/api/files/backup_database.ts +++ b/website/src/pages/api/files/backup_database.ts @@ -23,7 +23,7 @@ export default async function handler( // Check if the user has the correct permissions const session = await getServerSession(request, response, authOptions) - const permission = await checkApiPermissions(request, response, session, client, "api:files:backup_database:access") + const permission = await checkApiPermissions(request, response, session, client, makeQuery, "api:files:backup_database:access") if(!permission) return response.status(401).json({error: "Not Authorized"}) // Create the query diff --git a/website/src/pages/api/files/backup_files.ts b/website/src/pages/api/files/backup_files.ts index 9f4d7c3..020ad8a 100644 --- a/website/src/pages/api/files/backup_files.ts +++ b/website/src/pages/api/files/backup_files.ts @@ -1,5 +1,5 @@ import {NextApiRequest, NextApiResponse} from 'next'; -import {getClient} from "@/lib/databse"; +import {getClient, makeQuery} from "@/lib/databse"; import {checkApiPermissions} from "@/lib/api_tools"; import archiver from 'archiver'; import fs from 'fs'; @@ -104,7 +104,7 @@ export default async function handler( // Check if the user has the correct permissions const session = await getServerSession(request, response, authOptions) - const permission = await checkApiPermissions(request, response, session, client, "api:files:backup_files:access") + const permission = await checkApiPermissions(request, response, session, client, makeQuery, "api:files:backup_files:access") if(!permission) return response.status(401).json({error: "Not Authorized"}) // Try uploading the data to the database diff --git a/website/src/pages/api/files/upload.ts b/website/src/pages/api/files/upload.ts index 4b58a4c..2383bdf 100644 --- a/website/src/pages/api/files/upload.ts +++ b/website/src/pages/api/files/upload.ts @@ -1,5 +1,5 @@ import {NextApiRequest, NextApiResponse} from 'next'; -import {getClient, PostgresSQL, SQLDatabase} from "@/lib/databse"; +import {getClient, makeQuery, PostgresSQL, SQLDatabase} from "@/lib/databse"; import {USE_POSTGRES} from "@/lib/constants"; import {Form} from "multiparty"; import fs from "fs"; @@ -23,7 +23,7 @@ export default async function handler( // Get the client const client = await getClient() const session = await getServerSession(request, response, authOptions) - const permission = await checkApiPermissions(request, response, session, client, "api:files:upload:access") + const permission = await checkApiPermissions(request, response, session, client, makeQuery, "api:files:upload:access") if(!permission) return response.status(401).json({error: "Not Authorized"}) // Try uploading the data to the database diff --git a/website/src/pages/api/plants/download.ts b/website/src/pages/api/plants/download.ts index 9f57859..1bd70bd 100644 --- a/website/src/pages/api/plants/download.ts +++ b/website/src/pages/api/plants/download.ts @@ -18,7 +18,7 @@ export default async function handler( // Get the client const client = await getClient() const session = await getServerSession(request, response, authOptions) - const permission = await checkApiPermissions(request, response, session, client, "api:plants:download:access") + const permission = await checkApiPermissions(request, response, session, client, makeQuery, "api:plants:download:access") if(!permission) return response.status(401).json({error: "Not Authorized"}) // Get the ID and table from the query string @@ -34,7 +34,7 @@ export default async function handler( return response.status(404).json({ error: 'ID parameter not found' }); } - const restrictedData = await checkApiPermissions(request, response, session, client, "data:plants:viewRestrictedSections") + const restrictedData = await checkApiPermissions(request, response, session, client, makeQuery, "data:plants:viewRestrictedSections") const data = await downloadPlantData(table, id, client, restrictedData); if(data[0] === "error"){ diff --git a/website/src/pages/api/plants/json.ts b/website/src/pages/api/plants/json.ts index 741683b..4b885f6 100644 --- a/website/src/pages/api/plants/json.ts +++ b/website/src/pages/api/plants/json.ts @@ -11,7 +11,7 @@ import { ValidPlantData } from "@/lib/plant_data"; import axios from "axios"; -import {getClient} from "@/lib/databse"; +import {getClient, makeQuery} from "@/lib/databse"; import {checkApiPermissions} from "@/lib/api_tools"; import {getServerSession} from "next-auth"; import {authOptions} from "@/pages/api/auth/[...nextauth]"; @@ -42,7 +42,7 @@ export default async function handler( // Check if the user is permitted to access the API const session = await getServerSession(request, response, authOptions) - const permission = await checkApiPermissions(request, response, session, client, "api:plants:json:access") + const permission = await checkApiPermissions(request, response, session, client, makeQuery, "api:plants:json:access") if(!permission) return response.status(401).json({error: "Not Authorized"}) // Try running the operation @@ -51,7 +51,7 @@ export default async function handler( switch (operation) { case 'download': - const permissionD = await checkApiPermissions(request, response, session, client, "api:plants:json:download") + const permissionD = await checkApiPermissions(request, response, session, client, makeQuery, "api:plants:json:download") if(!permissionD) return response.status(401).json({error: "Not Authorized"}) // If the ID is not found, return an error @@ -64,7 +64,7 @@ export default async function handler( return response.status(404).json({ error: 'ID parameter is not a number' }); } - const restrictedData = await checkApiPermissions(request, response, session, client, "data:plants:viewRestrictedSections") + const restrictedData = await checkApiPermissions(request, response, session, client, makeQuery, "data:plants:viewRestrictedSections") // Download the data from the database using the download API with the ID and table let plantsInfo = await downloadPlantData(["plants", "months_ready_for_use", "edible", "medical", "craft", "source", "custom", "attachments"], Number(id), client, restrictedData) @@ -120,7 +120,7 @@ export default async function handler( case 'upload': - const permissionU = await checkApiPermissions(request, response, session, client, "api:plants:json:upload") + const permissionU = await checkApiPermissions(request, response, session, client, makeQuery, "api:plants:json:upload") if(!permissionU) return response.status(401).json({error: "Not Authorized"}) // Check if the JSON param exists @@ -162,7 +162,7 @@ export default async function handler( return response.status(200).json({ data: result.data }); case "convert": - const permissionC = await checkApiPermissions(request, response, session, client, "api:plants:json:convert") + const permissionC = await checkApiPermissions(request, response, session, client, makeQuery, "api:plants:json:convert") if(!permissionC) return response.status(401).json({error: "Not Authorized"}) // Check if there is the tableName param diff --git a/website/src/pages/api/plants/months.ts b/website/src/pages/api/plants/months.ts index caa33a1..6c41d48 100644 --- a/website/src/pages/api/plants/months.ts +++ b/website/src/pages/api/plants/months.ts @@ -20,7 +20,7 @@ export default async function handler( // Check if the user is permitted to access the API const session = await getServerSession(request, response, authOptions) - const permission = await checkApiPermissions(request, response, session, client, "api:plants:months:access") + const permission = await checkApiPermissions(request, response, session, client, makeQuery, "api:plants:months:access") if(!permission) return response.status(401).json({error: "Not Authorized"}) // Try downloading the data from the database diff --git a/website/src/pages/api/plants/random.ts b/website/src/pages/api/plants/random.ts index d6f96d4..c25ae14 100644 --- a/website/src/pages/api/plants/random.ts +++ b/website/src/pages/api/plants/random.ts @@ -29,7 +29,7 @@ export default async function handler( // Check if the user is permitted to access the API const session = await getServerSession(request, response, authOptions) - const permission = await checkApiPermissions(request, response, session, client, "api:plants:random:access") + const permission = await checkApiPermissions(request, response, session, client, makeQuery, "api:plants:random:access") if(!permission) return response.status(401).json({error: "Not Authorized"}) // Try querying the database diff --git a/website/src/pages/api/plants/remove.ts b/website/src/pages/api/plants/remove.ts index e5bbeb2..34cb967 100644 --- a/website/src/pages/api/plants/remove.ts +++ b/website/src/pages/api/plants/remove.ts @@ -20,7 +20,7 @@ export default async function handler( // Check if the user is permitted to access the API const session = await getServerSession(request, response, authOptions) - const permission = await checkApiPermissions(request, response, session, client, "api:plants:remove:access") + const permission = await checkApiPermissions(request, response, session, client, makeQuery, "api:plants:remove:access") if(!permission) return response.status(401).json({error: "Not Authorized"}) try { diff --git a/website/src/pages/api/plants/search.ts b/website/src/pages/api/plants/search.ts index 1612120..0585cb0 100644 --- a/website/src/pages/api/plants/search.ts +++ b/website/src/pages/api/plants/search.ts @@ -19,7 +19,7 @@ export default async function handler( // Check if the user is permitted to access the API const session = await getServerSession(request, response, authOptions) - const permission = await checkApiPermissions(request, response, session, client, "api:plants:search:access") + const permission = await checkApiPermissions(request, response, session, client, makeQuery, "api:plants:search:access") if(!permission) return response.status(401).json({error: "Not Authorized"}) // Get the ID and table from the query string diff --git a/website/src/pages/api/plants/upload.ts b/website/src/pages/api/plants/upload.ts index f9e4c18..2a93fe9 100644 --- a/website/src/pages/api/plants/upload.ts +++ b/website/src/pages/api/plants/upload.ts @@ -24,7 +24,7 @@ export default async function handler( // Check if the user is permitted to access the API const session = await getServerSession(request, response, authOptions) - const permission = await checkApiPermissions(request, response, session, client, "api:plants:upload:access") + const permission = await checkApiPermissions(request, response, session, client, makeQuery, "api:plants:upload:access") if(!permission) return response.status(401).json({error: "Not Authorized"}) // Try uploading the data to the database diff --git a/website/src/pages/api/plants/uses.ts b/website/src/pages/api/plants/uses.ts index 42477c9..193e648 100644 --- a/website/src/pages/api/plants/uses.ts +++ b/website/src/pages/api/plants/uses.ts @@ -19,7 +19,7 @@ export default async function handler( // Check if the user is permitted to access the API const session = await getServerSession(request, response, authOptions) - const permission = await checkApiPermissions(request, response, session, client, "api:plants:uses:access") + const permission = await checkApiPermissions(request, response, session, client, makeQuery, "api:plants:uses:access") if(!permission) return response.status(401).json({error: "Not Authorized"}) // Try downloading the data from the database diff --git a/website/src/pages/api/user/api_keys.ts b/website/src/pages/api/user/api_keys.ts index 7b540a5..e2e6721 100644 --- a/website/src/pages/api/user/api_keys.ts +++ b/website/src/pages/api/user/api_keys.ts @@ -1,8 +1,9 @@ import {NextApiRequest, NextApiResponse} from 'next'; -import {getClient, getTables} from "@/lib/databse"; +import {getClient, getTables, makeQuery} from "@/lib/databse"; import {getServerSession} from "next-auth"; import {authOptions} from "@/pages/api/auth/[...nextauth]"; import {checkApiPermissions} from "@/lib/api_tools"; +import {RongoaUser} from "@/lib/users"; export default async function handler( request: NextApiRequest, @@ -17,9 +18,13 @@ export default async function handler( // Check if the user is permitted to access the API const session = await getServerSession(request, response, authOptions) - let permission = await checkApiPermissions(request, response, session, client, "api:user:api_keys:access") + let permission = await checkApiPermissions(request, response, session, client, makeQuery, "api:user:api_keys:access") if(!permission) return response.status(401).json({error: "Not Authorized"}) + let query = '' + + const { id, publicUserID } = request.query; + try { // Get the session @@ -31,32 +36,70 @@ export default async function handler( } // Get the user details - const user_email = session.user.email; - const user_name = session.user.name; + const user = session.user as RongoaUser; + const userId = user.database.id; // Get the operation - const { operation } = request.query; + const { operation, keyName, permissions} = request.query; if(!operation) { return response.status(400).json({ error: 'No operation specified'}); } + const privateData = await checkApiPermissions(request, response, session, client, makeQuery, "data:account:viewPrivateDetails") + switch (operation) { case "new": - permission = await checkApiPermissions(request, response, session, client, "api:user:api_keys:add") + + // Check if the user has permission to add a key + permission = await checkApiPermissions(request, response, session, client, makeQuery, "api:user:api_keys:add") if(!permission) return response.status(401).json({error: "Not Authorized"}) - break + + // Check if the key name and permissions are set + if(!keyName || !permissions) return response.status(400).json({ error: 'Missing parameters'}); + + // Generate the missing parameters + let key = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15); + let logs = [{time: Date.now(), action: "Created"}] + + // Insert the key + query = `INSERT INTO apikey (${tables.user_id}, ${tables.api_key_name}, ${tables.api_key_value}, ${tables.api_key_permissions}, ${tables.api_key_logs}, ${tables.api_key_last_used} ) VALUES ('${userId}', '${keyName}', '${key}', '${permissions}', '${JSON.stringify(logs)}', NOW())`; + const inserted = await makeQuery(query, client); + + // Return the key + return response.status(200).json({ data: { key: key }}); case "remove": - permission = await checkApiPermissions(request, response, session, client, "api:user:api_keys:remove") + permission = await checkApiPermissions(request, response, session, client, makeQuery, "api:user:api_keys:remove") if(!permission) return response.status(401).json({error: "Not Authorized"}) break case "edit": - permission = await checkApiPermissions(request, response, session, client, "api:user:api_keys:edit") + permission = await checkApiPermissions(request, response, session, client, makeQuery, "api:user:api_keys:edit") if(!permission) return response.status(401).json({error: "Not Authorized"}) break + case "fetch": + permission = await checkApiPermissions(request, response, session, client, makeQuery, "api:user:api_keys:fetch") + if(!permission) return response.status(401).json({error: "Not Authorized"}) + + + if(publicUserID && privateData){ + query = `SELECT * FROM apikey WHERE ${tables.user_id} = '${publicUserID}'`; + }else{ + query = `SELECT * FROM apikey WHERE ${tables.user_id} = '${userId}'`; + } + + console.log(query); + const keys = await makeQuery(query, client) + + // Check if the user has any keys + if(keys.length === 0) { + return response.status(404).json({ error: 'No keys found'}); + } + + // Return the keys + return response.status(200).json({ data: keys }); } diff --git a/website/src/pages/api/user/data.ts b/website/src/pages/api/user/data.ts index 3c84d8a..3d9d214 100644 --- a/website/src/pages/api/user/data.ts +++ b/website/src/pages/api/user/data.ts @@ -22,10 +22,10 @@ export default async function handler( // Check if the user is permitted to access the API const session = await getServerSession(request, response, authOptions) - const permission = await checkApiPermissions(request, response, session, client, "api:user:data:access") + const permission = await checkApiPermissions(request, response, session, client, makeQuery, "api:user:data:access") if(!permission) return response.status(401).json({error: "Not Authorized"}) - const privateData = await checkApiPermissions(request, response, session, client, "data:account:viewPrivateDetails") + const privateData = await checkApiPermissions(request, response, session, client, makeQuery, "data:account:viewPrivateDetails") try { @@ -56,7 +56,7 @@ export default async function handler( } // Return the user - return response.status(200).json({ user: user[0] }); + return response.status(200).json({ data: user[0] }); } catch (error) { diff --git a/website/src/pages/api/user/delete.ts b/website/src/pages/api/user/delete.ts index a2fabae..acdd0bc 100644 --- a/website/src/pages/api/user/delete.ts +++ b/website/src/pages/api/user/delete.ts @@ -1,5 +1,5 @@ import {NextApiRequest, NextApiResponse} from 'next'; -import {getClient, getTables} from "@/lib/databse"; +import {getClient, getTables, makeQuery} from "@/lib/databse"; import {getServerSession} from "next-auth"; import {authOptions} from "@/pages/api/auth/[...nextauth]"; import {RongoaUser} from "@/lib/users"; @@ -21,7 +21,7 @@ export default async function handler( // Check if the user is permitted to access the API const session = await getServerSession(request, response, authOptions) - const permission = await checkApiPermissions(request, response, session, client, "api:user:delete:access") + const permission = await checkApiPermissions(request, response, session, client, makeQuery, "api:user:delete:access") if(!permission) return response.status(401).json({error: "Not Authorized"}) try { @@ -42,10 +42,10 @@ export default async function handler( // Remove the user let query = `DELETE FROM users WHERE ${tables.user_email} = '${user_email}' AND ${tables.user_name} = '${user_name}'`; console.log(query); - const removed = await client.query(query); + const removed = makeQuery(query, client) // Return the user - return response.status(200).json({removed: removed.affectedRows}); + return response.status(200).json({data : removed}); } catch (error) { diff --git a/website/src/pages/api/user/email.ts b/website/src/pages/api/user/email.ts index 46ba535..3b252ad 100644 --- a/website/src/pages/api/user/email.ts +++ b/website/src/pages/api/user/email.ts @@ -23,7 +23,7 @@ export default async function handler( // Check if the user is permitted to access the API const session = await getServerSession(request, response, authOptions) - const permission = await checkApiPermissions(request, response, session, client, "api:user:email:access") + const permission = await checkApiPermissions(request, response, session, client, makeQuery, "api:user:email:access") if(!permission) return response.status(401).json({error: "Not Authorized"}) try { @@ -42,7 +42,7 @@ export default async function handler( } // Return the user - return response.status(200).json({ user: user[0] }); + return response.status(200).json({ data: user[0] }); } catch (error) { diff --git a/website/src/pages/api/user/plants.ts b/website/src/pages/api/user/plants.ts index 0cfcd6a..883d5c9 100644 --- a/website/src/pages/api/user/plants.ts +++ b/website/src/pages/api/user/plants.ts @@ -24,7 +24,7 @@ export default async function handler( // Check if the user is permitted to access the API const session = await getServerSession(request, response, authOptions) - const permission = await checkApiPermissions(request, response, session, client, "api:user:plants:access") + const permission = await checkApiPermissions(request, response, session, client, makeQuery, "api:user:plants:access") if(!permission) return response.status(401).json({error: "Not Authorized"}) const handleGet = async (userID: string) => { diff --git a/website/src/pages/api/user/update.ts b/website/src/pages/api/user/update.ts index d7229ac..4b3d32a 100644 --- a/website/src/pages/api/user/update.ts +++ b/website/src/pages/api/user/update.ts @@ -28,7 +28,7 @@ export default async function handler( // Check if the user is permitted to access the API const session = await getServerSession(request, response, authOptions) - const permission = await checkApiPermissions(request, response, session, client, "api:user:update:access") + const permission = await checkApiPermissions(request, response, session, client, makeQuery, "api:user:update:access") if(!permission) return response.status(401).json({error: "Not Authorized"}) try { diff --git a/website/src/pages/index.tsx b/website/src/pages/index.tsx index 351ff6c..d52c15a 100644 --- a/website/src/pages/index.tsx +++ b/website/src/pages/index.tsx @@ -8,7 +8,7 @@ // - Display the user's info on the profile page | DONE // - Edit the user's info | DONE // - Permissions for users | DONE -// - User api keys | +// - User api keys | Create page CSS, View Logs, Edit Name/Permissions, Delete // - Rewrite docs for users | import React, {useEffect, useRef} from "react"; @@ -92,7 +92,7 @@ export default function Home() { if(!userData.data?.user) return - const user = userData.data.user as UserDatabaseDetails + const user = userData.data.data as UserDatabaseDetails update({database: user}) saveToCache("user_data_refreshed", true) diff --git a/website/src/styles/pages/account/index.module.css b/website/src/styles/pages/account/index.module.css index feebe54..9c1959b 100644 --- a/website/src/styles/pages/account/index.module.css +++ b/website/src/styles/pages/account/index.module.css @@ -131,6 +131,7 @@ .tableContainer{ width: 100%; + overflow-x:auto; } .dataTable{