Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix auth renewal #211

Merged
merged 3 commits into from
May 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions changelog/unreleased/bugfix-clear-state-when-unauthorized
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Bugfix: Clear state when unauthorized

The filepicker now reacts to `401` responses by resetting the internal authentication state to `unauthorized`,
forcing the user to log in again.
This situation can happen when an access token that's not expired, yet, was invalidated server side (e.g. through
a backchannel logout or session inactivity) and would previously lead to a broken application state.

https://github.com/owncloud/file-picker/pull/211
7 changes: 7 additions & 0 deletions changelog/unreleased/bugfix-token-renewal-hook
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Bugfix: Reduce requests on token renewal

We've fixed a bug that caused always re-fetching the logged in user and the server capabilities on token renewal
under certain circumstances. Now the logged in user and the server capabilities are only fetched once after
successful authentication.

https://github.com/owncloud/file-picker/pull/211
71 changes: 45 additions & 26 deletions src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,11 @@
:is-select-btn-displayed="isSelectBtnDisplayed"
:cancel-btn-label="cancelBtnLabel"
:is-initial-focus-enabled="isInitialFocusEnabled"
@update="selectResources"
@select="emitSelectBtnClick"
@auth-error="handleAuthError"
@cancel="cancel"
@folderLoaded="onFolderLoaded"
@folder-loaded="onFolderLoaded"
@select="emitSelectBtnClick"
@update="selectResources"
/>
</div>
</template>
Expand Down Expand Up @@ -170,11 +171,25 @@ export default {
authInstance.mgr.signinPopupCallback()
}

const get_axios_instance = (token) => {
const handleAuthError = async () => {
if (authInstance) {
await authInstance.mgr.clearStaleState()
state.value = 'unauthorized'
}
}

const createAxiosInstance = (token) => {
const instance = axios.create({
headers: { Authorization: token.startsWith('Bearer') ? token : `Bearer ${token}` }
})

instance.interceptors.response.use(
(response) => response,
(error) => {
if (error?.response?.status === 401) {
return handleAuthError()
}
}
)
return instance
}

Expand All @@ -199,7 +214,7 @@ export default {
const initApp = async () => {
try {
const token = await authInstance.getToken()
axiosInstance = get_axios_instance(token)
axiosInstance = createAxiosInstance(token)
const _client = webClient(config.value.server, axiosInstance)
// const { data: userData } = await _client.graph.users.getMe()

Expand All @@ -215,8 +230,11 @@ export default {
state.value = 'authorized'

emit('token', token)

return true
} catch (error) {
console.error(error)
return false
}
}

Expand All @@ -231,24 +249,6 @@ export default {
emit('token', token)
}

const checkUserAuthentication = async () => {
if (await authInstance.isAuthenticated()) {
// If the user is authenticated, we add event listener when he's updated to automatically update the token
authInstance.mgr.events.addUserLoaded(() => {
updateBearerToken()
})

return initApp()
}

state.value = 'unauthorized'

// If the user is not authenticated, we add event listener when he logs in to automatically init the application afterwards
authInstance.mgr.events.addUserLoaded(() => {
initApp()
})
}

const initAuthentication = async () => {
config.value = await loadConfig(props.configObject, props.configLocation)

Expand All @@ -268,7 +268,25 @@ export default {
return
}

checkUserAuthentication()
authInstance.mgr.events.addUserLoaded(() => {
if (state.value === 'authorized') {
updateBearerToken()
} else {
initApp()
}
})

authInstance.mgr.events.addSilentRenewError(() => {
handleAuthError()
})

if (await authInstance.isAuthenticated()) {
if (await initApp()) {
return
}
await authInstance.mgr.clearStaleState()
}
state.value = 'unauthorized'
}

const authenticate = () => {
Expand Down Expand Up @@ -321,7 +339,8 @@ export default {
authenticate,
emitSelectBtnClick,
onFolderLoaded,
selectResources
selectResources,
handleAuthError
}
}
}
Expand Down
5 changes: 4 additions & 1 deletion src/components/FilePicker.vue
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ export default defineComponent({
}
},

emits: ['select', 'cancel', 'update', 'folderLoaded'],
emits: ['authError', 'cancel', 'folderLoaded', 'select', 'update'],

setup(props, { emit }) {
let currentSpace = null
Expand Down Expand Up @@ -181,6 +181,9 @@ export default defineComponent({
isInitial = false
} catch (error) {
console.error(error)
if (error?.response?.status === 401) {
emit('authError')
}

state.value = 'failed'
}
Expand Down
6 changes: 3 additions & 3 deletions src/services/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,12 +89,12 @@ export function initVueAuthenticate(config) {
})

mgr.events.addSilentRenewError(() => {
mgr.clearStaleState()
return mgr.clearStaleState()
})

return {
authenticate() {
mgr.clearStaleState()
async authenticate() {
await mgr.clearStaleState()
return mgr.signinPopup()
},

Expand Down