diff --git a/tfweb/src/components/AdminBar.svelte b/tfweb/src/components/AdminBar.svelte new file mode 100644 index 0000000..11c3651 --- /dev/null +++ b/tfweb/src/components/AdminBar.svelte @@ -0,0 +1,12 @@ + + +
+ + + + +
\ No newline at end of file diff --git a/tfweb/src/lib/api/models/Actor.ts b/tfweb/src/lib/api/models/Actor.ts index 1059f8e..4f901aa 100644 --- a/tfweb/src/lib/api/models/Actor.ts +++ b/tfweb/src/lib/api/models/Actor.ts @@ -12,43 +12,43 @@ * Do not edit the class manually. */ +import type {ActorAPIKey} from './ActorAPIKey'; import { - ActorAPIKey, instanceOfActorAPIKey, ActorAPIKeyFromJSON, ActorAPIKeyFromJSONTyped, ActorAPIKeyToJSON, } from './ActorAPIKey'; +import type {ActorHost} from './ActorHost'; import { - ActorHost, instanceOfActorHost, ActorHostFromJSON, ActorHostFromJSONTyped, ActorHostToJSON, } from './ActorHost'; +import type {ActorOIDCUser} from './ActorOIDCUser'; import { - ActorOIDCUser, instanceOfActorOIDCUser, ActorOIDCUserFromJSON, ActorOIDCUserFromJSONTyped, ActorOIDCUserToJSON, } from './ActorOIDCUser'; +import type {ActorSupport} from './ActorSupport'; import { - ActorSupport, instanceOfActorSupport, ActorSupportFromJSON, ActorSupportFromJSONTyped, ActorSupportToJSON, } from './ActorSupport'; +import type {ActorSystem} from './ActorSystem'; import { - ActorSystem, instanceOfActorSystem, ActorSystemFromJSON, ActorSystemFromJSONTyped, ActorSystemToJSON, } from './ActorSystem'; +import type {ActorUser} from './ActorUser'; import { - ActorUser, instanceOfActorUser, ActorUserFromJSON, ActorUserFromJSONTyped, diff --git a/tfweb/src/lib/auth.ts b/tfweb/src/lib/auth.ts index ab9a690..dd28ef3 100644 --- a/tfweb/src/lib/auth.ts +++ b/tfweb/src/lib/auth.ts @@ -1,7 +1,7 @@ import {Logger, logSetup} from "$lib/logger"; import {PUBLIC_BASE_URL} from "$env/static/public"; -export enum AuthResult { +export enum APIResult { Failed = 0, Successful = 1 } @@ -22,12 +22,12 @@ export interface APIError { logSetup(); const logger = new Logger("auth.ts"); -export async function isAuthedSession(): Promise<[AuthResult, SessionInfo | APIError]> { +export async function isAuthedSession(): Promise<[APIResult, SessionInfo | APIError]> { logger.info('Checking for session authentication'); if (window.localStorage.getItem("session") === null) { logger.error('unable to check session: session token not set'); - return [AuthResult.Failed, {code: "missing_token", message: "not logged in"}]; + return [APIResult.Failed, {code: "missing_token", message: "not logged in"}]; } try { @@ -41,27 +41,27 @@ export async function isAuthedSession(): Promise<[AuthResult, SessionInfo | APIE if (!resp.ok) { const rawerror = JSON.parse(await resp.text()).errors[0]; logger.error(`error fetching user information: ${rawerror.message}`); - return [AuthResult.Failed, {code: rawerror.code, message: rawerror.message}]; + return [APIResult.Failed, {code: rawerror.code, message: rawerror.message}]; } - return [AuthResult.Successful, JSON.parse(await resp.text()).data.actor] + return [APIResult.Successful, JSON.parse(await resp.text()).data.actor] } catch (e) { logger.error(`error making API request: ${e}`); - return [AuthResult.Failed, {code: "api_call_failed", message: `${e}`}] + return [APIResult.Failed, {code: "api_call_failed", message: `${e}`}] } } -export async function isAuthedMFA(): Promise<[AuthResult, SessionInfo | APIError]> { +export async function isAuthedMFA(): Promise<[APIResult, SessionInfo | APIError]> { logger.info('Checking for MFA authentication'); if (window.localStorage.getItem("mfa") === null) { logger.error('unable to check mfa: mfa token not set'); - return [AuthResult.Failed, {code: "missing_token", message: "not logged in"}]; + return [APIResult.Failed, {code: "missing_token", message: "not logged in"}]; } const sess = await isAuthedSession(); - if (sess[0] !== AuthResult.Successful) { + if (sess[0] !== APIResult.Successful) { logger.error('unable to check mfa: session token invalid'); - return [AuthResult.Failed, sess[1]]; + return [APIResult.Failed, sess[1]]; } try { @@ -75,16 +75,16 @@ export async function isAuthedMFA(): Promise<[AuthResult, SessionInfo | APIError if (!resp.ok) { const rawerror = JSON.parse(await resp.text()).errors[0]; logger.error(`error fetching user information: ${rawerror.message}`); - return [AuthResult.Failed, {code: rawerror.code, message: rawerror.message}]; + return [APIResult.Failed, {code: rawerror.code, message: rawerror.message}]; } - return [AuthResult.Successful, JSON.parse(await resp.text()).data.actor] + return [APIResult.Successful, JSON.parse(await resp.text()).data.actor] } catch (e) { logger.error(`error making API request: ${e}`); - return [AuthResult.Failed, {code: "api_call_failed", message: `${e}`}] + return [APIResult.Failed, {code: "api_call_failed", message: `${e}`}] } } -export async function authSession(email: string): Promise<[AuthResult, null | APIError]> { +export async function authSession(email: string): Promise<[APIResult, null | APIError]> { logger.info('Sending new session authentication'); try { @@ -101,16 +101,16 @@ export async function authSession(email: string): Promise<[AuthResult, null | AP if (!resp.ok) { const rawerror = JSON.parse(await resp.text()).errors[0]; logger.error(`error sending authentication: ${rawerror.message}`); - return [AuthResult.Failed, {code: rawerror.code, message: rawerror.message}]; + return [APIResult.Failed, {code: rawerror.code, message: rawerror.message}]; } - return [AuthResult.Successful, null] + return [APIResult.Successful, null] } catch (e) { logger.error(`error making API request: ${e}`); - return [AuthResult.Failed, {code: "api_call_failed", message: `${e}`}] + return [APIResult.Failed, {code: "api_call_failed", message: `${e}`}] } } -export async function signup(email: string): Promise<[AuthResult, null | APIError]> { +export async function signup(email: string): Promise<[APIResult, null | APIError]> { logger.info('sending signup'); try { @@ -127,16 +127,16 @@ export async function signup(email: string): Promise<[AuthResult, null | APIErro if (!resp.ok) { const rawerror = JSON.parse(await resp.text()).errors[0]; logger.error(`error sending authentication: ${rawerror.message}`); - return [AuthResult.Failed, {code: rawerror.code, message: rawerror.message}]; + return [APIResult.Failed, {code: rawerror.code, message: rawerror.message}]; } - return [AuthResult.Successful, null] + return [APIResult.Successful, null] } catch (e) { logger.error(`error making API request: ${e}`); - return [AuthResult.Failed, {code: "api_call_failed", message: `${e}`}] + return [APIResult.Failed, {code: "api_call_failed", message: `${e}`}] } } -export async function verifyLink(ml: string): Promise<[AuthResult, null | APIError]> { +export async function verifyLink(ml: string): Promise<[APIResult, null | APIError]> { logger.info('checking magic link authentication'); try { @@ -153,15 +153,15 @@ export async function verifyLink(ml: string): Promise<[AuthResult, null | APIErr if (!resp.ok) { const rawerror = JSON.parse(await resp.text()).errors[0]; logger.error(`error sending authentication: ${rawerror.message}`); - return [AuthResult.Failed, {code: rawerror.code, message: rawerror.message}]; + return [APIResult.Failed, {code: rawerror.code, message: rawerror.message}]; } window.localStorage.setItem("session", JSON.parse(await resp.text()).data.sessionToken); - return [AuthResult.Successful, null] + return [APIResult.Successful, null] } catch (e) { logger.error(`error making API request: ${e}`); - return [AuthResult.Failed, {code: "api_call_failed", message: `${e}`}] + return [APIResult.Failed, {code: "api_call_failed", message: `${e}`}] } } @@ -171,7 +171,7 @@ export interface TOTPCreateInfo { url: string } -export async function createTotp(token: string): Promise<[AuthResult, TOTPCreateInfo | APIError]> { +export async function createTotp(token: string): Promise<[APIResult, TOTPCreateInfo | APIError]> { logger.info('creating totp authenticator'); try { @@ -188,21 +188,21 @@ export async function createTotp(token: string): Promise<[AuthResult, TOTPCreate logger.error('call returned error code'); const rawerror = JSON.parse(await resp.text()).errors[0]; logger.error(`error sending totp create: ${rawerror.message}`); - return [AuthResult.Failed, {code: rawerror.code, message: rawerror.message}]; + return [APIResult.Failed, {code: rawerror.code, message: rawerror.message}]; } - return [AuthResult.Successful, JSON.parse(await resp.text()).data] + return [APIResult.Successful, JSON.parse(await resp.text()).data] } catch (e) { logger.error(`error making API request: ${e}`); - return [AuthResult.Failed, {code: "api_call_failed", message: `${e}`}] + return [APIResult.Failed, {code: "api_call_failed", message: `${e}`}] } } -export async function verifyTotp(token: string, totpToken: string, totpCode: string): Promise<[AuthResult, undefined | APIError]> { +export async function verifyTotp(token: string, totpToken: string, totpCode: string): Promise<[APIResult, undefined | APIError]> { logger.info('verifying totp authenticator'); try { logger.debug(`api call: baseurl ${PUBLIC_BASE_URL}`); - const resp = await fetch(`${PUBLIC_BASE_URL}/v1/totp-authenticators`, { + const resp = await fetch(`${PUBLIC_BASE_URL}/v1/verify-totp-authenticators`, { 'method': 'POST', 'body': JSON.stringify({ totpToken: totpToken, @@ -217,14 +217,45 @@ export async function verifyTotp(token: string, totpToken: string, totpCode: str logger.error('call returned error code'); const rawerror = JSON.parse(await resp.text()).errors[0]; logger.error(`error sending totp create: ${rawerror.message}`); - return [AuthResult.Failed, {code: rawerror.code, message: rawerror.message}]; + return [APIResult.Failed, {code: rawerror.code, message: rawerror.message}]; } window.localStorage.setItem("mfa", JSON.parse(await resp.text()).data.authToken); - return [AuthResult.Successful, undefined] + return [APIResult.Successful, undefined] } catch (e) { logger.error(`error making API request: ${e}`); - return [AuthResult.Failed, {code: "api_call_failed", message: `${e}`}] + return [APIResult.Failed, {code: "api_call_failed", message: `${e}`}] + } +} + +export async function authTotp(token: string, totpCode: string): Promise<[APIResult, undefined | APIError]> { + logger.info('authorizing totp authenticator'); + + try { + logger.debug(`api call: baseurl ${PUBLIC_BASE_URL}`); + const resp = await fetch(`${PUBLIC_BASE_URL}/v1/auth/totp`, { + 'method': 'POST', + 'body': JSON.stringify({ + code: `${totpCode}` + }), + 'headers': { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}` + } + }); + if (!resp.ok) { + logger.error('call returned error code'); + const rawerror = JSON.parse(await resp.text()).errors[0]; + logger.error(`error sending totp create: ${rawerror.message}`); + return [APIResult.Failed, {code: rawerror.code, message: rawerror.message}]; + } + + window.localStorage.setItem("mfa", JSON.parse(await resp.text()).data.authToken); + + return [APIResult.Successful, undefined] + } catch (e) { + logger.error(`error making API request: ${e}`); + return [APIResult.Failed, {code: "api_call_failed", message: `${e}`}] } } diff --git a/tfweb/src/lib/i18n/locales/en.json b/tfweb/src/lib/i18n/locales/en.json index 6a19098..f648fad 100644 --- a/tfweb/src/lib/i18n/locales/en.json +++ b/tfweb/src/lib/i18n/locales/en.json @@ -63,6 +63,50 @@ "label": "TOTP Code", "button": "Verify" }, + "networkcreate": { + "title": "Create your network", + "explain": "This defines what IP addresses will be assigned to your devices. The range you enter below must fall within either the RFC 1918 Private Address Space or the RFC 6598 Shared Address Space. Enter your network via CIDR notation.", + "label": "Network range", + "button": "Create network", + "valid": "Valid - {numIps} addresses from {start} to {end}", + "invalid": "Invalid", + "error": { + "generic": "Unable to create network: {err}", + "api": "Unable to contact the server. Try again later." + } + }, + "roles": { + "create": "Add", + "explain": "Roles control how hosts, lighthouses, and relays communicate through firewall rules.", + "noroles": "You don't have any roles. You'll need to add at least one before you can add any hosts.", + "add": { + "any": "Any", + "name": "Role name", + "desc": "Role description", + "button": "Create role", + "cancel": "Cancel", + "rules": "Inbound firewall rules", + "rulesexplainer": "Inbound traffic is denied by default. Add rules to allow traffic from hosts belonging to specific roles.", + "rulescols": { + "description": "Description", + "protocol": "Protocol", + "portrange": "Port range", + "allowedrole": "Allowed role" + }, + "ruleremove": "Remove rule", + "ruleedit": "Edit rule", + "rulesadd": "Add rule", + "editrule": { + "protocol": "Protocol", + "range": "Port or port range", + "role": "Allowed role", + "desc": "Description", + "add": "Add rule", + "edit": "Edit rule", + "cancel": "Cancel" + } + } + }, "common": { "title": "{title} | Trifid Web UI", "page": { @@ -71,7 +115,10 @@ "login": "Login", "ml": "Verify Magic Link", "2fasetup": "Configure TOTP", - "2fa": "2-Factor Authentication" + "2fa": "2-Factor Authentication", + "networkcreate": "Create Network", + "hosts": "Hosts", + "roles": "Roles" } } } diff --git a/tfweb/src/lib/netcreate.ts b/tfweb/src/lib/netcreate.ts new file mode 100644 index 0000000..974751f --- /dev/null +++ b/tfweb/src/lib/netcreate.ts @@ -0,0 +1,42 @@ +import {APIResult} from "$lib/auth"; +import type {APIError} from "$lib/auth"; +import {Logger, logSetup} from "$lib/logger"; +import {PUBLIC_BASE_URL} from "$env/static/public"; + +logSetup(); +const logger = new Logger("netcreate.ts"); + +export interface OrgCreateInfo { + organization: string, + ca: string, + network: string +} + +export async function createNetwork(token: string, cidr: string): Promise<[APIResult, OrgCreateInfo | APIError]> { + logger.info('creating network'); + + try { + logger.debug(`api call: baseurl ${PUBLIC_BASE_URL}`); + const resp = await fetch(`${PUBLIC_BASE_URL}/v1/organization`, { + 'method': 'POST', + 'body': JSON.stringify({ + cidr: `${cidr}` + }), + 'headers': { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}` + } + }); + if (!resp.ok) { + logger.error('call returned error code'); + const rawerror = JSON.parse(await resp.text()).errors[0]; + logger.error(`error sending org create: ${rawerror.message}`); + return [APIResult.Failed, {code: rawerror.code, message: rawerror.message}]; + } + + return [APIResult.Successful, await resp.json()] + } catch (e) { + logger.error(`error making API request: ${e}`); + return [APIResult.Failed, {code: "api_call_failed", message: `${e}`}] + } +} diff --git a/tfweb/src/routes/2fa/+page.svelte b/tfweb/src/routes/2fa/+page.svelte index 2de8eac..a526020 100644 --- a/tfweb/src/routes/2fa/+page.svelte +++ b/tfweb/src/routes/2fa/+page.svelte @@ -2,7 +2,7 @@ import {isLoading, t} from "svelte-i18n"; import LoadingWrapper from "$components/LoadingWrapper.svelte"; import {onMount} from "svelte"; - import {AuthResult, authSession, isAuthedSession} from "$lib/auth.ts"; + import {APIResult, authSession, authTotp, isAuthedSession, verifyTotp} from "$lib/auth.ts"; import type {SessionInfo} from "$lib/auth.ts"; import type {APIError} from "$lib/auth.ts"; import {Logger, logSetup} from "$lib/logger"; @@ -17,7 +17,7 @@ onMount(async () => { let session_load_info = await isAuthedSession(); - if (session_load_info[0] == AuthResult.Failed) { + if (session_load_info[0] == APIResult.Failed) { logger.error(`session load fail, the user is not already logged in`); window.location.href = '/login'; return; @@ -39,6 +39,29 @@ async function onSubmit() { loading = true; + let create_res = await authTotp(window.localStorage.getItem("session"), mfacode); + + if (create_res[0] == APIResult.Failed) { + logger.error(`totp auth fail`); + isError = true; + let err = create_res[1] as APIError; + + let etext = err.code; + + if (etext === "api_call_failed") { + etext = $t('2fasetup.error.api'); + } else if (etext === "ERR_USER_NO_TOTP") { + window.location.href = '/2fasetup'; + return; + } + + error = $t('2fasetup.error.generic', {values:{err:etext}}); + loading = false; + return; + } + + window.location.href = '/admin'; + loading = false; } @@ -52,7 +75,7 @@

{$t('2fa.subtitle')}

- + {#if hasErrForm}

{errForm}

diff --git a/tfweb/src/routes/2fasetup/+page.svelte b/tfweb/src/routes/2fasetup/+page.svelte index 15dfa59..4a33a28 100644 --- a/tfweb/src/routes/2fasetup/+page.svelte +++ b/tfweb/src/routes/2fasetup/+page.svelte @@ -2,7 +2,7 @@ import {isLoading, t} from "svelte-i18n"; import LoadingWrapper from "$components/LoadingWrapper.svelte"; import {onMount} from "svelte"; - import {AuthResult, authSession, createTotp, isAuthedSession, verifyTotp} from "$lib/auth.ts"; + import {APIResult, authSession, createTotp, isAuthedSession, verifyTotp} from "$lib/auth.ts"; import type {TOTPCreateInfo} from "$lib/auth.ts"; import type {SessionInfo} from "$lib/auth.ts"; import type {APIError} from "$lib/auth.ts"; @@ -26,7 +26,7 @@ onMount(async () => { let session_load_info = await isAuthedSession(); - if (session_load_info[0] == AuthResult.Failed) { + if (session_load_info[0] == APIResult.Failed) { logger.error(`session load fail, the user is not already logged in`); window.location.href = '/login'; return; @@ -40,7 +40,7 @@ let create_res = await createTotp(window.localStorage.getItem("session")); - if (create_res[0] == AuthResult.Failed) { + if (create_res[0] == APIResult.Failed) { logger.error(`totp create fail`); isError = true; let err = create_res[1] as APIError; @@ -74,7 +74,7 @@ async function onSubmit() { let create_res = await verifyTotp(window.localStorage.getItem("session"), totp_create_token, code); - if (create_res[0] == AuthResult.Failed) { + if (create_res[0] == APIResult.Failed) { logger.error(`totp auth fail`); isError = true; let err = create_res[1] as APIError; @@ -113,7 +113,7 @@
{totp_secret}
- +
diff --git a/tfweb/src/routes/admin/+page.svelte b/tfweb/src/routes/admin/+page.svelte index ac02147..9cc188e 100644 --- a/tfweb/src/routes/admin/+page.svelte +++ b/tfweb/src/routes/admin/+page.svelte @@ -2,9 +2,11 @@ import {isLoading, t} from "svelte-i18n"; import LoadingWrapper from "$components/LoadingWrapper.svelte"; import {onMount} from "svelte"; - import {AuthResult, isAuthedMFA, isAuthedSession} from "$lib/auth.ts"; + import {APIResult, isAuthedMFA, isAuthedSession} from "$lib/auth.ts"; import {Logger, logSetup} from "$lib/logger"; import type {APIError} from "$lib/auth.ts"; + import {PUBLIC_BASE_URL} from "$env/static/public"; + import {Configuration, NetworksApi} from "$lib/api"; let loading = true; let isError = false; @@ -16,7 +18,7 @@ onMount(async () => { let session_load_info = await isAuthedSession(); - if (session_load_info[0] == AuthResult.Failed) { + if (session_load_info[0] == APIResult.Failed) { let err = session_load_info[1] as APIError; logger.error(`session load failed: ${err.code} ${err.message}`); window.location.href = '/login'; @@ -24,13 +26,43 @@ } let mfa_load_info = await isAuthedMFA(); - if (mfa_load_info[0] == AuthResult.Failed) { + if (mfa_load_info[0] == APIResult.Failed) { let err = mfa_load_info[1] as APIError; logger.error(`mfa load failed: ${err.code} ${err.message}`); window.location.href = '/2fa'; return; } + // pull networks + const confgiuration = new Configuration({ + basePath: PUBLIC_BASE_URL, + accessToken: window.localStorage.getItem("session") + " " + window.localStorage.getItem("mfa") + }); + const networksApi = new NetworksApi(confgiuration); + let networks; + try { + networks = await networksApi.networksList(); + } catch (e) { + let resp_json = await e.response.json(); + if (resp_json.errors[0].code == "ERR_NO_ORG") { + window.location.href = "/networkcreate"; + return; + } else { + isError = true; + error = $t("networkcreate.error.generic", {values:{err:resp_json.errors[0].code}}); + loading = false; + return; + } + } + console.log(networks); + + if (networks.data?.length == 0) { + window.location.href = '/networkcreate'; + return; + } + + window.location.href = '/hosts'; + loading = false; }) diff --git a/tfweb/src/routes/hosts/+page.svelte b/tfweb/src/routes/hosts/+page.svelte new file mode 100644 index 0000000..df8df94 --- /dev/null +++ b/tfweb/src/routes/hosts/+page.svelte @@ -0,0 +1,77 @@ + + + + {$t("common.title", {values: {title: $t("common.page.hosts")}})} + + + + +

its not loading anymore

+
diff --git a/tfweb/src/routes/login/+page.svelte b/tfweb/src/routes/login/+page.svelte index e98cb30..e0c1225 100644 --- a/tfweb/src/routes/login/+page.svelte +++ b/tfweb/src/routes/login/+page.svelte @@ -2,7 +2,7 @@ import {isLoading, t} from "svelte-i18n"; import LoadingWrapper from "$components/LoadingWrapper.svelte"; import {onMount} from "svelte"; - import {AuthResult, authSession, isAuthedSession} from "$lib/auth.ts"; + import {APIResult, authSession, isAuthedSession} from "$lib/auth.ts"; import type {APIError} from "$lib/auth.ts"; import {Logger, logSetup} from "$lib/logger"; @@ -16,7 +16,7 @@ onMount(async () => { let session_load_info = await isAuthedSession(); - if (session_load_info[0] != AuthResult.Failed) { + if (session_load_info[0] != APIResult.Failed) { logger.error(`session load success, the user is already logged in`); window.location.href = '/2fa'; return; @@ -45,7 +45,7 @@ let auth_result = await authSession(email); - if (auth_result[0] === AuthResult.Failed) { + if (auth_result[0] === APIResult.Failed) { hasErrForm = true; diff --git a/tfweb/src/routes/magic-link/+page.svelte b/tfweb/src/routes/magic-link/+page.svelte index 4477ed7..3ece4bb 100644 --- a/tfweb/src/routes/magic-link/+page.svelte +++ b/tfweb/src/routes/magic-link/+page.svelte @@ -2,7 +2,7 @@ import {isLoading, t} from "svelte-i18n"; import LoadingWrapper from "$components/LoadingWrapper.svelte"; import {onMount} from "svelte"; - import {AuthResult, verifyLink} from "$lib/auth.ts"; + import {APIResult, verifyLink} from "$lib/auth.ts"; import type {APIError} from "$lib/auth.ts"; import {Logger, logSetup} from "$lib/logger"; @@ -24,7 +24,7 @@ let call_result = await verifyLink(url.get("magicLinkToken")); - if (call_result[0] !== AuthResult.Successful) { + if (call_result[0] !== APIResult.Successful) { let err = (call_result[1] as APIError).code; if (err === "ERR_UNAUTHORIZED" || err === "ERR_EXPIRED") { diff --git a/tfweb/src/routes/networkcreate/+page.svelte b/tfweb/src/routes/networkcreate/+page.svelte new file mode 100644 index 0000000..4a7fded --- /dev/null +++ b/tfweb/src/routes/networkcreate/+page.svelte @@ -0,0 +1,183 @@ + + + + {$t("common.title", {values: {title: $t("common.page.networkcreate")}})} + + + +

{$t("networkcreate.title")}

+

{$t("networkcreate.explain")}

+
+ + + + {sub} +
+
diff --git a/tfweb/src/routes/roles/+page.svelte b/tfweb/src/routes/roles/+page.svelte new file mode 100644 index 0000000..063f42b --- /dev/null +++ b/tfweb/src/routes/roles/+page.svelte @@ -0,0 +1,97 @@ + + + + {$t("common.title", {values: {title: $t("common.page.roles")}})} + + + + + +

{$t("common.page.roles")}

+ + +

{$t("roles.explain")}

+ + {#if (roles.data.length === 0)} +

{$t("roles.noroles")}

+ + {/if} +
diff --git a/tfweb/src/routes/roles/add/+page.svelte b/tfweb/src/routes/roles/add/+page.svelte new file mode 100644 index 0000000..9766224 --- /dev/null +++ b/tfweb/src/routes/roles/add/+page.svelte @@ -0,0 +1,296 @@ + + + + {$t("common.title", {values: {title: $t("common.page.roles")}})} + + + + + +
+ + + + +

{$t("roles.add.rules")}

+

{$t("roles.add.rulesexplainer")}

+ + + + + + + + + + {#each rules as rule} + + + + + + + + + {/each} +
{$t("roles.add.rulescols.description")}{$t("roles.add.rulescols.protocol")}{$t("roles.add.rulescols.portrange")}{$t("roles.add.rulescols.allowedrole")}
{rule.description}{protoToStringName(rule.protocol)}{prettyPortRange(rule.portRange)}{findRole(rule.allowedRole)}
+ + + + {#if isEditingRule} + + + + + + + + + + + +
+ {/if} + +
+ + + + +
diff --git a/tfweb/src/routes/signup/+page.svelte b/tfweb/src/routes/signup/+page.svelte index 32f52fb..1031dbc 100644 --- a/tfweb/src/routes/signup/+page.svelte +++ b/tfweb/src/routes/signup/+page.svelte @@ -2,7 +2,7 @@ import {isLoading, t} from "svelte-i18n"; import LoadingWrapper from "$components/LoadingWrapper.svelte"; import {onMount} from "svelte"; - import {AuthResult, authSession, isAuthedSession, signup} from "$lib/auth.ts"; + import {APIResult, authSession, isAuthedSession, signup} from "$lib/auth.ts"; import type {APIError} from "$lib/auth.ts"; import {Logger, logSetup} from "$lib/logger"; @@ -16,7 +16,7 @@ onMount(async () => { let session_load_info = await isAuthedSession(); - if (session_load_info[0] != AuthResult.Failed) { + if (session_load_info[0] != APIResult.Failed) { logger.error(`session load success, the user is already logged in`); window.location.href = '/2fa'; return; @@ -45,7 +45,7 @@ let auth_result = await signup(email); - if (auth_result[0] === AuthResult.Failed) { + if (auth_result[0] === APIResult.Failed) { hasErrForm = true; diff --git a/trifid-api/src/codegen/mod.rs b/trifid-api/src/codegen/mod.rs index 8489316..101a046 100644 --- a/trifid-api/src/codegen/mod.rs +++ b/trifid-api/src/codegen/mod.rs @@ -30,7 +30,6 @@ use trifid_pki::cert::{ pub struct CodegenRequiredInfo { pub host: host::Model, pub host_static_addresses: HashMap>, - pub host_config_overrides: Vec, pub network: network::Model, pub organization: organization::Model, pub dh_pubkey: Vec, @@ -40,6 +39,7 @@ pub struct CodegenRequiredInfo { pub lighthouse_ips: Vec, pub blocked_hosts: Vec, pub firewall_rules: Vec, + pub config_overrides: Vec<(String, String)> } pub async fn generate_config( @@ -196,6 +196,28 @@ pub async fn generate_config( local_range: None, }; + // Merge with config overrides and re-parse + let config_str = serde_yaml::to_string(&nebula_config)?; + let mut value: serde_yaml::Value = serde_yaml::from_str(&config_str)?; + + for (key, kv_value) in &info.config_overrides { + // split up the key + // a.b.c.d = ['a']['b']['c']['d'] = value + let key_split = key.split('.'); + + let mut current_val = &mut value; + + for key_iter in key_split { + current_val = current_val.get_mut(key_iter).ok_or("Invalid key-value override")?; + } + + *current_val = serde_yaml::from_str(kv_value)?; + } + + let config_str_merged = serde_yaml::to_string(&value)?; + + let nebula_config = serde_yaml::from_str(&config_str_merged)?; + Ok((nebula_config, cert)) } @@ -315,9 +337,15 @@ pub async fn collect_info<'a>( let best_ca = best_ca.unwrap(); + // pull our host's config overrides + let config_overrides = host_config_overrides.iter().map(|u| { + (u.key.clone(), u.value.clone()) + }).collect(); + + // pull our role's firewall rules let firewall_rules = trifid_api_entities::entity::firewall_rule::Entity::find() - .filter(firewall_rule::Column::Role.eq(&host.id)) + .filter(firewall_rule::Column::Role.eq(&host.role)) .all(&db.conn) .await?; let firewall_rules = firewall_rules @@ -349,7 +377,6 @@ pub async fn collect_info<'a>( Ok(CodegenRequiredInfo { host, host_static_addresses: host_x_static_addresses, - host_config_overrides, network, organization: org, dh_pubkey: dh_pubkey.to_vec(), @@ -359,5 +386,6 @@ pub async fn collect_info<'a>( lighthouse_ips: lighthouses, blocked_hosts, firewall_rules, + config_overrides }) }