From 6cb7da37824d02a1898d08d0f9495c77fde4dd1d Mon Sep 17 00:00:00 2001 From: vnugent Date: Sat, 20 Jan 2024 23:49:29 -0500 Subject: inital commit --- front-end/src/store/oauthAppsPlugin.ts | 174 +++++++++++++++++++++++++++++++++ 1 file changed, 174 insertions(+) create mode 100644 front-end/src/store/oauthAppsPlugin.ts (limited to 'front-end/src/store/oauthAppsPlugin.ts') diff --git a/front-end/src/store/oauthAppsPlugin.ts b/front-end/src/store/oauthAppsPlugin.ts new file mode 100644 index 0000000..d12eed2 --- /dev/null +++ b/front-end/src/store/oauthAppsPlugin.ts @@ -0,0 +1,174 @@ +// Copyright (C) 2024 Vaughn Nugent +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +import 'pinia' +import { MaybeRef, computed, shallowRef, watch } from 'vue'; +import { apiCall, useAxios } from '@vnuge/vnlib.browser'; +import { get, set, useToggle } from '@vueuse/core'; +import { PiniaPlugin, PiniaPluginContext, storeToRefs } from 'pinia' +import { map, sortBy, isArray, defer } from 'lodash-es'; + +export interface OAuth2Application { + readonly Id: string, + readonly name: string, + readonly description: string, + readonly permissions: string[], + readonly client_id: string, + Created: Date, + readonly LastModified: Date, +} + +export interface NewAppResponse { + readonly secret: string + readonly app: OAuth2Application +} + +export interface OAuth2Api { + create(app: OAuth2Application): Promise + updateSecret(app: OAuth2Application, password: string): Promise + updateMeta(app: OAuth2Application): Promise + deleteApp(app: OAuth2Application, password: string): Promise +} + +declare module 'pinia' { + export interface PiniaCustomProperties { + oauth2:{ + apps: OAuth2Application[], + scopes: string[], + refresh(): void + } & OAuth2Api + } +} + +export const oauth2AppsPlugin = (o2EndpointUrl: MaybeRef, scopeEndpoint: MaybeRef): PiniaPlugin => { + + return ({ store }: PiniaPluginContext) => { + + const axios = useAxios(null); + const { loggedIn } = storeToRefs(store) + + const [onRefresh, refresh] = useToggle() + + const _oauth2Apps = shallowRef([]) + const scopes = shallowRef([]) + + /** + * Updates an Oauth2 application's metadata + */ + const updateMeta = async (app: OAuth2Application): Promise => { + //Update the app metadata + await axios.put(get(o2EndpointUrl), app) + } + + /** + * Gets all of the user's oauth2 applications from the server + * @returns The user's oauth2 applications + */ + const getApps = async () => { + // Get all apps + const { data } = await axios.get(get(o2EndpointUrl)); + + if (!isArray(data)) { + throw new Error("Invalid response from server") + } + + return map(data, (appData) => { + //Store the created time as a date object + appData.Created = new Date(appData?.Created ?? 0) + //create a new state manager for the user's profile + return appData; + }) + } + + /** + * Creates a new application from the given data + * @param param0 The application server buffer + * @returns The newly created application + */ + const create = async ({ name, description, permissions }: OAuth2Application): Promise => { + + // make the post request, response is the new app data with a secret + const { data } = await axios.post(`${get(o2EndpointUrl)}?action=create`, { name, description, permissions }) + + // Store secret + const secret = data.raw_secret + + // remove secre tfrom the response + delete (data as any).raw_secret + + return { secret, app: data } + } + + /** + * Requets a new secret for an application from the server + * @param app The app to request a new secret for + * @param password The user's password + * @returns The new secret + */ + const updateSecret = async (app: OAuth2Application, password: string): Promise => { + const { data } = await axios.post(`${o2EndpointUrl}?action=secret`, { Id: app.Id, password }) + return data.raw_secret + } + + /** + * Deletes an application from the server + * @param app The application to delete + * @param password The user's password + * @returns The response from the server + */ + const deleteApp = async (app: OAuth2Application, password: string): Promise => { + await axios.post(`${o2EndpointUrl}?action=delete`, { password, Id: app.Id }); + } + + const apps = computed(() => sortBy(_oauth2Apps.value, a => a.Created)) + + watch([loggedIn, onRefresh], async ([li]) => { + if (!li) { + set(_oauth2Apps, []) + return; + } + + //Load the user's oauth2 apps after scopes load. to make sure the plugin is enabled + apiCall(async () => { + try { + //Load the oauth2 scopes + const { data } = await axios.get(get(scopeEndpoint)) + set(scopes, data) + + _oauth2Apps.value = await getApps(); + } + catch (e: any) { + if (e.response?.status === 404) { + console.warn("OAuth2 plugin is not configured"); + } + } + }) + }) + + defer(refresh) + + return { + oauth2:{ + scopes, + apps, + deleteApp, + updateSecret, + updateMeta, + create, + refresh + } + } + } +} \ No newline at end of file -- cgit