diff --git a/app/core/api_auth.py b/app/core/api_auth.py index 0771b93..5f8b282 100644 --- a/app/core/api_auth.py +++ b/app/core/api_auth.py @@ -9,6 +9,8 @@ from app.lib.utils import cryptography +import pandas as pd + dotenv.load_dotenv() @@ -48,55 +50,46 @@ def get_all_user_api_keys() -> List[str]: results: List[Dict] = response["result"] - api_keys: List[str] = [result["name"] for result in results] + keys: List[str] = [result["name"] for result in results] + + # Filter out the emails from the keys + keys_series = pd.Series(keys) + + # Filter the series to exclude strings containing '@' + api_keys: List[str] = keys_series[~keys_series.str.contains("@")].tolist() return api_keys -def read_api_key_from_email(email: str): +# The KV is str -> str +def _read_key(key_name: str) -> str: account_id: str = CLOUDFLARE_ACCOUNT_ID namespace_id: str = CLOUDFLARE_KV_NAMESPACE_ID - key_name: str = email headers = {"Authorization": "Bearer undefined", "Content-Type": "application/json"} # Get response from api - response = requests.get( + response: str = requests.get( f"https://api.cloudflare.com/client/v4/accounts/{account_id}/storage/kv/namespaces/{namespace_id}/values/{key_name}", headers=headers, ) - # if isinstance(response, dict) and response["success"] is False: - # return None + return response - api_key: str = response + +def read_api_key_from_email(email: str): + api_key: str = _read_key(email) return api_key def read_email_from_api_key(api_key: str): - account_id: str = CLOUDFLARE_ACCOUNT_ID - namespace_id: str = CLOUDFLARE_KV_NAMESPACE_ID - key_name: str = api_key - - headers = {"Authorization": "Bearer undefined", "Content-Type": "application/json"} - - # Get response from api - response = requests.get( - f"https://api.cloudflare.com/client/v4/accounts/{account_id}/storage/kv/namespaces/{namespace_id}/values/{key_name}", - headers=headers, - ) - - # if isinstance(response, dict) and response["success"] is False: - # return None - - user_email: str = response + user_email: str = _read_key(api_key) return user_email -# user_id is hopefully a string -def create_api_key(user_email: str) -> str: +def create_api_key(user_email: str, expiration_ttl: int = -1) -> str: # Generate it first api_key: str = cryptography.generate_api_key() @@ -104,30 +97,37 @@ def create_api_key(user_email: str) -> str: account_id: str = CLOUDFLARE_ACCOUNT_ID namespace_id: str = CLOUDFLARE_KV_NAMESPACE_ID - headers = {"Authorization": "Bearer undefined", "Content-Type": "application/json"} + def make_url(key_name: str) -> str: + return f"https://api.cloudflare.com/client/v4/accounts/{account_id}/storage/kv/namespaces/{namespace_id}/values/{key_name}" - # Forward mapping: [email -> api_key] - query_params = {"base64": False, "key": user_email, "value": api_key} + headers = { + "Content-Type": "multipart/form-data", + "Authorization": "Bearer undefined", + } - response = requests.put( - f"https://api.cloudflare.com/client/v4/accounts/{account_id}/storage/kv/namespaces/{namespace_id}/bulk", - headers=headers, - params=query_params, - ) + def make_request(key: str, val, expiration_ttl: int = -1): + url: str = make_url(key_name=key) + + body = {"metadata": {}, "value": val} + + if expiration_ttl != -1: + body["expiration_ttl"] = expiration_ttl + + response = requests.request("PUT", url, headers=headers, json=body) + return response + + # Forward mapping: [user_email -> api_key] + (K, V) = (user_email, api_key) + response = make_request(K, V, expiration_ttl=expiration_ttl) # Print results of call print("Forward Mapping:") print(response.status_code) print(response.text) - # Reverse Mapping: [api_key -> email] - query_params = {"base64": False, "key": api_key, "value": user_email} - - response = requests.put( - f"https://api.cloudflare.com/client/v4/accounts/{account_id}/storage/kv/namespaces/{namespace_id}/bulk", - headers=headers, - params=query_params, - ) + # Reverse Mapping: [api_key -> user_email] + (K, V) = (api_key, user_email) + response = make_request(K, V, expiration_ttl=expiration_ttl) # Print results of call print("Reverse Mapping:") diff --git a/app/core/routes/user.py b/app/core/routes/user.py index b2cb6dc..617d60e 100644 --- a/app/core/routes/user.py +++ b/app/core/routes/user.py @@ -33,10 +33,29 @@ async def create_api_key(request: Request) -> str: user_details: UserDetails = UserDetails(**user_client.get_user_details()) # user_auth_id: str = user_details.id + expiration_ttl = request.query_params.get("expiration_ttl_seconds") + + # 'int'ify it + if expiration_ttl is None: + expiration_ttl = -1 + else: + expiration_ttl = int(expiration_ttl) # Make api key and store it - api_key: str = api_auth.create_api_key(user_details.email) + api_key: str = api_auth.create_api_key( + user_details.email, expiration_ttl=expiration_ttl + ) + + return api_key + + +# using post for the additional security +@router.post("/get_apikey") +async def get_api_key(request: Request) -> str: + user_client = user_kinde_client(request.url) + user_details: UserDetails = UserDetails(**user_client.get_user_details()) + api_key: str = api_auth.read_api_key_from_email(user_details.email) return api_key diff --git a/frontend/package.json b/frontend/package.json index 4525a4d..f4ef5d7 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -74,6 +74,8 @@ "typescript": "^5.4.5" }, "devDependencies": { + "@melt-ui/pp": "^0.3.2", + "@melt-ui/svelte": "^0.81.0", "flowbite": "^2.3.0", "flowbite-svelte": "^0.46.1", "svelte-headless-table": "^0.18.2" diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index dd651b3..f17e3d2 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -193,6 +193,12 @@ dependencies: version: 5.4.5 devDependencies: + '@melt-ui/pp': + specifier: ^0.3.2 + version: 0.3.2(@melt-ui/svelte@0.81.0)(svelte@4.2.17) + '@melt-ui/svelte': + specifier: ^0.81.0 + version: 0.81.0(svelte@4.2.17) flowbite: specifier: ^2.3.0 version: 2.3.0 @@ -1496,7 +1502,6 @@ packages: resolution: {integrity: sha512-qoVJVro+O0rBaw+8HPjUB1iH8Ihf8oziEnqMnvhJUSuVIrHOuZ6eNLHNvzXJKUvAtaDiqMnRlg8Z2mgh09BlUw==} dependencies: '@swc/helpers': 0.5.11 - dev: false /@isaacs/cliui@8.0.2: resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} @@ -1709,6 +1714,18 @@ packages: resolution: {integrity: sha512-Rp7ll8BHrKB3wXaRFKhrltwZl1CiXGdibPxuWXvqGnKTnv8fqa/nvftYNuSbf+pbJWKYCXdBtYTITdAUTGGh0Q==} dev: false + /@melt-ui/pp@0.3.2(@melt-ui/svelte@0.81.0)(svelte@4.2.17): + resolution: {integrity: sha512-xKkPvaIAFinklLXcQOpwZ8YSpqAFxykjWf8Y/fSJQwsixV/0rcFs07hJ49hJjPy5vItvw5Qa0uOjzFUbXzBypQ==} + peerDependencies: + '@melt-ui/svelte': '>= 0.29.0' + svelte: ^3.55.0 || ^4.0.0 || ^5.0.0-next.1 + dependencies: + '@melt-ui/svelte': 0.81.0(svelte@4.2.17) + estree-walker: 3.0.3 + magic-string: 0.30.10 + svelte: 4.2.17 + dev: true + /@melt-ui/svelte@0.76.2(svelte@4.2.17): resolution: {integrity: sha512-7SbOa11tXUS95T3fReL+dwDs5FyJtCEqrqG3inRziDws346SYLsxOQ6HmX+4BkIsQh1R8U3XNa+EMmdMt38lMA==} peerDependencies: @@ -1723,6 +1740,20 @@ packages: svelte: 4.2.17 dev: false + /@melt-ui/svelte@0.81.0(svelte@4.2.17): + resolution: {integrity: sha512-QWVy+kVp8CZ+ph4W780PI6rPBa3MJIUFH/E1EHYfI05QloOGVBXtrI9MlzQSIrGYtGgSGQoTbh8cPO+YtkNAwg==} + peerDependencies: + svelte: ^3.0.0 || ^4.0.0 || ^5.0.0-next.118 + dependencies: + '@floating-ui/core': 1.6.2 + '@floating-ui/dom': 1.6.5 + '@internationalized/date': 3.5.4 + dequal: 2.0.3 + focus-trap: 7.5.4 + nanoid: 5.0.7 + svelte: 4.2.17 + dev: true + /@monogrid/gainmap-js@3.0.5(three@0.164.1): resolution: {integrity: sha512-53sCTG4FaJBaAq/tcufARtVYDMDGqyBT9i7F453pWGhZ5LqubDHDWtYoHo9VhQqMcHTEexdJqSsR58y+9HVmQA==} peerDependencies: @@ -3195,7 +3226,6 @@ packages: resolution: {integrity: sha512-YNlnKRWF2sVojTpIyzwou9XoTNbzbzONwRhOoniEioF1AtaitTvVZblaQRrAzChWQ1bLYyYSWzM18y4WwgzJ+A==} dependencies: tslib: 2.6.2 - dev: false /@tabler/icons-svelte@3.5.0(svelte@4.2.17): resolution: {integrity: sha512-mc5ardGEM7cnUA4/q6Mz5bmW9B6t28vAAOf4Wl6+KXiTwG00EjImfnIr3pS3Ihi9sFIiXvJPYRl4H5IHlgvJvQ==} @@ -5851,7 +5881,6 @@ packages: resolution: {integrity: sha512-N7kHdlgsO/v+iD/dMoJKtsSqs5Dz/dXZVebRgJw23LDk+jMi/974zyiOYDziY2JPp8xivq9BmUGwIJMiuSBi7w==} dependencies: tabbable: 6.2.0 - dev: false /follow-redirects@1.15.6: resolution: {integrity: sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==} @@ -7799,7 +7828,6 @@ packages: resolution: {integrity: sha512-oLxFY2gd2IqnjcYyOXD8XGCftpGtZP2AbHbOkthDkvRywH5ayNtPVy9YlOPcHckXzbLTCHpkb7FB+yuxKV13pQ==} engines: {node: ^18 || >=20} hasBin: true - dev: false /nanostores@0.10.3: resolution: {integrity: sha512-Nii8O1XqmawqSCf9o2aWqVxhKRN01+iue9/VEd1TiJCr9VT5XxgPFbF1Edl1XN6pwJcZRsl8Ki+z01yb/T/C2g==} @@ -9654,7 +9682,6 @@ packages: /tabbable@6.2.0: resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==} - dev: false /tailwind-merge@2.3.0: resolution: {integrity: sha512-vkYrLpIP+lgR0tQCG6AP7zZXCTLc1Lnv/CCRT3BqJ9CZ3ui2++GPaGb1x/ILsINIMSYqqvrpqjUFsMNLlW99EA==} @@ -9921,7 +9948,6 @@ packages: /tslib@2.6.2: resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} requiresBuild: true - dev: false /tunnel-rat@0.1.2(@types/react@18.3.2)(react@18.3.1): resolution: {integrity: sha512-lR5VHmkPhzdhrM092lI2nACsLO4QubF0/yoOhzX7c+wIpbN1GjHNzCc91QlpxBi+cnx8vVJ+Ur6vL5cEoQPFpQ==} diff --git a/frontend/src/components/CreateCommunity/CreateCommunityDialog.svelte b/frontend/src/components/CreateCommunity/CreateCommunityDialog.svelte new file mode 100644 index 0000000..ff885bb --- /dev/null +++ b/frontend/src/components/CreateCommunity/CreateCommunityDialog.svelte @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/frontend/src/components/CreateCommunity/CreateCommunityForm.svelte b/frontend/src/components/CreateCommunity/CreateCommunityForm.svelte index e69de29..6243d8d 100644 --- a/frontend/src/components/CreateCommunity/CreateCommunityForm.svelte +++ b/frontend/src/components/CreateCommunity/CreateCommunityForm.svelte @@ -0,0 +1,221 @@ + + + + + New Community + Create a community + {#if alertGenerateAPIKey} +

You Must Generate an API Key first in order to use UniverseLM. Do so by heading to your profile

+ {/if} +
+ +
+
+ +
+ + + + {#if nameRejected} +

{nameRejectionMessage}

+ {/if} +
+
+ +
+ +
+ + + {#if privateRejected} +

{privacyRejectionMessage}

+ {/if} +
+
+ + +
+ + +
+ + {#if isAIGeneratedBase} + +
+ + diff --git a/frontend/src/data/datafunctions.ts b/frontend/src/data/datafunctions.ts index 9c97678..68b8451 100644 --- a/frontend/src/data/datafunctions.ts +++ b/frontend/src/data/datafunctions.ts @@ -1,6 +1,13 @@ import axios from "axios"; import { BACKEND_URL } from "./envconfig"; -import { userCoreID } from "./stores"; +import { userCoreID, apiKey } from "./stores"; + +const MIN_ID_LENGTH: number = 3; +export const idNotFound = (id): boolean => (id === null || id === undefined || id === "undefined" || id === "null" || id.length < MIN_ID_LENGTH); + +export function hasAPIKey(): boolean { + return !idNotFound(apiKey.get()); +} export async function retrieveUserCoreID() { try { @@ -51,3 +58,19 @@ export async function retrieveUserDetails() { console.log("Error retrieving user details: " + error); } } + +export async function retrieveAPIKey() { + try { + const response = await axios.get(`${BACKEND_URL}/user/get_apikey`, { + withCredentials: true + }); + + const API_KEY: string = response.data; + + // Update stored api key + apiKey.set(API_KEY); + return API_KEY; + } catch (error) { + console.log("Error retrieving user api key: " + error); + } +} diff --git a/frontend/src/data/stores.ts b/frontend/src/data/stores.ts index 451c5fe..b667cb8 100644 --- a/frontend/src/data/stores.ts +++ b/frontend/src/data/stores.ts @@ -54,6 +54,7 @@ export const userAuthID = persistentAtom("user_id"); // CORE STORES // stores relating to the core of the application +export const apiKey = persistentAtom("api_key"); const user_core_id = persistentAtom("user_core_id"); // this one's an int // probably don't RELY on this TOO much except as a cache. Functional is more secure diff --git a/frontend/src/scripts/updatestate.ts b/frontend/src/scripts/updatestate.ts index b7eaf28..3cb0768 100644 --- a/frontend/src/scripts/updatestate.ts +++ b/frontend/src/scripts/updatestate.ts @@ -6,9 +6,12 @@ import { BACKEND_URL } from "$lib/data/envconfig"; import { githubStars, authState, authentication, coreRegistration, userAuthID, - userCoreID + userCoreID, + apiKey } from "$lib/data/stores"; +import { retrieveAPIKey, idNotFound } from "$lib/data/datafunctions"; + // when to update all major values (where 'major' is defined by whether the whole site will break without correctly setting these values) export default async function update() { await updateAuthentication(); @@ -49,10 +52,6 @@ export async function updateAuthentication() { console.log("Getting ID..."); console.log("userAuthID: " + userAuthID.get()); - // I FUCKING HATE JS WITH ITS BULLSHIT - const MIN_ID_LENGTH: number = 3; - const idNotFound = (id) => (id === null || id === undefined || id === "undefined" || id === "null" || id.length < MIN_ID_LENGTH); - function onUserAuthIDNotFound() { console.log("No userAuthID found"); authentication.setIsAuthenticated(false); @@ -134,4 +133,13 @@ function postAuthentication() { // activate the registration coreRegistration.activateRegistration(); } + + // Check for and update API Key + retrieveAPIKey() + .then((api_key) => { + // It's already set by the function, so no need to do it again + console.log("API Key Retrieved."); + }).catch((error) => { + console.log("Error retrieving API Key: " + error.toString()); + }); } diff --git a/frontend/svelte.config.js b/frontend/svelte.config.js index 522c1ef..86a17d3 100644 --- a/frontend/svelte.config.js +++ b/frontend/svelte.config.js @@ -1,5 +1,9 @@ import { vitePreprocess } from '@astrojs/svelte'; +import { preprocessMeltUI, sequence } from '@melt-ui/pp' export default { - preprocess: vitePreprocess(), + preprocess: sequence([ + vitePreprocess(), + preprocessMeltUI() + ]), }