diff --git a/src/components/PasswordDialog.vue b/src/components/PasswordDialog.vue index bf6a77d..ce353cb 100644 --- a/src/components/PasswordDialog.vue +++ b/src/components/PasswordDialog.vue @@ -21,7 +21,7 @@ + :disabled="!password || loading"> @@ -59,6 +59,13 @@ export default defineComponent({ NcPasswordField, }, + props: { + callback: { + type: Function, + required: true, + }, + }, + setup() { // non reactive props return { @@ -102,10 +109,8 @@ export default defineComponent({ return } - const url = generateUrl('/login/confirm') try { - const { data } = await axios.post(url, { password: this.password }) - window.nc_lastLogin = data.lastLogin + await this.callback(this.password) this.$emit('confirmed') } catch (e) { this.showError = true diff --git a/src/main.ts b/src/main.ts index 2c75dba..fea08ee 100644 --- a/src/main.ts +++ b/src/main.ts @@ -3,11 +3,14 @@ * SPDX-License-Identifier: MIT */ import Vue from 'vue' +import type { ComponentInstance } from 'vue' + +import { Axios } from '@nextcloud/axios' +import { getCurrentUser } from '@nextcloud/auth' + import PasswordDialogVue from './components/PasswordDialog.vue' import { DIALOG_ID, MODAL_CLASS } from './globals' -import type { ComponentInstance } from 'vue' - const PAGE_LOAD_TIME = Date.now() /** @@ -34,15 +37,25 @@ export const isPasswordConfirmationRequired = (): boolean => { * or confirmation is already in process. */ export const confirmPassword = (): Promise => { + if (!isPasswordConfirmationRequired()) { + return Promise.resolve() + } + + return getPasswordDialog() +} + +/** + * + * @param mode + * @param callback + * @return + */ +function getPasswordDialog(callback?: (password: string) => Promise): Promise { const isDialogMounted = Boolean(document.getElementById(DIALOG_ID)) if (isDialogMounted) { return Promise.reject(new Error('Password confirmation dialog already mounted')) } - if (!isPasswordConfirmationRequired()) { - return Promise.resolve() - } - const mountPoint = document.createElement('div') mountPoint.setAttribute('id', DIALOG_ID) @@ -61,7 +74,7 @@ export const confirmPassword = (): Promise => { const DialogClass = Vue.extend(PasswordDialogVue) // Mount point element is replaced by the component - const dialog = (new DialogClass() as ComponentInstance).$mount(mountPoint) + const dialog = (new DialogClass({ propsData: { callback } }) as ComponentInstance).$mount(mountPoint) return new Promise((resolve, reject) => { dialog.$on('confirmed', () => { @@ -74,3 +87,64 @@ export const confirmPassword = (): Promise => { }) }) } + +/** + * Add interceptors to an axios instance that for every request + * will prompt for password confirmation and add it as Basic Auth. + * @param axios + */ +export function withPasswordConfirmation(axios: Axios): Axios { + let resolvePwdDialogCallback: (value: unknown) => void + let rejectPwdDialogCallback: (reason?: any) => void + let firstTry = true + + const pwdConfirmationAxios = axios.create() as Axios + + // TODO: copy axios instance + pwdConfirmationAxios.interceptors.request.use(async (config) => { + return new Promise((resolve, reject) => { + getPasswordDialog((password: string) => { + const authenticatedConfig = { + ...config, + auth: { + username: getCurrentUser()?.uid ?? '', + password, + }, + } + + if (firstTry) { + firstTry = false + resolve(authenticatedConfig) + } else { + return axios.request(authenticatedConfig) + } + + const { promise, resolve: subResolve, reject: subReject } = Promise.withResolvers() + resolvePwdDialogCallback = subResolve + rejectPwdDialogCallback = subReject + + return promise + }) + }) + }) + + pwdConfirmationAxios.interceptors.response.use( + (response) => { + resolvePwdDialogCallback(undefined) + return response + }, + (error) => { + // TODO intercept reject to inform dialog + if (error.config.auth !== undefined) { + rejectPwdDialogCallback(error) + } + }, + { + runWhen(config) { + return config.auth !== undefined + }, + }, + ) + + return pwdConfirmationAxios +}