feat: initial working discord authentication
This commit is contained in:
		
							parent
							
								
									6186eda092
								
							
						
					
					
						commit
						a7950671e1
					
				
					 12 changed files with 262 additions and 25 deletions
				
			
		
							
								
								
									
										18
									
								
								Foxnouns.Frontend/src/lib/api/auth.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								Foxnouns.Frontend/src/lib/api/auth.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,18 @@ | |||
| import type { User } from "./user"; | ||||
| 
 | ||||
| export type CallbackRequest = { | ||||
| 	code: string; | ||||
| 	state: string; | ||||
| }; | ||||
| 
 | ||||
| export type CallbackResponse = { | ||||
| 	has_account: boolean; | ||||
| 	ticket: string; | ||||
| 	remote_username: string | null; | ||||
| }; | ||||
| 
 | ||||
| export type AuthResponse = { | ||||
| 	user: User; | ||||
| 	token: string; | ||||
| 	expires_at: string; | ||||
| }; | ||||
|  | @ -2,12 +2,12 @@ import type Meta from "$lib/api/meta"; | |||
| import type { User } from "$lib/api/user"; | ||||
| import request from "$lib/request"; | ||||
| 
 | ||||
| export async function load({ fetch }) { | ||||
| export async function load({ fetch, locals }) { | ||||
| 	const meta = await request<Meta>(fetch, "GET", "/meta"); | ||||
| 	let user: User | undefined; | ||||
| 	try { | ||||
| 		user = await request<User>(fetch, "GET", "/users/@me"); | ||||
| 	} catch {} | ||||
| 
 | ||||
| 	return { meta, user }; | ||||
| 	return { meta, user, token: locals.token }; | ||||
| } | ||||
|  |  | |||
|  | @ -9,7 +9,7 @@ | |||
| 		<div class="grid"> | ||||
| 			<div class="cell"> | ||||
| 				<p class="title">Log in with email address</p> | ||||
| 				<form action="?/login"> | ||||
| 				<form method="POST" action="?/login"> | ||||
| 					<div class="field"> | ||||
| 						<label for="email" class="label">Email address</label> | ||||
| 						<div class="control"> | ||||
|  |  | |||
|  | @ -1,10 +1,55 @@ | |||
| import { fastRequest } from "$lib/request"; | ||||
| import request from "$lib/request"; | ||||
| import type { AuthResponse, CallbackResponse } from "$lib/api/auth"; | ||||
| 
 | ||||
| export const load = async ({ fetch, url }) => { | ||||
| 	await fastRequest(fetch, "POST", "/auth/discord/callback", { | ||||
| 		body: { | ||||
| 			code: url.searchParams.get("code"), | ||||
| 			state: url.searchParams.get("state"), | ||||
| export const load = async ({ fetch, url, cookies, parent }) => { | ||||
| 	const data = await parent(); | ||||
| 	if (data.user) { | ||||
| 		return { loggedIn: true, token: data.token, user: data.user }; | ||||
| 	} | ||||
| 
 | ||||
| 	const resp = await request<AuthResponse | CallbackResponse>( | ||||
| 		fetch, | ||||
| 		"POST", | ||||
| 		"/auth/discord/callback", | ||||
| 		{ | ||||
| 			body: { | ||||
| 				code: url.searchParams.get("code"), | ||||
| 				state: url.searchParams.get("state"), | ||||
| 			}, | ||||
| 		}, | ||||
| 	}); | ||||
| 	); | ||||
| 
 | ||||
| 	console.log(JSON.stringify(resp)); | ||||
| 
 | ||||
| 	if ("token" in resp) { | ||||
| 		const authResp = resp as AuthResponse; | ||||
| 		cookies.set("pronounscc-token", authResp.token, { path: "/" }); | ||||
| 		return { loggedIn: true, token: authResp.token, user: authResp.user }; | ||||
| 	} | ||||
| 
 | ||||
| 	const callbackResp = resp as CallbackResponse; | ||||
| 	return { | ||||
| 		loggedIn: false, | ||||
| 		hasAccount: callbackResp.has_account, | ||||
| 		ticket: resp.ticket, | ||||
| 		remoteUsername: resp.remote_username, | ||||
| 	}; | ||||
| }; | ||||
| 
 | ||||
| export const actions = { | ||||
| 	register: async ({ cookies, request: req, fetch, locals }) => { | ||||
| 		const data = await req.formData(); | ||||
| 		const username = data.get("username"); | ||||
| 		const ticket = data.get("ticket"); | ||||
| 
 | ||||
| 		console.log(JSON.stringify({ username, ticket })); | ||||
| 
 | ||||
| 		const resp = await request<AuthResponse>(fetch, "POST", "/auth/discord/register", { | ||||
| 			body: { username, ticket }, | ||||
| 		}); | ||||
| 		cookies.set("pronounscc-token", resp.token, { path: "/" }); | ||||
| 		locals.token = resp.token; | ||||
| 
 | ||||
| 		return { token: resp.token, user: resp.user }; | ||||
| 	}, | ||||
| }; | ||||
|  |  | |||
|  | @ -1,5 +1,79 @@ | |||
| <script lang="ts"> | ||||
| 	import { onMount } from "svelte"; | ||||
| 	import { goto } from "$app/navigation"; | ||||
| 	import { enhance } from "$app/forms"; | ||||
| 	import type { PageData, ActionData } from "./$types"; | ||||
| 	export let data: PageData; | ||||
| 
 | ||||
| 	export let form: ActionData; | ||||
| 
 | ||||
| 	onMount(async () => { | ||||
| 		if (data.user) { | ||||
| 			await new Promise((r) => setTimeout(r, 3000)); | ||||
| 			await goto(`/@${data.user.username}`); | ||||
| 		} | ||||
| 	}); | ||||
| 
 | ||||
| 	const redirectOnForm = async (action: ActionData) => { | ||||
| 		if (form?.user) { | ||||
| 			await new Promise((r) => setTimeout(r, 3000)); | ||||
| 			await goto(`/@${form.user.username}`); | ||||
| 		} | ||||
| 	}; | ||||
| 
 | ||||
| 	$: redirectOnForm(form); | ||||
| </script> | ||||
| 
 | ||||
| <p>omg its a login page</p> | ||||
| <div class="container"> | ||||
| 	{#if form?.user} | ||||
| 		<h1 class="title">Successfully created account!</h1> | ||||
| 		<p>Welcome, <strong>@{form.user.username}</strong>!</p> | ||||
| 		<p> | ||||
| 			You should automatically be redirected to your profile in a few seconds. If you're not | ||||
| 			redirected, please press the link above. | ||||
| 		</p> | ||||
| 	{:else if data.loggedIn} | ||||
| 		<h1 class="title">Successfully logged in!</h1> | ||||
| 		<p>You are now logged in as <a href="/@{data.user?.username}">@{data.user?.username}</a>.</p> | ||||
| 		<p> | ||||
| 			You should automatically be redirected to your profile in a few seconds. If you're not | ||||
| 			redirected, please press the link above. | ||||
| 		</p> | ||||
| 	{:else} | ||||
| 		<h1 class="title">Finish signing up with a Discord account</h1> | ||||
| 		<form method="POST" action="?/register" use:enhance> | ||||
| 			<div class="field"> | ||||
| 				<label for="remote_username" class="label">Discord username</label> | ||||
| 				<div class="control"> | ||||
| 					<input | ||||
| 						type="text" | ||||
| 						name="remote_username" | ||||
| 						id="remote_username" | ||||
| 						class="input" | ||||
| 						value={data.remoteUsername} | ||||
| 						disabled | ||||
| 					/> | ||||
| 				</div> | ||||
| 			</div> | ||||
| 			<div class="field"> | ||||
| 				<label for="username" class="label">Username</label> | ||||
| 				<div class="control"> | ||||
| 					<input type="text" name="username" id="username" class="input" placeholder="Username" /> | ||||
| 				</div> | ||||
| 			</div> | ||||
| 			<input | ||||
| 				type="text" | ||||
| 				name="ticket" | ||||
| 				id="ticket" | ||||
| 				class="hidden" | ||||
| 				style="display: hidden;" | ||||
| 				value={data.ticket} | ||||
| 			/> | ||||
| 			<div class="field"> | ||||
| 				<div class="control"> | ||||
| 					<button class="button is-primary">Sign up</button> | ||||
| 				</div> | ||||
| 			</div> | ||||
| 		</form> | ||||
| 	{/if} | ||||
| </div> | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue