diff --git a/src/lib/helpers/oauth.ts b/src/lib/helpers/oauth.ts new file mode 100644 index 000000000..b02805d1a --- /dev/null +++ b/src/lib/helpers/oauth.ts @@ -0,0 +1,98 @@ +type BuildOAuthSuccessUrlParams = { + pageUrl: URL; + basePath: string; + origin: string; + isStudio: boolean; +}; + +const STUDIO_PROMPT_KEY = 'studioPrompt'; +const ABSOLUTE_URL = /^[a-zA-Z][a-zA-Z\d+.-]*:/; + +function isAbsoluteUrl(value: string): boolean { + return ABSOLUTE_URL.test(value) || value.startsWith('//'); +} + +function stashStudioPrompt(prompt: string, isStudio: boolean): void { + if (!isStudio || !prompt) { + return; + } + + try { + sessionStorage.setItem(STUDIO_PROMPT_KEY, prompt); + } catch { + // ignore + } +} + +function formatUrl(url: URL, original: string): string { + if (isAbsoluteUrl(original)) { + return url.toString(); + } + + return `${url.pathname}${url.search}${url.hash}`; +} + +function stripPromptFromTarget(target: string, isStudio: boolean): string { + if (!isStudio) { + return target; + } + + try { + const url = new URL(target, window.location.origin); + const prompt = url.searchParams.get('prompt'); + + if (prompt) { + stashStudioPrompt(prompt, isStudio); + url.searchParams.delete('prompt'); + } + + return formatUrl(url, target); + } catch { + return target; + } +} + +function appendQuery(target: string, params: URLSearchParams): string { + const query = params.toString(); + if (!query) { + return target; + } + + const hashIndex = target.indexOf('#'); + const hash = hashIndex >= 0 ? target.slice(hashIndex) : ''; + const base = hashIndex >= 0 ? target.slice(0, hashIndex) : target; + const separator = base.includes('?') ? '&' : '?'; + + return `${base}${separator}${query}${hash}`; +} + +export function buildOAuthSuccessUrl({ + pageUrl, + basePath, + origin, + isStudio +}: BuildOAuthSuccessUrlParams): string { + const params = new URLSearchParams(pageUrl.search); + const redirect = params.get('redirect'); + + if (redirect) { + params.delete('redirect'); + } + + if (isStudio) { + const prompt = params.get('prompt'); + if (prompt) { + stashStudioPrompt(prompt, isStudio); + params.delete('prompt'); + } + } + + let target = redirect ? stripPromptFromTarget(redirect, isStudio) : basePath; + target = appendQuery(target, params); + + if (isAbsoluteUrl(target)) { + return target; + } + + return origin + target; +} diff --git a/src/routes/(public)/(guest)/login/+page.svelte b/src/routes/(public)/(guest)/login/+page.svelte index 9766f0e4d..7f6118655 100644 --- a/src/routes/(public)/(guest)/login/+page.svelte +++ b/src/routes/(public)/(guest)/login/+page.svelte @@ -4,13 +4,14 @@ import { Button, Form, InputEmail, InputPassword } from '$lib/elements/forms'; import { addNotification } from '$lib/stores/notifications'; import { sdk } from '$lib/stores/sdk'; + import { buildOAuthSuccessUrl } from '$lib/helpers/oauth'; import { Dependencies } from '$lib/constants'; import { Submit, trackEvent, trackError } from '$lib/actions/analytics'; import { page } from '$app/state'; import { redirectTo } from '$routes/store'; import { user } from '$lib/stores/user'; import { Layout } from '@appwrite.io/pink-svelte'; - import { Logins, resolvedProfile } from '$lib/profiles/index.svelte'; + import { Logins, ProfileMode, resolvedProfile } from '$lib/profiles/index.svelte'; import type { OAuthProvider } from '@appwrite.io/console'; import type { PageProps } from './$types.js'; @@ -71,20 +72,17 @@ function onOauthLogin(config: { provider: OAuthProvider; scopes: string[] }) { clearAuthToken(); - let url = window.location.origin; - if (page.url.searchParams) { - const redirect = page.url.searchParams.get('redirect'); - page.url.searchParams.delete('redirect'); - if (redirect) { - url = `${redirect}${page.url.search}`; - } else { - url = `${base}${page.url.search ?? ''}`; - } - } + const successUrl = buildOAuthSuccessUrl({ + pageUrl: page.url, + basePath: base, + origin: window.location.origin, + isStudio: resolvedProfile.id === ProfileMode.STUDIO + }); + sdk.forConsole.account.createOAuth2Session({ provider: config.provider, - success: window.location.origin + url, + success: successUrl, failure: window.location.origin, scopes: config.scopes }); diff --git a/src/routes/(public)/auth/preview/access/+page.svelte b/src/routes/(public)/auth/preview/access/+page.svelte index 128774104..6fed47089 100644 --- a/src/routes/(public)/auth/preview/access/+page.svelte +++ b/src/routes/(public)/auth/preview/access/+page.svelte @@ -17,6 +17,7 @@ } from '$lib/elements/forms'; import { logout } from '$lib/helpers/logout'; import { sdk } from '$lib/stores/sdk'; + import { buildOAuthSuccessUrl } from '$lib/helpers/oauth'; import { isCloud } from '$lib/system'; import { ID, OAuthProvider } from '@appwrite.io/console'; import { Layout, Typography } from '@appwrite.io/pink-svelte'; @@ -24,7 +25,7 @@ import BGDark from './bg_dark.jpg'; import BGLight from './bg_light.jpg'; import { app } from '$lib/stores/app.js'; - import { resolvedProfile } from '$lib/profiles/index.svelte'; + import { ProfileMode, resolvedProfile } from '$lib/profiles/index.svelte'; export let data; @@ -88,20 +89,16 @@ } function onGithubAuth() { - let url = window.location.origin; + const success = buildOAuthSuccessUrl({ + pageUrl: page.url, + basePath: base, + origin: window.location.origin, + isStudio: resolvedProfile.id === ProfileMode.STUDIO + }); - if (page.url.searchParams) { - const redirect = page.url.searchParams.get('redirect'); - page.url.searchParams.delete('redirect'); - if (redirect) { - url = `${redirect}${page.url.search}`; - } else { - url = `${base}${page.url.search ?? ''}`; - } - } sdk.forConsole.account.createOAuth2Session({ provider: OAuthProvider.Github, - success: window.location.origin + url, + success, failure: window.location.origin, scopes: ['read:user', 'user:email'] });