feat(sso): accept base-issued iframe login
Handle Base SSO tokens on initial requests so HRM can bootstrap PocketBase auth and redirect into the app without a separate login.
This commit is contained in:
@@ -3,6 +3,7 @@ import PocketBase from 'pocketbase';
|
||||
import type { Handle } from '@sveltejs/kit';
|
||||
|
||||
const POCKETBASE_URL = 'https://pocketbase.ccllc.pro';
|
||||
const BASE_SSO_URL = process.env.BASE_SSO_URL ?? 'https://base.ccllc.pro';
|
||||
|
||||
const LOGIN_PATH = '/login';
|
||||
const ACCESS_DENIED_PATH = '/access-denied';
|
||||
@@ -16,6 +17,25 @@ function hasHrmAccess(user: { HRM?: boolean } | null): boolean {
|
||||
}
|
||||
|
||||
export const handle: Handle = async ({ event, resolve }) => {
|
||||
// SSO: if opened from Base with ?sso=token, exchange for session and redirect (no auth yet)
|
||||
const ssoParam = event.url.searchParams.get('sso');
|
||||
if (ssoParam) {
|
||||
try {
|
||||
const res = await fetch(`${BASE_SSO_URL}/api/sso/exchange?token=${encodeURIComponent(ssoParam)}`);
|
||||
if (res.ok) {
|
||||
const auth = await res.json();
|
||||
const cookieVal = encodeURIComponent(JSON.stringify({ token: auth.token, model: auth.model }));
|
||||
const setCookie = `pb_auth=${cookieVal}; Path=/; SameSite=Lax; Max-Age=604800`;
|
||||
return new Response(null, {
|
||||
status: 302,
|
||||
headers: { Location: event.url.pathname, 'Set-Cookie': setCookie }
|
||||
});
|
||||
}
|
||||
} catch {
|
||||
// fall through to normal auth
|
||||
}
|
||||
}
|
||||
|
||||
event.locals.pb = new PocketBase(POCKETBASE_URL);
|
||||
|
||||
// Load auth store from request cookie
|
||||
|
||||
+29
-2
@@ -1,5 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { get } from 'svelte/store';
|
||||
import { page } from '$app/stores';
|
||||
import { goto } from '$app/navigation';
|
||||
import * as Table from '$lib/components/ui/table/index.js';
|
||||
import * as Dialog from '$lib/components/ui/dialog/index.js';
|
||||
@@ -33,8 +35,25 @@
|
||||
return typeFilter === 'all' ? '' : `&type=${encodeURIComponent(typeFilter)}`;
|
||||
}
|
||||
|
||||
function listUrl(): string {
|
||||
const params = new URLSearchParams();
|
||||
if (typeFilter !== 'all') params.set('type', typeFilter);
|
||||
const q = searchQuery.trim();
|
||||
if (q) params.set('search', q);
|
||||
const s = params.toString();
|
||||
return s ? `/?${s}` : '/';
|
||||
}
|
||||
|
||||
function syncUrlToState() {
|
||||
const params = get(page).url.searchParams;
|
||||
const type = params.get('type');
|
||||
typeFilter = type === 'Sub-Contractor' ? 'Sub-Contractor' : type === 'Employee' ? 'Employee' : 'all';
|
||||
searchQuery = params.get('search') ?? '';
|
||||
}
|
||||
|
||||
function setTypeFilter(value: TypeFilterValue) {
|
||||
typeFilter = value;
|
||||
goto(listUrl(), { replaceState: true });
|
||||
loading = true;
|
||||
if (searchQuery.trim()) {
|
||||
searchEmployees(searchQuery);
|
||||
@@ -109,13 +128,14 @@
|
||||
const q = searchQuery.trim();
|
||||
if (!q) {
|
||||
searchDebounceId = null;
|
||||
// Clear search: reload first page of full list
|
||||
goto(listUrl(), { replaceState: true });
|
||||
loading = true;
|
||||
loadEmployees();
|
||||
return;
|
||||
}
|
||||
searchDebounceId = setTimeout(() => {
|
||||
searchDebounceId = null;
|
||||
goto(listUrl(), { replaceState: true });
|
||||
searchEmployees(searchQuery);
|
||||
}, 300);
|
||||
}
|
||||
@@ -273,7 +293,14 @@
|
||||
};
|
||||
}
|
||||
|
||||
onMount(loadEmployees);
|
||||
onMount(() => {
|
||||
syncUrlToState();
|
||||
if (searchQuery.trim()) {
|
||||
searchEmployees(searchQuery);
|
||||
} else {
|
||||
loadEmployees();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<main class="flex flex-col gap-6 p-6 md:p-8">
|
||||
|
||||
@@ -267,7 +267,18 @@
|
||||
|
||||
<main class="flex flex-col gap-6 p-6 md:p-8">
|
||||
<div class="flex flex-wrap items-center gap-4">
|
||||
<Button variant="ghost" size="icon" href="/" class="shrink-0">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
href="/"
|
||||
class="shrink-0"
|
||||
onclick={(e) => {
|
||||
if (typeof window !== 'undefined' && window.history.length > 1) {
|
||||
e.preventDefault();
|
||||
window.history.back();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ArrowLeftIcon class="size-4" />
|
||||
</Button>
|
||||
<h1 class="text-2xl font-semibold tracking-tight shrink-0 flex items-center gap-2 flex-wrap">
|
||||
|
||||
Reference in New Issue
Block a user