ws.synops.no: ren domene/ruting/auth-opprydding

Hvert subdomain har én jobb. Offentlig og internt er separate domener.
- synops.no = statisk landingsside + /pub/* + /media/*
- ws.synops.no = appen (SvelteKit + /api/* → maskinrommet)
- workspace.synops.no → redirect til ws.synops.no (legacy)
- Fjernet hostname-sjekker fra hooks.server.ts
- Fjernet LandingPage.svelte (landingsside er statisk HTML)
- Alle API-URLer peker til 127.0.0.1:3100 (ikke sidelinja.org)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
vegard 2026-03-19 04:31:39 +00:00
parent 7c4d51a267
commit 70b33d5387
8 changed files with 17 additions and 131 deletions

View file

@ -137,13 +137,15 @@ maskinrommet deler verktøykasse.
- **Root-login:** Deaktivert
- **Server-filer:** `/srv/synops/` (docker-compose.yml, .env, config/, data/)
- **Domener (synops.no er primær):**
- `synops.no` — Plattform (app + landingsside + /pub/)
- `synops.no` — Offentlig landingsside + /pub/* + /media/* (Caddy → statisk + maskinrommet)
- `ws.synops.no` — Appen (SvelteKit + /api/* → maskinrommet). Krever login.
- `auth.synops.no` — Authentik SSO
- `api.synops.no` — Maskinrommet API
- `api.synops.no` — Maskinrommet direkte (CLI, webhooks, eksternt)
- `git.synops.no` — Forgejo (SSH port 222)
- `sidelinja.org` — Tenant-redirect til synops.no
- `vegard.info` — Personlig nettsted
- `*.sidelinja.org` — bakoverkompatible aliaser (auth, api, git)
- `vegard.info` — Personlig nettsted (statisk)
- `sidelinja.org` — Redirect til synops.no
- `workspace.synops.no` — Legacy redirect til ws.synops.no
- `*.sidelinja.org` — Legacy aliaser (auth, api, git)
## Git
- **Repos i Forgejo:**

View file

@ -1,9 +1,10 @@
# Authentik OIDC
AUTHENTIK_ISSUER=https://auth.sidelinja.org/application/o/sidelinja/
AUTHENTIK_ISSUER=https://auth.synops.no/application/o/sidelinja/
AUTHENTIK_CLIENT_ID=
AUTHENTIK_CLIENT_SECRET=
AUTH_SECRET=
AUTH_TRUST_HOST=true
ORIGIN=https://ws.synops.no
# Maskinrommet API
MASKINROMMET_URL=https://api.sidelinja.org
MASKINROMMET_URL=http://127.0.0.1:3100

View file

@ -15,7 +15,7 @@ interface AuthentikProfile {
* Called once at sign-in so the node_id is cached in the JWT for future requests.
*/
async function fetchNodeId(accessToken: string): Promise<string | null> {
const url = env.MASKINROMMET_URL ?? 'https://api.sidelinja.org';
const url = env.MASKINROMMET_URL ?? 'http://127.0.0.1:3100';
try {
const res = await fetch(`${url}/me`, {
headers: { Authorization: `Bearer ${accessToken}` }
@ -36,7 +36,7 @@ async function fetchNodeId(accessToken: string): Promise<string | null> {
* Stores Authentik preferred_username in auth_identities.username.
*/
async function syncUsername(accessToken: string, username: string): Promise<void> {
const url = env.MASKINROMMET_URL ?? 'https://api.sidelinja.org';
const url = env.MASKINROMMET_URL ?? 'http://127.0.0.1:3100';
try {
const res = await fetch(`${url}/auth/sync`, {
method: 'POST',

View file

@ -2,37 +2,19 @@ import { redirect, type Handle } from '@sveltejs/kit';
import { handle as authHandle } from './auth';
import { sequence } from '@sveltejs/kit/hooks';
/** Protect all routes except /signin and /auth/* (OIDC callback paths). */
/** Protect all routes except /auth/* (OIDC callback paths). */
const authorizationHandle: Handle = async ({ event, resolve }) => {
const path = event.url.pathname;
// Allow auth-related routes and signin through without session check
// Auth-ruter trenger ikke session-sjekk
if (path.startsWith('/auth/') || path === '/signin') {
return resolve(event);
}
const session = await event.locals.auth();
// Landing page: only on synops.no (not workspace.synops.no)
if (path === '/') {
const host = event.url.hostname;
if (host.startsWith('workspace.')) {
// workspace.synops.no: uautentisert → login, autentisert → vis direkte
if (!session?.user) {
throw redirect(303, '/auth/signin');
}
return resolve(event);
}
// synops.no: vis landingsside for alle
if (session?.user) {
throw redirect(303, '/workspace');
}
return resolve(event);
}
if (!session?.user) {
throw redirect(303, '/signin');
}
return resolve(event);
};

View file

@ -1,7 +1,6 @@
/**
* Client for maskinrommet intentions API.
* Uses the Vite dev proxy (/api api.sidelinja.org) in development.
* In production, set VITE_API_URL to the maskinrommet URL.
* Uses /api which Caddy proxies to maskinrommet (127.0.0.1:3100).
*/
const BASE_URL = import.meta.env.VITE_API_URL ?? '/api';

View file

@ -1,92 +0,0 @@
<div class="landing">
<div class="container">
<header>
<div class="logo">synops<span>.</span></div>
<a href="https://workspace.synops.no" class="login-btn">Logg inn</a>
</header>
<section class="hero">
<h1>Stappfull av <em>features</em><br>(hvorav et fåtall virker som forutsatt)</h1>
<p class="tagline">En useriøs plattform for kommunikasjon, redaksjonelt arbeid, podcastproduksjon og egentlig alt du vil. Bygget med uvitenhet, overmot og en urimelig mengde AI-assistanse.</p>
<a href="https://workspace.synops.no" class="hero-cta">Kom i gang &rarr;</a>
</section>
<div class="divider"></div>
<section class="features">
<div class="feature"><h3>Alt er noder</h3><p>Meldinger, oppgaver, lydfiler, dokumenter, mennesker — alt er samme ting med ulike relasjoner. Elegansen i dette vil slå deg en dag. Eller forvirre deg. Sannsynligvis begge.</p></div>
<div class="feature"><h3>Podcast-studio</h3><p>Spill inn med kolleger via nettleseren. Lydmixer, sound pads, stemmeeffekter og en AI som dytter fakta på deg mens du snakker. Om den finner noen.</p></div>
<div class="feature"><h3>Publiseringsplattform</h3><p>Skriv en chatmelding. Se den vokse til en artikkel. Publiser den på nettet med et tema som ser ut som du brukte måneder på det. Du brukte sekunder.</p></div>
<div class="feature"><h3>Spatial arbeidsflate</h3><p>Dra verktøy rundt på en uendelig flate. Chat ved siden av kanban ved siden av kalender ved siden av lydstudio. Det er som et skrivebord, bare at du aldri finner noe.</p></div>
<div class="feature"><h3>@bot overalt</h3><p>Skriv @bot i en hvilken som helst samtale. En AI svarer, diskuterer og fanger opp oppgaver i bakgrunnen. Hvilken AI? Den som tilfeldigvis er oppe akkurat nå.</p></div>
<div class="feature"><h3>Kunnskapsgraf</h3><p>Alt du skriver, sier og lenker bygger en levende graf av kunnskap. Synops husker sammenhenger du selv har glemt. Eller fabrikkerer nye. Vanskelig å si.</p></div>
</section>
<div class="divider"></div>
<section class="faq">
<div class="faq-item"><h3>Er Synops sikkert?</h3><p>Hahaha. Overhodet ikke. Vi kjører på én server, med én utvikler, og en AI som committer kode uten tilsyn. Men dataene dine er i hvert fall kryptert i transit. Og vi mener det godt.</p></div>
<div class="faq-item"><h3>Kan jeg stole på at ting fungerer?</h3><p>Du kan stole på at vi prøver. Og at PostgreSQL er mer pålitelig enn resten av stacken. Sannsynligheten for at akkurat den funksjonen du trenger virker akkurat nå er statistisk sett overraskende høy.</p></div>
<div class="faq-item"><h3>Hvem er dette for?</h3><p>Folk som lager podcast, skriver tekster, eller driver redaksjonelt arbeid — og som har en uforklarlig toleranse for betaversjoner. Tålmodige optimister. Eventyrlystne journalister. Eller folk som bare vil pludre med noen kompiser og kanskje ha en videosamtale.</p></div>
<div class="faq-item"><h3>Koster det noe?</h3><p>Nei.. altså, aah. Hvis du har for mye penger så skal vi finne en måte å ta imot? Det koster utvikleren søvn og penger, men deg koster det ingenting. Foreløpig. Muligens for alltid. Vi har ikke kommet så langt i forretningsplanen.</p></div>
<div class="faq-item"><h3>Er det open source?</h3><p>Koden bor på en Forgejo-instans som er omtrent like tilgjengelig som resten av plattformen. Vi har intensjoner om åpenhet. Intensjoner er det vi er best på.</p></div>
</section>
<div class="divider"></div>
<section class="reviews">
<div class="review"><div class="stars">★★☆☆☆</div><blockquote>«Jeg trodde virkelig dette hadde potensiale!»</blockquote><div class="author">— Anonym betatester</div></div>
<div class="review"><div class="stars">★★★☆☆</div><blockquote>«Ideen var ok. Gjennomføringen... original.»</blockquote><div class="author">— En som prøvde</div></div>
</section>
<div class="divider"></div>
<section class="expect">
<h2>Hva kan du forvente?</h2>
<p>Som bruker av Synops kan du forvente en dønn ustabil plattform som er i stadig endring, men som du likevel tenker at det er en grunn til å være på. Nye features dukker opp uten forvarsel. Gamle features forsvinner uten forvarsel. Og en og annen gang fungerer alt samtidig — og da er det ganske magisk.</p>
<div class="cta-row"><a href="https://workspace.synops.no" class="hero-cta">Jeg er overbevist &rarr;</a></div>
</section>
<footer><p>synops.no — et hobbyprosjekt med ambisjoner over evne</p></footer>
</div>
</div>
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Literata:ital,wght@0,400;0,700;1,400&display=swap');
.landing { background: #0a0a0b; color: #e8e8ec; font-family: 'Inter', -apple-system, sans-serif; line-height: 1.6; min-height: 100vh; }
.landing::before { content: ''; position: fixed; inset: 0; background-image: linear-gradient(rgba(99,102,241,0.03) 1px, transparent 1px), linear-gradient(90deg, rgba(99,102,241,0.03) 1px, transparent 1px); background-size: 60px 60px; pointer-events: none; z-index: 0; }
.container { position: relative; z-index: 1; max-width: 800px; margin: 0 auto; padding: 0 24px; }
header { display: flex; justify-content: space-between; align-items: center; padding: 32px 0; }
.logo { font-size: 1.25rem; font-weight: 700; letter-spacing: -0.02em; }
.logo span { color: #6366f1; }
.login-btn { padding: 10px 24px; background: #6366f1; color: white; border: none; border-radius: 8px; font-size: 0.9rem; font-weight: 500; text-decoration: none; transition: all 0.2s; }
.login-btn:hover { filter: brightness(1.15); transform: translateY(-1px); }
.hero { padding: 80px 0 60px; text-align: center; }
.hero h1 { font-size: clamp(2.5rem, 6vw, 4rem); font-weight: 700; letter-spacing: -0.03em; line-height: 1.1; margin-bottom: 24px; }
.hero h1 em { font-style: normal; background: linear-gradient(135deg, #6366f1, #a78bfa); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; }
.tagline { font-family: 'Literata', Georgia, serif; font-style: italic; font-size: 1.15rem; color: #8a8a96; max-width: 560px; margin: 0 auto 48px; }
.hero-cta { display: inline-flex; padding: 14px 32px; background: #6366f1; color: white; border-radius: 10px; font-size: 1rem; font-weight: 600; text-decoration: none; box-shadow: 0 0 40px rgba(99,102,241,0.15); transition: all 0.2s; }
.hero-cta:hover { filter: brightness(1.15); transform: translateY(-2px); }
.divider { width: 48px; height: 1px; background: #2a2a2e; margin: 60px auto; }
.features { display: grid; gap: 20px; padding-bottom: 40px; }
.feature { background: #141416; border: 1px solid #2a2a2e; border-radius: 12px; padding: 28px 32px; transition: border-color 0.2s; }
.feature:hover { border-color: rgba(99,102,241,0.3); }
.feature h3 { font-size: 1rem; font-weight: 600; margin-bottom: 8px; }
.feature p { font-family: 'Literata', Georgia, serif; font-style: italic; color: #8a8a96; font-size: 0.95rem; line-height: 1.7; }
.faq { padding-bottom: 40px; }
.faq-item { border-bottom: 1px solid #2a2a2e; padding: 28px 0; }
.faq-item:first-child { border-top: 1px solid #2a2a2e; }
.faq-item h3 { font-size: 1rem; font-weight: 600; margin-bottom: 8px; }
.faq-item p { font-family: 'Literata', Georgia, serif; font-style: italic; color: #8a8a96; font-size: 0.95rem; line-height: 1.7; }
.reviews { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; padding-bottom: 40px; }
.review { background: #141416; border: 1px solid #2a2a2e; border-radius: 12px; padding: 24px; }
.review .stars { color: #6366f1; font-size: 0.85rem; margin-bottom: 10px; }
.review blockquote { font-family: 'Literata', Georgia, serif; font-style: italic; color: #8a8a96; font-size: 0.9rem; line-height: 1.6; }
.review .author { margin-top: 12px; font-size: 0.8rem; color: #8a8a96; opacity: 0.6; }
.expect { background: #141416; border: 1px solid #2a2a2e; border-radius: 16px; padding: 40px; margin-bottom: 60px; text-align: center; }
.expect h2 { font-size: 1.5rem; font-weight: 700; margin-bottom: 16px; }
.expect p { font-family: 'Literata', Georgia, serif; font-style: italic; color: #8a8a96; font-size: 1.05rem; line-height: 1.8; max-width: 580px; margin: 0 auto; }
.expect .cta-row { margin-top: 32px; }
footer { text-align: center; padding: 40px 0; border-top: 1px solid #2a2a2e; color: #8a8a96; font-size: 0.85rem; }
@media (max-width: 600px) { .hero { padding: 48px 0 40px; } .feature { padding: 20px 24px; } .expect { padding: 28px 24px; } .reviews { grid-template-columns: 1fr; } header { padding: 20px 0; } }
</style>

View file

@ -7,8 +7,6 @@
import NewChatDialog from '$lib/components/NewChatDialog.svelte';
import AudioPlayer from '$lib/components/AudioPlayer.svelte';
import { createNode, createEdge, createCommunication, casUrl } from '$lib/api';
import LandingPage from '$lib/components/LandingPage.svelte';
const session = $derived($page.data.session as Record<string, unknown> | undefined);
const nodeId = $derived(session?.nodeId as string | undefined);
const accessToken = $derived(session?.accessToken as string | undefined);
@ -294,9 +292,6 @@
}
</script>
{#if !$page.data.session?.user}
<LandingPage />
{:else}
<div class="min-h-screen bg-gray-50">
<!-- Header -->
<header class="border-b border-gray-200 bg-white">
@ -540,4 +535,3 @@
/>
{/if}
</div>
{/if}

View file

@ -7,7 +7,7 @@ export default defineConfig({
server: {
proxy: {
'/api': {
target: 'https://api.sidelinja.org',
target: 'http://127.0.0.1:3100',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
}