From d247cc6cad70dfb2d48e3504cedc19f9bf15f8e3 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 6 Jun 2024 12:11:15 +0530 Subject: [PATCH 01/16] Reword template --- web/apps/photos/.env.development | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/web/apps/photos/.env.development b/web/apps/photos/.env.development index fd4d63c081..5b43c1bb87 100644 --- a/web/apps/photos/.env.development +++ b/web/apps/photos/.env.development @@ -11,15 +11,18 @@ #NEXT_PUBLIC_ENTE_ENDPOINT = http://localhost:8080 -# If you wish to preview how the shared albums work, you can use `yarn -# dev:albums`. You'll need to run two instances. - -# The equivalent CLI commands using env vars would be: +# We also have various sidecar apps. These all run on a separate port, 3002, +# since usually when developing we usually need to run only one of them in +# addition to the main photos app. So you can uncomment this entire set. # -# # For the normal web app -# NEXT_PUBLIC_ENTE_ENDPOINT=http://localhost:8080 NEXT_PUBLIC_ENTE_ALBUMS_ENDPOINT=http://localhost:3002 yarn dev:photos +# For example, if you wish to preview how the shared albums work, you can +# uncomment these (and the NEXT_PUBLIC_ENTE_ENDPOINT above), and run two apps: # -# # For the albums app -# NEXT_PUBLIC_ENTE_ENDPOINT=http://localhost:8080 NEXT_PUBLIC_ENTE_ALBUMS_ENDPOINT=http://localhost:3002 yarn dev:albums +# - `yarn dev:photos` (the main app) +# - `yarn dev:albums` (the sidecar app, in this case, albums) +# +# You'll also need to create a similar `.env.local` or `.env.development.local` +# in the app you're running (e.g. in apps/accounts). #NEXT_PUBLIC_ENTE_ALBUMS_ENDPOINT = http://localhost:3002 +#NEXT_PUBLIC_ENTE_ACCOUNTS_ENDPOINT = http://localhost:3002 From c035d5cafa30c815fa6d0d54f17a5ec27126af65 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 6 Jun 2024 12:16:07 +0530 Subject: [PATCH 02/16] Ren --- web/apps/photos/.env.development | 1 + web/packages/accounts/pages/credentials.tsx | 4 ++-- web/packages/accounts/pages/verify.tsx | 4 ++-- web/packages/shared/network/api.ts | 17 ++++++++++------- 4 files changed, 15 insertions(+), 11 deletions(-) diff --git a/web/apps/photos/.env.development b/web/apps/photos/.env.development index 5b43c1bb87..1fd1fcf31e 100644 --- a/web/apps/photos/.env.development +++ b/web/apps/photos/.env.development @@ -26,3 +26,4 @@ #NEXT_PUBLIC_ENTE_ALBUMS_ENDPOINT = http://localhost:3002 #NEXT_PUBLIC_ENTE_ACCOUNTS_ENDPOINT = http://localhost:3002 +#NEXT_PUBLIC_ENTE_PAYMENTS_ENDPOINT = http://localhost:3002 diff --git a/web/packages/accounts/pages/credentials.tsx b/web/packages/accounts/pages/credentials.tsx index 28b908b28a..4d6bae3f57 100644 --- a/web/packages/accounts/pages/credentials.tsx +++ b/web/packages/accounts/pages/credentials.tsx @@ -18,7 +18,7 @@ import { } from "@ente/shared/crypto/helpers"; import type { B64EncryptionResult } from "@ente/shared/crypto/types"; import { CustomError } from "@ente/shared/error"; -import { getAccountsURL, getEndpoint } from "@ente/shared/network/api"; +import { accountsAppURL, getEndpoint } from "@ente/shared/network/api"; import InMemoryStore, { MS_KEYS } from "@ente/shared/storage/InMemoryStore"; import { LS_KEYS, @@ -166,7 +166,7 @@ const Page: React.FC = ({ appContext }) => { isTwoFactorPasskeysEnabled: true, }); InMemoryStore.set(MS_KEYS.REDIRECT_URL, PAGES.ROOT); - window.location.href = `${getAccountsURL()}/passkeys/flow?passkeySessionID=${passkeySessionID}&redirect=${ + window.location.href = `${accountsAppURL()}/passkeys/flow?passkeySessionID=${passkeySessionID}&redirect=${ window.location.origin }/passkeys/finish`; return undefined; diff --git a/web/packages/accounts/pages/verify.tsx b/web/packages/accounts/pages/verify.tsx index 87a954979e..852995a88f 100644 --- a/web/packages/accounts/pages/verify.tsx +++ b/web/packages/accounts/pages/verify.tsx @@ -10,7 +10,7 @@ import SingleInputForm, { type SingleInputFormProps, } from "@ente/shared/components/SingleInputForm"; import { ApiError } from "@ente/shared/error"; -import { getAccountsURL } from "@ente/shared/network/api"; +import { accountsAppURL } from "@ente/shared/network/api"; import InMemoryStore, { MS_KEYS } from "@ente/shared/storage/InMemoryStore"; import localForage from "@ente/shared/storage/localForage"; import { LS_KEYS, getData, setData } from "@ente/shared/storage/localStorage"; @@ -85,7 +85,7 @@ const Page: React.FC = ({ appContext }) => { isTwoFactorPasskeysEnabled: true, }); setIsFirstLogin(true); - window.location.href = `${getAccountsURL()}/passkeys/flow?passkeySessionID=${passkeySessionID}&redirect=${ + window.location.href = `${accountsAppURL()}/passkeys/flow?passkeySessionID=${passkeySessionID}&redirect=${ window.location.origin }/passkeys/finish`; router.push(PAGES.CREDENTIALS); diff --git a/web/packages/shared/network/api.ts b/web/packages/shared/network/api.ts index d6cdb403bf..437f4a45f0 100644 --- a/web/packages/shared/network/api.ts +++ b/web/packages/shared/network/api.ts @@ -34,13 +34,16 @@ export const getUploadEndpoint = () => { return `https://uploader.ente.io`; }; -export const getAccountsURL = () => { - const accountsURL = process.env.NEXT_PUBLIC_ENTE_ACCOUNTS_ENDPOINT; - if (accountsURL) { - return accountsURL; - } - return `https://accounts.ente.io`; -}; +/** + * Return the URL of the Ente Accounts app. + * + * Defaults to our production instance, "https://accounts.ente.io", but can be + * overridden by setting the `NEXT_PUBLIC_ENTE_ACCOUNTS_ENDPOINT` environment + * variable to a non-empty value. + */ +export const accountsAppURL = () => + process.env.NEXT_PUBLIC_ENTE_ACCOUNTS_ENDPOINT || + `https://accounts.ente.io`; export const getAlbumsURL = () => { const albumsURL = process.env.NEXT_PUBLIC_ENTE_ALBUMS_ENDPOINT; From 836712c020e8e0f705c7c6980f59d5bb49f5e473 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 6 Jun 2024 12:19:19 +0530 Subject: [PATCH 03/16] Ren --- web/packages/accounts/pages/credentials.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/web/packages/accounts/pages/credentials.tsx b/web/packages/accounts/pages/credentials.tsx index 4d6bae3f57..4d130dd336 100644 --- a/web/packages/accounts/pages/credentials.tsx +++ b/web/packages/accounts/pages/credentials.tsx @@ -18,7 +18,7 @@ import { } from "@ente/shared/crypto/helpers"; import type { B64EncryptionResult } from "@ente/shared/crypto/types"; import { CustomError } from "@ente/shared/error"; -import { accountsAppURL, getEndpoint } from "@ente/shared/network/api"; +import { accountsAppURL, apiOrigin } from "@ente/shared/network/api"; import InMemoryStore, { MS_KEYS } from "@ente/shared/storage/InMemoryStore"; import { LS_KEYS, @@ -313,12 +313,12 @@ const Header_ = styled("div")` `; const ConnectionDetails: React.FC = () => { - const apiOrigin = new URL(getEndpoint()); + const host = new URL(apiOrigin()).host; return ( - {apiOrigin.host} + {host} ); From 71e908c3a230a1f2bb0a8389b868e77d064381bd Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 6 Jun 2024 12:24:17 +0530 Subject: [PATCH 04/16] Separate --- web/apps/photos/.env.development | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/web/apps/photos/.env.development b/web/apps/photos/.env.development index 1fd1fcf31e..fe83001208 100644 --- a/web/apps/photos/.env.development +++ b/web/apps/photos/.env.development @@ -11,19 +11,21 @@ #NEXT_PUBLIC_ENTE_ENDPOINT = http://localhost:8080 -# We also have various sidecar apps. These all run on a separate port, 3002, -# since usually when developing we usually need to run only one of them in -# addition to the main photos app. So you can uncomment this entire set. -# -# For example, if you wish to preview how the shared albums work, you can -# uncomment these (and the NEXT_PUBLIC_ENTE_ENDPOINT above), and run two apps: +# If you wish to preview how the shared albums work, you can also uncomment +# this, and run two apps: # # - `yarn dev:photos` (the main app) # - `yarn dev:albums` (the sidecar app, in this case, albums) -# -# You'll also need to create a similar `.env.local` or `.env.development.local` -# in the app you're running (e.g. in apps/accounts). #NEXT_PUBLIC_ENTE_ALBUMS_ENDPOINT = http://localhost:3002 -#NEXT_PUBLIC_ENTE_ACCOUNTS_ENDPOINT = http://localhost:3002 -#NEXT_PUBLIC_ENTE_PAYMENTS_ENDPOINT = http://localhost:3002 + +# We also have various sidecar apps. These all run on a separate port, 3001, +# since usually when developing we usually need to run only one of them in +# addition to the main photos app. So you can uncomment this entire set. +# +# You'll also need to create a similar `.env.local` or `.env.development.local` +# in the app you're running (e.g. in apps/accounts), and put an +# `NEXT_PUBLIC_ENTE_ENDPOINT` in there. + +#NEXT_PUBLIC_ENTE_ACCOUNTS_ENDPOINT = http://localhost:3001 +#NEXT_PUBLIC_ENTE_PAYMENTS_ENDPOINT = http://localhost:3001 From c51edddb5328985d35997c524242aecd1e4286b9 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 6 Jun 2024 12:26:42 +0530 Subject: [PATCH 05/16] Clarify --- web/apps/payments/README.md | 2 +- web/apps/photos/.env | 12 ++++++------ web/apps/photos/.env.development | 4 ++-- web/packages/shared/network/api.ts | 9 ++++----- 4 files changed, 13 insertions(+), 14 deletions(-) diff --git a/web/apps/payments/README.md b/web/apps/payments/README.md index ebf3a63901..4b0f99402a 100644 --- a/web/apps/payments/README.md +++ b/web/apps/payments/README.md @@ -20,7 +20,7 @@ Add the following to `web/apps/photos/.env.local`: ```env NEXT_PUBLIC_ENTE_ENDPOINT = http://localhost:8080 -NEXT_PUBLIC_ENTE_PAYMENTS_ENDPOINT = http://localhost:3001 +NEXT_PUBLIC_ENTE_PAYMENTS_URL = http://localhost:3001 ``` Then start it locally diff --git a/web/apps/photos/.env b/web/apps/photos/.env index 978c677769..766c3b861e 100644 --- a/web/apps/photos/.env +++ b/web/apps/photos/.env @@ -41,13 +41,13 @@ # # NEXT_PUBLIC_ENTE_ENDPOINT = http://localhost:3000 -# The Ente API endpoint for accounts related functionality +# The URL of the accounts app # -# NEXT_PUBLIC_ENTE_ACCOUNTS_ENDPOINT = http://localhost:3001 +# NEXT_PUBLIC_ENTE_ACCOUNTS_URL = http://localhost:3001 -# The Ente API endpoint for payments related functionality +# The URL of the payments app # -# NEXT_PUBLIC_ENTE_PAYMENTS_ENDPOINT = http://localhost:3001 +# NEXT_PUBLIC_ENTE_PAYMENTS_URL = http://localhost:3001 # The URL for the shared albums deployment # @@ -69,7 +69,7 @@ # # NEXT_PUBLIC_ENTE_ALBUMS_ENDPOINT = http://localhost:3002 -# The URL of the family plans web app deployment +# The URL of the family plans web app # # Currently the source code for the family plan related pages is in a separate # repository (https://github.com/ente-io/families). The mobile app also uses @@ -77,7 +77,7 @@ # # Enhancement: Consider moving that into the app/ folder in this repository. # -# NEXT_PUBLIC_ENTE_FAMILY_ENDPOINT = http://localhost:3001 +# NEXT_PUBLIC_ENTE_FAMILY_URL = http://localhost:3001 # The JSON which describes the expected results of our integration tests. See # `upload.test.ts` for more details of the expected format. diff --git a/web/apps/photos/.env.development b/web/apps/photos/.env.development index fe83001208..1da6070f45 100644 --- a/web/apps/photos/.env.development +++ b/web/apps/photos/.env.development @@ -27,5 +27,5 @@ # in the app you're running (e.g. in apps/accounts), and put an # `NEXT_PUBLIC_ENTE_ENDPOINT` in there. -#NEXT_PUBLIC_ENTE_ACCOUNTS_ENDPOINT = http://localhost:3001 -#NEXT_PUBLIC_ENTE_PAYMENTS_ENDPOINT = http://localhost:3001 +#NEXT_PUBLIC_ENTE_ACCOUNTS_URL = http://localhost:3001 +#NEXT_PUBLIC_ENTE_PAYMENTS_URL = http://localhost:3001 diff --git a/web/packages/shared/network/api.ts b/web/packages/shared/network/api.ts index 437f4a45f0..6ce97a786d 100644 --- a/web/packages/shared/network/api.ts +++ b/web/packages/shared/network/api.ts @@ -38,12 +38,11 @@ export const getUploadEndpoint = () => { * Return the URL of the Ente Accounts app. * * Defaults to our production instance, "https://accounts.ente.io", but can be - * overridden by setting the `NEXT_PUBLIC_ENTE_ACCOUNTS_ENDPOINT` environment + * overridden by setting the `NEXT_PUBLIC_ENTE_ACCOUNTS_URL` environment * variable to a non-empty value. */ export const accountsAppURL = () => - process.env.NEXT_PUBLIC_ENTE_ACCOUNTS_ENDPOINT || - `https://accounts.ente.io`; + process.env.NEXT_PUBLIC_ENTE_ACCOUNTS_URL || `https://accounts.ente.io`; export const getAlbumsURL = () => { const albumsURL = process.env.NEXT_PUBLIC_ENTE_ALBUMS_ENDPOINT; @@ -58,7 +57,7 @@ export const getAlbumsURL = () => { * family plans. */ export const getFamilyPortalURL = () => { - const familyURL = process.env.NEXT_PUBLIC_ENTE_FAMILY_ENDPOINT; + const familyURL = process.env.NEXT_PUBLIC_ENTE_FAMILY_URL; if (familyURL) { return familyURL; } @@ -69,7 +68,7 @@ export const getFamilyPortalURL = () => { * Return the URL for the host that handles payment related functionality. */ export const getPaymentsURL = () => { - const paymentsURL = process.env.NEXT_PUBLIC_ENTE_PAYMENTS_ENDPOINT; + const paymentsURL = process.env.NEXT_PUBLIC_ENTE_PAYMENTS_URL; if (paymentsURL) { return paymentsURL; } From 4bdca0f09ffcb2c024e233600139e9ef329d5726 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 6 Jun 2024 12:40:02 +0530 Subject: [PATCH 06/16] Add a smaller sample --- web/apps/accounts/.env.development | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 web/apps/accounts/.env.development diff --git a/web/apps/accounts/.env.development b/web/apps/accounts/.env.development new file mode 100644 index 0000000000..e0cf7acd14 --- /dev/null +++ b/web/apps/accounts/.env.development @@ -0,0 +1,9 @@ +# Copy this file into `.env.local` and uncomment these to develop against apps +# and server running on localhost. +# +# For details, please see `apps/photos/.env.development` + +#NEXT_PUBLIC_ENTE_ENDPOINT = http://localhost:8080 +#NEXT_PUBLIC_ENTE_ALBUMS_ENDPOINT = http://localhost:3002 +#NEXT_PUBLIC_ENTE_ACCOUNTS_URL = http://localhost:3001 +#NEXT_PUBLIC_ENTE_PAYMENTS_URL = http://localhost:3001 From 4d2e4f0194686659360be2fc7b05452adcaa4c94 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 6 Jun 2024 12:41:50 +0530 Subject: [PATCH 07/16] Fix --- web/apps/photos/src/components/Sidebar/index.tsx | 4 ++-- web/docs/webauthn-passkeys.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/web/apps/photos/src/components/Sidebar/index.tsx b/web/apps/photos/src/components/Sidebar/index.tsx index 4a085b4a7f..37029eb8de 100644 --- a/web/apps/photos/src/components/Sidebar/index.tsx +++ b/web/apps/photos/src/components/Sidebar/index.tsx @@ -22,7 +22,7 @@ import { generateEncryptionKey, } from "@ente/shared/crypto/internal/libsodium"; import { useLocalState } from "@ente/shared/hooks/useLocalState"; -import { getAccountsURL } from "@ente/shared/network/api"; +import { accountsAppURL } from "@ente/shared/network/api"; import { LS_KEYS, getData, setData } from "@ente/shared/storage/localStorage"; import { THEME_COLOR } from "@ente/shared/themes/constants"; import { downloadAsFile } from "@ente/shared/utils"; @@ -510,7 +510,7 @@ const UtilitySection: React.FC = ({ closeSidebar }) => { const accountsToken = await getAccountsToken(); window.open( - `${getAccountsURL()}${ + `${accountsAppURL()}${ ACCOUNTS_PAGES.ACCOUNT_HANDOFF }?package=${clientPackageName["photos"]}&token=${accountsToken}`, ); diff --git a/web/docs/webauthn-passkeys.md b/web/docs/webauthn-passkeys.md index d521dc0ede..0156726029 100644 --- a/web/docs/webauthn-passkeys.md +++ b/web/docs/webauthn-passkeys.md @@ -342,7 +342,7 @@ credential authentication. We use Accounts as the central WebAuthn hub because credentials are locked to an FQDN. ```tsx -window.location.href = `${getAccountsURL()}/passkeys/flow?passkeySessionID=${passkeySessionID}&redirect=${ +window.location.href = `${accountsAppURL()}/passkeys/flow?passkeySessionID=${passkeySessionID}&redirect=${ window.location.origin }/passkeys/finish`; ``` From e96eeb2315e385c8ef7912eaf1b93788522478d5 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 6 Jun 2024 13:48:05 +0530 Subject: [PATCH 08/16] Allow localhost in development --- web/apps/accounts/src/pages/passkeys/flow.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/web/apps/accounts/src/pages/passkeys/flow.tsx b/web/apps/accounts/src/pages/passkeys/flow.tsx index 5e62885f34..75f3bd9b38 100644 --- a/web/apps/accounts/src/pages/passkeys/flow.tsx +++ b/web/apps/accounts/src/pages/passkeys/flow.tsx @@ -1,3 +1,4 @@ +import { isDevBuild } from "@/next/env"; import log from "@/next/log"; import { clientPackageName } from "@/next/types/app"; import { @@ -41,7 +42,8 @@ const PasskeysFlow = () => { !( redirectURL.host.endsWith(".ente.io") || redirectURL.host.endsWith(".ente.sh") || - redirectURL.host.endsWith("bada-frame.pages.dev") + redirectURL.host.endsWith("bada-frame.pages.dev") || + (isDevBuild && redirectURL.host.endsWith("localhost")) ) && redirectURL.protocol !== "ente:" && redirectURL.protocol !== "enteauth:" From 76c684b25e80de2d957ed8b9e965c913e0443cd3 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 6 Jun 2024 14:13:10 +0530 Subject: [PATCH 09/16] Notes from discussion --- web/apps/accounts/src/pages/passkeys/flow.tsx | 1 + web/apps/photos/src/components/Sidebar/index.tsx | 1 + web/packages/accounts/pages/passkeys/finish.tsx | 3 +++ 3 files changed, 5 insertions(+) diff --git a/web/apps/accounts/src/pages/passkeys/flow.tsx b/web/apps/accounts/src/pages/passkeys/flow.tsx index 75f3bd9b38..12cda4ed68 100644 --- a/web/apps/accounts/src/pages/passkeys/flow.tsx +++ b/web/apps/accounts/src/pages/passkeys/flow.tsx @@ -62,6 +62,7 @@ const PasskeysFlow = () => { } setData(LS_KEYS.CLIENT_PACKAGE, { name: pkg }); + // The server needs to know the app on whose behalf we're trying to log in HTTPService.setHeaders({ "X-Client-Package": pkg, }); diff --git a/web/apps/photos/src/components/Sidebar/index.tsx b/web/apps/photos/src/components/Sidebar/index.tsx index 37029eb8de..08bc50ad9b 100644 --- a/web/apps/photos/src/components/Sidebar/index.tsx +++ b/web/apps/photos/src/components/Sidebar/index.tsx @@ -507,6 +507,7 @@ const UtilitySection: React.FC = ({ closeSidebar }) => { ); } + // Ente Accounts specific JWT token. const accountsToken = await getAccountsToken(); window.open( diff --git a/web/packages/accounts/pages/passkeys/finish.tsx b/web/packages/accounts/pages/passkeys/finish.tsx index b9b5b2cd40..d5347f835d 100644 --- a/web/packages/accounts/pages/passkeys/finish.tsx +++ b/web/packages/accounts/pages/passkeys/finish.tsx @@ -19,6 +19,9 @@ const Page: React.FC = () => { // decode response const decodedResponse = JSON.parse(atob(response)); + // only one will be present + // token only during fresh signup + // encryptedToken othw const { keyAttributes, encryptedToken, token, id } = decodedResponse; setData(LS_KEYS.USER, { ...getData(LS_KEYS.USER), From a0393bc2b6a2d1e2b9becd1c265a4a47b5da3b08 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 6 Jun 2024 14:31:24 +0530 Subject: [PATCH 10/16] README --- web/apps/accounts/README.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/web/apps/accounts/README.md b/web/apps/accounts/README.md index 4a30c3e2de..562029b009 100644 --- a/web/apps/accounts/README.md +++ b/web/apps/accounts/README.md @@ -2,7 +2,11 @@ Code that runs on `accounts.ente.io`. -Passkeys are tied to domains, so this app serves as a common sharing point to -allow the passkey to be tied to the user's Ente account and be used by all our -other apps. For more details, see +Primarily, this serves a common domain where our clients (mobile and web / auth +and photos) can create and authenticate using shared passkeys tied to the user's +Ente account. Passkeys can be shared by multiple domains, so we didn't strictly +need a separate web origin for sharing passkeys across our web clients, but we +do need a web origin to handle the passkey flow for the mobile clients. + +For more details about the Passkey flows, [docs/webauthn-passkeys.md](../../docs/webauthn-passkeys.md). From 54884a7dd2e4a1dea02d806c0f8511927eb8ca74 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 6 Jun 2024 14:37:57 +0530 Subject: [PATCH 11/16] Dev notes --- web/apps/accounts/README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/web/apps/accounts/README.md b/web/apps/accounts/README.md index 562029b009..1f7554435b 100644 --- a/web/apps/accounts/README.md +++ b/web/apps/accounts/README.md @@ -10,3 +10,19 @@ do need a web origin to handle the passkey flow for the mobile clients. For more details about the Passkey flows, [docs/webauthn-passkeys.md](../../docs/webauthn-passkeys.md). + +## Development + +To set this up to work with a locally running museum, modify your local +`museum.yaml` to set the relaying party's ID to "localhost" (without any port +number). + +```yaml +webauthn: + rpid: "localhost" + rporigins: + - "http://localhost:3001" +``` + +Note that browsers already treat `localhost` as a secure domain, so Passkey APIs +will work even if our local dev server is using `http`. From 0d3db76cb07a2f38a252642aa32b1f3d76583c4f Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 6 Jun 2024 14:43:38 +0530 Subject: [PATCH 12/16] Remove unused styles --- web/apps/accounts/src/styles/global.css | 111 ------------------------ 1 file changed, 111 deletions(-) diff --git a/web/apps/accounts/src/styles/global.css b/web/apps/accounts/src/styles/global.css index 98ad85a9b3..95a483b907 100644 --- a/web/apps/accounts/src/styles/global.css +++ b/web/apps/accounts/src/styles/global.css @@ -54,102 +54,6 @@ body { height: 100vh; } -.pswp__button--custom { - width: 48px; - height: 48px; - background: none !important; - background-image: none !important; - color: #fff; -} - -.pswp__item video { - width: 100%; - height: 100%; -} - -.pswp-item-container { - width: 100%; - height: 100%; - display: flex; - align-items: center; - justify-content: center; - object-fit: contain; -} - -.pswp-item-container > * { - position: absolute; - transition: opacity 1s ease; - max-width: 100%; - max-height: 100%; -} - -.pswp-item-container > img { - opacity: 1; -} - -.pswp-item-container > video { - opacity: 0; -} - -.pswp-item-container > div.download-banner { - width: 100%; - height: 16vh; - padding: 2vh 0; - background-color: #151414; - color: #ddd; - display: flex; - flex-direction: column; - align-items: center; - justify-content: space-around; - opacity: 0.8; - font-size: 20px; -} - -.download-banner > a { - width: 130px; -} - -.pswp__img { - object-fit: contain; -} - -.pswp__button--arrow--left, -.pswp__button--arrow--right { - color: #fff; - background-color: #333333 !important; - border-radius: 50%; - width: 56px; - height: 56px; -} -.pswp__button--arrow--left::before, -.pswp__button--arrow--right::before { - background: none !important; -} - -.pswp__button--arrow--left { - margin-left: 20px; -} - -.pswp__button--arrow--right { - margin-right: 20px; -} - -.pswp-custom-caption-container { - width: 100%; - display: flex; - justify-content: flex-end; - bottom: 56px; - background-color: transparent !important; -} - -.pswp__caption--empty { - display: none; -} - -.bg-upload-progress-bar { - background-color: #51cd7c; -} - div.otp-input input { width: 36px !important; height: 36px; @@ -168,18 +72,3 @@ div.otp-input input:focus { transition: 0.5s; outline: none; } - -.flash-message { - padding: 16px; - display: flex; - align-items: center; -} - -@-webkit-keyframes rotation { - from { - -webkit-transform: rotate(0deg); - } - to { - -webkit-transform: rotate(359deg); - } -} From ba20fd47633333c2ecad065f916100deaa4ce50f Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 6 Jun 2024 14:57:09 +0530 Subject: [PATCH 13/16] Document --- .../accounts/src/pages/passkeys/finish.tsx | 1 + .../accounts/pages/passkeys/finish.tsx | 53 ++++++++++++++----- 2 files changed, 41 insertions(+), 13 deletions(-) diff --git a/web/apps/accounts/src/pages/passkeys/finish.tsx b/web/apps/accounts/src/pages/passkeys/finish.tsx index 866dcf9e3a..d59dd45597 100644 --- a/web/apps/accounts/src/pages/passkeys/finish.tsx +++ b/web/apps/accounts/src/pages/passkeys/finish.tsx @@ -1,3 +1,4 @@ import Page from "@ente/accounts/pages/passkeys/finish"; +// See: [Note: Finish passkey flow in the requesting app] export default Page; diff --git a/web/packages/accounts/pages/passkeys/finish.tsx b/web/packages/accounts/pages/passkeys/finish.tsx index d5347f835d..876a758c4c 100644 --- a/web/packages/accounts/pages/passkeys/finish.tsx +++ b/web/packages/accounts/pages/passkeys/finish.tsx @@ -6,6 +6,13 @@ import { LS_KEYS, getData, setData } from "@ente/shared/storage/localStorage"; import { useRouter } from "next/router"; import React, { useEffect } from "react"; +/** + * [Note: Finish passkey flow in the requesting app] + * + * The passkey finish step needs to happen in the context of the client which + * invoked the passkey flow since it needs to save the obtained credentials + * in local storage (which is tied to the current origin). + */ const Page: React.FC = () => { const router = useRouter(); @@ -16,20 +23,8 @@ const Page: React.FC = () => { if (!response) return; - // decode response - const decodedResponse = JSON.parse(atob(response)); + saveCredentials(response); - // only one will be present - // token only during fresh signup - // encryptedToken othw - const { keyAttributes, encryptedToken, token, id } = decodedResponse; - setData(LS_KEYS.USER, { - ...getData(LS_KEYS.USER), - token, - encryptedToken, - id, - }); - setData(LS_KEYS.KEY_ATTRIBUTES, keyAttributes); const redirectURL = InMemoryStore.get(MS_KEYS.REDIRECT_URL); InMemoryStore.delete(MS_KEYS.REDIRECT_URL); router.push(redirectURL ?? PAGES.ROOT); @@ -47,3 +42,35 @@ const Page: React.FC = () => { }; export default Page; + +/** + * Extract credentials from a successful passkey flow "response" query parameter + * and save them to local storage for use by subsequent steps (or normal + * functioning) of the app. + * + * @param response The string that is passed as the response query parameter to + * us (we're the final "finish" page in the passkey flow). + */ +const saveCredentials = (response: string) => { + // Decode response string. + const decodedResponse = JSON.parse(atob(response)); + + // Only one of `encryptedToken` or `token` will be present depending on the + // account's lifetime: + // + // - The plaintext "token" will be passed during fresh signups, where we + // don't yet have keys to encrypt it, the account itself is being created + // as we go through this flow. + // + // + // - The encrypted `encryptedToken` will be present otherwise (i.e. if the + // user is signing into an existing account). + const { keyAttributes, encryptedToken, token, id } = decodedResponse; + setData(LS_KEYS.USER, { + ...getData(LS_KEYS.USER), + token, + encryptedToken, + id, + }); + setData(LS_KEYS.KEY_ATTRIBUTES, keyAttributes); +}; From aed516988f76675ff0d06063102e2678e4ef85ab Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 6 Jun 2024 15:04:35 +0530 Subject: [PATCH 14/16] Inline --- web/packages/accounts/pages/passkeys/finish.tsx | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/web/packages/accounts/pages/passkeys/finish.tsx b/web/packages/accounts/pages/passkeys/finish.tsx index 876a758c4c..3f602b2305 100644 --- a/web/packages/accounts/pages/passkeys/finish.tsx +++ b/web/packages/accounts/pages/passkeys/finish.tsx @@ -16,11 +16,10 @@ import React, { useEffect } from "react"; const Page: React.FC = () => { const router = useRouter(); - const init = async () => { - // get response from query params + useEffect(() => { + // Extract response from query params const searchParams = new URLSearchParams(window.location.search); const response = searchParams.get("response"); - if (!response) return; saveCredentials(response); @@ -28,10 +27,6 @@ const Page: React.FC = () => { const redirectURL = InMemoryStore.get(MS_KEYS.REDIRECT_URL); InMemoryStore.delete(MS_KEYS.REDIRECT_URL); router.push(redirectURL ?? PAGES.ROOT); - }; - - useEffect(() => { - init(); }, []); return ( @@ -62,7 +57,6 @@ const saveCredentials = (response: string) => { // don't yet have keys to encrypt it, the account itself is being created // as we go through this flow. // - // // - The encrypted `encryptedToken` will be present otherwise (i.e. if the // user is signing into an existing account). const { keyAttributes, encryptedToken, token, id } = decodedResponse; From 7f08b317711ca68251894d8a64e9ef5e2a691570 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 6 Jun 2024 15:28:08 +0530 Subject: [PATCH 15/16] Fix --- web/apps/accounts/src/pages/passkeys/flow.tsx | 31 +++--- .../accounts/src/pages/passkeys/index.tsx | 94 +++++++++---------- web/apps/accounts/src/services/passkey.ts | 13 +++ 3 files changed, 69 insertions(+), 69 deletions(-) diff --git a/web/apps/accounts/src/pages/passkeys/flow.tsx b/web/apps/accounts/src/pages/passkeys/flow.tsx index 12cda4ed68..8cbba4c422 100644 --- a/web/apps/accounts/src/pages/passkeys/flow.tsx +++ b/web/apps/accounts/src/pages/passkeys/flow.tsx @@ -1,6 +1,6 @@ -import { isDevBuild } from "@/next/env"; import log from "@/next/log"; import { clientPackageName } from "@/next/types/app"; +import { nullToUndefined } from "@/utils/transform"; import { CenteredFlex, VerticallyCentered, @@ -19,6 +19,7 @@ import { useEffect, useState } from "react"; import { beginPasskeyAuthentication, finishPasskeyAuthentication, + isWhitelistedRedirect, type BeginPasskeyAuthenticationResponse, } from "services/passkey"; @@ -32,26 +33,16 @@ const PasskeysFlow = () => { const init = async () => { const searchParams = new URLSearchParams(window.location.search); - // get redirect from the query params - const redirect = searchParams.get("redirect") as string; + // Extract redirect from the query params. + const redirect = nullToUndefined(searchParams.get("redirect")); + const redirectURL = redirect ? new URL(redirect) : undefined; - const redirectURL = new URL(redirect); - if (process.env.NEXT_PUBLIC_DISABLE_REDIRECT_CHECK !== "true") { - if ( - redirect !== "" && - !( - redirectURL.host.endsWith(".ente.io") || - redirectURL.host.endsWith(".ente.sh") || - redirectURL.host.endsWith("bada-frame.pages.dev") || - (isDevBuild && redirectURL.host.endsWith("localhost")) - ) && - redirectURL.protocol !== "ente:" && - redirectURL.protocol !== "enteauth:" - ) { - setInvalidInfo(true); - setLoading(false); - return; - } + // Ensure that redirectURL is whitelisted, otherwise show an invalid + // "login" URL error to the user. + if (!redirectURL || !isWhitelistedRedirect(redirectURL)) { + setInvalidInfo(true); + setLoading(false); + return; } let pkg = clientPackageName["photos"]; diff --git a/web/apps/accounts/src/pages/passkeys/index.tsx b/web/apps/accounts/src/pages/passkeys/index.tsx index 92683f631d..b841c030db 100644 --- a/web/apps/accounts/src/pages/passkeys/index.tsx +++ b/web/apps/accounts/src/pages/passkeys/index.tsx @@ -140,46 +140,44 @@ const Passkeys = () => { }; return ( - <> - - - - - {t("PASSKEYS_DESCRIPTION")} - - - - - - - + + + + + {t("PASSKEYS_DESCRIPTION")} - - - - + + + + + + + + + + ); }; @@ -191,16 +189,14 @@ interface PasskeysListProps { const PasskeysList: React.FC = ({ passkeys }) => { return ( - <> - - {passkeys.map((passkey, i) => ( - - - {i < passkeys.length - 1 && } - - ))} - - + + {passkeys.map((passkey, i) => ( + + + {i < passkeys.length - 1 && } + + ))} + ); }; diff --git a/web/apps/accounts/src/services/passkey.ts b/web/apps/accounts/src/services/passkey.ts index e5a1259eeb..a3f4b94a5e 100644 --- a/web/apps/accounts/src/services/passkey.ts +++ b/web/apps/accounts/src/services/passkey.ts @@ -1,3 +1,4 @@ +import { isDevBuild } from "@/next/env"; import log from "@/next/log"; import { toB64URLSafeNoPadding } from "@ente/shared/crypto/internal/libsodium"; import HTTPService from "@ente/shared/network/HTTPService"; @@ -81,6 +82,18 @@ export const getPasskeyRegistrationOptions = async () => { } }; +/** + * Return `true` if the given {@link redirectURL} (obtained from the redirect + * query parameter passed around during the passkey verification flow) is one of + * the whitelisted URLs that we allow redirecting to on success. + */ +export const isWhitelistedRedirect = (redirectURL: URL) => + (isDevBuild && redirectURL.host.endsWith("localhost")) || + redirectURL.host.endsWith(".ente.io") || + redirectURL.host.endsWith(".ente.sh") || + redirectURL.protocol == "ente:" || + redirectURL.protocol == "enteauth:"; + export const finishPasskeyRegistration = async ( friendlyName: string, credential: Credential, From 144ebca2039f708f1cd73e1402e2ce9a89975083 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 6 Jun 2024 15:38:35 +0530 Subject: [PATCH 16/16] Untab --- web/apps/accounts/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/apps/accounts/README.md b/web/apps/accounts/README.md index 1f7554435b..51edb152e9 100644 --- a/web/apps/accounts/README.md +++ b/web/apps/accounts/README.md @@ -21,7 +21,7 @@ number). webauthn: rpid: "localhost" rporigins: - - "http://localhost:3001" + - "http://localhost:3001" ``` Note that browsers already treat `localhost` as a secure domain, so Passkey APIs