Working User Authorization Flow

This commit is contained in:
2026-01-26 15:56:30 -06:00
parent e9a7ded305
commit e517a45c0f
13 changed files with 349 additions and 92 deletions
+27
View File
@@ -0,0 +1,27 @@
import axios, { AxiosInstance } from "axios";
export async function fetchAuthRedirectUri(api_url: string): Promise<{
uri: string;
callbackKey: string;
}> {
const client: AxiosInstance = axios.create({
baseURL: api_url || "",
timeout: 5000,
});
try {
const res = await client.get("/v1/auth/uri");
const d = res.data ?? {};
const uri = d.data.uri;
const callbackKey = d.data.callbackKey;
if (typeof uri !== "string" || !uri)
throw new Error("redirect uri missing from response");
return {
uri,
callbackKey,
};
} catch (e) {
throw new Error(
`Failed to fetch auth redirect uri: ${(e as Error).message}`,
);
}
}
+1 -1
View File
@@ -1,3 +1,3 @@
// place files you want to import through the `$lib` alias in this folder.
export * from "./axios";
//export * from "./axios";
export * from "./user";
+42 -33
View File
@@ -1,5 +1,7 @@
import { getRequestEvent } from "$app/server";
import { redirect } from "@sveltejs/kit";
import { PUBLIC_API_URL } from "$env/static/public";
import { redirect, RequestEvent } from "@sveltejs/kit";
import axios from "axios";
import { io } from "socket.io-client";
export const user = {
@@ -9,39 +11,51 @@ export const user = {
return !!authToken;
},
logout() {
const event = getRequestEvent();
async refreshSession(refreshToken: string) {
const refreshedTokens = (
await axios.post(
`${PUBLIC_API_URL}/v1/auth/refresh`,
{},
{
headers: {
"x-refresh-token": refreshToken,
},
},
)
).data.data;
console.log("Refreshed tokens:", refreshedTokens);
return refreshedTokens;
},
logout(event: RequestEvent) {
if (!event) return;
// Clear authentication cookies
event.cookies.delete("authToken", { path: "/" });
event.cookies.delete("refreshToken", { path: "/" });
return redirect(303, "/");
return redirect(303, "/login");
},
/**
* @todo Get communication with server working and setup a key system so that the frontend can listen for a specific key from the backend so that nobody can poach off of login events.
*
* Note: This function no longer mutates SvelteKit request event/cookies asynchronously.
* It returns the tokens to the caller so the caller (within the same request lifecycle)
* can set cookies using the event object synchronously.
*/
async awaitAuthCallback(): Promise<{
authToken: string;
async awaitAuthCallback(callbackKey: string): Promise<{
accessToken: string;
refreshToken: string;
}> {
const event = getRequestEvent();
if (!event) return Promise.reject(new Error("No request event"));
const state =
event.url.searchParams.get("state") ??
event.cookies.get("authState") ??
"";
if (!state)
return Promise.reject(new Error("Missing state to correlate socket"));
const base = process.env.API_URL || "";
const base = PUBLIC_API_URL || "";
return new Promise((resolve, reject) => {
let settled = false;
const socket = io(base, { auth: { state }, transports: ["websocket"] });
const socket = io(`${base}/auth_callback`, {
transports: ["websocket"],
});
const timeout = setTimeout(
() => {
if (settled) return;
@@ -56,25 +70,15 @@ export const user = {
const handlePayload = (payload: any) => {
try {
const { authToken, refreshToken } = payload ?? {};
if (authToken && refreshToken) {
const { accessToken, refreshToken } = payload ?? {};
if (accessToken && refreshToken) {
if (settled) return;
settled = true;
event.cookies.set("authToken", authToken, {
path: "/",
httpOnly: true,
sameSite: "lax",
});
event.cookies.set("refreshToken", refreshToken, {
path: "/",
httpOnly: true,
sameSite: "lax",
});
clearTimeout(timeout);
try {
socket.disconnect();
} catch {}
resolve({ authToken, refreshToken });
resolve({ accessToken, refreshToken });
}
} catch {
// ignore parse errors
@@ -82,8 +86,13 @@ export const user = {
};
socket.on("connect", () => {});
socket.on("auth-callback", handlePayload);
socket.on("message", handlePayload);
// listen for a specific callback key if provided
if (callbackKey) {
socket.on(`auth:login:callback:${callbackKey}`, handlePayload);
} else {
socket.on("auth-callback", handlePayload);
}
socket.on("message", console.log);
socket.on("connect_error", (err: any) => {
if (settled) return;
settled = true;