// 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
}
}
}
}