"some" wokr on roles (this is last major roadblock to milestone:feat-org-keys)
This commit is contained in:
parent
cde5b73907
commit
39978c3579
|
@ -0,0 +1,44 @@
|
||||||
|
<script lang="ts">
|
||||||
|
export let selected;
|
||||||
|
export let orgid;
|
||||||
|
|
||||||
|
let hostsstyle = (selected == "hosts" ? "dark:bg-slate-600 bg-slate-200" : "dark:hover:bg-slate-600 hover:bg-slate-200");
|
||||||
|
let lighthousestyle = (selected == "lighthouses" ? "dark:bg-slate-600 bg-slate-200" : "dark:hover:bg-slate-600 hover:bg-slate-200");
|
||||||
|
let relaysstyle = (selected == "relays" ? "dark:bg-slate-600 bg-slate-200" : "dark:hover:bg-slate-600 hover:bg-slate-200");
|
||||||
|
let rolesstyle = (selected == "roles" ? "dark:bg-slate-600 bg-slate-200" : "dark:hover:bg-slate-600 hover:bg-slate-200");
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="min-h-screen flex flex-row overflow-y-hidden">
|
||||||
|
<div class="max-h flex flex-col w-56 overflow-hidden border-r-2 border-slate-500">
|
||||||
|
<ul class="flex flex-col py-4">
|
||||||
|
<li>
|
||||||
|
<a href="/org/{orgid}/hosts" class="{hostsstyle} mt-2 flex flex-row items-center h-12 transition">
|
||||||
|
<span class="ml-10 text-sm font-medium">Hosts</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<a href="/org/{orgid}/lighthouses" class="{lighthousestyle} mt-2 flex flex-row items-center h-12 dark:hover:bg-slate-600 hover:bg-slate-200 transition">
|
||||||
|
<span class="ml-10 text-sm font-medium">Lighthouses</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<a href="/org/{orgid}/relays" class="{relaysstyle} mt-2 flex flex-row items-center h-12 dark:hover:bg-slate-600 hover:bg-slate-200 transition">
|
||||||
|
<span class="ml-10 text-sm font-medium">Relays</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<a href="/org/{orgid}/roles" class="{rolesstyle} mt-2 flex flex-row items-center h-12 dark:hover:bg-slate-600 hover:bg-slate-200 transition">
|
||||||
|
<span class="ml-10 text-sm font-medium">Roles</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="overflow-hidden w-full min-w-screen min-h-screen ml-5 mr-5 m-2">
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -106,5 +106,48 @@
|
||||||
"loadownedorg": "Unable to load organizations",
|
"loadownedorg": "Unable to load organizations",
|
||||||
"this user does not have permission to access this org": "You do not have permission to access one of the orgs associated with your account. Your access may have been revoked. Please reload the page."
|
"this user does not have permission to access this org": "You do not have permission to access one of the orgs associated with your account. Your access may have been revoked. Please reload the page."
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"org": {
|
||||||
|
"apierror": {
|
||||||
|
"notyourorg": "Access denied",
|
||||||
|
"notyourorgexplainer": "You do not have permission to manage this org. Ask the owner of this org to add you, or go back to the homepage.",
|
||||||
|
"loadroles": "Unable to load roles",
|
||||||
|
"TypeError: NetworkError when attempting to fetch resource": "Unable to contact the server. Please try again later",
|
||||||
|
"Unable to create default role firewall rules": "Unable to create the default role. Please try again later."
|
||||||
|
},
|
||||||
|
|
||||||
|
"nohosts": "You don't have any hosts set up",
|
||||||
|
"nohostsdesc": "Create and enroll a host to begin using it in your network.",
|
||||||
|
"nohostsdesc2": "Be sure to also create and enroll a lighthouse, or your hosts wont be able to communicate.",
|
||||||
|
"nohosts_cta": "Add host",
|
||||||
|
|
||||||
|
"nolighthouses": "You don't have any lighthouses set up",
|
||||||
|
"nolighthousesdesc": "Lighthouses keep track of potential routes to overlay network hosts, and they assist in establishing connections between hosts.",
|
||||||
|
"nolighthouses_list_item1": "Each network needs at least one lighthouse.",
|
||||||
|
"nolighthouses_list_item2": "If you plan to access hosts over the Internet, at least one lighthouse will need a static, public IPv4 address with its firewall configured to allow inbound udp traffic on a specific port.",
|
||||||
|
"nolighthouses_list_item3": "A modestly-sized cloud instance should be sufficient for most users.",
|
||||||
|
"nolighthouses_list_item4": "Lighthouses are also hosts - they will have an IP and you will be able to access them over the network.",
|
||||||
|
"nolighthouses_cta": "Add lighthouse",
|
||||||
|
|
||||||
|
"norelays": "Adding a relay is recommended",
|
||||||
|
"norelays_list_item1": "Relays ensure connectivity for scenarios when direct connections fail.",
|
||||||
|
"norelays_list_item2": "Without a relay, some devices may be unable to communicate.",
|
||||||
|
"norelays_list_item3": "Your lighthouses also act as relays.",
|
||||||
|
"norelays_lighthouse": "You currently have {} lighthouses acting as relays",
|
||||||
|
"norelays_cta": "Add relay",
|
||||||
|
|
||||||
|
"roles_title": "Roles",
|
||||||
|
"roles_bar": "Roles control how hosts, lighthouses, and relays communicate through firewall rules.",
|
||||||
|
"roles_cta": "Add",
|
||||||
|
|
||||||
|
"guide": "Read the guide →",
|
||||||
|
|
||||||
|
"noroles": "Wow, such empty",
|
||||||
|
"noroles_cta": "You don't have any roles. Consider creating one with the button above.",
|
||||||
|
|
||||||
|
"roleaddprompt": "What's the name of your new role?",
|
||||||
|
"roleaddpromptdesc": "What's the description of your new role?",
|
||||||
|
"roleadd_cta": "Create role"
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -11,7 +11,7 @@
|
||||||
import {finishTOTPSetup} from "$lib/totp";
|
import {finishTOTPSetup} from "$lib/totp";
|
||||||
import {setCookie} from "$lib/cookie";
|
import {setCookie} from "$lib/cookie";
|
||||||
|
|
||||||
let logger = new Logger("auth/mfa/+page.svelte");
|
let logger = new Logger("auth/mfasetup/+page.svelte");
|
||||||
logSetup();
|
logSetup();
|
||||||
|
|
||||||
let api_token = "";
|
let api_token = "";
|
||||||
|
|
|
@ -0,0 +1,86 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import {onMount} from "svelte";
|
||||||
|
import {enforce_auth, enforce_session} from "$lib/auth";
|
||||||
|
import {Logger, logSetup} from "$lib/logger";
|
||||||
|
import {list_org} from "$lib/orgs";
|
||||||
|
import {t} from "$lib/i18n";
|
||||||
|
import FullPageError from "../../../components/FullPageError.svelte";
|
||||||
|
import {get_user_info} from "$lib/auth";
|
||||||
|
import {org} from "$lib/orgs";
|
||||||
|
import type {Organization} from "$lib/orgs";
|
||||||
|
import {API_ROOT} from "$lib/config";
|
||||||
|
import {fetch_timeout} from "$lib/util";
|
||||||
|
import {page} from "$app/stores";
|
||||||
|
|
||||||
|
let logger = new Logger("org/[orgid]/+page.svelte");
|
||||||
|
logSetup();
|
||||||
|
|
||||||
|
let orgid = $page.params.orgid;
|
||||||
|
|
||||||
|
let api_token = "";
|
||||||
|
|
||||||
|
let fullPageError = false;
|
||||||
|
let fullPageErrorTitle = "";
|
||||||
|
let fullPageErrorSubtitle = "";
|
||||||
|
|
||||||
|
let user_info;
|
||||||
|
|
||||||
|
// this page requires session and mfa auth.
|
||||||
|
onMount(async () => {
|
||||||
|
let st_result = await enforce_session();
|
||||||
|
if (!st_result[0]) {
|
||||||
|
// Session token is invalid. redirect to login
|
||||||
|
window.location = "/auth/login";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let at_result = await enforce_auth();
|
||||||
|
if (!at_result[0]) {
|
||||||
|
// Auth token is invalid. Redirect to mfa page.
|
||||||
|
window.location = "/auth/mfa";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// user is fully authenticated and permitted to proceed
|
||||||
|
api_token = at_result[1];
|
||||||
|
logger.info("User authenticated successfully");
|
||||||
|
|
||||||
|
// GET USER INFO
|
||||||
|
user_info = await get_user_info(api_token);
|
||||||
|
if (typeof user_info === "string") {
|
||||||
|
logger.error(user_info);
|
||||||
|
fullPageError = true;
|
||||||
|
fullPageErrorTitle = t('admin.apierror.loaduser');
|
||||||
|
fullPageErrorSubtitle = t('admin.apierror.' + user_info);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let org_list: string | number[] = await list_org(api_token);
|
||||||
|
if (typeof org_list === "string") {
|
||||||
|
// Error
|
||||||
|
logger.error(org_list);
|
||||||
|
fullPageError = true;
|
||||||
|
fullPageErrorTitle = t('org.apierror.loadorg');
|
||||||
|
fullPageErrorSubtitle = t('org.apierror.' + org_list);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let list: number[] = org_list;
|
||||||
|
|
||||||
|
if (!list.includes(Number(orgid))) {
|
||||||
|
fullPageError = true;
|
||||||
|
fullPageErrorTitle = t('org.apierror.notyourorg');
|
||||||
|
fullPageErrorSubtitle = t('org.apierror.notyourorgexplainer');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.location.href = `/org/${orgid}/lighthouses`;
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if fullPageError}
|
||||||
|
<FullPageError title="{fullPageErrorTitle}" subtitle="{fullPageErrorSubtitle}"/>
|
||||||
|
{:else}
|
||||||
|
<!-- BREAKING THE LAYOUT! -->
|
||||||
|
|
||||||
|
<!-- Sidenav :O -->
|
||||||
|
|
||||||
|
|
||||||
|
{/if}
|
|
@ -0,0 +1,86 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import {onMount} from "svelte";
|
||||||
|
import {enforce_auth, enforce_session} from "$lib/auth";
|
||||||
|
import {Logger, logSetup} from "$lib/logger";
|
||||||
|
import {list_org} from "$lib/orgs";
|
||||||
|
import {t} from "$lib/i18n";
|
||||||
|
import FullPageError from "../../../../components/FullPageError.svelte";
|
||||||
|
import {get_user_info} from "$lib/auth";
|
||||||
|
import {org} from "$lib/orgs";
|
||||||
|
import type {Organization} from "$lib/orgs";
|
||||||
|
import {API_ROOT} from "$lib/config";
|
||||||
|
import {fetch_timeout} from "$lib/util";
|
||||||
|
import {page} from "$app/stores";
|
||||||
|
import OrgWrapper from "$components/org/OrgWrapper.svelte";
|
||||||
|
|
||||||
|
let logger = new Logger("org/[orgid]/hosts/+page.svelte");
|
||||||
|
logSetup();
|
||||||
|
|
||||||
|
let orgid = $page.params.orgid;
|
||||||
|
|
||||||
|
let api_token = "";
|
||||||
|
|
||||||
|
let fullPageError = false;
|
||||||
|
let fullPageErrorTitle = "";
|
||||||
|
let fullPageErrorSubtitle = "";
|
||||||
|
|
||||||
|
let user_info;
|
||||||
|
|
||||||
|
// this page requires session and mfa auth.
|
||||||
|
onMount(async () => {
|
||||||
|
let st_result = await enforce_session();
|
||||||
|
if (!st_result[0]) {
|
||||||
|
// Session token is invalid. redirect to login
|
||||||
|
window.location = "/auth/login";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let at_result = await enforce_auth();
|
||||||
|
if (!at_result[0]) {
|
||||||
|
// Auth token is invalid. Redirect to mfa page.
|
||||||
|
window.location = "/auth/mfa";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// user is fully authenticated and permitted to proceed
|
||||||
|
api_token = at_result[1];
|
||||||
|
logger.info("User authenticated successfully");
|
||||||
|
|
||||||
|
// GET USER INFO
|
||||||
|
user_info = await get_user_info(api_token);
|
||||||
|
if (typeof user_info === "string") {
|
||||||
|
logger.error(user_info);
|
||||||
|
fullPageError = true;
|
||||||
|
fullPageErrorTitle = t('admin.apierror.loaduser');
|
||||||
|
fullPageErrorSubtitle = t('admin.apierror.' + user_info);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let org_list: string | number[] = await list_org(api_token);
|
||||||
|
if (typeof org_list === "string") {
|
||||||
|
// Error
|
||||||
|
logger.error(org_list);
|
||||||
|
fullPageError = true;
|
||||||
|
fullPageErrorTitle = t('org.apierror.loadorg');
|
||||||
|
fullPageErrorSubtitle = t('org.apierror.' + org_list);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let list: number[] = org_list;
|
||||||
|
|
||||||
|
if (!list.includes(Number(orgid))) {
|
||||||
|
fullPageError = true;
|
||||||
|
fullPageErrorTitle = t('org.apierror.notyourorg');
|
||||||
|
fullPageErrorSubtitle = t('org.apierror.notyourorgexplainer');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if fullPageError}
|
||||||
|
<FullPageError title="{fullPageErrorTitle}" subtitle="{fullPageErrorSubtitle}"/>
|
||||||
|
{:else}
|
||||||
|
<!-- BREAKING THE LAYOUT! -->
|
||||||
|
|
||||||
|
<!-- Sidenav :O -->
|
||||||
|
<OrgWrapper selected="hosts" orgid="{orgid}">
|
||||||
|
|
||||||
|
</OrgWrapper>
|
||||||
|
{/if}
|
|
@ -0,0 +1,86 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import {onMount} from "svelte";
|
||||||
|
import {enforce_auth, enforce_session} from "$lib/auth";
|
||||||
|
import {Logger, logSetup} from "$lib/logger";
|
||||||
|
import {list_org} from "$lib/orgs";
|
||||||
|
import {t} from "$lib/i18n";
|
||||||
|
import FullPageError from "../../../../components/FullPageError.svelte";
|
||||||
|
import {get_user_info} from "$lib/auth";
|
||||||
|
import {org} from "$lib/orgs";
|
||||||
|
import type {Organization} from "$lib/orgs";
|
||||||
|
import {API_ROOT} from "$lib/config";
|
||||||
|
import {fetch_timeout} from "$lib/util";
|
||||||
|
import {page} from "$app/stores";
|
||||||
|
import OrgWrapper from "../../../../components/org/OrgWrapper.svelte";
|
||||||
|
|
||||||
|
let logger = new Logger("org/[orgid]/lighthouses/+page.svelte");
|
||||||
|
logSetup();
|
||||||
|
|
||||||
|
let orgid = $page.params.orgid;
|
||||||
|
|
||||||
|
let api_token = "";
|
||||||
|
|
||||||
|
let fullPageError = false;
|
||||||
|
let fullPageErrorTitle = "";
|
||||||
|
let fullPageErrorSubtitle = "";
|
||||||
|
|
||||||
|
let user_info;
|
||||||
|
|
||||||
|
// this page requires session and mfa auth.
|
||||||
|
onMount(async () => {
|
||||||
|
let st_result = await enforce_session();
|
||||||
|
if (!st_result[0]) {
|
||||||
|
// Session token is invalid. redirect to login
|
||||||
|
window.location = "/auth/login";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let at_result = await enforce_auth();
|
||||||
|
if (!at_result[0]) {
|
||||||
|
// Auth token is invalid. Redirect to mfa page.
|
||||||
|
window.location = "/auth/mfa";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// user is fully authenticated and permitted to proceed
|
||||||
|
api_token = at_result[1];
|
||||||
|
logger.info("User authenticated successfully");
|
||||||
|
|
||||||
|
// GET USER INFO
|
||||||
|
user_info = await get_user_info(api_token);
|
||||||
|
if (typeof user_info === "string") {
|
||||||
|
logger.error(user_info);
|
||||||
|
fullPageError = true;
|
||||||
|
fullPageErrorTitle = t('admin.apierror.loaduser');
|
||||||
|
fullPageErrorSubtitle = t('admin.apierror.' + user_info);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let org_list: string | number[] = await list_org(api_token);
|
||||||
|
if (typeof org_list === "string") {
|
||||||
|
// Error
|
||||||
|
logger.error(org_list);
|
||||||
|
fullPageError = true;
|
||||||
|
fullPageErrorTitle = t('org.apierror.loadorg');
|
||||||
|
fullPageErrorSubtitle = t('org.apierror.' + org_list);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let list: number[] = org_list;
|
||||||
|
|
||||||
|
if (!list.includes(Number(orgid))) {
|
||||||
|
fullPageError = true;
|
||||||
|
fullPageErrorTitle = t('org.apierror.notyourorg');
|
||||||
|
fullPageErrorSubtitle = t('org.apierror.notyourorgexplainer');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if fullPageError}
|
||||||
|
<FullPageError title="{fullPageErrorTitle}" subtitle="{fullPageErrorSubtitle}"/>
|
||||||
|
{:else}
|
||||||
|
<!-- BREAKING THE LAYOUT! -->
|
||||||
|
|
||||||
|
<!-- Sidenav :O -->
|
||||||
|
<OrgWrapper selected="lighthouses" orgid="{orgid}">
|
||||||
|
|
||||||
|
</OrgWrapper>
|
||||||
|
{/if}
|
|
@ -0,0 +1,86 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import {onMount} from "svelte";
|
||||||
|
import {enforce_auth, enforce_session} from "$lib/auth";
|
||||||
|
import {Logger, logSetup} from "$lib/logger";
|
||||||
|
import {list_org} from "$lib/orgs";
|
||||||
|
import {t} from "$lib/i18n";
|
||||||
|
import FullPageError from "../../../../components/FullPageError.svelte";
|
||||||
|
import {get_user_info} from "$lib/auth";
|
||||||
|
import {org} from "$lib/orgs";
|
||||||
|
import type {Organization} from "$lib/orgs";
|
||||||
|
import {API_ROOT} from "$lib/config";
|
||||||
|
import {fetch_timeout} from "$lib/util";
|
||||||
|
import {page} from "$app/stores";
|
||||||
|
import OrgWrapper from "$components/org/OrgWrapper.svelte";
|
||||||
|
|
||||||
|
let logger = new Logger("org/[orgid]/relays/+page.svelte");
|
||||||
|
logSetup();
|
||||||
|
|
||||||
|
let orgid = $page.params.orgid;
|
||||||
|
|
||||||
|
let api_token = "";
|
||||||
|
|
||||||
|
let fullPageError = false;
|
||||||
|
let fullPageErrorTitle = "";
|
||||||
|
let fullPageErrorSubtitle = "";
|
||||||
|
|
||||||
|
let user_info;
|
||||||
|
|
||||||
|
// this page requires session and mfa auth.
|
||||||
|
onMount(async () => {
|
||||||
|
let st_result = await enforce_session();
|
||||||
|
if (!st_result[0]) {
|
||||||
|
// Session token is invalid. redirect to login
|
||||||
|
window.location = "/auth/login";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let at_result = await enforce_auth();
|
||||||
|
if (!at_result[0]) {
|
||||||
|
// Auth token is invalid. Redirect to mfa page.
|
||||||
|
window.location = "/auth/mfa";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// user is fully authenticated and permitted to proceed
|
||||||
|
api_token = at_result[1];
|
||||||
|
logger.info("User authenticated successfully");
|
||||||
|
|
||||||
|
// GET USER INFO
|
||||||
|
user_info = await get_user_info(api_token);
|
||||||
|
if (typeof user_info === "string") {
|
||||||
|
logger.error(user_info);
|
||||||
|
fullPageError = true;
|
||||||
|
fullPageErrorTitle = t('admin.apierror.loaduser');
|
||||||
|
fullPageErrorSubtitle = t('admin.apierror.' + user_info);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let org_list: string | number[] = await list_org(api_token);
|
||||||
|
if (typeof org_list === "string") {
|
||||||
|
// Error
|
||||||
|
logger.error(org_list);
|
||||||
|
fullPageError = true;
|
||||||
|
fullPageErrorTitle = t('org.apierror.loadorg');
|
||||||
|
fullPageErrorSubtitle = t('org.apierror.' + org_list);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let list: number[] = org_list;
|
||||||
|
|
||||||
|
if (!list.includes(Number(orgid))) {
|
||||||
|
fullPageError = true;
|
||||||
|
fullPageErrorTitle = t('org.apierror.notyourorg');
|
||||||
|
fullPageErrorSubtitle = t('org.apierror.notyourorgexplainer');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if fullPageError}
|
||||||
|
<FullPageError title="{fullPageErrorTitle}" subtitle="{fullPageErrorSubtitle}"/>
|
||||||
|
{:else}
|
||||||
|
<!-- BREAKING THE LAYOUT! -->
|
||||||
|
|
||||||
|
<!-- Sidenav :O -->
|
||||||
|
<OrgWrapper selected="relays" orgid="{orgid}">
|
||||||
|
|
||||||
|
</OrgWrapper>
|
||||||
|
{/if}
|
|
@ -0,0 +1,169 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import {onMount} from "svelte";
|
||||||
|
import {enforce_auth, enforce_session} from "$lib/auth";
|
||||||
|
import {Logger, logSetup} from "$lib/logger";
|
||||||
|
import {list_org} from "$lib/orgs";
|
||||||
|
import {t} from "$lib/i18n";
|
||||||
|
import FullPageError from "../../../../components/FullPageError.svelte";
|
||||||
|
import {get_user_info} from "$lib/auth";
|
||||||
|
import {org} from "$lib/orgs";
|
||||||
|
import type {Organization} from "$lib/orgs";
|
||||||
|
import {API_ROOT} from "$lib/config";
|
||||||
|
import {fetch_timeout} from "$lib/util";
|
||||||
|
import {page} from "$app/stores";
|
||||||
|
import OrgWrapper from "../../../../components/org/OrgWrapper.svelte";
|
||||||
|
|
||||||
|
let logger = new Logger("org/[orgid]/roles/+page.svelte");
|
||||||
|
logSetup();
|
||||||
|
|
||||||
|
let orgid = $page.params.orgid;
|
||||||
|
|
||||||
|
let api_token = "";
|
||||||
|
|
||||||
|
let fullPageError = false;
|
||||||
|
let fullPageErrorTitle = "";
|
||||||
|
let fullPageErrorSubtitle = "";
|
||||||
|
|
||||||
|
let user_info;
|
||||||
|
|
||||||
|
let roles = {
|
||||||
|
'data': []
|
||||||
|
};
|
||||||
|
|
||||||
|
// this page requires session and mfa auth.
|
||||||
|
onMount(async () => {
|
||||||
|
let st_result = await enforce_session();
|
||||||
|
if (!st_result[0]) {
|
||||||
|
// Session token is invalid. redirect to login
|
||||||
|
window.location = "/auth/login";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let at_result = await enforce_auth();
|
||||||
|
if (!at_result[0]) {
|
||||||
|
// Auth token is invalid. Redirect to mfa page.
|
||||||
|
window.location = "/auth/mfa";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// user is fully authenticated and permitted to proceed
|
||||||
|
api_token = at_result[1];
|
||||||
|
logger.info("User authenticated successfully");
|
||||||
|
|
||||||
|
// GET USER INFO
|
||||||
|
user_info = await get_user_info(api_token);
|
||||||
|
if (typeof user_info === "string") {
|
||||||
|
logger.error(user_info);
|
||||||
|
fullPageError = true;
|
||||||
|
fullPageErrorTitle = t('admin.apierror.loaduser');
|
||||||
|
fullPageErrorSubtitle = t('admin.apierror.' + user_info);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let org_list: string | number[] = await list_org(api_token);
|
||||||
|
if (typeof org_list === "string") {
|
||||||
|
// Error
|
||||||
|
logger.error(org_list);
|
||||||
|
fullPageError = true;
|
||||||
|
fullPageErrorTitle = t('org.apierror.loadorg');
|
||||||
|
fullPageErrorSubtitle = t('org.apierror.' + org_list);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let list: number[] = org_list;
|
||||||
|
|
||||||
|
if (!list.includes(Number(orgid))) {
|
||||||
|
fullPageError = true;
|
||||||
|
fullPageErrorTitle = t('org.apierror.notyourorg');
|
||||||
|
fullPageErrorSubtitle = t('org.apierror.notyourorgexplainer');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
let resp = await fetch_timeout(`${API_ROOT}/v1/org/${orgid}/roles`, {
|
||||||
|
'method': 'GET',
|
||||||
|
'headers': {
|
||||||
|
'Authorization': `Bearer ${api_token}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (!resp.ok) {
|
||||||
|
let err = JSON.parse(await resp.text()).errors[0].message;
|
||||||
|
logger.error(`${err}`);
|
||||||
|
fullPageError = true;
|
||||||
|
fullPageErrorTitle = t('org.apierror.loadroles');
|
||||||
|
fullPageErrorSubtitle = t('org.apierror.' + err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
roles = JSON.parse(await resp.text());
|
||||||
|
} catch (e) {
|
||||||
|
logger.error(`${e}`);
|
||||||
|
fullPageError = true;
|
||||||
|
fullPageErrorTitle = t('org.apierror.loadroles');
|
||||||
|
fullPageErrorSubtitle = t('org.apierror.' + `${e}`.replaceAll('.', ''));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if fullPageError}
|
||||||
|
<FullPageError title="{fullPageErrorTitle}" subtitle="{fullPageErrorSubtitle}"/>
|
||||||
|
{:else}
|
||||||
|
<!-- BREAKING THE LAYOUT! -->
|
||||||
|
|
||||||
|
<!-- Sidenav :O -->
|
||||||
|
<OrgWrapper selected="roles" orgid="{orgid}">
|
||||||
|
<div class="w-full">
|
||||||
|
<!-- Top bar -->
|
||||||
|
<h1 class="align-middle inline mt-10 font-semibold text-2xl">{t('org.roles_title')}</h1>
|
||||||
|
<button on:click={() => {window.location.href = `/org/${orgid}/roles/add`}} class="align-middle inline w-20 h-31 float-right dark:bg-purple-700 rounded font-bold text-sm p-2 w-25 border-2 dark:border-purple-600 border-purple-700 bg-purple-200">{t('org.roles_cta')}</button>
|
||||||
|
|
||||||
|
<!-- Infobox -->
|
||||||
|
<div class="mt-5 p-4 bg-purple-800/20 text-sm rounded">
|
||||||
|
<span>{t('org.roles_bar')}</span>
|
||||||
|
<a class="inline text-purple-600 hover:text-purple-700 transition font-semibold"
|
||||||
|
href="/docs/roles">{t('org.guide')}</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Table -->
|
||||||
|
<!-- roles.data.length === 0 -->
|
||||||
|
{#if roles.data.length === 0}
|
||||||
|
<div class="mt-5">
|
||||||
|
<h2 class="text-center text-xl font-semibold">{t('org.noroles')}</h2>
|
||||||
|
<h3 class="text-center text-md">{t('org.noroles_cta')}</h3>
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<div class="mt-5">
|
||||||
|
<table class="rounded w-full bg-white dark:bg-slate-800 text-sm shadow-sm">
|
||||||
|
<tr>
|
||||||
|
<th class="w-1/4 border-b font-semibold p-4 text-slate-900 dark:text-slate-200 text-left">
|
||||||
|
Name
|
||||||
|
</th>
|
||||||
|
<th class="w-1/8 border-b font-semibold p-4 text-slate-900 dark:text-slate-200 text-left">ID
|
||||||
|
</th>
|
||||||
|
<th class="w-1/8 border-b font-semibold p-4 text-slate-900 dark:text-slate-200 text-left">
|
||||||
|
Rules
|
||||||
|
</th>
|
||||||
|
<th class="w-3/4 border-b font-semibold p-4 text-slate-900 dark:text-slate-200 text-left">
|
||||||
|
Description
|
||||||
|
</th>
|
||||||
|
<th class="border-b p-4"></th>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
{#each roles.data as role}
|
||||||
|
<tr>
|
||||||
|
<td class="p-4 text-slate-600 dark:text-slate-300 text-left">{role.name}</td>
|
||||||
|
<td class="p-4 text-slate-600 dark:text-slate-300 text-left">{role.id}</td>
|
||||||
|
<td class="p-4 text-slate-600 dark:text-slate-300 text-left">{role.firewall_rules.length}</td>
|
||||||
|
<td class="p-4 text-slate-600 dark:text-slate-300 text-left">{role.description}</td>
|
||||||
|
<td>
|
||||||
|
<button class="w-16 border p-2 rounded m-2 hover:bg-slate-100 transition dark:hover:bg-slate-700" on:click={() => {
|
||||||
|
window.location.href = `/org/${orgid}/roles/${role.id}`
|
||||||
|
}}>
|
||||||
|
Edit
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{/each}
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</OrgWrapper>
|
||||||
|
{/if}
|
|
@ -0,0 +1,144 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import {onMount} from "svelte";
|
||||||
|
import {enforce_auth, enforce_session} from "$lib/auth";
|
||||||
|
import {Logger, logSetup} from "$lib/logger";
|
||||||
|
import {list_org} from "$lib/orgs";
|
||||||
|
import {t} from "$lib/i18n";
|
||||||
|
import FullPageError from "../../../../../components/FullPageError.svelte";
|
||||||
|
import {get_user_info} from "$lib/auth";
|
||||||
|
import {org} from "$lib/orgs";
|
||||||
|
import type {Organization} from "$lib/orgs";
|
||||||
|
import {API_ROOT} from "$lib/config";
|
||||||
|
import {fetch_timeout} from "$lib/util";
|
||||||
|
import {page} from "$app/stores";
|
||||||
|
import OrgWrapper from "../../../../../components/org/OrgWrapper.svelte";
|
||||||
|
|
||||||
|
let logger = new Logger("org/[orgid]/roles/+page.svelte");
|
||||||
|
logSetup();
|
||||||
|
|
||||||
|
let orgid = $page.params.orgid;
|
||||||
|
|
||||||
|
let api_token = "";
|
||||||
|
|
||||||
|
let fullPageError = false;
|
||||||
|
let fullPageErrorTitle = "";
|
||||||
|
let fullPageErrorSubtitle = "";
|
||||||
|
|
||||||
|
let user_info;
|
||||||
|
|
||||||
|
let roles = {
|
||||||
|
'data': []
|
||||||
|
};
|
||||||
|
|
||||||
|
// this page requires session and mfa auth.
|
||||||
|
onMount(async () => {
|
||||||
|
let st_result = await enforce_session();
|
||||||
|
if (!st_result[0]) {
|
||||||
|
// Session token is invalid. redirect to login
|
||||||
|
window.location = "/auth/login";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let at_result = await enforce_auth();
|
||||||
|
if (!at_result[0]) {
|
||||||
|
// Auth token is invalid. Redirect to mfa page.
|
||||||
|
window.location = "/auth/mfa";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// user is fully authenticated and permitted to proceed
|
||||||
|
api_token = at_result[1];
|
||||||
|
logger.info("User authenticated successfully");
|
||||||
|
|
||||||
|
// GET USER INFO
|
||||||
|
user_info = await get_user_info(api_token);
|
||||||
|
if (typeof user_info === "string") {
|
||||||
|
logger.error(user_info);
|
||||||
|
fullPageError = true;
|
||||||
|
fullPageErrorTitle = t('admin.apierror.loaduser');
|
||||||
|
fullPageErrorSubtitle = t('admin.apierror.' + user_info);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let org_list: string | number[] = await list_org(api_token);
|
||||||
|
if (typeof org_list === "string") {
|
||||||
|
// Error
|
||||||
|
logger.error(org_list);
|
||||||
|
fullPageError = true;
|
||||||
|
fullPageErrorTitle = t('org.apierror.loadorg');
|
||||||
|
fullPageErrorSubtitle = t('org.apierror.' + org_list);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let list: number[] = org_list;
|
||||||
|
|
||||||
|
if (!list.includes(Number(orgid))) {
|
||||||
|
fullPageError = true;
|
||||||
|
fullPageErrorTitle = t('org.apierror.notyourorg');
|
||||||
|
fullPageErrorSubtitle = t('org.apierror.notyourorgexplainer');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
let name = "";
|
||||||
|
let description = "";
|
||||||
|
|
||||||
|
async function createRole() {
|
||||||
|
try {
|
||||||
|
let resp = await fetch_timeout(`${API_ROOT}/v1/org/${orgid}/role`, {
|
||||||
|
'method': 'POST',
|
||||||
|
'headers': {
|
||||||
|
'Authorization': `Bearer ${api_token}`
|
||||||
|
},
|
||||||
|
'body': JSON.stringify({
|
||||||
|
'name': name,
|
||||||
|
'description': description
|
||||||
|
})
|
||||||
|
});
|
||||||
|
if (!resp.ok) {
|
||||||
|
let err = JSON.parse(await resp.text()).errors[0].message;
|
||||||
|
logger.error(`${err}`);
|
||||||
|
fullPageError = true;
|
||||||
|
fullPageErrorTitle = t('org.apierror.addrole');
|
||||||
|
fullPageErrorSubtitle = t('org.apierror.' + err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
roles = JSON.parse(await resp.text());
|
||||||
|
window.location.href = `/org/${orgid}/roles`
|
||||||
|
} catch (e) {
|
||||||
|
logger.error(`${e}`);
|
||||||
|
fullPageError = true;
|
||||||
|
fullPageErrorTitle = t('org.apierror.loadroles');
|
||||||
|
fullPageErrorSubtitle = t('org.apierror.' + `${e}`.replaceAll('.', ''));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if fullPageError}
|
||||||
|
<FullPageError title="{fullPageErrorTitle}" subtitle="{fullPageErrorSubtitle}"/>
|
||||||
|
{:else}
|
||||||
|
<div class="flex in-h-full items-center justify-center py-12 px-4 sm:px-6 lg:px-8">
|
||||||
|
<div class="w-full max-w-md">
|
||||||
|
|
||||||
|
<!-- The actual form -->
|
||||||
|
<form class="mt-5" action="#" method="POST" on:submit|preventDefault={createRole}>
|
||||||
|
<div class="rounded-md shadow-sm">
|
||||||
|
<label for="name">{t('org.roleaddprompt')}</label>
|
||||||
|
<input bind:value={name} id="name"
|
||||||
|
class="dark:bg-slate-500 bg-gray-200 w-full rounded px-3 py-2 focus:outline-none focus:ring-purple-500 appearance-none">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="rounded-md shadow-sm mt-5">
|
||||||
|
<label for="desc">{t('org.roleaddpromptdesc')}</label>
|
||||||
|
<input bind:value={description} id="desc"
|
||||||
|
class="dark:bg-slate-500 bg-gray-200 w-full rounded px-3 py-2 focus:outline-none focus:ring-purple-500 appearance-none">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="bg-purple-400 dark:bg-purple-600 mt-4 w-full py-2 -space-y-px rounded-md shadow-sm place-content-center">
|
||||||
|
{t('org.roleadd_cta')}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
|
@ -11,7 +11,7 @@
|
||||||
import {API_ROOT} from "$lib/config";
|
import {API_ROOT} from "$lib/config";
|
||||||
import {fetch_timeout} from "$lib/util";
|
import {fetch_timeout} from "$lib/util";
|
||||||
|
|
||||||
let logger = new Logger("admin/+page.svelte");
|
let logger = new Logger("org/new/+page.svelte");
|
||||||
logSetup();
|
logSetup();
|
||||||
|
|
||||||
let api_token = "";
|
let api_token = "";
|
||||||
|
@ -111,15 +111,18 @@
|
||||||
fullPageErrorSubtitle = t('neworg.apierror.invaliddata');
|
fullPageErrorSubtitle = t('neworg.apierror.invaliddata');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let err = JSON.parse(await resp.text()).errors[0].message;
|
let text = await resp.text();
|
||||||
logger.error(`${await resp.json().errors[0]}`);
|
console.log(text);
|
||||||
|
let err = JSON.parse(text).errors[0].message;
|
||||||
|
logger.error(`${err}`);
|
||||||
fullPageError = true;
|
fullPageError = true;
|
||||||
fullPageErrorTitle = t('neworg.apierror.orgcreate');
|
fullPageErrorTitle = t('neworg.apierror.orgcreate');
|
||||||
fullPageErrorSubtitle = t('neworg.apierror.' + err);
|
fullPageErrorSubtitle = t('neworg.apierror.' + err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let resp_objectified = JSON.parse(await resp.text());
|
let text = await resp.text();
|
||||||
console.log(JSON.stringify(resp_objectified));
|
console.log(text);
|
||||||
|
let resp_objectified = JSON.parse(text);
|
||||||
created_org_id = resp_objectified.org_id;
|
created_org_id = resp_objectified.org_id;
|
||||||
logger.info("Able to create organization with id " + created_org_id);
|
logger.info("Able to create organization with id " + created_org_id);
|
||||||
logger.info("Org create success! Redirecting to manage page");
|
logger.info("Org create success! Redirecting to manage page");
|
||||||
|
|
|
@ -15,6 +15,12 @@
|
||||||
],
|
],
|
||||||
"$lib/*": [
|
"$lib/*": [
|
||||||
"./src/lib/*"
|
"./src/lib/*"
|
||||||
|
],
|
||||||
|
"$components": [
|
||||||
|
"./src/components"
|
||||||
|
],
|
||||||
|
"$components/*": [
|
||||||
|
"./src/components/*"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
-- trifid-api, an open source reimplementation of the Defined Networking nebula management server.
|
||||||
|
-- Copyright (C) 2023 c0repwn3r
|
||||||
|
--
|
||||||
|
-- This program is free software: you can redistribute it and/or modify
|
||||||
|
-- it under the terms of the GNU General Public License as published by
|
||||||
|
-- the Free Software Foundation, either version 3 of the License, or
|
||||||
|
-- (at your option) any later version.
|
||||||
|
--
|
||||||
|
-- This program is distributed in the hope that it will be useful,
|
||||||
|
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
-- GNU General Public License for more details.
|
||||||
|
--
|
||||||
|
-- You should have received a copy of the GNU General Public License
|
||||||
|
-- along with this program. If not, see <https:--www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
CREATE TABLE roles (
|
||||||
|
id SERIAL NOT NULL PRIMARY KEY,
|
||||||
|
org SERIAL NOT NULL REFERENCES organizations(id),
|
||||||
|
name VARCHAR(128) NOT NULL,
|
||||||
|
description VARCHAR(4096) NOT NULL
|
||||||
|
);
|
|
@ -0,0 +1,27 @@
|
||||||
|
-- trifid-api, an open source reimplementation of the Defined Networking nebula management server.
|
||||||
|
-- Copyright (C) 2023 c0repwn3r
|
||||||
|
--
|
||||||
|
-- This program is free software: you can redistribute it and/or modify
|
||||||
|
-- it under the terms of the GNU General Public License as published by
|
||||||
|
-- the Free Software Foundation, either version 3 of the License, or
|
||||||
|
-- (at your option) any later version.
|
||||||
|
--
|
||||||
|
-- This program is distributed in the hope that it will be useful,
|
||||||
|
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
-- GNU General Public License for more details.
|
||||||
|
--
|
||||||
|
-- You should have received a copy of the GNU General Public License
|
||||||
|
-- along with this program. If not, see <https:--www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
CREATE TABLE roles_firewall_rules (
|
||||||
|
id SERIAL NOT NULL PRIMARY KEY,
|
||||||
|
role SERIAL NOT NULL REFERENCES roles(id),
|
||||||
|
protocol INTEGER NOT NULL, -- 0: any 1: tcp 2: udp 3: icmp
|
||||||
|
port_range_start INTEGER NOT NULL, -- min: 1 max: 65535. Ignored if protocol==3
|
||||||
|
port_range_end INTEGER NOT NULL, -- min: 1 max: 65535, must be greater than or equal to port_range_start. Ignored if protocol==3
|
||||||
|
allow_from INTEGER NOT NULL, -- Allow traffic goverened by above rules from who?
|
||||||
|
-- -1: anybody
|
||||||
|
-- (a role, anything else): only that role
|
||||||
|
description VARCHAR(4096) NOT NULL
|
||||||
|
);
|
|
@ -41,6 +41,7 @@ pub mod auth;
|
||||||
pub mod crypto;
|
pub mod crypto;
|
||||||
pub mod org;
|
pub mod org;
|
||||||
pub mod kv;
|
pub mod kv;
|
||||||
|
pub mod role;
|
||||||
|
|
||||||
static MIGRATOR: Migrator = sqlx::migrate!();
|
static MIGRATOR: Migrator = sqlx::migrate!();
|
||||||
|
|
||||||
|
@ -183,7 +184,11 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||||
crate::routes::v1::user::options,
|
crate::routes::v1::user::options,
|
||||||
crate::routes::v1::organization::createorgoptions,
|
crate::routes::v1::organization::createorgoptions,
|
||||||
crate::routes::v1::ca::get_cas_for_org,
|
crate::routes::v1::ca::get_cas_for_org,
|
||||||
crate::routes::v1::ca::options
|
crate::routes::v1::ca::options,
|
||||||
|
crate::routes::v1::roles::get_roles,
|
||||||
|
crate::routes::v1::roles::options,
|
||||||
|
crate::routes::v1::roles::options_roleadd,
|
||||||
|
crate::routes::v1::roles::role_add
|
||||||
])
|
])
|
||||||
.register("/", catchers![
|
.register("/", catchers![
|
||||||
crate::routes::handler_400,
|
crate::routes::handler_400,
|
||||||
|
|
|
@ -0,0 +1,98 @@
|
||||||
|
// trifid-api, an open source reimplementation of the Defined Networking nebula management server.
|
||||||
|
// Copyright (C) 2023 c0repwn3r
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use std::error::Error;
|
||||||
|
use sqlx::PgPool;
|
||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
#[repr(i32)]
|
||||||
|
pub enum Protocol {
|
||||||
|
Any = 0,
|
||||||
|
TCP = 1,
|
||||||
|
UDP = 2,
|
||||||
|
ICMP = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub enum AllowFrom {
|
||||||
|
Anyone,
|
||||||
|
SpecificRole(i32)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct FirewallRule {
|
||||||
|
pub id: i32,
|
||||||
|
pub protocol: Protocol,
|
||||||
|
pub port_start: u16,
|
||||||
|
pub port_end: u16,
|
||||||
|
pub allow_from: AllowFrom,
|
||||||
|
pub description: String
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct Role {
|
||||||
|
pub id: i32,
|
||||||
|
pub org_id: i32,
|
||||||
|
pub name: String,
|
||||||
|
pub description: String,
|
||||||
|
pub firewall_rules: Vec<FirewallRule>
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_role(role_id: i32, db: &PgPool) -> Result<Option<Role>, Box<dyn Error>> {
|
||||||
|
let query_result: Option<_> = sqlx::query!("SELECT * FROM roles WHERE id = $1", role_id).fetch_optional(db).await?;
|
||||||
|
|
||||||
|
if let Some(res) = query_result {
|
||||||
|
// get all firewall rules
|
||||||
|
let rules_res = sqlx::query!("SELECT * FROM roles_firewall_rules WHERE role = $1", Some(role_id)).fetch_all(db).await?;
|
||||||
|
|
||||||
|
let mut rules = vec![];
|
||||||
|
|
||||||
|
for rule in rules_res {
|
||||||
|
rules.push(FirewallRule {
|
||||||
|
id: rule.id,
|
||||||
|
protocol: match rule.protocol {
|
||||||
|
0 => Protocol::Any,
|
||||||
|
1 => Protocol::TCP,
|
||||||
|
2 => Protocol::UDP,
|
||||||
|
3 => Protocol::ICMP,
|
||||||
|
_ => return Err(format!("invalid protocol on a firewall rule {}", rule.id).into())
|
||||||
|
},
|
||||||
|
port_start: rule.port_range_start as u16,
|
||||||
|
port_end: rule.port_range_end as u16,
|
||||||
|
allow_from: match rule.allow_from {
|
||||||
|
-1 => AllowFrom::Anyone,
|
||||||
|
_ => AllowFrom::SpecificRole(rule.allow_from)
|
||||||
|
},
|
||||||
|
description: rule.description,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Some(Role {
|
||||||
|
id: role_id,
|
||||||
|
org_id: res.org,
|
||||||
|
name: res.name,
|
||||||
|
description: res.description,
|
||||||
|
firewall_rules: rules,
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_role_ids_for_ca(org_id: i32, db: &PgPool) -> Result<Vec<i32>, Box<dyn Error>> {
|
||||||
|
Ok(sqlx::query!("SELECT id FROM roles WHERE org = $1", org_id).fetch_all(db).await?.iter().map(|r| r.id).collect())
|
||||||
|
}
|
|
@ -22,3 +22,4 @@ pub mod verify_totp_authenticator;
|
||||||
pub mod organization;
|
pub mod organization;
|
||||||
pub mod user;
|
pub mod user;
|
||||||
pub mod ca;
|
pub mod ca;
|
||||||
|
pub mod roles;
|
|
@ -162,6 +162,23 @@ pub async fn create_org(req: Json<CreateCARequest>, user: TOTPAuthenticatedUserI
|
||||||
|
|
||||||
let result = sqlx::query!("INSERT INTO organizations (owner, ca_key, ca_crt, iv) VALUES ($1, $2, $3, $4) RETURNING id", owner_id, ca_key, ca_crt, iv_hex).fetch_one(db.inner()).await.map_err(|e| (Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_QL_QUERY_FAILED", "an error occurred while running the graphql query", e)))?;
|
let result = sqlx::query!("INSERT INTO organizations (owner, ca_key, ca_crt, iv) VALUES ($1, $2, $3, $4) RETURNING id", owner_id, ca_key, ca_crt, iv_hex).fetch_one(db.inner()).await.map_err(|e| (Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_QL_QUERY_FAILED", "an error occurred while running the graphql query", e)))?;
|
||||||
|
|
||||||
|
// last step: create a default role to allow pings from all hosts
|
||||||
|
let role_id = match sqlx::query!("INSERT INTO roles (org, name, description) VALUES ($1, 'Default', 'Allow pings from other hosts. Default role for new hosts.') RETURNING id", result.id).fetch_one(db.inner()).await {
|
||||||
|
Ok(r) => r.id,
|
||||||
|
Err(e) => {
|
||||||
|
error!("[tfapi] dberror: {}", e);
|
||||||
|
return Err((Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{}\"}}]}}", "ERR_INTERNAL_SERVER_ERROR", "Unable to create default role")));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
match sqlx::query!("INSERT INTO roles_firewall_rules (role, protocol, port_range_start, port_range_end, allow_from, description) VALUES ($1, 3, 1, 1, -1, 'Allow pings from anyone on the network')", role_id).execute(db.inner()).await {
|
||||||
|
Ok(_) => {},
|
||||||
|
Err(e) => {
|
||||||
|
error!("[tfapi] dberror: {} inserting on roleid {}", e, role_id);
|
||||||
|
return Err((Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{}\"}}]}}", "ERR_INTERNAL_SERVER_ERROR", "Unable to create default role firewall rules")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok((ContentType::JSON, Json(
|
Ok((ContentType::JSON, Json(
|
||||||
OrginfoStruct {
|
OrginfoStruct {
|
||||||
org_id: result.id,
|
org_id: result.id,
|
||||||
|
|
|
@ -0,0 +1,95 @@
|
||||||
|
// trifid-api, an open source reimplementation of the Defined Networking nebula management server.
|
||||||
|
// Copyright (C) 2023 c0repwn3r
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use log::error;
|
||||||
|
use rocket::{options, get, State, post};
|
||||||
|
use rocket::http::{ContentType, Status};
|
||||||
|
use rocket::serde::json::Json;
|
||||||
|
|
||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
use sqlx::PgPool;
|
||||||
|
use crate::auth::TOTPAuthenticatedUserInfo;
|
||||||
|
use crate::org::user_has_org_assoc;
|
||||||
|
use crate::role::{get_role, get_role_ids_for_ca, Role};
|
||||||
|
|
||||||
|
#[options("/v1/org/<_org>/roles")]
|
||||||
|
pub fn options(_org: i32) -> &'static str { "" }
|
||||||
|
#[options("/v1/org/<_org>/role")]
|
||||||
|
pub fn options_roleadd(_org: i32) -> &'static str { "" }
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
#[serde(crate = "rocket::serde")]
|
||||||
|
pub struct RolesResponse {
|
||||||
|
pub data: Vec<Role>
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/v1/org/<org>/roles")]
|
||||||
|
pub async fn get_roles(org: i32, user: TOTPAuthenticatedUserInfo, db: &State<PgPool>) -> Result<(ContentType, Json<RolesResponse>), (Status, String)> {
|
||||||
|
if !user_has_org_assoc(user.user_id, org, db.inner()).await.map_err(|e| (Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_QL_QUERY_FAILED", "an error occurred while running the graphql query", e)))? {
|
||||||
|
return Err((Status::Unauthorized, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{}\"}}]}}", "ERR_MISSING_ORG_AUTHORIZATION", "this user does not have permission to access this org")));
|
||||||
|
}
|
||||||
|
|
||||||
|
let roles = match get_role_ids_for_ca(org, db.inner()).await {
|
||||||
|
Ok(r) => r,
|
||||||
|
Err(e) => {
|
||||||
|
return Err((Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_INTERNAL_SERVER_ERR", "there was an error querying the db, please try again later", e)))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut resp = RolesResponse {
|
||||||
|
data: vec![]
|
||||||
|
};
|
||||||
|
|
||||||
|
for role in roles {
|
||||||
|
let role_info = match get_role(role, db.inner()).await {
|
||||||
|
Ok(r) => r,
|
||||||
|
Err(e) => {
|
||||||
|
return Err((Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_INTERNAL_SERVER_ERR", "there was an error querying the db, please try again later", e)))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if let Some(info) = role_info {
|
||||||
|
resp.data.push(info);
|
||||||
|
} else {
|
||||||
|
return Err((Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_INTERNAL_SERVER_ERR", "there was an error querying the db, please try again later", "missing role as returned by server - possibly deleted inbetween?")))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((ContentType::JSON, Json(resp)))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
#[serde(crate = "rocket::serde")]
|
||||||
|
pub struct RoleaddReq {
|
||||||
|
pub name: String,
|
||||||
|
pub description: String
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/v1/org/<org>/role", data = "<req>")]
|
||||||
|
pub async fn role_add(req: Json<RoleaddReq>, org: i32, user: TOTPAuthenticatedUserInfo, db: &State<PgPool>) -> Result<(ContentType, String), (Status, String)> {
|
||||||
|
if !user_has_org_assoc(user.user_id, org, db.inner()).await.map_err(|e| (Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_QL_QUERY_FAILED", "an error occurred while running the graphql query", e)))? {
|
||||||
|
return Err((Status::Unauthorized, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{}\"}}]}}", "ERR_MISSING_ORG_AUTHORIZATION", "this user does not have permission to access this org")));
|
||||||
|
}
|
||||||
|
|
||||||
|
match sqlx::query!("INSERT INTO roles (org, name, description) VALUES ($1, $2, $3)", org, req.name, req.description).execute(db.inner()).await {
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(e) => {
|
||||||
|
error!("[tfapi] dberror: {}", e);
|
||||||
|
return Err((Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_INTERNAL_SERVER_ERR", "database returned error while trying to create role", "unable to insert role")))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((ContentType::JSON, "{}".to_string()))
|
||||||
|
}
|
Loading…
Reference in New Issue