From 5abc489b9954111d66d1385aa62a3ea962fa0a55 Mon Sep 17 00:00:00 2001 From: vnugent Date: Tue, 30 Jan 2024 15:23:06 -0500 Subject: merge upstream. Add dynamic client-side support for optional oauth2 and social login methods --- front-end/src/store/oauthAppsPlugin.ts | 154 +++++++++++++++++++++++++++++++++ 1 file changed, 154 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..7a76992 --- /dev/null +++ b/front-end/src/store/oauthAppsPlugin.ts @@ -0,0 +1,154 @@ +import 'pinia' +import { MaybeRef, computed, ref, 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 } from 'lodash-es'; +import { storeExport } from '.'; + +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 Oauth2Store{ + oauth2: { + apps: OAuth2Application[], + scopes: string[], + getApps(): Promise + createApp(app: OAuth2Application): Promise + updateAppSecret(app: OAuth2Application, password: string): Promise + updateAppMeta(app: OAuth2Application): Promise + deleteApp(app: OAuth2Application, password: string): Promise + refresh(): void + } +} + +declare module 'pinia' { + export interface PiniaCustomProperties extends Oauth2Store{ + + } +} + +export const oauth2AppsPlugin = (o2EndpointUrl: MaybeRef, scopeEndpoint: MaybeRef): PiniaPlugin =>{ + + return ({ store }: PiniaPluginContext): Oauth2Store => { + + const axios = useAxios(null); + const { loggedIn } = storeToRefs(store) + + const [onRefresh, refresh] = useToggle() + + const _oauth2Apps = shallowRef([]) + const scopes = ref([]) + + /** + * Updates an Oauth2 application's metadata + */ + const updateAppMeta = 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 createApp = 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 updateAppSecret = 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 ({ Id }: OAuth2Application, password: string): Promise => { + await axios.post(`${o2EndpointUrl}?action=delete`, { password, 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 + apiCall(async () => { + _oauth2Apps.value = await getApps() + + //Load the oauth2 scopes + const { data } = await axios.get(get(scopeEndpoint)) + set(scopes, data) + }) + }) + + return storeExport({ + oauth2:{ + apps, + scopes, + getApps, + createApp, + updateAppMeta, + updateAppSecret, + deleteApp, + refresh + } + }) + } +} \ No newline at end of file -- cgit