feat: improve OIDC to use refresh tokens
Some checks failed
TrafficCue CI / check (push) Failing after 46s
TrafficCue CI / build (push) Failing after 34s

This commit is contained in:
2025-08-19 11:21:27 +02:00
parent 8e37eb0d51
commit babfb526de
6 changed files with 87 additions and 7 deletions

View File

@@ -5,6 +5,7 @@
import { getAuthURL, getOIDCUser } from "$lib/services/oidc";
import * as Avatar from "$lib/components/ui/avatar";
import { m } from "$lang/messages";
import { refreshToken } from "$lib/services/lnv";
interface OIDCUser {
sub: string;
@@ -68,6 +69,9 @@
</Avatar.Root>
{user.name || user.preferred_username}
</SidebarHeader>
<button onclick={() => {
refreshToken();
}}>refresh</button>
<pre>{user.sub}</pre>
{JSON.stringify(user, null, 2)}
{/if}

View File

@@ -1,4 +1,5 @@
import { LNV_SERVER } from "./hosts";
import type { OIDCUser } from "./oidc";
export type Capabilities = ("auth" | "reviews" | "ai" | "fuel" | "post")[];
export let capabilities: Capabilities = [];
@@ -52,6 +53,45 @@ export async function hasCapability(
return caps.includes(capability);
}
export async function refreshToken() {
const config = await getOIDCConfig();
if(!config) throw new Error("Server does not support OIDC.");
const refresh_token = localStorage.getItem("lnv-refresh");
if(!refresh_token) throw new Error("No refresh token.")
const params = new URLSearchParams();
params.append("grant_type", "refresh_token");
params.append("refresh_token", refresh_token);
params.append("client_id", config.CLIENT_ID);
const res = await fetch(config.TOKEN_URL, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded"
},
body: params
});
const data = (await res.json()) as OIDCUser;
if(!res.ok) {
console.error("Refreshing token: " + res.status + " " + res.statusText)
console.error(data);
}
console.log(data);
localStorage.setItem("lnv-id", data.id_token);
localStorage.setItem("lnv-token", data.access_token);
localStorage.setItem("lnv-refresh", data.refresh_token);
}
export async function authFetch(url: string, params: RequestInit): ReturnType<typeof fetch> {
let res = await fetch(url, params);
if(res.status == 401) {
await refreshToken();
}
res = await fetch(url, params);
if(res.status == 401) {
console.error("Server is misconfigured.");
}
return res;
}
export interface Review {
user_id: string;
username: string;

View File

@@ -17,7 +17,7 @@ export async function getAuthURL() {
const state = generateRandomString(16);
return {
url: `${AUTH_URL}?client_id=${CLIENT_ID}&response_type=code&redirect_uri=${window.location.origin}/login/callback&scope=openid%20profile&code_challenge=${pkce.codeChallenge}&code_challenge_method=S256&state=${state}`,
url: `${AUTH_URL}?client_id=${CLIENT_ID}&response_type=code&redirect_uri=${window.location.origin}/oidc&scope=openid%20profile&code_challenge=${pkce.codeChallenge}&code_challenge_method=S256&state=${state}`,
codeVerifier: pkce.codeVerifier,
state,
};
@@ -60,6 +60,14 @@ async function sha256(input: string | undefined): Promise<ArrayBuffer> {
return await window.crypto.subtle.digest("SHA-256", data);
}
export type OIDCUser = {
access_token: string;
token_type: string;
refresh_token: string;
expires_in: number;
id_token: string;
}
export async function getOIDCUser(code: string, codeVerifier: string) {
if (!(await hasCapability("auth"))) {
throw new Error("Server does not support OIDC authentication");
@@ -75,7 +83,7 @@ export async function getOIDCUser(code: string, codeVerifier: string) {
params.append("code", code);
params.append("client_id", CLIENT_ID);
params.append("code_verifier", codeVerifier);
params.append("redirect_uri", window.location.origin + "/login/callback");
params.append("redirect_uri", window.location.origin + "/oidc");
const res = await fetch(TOKEN_URL, {
method: "POST",
@@ -85,6 +93,6 @@ export async function getOIDCUser(code: string, codeVerifier: string) {
body: params,
}).then((res) => res.json());
return res;
return res as OIDCUser;
// return JSON.parse(atob(id_token.split(".")[1]));
}

View File

@@ -2,7 +2,7 @@ import { mount } from "svelte";
import "./app.css";
import App from "./App.svelte";
if (location.href.includes("/login/callback")) {
if (location.href.includes("/oidc")) {
const url = new URL(location.href);
const code = url.searchParams.get("code");
const state = url.searchParams.get("state");