Skip to content
Open
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
3 changes: 3 additions & 0 deletions lib/globals.d.ts → globals.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
*/

declare global {
// eslint-disable-next-line camelcase
var _nc_auth_requestToken: string | undefined

interface Window {
_oc_isadmin?: boolean
}
Expand Down
4 changes: 2 additions & 2 deletions lib/csp-nonce.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/

import { getRequestToken } from './requesttoken.ts'
import { getRequestToken } from './requestToken.ts'

/**
* Get the CSP nonce for script loading
Expand All @@ -18,7 +18,7 @@ import { getRequestToken } from './requesttoken.ts'
*/
export function getCSPNonce(): string | undefined {
const meta = document?.querySelector<HTMLMetaElement>('meta[name="csp-nonce"]')
// backwards compatibility with older Nextcloud versions
// backwards compatibility with older Nextcloud versions (before Nextcloud 30)
if (!meta) {
const token = getRequestToken()
return token ? btoa(token) : undefined
Expand Down
1 change: 1 addition & 0 deletions lib/eventbus.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type { NextcloudUser } from './user.ts'

declare module '@nextcloud/event-bus' {
export interface NextcloudEvents {
'csrf-token-update': { token: string, _internal?: true }
// mapping of 'event name' => 'event type'
'user:info:changed': NextcloudUser
}
Expand Down
4 changes: 2 additions & 2 deletions lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/

export type { CsrfTokenObserver } from './requesttoken.ts'
export type { CsrfTokenObserver } from './requestToken.ts'
export type { NextcloudUser } from './user.ts'

export { getCSPNonce } from './csp-nonce.ts'
export { getGuestNickname, getGuestUser, setGuestNickname } from './guest.ts'
export { getRequestToken, onRequestTokenUpdate } from './requesttoken.ts'
export { fetchRequestToken, getRequestToken, onRequestTokenUpdate, setRequestToken } from './requestToken.ts'
export { getCurrentUser } from './user.ts'
107 changes: 107 additions & 0 deletions lib/requestToken.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/**
* SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/

import { emit, subscribe } from '@nextcloud/event-bus'
import { generateUrl } from '@nextcloud/router'

export interface CsrfTokenObserver {
(token: string): void
}

_subscribeToTokenUpdates() // TODO: remove once we drop support for Nextcloud 33.0.1 and before

/**
* Get current request token
*
* @return Current request token or null if not set
*/
export function getRequestToken(): string | null {
if (globalThis._nc_auth_requestToken) {
return globalThis._nc_auth_requestToken
}

if (globalThis.document) {
// for service workers or other contexts without DOM we need to safeguard this
return document.head.dataset.requesttoken ?? null
}
return null
}
Comment on lines +20 to +30
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the previous implementation it only used DOM API once to initiate the local state. Now every getRequestToken call gets it via DOM API.

What about checking _nc_auth_requestToken first and only as a fallback get it from the document to init the local state?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then we still have a problem if updated outside of this library (aka old server versions).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(I mean we can do so and hope to update server in time - but still wonder how much overhead the DOM API call would be here)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the problem?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • If we change the token here in an app A
  • Then another app B causes a token change in server
  • Now this app A tries to get the token and gets the outdated because server only updated the DOM
  • App A needs 2 useless requests to recover

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't setToken on server emit the event even on a very old server?
nextcloud/server#17404

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yes seems to work.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let me adjust this

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have adjusted the implementation to use the cached version if possible.
Also prevented an event loop

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(see fixup commit)


/**
* Set a new CSRF token (e.g. because of session refresh).
* This also emits an event bus event for the updated token.
*
* @param token - The new token
* @fires Error - If the passed token is not a potential valid token
*/
export function setRequestToken(token: string): void {
if (!token || typeof token !== 'string') {
throw new Error('Invalid CSRF token given', { cause: { token } })
}

if (globalThis._nc_auth_requestToken === token) {
// token is the same as before, no need to update and especially no need to notify the observers
return
}

globalThis._nc_auth_requestToken = token
if (globalThis.document) {
// For DOM environments we also set the token to the DOM, so it is available for legacy code
document.head.dataset.requesttoken = token
}

emit('csrf-token-update', { token, _internal: true })
}

/**
* Fetch the request token from the API.
* This does also set it on the current context, see `setRequestToken`.
*
* @fires Error - If the request failed
*/
export async function fetchRequestToken(): Promise<string> {
const url = generateUrl('/csrftoken')

const response = await fetch(url)
if (!response.ok) {
throw new Error('Could not fetch CSRF token from API', { cause: response })
}

const { token } = await response.json()
setRequestToken(token)
return token
}

/**
* Add an observer which is called when the CSRF token changes
*
* @param observer The observer
*/
export function onRequestTokenUpdate(observer: CsrfTokenObserver): void {
subscribe('csrf-token-update', async ({ token }) => {
try {
observer(token)
} catch (error) {
// we cannot use the logger as the logger uses this library = circular dependency
// eslint-disable-next-line no-console
console.error('Error updating CSRF token observer', error)
}
})
}

/**
* Subscribe to token update events from server.
*
* @todo - This is legacy and not needed once all supported server versions use `setRequestToken` of this library.
*/
function _subscribeToTokenUpdates(): void {
// Listen to server event and keep token in sync
subscribe('csrf-token-update', ({ token, _internal }) => {
if (!_internal) {
// Only update the token if the event is not emitted from this library, otherwise we would end in a loop
setRequestToken(token)
}
})
}
50 changes: 0 additions & 50 deletions lib/requesttoken.ts

This file was deleted.

Loading
Loading