feat: improve OIDC login flow and stores handling
This commit is contained in:
35
src/OIDCCallback.svelte
Normal file
35
src/OIDCCallback.svelte
Normal file
@ -0,0 +1,35 @@
|
||||
<script>
|
||||
import { uploadID } from "$lib/services/lnv";
|
||||
import { getOIDCUser } from "$lib/services/oidc";
|
||||
import { onMount } from "svelte";
|
||||
|
||||
const url = new URL(location.href);
|
||||
const code = url.searchParams.get("code");
|
||||
const state = url.searchParams.get("state");
|
||||
const storedState = localStorage.getItem("lnv-oidcstate");
|
||||
const codeVerifier = localStorage.getItem("lnv-codeVerifier");
|
||||
localStorage.removeItem("lnv-oidcstate");
|
||||
localStorage.removeItem("lnv-codeVerifier");
|
||||
|
||||
async function login() {
|
||||
if (!code || !state || !codeVerifier || !storedState) {
|
||||
alert("Missing code, state, codeVerifier or storedState.");
|
||||
return;
|
||||
}
|
||||
if (state !== storedState) {
|
||||
alert("State mismatch. Please try again.");
|
||||
return;
|
||||
}
|
||||
|
||||
const token = await getOIDCUser(code, codeVerifier);
|
||||
localStorage.setItem("lnv-id", token.id_token);
|
||||
localStorage.setItem("lnv-token", token.access_token);
|
||||
localStorage.setItem("lnv-refresh", token.refresh_token);
|
||||
await uploadID();
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
await login();
|
||||
location.href = "/";
|
||||
})
|
||||
</script>
|
||||
@ -33,9 +33,9 @@
|
||||
<Button
|
||||
onclick={async () => {
|
||||
const auth = await getAuthURL();
|
||||
// localStorage.setItem("lnv-codeVerifier", auth.codeVerifier);
|
||||
// localStorage.setItem("lnv-oidcstate", auth.state);
|
||||
const popup = window.open(auth.url, "Login", "width=500,height=600");
|
||||
localStorage.setItem("lnv-codeVerifier", auth.codeVerifier);
|
||||
localStorage.setItem("lnv-oidcstate", auth.state);
|
||||
location.href = auth.url;
|
||||
window.addEventListener("message", async (e) => {
|
||||
if (e.origin !== window.location.origin) return;
|
||||
|
||||
@ -45,7 +45,6 @@
|
||||
console.error("Invalid response from popup");
|
||||
return;
|
||||
}
|
||||
popup?.close();
|
||||
if (state !== auth.state) {
|
||||
alert("State mismatch. Please try again.");
|
||||
return;
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
MapIcon,
|
||||
PackageMinusIcon,
|
||||
PackagePlusIcon,
|
||||
PackageXIcon,
|
||||
RefreshCcwIcon,
|
||||
SpeechIcon,
|
||||
ToggleLeftIcon,
|
||||
@ -17,7 +18,7 @@
|
||||
import { view } from "../../view.svelte";
|
||||
import { m } from "$lang/messages";
|
||||
import { setOnboardingState } from "$lib/onboarding.svelte";
|
||||
import { stores, syncStores, updateStore } from "$lib/services/stores.svelte";
|
||||
import { getDB, stores, syncStores, updateStore } from "$lib/services/stores.svelte";
|
||||
|
||||
const dev = getDeveloperToggle();
|
||||
|
||||
@ -108,6 +109,19 @@
|
||||
await updateStore({ name, type }, null);
|
||||
}}
|
||||
/>
|
||||
<SettingsButton
|
||||
icon={PackageXIcon}
|
||||
text="Nuke all Stores"
|
||||
onclick={async () => {
|
||||
if (!confirm("Are you sure?")) return;
|
||||
const db = await getDB();
|
||||
const tx = db.transaction(["stores", "changes"], "readwrite");
|
||||
await tx.objectStore("stores").clear();
|
||||
await tx.objectStore("changes").clear();
|
||||
await tx.done;
|
||||
alert("Nuked all stores");
|
||||
}}
|
||||
/>
|
||||
<span>LOCATION STORES: {JSON.stringify(locationStores.current)}</span>
|
||||
</section>
|
||||
|
||||
|
||||
@ -9,4 +9,4 @@ export const LNV_SERVER =
|
||||
? "http://localhost:3000/api"
|
||||
: location.hostname.includes("staging")
|
||||
? "https://staging-trafficcue-api.picoscratch.de/api"
|
||||
: "https://trafficcue-api.picoscratch.de/api";
|
||||
: "https://staging-trafficcue-api.picoscratch.de/api";
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
// import { getStores, putStore } from "./lnv";
|
||||
|
||||
import { openDB, type DBSchema } from "idb";
|
||||
import { openDB, type DBSchema, type IDBPDatabase } from "idb";
|
||||
import { authFetch, hasCapability, ping } from "./lnv";
|
||||
import { LNV_SERVER } from "./hosts";
|
||||
|
||||
@ -44,7 +44,9 @@ interface TCDB extends DBSchema {
|
||||
};
|
||||
}
|
||||
|
||||
export const db = await openDB<TCDB>("tc", 1, {
|
||||
export async function getDB(): Promise<IDBPDatabase<TCDB>> {
|
||||
if (_db) return _db;
|
||||
_db = await openDB<TCDB>("tc", 1, {
|
||||
upgrade(db, _oldVersion, _newVersion, _transaction, _event) {
|
||||
if (!db.objectStoreNames.contains("stores")) {
|
||||
const store = db.createObjectStore("stores", { keyPath: "id" });
|
||||
@ -58,6 +60,10 @@ export const db = await openDB<TCDB>("tc", 1, {
|
||||
}
|
||||
},
|
||||
});
|
||||
return _db;
|
||||
}
|
||||
|
||||
let _db: IDBPDatabase<TCDB>;
|
||||
|
||||
const eventTarget = new EventTarget();
|
||||
|
||||
@ -77,6 +83,10 @@ export async function syncStores() {
|
||||
if (!(await hasCapability("stores"))) {
|
||||
return;
|
||||
}
|
||||
if (!(localStorage.getItem("lnv-token"))) {
|
||||
return;
|
||||
}
|
||||
const db = await getDB();
|
||||
const changes = await Promise.all(
|
||||
await db.getAll("changes").then((changes) =>
|
||||
changes.map(async (change) => {
|
||||
@ -133,6 +143,7 @@ export async function syncStores() {
|
||||
}
|
||||
|
||||
async function createStore(info: StoreInfo, data: object) {
|
||||
const db = await getDB();
|
||||
const id = crypto.randomUUID();
|
||||
const store: Store = {
|
||||
id,
|
||||
@ -154,6 +165,7 @@ async function createStore(info: StoreInfo, data: object) {
|
||||
}
|
||||
|
||||
export async function updateStore(info: StoreInfo, data: object | null) {
|
||||
const db = await getDB();
|
||||
const store = await db.getFromIndex("stores", "by-name-and-type", [
|
||||
info.name,
|
||||
info.type,
|
||||
@ -183,6 +195,7 @@ export async function updateStore(info: StoreInfo, data: object | null) {
|
||||
}
|
||||
|
||||
export async function hasStore(info: StoreInfo) {
|
||||
const db = await getDB();
|
||||
const store = await db.getFromIndex("stores", "by-name-and-type", [
|
||||
info.name,
|
||||
info.type,
|
||||
@ -217,6 +230,7 @@ export function stores<T extends object>(
|
||||
const customEvent = event as CustomEvent;
|
||||
const updatedStore = customEvent.detail as Store;
|
||||
if (updatedStore.type === type) {
|
||||
const db = await getDB();
|
||||
const stores = await db.getAllFromIndex("stores", "by-type", type);
|
||||
state.splice(
|
||||
0,
|
||||
@ -228,6 +242,7 @@ export function stores<T extends object>(
|
||||
}
|
||||
});
|
||||
(async () => {
|
||||
const db = await getDB();
|
||||
const stores = await db.getAllFromIndex("stores", "by-type", type);
|
||||
state.splice(
|
||||
0,
|
||||
|
||||
21
src/main.ts
21
src/main.ts
@ -1,28 +1,15 @@
|
||||
import { mount } from "svelte";
|
||||
import "./app.css";
|
||||
import App from "./App.svelte";
|
||||
import OIDCCallback from "./OIDCCallback.svelte";
|
||||
import { trySync } from "$lib/services/stores.svelte";
|
||||
|
||||
if (location.href.includes("/oidc")) {
|
||||
const url = new URL(location.href);
|
||||
const code = url.searchParams.get("code");
|
||||
const state = url.searchParams.get("state");
|
||||
if (code && state) {
|
||||
window.opener.postMessage(
|
||||
{
|
||||
code,
|
||||
state,
|
||||
},
|
||||
window.location.origin,
|
||||
);
|
||||
window.close();
|
||||
}
|
||||
}
|
||||
|
||||
const app = mount(App, {
|
||||
const app = mount(location.href.includes("/oidc") ? OIDCCallback : App, {
|
||||
target: document.getElementById("app")!,
|
||||
});
|
||||
|
||||
(async () => {
|
||||
await trySync();
|
||||
})();
|
||||
|
||||
export default app;
|
||||
|
||||
Reference in New Issue
Block a user