Skip to content

Commit 076b163

Browse files
committed
Use POST forms instead of fetch for OAuth redirects
1 parent 8148fb3 commit 076b163

4 files changed

Lines changed: 12 additions & 64 deletions

File tree

src/lib/api/next/base.ts

Lines changed: 1 addition & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -65,34 +65,4 @@ export async function PostJson<T>(
6565
const data = await res.json();
6666

6767
return transformer(data);
68-
}
69-
70-
export async function PostRedirect(
71-
path: Path,
72-
expectedStatus: 302 | 303 | 307 | 308 = 302
73-
): Promise<void> {
74-
const url = BaseUrl + path;
75-
const res = await fetch(url, { method: 'POST', redirect: 'manual' });
76-
77-
if (res.status !== expectedStatus) {
78-
throw new ResponseError(res, `Unexpected status ${res.status} for POST ${url}`);
79-
}
80-
81-
const loc = res.headers.get('Location');
82-
if (!loc) throw new ResponseError(res, 'Missing Location header');
83-
84-
const target = new URL(loc, url); // handles absolute or relative
85-
86-
// Restrict which domains we can be redirected to
87-
const allowedHosts = new Set([PUBLIC_BACKEND_API_DOMAIN, 'accounts.google.com', 'github.com']);
88-
89-
if (!allowedHosts.has(target.hostname)) {
90-
throw new ResponseError(res, `Blocked redirect to disallowed host ${target.href}`);
91-
}
92-
93-
if (target.protocol !== 'https:') {
94-
throw new ResponseError(res, `Blocked non-HTTPS redirect to ${target.href}`);
95-
}
96-
97-
window.location.assign(target.toString());
98-
}
68+
}

src/lib/api/next/oauth.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { GetJson, PostJson, PostRedirect } from './base';
1+
import { GetBackendUrl, GetJson, PostJson } from './base';
22
import type { LoginOkResponse, OAuthFinalizeRequest, OAuthSignupData } from './models';
33
import {
44
TransformLoginOkResponse,
@@ -10,10 +10,10 @@ export function OAuthListProviders(): Promise<string[]> {
1010
return GetJson<string[]>('/1/oauth/providers', 200, ValidateStringArray);
1111
}
1212

13-
export function OAuthAuthorize(provider: string, flow: 'LoginOrCreate' | 'Link') {
13+
export function GetOAuthAuthorizeUrl(provider: string, flow: 'LoginOrCreate' | 'Link') {
1414
const providerEnc = encodeURIComponent(provider);
1515
const flowEnc = encodeURIComponent(flow);
16-
return PostRedirect(`/1/oauth/${providerEnc}/authorize?flow=${flowEnc}`, 302);
16+
return GetBackendUrl(`/1/oauth/${providerEnc}/authorize?flow=${flowEnc}`);
1717
}
1818

1919
export async function OAuthSignupGetData(provider: string) {

src/routes/(anonymous)/login/+page.svelte

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
<script lang="ts">
22
import { goto } from '$app/navigation';
33
import { page } from '$app/state';
4-
import { PUBLIC_BACKEND_API_DOMAIN } from '$env/static/public';
54
import { accountV2Api } from '$lib/api';
6-
import { OAuthAuthorize } from '$lib/api/next/oauth';
5+
import { GetOAuthAuthorizeUrl } from '$lib/api/next/oauth';
76
import Container from '$lib/components/Container.svelte';
87
import Turnstile from '$lib/components/Turnstile.svelte';
98
import PasswordInput from '$lib/components/input/PasswordInput.svelte';
@@ -54,11 +53,6 @@
5453
}
5554
}
5655
57-
async function startOAuth() {
58-
const provider = 'discord';
59-
await OAuthAuthorize(provider, 'LoginOrCreate');
60-
}
61-
6256
let canSubmit = $derived(
6357
usernameOrEmail.length > 0 && password.length > 0 && turnstileResponse != null
6458
);
@@ -89,7 +83,9 @@
8983
<Button type="submit" disabled={!canSubmit}>Log In</Button>
9084

9185
<a class=" text-sm opacity-75 hover:underline" href="/forgot-password">Forgot your password?</a>
92-
93-
<Button type="button" onclick={startOAuth}>Log In With Discord</Button>
86+
</form>
87+
88+
<form action={GetOAuthAuthorizeUrl('discord', 'LoginOrCreate')} method="POST">
89+
<Button type="submit">Log In With Discord</Button>
9490
</form>
9591
</Container>

src/routes/(authenticated)/settings/connections/+page.svelte

Lines changed: 3 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import { page } from '$app/state';
77
import { accountV1Api } from '$lib/api';
88
import type { OAuthConnectionResponse } from '$lib/api/internal/v1/models';
9-
import { OAuthAuthorize, OAuthListProviders } from '$lib/api/next/oauth';
9+
import { OAuthListProviders } from '$lib/api/next/oauth';
1010
import Container from '$lib/components/Container.svelte';
1111
import { Button } from '$lib/components/ui/button';
1212
import * as Card from '$lib/components/ui/card';
@@ -21,7 +21,6 @@
2121
let loading = $state(false); // overall refresh button state
2222
let loadingProviders = $state(true);
2323
let loadingConnections = $state(true);
24-
let actionBusy = $state<string | null>(null); // providerKey currently acting on
2524
2625
// From redirect (?status=)
2726
let queryStatus = $derived(page.url.searchParams.get('status'));
@@ -82,18 +81,6 @@
8281
return connections.find((c) => c.providerKey === key)?.displayName ?? null;
8382
}
8483
85-
// Start an OAuth flow via your backend redirect
86-
async function beginLink(providerKey: string) {
87-
actionBusy = providerKey;
88-
try {
89-
await OAuthAuthorize(providerKey, 'Link');
90-
} catch (err) {
91-
await handleApiError(err);
92-
} finally {
93-
actionBusy = null;
94-
}
95-
}
96-
9784
// Open confirm dialog for disconnect
9885
function confirmDisconnect(providerKey: string) {
9986
const existing = connections.find((c) => c.providerKey === providerKey);
@@ -145,13 +132,8 @@
145132
{:else}
146133
{#each providers as p}
147134
{#if !isConnected(p)}
148-
<Dropdown.Item
149-
onclick={() => beginLink(p)}
150-
data-provider={p}
151-
disabled={actionBusy === p}
152-
>
153-
<Link2 class="mr-2 size-4" />
154-
{actionBusy === p ? `Starting ${p}…` : p}
135+
<Dropdown.Item>
136+
<Link2 class="mr-2 size-4" /> <!-- TODO: Form + button [POST] -->
155137
</Dropdown.Item>
156138
{/if}
157139
{/each}

0 commit comments

Comments
 (0)