Quick actions
+-
+
- Create project +
- Edit profile +
diff --git a/bun.lock b/bun.lock index 286aefe..2d8c9e0 100644 --- a/bun.lock +++ b/bun.lock @@ -6,6 +6,7 @@ "name": "electron-svelte", "dependencies": { "axios": "^1.13.3", + "dotenv": "^17.2.3", "electron-squirrel-startup": "^1.0.1", "socket.io-client": "^4.8.3", }, @@ -605,6 +606,8 @@ "dom-accessibility-api": ["dom-accessibility-api@0.6.3", "", {}, "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w=="], + "dotenv": ["dotenv@17.2.3", "", {}, "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w=="], + "ds-store": ["ds-store@0.1.6", "", { "dependencies": { "bplist-creator": "0.0.8", "macos-alias": "0.2.12", "tn1150": "0.1.0" } }, "sha512-kY21M6Lz+76OS3bnCzjdsJSF7LBpLYGCVfavW8TgQD2XkcqIZ86W0y9qUDZu6fp7SIZzqosMDW2zi7zVFfv4hw=="], "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], diff --git a/electron/main.ts b/electron/main.ts index 6b0dc68..0c13cf6 100644 --- a/electron/main.ts +++ b/electron/main.ts @@ -19,7 +19,7 @@ const createWindow = () => { // and load the index.html of the app. if (MAIN_WINDOW_VITE_DEV_SERVER_URL) { - mainWindow.loadURL(MAIN_WINDOW_VITE_DEV_SERVER_URL); + mainWindow.loadURL(`${MAIN_WINDOW_VITE_DEV_SERVER_URL}/login`); mainWindow.webContents.on("did-frame-finish-load", () => { mainWindow.webContents.openDevTools({ mode: "detach" }); }); diff --git a/package.json b/package.json index 77659ee..2cd35c6 100644 --- a/package.json +++ b/package.json @@ -55,6 +55,7 @@ }, "dependencies": { "axios": "^1.13.3", + "dotenv": "^17.2.3", "electron-squirrel-startup": "^1.0.1", "socket.io-client": "^4.8.3" }, diff --git a/src/app.css b/src/app.css index cd67023..4c62860 100644 --- a/src/app.css +++ b/src/app.css @@ -1,3 +1,3 @@ -@import 'tailwindcss'; +@import "tailwindcss"; @plugin '@tailwindcss/forms'; @plugin '@tailwindcss/typography'; diff --git a/src/components/LoadingSpinner.svelte b/src/components/LoadingSpinner.svelte new file mode 100644 index 0000000..a783104 --- /dev/null +++ b/src/components/LoadingSpinner.svelte @@ -0,0 +1,37 @@ + + +{#if loading} +
+{/if} + + diff --git a/src/hooks.server.ts b/src/hooks.server.ts index b88efb0..41013b9 100644 --- a/src/hooks.server.ts +++ b/src/hooks.server.ts @@ -1,10 +1,54 @@ -import type { Handle } from '@sveltejs/kit'; +import { user } from "$lib"; +import { redirect, type Handle } from "@sveltejs/kit"; +import { access } from "fs"; +import { a } from "vitest/dist/chunks/suite.d.FvehnV49.js"; export const handle: Handle = async ({ event, resolve }) => { - if (event.url.pathname.startsWith('/custom')) { - return new Response('custom response'); - } + const accessToken = event.cookies.get("access_token"); + const refreshToken = event.cookies.get("refresh_token"); - const response = await resolve(event); - return response; -}; \ No newline at end of file + if (event.url.pathname === "/logout") { + event.cookies.delete("access_token", { path: "/" }); + event.cookies.delete("refresh_token", { path: "/" }); + + redirect(303, "/login"); + + return resolve(event); + } + + if (event.url.pathname.startsWith("/login") && user.isLoggedIn()) { + return redirect(303, "/"); + } + + if (event.url.pathname.startsWith("/login")) { + return await resolve(event); + } + + if (!accessToken || !refreshToken) { + user.logout(event); + return resolve(event); + } + + try { + if (accessToken && refreshToken) { + const newSession = await user.refreshSession(refreshToken); + + console.log(newSession); + + event.cookies.set("access_token", newSession.accessToken, { + httpOnly: true, + path: "/", + }); + event.cookies.set("refresh_token", newSession.refreshToken, { + httpOnly: true, + path: "/", + }); + } + } catch (err) { + console.trace(err); + + user.logout(event); + } finally { + return await resolve(event); + } +}; diff --git a/src/lib/authUri.ts b/src/lib/authUri.ts new file mode 100644 index 0000000..c4018d1 --- /dev/null +++ b/src/lib/authUri.ts @@ -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}`, + ); + } +} diff --git a/src/lib/index.ts b/src/lib/index.ts index 600e1ce..a352ed0 100644 --- a/src/lib/index.ts +++ b/src/lib/index.ts @@ -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"; diff --git a/src/lib/user.ts b/src/lib/user.ts index 8f83421..2b09cfb 100644 --- a/src/lib/user.ts +++ b/src/lib/user.ts @@ -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; diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index d5c36c0..1deb595 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -1,53 +1,62 @@ - +This is your protected home page. Quick links and recent activity appear below.
+No recent activity.
+