thewordisbird / VaultLink

Obsidian plugin to link your vault to a cloud storage provider.
2 stars 0 forks source link

Connect dropbox #3

Closed thewordisbird closed 7 months ago

thewordisbird commented 8 months ago

Setup

Use the dropbox sdk to connect with dropbox. This will handle all the requirements to setup a proper PKCE flow.

Register the application with dropbox to use the dropbox sdk.

Install dropbox sdk:

$ yarn add dropbox

Authorization Flow

The plugin can be considered a client side application. Therefore, the suggested oauth2 authorization flow is the PKCE (Proof Key for Code Exchange) flow. The dropbox sdk handles almost all the steps in the flow. The only responsibility of the app is to properly store the refresh token to allow the authorization state to be persistent - the user will not have to authorize the plugin every time they start the application.

PKCE Flow Overview

The following is taken from the PKCE RFC(1):

Screenshot 2024-03-22 at 1 58 03 PM

A. The client creates and records a secret named the "code_verifier" and derives a transformed version "t(code_verifier)" (referred to as the "code_challenge"), which is sent in the OAuth 2.0 Authorization Request along with the transformation method "t_m".

B. The Authorization Endpoint responds as usual but records "t(code_verifier)" and the transformation method.

C. The client then sends the authorization code in the Access Token Request as usual but includes the "code_verifier" secret generated at (A).

D. The authorization server transforms "code_verifier" and compares it to "t(code_verifier)" from (B). Access is denied if they are not equal.

Plugin Implementation

The authorization will be triggered in the settings view.

  1. The user will click a button to start the authorization flow
  2. The user will be redirected to the dropbox authorization site in their web browser where the will authorize the application
  3. The user will be returned to the obsidian application. The protocolHandler will handle the authorization response:
    1. exchange the authorization code for an access token
    2. set the access token in the dropbox sdk
    3. set the refresh token in the dropbox sdk
    4. store the refresh token in localstorage.

Implementation in code (The UI is not included in these notes. Can be seen in settings.ts file):

const REDIRECT_URI = "obsidian://connect-dropbox";

// Create the authorization url and redirect the user to accept authorization for the application
dropboxAuth.getAuthenticationUrl(
    REDIRECT_URI, // redirectUri
    undefined, // state
    "code", // authType
    "offline", // tokenAccessType
    undefined, // scope
    undefined, // includeGrantedScopes
    true, // usePKCE
)
.then((authUrl) => {
    window.sessionStorage.clear();
    window.sessionStorage.setItem(
        "codeVerifier",
        this.dropboxAuth.getCodeVerifier(),
    );
    window.location.href = authUrl as string;
})
.catch((error) => console.error(error));

Important Implementation Details:

// Exchange the authorization token for an access token. This is run in the obsidian protocol handler
getAccessToken(protocolData: ObsidianProtocolData): Promise<void> {
    const { code } = protocolData;

    if (!code) throw new Error("Authorization Error: Code Not Available");

    const codeVerifier = window.sessionStorage.getItem("codeVerifier");
    if (!codeVerifier) {
        throw new Error("Authorization Error: Code Verifier Not Available");
    }

    this.dropboxAuth.setCodeVerifier(codeVerifier);

    return this.dropboxAuth
        .getAccessTokenFromCode(REDIRECT_URI, code)
        .then(
            (
                response: DropboxResponse<{
                    access_token: string;
                    refresh_token: string;
                }>,
            ) => {
                this.dropboxAuth.setAccessToken(
                    response.result.access_token,
                );

                this.dropboxAuth.setRefreshToken(
                    response.result.refresh_token,
                );

                // Store Refresh token in local storage for persistant authorization
                localStorage.setItem(
                    "dropboxRefreshToken",
                    response.result.refresh_token,
                );
            },
        )
        .catch((error) => console.error(error));
}

Important Implementation Details:

Refresh Token and Continued Access

The authorization flow handles storing the refresh token after initial authorization. The refresh token is also stored in local storage to allow the plugin to attempt to reauthorize on loading the plugin. This is handled in the main.ts file in the Plugin class. The following code is in the dropboxProvider

    authorizeWithRefreshToken(): void {
        const refreshToken = localStorage.getItem("dropboxRefreshToken");
        if (!refreshToken) return;

        this.dropboxAuth.setRefreshToken(refreshToken);
        this.dropboxAuth.refreshAccessToken();
    }

Resouces

  1. PKCE R.F.C
  2. Where to store JWT refresh tokens