From 6d73462fd97df4165ae4f1f1d90ca9f9575bfd15 Mon Sep 17 00:00:00 2001 From: Cyrus Goh Date: Wed, 13 Nov 2024 10:19:54 -0800 Subject: [PATCH 01/18] initial feedback --- web-auth/src/components/Auth.svelte | 2 ++ web-common/src/components/calls-to-action/CTANeedHelp.svelte | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/web-auth/src/components/Auth.svelte b/web-auth/src/components/Auth.svelte index d76f0924408..dc4a3265d6b 100644 --- a/web-auth/src/components/Auth.svelte +++ b/web-auth/src/components/Auth.svelte @@ -46,6 +46,8 @@ decodeURIComponent(escape(window.atob(configParams))), ) as Config; + // Entry point: FIXME - There is no way to tell if the user is signing up unless we ask them to make a selection + // Entry point: Email invite redirect to signup const isSignup = config?.extraParams?.screen_hint === "signup"; if (isSignup) { diff --git a/web-common/src/components/calls-to-action/CTANeedHelp.svelte b/web-common/src/components/calls-to-action/CTANeedHelp.svelte index 91574c9e6b7..5ae505b6ae4 100644 --- a/web-common/src/components/calls-to-action/CTANeedHelp.svelte +++ b/web-common/src/components/calls-to-action/CTANeedHelp.svelte @@ -1,5 +1,5 @@

- Need help? Reach out to us on Discordsupport@rilldata.com

