initial commit, lots of random shit
This commit is contained in:
commit
7bb18f0a71
27 changed files with 3394 additions and 0 deletions
51
src/routes/+layout.server.ts
Normal file
51
src/routes/+layout.server.ts
Normal 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
14
src/routes/+page.svelte
Normal 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}
|
||||
40
src/routes/auth/discord/+page.server.ts
Normal file
40
src/routes/auth/discord/+page.server.ts
Normal 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));
|
||||
}
|
||||
97
src/routes/auth/discord/callback/+page.server.ts
Normal file
97
src/routes/auth/discord/callback/+page.server.ts
Normal 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, '/');
|
||||
};
|
||||
10
src/routes/auth/discord/callback/+page.svelte
Normal file
10
src/routes/auth/discord/callback/+page.svelte
Normal 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}
|
||||
Loading…
Add table
Add a link
Reference in a new issue