aboutsummaryrefslogtreecommitdiff
path: root/front-end/src/store/oauthAppsPlugin.ts
blob: d12eed24f3da76911fb16e49f7133a725b05ecde (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
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 <https://www.gnu.org/licenses/>.

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<NewAppResponse>
    updateSecret(app: OAuth2Application, password: string): Promise<string>
    updateMeta(app: OAuth2Application): Promise<void>
    deleteApp(app: OAuth2Application, password: string): Promise<void>
}

declare module 'pinia' {
    export interface PiniaCustomProperties {
        oauth2:{
            apps: OAuth2Application[],
            scopes: string[],
            refresh(): void
        } & OAuth2Api
    }
}

export const oauth2AppsPlugin = (o2EndpointUrl: MaybeRef<string>, scopeEndpoint: MaybeRef<string>): PiniaPlugin => {

    return ({ store }: PiniaPluginContext) => {

        const axios = useAxios(null);
        const { loggedIn } = storeToRefs(store)

        const [onRefresh, refresh] = useToggle()

        const _oauth2Apps = shallowRef<OAuth2Application[]>([])
        const scopes = shallowRef<string[]>([])

        /**
        * Updates an Oauth2 application's metadata
        */
        const updateMeta = async (app: OAuth2Application): Promise<void> => {
            //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<OAuth2Application[]>(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<NewAppResponse> => {

            // make the post request, response is the new app data with a secret
            const { data } = await axios.post<OAuth2Application & { raw_secret: string }>(`${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<string> => {
            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<void> => {
            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<string[]>(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
            }
        }
    }
}