From e393189639709d7f57a2cd12a7898b7f484aed34 Mon Sep 17 00:00:00 2001 From: Cyrus Goh Date: Wed, 13 Nov 2024 10:50:46 -0800 Subject: [PATCH 02/18] rework sso, sso directly with email --- web-auth/src/components/Auth.svelte | 28 +++++------ web-auth/src/components/SSOForm.svelte | 70 -------------------------- 2 files changed, 12 insertions(+), 86 deletions(-) delete mode 100644 web-auth/src/components/SSOForm.svelte diff --git a/web-auth/src/components/Auth.svelte b/web-auth/src/components/Auth.svelte index dc4a3265d6b..cef04da7508 100644 --- a/web-auth/src/components/Auth.svelte +++ b/web-auth/src/components/Auth.svelte @@ -9,7 +9,6 @@ import EmailPasswordForm from "./EmailPasswordForm.svelte"; import { getConnectionFromEmail } from "./utils"; import OrSeparator from "./OrSeparator.svelte"; - import SSOForm from "./SSOForm.svelte"; import EmailSubmissionForm from "./EmailSubmissionForm.svelte"; import Disclaimer from "./Disclaimer.svelte"; import Spacer from "./Spacer.svelte"; @@ -75,28 +74,30 @@ webAuth = new auth0.WebAuth(authOptions); } + function authorizeSSO(email: string, connectionName: string) { + webAuth.authorize({ + connection: connectionName, + login_hint: email, + prompt: "login", + }); + } + function processEmailSubmission(event) { email = event.detail.email; const connectionName = getConnectionFromEmail(email, connectionMapObj); if (connectionName) { - step = AuthStep.SSO; + authorizeSSO(email, connectionName); } else { step = AuthStep.Login; } } function getHeadingText(step: AuthStep): string { - if (isLegacy) { - return "Log in"; - } - switch (step) { case AuthStep.Base: return "Log in or sign up"; - case AuthStep.SSO: - return "Log in with SSO"; case AuthStep.Login: return "Log in with email"; case AuthStep.SignUp: @@ -107,19 +108,15 @@ return ""; } } - $: headingText = getHeadingText(step); function getSubheadingText(step: AuthStep, email: string): string { switch (step) { - case AuthStep.SSO: - return `SAML SSO enabled workspace is associated with ${email}`; case AuthStep.Login: return `Log in using ${email}`; default: return ""; } } - $: subheadingText = getSubheadingText(step, email); function backToBaseStep() { step = AuthStep.Base; @@ -128,6 +125,9 @@ onMount(() => { initConfig(); }); + + $: headingText = getHeadingText(step); + $: subheadingText = getSubheadingText(step, email); @@ -169,10 +169,6 @@ {/if} - {#if step === AuthStep.SSO} - - {/if} - {#if step === AuthStep.Login || step === AuthStep.SignUp} - import { createEventDispatcher } from "svelte"; - import CtaButton from "@rilldata/web-common/components/calls-to-action/CTAButton.svelte"; - import { getConnectionFromEmail } from "./utils"; - import { ArrowLeftIcon } from "lucide-svelte"; - import { WebAuth } from "auth0-js"; - - const dispatch = createEventDispatcher(); - - export let disabled = false; - export let email = ""; - export let webAuth: WebAuth; - export let connectionMapObj: Record; - - let errorText = ""; - - function handleClick() { - void authorizeSSO(email.toLowerCase()); - } - - function displayError(err: any) { - errorText = err.message; - } - - function authorizeSSO(email: string) { - disabled = true; - errorText = ""; - - const connectionName = getConnectionFromEmail(email, connectionMapObj); - - if (!connectionName) { - displayError({ - message: `IDP for the email ${email} not found. Please contact your administrator.`, - }); - disabled = false; - return; - } - - webAuth.authorize({ - connection: connectionName, - login_hint: email, - prompt: "login", - }); - } - - -
- -
- Continue with SAML SSO -
-
- { - dispatch("back"); - }} - > -
- - Back -
-
-
- -{#if errorText} -
{errorText}
-{/if} From deea0d90f01cb5a31bb4a2b51cca17fdab78d79d Mon Sep 17 00:00:00 2001 From: Cyrus Goh Date: Tue, 3 Dec 2024 19:35:26 -0800 Subject: [PATCH 03/18] offload login/signup to auth0 --- web-auth/package.json | 4 +- web-auth/src/components/Auth.svelte | 23 ++----- .../src/components/EmailPasswordForm.svelte | 67 +++++++++---------- 3 files changed, 39 insertions(+), 55 deletions(-) diff --git a/web-auth/package.json b/web-auth/package.json index 579914215f3..fe99664f254 100644 --- a/web-auth/package.json +++ b/web-auth/package.json @@ -13,8 +13,8 @@ }, "devDependencies": { "@sveltejs/vite-plugin-svelte": "^3.1.2", - "@types/auth0-js": "^9.14.7", - "auth0-js": "^9.20.2", + "@types/auth0-js": "^9.21.6", + "auth0-js": "^9.28.0", "autoprefixer": "^10.4.20", "postcss": "^8.4.47", "svelte": "^4.2.19", diff --git a/web-auth/src/components/Auth.svelte b/web-auth/src/components/Auth.svelte index cef04da7508..b9b74c4f589 100644 --- a/web-auth/src/components/Auth.svelte +++ b/web-auth/src/components/Auth.svelte @@ -30,7 +30,8 @@ let step: AuthStep = AuthStep.Base; let webAuth: WebAuth; - $: isLegacy = false; + $: isSignup = false; + $: isRillDash = false; function isDomainDisabled(email: string): boolean { return disableForgotPassDomainsArr.some((domain) => @@ -45,16 +46,10 @@ decodeURIComponent(escape(window.atob(configParams))), ) as Config; - // Entry point: FIXME - There is no way to tell if the user is signing up unless we ask them to make a selection - // Entry point: Email invite redirect to signup - const isSignup = config?.extraParams?.screen_hint === "signup"; - - if (isSignup) { - step = AuthStep.SignUp; - } + isSignup = config?.extraParams?.screen_hint === "signup"; if (cloudClientIDsArr.includes(config?.clientID)) { - isLegacy = true; + isRillDash = true; } const authOptions: AuthOptions = Object.assign( @@ -97,11 +92,9 @@ function getHeadingText(step: AuthStep): string { switch (step) { case AuthStep.Base: - return "Log in or sign up"; case AuthStep.Login: - return "Log in with email"; case AuthStep.SignUp: - return "Sign up with email"; + return "Log in or sign up"; case AuthStep.Thanks: return "Thanks for signing up!"; default: @@ -112,7 +105,7 @@ function getSubheadingText(step: AuthStep, email: string): string { switch (step) { case AuthStep.Login: - return `Log in using ${email}`; + return `Log in or sign up using ${email}`; default: return ""; } @@ -171,10 +164,8 @@ {#if step === AuthStep.Login || step === AuthStep.SignUp} { + if (err) { + if (isRillDash && err.code === "user_not_found") { + // More restrictive error for Rill Dash + displayError({ + message: + "Invalid credentials. Please check your email and password.", + }); + } else { + displayError({ message: err?.description }); + } + } else { + disabled = false; + } + }, + ); + } + function authenticateUser(email: string, password: string) { disabled = true; errorText = ""; try { - // NOTE: Sign up is only supported on Rill Cloud login pages, not Rill Dash - if (step === AuthStep.SignUp && !isLegacy) { - // Directly attempt to sign up and log in the user - webAuth.redirect.signupAndLogin( - { - connection: DATABASE_CONNECTION, - email: email, - password: password, - }, - (signupErr: any) => { - if (signupErr) { - handleAuthError(signupErr); - } else { - disabled = false; - } - }, - ); - } else { - webAuth.login( - { - realm: DATABASE_CONNECTION, - username: email, - password: password, - }, - (err) => { - if (err) { - displayError({ message: err?.description }); - } else { - disabled = false; - } - }, - ); - } + handleLogin(email, password); } catch (err) { handleAuthError(err); } From 5783a808fd792490d555b541e7b3a00653fc17fa Mon Sep 17 00:00:00 2001 From: Cyrus Goh Date: Wed, 4 Dec 2024 09:32:12 -0800 Subject: [PATCH 04/18] bump package lock --- package-lock.json | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 90d950660eb..6cbbabc4784 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8803,7 +8803,9 @@ "license": "MIT" }, "node_modules/@types/auth0-js": { - "version": "9.21.5", + "version": "9.21.6", + "resolved": "https://registry.npmjs.org/@types/auth0-js/-/auth0-js-9.21.6.tgz", + "integrity": "sha512-wsvfk03WzQDXCbMdX8lQZH2Thh5AQk9SKQcxrBN1EdRkIOgkw9aIixxBpzsTHu/gj0I514BGQv7t5EyZSgVRmQ==", "dev": true, "license": "MIT" }, @@ -10788,7 +10790,9 @@ } }, "node_modules/auth0-js": { - "version": "9.24.1", + "version": "9.28.0", + "resolved": "https://registry.npmjs.org/auth0-js/-/auth0-js-9.28.0.tgz", + "integrity": "sha512-2xIfQIGM0vX3IdPR91ztLO2+Ar2I5+3iFKcjuZO+LV9vRh4Wje+Ka1hnHjMU9dH892Lm3ZxBAHxRo68YToUhfg==", "dev": true, "license": "MIT", "dependencies": { @@ -34873,8 +34877,8 @@ "version": "0.0.0", "devDependencies": { "@sveltejs/vite-plugin-svelte": "^3.1.2", - "@types/auth0-js": "^9.14.7", - "auth0-js": "^9.20.2", + "@types/auth0-js": "^9.21.6", + "auth0-js": "^9.28.0", "autoprefixer": "^10.4.20", "postcss": "^8.4.47", "svelte": "^4.2.19", From 41c45697fe2bd7c5f69ca92ee655e2edc2081a86 Mon Sep 17 00:00:00 2001 From: Cyrus Goh Date: Thu, 5 Dec 2024 13:20:50 -0800 Subject: [PATCH 05/18] remove forgotPassword domains usage, passwordless wip --- web-auth/src/App.svelte | 9 +- web-auth/src/components/Auth.svelte | 166 +++++++++++++----- .../src/components/EmailPasswordForm.svelte | 2 - web-auth/src/types.ts | 4 +- 4 files changed, 124 insertions(+), 57 deletions(-) diff --git a/web-auth/src/App.svelte b/web-auth/src/App.svelte index 008c4a8cd06..278a00793e9 100644 --- a/web-auth/src/App.svelte +++ b/web-auth/src/App.svelte @@ -3,8 +3,6 @@ const connectionMap = import.meta.env.VITE_CONNECTION_MAP; const cloudClientIDs = import.meta.env.VITE_RILL_CLOUD_AUTH0_CLIENT_IDS; - const disableForgotPassDomains = import.meta.env - .VITE_DISABLE_FORGOT_PASS_DOMAINS; // This gets populated by Auth0 runtime const configParams = "@@config@@"; @@ -28,10 +26,5 @@
- +
diff --git a/web-auth/src/components/Auth.svelte b/web-auth/src/components/Auth.svelte index b9b74c4f589..c326dcdd0ec 100644 --- a/web-auth/src/components/Auth.svelte +++ b/web-auth/src/components/Auth.svelte @@ -6,7 +6,6 @@ import { onMount } from "svelte"; import { LOGIN_OPTIONS } from "../config"; import AuthContainer from "./AuthContainer.svelte"; - import EmailPasswordForm from "./EmailPasswordForm.svelte"; import { getConnectionFromEmail } from "./utils"; import OrSeparator from "./OrSeparator.svelte"; import EmailSubmissionForm from "./EmailSubmissionForm.svelte"; @@ -17,12 +16,10 @@ export let configParams: string; export let cloudClientIDs = ""; - export let disableForgotPassDomains = ""; export let connectionMap = "{}"; const connectionMapObj = JSON.parse(connectionMap); const cloudClientIDsArr = cloudClientIDs.split(","); - const disableForgotPassDomainsArr = disableForgotPassDomains.split(","); $: errorText = ""; @@ -33,43 +30,55 @@ $: isSignup = false; $: isRillDash = false; - function isDomainDisabled(email: string): boolean { - return disableForgotPassDomainsArr.some((domain) => - email.toLowerCase().endsWith(domain.toLowerCase()), - ); - } - - $: domainDisabled = isDomainDisabled(email); + let verifying = false; + let verificationCode: string = ""; function initConfig() { - const config = JSON.parse( - decodeURIComponent(escape(window.atob(configParams))), - ) as Config; + try { + if ( + import.meta.env.DEV && + (!configParams || configParams === "undefined") + ) { + console.warn( + "No auth config provided. In development mode - auth flows will not work.", + ); + errorText = "Authentication is not configured in development mode"; + return; + } - isSignup = config?.extraParams?.screen_hint === "signup"; + const config = JSON.parse( + decodeURIComponent(escape(window.atob(configParams))), + ) as Config; - if (cloudClientIDsArr.includes(config?.clientID)) { - isRillDash = true; - } + isSignup = config?.extraParams?.screen_hint === "signup"; - const authOptions: AuthOptions = Object.assign( - { - overrides: { - __tenant: config.auth0Tenant, - __token_issuer: config.authorizationServer.issuer, + if (cloudClientIDsArr.includes(config?.clientID)) { + isRillDash = true; + } + + const authOptions: AuthOptions = Object.assign( + { + overrides: { + __tenant: config.auth0Tenant, + __token_issuer: config.authorizationServer.issuer, + }, + domain: config.auth0Domain, + clientID: config.clientID, + redirectUri: config.callbackURL, + responseType: "code", }, - domain: config.auth0Domain, - clientID: config.clientID, - redirectUri: config.callbackURL, - responseType: "code", - }, - config.internalOptions, - ); + config.internalOptions, + ); - webAuth = new auth0.WebAuth(authOptions); + webAuth = new auth0.WebAuth(authOptions); + } catch (e) { + console.error("Failed to initialize auth:", e); + errorText = "Failed to initialize authentication"; + } } function authorizeSSO(email: string, connectionName: string) { + // See: https://community.auth0.com/t/home-realm-discovery-using-auth0-js/17643/2 webAuth.authorize({ connection: connectionName, login_hint: email, @@ -77,6 +86,23 @@ }); } + function startPasswordless(email: string) { + webAuth.passwordlessStart( + { + connection: "email", + send: "code", + email: email, + }, + (err) => { + if (err) { + errorText = err.description || "An error occurred"; + return; + } + step = AuthStep.Thanks; + }, + ); + } + function processEmailSubmission(event) { email = event.detail.email; @@ -85,18 +111,16 @@ if (connectionName) { authorizeSSO(email, connectionName); } else { - step = AuthStep.Login; + startPasswordless(email); } } function getHeadingText(step: AuthStep): string { switch (step) { case AuthStep.Base: - case AuthStep.Login: - case AuthStep.SignUp: return "Log in or sign up"; case AuthStep.Thanks: - return "Thanks for signing up!"; + return "Check your email"; default: return ""; } @@ -104,8 +128,8 @@ function getSubheadingText(step: AuthStep, email: string): string { switch (step) { - case AuthStep.Login: - return `Log in or sign up using ${email}`; + case AuthStep.Thanks: + return `We sent a verification code to ${email}`; default: return ""; } @@ -115,8 +139,45 @@ step = AuthStep.Base; } + function parseQueryString() { + const params = new URLSearchParams(window.location.search); + return { + code: params.get("code"), + email: params.get("email"), + }; + } + + function verifyPasswordlessCode(email: string, code: string) { + verifying = true; + errorText = ""; + + webAuth.passwordlessVerify( + { + connection: "email", + email: email, + verificationCode: code, + }, + (err) => { + verifying = false; + if (err) { + errorText = err.description || "Failed to verify email code"; + console.error("Verification error:", err); + } + }, + ); + } + + function handleCodeSubmit(code: string) { + verifyPasswordlessCode(email, code); + } + onMount(() => { initConfig(); + + const { code, email } = parseQueryString(); + if (code && email) { + verifyPasswordlessCode(email, code); + } }); $: headingText = getHeadingText(step); @@ -140,7 +201,9 @@
- {#if step === AuthStep.Base} + {#if verifying} +
Verifying your email...
+ {:else if step === AuthStep.Base} {#each LOGIN_OPTIONS as { label, icon, style, connection } (connection)} {/if} - {#if step === AuthStep.Login || step === AuthStep.SignUp} - + {#if step === AuthStep.Thanks} +
+ Enter the verification code sent to your email +
+
+ + handleCodeSubmit(verificationCode)} + > + Verify Code + + + Use a different email + +
{/if}
diff --git a/web-auth/src/components/EmailPasswordForm.svelte b/web-auth/src/components/EmailPasswordForm.svelte index 96089b1520c..58a30e3c7df 100644 --- a/web-auth/src/components/EmailPasswordForm.svelte +++ b/web-auth/src/components/EmailPasswordForm.svelte @@ -90,8 +90,6 @@ } function handleLogin(email: string, password: string) { - // NOTE: offloaded to Auth0 to handle both login and signup - // We don't need to check if the user exists in our database return webAuth.login( { realm: DATABASE_CONNECTION, diff --git a/web-auth/src/types.ts b/web-auth/src/types.ts index 05cfd2e18f3..86ba516e912 100644 --- a/web-auth/src/types.ts +++ b/web-auth/src/types.ts @@ -15,7 +15,7 @@ type InternalOptions = { leeway: number; }; -export type Config = { +export interface Config { auth0Domain: string; clientID: string; auth0Tenant: string; @@ -25,4 +25,4 @@ export type Config = { callbackURL: string; internalOptions: InternalOptions; extraParams?: { screen_hint?: string }; -}; +} From d7203fc0af6398da20508ac8eb0167f0e5d8445c Mon Sep 17 00:00:00 2001 From: Cyrus Goh Date: Tue, 10 Dec 2024 16:02:28 +0800 Subject: [PATCH 06/18] Revert "remove forgotPassword domains usage, passwordless wip" This reverts commit 41c45697fe2bd7c5f69ca92ee655e2edc2081a86. --- web-auth/src/App.svelte | 9 +- web-auth/src/components/Auth.svelte | 166 +++++------------- .../src/components/EmailPasswordForm.svelte | 2 + web-auth/src/types.ts | 4 +- 4 files changed, 57 insertions(+), 124 deletions(-) diff --git a/web-auth/src/App.svelte b/web-auth/src/App.svelte index 278a00793e9..008c4a8cd06 100644 --- a/web-auth/src/App.svelte +++ b/web-auth/src/App.svelte @@ -3,6 +3,8 @@ const connectionMap = import.meta.env.VITE_CONNECTION_MAP; const cloudClientIDs = import.meta.env.VITE_RILL_CLOUD_AUTH0_CLIENT_IDS; + const disableForgotPassDomains = import.meta.env + .VITE_DISABLE_FORGOT_PASS_DOMAINS; // This gets populated by Auth0 runtime const configParams = "@@config@@"; @@ -26,5 +28,10 @@
- +
diff --git a/web-auth/src/components/Auth.svelte b/web-auth/src/components/Auth.svelte index c326dcdd0ec..b9b74c4f589 100644 --- a/web-auth/src/components/Auth.svelte +++ b/web-auth/src/components/Auth.svelte @@ -6,6 +6,7 @@ import { onMount } from "svelte"; import { LOGIN_OPTIONS } from "../config"; import AuthContainer from "./AuthContainer.svelte"; + import EmailPasswordForm from "./EmailPasswordForm.svelte"; import { getConnectionFromEmail } from "./utils"; import OrSeparator from "./OrSeparator.svelte"; import EmailSubmissionForm from "./EmailSubmissionForm.svelte"; @@ -16,10 +17,12 @@ export let configParams: string; export let cloudClientIDs = ""; + export let disableForgotPassDomains = ""; export let connectionMap = "{}"; const connectionMapObj = JSON.parse(connectionMap); const cloudClientIDsArr = cloudClientIDs.split(","); + const disableForgotPassDomainsArr = disableForgotPassDomains.split(","); $: errorText = ""; @@ -30,55 +33,43 @@ $: isSignup = false; $: isRillDash = false; - let verifying = false; - let verificationCode: string = ""; + function isDomainDisabled(email: string): boolean { + return disableForgotPassDomainsArr.some((domain) => + email.toLowerCase().endsWith(domain.toLowerCase()), + ); + } - function initConfig() { - try { - if ( - import.meta.env.DEV && - (!configParams || configParams === "undefined") - ) { - console.warn( - "No auth config provided. In development mode - auth flows will not work.", - ); - errorText = "Authentication is not configured in development mode"; - return; - } + $: domainDisabled = isDomainDisabled(email); - const config = JSON.parse( - decodeURIComponent(escape(window.atob(configParams))), - ) as Config; + function initConfig() { + const config = JSON.parse( + decodeURIComponent(escape(window.atob(configParams))), + ) as Config; - isSignup = config?.extraParams?.screen_hint === "signup"; + isSignup = config?.extraParams?.screen_hint === "signup"; - if (cloudClientIDsArr.includes(config?.clientID)) { - isRillDash = true; - } + if (cloudClientIDsArr.includes(config?.clientID)) { + isRillDash = true; + } - const authOptions: AuthOptions = Object.assign( - { - overrides: { - __tenant: config.auth0Tenant, - __token_issuer: config.authorizationServer.issuer, - }, - domain: config.auth0Domain, - clientID: config.clientID, - redirectUri: config.callbackURL, - responseType: "code", + const authOptions: AuthOptions = Object.assign( + { + overrides: { + __tenant: config.auth0Tenant, + __token_issuer: config.authorizationServer.issuer, }, - config.internalOptions, - ); + domain: config.auth0Domain, + clientID: config.clientID, + redirectUri: config.callbackURL, + responseType: "code", + }, + config.internalOptions, + ); - webAuth = new auth0.WebAuth(authOptions); - } catch (e) { - console.error("Failed to initialize auth:", e); - errorText = "Failed to initialize authentication"; - } + webAuth = new auth0.WebAuth(authOptions); } function authorizeSSO(email: string, connectionName: string) { - // See: https://community.auth0.com/t/home-realm-discovery-using-auth0-js/17643/2 webAuth.authorize({ connection: connectionName, login_hint: email, @@ -86,23 +77,6 @@ }); } - function startPasswordless(email: string) { - webAuth.passwordlessStart( - { - connection: "email", - send: "code", - email: email, - }, - (err) => { - if (err) { - errorText = err.description || "An error occurred"; - return; - } - step = AuthStep.Thanks; - }, - ); - } - function processEmailSubmission(event) { email = event.detail.email; @@ -111,16 +85,18 @@ if (connectionName) { authorizeSSO(email, connectionName); } else { - startPasswordless(email); + step = AuthStep.Login; } } function getHeadingText(step: AuthStep): string { switch (step) { case AuthStep.Base: + case AuthStep.Login: + case AuthStep.SignUp: return "Log in or sign up"; case AuthStep.Thanks: - return "Check your email"; + return "Thanks for signing up!"; default: return ""; } @@ -128,8 +104,8 @@ function getSubheadingText(step: AuthStep, email: string): string { switch (step) { - case AuthStep.Thanks: - return `We sent a verification code to ${email}`; + case AuthStep.Login: + return `Log in or sign up using ${email}`; default: return ""; } @@ -139,45 +115,8 @@ step = AuthStep.Base; } - function parseQueryString() { - const params = new URLSearchParams(window.location.search); - return { - code: params.get("code"), - email: params.get("email"), - }; - } - - function verifyPasswordlessCode(email: string, code: string) { - verifying = true; - errorText = ""; - - webAuth.passwordlessVerify( - { - connection: "email", - email: email, - verificationCode: code, - }, - (err) => { - verifying = false; - if (err) { - errorText = err.description || "Failed to verify email code"; - console.error("Verification error:", err); - } - }, - ); - } - - function handleCodeSubmit(code: string) { - verifyPasswordlessCode(email, code); - } - onMount(() => { initConfig(); - - const { code, email } = parseQueryString(); - if (code && email) { - verifyPasswordlessCode(email, code); - } }); $: headingText = getHeadingText(step); @@ -201,9 +140,7 @@
- {#if verifying} -
Verifying your email...
- {:else if step === AuthStep.Base} + {#if step === AuthStep.Base} {#each LOGIN_OPTIONS as { label, icon, style, connection } (connection)} {/if} - {#if step === AuthStep.Thanks} -
- Enter the verification code sent to your email -
-
- - handleCodeSubmit(verificationCode)} - > - Verify Code - - - Use a different email - -
+ {#if step === AuthStep.Login || step === AuthStep.SignUp} + {/if}
diff --git a/web-auth/src/components/EmailPasswordForm.svelte b/web-auth/src/components/EmailPasswordForm.svelte index 58a30e3c7df..96089b1520c 100644 --- a/web-auth/src/components/EmailPasswordForm.svelte +++ b/web-auth/src/components/EmailPasswordForm.svelte @@ -90,6 +90,8 @@ } function handleLogin(email: string, password: string) { + // NOTE: offloaded to Auth0 to handle both login and signup + // We don't need to check if the user exists in our database return webAuth.login( { realm: DATABASE_CONNECTION, diff --git a/web-auth/src/types.ts b/web-auth/src/types.ts index 86ba516e912..05cfd2e18f3 100644 --- a/web-auth/src/types.ts +++ b/web-auth/src/types.ts @@ -15,7 +15,7 @@ type InternalOptions = { leeway: number; }; -export interface Config { +export type Config = { auth0Domain: string; clientID: string; auth0Tenant: string; @@ -25,4 +25,4 @@ export interface Config { callbackURL: string; internalOptions: InternalOptions; extraParams?: { screen_hint?: string }; -} +}; From cd5766cb1cdbbd48bd3ca2424ba582eead998288 Mon Sep 17 00:00:00 2001 From: Cyrus Goh Date: Tue, 10 Dec 2024 16:06:32 +0800 Subject: [PATCH 07/18] Revert "offload login/signup to auth0" This reverts commit deea0d90f01cb5a31bb4a2b51cca17fdab78d79d. --- web-auth/package.json | 4 +- web-auth/src/components/Auth.svelte | 23 +++++-- .../src/components/EmailPasswordForm.svelte | 67 ++++++++++--------- 3 files changed, 55 insertions(+), 39 deletions(-) diff --git a/web-auth/package.json b/web-auth/package.json index fe99664f254..579914215f3 100644 --- a/web-auth/package.json +++ b/web-auth/package.json @@ -13,8 +13,8 @@ }, "devDependencies": { "@sveltejs/vite-plugin-svelte": "^3.1.2", - "@types/auth0-js": "^9.21.6", - "auth0-js": "^9.28.0", + "@types/auth0-js": "^9.14.7", + "auth0-js": "^9.20.2", "autoprefixer": "^10.4.20", "postcss": "^8.4.47", "svelte": "^4.2.19", diff --git a/web-auth/src/components/Auth.svelte b/web-auth/src/components/Auth.svelte index b9b74c4f589..cef04da7508 100644 --- a/web-auth/src/components/Auth.svelte +++ b/web-auth/src/components/Auth.svelte @@ -30,8 +30,7 @@ let step: AuthStep = AuthStep.Base; let webAuth: WebAuth; - $: isSignup = false; - $: isRillDash = false; + $: isLegacy = false; function isDomainDisabled(email: string): boolean { return disableForgotPassDomainsArr.some((domain) => @@ -46,10 +45,16 @@ decodeURIComponent(escape(window.atob(configParams))), ) as Config; - isSignup = config?.extraParams?.screen_hint === "signup"; + // Entry point: FIXME - There is no way to tell if the user is signing up unless we ask them to make a selection + // Entry point: Email invite redirect to signup + const isSignup = config?.extraParams?.screen_hint === "signup"; + + if (isSignup) { + step = AuthStep.SignUp; + } if (cloudClientIDsArr.includes(config?.clientID)) { - isRillDash = true; + isLegacy = true; } const authOptions: AuthOptions = Object.assign( @@ -92,9 +97,11 @@ function getHeadingText(step: AuthStep): string { switch (step) { case AuthStep.Base: + return "Log in or sign up"; case AuthStep.Login: + return "Log in with email"; case AuthStep.SignUp: - return "Log in or sign up"; + return "Sign up with email"; case AuthStep.Thanks: return "Thanks for signing up!"; default: @@ -105,7 +112,7 @@ function getSubheadingText(step: AuthStep, email: string): string { switch (step) { case AuthStep.Login: - return `Log in or sign up using ${email}`; + return `Log in using ${email}`; default: return ""; } @@ -164,8 +171,10 @@ {#if step === AuthStep.Login || step === AuthStep.SignUp} { - if (err) { - if (isRillDash && err.code === "user_not_found") { - // More restrictive error for Rill Dash - displayError({ - message: - "Invalid credentials. Please check your email and password.", - }); - } else { - displayError({ message: err?.description }); - } - } else { - disabled = false; - } - }, - ); - } - function authenticateUser(email: string, password: string) { disabled = true; errorText = ""; try { - handleLogin(email, password); + // NOTE: Sign up is only supported on Rill Cloud login pages, not Rill Dash + if (step === AuthStep.SignUp && !isLegacy) { + // Directly attempt to sign up and log in the user + webAuth.redirect.signupAndLogin( + { + connection: DATABASE_CONNECTION, + email: email, + password: password, + }, + (signupErr: any) => { + if (signupErr) { + handleAuthError(signupErr); + } else { + disabled = false; + } + }, + ); + } else { + webAuth.login( + { + realm: DATABASE_CONNECTION, + username: email, + password: password, + }, + (err) => { + if (err) { + displayError({ message: err?.description }); + } else { + disabled = false; + } + }, + ); + } } catch (err) { handleAuthError(err); } From cb465edfc0a2691b02a4e08599cb879eceb954d8 Mon Sep 17 00:00:00 2001 From: Cyrus Goh Date: Tue, 10 Dec 2024 16:13:30 +0800 Subject: [PATCH 08/18] prep work for checkUserExists --- web-auth/src/components/Auth.svelte | 44 ++++++++++++++++--- .../src/components/EmailPasswordForm.svelte | 8 ++-- 2 files changed, 42 insertions(+), 10 deletions(-) diff --git a/web-auth/src/components/Auth.svelte b/web-auth/src/components/Auth.svelte index cef04da7508..126381910ec 100644 --- a/web-auth/src/components/Auth.svelte +++ b/web-auth/src/components/Auth.svelte @@ -32,6 +32,8 @@ $: isLegacy = false; + let auth0Domain = ""; + function isDomainDisabled(email: string): boolean { return disableForgotPassDomainsArr.some((domain) => email.toLowerCase().endsWith(domain.toLowerCase()), @@ -45,8 +47,8 @@ decodeURIComponent(escape(window.atob(configParams))), ) as Config; - // Entry point: FIXME - There is no way to tell if the user is signing up unless we ask them to make a selection - // Entry point: Email invite redirect to signup + auth0Domain = config.auth0Domain; + const isSignup = config?.extraParams?.screen_hint === "signup"; if (isSignup) { @@ -82,15 +84,45 @@ }); } - function processEmailSubmission(event) { - email = event.detail.email; + // TODO: Revisit when we have the endpoint + async function checkUserExists(email: string): Promise { + try { + const response = await fetch( + `https://${auth0Domain}/dbconnections/change_password`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + email: email, + }), + }, + ); + + const data = await response.json(); + + if (data.error === "user not found") { + return false; + } + return true; + } catch (error) { + console.error("Error checking if user exists:", error); + return false; + } + } + + async function processEmailSubmission(event) { + email = event.detail.email; const connectionName = getConnectionFromEmail(email, connectionMapObj); if (connectionName) { authorizeSSO(email, connectionName); } else { - step = AuthStep.Login; + // Check if user exists before setting the step + const userExists = await checkUserExists(email); + step = userExists ? AuthStep.Login : AuthStep.SignUp; } } @@ -171,9 +203,9 @@ {#if step === AuthStep.Login || step === AuthStep.SignUp} Date: Thu, 12 Dec 2024 00:27:01 +0800 Subject: [PATCH 09/18] working version with pre user registration trigger --- web-auth/src/components/Auth.svelte | 102 +++++++----------- .../src/components/EmailPasswordForm.svelte | 74 +++++++------ 2 files changed, 79 insertions(+), 97 deletions(-) diff --git a/web-auth/src/components/Auth.svelte b/web-auth/src/components/Auth.svelte index 126381910ec..a90d40f25a1 100644 --- a/web-auth/src/components/Auth.svelte +++ b/web-auth/src/components/Auth.svelte @@ -31,8 +31,7 @@ let webAuth: WebAuth; $: isLegacy = false; - - let auth0Domain = ""; + $: isSignup = false; function isDomainDisabled(email: string): boolean { return disableForgotPassDomainsArr.some((domain) => @@ -43,37 +42,47 @@ $: domainDisabled = isDomainDisabled(email); function initConfig() { - const config = JSON.parse( - decodeURIComponent(escape(window.atob(configParams))), - ) as Config; + try { + if ( + import.meta.env.DEV && + (!configParams || configParams === "undefined") + ) { + console.warn( + "No auth config provided. In development mode - auth flows will not work.", + ); + errorText = "Authentication is not configured in development mode"; + return; + } - auth0Domain = config.auth0Domain; + const config = JSON.parse( + decodeURIComponent(escape(window.atob(configParams))), + ) as Config; - const isSignup = config?.extraParams?.screen_hint === "signup"; + isSignup = config?.extraParams?.screen_hint === "signup"; - if (isSignup) { - step = AuthStep.SignUp; - } - - if (cloudClientIDsArr.includes(config?.clientID)) { - isLegacy = true; - } + if (cloudClientIDsArr.includes(config?.clientID)) { + isLegacy = true; + } - const authOptions: AuthOptions = Object.assign( - { - overrides: { - __tenant: config.auth0Tenant, - __token_issuer: config.authorizationServer.issuer, + const authOptions: AuthOptions = Object.assign( + { + overrides: { + __tenant: config.auth0Tenant, + __token_issuer: config.authorizationServer.issuer, + }, + domain: config.auth0Domain, + clientID: config.clientID, + redirectUri: config.callbackURL, + responseType: "code", }, - domain: config.auth0Domain, - clientID: config.clientID, - redirectUri: config.callbackURL, - responseType: "code", - }, - config.internalOptions, - ); + config.internalOptions, + ); - webAuth = new auth0.WebAuth(authOptions); + webAuth = new auth0.WebAuth(authOptions); + } catch (e) { + console.error("Failed to initialize auth:", e); + errorText = "Failed to initialize authentication in development mode"; + } } function authorizeSSO(email: string, connectionName: string) { @@ -84,35 +93,6 @@ }); } - // TODO: Revisit when we have the endpoint - async function checkUserExists(email: string): Promise { - try { - const response = await fetch( - `https://${auth0Domain}/dbconnections/change_password`, - { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - email: email, - }), - }, - ); - - const data = await response.json(); - - if (data.error === "user not found") { - return false; - } - - return true; - } catch (error) { - console.error("Error checking if user exists:", error); - return false; - } - } - async function processEmailSubmission(event) { email = event.detail.email; const connectionName = getConnectionFromEmail(email, connectionMapObj); @@ -120,9 +100,7 @@ if (connectionName) { authorizeSSO(email, connectionName); } else { - // Check if user exists before setting the step - const userExists = await checkUserExists(email); - step = userExists ? AuthStep.Login : AuthStep.SignUp; + step = AuthStep.SignUp; } } @@ -133,7 +111,7 @@ case AuthStep.Login: return "Log in with email"; case AuthStep.SignUp: - return "Sign up with email"; + return `Log in or sign up with ${email}`; case AuthStep.Thanks: return "Thanks for signing up!"; default: @@ -167,7 +145,7 @@
- {headingText} + {@html headingText}
{#if subheadingText}
@@ -205,8 +183,6 @@ { - if (signupErr) { - handleAuthError(signupErr); + console.log("attempt to sign up and login"); + // Attempt to sign up and login the user + webAuth.redirect.signupAndLogin( + { + connection: DATABASE_CONNECTION, + email: email, + password: password, + }, + (err) => { + if (err) { + console.log("err", err); + // Check if the error is about user already existing + if (err.description && err.description.includes("User exists.")) { + // If user exists, try logging them in + console.log("user exists, trying to login"); + webAuth.login( + { + realm: DATABASE_CONNECTION, + username: email, + password: password, + }, + (loginErr) => { + if (loginErr) { + displayError({ message: loginErr?.description }); + showForgetPassword = true; + } else { + disabled = false; + } + }, + ); } else { - disabled = false; + handleAuthError(err); + showForgetPassword = true; } - }, - ); - } else { - // User exists, attempt to login - webAuth.login( - { - realm: DATABASE_CONNECTION, - username: email, - password: password, - }, - (err) => { - if (err) { - displayError({ message: err?.description }); - } else { - disabled = false; - } - }, - ); - } + } else { + disabled = false; + } + }, + ); } catch (err) { handleAuthError(err); + showForgetPassword = true; } } From 33f91f3c2b65e113a7b82dab7b655d3b536a8a40 Mon Sep 17 00:00:00 2001 From: Cyrus Goh Date: Thu, 12 Dec 2024 15:27:53 +0800 Subject: [PATCH 10/18] clean up --- web-auth/src/components/EmailPasswordForm.svelte | 3 --- 1 file changed, 3 deletions(-) diff --git a/web-auth/src/components/EmailPasswordForm.svelte b/web-auth/src/components/EmailPasswordForm.svelte index fbd0f0a4149..695bf606fef 100644 --- a/web-auth/src/components/EmailPasswordForm.svelte +++ b/web-auth/src/components/EmailPasswordForm.svelte @@ -95,7 +95,6 @@ showForgetPassword = false; try { - console.log("attempt to sign up and login"); // Attempt to sign up and login the user webAuth.redirect.signupAndLogin( { @@ -105,11 +104,9 @@ }, (err) => { if (err) { - console.log("err", err); // Check if the error is about user already existing if (err.description && err.description.includes("User exists.")) { // If user exists, try logging them in - console.log("user exists, trying to login"); webAuth.login( { realm: DATABASE_CONNECTION, From f36ae9c694ba49ce8cf79370bac0dffdc2c498a4 Mon Sep 17 00:00:00 2001 From: Cyrus Goh Date: Thu, 12 Dec 2024 15:39:51 +0800 Subject: [PATCH 11/18] clean up --- web-auth/src/components/Auth.svelte | 1 - web-auth/src/components/EmailPasswordForm.svelte | 1 - 2 files changed, 2 deletions(-) diff --git a/web-auth/src/components/Auth.svelte b/web-auth/src/components/Auth.svelte index a90d40f25a1..49358d44cf3 100644 --- a/web-auth/src/components/Auth.svelte +++ b/web-auth/src/components/Auth.svelte @@ -182,7 +182,6 @@ {#if step === AuthStep.Login || step === AuthStep.SignUp} Date: Thu, 12 Dec 2024 16:14:44 +0800 Subject: [PATCH 12/18] clean up --- web-auth/src/components/Auth.svelte | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/web-auth/src/components/Auth.svelte b/web-auth/src/components/Auth.svelte index 49358d44cf3..7a274e9fd8a 100644 --- a/web-auth/src/components/Auth.svelte +++ b/web-auth/src/components/Auth.svelte @@ -30,8 +30,7 @@ let step: AuthStep = AuthStep.Base; let webAuth: WebAuth; - $: isLegacy = false; - $: isSignup = false; + // $: isLegacy = false; function isDomainDisabled(email: string): boolean { return disableForgotPassDomainsArr.some((domain) => @@ -58,12 +57,19 @@ decodeURIComponent(escape(window.atob(configParams))), ) as Config; - isSignup = config?.extraParams?.screen_hint === "signup"; + const isSignup = config?.extraParams?.screen_hint === "signup"; - if (cloudClientIDsArr.includes(config?.clientID)) { - isLegacy = true; + if (isSignup) { + step = AuthStep.SignUp; } + // UNCOMMENT to use `isLegacy` for rill dash check if needed + // NOTE: Check for cloud client ids from auth0 to allow sign up and login + // NOTE: Prevent rill dash (legacy) users from signing up + // if (cloudClientIDsArr.includes(config?.clientID)) { + // isLegacy = true; + // } + const authOptions: AuthOptions = Object.assign( { overrides: { From 509fa8e09ea7e761f32146be35085d2d3bdc2749 Mon Sep 17 00:00:00 2001 From: Cyrus Goh Date: Tue, 4 Feb 2025 17:37:57 -0800 Subject: [PATCH 13/18] reinstate cloud client ids --- web-auth/src/components/Auth.svelte | 55 ++++++++++--------- .../src/components/EmailSubmissionForm.svelte | 4 +- 2 files changed, 31 insertions(+), 28 deletions(-) diff --git a/web-auth/src/components/Auth.svelte b/web-auth/src/components/Auth.svelte index 7a274e9fd8a..3c8b9edddbb 100644 --- a/web-auth/src/components/Auth.svelte +++ b/web-auth/src/components/Auth.svelte @@ -24,13 +24,15 @@ const cloudClientIDsArr = cloudClientIDs.split(","); const disableForgotPassDomainsArr = disableForgotPassDomains.split(","); - $: errorText = ""; - let email = ""; let step: AuthStep = AuthStep.Base; let webAuth: WebAuth; - // $: isLegacy = false; + $: errorText = ""; + $: isAllowedClient = false; + $: domainDisabled = isDomainDisabled(email); + $: headingText = getHeadingText(step); + $: subheadingText = getSubheadingText(step, email); function isDomainDisabled(email: string): boolean { return disableForgotPassDomainsArr.some((domain) => @@ -38,38 +40,41 @@ ); } - $: domainDisabled = isDomainDisabled(email); + function configureDevMode() { + if ( + import.meta.env.DEV && + (!configParams || configParams === "undefined") + ) { + console.warn( + "No auth config provided. In development mode - auth flows will not work.", + ); + errorText = "Authentication is not configured in development mode"; + isAllowedClient = true; + return; + } + } - function initConfig() { + function init() { try { - if ( - import.meta.env.DEV && - (!configParams || configParams === "undefined") - ) { - console.warn( - "No auth config provided. In development mode - auth flows will not work.", - ); - errorText = "Authentication is not configured in development mode"; - return; - } + configureDevMode(); const config = JSON.parse( decodeURIComponent(escape(window.atob(configParams))), ) as Config; + if (!cloudClientIDsArr.includes(config?.clientID)) { + errorText = "Authentication is not available for this client"; + isAllowedClient = false; + return; + } + isAllowedClient = true; + const isSignup = config?.extraParams?.screen_hint === "signup"; if (isSignup) { step = AuthStep.SignUp; } - // UNCOMMENT to use `isLegacy` for rill dash check if needed - // NOTE: Check for cloud client ids from auth0 to allow sign up and login - // NOTE: Prevent rill dash (legacy) users from signing up - // if (cloudClientIDsArr.includes(config?.clientID)) { - // isLegacy = true; - // } - const authOptions: AuthOptions = Object.assign( { overrides: { @@ -139,11 +144,8 @@ } onMount(() => { - initConfig(); + init(); }); - - $: headingText = getHeadingText(step); - $: subheadingText = getSubheadingText(step, email); @@ -191,6 +193,7 @@ isDomainDisabled={domainDisabled} {webAuth} on:back={backToBaseStep} + disabled={!isAllowedClient} /> {/if}
diff --git a/web-auth/src/components/EmailSubmissionForm.svelte b/web-auth/src/components/EmailSubmissionForm.svelte index 9b2f2608bfe..5e4a207b865 100644 --- a/web-auth/src/components/EmailSubmissionForm.svelte +++ b/web-auth/src/components/EmailSubmissionForm.svelte @@ -37,7 +37,7 @@ } } - $: disabled = !(email.length > 0 && validateEmail(email)); + $: buttonDisabled = disabled || !(email.length > 0 && validateEmail(email));
@@ -58,7 +58,7 @@ {/if}
- +
Continue with email
From b1c1f992100c61f52db8d0e7febf0650ad9fe568 Mon Sep 17 00:00:00 2001 From: Cyrus Goh Date: Tue, 4 Feb 2025 18:48:14 -0800 Subject: [PATCH 14/18] more contextual error message in dev --- web-auth/src/components/Auth.svelte | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/web-auth/src/components/Auth.svelte b/web-auth/src/components/Auth.svelte index 3c8b9edddbb..b3b06920160 100644 --- a/web-auth/src/components/Auth.svelte +++ b/web-auth/src/components/Auth.svelte @@ -97,6 +97,11 @@ } function authorizeSSO(email: string, connectionName: string) { + if (import.meta.env.DEV) { + errorText = "SSO authentication is not available in development mode"; + return; + } + webAuth.authorize({ connection: connectionName, login_hint: email, @@ -105,6 +110,7 @@ } async function processEmailSubmission(event) { + errorText = ""; email = event.detail.email; const connectionName = getConnectionFromEmail(email, connectionMapObj); @@ -141,9 +147,15 @@ function backToBaseStep() { step = AuthStep.Base; + errorText = ""; } onMount(() => { + if (import.meta.env.DEV) { + errorText = "Unable to initialize auth0 client in development mode"; + return; + } + init(); }); @@ -170,6 +182,12 @@ { + errorText = ""; + if (import.meta.env.DEV) { + errorText = + "OAuth authentication is not available in development mode"; + return; + } webAuth.authorize({ connection }); }} > From 337850e2adaf98400998530eda7471e297a2981d Mon Sep 17 00:00:00 2001 From: Cyrus Goh Date: Wed, 5 Feb 2025 17:39:37 -0800 Subject: [PATCH 15/18] check user exists when continue with email --- web-auth/src/App.svelte | 4 + web-auth/src/components/Auth.svelte | 83 +++++++++++++++++-- .../src/components/EmailPasswordForm.svelte | 63 +++++++------- web-auth/src/types.ts | 5 +- 4 files changed, 116 insertions(+), 39 deletions(-) diff --git a/web-auth/src/App.svelte b/web-auth/src/App.svelte index 008c4a8cd06..36bfd6c1675 100644 --- a/web-auth/src/App.svelte +++ b/web-auth/src/App.svelte @@ -5,6 +5,8 @@ const cloudClientIDs = import.meta.env.VITE_RILL_CLOUD_AUTH0_CLIENT_IDS; const disableForgotPassDomains = import.meta.env .VITE_DISABLE_FORGOT_PASS_DOMAINS; + const auth0Domain = import.meta.env.VITE_AUTH0_DOMAIN; + const auth0BearerToken = import.meta.env.VITE_AUTH0_BEARER_TOKEN; // This gets populated by Auth0 runtime const configParams = "@@config@@"; @@ -33,5 +35,7 @@ {cloudClientIDs} {disableForgotPassDomains} {connectionMap} + {auth0Domain} + {auth0BearerToken} /> diff --git a/web-auth/src/components/Auth.svelte b/web-auth/src/components/Auth.svelte index b3b06920160..18284e636fd 100644 --- a/web-auth/src/components/Auth.svelte +++ b/web-auth/src/components/Auth.svelte @@ -19,19 +19,25 @@ export let cloudClientIDs = ""; export let disableForgotPassDomains = ""; export let connectionMap = "{}"; + export let auth0Domain = ""; + export let auth0BearerToken = ""; const connectionMapObj = JSON.parse(connectionMap); const cloudClientIDsArr = cloudClientIDs.split(","); const disableForgotPassDomainsArr = disableForgotPassDomains.split(","); + const AUTH0_DOMAIN = auth0Domain; + const AUTH0_MANAGEMENT_API_TOKEN = auth0BearerToken; + let email = ""; let step: AuthStep = AuthStep.Base; let webAuth: WebAuth; + let isExistingUser = false; $: errorText = ""; $: isAllowedClient = false; $: domainDisabled = isDomainDisabled(email); - $: headingText = getHeadingText(step); + $: headingText = getHeadingText(step, isExistingUser, email); $: subheadingText = getSubheadingText(step, email); function isDomainDisabled(email: string): boolean { @@ -50,6 +56,8 @@ ); errorText = "Authentication is not configured in development mode"; isAllowedClient = true; + + step = AuthStep.Base; return; } } @@ -109,26 +117,87 @@ }); } + async function checkUserExists(email: string) { + if (import.meta.env.DEV) { + errorText = "User existence check is not available in development mode"; + return; + } + + try { + const response = await fetch( + `https://${AUTH0_DOMAIN}/api/v2/users-by-email?email=${encodeURIComponent(email)}`, + { + method: "GET", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${AUTH0_MANAGEMENT_API_TOKEN}`, + }, + }, + ); + + if (!response.ok) { + throw new Error( + `Failed to check user existence: ${response.statusText}`, + ); + } + + const users = await response.json(); + isExistingUser = users.length > 0; + + console.log("User existence check:", { isExistingUser }); + + step = isExistingUser ? AuthStep.Login : AuthStep.SignUp; + } catch (error) { + console.error("Error checking user existence:", error); + errorText = "Unable to verify user existence. Please try again."; + step = AuthStep.Base; // Reset to allow retry + } + } + async function processEmailSubmission(event) { errorText = ""; + isExistingUser = false; email = event.detail.email; const connectionName = getConnectionFromEmail(email, connectionMapObj); + step = AuthStep.Loading; + headingText = getHeadingText(step, isExistingUser, email); // Temporary loading heading + subheadingText = getSubheadingText(step, email); // No subheading during loading + if (connectionName) { authorizeSSO(email, connectionName); } else { - step = AuthStep.SignUp; + await checkUserExists(email); + + console.log("after checking: ", email, isExistingUser); + + if (isExistingUser) { + step = AuthStep.Login; + } else { + step = AuthStep.SignUp; + } + + headingText = getHeadingText(step, isExistingUser, email); + subheadingText = getSubheadingText(step, email); } } - function getHeadingText(step: AuthStep): string { + function getHeadingText( + step: AuthStep, + isExisting: boolean, + email: string, + ): string { switch (step) { case AuthStep.Base: - return "Log in or sign up"; + return "Continue to Rill"; case AuthStep.Login: return "Log in with email"; case AuthStep.SignUp: - return `Log in or sign up with ${email}`; + return isExisting + ? `Welcome back to Rill` + : `Sign up with ${email}`; + case AuthStep.Loading: + return "Checking..."; case AuthStep.Thanks: return "Thanks for signing up!"; default: @@ -140,6 +209,8 @@ switch (step) { case AuthStep.Login: return `Log in using ${email}`; + case AuthStep.Loading: + return ""; default: return ""; } @@ -148,6 +219,7 @@ function backToBaseStep() { step = AuthStep.Base; errorText = ""; + isExistingUser = false; } onMount(() => { @@ -208,6 +280,7 @@ {#if step === AuthStep.Login || step === AuthStep.SignUp} { - if (err) { - // Check if the error is about user already existing - if (err.description && err.description.includes("User exists.")) { - // If user exists, try logging them in - webAuth.login( - { - realm: DATABASE_CONNECTION, - username: email, - password: password, - }, - (loginErr) => { - if (loginErr) { - displayError({ message: loginErr?.description }); - showForgetPassword = true; - } else { - disabled = false; - } - }, - ); + if (step === AuthStep.Login) { + webAuth.login( + { + realm: DATABASE_CONNECTION, + username: email, + password: password, + }, + (loginErr) => { + if (loginErr) { + displayError({ message: loginErr?.description }); + showForgetPassword = true; } else { + disabled = false; + } + }, + ); + } else { + webAuth.redirect.signupAndLogin( + { + connection: DATABASE_CONNECTION, + email: email, + password: password, + }, + (err) => { + if (err) { handleAuthError(err); showForgetPassword = true; + } else { + disabled = false; } - } else { - disabled = false; - } - }, - ); + }, + ); + } } catch (err) { handleAuthError(err); showForgetPassword = true; diff --git a/web-auth/src/types.ts b/web-auth/src/types.ts index 05cfd2e18f3..14f6230613e 100644 --- a/web-auth/src/types.ts +++ b/web-auth/src/types.ts @@ -1,9 +1,10 @@ export enum AuthStep { Base = 0, - SSO = 1, + // SSO = 1, Login = 2, SignUp = 3, - Thanks = 4, + // Thanks = 4, + Loading = 5, } type InternalOptions = { From dbaadca8f18214f4a7742ce3e7fb6c5ff153500c Mon Sep 17 00:00:00 2001 From: Cyrus Goh Date: Wed, 5 Feb 2025 18:11:27 -0800 Subject: [PATCH 16/18] lint --- web-auth/src/components/Auth.svelte | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web-auth/src/components/Auth.svelte b/web-auth/src/components/Auth.svelte index 18284e636fd..26abb83925c 100644 --- a/web-auth/src/components/Auth.svelte +++ b/web-auth/src/components/Auth.svelte @@ -198,8 +198,8 @@ : `Sign up with ${email}`; case AuthStep.Loading: return "Checking..."; - case AuthStep.Thanks: - return "Thanks for signing up!"; + // case AuthStep.Thanks: + // return "Thanks for signing up!"; default: return ""; } From 5ee9fcd2b0f9dc0a174c488559c2887c6743331c Mon Sep 17 00:00:00 2001 From: Cyrus Goh Date: Thu, 6 Feb 2025 12:26:40 -0800 Subject: [PATCH 17/18] specify which fields show in user existence check --- web-auth/src/components/Auth.svelte | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/web-auth/src/components/Auth.svelte b/web-auth/src/components/Auth.svelte index 26abb83925c..5a049ff3a0e 100644 --- a/web-auth/src/components/Auth.svelte +++ b/web-auth/src/components/Auth.svelte @@ -125,7 +125,7 @@ try { const response = await fetch( - `https://${AUTH0_DOMAIN}/api/v2/users-by-email?email=${encodeURIComponent(email)}`, + `https://${AUTH0_DOMAIN}/api/v2/users-by-email?email=${encodeURIComponent(email)}&fields=email`, { method: "GET", headers: { @@ -150,7 +150,7 @@ } catch (error) { console.error("Error checking user existence:", error); errorText = "Unable to verify user existence. Please try again."; - step = AuthStep.Base; // Reset to allow retry + step = AuthStep.Base; } } @@ -161,8 +161,8 @@ const connectionName = getConnectionFromEmail(email, connectionMapObj); step = AuthStep.Loading; - headingText = getHeadingText(step, isExistingUser, email); // Temporary loading heading - subheadingText = getSubheadingText(step, email); // No subheading during loading + headingText = getHeadingText(step, isExistingUser, email); + subheadingText = getSubheadingText(step, email); if (connectionName) { authorizeSSO(email, connectionName); @@ -198,8 +198,6 @@ : `Sign up with ${email}`; case AuthStep.Loading: return "Checking..."; - // case AuthStep.Thanks: - // return "Thanks for signing up!"; default: return ""; } From 9fd8fb4f7167db376d2ed6894d90b3c00188bf71 Mon Sep 17 00:00:00 2001 From: Cyrus Goh Date: Thu, 6 Feb 2025 12:59:33 -0800 Subject: [PATCH 18/18] token generation, todo --- web-auth/src/App.svelte | 6 ++- web-auth/src/components/Auth.svelte | 60 ++++++++++++++++++++++++++--- 2 files changed, 59 insertions(+), 7 deletions(-) diff --git a/web-auth/src/App.svelte b/web-auth/src/App.svelte index 36bfd6c1675..c55aa8193af 100644 --- a/web-auth/src/App.svelte +++ b/web-auth/src/App.svelte @@ -6,7 +6,8 @@ const disableForgotPassDomains = import.meta.env .VITE_DISABLE_FORGOT_PASS_DOMAINS; const auth0Domain = import.meta.env.VITE_AUTH0_DOMAIN; - const auth0BearerToken = import.meta.env.VITE_AUTH0_BEARER_TOKEN; + const auth0ClientID = import.meta.env.VITE_AUTH0_CLIENT_ID; + const auth0ClientSecret = import.meta.env.VITE_AUTH0_CLIENT_SECRET; // This gets populated by Auth0 runtime const configParams = "@@config@@"; @@ -36,6 +37,7 @@ {disableForgotPassDomains} {connectionMap} {auth0Domain} - {auth0BearerToken} + {auth0ClientID} + {auth0ClientSecret} /> diff --git a/web-auth/src/components/Auth.svelte b/web-auth/src/components/Auth.svelte index 5a049ff3a0e..ebb9d4e9870 100644 --- a/web-auth/src/components/Auth.svelte +++ b/web-auth/src/components/Auth.svelte @@ -20,19 +20,22 @@ export let disableForgotPassDomains = ""; export let connectionMap = "{}"; export let auth0Domain = ""; - export let auth0BearerToken = ""; + export let auth0ClientID = ""; + export let auth0ClientSecret = ""; const connectionMapObj = JSON.parse(connectionMap); const cloudClientIDsArr = cloudClientIDs.split(","); const disableForgotPassDomainsArr = disableForgotPassDomains.split(","); - const AUTH0_DOMAIN = auth0Domain; - const AUTH0_MANAGEMENT_API_TOKEN = auth0BearerToken; + const AUTH0_CLIENT_ID = auth0ClientID; + const AUTH0_CLIENT_SECRET = auth0ClientSecret; let email = ""; let step: AuthStep = AuthStep.Base; let webAuth: WebAuth; let isExistingUser = false; + let tokenExpiry = 0; + let managementApiToken = null; $: errorText = ""; $: isAllowedClient = false; @@ -117,6 +120,52 @@ }); } + // TODO: Serverless function + async function getManagementApiToken() { + const now = Math.floor(Date.now() / 1000); + + if (managementApiToken && now < tokenExpiry) { + return managementApiToken; + } + + try { + const response = await fetch(`https://${AUTH0_DOMAIN}/oauth/token`, { + method: "POST", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Methods": "POST, OPTIONS", + "Access-Control-Allow-Headers": "Content-Type", + }, + body: JSON.stringify({ + client_id: AUTH0_CLIENT_ID, + client_secret: AUTH0_CLIENT_SECRET, + audience: `https://${AUTH0_DOMAIN}/api/v2/`, + grant_type: "client_credentials", + }), + }); + + if (!response.ok) { + throw new Error( + `Failed to obtain management API token: ${response.statusText}`, + ); + } + + const data = await response.json(); + managementApiToken = data.access_token; + tokenExpiry = now + data.expires_in; + + return managementApiToken; + } catch (error) { + console.error("Error obtaining management API token:", error); + step = AuthStep.Base; + errorText = "Unable to verify user existence. Please try again."; + throw error; + } + } + + // TODO: Serverless function async function checkUserExists(email: string) { if (import.meta.env.DEV) { errorText = "User existence check is not available in development mode"; @@ -124,13 +173,15 @@ } try { + const token = await getManagementApiToken(); + // TODO: use our own API const response = await fetch( `https://${AUTH0_DOMAIN}/api/v2/users-by-email?email=${encodeURIComponent(email)}&fields=email`, { method: "GET", headers: { "Content-Type": "application/json", - Authorization: `Bearer ${AUTH0_MANAGEMENT_API_TOKEN}`, + Authorization: `Bearer ${token}`, }, }, ); @@ -143,7 +194,6 @@ const users = await response.json(); isExistingUser = users.length > 0; - console.log("User existence check:", { isExistingUser }); step = isExistingUser ? AuthStep.Login : AuthStep.SignUp;