Add global domain option and fix some minor bugs

This commit is contained in:
Melon 2023-10-28 12:43:34 +01:00
parent 899cf20408
commit 1315a8912b
Signed by: melon
GPG Key ID: 6C9D970C50D26A25
9 changed files with 97 additions and 21 deletions

View File

@ -3,8 +3,9 @@
import GeneralView from "./views/GeneralView.svelte"; import GeneralView from "./views/GeneralView.svelte";
import VioletView from "./views/VioletView.svelte"; import VioletView from "./views/VioletView.svelte";
import OrchidView from "./views/OrchidView.svelte"; import OrchidView from "./views/OrchidView.svelte";
import {loginStore} from "./stores/login"; import {loginStore, parseJwt, type LoginStore} from "./stores/login";
import {openLoginPopup} from "./utils/login-popup"; import {openLoginPopup} from "./utils/login-popup";
import {domainOption} from "./stores/domain-option";
let sidebarOptions: Array<{name: string; view: typeof SvelteComponent<{}>}> = [ let sidebarOptions: Array<{name: string; view: typeof SvelteComponent<{}>}> = [
{name: "General", view: GeneralView}, {name: "General", view: GeneralView},
@ -15,13 +16,34 @@
let tokenPerms: string[] = []; let tokenPerms: string[] = [];
$: tokenPerms = []; $: tokenPerms = [];
let domainOptions: string[];
$: domainOptions = getDomainOptions($loginStore);
function getDomainOptions(login: LoginStore | null) {
let accessToken = login?.tokens?.access;
if (accessToken == null) return [];
let jwt = parseJwt(accessToken);
return jwt.per.filter((x: string) => x.startsWith("domain:owns=")).map((x: string) => x.slice("domain:owns=".length));
}
</script> </script>
<header> <header>
<div> <div>
<h1>🍉 - 1f349 Admin Dashboard</h1> <h1>🍉 Admin Panel</h1>
</div> </div>
<div class="flex-gap" /> <div class="flex-gap" />
<div>
<label>
<span>Domain:</span>
<select bind:value={$domainOption}>
<option value="*">All</option>
{#each domainOptions as domain}
<option value={domain}>{domain}</option>
{/each}
</select>
</label>
</div>
<div class="nav-link"> <div class="nav-link">
<a href="https://status.1f349.net" target="_blank">Status</a> <a href="https://status.1f349.net" target="_blank">Status</a>
</div> </div>

View File

@ -23,3 +23,9 @@
</button> </button>
</td> </td>
</tr> </tr>
<style lang="scss">
tr:nth-child(2n) {
background-color: #2a2a2a;
}
</style>

View File

@ -34,20 +34,35 @@
{/if} {/if}
<style lang="scss"> <style lang="scss">
tr:nth-child(2n) {
background-color: #2a2a2a;
}
tr.created { tr.created {
background-color: #1a5100; background-color: #1a5100;
&:nth-child(2n) {
background-color: #103300;
}
} }
tr.modified { tr.modified {
background-color: #515100; background-color: #515100;
&:nth-child(2n) {
background-color: #333300;
}
} }
tr.deleted { tr.deleted {
background-color: #510000; background-color: #510000;
&:nth-child(2n) {
background-color: #330000;
}
} }
td input[type="text"] { td input[type="text"] {
font-family: "Fira Code";
padding: 4px; padding: 4px;
} }
</style> </style>

View File

@ -23,3 +23,9 @@
</button> </button>
</td> </td>
</tr> </tr>
<style lang="scss">
tr:nth-child(2n) {
background-color: #2a2a2a;
}
</style>

View File

@ -35,7 +35,7 @@
<style lang="scss"> <style lang="scss">
tr:nth-child(2n) { tr:nth-child(2n) {
background-color: #293138; background-color: #2a2a2a;
} }
tr.created { tr.created {

View File

@ -0,0 +1,3 @@
import { writable } from "svelte/store";
export const domainOption = writable<string>("*");

View File

@ -30,3 +30,18 @@ loginStore.subscribe(x => {
export function getBearer() { export function getBearer() {
return "Bearer " + (get(loginStore) as LoginStore).tokens.access; return "Bearer " + (get(loginStore) as LoginStore).tokens.access;
} }
export function parseJwt(token: string) {
const base64Url = token.split(".")[1];
const base64 = base64Url.replace(/-/g, "+").replace(/_/g, "/");
const jsonPayload = decodeURIComponent(
window
.atob(base64)
.split("")
.map(function (c) {
return "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2);
})
.join(""),
);
return JSON.parse(jsonPayload);
}

View File

@ -3,9 +3,10 @@
import RouteCreator from "../components/RouteCreator.svelte"; import RouteCreator from "../components/RouteCreator.svelte";
import RedirectRow from "../components/RedirectRow.svelte"; import RedirectRow from "../components/RedirectRow.svelte";
import RouteRow from "../components/RouteRow.svelte"; import RouteRow from "../components/RouteRow.svelte";
import {getBearer} from "../stores/login"; import {getBearer, loginStore, parseJwt, type LoginStore} from "../stores/login";
import type {CSPair} from "../types/cspair"; import type {CSPair} from "../types/cspair";
import {type Route, type Redirect} from "../types/target"; import {type Route, type Redirect} from "../types/target";
import {domainOption} from "../stores/domain-option";
const apiViolet = import.meta.env.VITE_API_VIOLET; const apiViolet = import.meta.env.VITE_API_VIOLET;
@ -18,13 +19,22 @@
$: routeSrcs = Object.entries(routeData) $: routeSrcs = Object.entries(routeData)
.filter(x => x[1].client != null || x[1].server != null) .filter(x => x[1].client != null || x[1].server != null)
.map(x => x[0]) .map(x => x[0])
.filter(x => domainFilter(x, $domainOption))
.sort((a, b) => a.localeCompare(b)); .sort((a, b) => a.localeCompare(b));
$: redirectSrcs = Object.entries(redirectData) $: redirectSrcs = Object.entries(redirectData)
.filter(x => x[1].client != null || x[1].server != null) .filter(x => x[1].client != null || x[1].server != null)
.map(x => x[0]) .map(x => x[0])
.filter(x => domainFilter(x, $domainOption))
.sort((a, b) => a.localeCompare(b)); .sort((a, b) => a.localeCompare(b));
$: console.log(routeData); function domainFilter(src: string, domain: string) {
if (domain == "*") return true;
let n = src.indexOf("/");
if (n == -1) n = src.length;
let p = src.slice(0, n);
if (p == domain) return true;
return p.endsWith(domain);
}
let promiseForRoutes = new Promise<void>((res, rej) => { let promiseForRoutes = new Promise<void>((res, rej) => {
fetch(apiViolet + "/route", {headers: {Authorization: getBearer()}}) fetch(apiViolet + "/route", {headers: {Authorization: getBearer()}})
@ -84,8 +94,7 @@
<tr><td colspan="5">Error loading row for {src}</td></tr> <tr><td colspan="5">Error loading row for {src}</td></tr>
{/if} {/if}
{/each} {/each}
</tbody>
<tfoot>
<RouteCreator <RouteCreator
on:make={e => { on:make={e => {
const x = e.detail; const x = e.detail;
@ -94,7 +103,7 @@
routeSrcs = routeSrcs; routeSrcs = routeSrcs;
}} }}
/> />
</tfoot> </tbody>
</table> </table>
{:catch err} {:catch err}
<div>{err}</div> <div>{err}</div>
@ -122,8 +131,7 @@
<tr><td colspan="5">Error loading row for {src}</td></tr> <tr><td colspan="5">Error loading row for {src}</td></tr>
{/if} {/if}
{/each} {/each}
</tbody>
<tfoot>
<RedirectCreator <RedirectCreator
on:make={e => { on:make={e => {
const x = e.detail; const x = e.detail;
@ -132,7 +140,7 @@
redirectSrcs = redirectSrcs; redirectSrcs = redirectSrcs;
}} }}
/> />
</tfoot> </tbody>
</table> </table>
{:catch err} {:catch err}
<div>{err}</div> <div>{err}</div>

View File

@ -36,6 +36,8 @@ func ssoServer(signer mjwt.Signer) {
ps := claims.NewPermStorage() ps := claims.NewPermStorage()
ps.Set("violet:route") ps.Set("violet:route")
ps.Set("violet:redirect") ps.Set("violet:redirect")
ps.Set("domain:owns=example.com")
ps.Set("domain:owns=example.org")
accessToken, err := signer.GenerateJwt("81b99bd7-bf74-4cc2-9133-80ed2393dfe6", uuid.NewString(), jwt.ClaimStrings{"d0555671-df9d-42d0-a4d6-94b694251f0b"}, 15*time.Minute, auth.AccessTokenClaims{ accessToken, err := signer.GenerateJwt("81b99bd7-bf74-4cc2-9133-80ed2393dfe6", uuid.NewString(), jwt.ClaimStrings{"d0555671-df9d-42d0-a4d6-94b694251f0b"}, 15*time.Minute, auth.AccessTokenClaims{
Perms: ps, Perms: ps,
}) })
@ -105,54 +107,53 @@ func apiServer(verify mjwt.Verifier) {
r.Handle("/v1/violet/route", hasPerm(verify, "violet:route", func(rw http.ResponseWriter, req *http.Request) { r.Handle("/v1/violet/route", hasPerm(verify, "violet:route", func(rw http.ResponseWriter, req *http.Request) {
json.NewEncoder(rw).Encode([]map[string]any{ json.NewEncoder(rw).Encode([]map[string]any{
{ {
"src": "a.example.test", "src": "example.com",
"dst": "127.0.0.1:8080", "dst": "127.0.0.1:8080",
"flags": 181, "flags": 181,
"active": true, "active": true,
}, },
{ {
"src": "b.example.test", "src": "test.example.com",
"dst": "127.0.0.1:8081", "dst": "127.0.0.1:8081",
"flags": 17, "flags": 17,
"active": true, "active": true,
}, },
{ {
"src": "c.example.test", "src": "example.org/hello",
"dst": "127.0.0.1:8082", "dst": "127.0.0.1:8082",
"flags": 16, "flags": 16,
"active": true, "active": true,
}, },
{ {
"src": "d.example.test", "src": "test.example.org/hello",
"dst": "127.0.0.1:8083", "dst": "127.0.0.1:8083",
"flags": 15, "flags": 15,
"active": true, "active": true,
}, },
}) })
})) }))
r.Handle("/v1/violet/redirect", hasPerm(verify, "violet:redirect", func(rw http.ResponseWriter, req *http.Request) { r.Handle("/v1/violet/redirect", hasPerm(verify, "violet:redirect", func(rw http.ResponseWriter, req *http.Request) {
json.NewEncoder(rw).Encode([]map[string]any{ json.NewEncoder(rw).Encode([]map[string]any{
{ {
"src": "e.example.test", "src": "example.org",
"dst": "127.0.0.1:8084", "dst": "127.0.0.1:8084",
"flags": 181, "flags": 181,
"active": true, "active": true,
}, },
{ {
"src": "f.example.test", "src": "test.example.org",
"dst": "127.0.0.1:8085", "dst": "127.0.0.1:8085",
"flags": 17, "flags": 17,
"active": true, "active": true,
}, },
{ {
"src": "g.example.test", "src": "example.org/hello",
"dst": "127.0.0.1:8086", "dst": "127.0.0.1:8086",
"flags": 16, "flags": 16,
"active": true, "active": true,
}, },
{ {
"src": "h.example.test", "src": "test.example.org/hello",
"dst": "127.0.0.1:8087", "dst": "127.0.0.1:8087",
"flags": 15, "flags": 15,
"active": true, "active": true,