mirror of
https://github.com/enso-org/enso.git
synced 2025-01-03 03:44:03 +03:00
3d045a7ceb
* turn object into var * add todo * fix style * fixes * remove forgot password + reset password * remove signout * remove setusername * remove login * remove registration * fix comments * re-enable flag * rename div * add comment * fix lints * remove tailwind conf * Revert "remove registration" This reverts commit02439c9b70
. * Revert "remove login" This reverts commit8e6f9c1112
. * Revert "remove setusername" This reverts commit84721bcccd
. * Revert "remove signout" This reverts commit08a96d3796
. * Revert "remove forgot password + reset password" This reverts commite52f51a762
. * remove opener * move opener * tmp * prettier * expand docs * tmp * replace react-scripts with craco * add tailwindcss * switch to brands * tmp * tmp * tmp * fixmes * fixmes * fixmes * fixmes * fixmes * fixes for e-hern's comments * use abortcontroller * add docs * fixes * revert craco, fix windows build * remove from gitignore * remove unnecessary check * tmp * augment window * tmptmp * split errors back up * tmp * tmp * prettier * fix * Fix lints * Prepare for addition for `as T` lint * Add lint for early returns * Address review issues * Fix lints * remove withrouter * fix file length * fixes * fixes * remove dashboard * fix * use switch * prettier * fixes * prettier * fixes * run prettier * run prettier * run prettier * fix main page url * allow node.js debugging * fix lints * change not equal * prettier * Remove references to withRouter; fix lints * Run prettier * Add cloud endpoints * Add JSON-RPC endpoints * Add dashboard skeleton * Add components and edit dashboard * Run prettier * (WIP) Add cloud endpoints * Add rpc endpoints * Address review issues * Formatting and minor fixes for `newtype.ts` * Address review issues * Rename `Brand` to `NewtypeVariant` * Rename `Brand` to `NewtypeVariant` * Fix formatting in `newtype.ts` * Switch dashboard to esbuild * Minor fixes; move Tailwind generation into esbuild-config * Fix watching `content/` and `client/` * Bump esbuild binary versions; minor dependency list fixes * Add dashboard skeleton * Run prettier * Fixes; rename "npm run dev" to "npm run watch-dashboard" * Avoid writing esbuild outputs to disk for `dashboard/` * Convert watch-dashboard to be fully in-memory; rebuild css files on change * Remove obsolete FIXME * Remove unused constants * Run prettier * add missing styles * Fixes * Fix the fixes * Run prettier * Fixes; use nesting plugin to wrap tailwind preflight * Remove testing flag from client/watch * Minor fixes * Run prettier * Export newtypes * Make css rebuild when tailwind config changes * Fix endpoints * Finish copying changes over * Remove duplicate type definitions * Fix bundling for dashboard * Fix dashboard/bundle.ts erroring when build directory does not exist * Move CSS to Tailwind config * Run prettier * Update endpoints * Fix esbuild binary package names * Remove redundant "npx" prefix from build scripts * Remove unused dependency * Begin adding interactivity * workaround for mac freeze * Fix modal bugs * Begin implementing forms, split forms and modals into new files * Get form UI working * add missing sections * Minor fixes, save current directory to localStorage * Fixes for drop-to-upload Note: currently it is opening in a new tab instead of actually uploading * Address review issue * Fix prettier config; run prettier * Fix live-reload of `npm run watch-dashboard` * (WIP) * Fix service worker for client-side routing * Add close button to asset creation forms; fix saving directory to localStorage * Remove workaround for backend bug when listing directories * Fix drop-to-upload * Fix sizing * Fix spacing, add fixed paths * WIP: fix toast notification styles, begin adding context menu * WIP: Add context menu, minor fixes * Fix authentication on desktop IDE * Allow unused locals and parameters in tsconfig.json * Run prettier * Fix TypeScript errors * Fix modals; minor refactor * Implement context menus for labels; fixes * Add modal provider and switch all modals to provider; fixes * Fix modals and user icon size * Fixes * Remove obsolete files from incorrect merge * Address review issues * Fix type error * Stop removing `#root` * Fixes for cloud * Implement search on frontend side * Fix race condition related to `directoryId` * Hide directories, files and secrets tables on desktop IDE * Fix lint errors * Properly update visible projects when a project is created * Pass directory id to create project * Hide column display switcher; remove placeholder column data --------- Co-authored-by: Nikita Pekin <nikita@frecency.com> Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> Co-authored-by: Paweł Buchowski <pawel.buchowski@enso.org>
188 lines
8.9 KiB
TypeScript
188 lines
8.9 KiB
TypeScript
/** @file Definition of the Electron-specific parts of the authentication flows of the IDE.
|
|
*
|
|
* # Overview of Authentication/Authorization
|
|
*
|
|
* Actions like creating projects, opening projects, uploading files to the cloud, etc. require the
|
|
* user to be authenticated and authorized. Authenticated means that the user has an account that
|
|
* the application recognizes, and that the user has provided their credentials to prove that they
|
|
* are who they say they are. Authorized means that the user has the necessary permissions to
|
|
* perform the action.
|
|
*
|
|
* Authentication and authorization are provided by the user logging in with their credentials,
|
|
* which we exchange for a JSON Web Token (JWT). The JWT is sent with every HTTP request to the
|
|
* backend.
|
|
*
|
|
* The authentication module of the dashboard and IDE handles these flows:
|
|
* - registering a new user account,
|
|
* - signing in to an existing user account (in exchange for an access token),
|
|
* - signing out of the user account,
|
|
* - setting the user's username (i.e., display name used in place of their email address),
|
|
* - changing/resetting the user's password,
|
|
* - etc.
|
|
*
|
|
* # Electron Inter-Process Communication (IPC)
|
|
*
|
|
* If the user is signing in through a federated identity provider (e.g., Google or GitHub), the
|
|
* authentication flows need be able to to:
|
|
* - redirect the user from the IDE to external sources (e.g., system web browser), and
|
|
* - redirect the user from external sources to the IDE (e.g., system web browser, email client).
|
|
*
|
|
* The main Electron process can launch the system web browser. The dashboard and IDE are sandboxed,
|
|
* so they can not launch the system web browser. By registering Inter-Process Communication (IPC)
|
|
* listeners in the Electron app, we can bridge this gap, and allow the dashboad + IDE to emit
|
|
* events that signal to the main Electron process to open URLs in the system web browser.
|
|
*
|
|
* ## Redirect To System Web Browser
|
|
*
|
|
* The user must use the system browser to complete sensitive flows such as signup and signin. These
|
|
* flows should not be done in the app as the user cannot be expected to trust the app with their
|
|
* credentials.
|
|
*
|
|
* To redirect the user from the IDE to an external source:
|
|
* 1. Call the {@link initIpc} function to register a listener for
|
|
* {@link ipc.Channel.openUrlInSystemBrowser} IPC events.
|
|
* 2. Emit an {@link ipc.Channel.openUrlInSystemBrowser} event. The listener registered in the
|
|
* {@link initIpc} function will use the {@link opener} library to open the event's {@link URL}
|
|
* argument in the system web browser, in a cross-platform way.
|
|
*
|
|
* ## Redirect To IDE
|
|
*
|
|
* The user must be redirected back to the IDE from the system web browser after completing a
|
|
* sensitive flow such as signup or signin. The user may also be redirected to the IDE from an
|
|
* external source such as an email client after verifying their email address.
|
|
*
|
|
* To handle these redirects, we use deep links. Deep links are URLs that are used to redirect the
|
|
* user to a specific page in the application. To handle deep links, we use a custom URL protocol
|
|
* scheme.
|
|
*
|
|
* To prepare the application to handle deep links:
|
|
* - Register a custom URL protocol scheme with the OS (c.f., `electron-builder-config.ts`).
|
|
* - Define a listener for Electron {@link OPEN_URL_EVENT}s (c.f., {@link initOpenUrlListener}).
|
|
* - Define a listener for {@link ipc.Channel.openDeepLink} events (c.f., `preload.ts`).
|
|
*
|
|
* Then when the user clicks on a deep link from an external source to the IDE:
|
|
* - The OS redirects the user to the application.
|
|
* - The application emits an Electron {@link OPEN_URL_EVENT}.
|
|
* - The {@link OPEN_URL_EVENT} listener checks if the {@link URL} is a deep link.
|
|
* - If the {@link URL} is a deep link, the {@link OPEN_URL_EVENT} listener prevents Electron from
|
|
* handling the event.
|
|
* - The {@link OPEN_URL_EVENT} listener then emits an {@link ipc.Channel.openDeepLink} event.
|
|
* - The {@link ipc.Channel.openDeepLink} listener registered by the dashboard receives the event.
|
|
* Then it parses the {@link URL} from the event's {@link URL} argument. Then it uses the
|
|
* {@link URL} to redirect the user to the dashboard, to the page specified in the {@link URL}'s
|
|
* `pathname`. */
|
|
|
|
import * as fs from 'node:fs'
|
|
import * as os from 'node:os'
|
|
import * as path from 'node:path'
|
|
|
|
import * as electron from 'electron'
|
|
import opener from 'opener'
|
|
|
|
import * as common from 'enso-common'
|
|
import * as contentConfig from 'enso-content-config'
|
|
import * as urlAssociations from 'url-associations'
|
|
|
|
import * as ipc from 'ipc'
|
|
|
|
const logger = contentConfig.logger
|
|
|
|
// ========================================
|
|
// === Initialize Authentication Module ===
|
|
// ========================================
|
|
|
|
/** Configures all the functionality that must be set up in the Electron app to support
|
|
* authentication-related flows. Must be called in the Electron app `whenReady` event.
|
|
*
|
|
* @param window - A function that returns the main Electron window. This argument is a lambda and
|
|
* not a variable because the main window is not available when this function is called. This module
|
|
* does not use the `window` until after it is initialized, so while the lambda may return `null` in
|
|
* theory, it never will in practice. */
|
|
export function initModule(window: () => electron.BrowserWindow) {
|
|
initIpc()
|
|
initOpenUrlListener(window)
|
|
initSaveAccessTokenListener()
|
|
}
|
|
|
|
/** Registers an Inter-Process Communication (IPC) channel between the Electron application and the
|
|
* served website.
|
|
*
|
|
* This channel listens for {@link ipc.Channel.openUrlInSystemBrowser} events. When this kind of
|
|
* event is fired, this listener will assume that the first and only argument of the event is a URL.
|
|
* This listener will then attempt to open the URL in a cross-platform way. The intent is to open
|
|
* the URL in the system browser.
|
|
*
|
|
* This functionality is necessary because we don't want to run the OAuth flow in the app. Users
|
|
* don't trust Electron apps to handle their credentials. */
|
|
function initIpc() {
|
|
electron.ipcMain.on(ipc.Channel.openUrlInSystemBrowser, (_event, url: string) => {
|
|
logger.log(`Opening URL in system browser: '${url}'.`)
|
|
urlAssociations.setAsUrlHandler()
|
|
opener(url)
|
|
})
|
|
}
|
|
|
|
/** Registers a listener that fires a callback for `open-url` events, when the URL is a deep link.
|
|
*
|
|
* This listener is used to open a page in *this* application window, when the user is
|
|
* redirected to a URL with a protocol supported by this application.
|
|
*
|
|
* All URLs that aren't deep links (i.e., URLs that don't use the {@link common.DEEP_LINK_SCHEME}
|
|
* protocol) will be ignored by this handler. Non-deep link URLs will be handled by Electron. */
|
|
function initOpenUrlListener(window: () => electron.BrowserWindow) {
|
|
urlAssociations.registerUrlCallback(url => {
|
|
onOpenUrl(url, window)
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Handles the 'open-url' event by parsing the received URL, checking if it is a deep link, and
|
|
* sending it to the appropriate BrowserWindow via IPC.
|
|
*
|
|
* @param url - The URL to handle.
|
|
* @param window - A function that returns the BrowserWindow to send the parsed URL to.
|
|
*/
|
|
export function onOpenUrl(url: URL, window: () => electron.BrowserWindow) {
|
|
logger.log(`Received 'open-url' event for '${url.toString()}'.`)
|
|
if (url.protocol !== `${common.DEEP_LINK_SCHEME}:`) {
|
|
logger.error(`'${url.toString()}' is not a deep link, ignoring.`)
|
|
} else {
|
|
logger.log(`'${url.toString()}' is a deep link, sending to renderer.`)
|
|
window().webContents.send(ipc.Channel.openDeepLink, url.toString())
|
|
}
|
|
}
|
|
|
|
/** Registers a listener that fires a callback for `save-access-token` events.
|
|
*
|
|
* This listener is used to save given access token to credentials file to be later used by enso backend.
|
|
*
|
|
* Credentials file is placed in users home directory in `.enso` subdirectory in `credentials` file. */
|
|
function initSaveAccessTokenListener() {
|
|
electron.ipcMain.on(ipc.Channel.saveAccessToken, (event, accessToken: string) => {
|
|
/** Enso home directory for credentials file. */
|
|
const ensoCredentialsDirectoryName = '.enso'
|
|
/** Enso credentials file. */
|
|
const ensoCredentialsFileName = 'credentials'
|
|
/** System agnostic credentials directory home path. */
|
|
const ensoCredentialsHomePath = path.join(os.homedir(), ensoCredentialsDirectoryName)
|
|
|
|
fs.mkdir(ensoCredentialsHomePath, { recursive: true }, error => {
|
|
if (error) {
|
|
logger.error(`Couldn't create ${ensoCredentialsDirectoryName} directory.`)
|
|
} else {
|
|
fs.writeFile(
|
|
path.join(ensoCredentialsHomePath, ensoCredentialsFileName),
|
|
accessToken,
|
|
innerError => {
|
|
if (innerError) {
|
|
logger.error(`Could not write to ${ensoCredentialsFileName} file.`)
|
|
}
|
|
}
|
|
)
|
|
}
|
|
})
|
|
|
|
event.preventDefault()
|
|
})
|
|
}
|