initial commit, lots of random shit

This commit is contained in:
Erin 2023-11-11 19:09:24 -05:00
commit 7bb18f0a71
27 changed files with 3394 additions and 0 deletions

View file

@ -0,0 +1,51 @@
import {discordAvatarURL} from '$lib/server/auth/discord';
import {getDataSource} from '$lib/server/db';
import {AuthSession} from '$lib/server/entity/AuthSession';
import type {LayoutServerLoad} from './$types';
async function findSession (sessionID?: string) {
const dataSource = await getDataSource();
const sessionsRepo = dataSource.getRepository(AuthSession);
if (!sessionID) { return null; }
return await sessionsRepo.findOne({where: {id: sessionID}});
}
export const load: LayoutServerLoad = async ({cookies}) => {
const sessionID = cookies.get('sessionid');
let session = await findSession(sessionID);
if (!session) {
return {};
}
console.log('i am happening');
const response = await fetch('https://discordapp.com/api/v6/users/@me', {
headers: {
Authorization: `Bearer ${session.accessToken}`,
},
});
if (response.status !== 200) {
console.log(await response.text());
return {
error: {
code: 'user_info_fetch_failed',
description:
`Discord gave non-200 status when fetching user info: ${response.status}`,
},
};
}
const data = await response.json();
console.log('have data', data.username);
return {
user: {
username: data.username,
discriminator: data.discriminator,
id: data.id,
avatarURL: discordAvatarURL(data),
},
};
};

14
src/routes/+page.svelte Normal file
View file

@ -0,0 +1,14 @@
<script lang="ts">
import type {PageData} from './$types';
export let data: PageData;
</script>
{#if data.user}
<h1>Success!</h1>
<p>You are {data.user.username}#{data.user.discriminator} and your ID is {data.user.id}</p>
<img src={data.user.avatarURL} alt="Discord avatar of {data.user.username}">
{:else}
<h1>Hello</h1>
<p>I do not know you</p>
<p>please <a href="/auth/discord">try again</a></p>
{/if}

View file

@ -0,0 +1,40 @@
import {redirect} from '@sveltejs/kit';
import * as crypto from 'node:crypto';
import {getDataSource} from '$lib/server/db';
import {AuthState} from '$lib/server/entity/AuthState';
import {env} from '$env/dynamic/private';
import {AuthProvider} from '$lib/server/entity/AuthMethod.js';
/**
* The redirect URI for Discord to send the user back to after auth. This must
* match what's configured for the OAuth application in the dev applications
* panel in order to fetch tokens.
*/
const discordRedirectURI = `${env.HOST}/auth/discord/callback`;
/** The base of the URI that starts the OAuth flow. State is attached later. */
const authURIBase = 'https://discordapp.com/api/oauth2/authorize'
+ `?client_id=${env.DISCORD_CLIENT_ID}`
+ '&response_type=code'
+ `&redirect_uri=${encodeURIComponent(discordRedirectURI)}`
+ `&scope=${encodeURIComponent(['identify', 'guilds'].join(' '))}`
+ '&prompt=consent'; // Special Discord-only thing to always show prompt
/** Generates a complete auth URI given a state. */
const buildAuthURI = (state: string) =>
`${authURIBase}&state=${encodeURIComponent(state)}`;
export async function load ({cookies}) {
const dataSource = await getDataSource();
const authStatesRepo = await dataSource.getRepository(AuthState);
const state = authStatesRepo.create({
provider: AuthProvider.DISCORD,
});
await authStatesRepo.save(state);
cookies.set('stateid', state.id, {path: '/auth/discord'});
throw redirect(302, buildAuthURI(state.state));
}

View file

@ -0,0 +1,97 @@
import {
discordAvatarURL,
type DiscordUserInfo,
fetchDiscordTokens,
fetchDiscordUserInfo,
} from '$lib/server/auth/discord';
import {getDataSource} from '$lib/server/db';
import {AuthProvider} from '$lib/server/entity/AuthMethod';
import {AuthSession} from '$lib/server/entity/AuthSession';
import {AuthState} from '$lib/server/entity/AuthState';
import {redirect} from '@sveltejs/kit';
import type {PageServerLoad} from './$types';
export const load: PageServerLoad = async ({cookies, url, fetch}) => {
const errorCode = url.searchParams.get('error');
const errorDescription = url.searchParams.get('error_description');
// if the user cancelled the login, redirect home gracefully
if (errorCode === 'access_denied') {
throw redirect(302, '/');
}
// if another error was encountered, return the error information only
if (errorCode) {
return {
error: {
code: errorCode,
description: errorDescription ?? '',
},
};
}
// retrieve the state we stored for this session and compare against the
// state we received from the provider
const dataSource = await getDataSource();
const statesRepo = dataSource.getRepository(AuthState);
const stateID = cookies.get('stateid');
let storedState: AuthState | null = null;
if (stateID) {
storedState = await statesRepo.findOne({where: {id: stateID}});
}
const receivedState = url.searchParams.get('state');
if (!storedState || !receivedState || storedState.state !== receivedState) {
return {
error: {
code: 'consumer_state_mismatch',
description:
`Expected state ${storedState?.state}, received ${receivedState}`,
},
};
}
// everything checks out - exchange code for tokens
const code = url.searchParams.get('code');
if (!code) {
return {
error: {
code: 'no_code',
description: 'No code was received',
},
};
}
let tokens;
try {
tokens = await fetchDiscordTokens(code);
} catch (tokenError) {
return {
error: {
code: 'token_exchange_failed',
description:
`Failed to exchange code for tokens: ${tokenError}`,
},
};
}
// Create a new auth session from the tokens and save it
const authSessionRepo = dataSource.getRepository(AuthSession);
const authSession = authSessionRepo.create({
provider: AuthProvider.DISCORD,
accessToken: tokens.accessToken,
refreshToken: tokens.refreshToken,
accessTokenExpiresAt: tokens.expiresAt,
});
await authSessionRepo.save(authSession);
cookies.set('sessionid', authSession.id, {path: '/'});
// remove the state we were using now that we're done with it
await statesRepo.remove(storedState);
cookies.delete('stateid');
// Woo we did it, redirect on to wherever we were trying to go before
throw redirect(302, '/');
};

View file

@ -0,0 +1,10 @@
<script lang="ts">
import type { PageData } from './$types';
export let data: PageData;
</script>
{#if data.error}
<h1>Error!</h1>
<pre>{data.error.code}: {data.error.description}</pre>
<p>Please <a href="/auth/discord">try again</a></p>
{/if}