Skip to content

Commit

Permalink
Create & Use Api Keys
Browse files Browse the repository at this point in the history
  • Loading branch information
maxtyson123 committed Nov 17, 2023
1 parent efd24cb commit cdd3883
Show file tree
Hide file tree
Showing 29 changed files with 560 additions and 223 deletions.
23 changes: 13 additions & 10 deletions server/my_sql/create_tables.sql
Original file line number Diff line number Diff line change
Expand Up @@ -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
);
-- 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)
);

6 changes: 3 additions & 3 deletions website/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

53 changes: 49 additions & 4 deletions website/src/lib/api_tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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{

Expand Down Expand Up @@ -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

}
Expand Down Expand Up @@ -120,4 +150,19 @@ export async function makeRequestWithToken (
console.error('Request failed:', error.message);
throw error;
}
};
};


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
}
33 changes: 14 additions & 19 deletions website/src/lib/databse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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";
}
}

Expand Down Expand Up @@ -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";
}
}

Expand Down
5 changes: 4 additions & 1 deletion website/src/lib/plant_data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down Expand Up @@ -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();
}

Expand Down
55 changes: 46 additions & 9 deletions website/src/lib/users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -96,6 +93,7 @@ export interface UserPermissions {
add: boolean;
remove: boolean;
edit: boolean;
fetch: boolean;
};
data: {
publicAccess: boolean;
Expand Down Expand Up @@ -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: {
Expand Down Expand Up @@ -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: {
Expand Down Expand Up @@ -271,7 +269,7 @@ export function getUserPermissions(user: RongoaUser | null) {
},
},

data:{
data: {
account: {
viewPrivateDetails: false,
},
Expand All @@ -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)
Expand All @@ -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) {
Expand Down Expand Up @@ -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
}
18 changes: 3 additions & 15 deletions website/src/pages/account/edit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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";
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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
Expand All @@ -234,16 +232,6 @@ export default function EditAccount() {
await router.push("/account")
}

const loginSection = () => {
return (
<>
<div className={globalStyles.gridCentre}>
<button className={styles.signInButton} onClick={() => signIn()}><FontAwesomeIcon icon={faPerson}/> Sign in</button>
</div>
</>
)
}

const editSection = () => {
return (
<>
Expand Down
Loading

1 comment on commit cdd3883

@vercel
Copy link

@vercel vercel bot commented on cdd3883 Nov 17, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.