diff --git a/src/store/account/types.ts b/src/store/account/types.ts
index aa0d007c..b2a71615 100644
--- a/src/store/account/types.ts
+++ b/src/store/account/types.ts
@@ -8,6 +8,9 @@ export interface Account {
jwt_groups_enabled: boolean;
groups_propagation_enabled: boolean;
jwt_groups_claim_name: string;
+ extra: {
+ peer_approval_enabled: boolean;
+ }
};
}
@@ -17,4 +20,5 @@ export interface FormAccount extends Account {
groups_propagation_enabled: boolean;
jwt_groups_claim_name: string;
peer_login_expiration_formatted: ExpiresInValue;
+ peer_approval_enabled: boolean;
}
\ No newline at end of file
diff --git a/src/store/peer/types.ts b/src/store/peer/types.ts
index af48b46f..3f26b3d6 100644
--- a/src/store/peer/types.ts
+++ b/src/store/peer/types.ts
@@ -16,7 +16,8 @@ export interface Peer {
dns_label: string,
last_login: string,
login_expired: boolean,
- login_expiration_enabled: boolean
+ login_expiration_enabled: boolean,
+ approval_required: boolean
}
export interface FormPeer extends Peer {
diff --git a/src/views/Activity.tsx b/src/views/Activity.tsx
index 3fc8af6d..15bedda6 100644
--- a/src/views/Activity.tsx
+++ b/src/views/Activity.tsx
@@ -338,6 +338,8 @@ export const Activity = () => {
case "peer.login.expiration.enable":
case "user.peer.login":
case "peer.login.expire":
+ case "peer.approve":
+ case "peer.approval.revoke":
return renderMultiRowSpan(event.meta.fqdn, event.meta.ip);
case "route.add":
case "route.delete":
@@ -387,6 +389,8 @@ export const Activity = () => {
case "account.setting.peer.login.expiration.enable":
case "account.setting.peer.login.expiration.disable":
case "account.setting.peer.login.expiration.update":
+ case "account.setting.peer.approval.enable":
+ case "account.setting.peer.approval.disable":
case "integration.create":
case "integration.update":
case "integration.delete":
diff --git a/src/views/Peers.tsx b/src/views/Peers.tsx
index b6ca5980..a9aebaa4 100644
--- a/src/views/Peers.tsx
+++ b/src/views/Peers.tsx
@@ -1,5 +1,5 @@
import React, { useEffect, useState } from "react";
-import { capitalize, formatOS, timeAgo } from "../utils/common";
+import {capitalize, formatOS, isLocalDev, isNetBirdHosted, timeAgo} from "../utils/common";
import { useDispatch, useSelector } from "react-redux";
import { RootState } from "typesafe-actions";
import { actions as peerActions } from "../store/peer";
@@ -79,6 +79,7 @@ export const Peers = () => {
(state: RootState) => state.peer.updateGroupsVisible
);
const users = useSelector((state: RootState) => state.user.data);
+ const account = useSelector((state: RootState) => state.account.data);
const [addPeerModalOpen, setAddPeerModalOpen] = useState(false);
const { oidcUser } = useOidcUser();
const [isAdmin, setIsAdmin] = useState(false);
@@ -100,6 +101,7 @@ export const Peers = () => {
const optionsOnOff = [
{ label: "Online", value: "on" },
{ label: "All", value: "all" },
+ // ...((isNetBirdHosted() || isLocalDev()) && account[0].settings.extra.peer_approval_enabled ? [{ label: "Needs approval", value: "approval" }] : []),
];
const transformDataTable = (d: Peer[]): PeerDataTable[] => {
@@ -291,6 +293,16 @@ export const Peers = () => {
);
}) as Peer[];
+ // switch (optionOnOff) {
+ // case "on":
+ // f = filter(f, (f: Peer) => f.connected);
+ // break;
+ // case "approval":
+ // f = filter(f, (f: Peer) => f.approval_required);
+ // break;
+ // default:
+ // break;
+ // }
if (optionOnOff === "on") {
f = filter(f, (f: Peer) => f.connected);
}
@@ -410,6 +422,36 @@ export const Peers = () => {
});
};
+ const showConfirmApprove = (record: PeerDataTable) => {
+ setPeerToAction(record);
+
+ let content = (
+
+ Are you sure you want to approve this peer?
+
+ );
+
+ let name = record ? record.name : "";
+ confirmModal.confirm({
+ icon: ,
+ title: Approve peer {name},
+ width: 600,
+ content: content,
+ onOk() {
+ record.approval_required = false
+ dispatch(
+ peerActions.updatePeer.request({
+ getAccessTokenSilently: getTokenSilently,
+ payload: record,
+ })
+ );
+ },
+ onCancel() {
+ setPeerToAction(null);
+ },
+ });
+ };
+
const showConfirmEnableSSH = (record: PeerDataTable) => {
confirmModal.confirm({
icon: ,
@@ -601,6 +643,19 @@ export const Peers = () => {
""
);
+ let approval = peer.approval_required ? (
+
+
+ needs approval
+
+
+ ) : (
+ ""
+ );
+
const userEmail = users?.find((u) => u.id === peer.user_id)?.email;
let expiry = !peer.login_expiration_enabled ? (
@@ -622,9 +677,9 @@ export const Peers = () => {
{status}
- {loginExpire}
+ {loginExpire}
>
);
}
@@ -643,11 +698,18 @@ export const Peers = () => {
{userEmail}
-
- {expiry} {loginExpire}
-
+
+ {expiry} {loginExpire}
+
);
};
diff --git a/src/views/Settings.tsx b/src/views/Settings.tsx
index c8dd1f83..efa9e98f 100644
--- a/src/views/Settings.tsx
+++ b/src/views/Settings.tsx
@@ -127,6 +127,8 @@ export const Settings = () => {
const [formPeerExpirationEnabled, setFormPeerExpirationEnabled] =
useState(true);
+ const [formPeerApprovalEnabled, setFormPeerApprovalEnabled] =
+ useState(false);
const [jwtGroupsEnabled, setJwtGroupsEnabled] = useState(true);
const [groupsPropagationEnabled, setGroupsPropagationEnabled] =
useState(true);
@@ -221,9 +223,11 @@ export const Settings = () => {
jwt_groups_enabled: account.settings.jwt_groups_enabled,
jwt_groups_claim_name: account.settings.jwt_groups_claim_name,
groups_propagation_enabled: account.settings.groups_propagation_enabled,
+ peer_approval_enabled: account.settings.extra ? account.settings.extra.peer_approval_enabled : false,
} as FormAccount;
setFormAccount(fAccount);
setFormPeerExpirationEnabled(fAccount.peer_login_expiration_enabled);
+ setFormPeerApprovalEnabled(fAccount.peer_approval_enabled);
setJwtGroupsEnabled(fAccount.jwt_groups_enabled);
setGroupsPropagationEnabled(fAccount.groups_propagation_enabled);
setJwtGroupsClaimName(fAccount.jwt_groups_claim_name);
@@ -394,6 +398,7 @@ export const Settings = () => {
updatedAccount.data.settings.jwt_groups_claim_name,
groups_propagation_enabled:
updatedAccount.data.settings.groups_propagation_enabled,
+ peer_approval_enabled: updatedAccount.data.settings.extra.peer_approval_enabled
} as FormAccount;
setFormAccount(fAccount);
} else if (updatedAccount.error) {
@@ -428,6 +433,7 @@ export const Settings = () => {
jwt_groups_enabled: jwtGroupsEnabled,
jwt_groups_claim_name: jwtGroupsClaimName,
groups_propagation_enabled: groupsPropagationEnabled,
+ peer_approval_enabled: formPeerApprovalEnabled,
});
})
.catch((errorInfo) => {
@@ -453,6 +459,9 @@ export const Settings = () => {
jwt_groups_enabled: jwtGroupsEnabled,
jwt_groups_claim_name: jwtGroupsClaimName,
groups_propagation_enabled: groupsPropagationEnabled,
+ extra: {
+ peer_approval_enabled: values.peer_approval_enabled
+ }
},
} as Account;
};
@@ -624,6 +633,59 @@ export const Settings = () => {
+ {(isNetBirdHosted() || isLocalDev()) &&
+
+
{
+ setFormPeerApprovalEnabled(checked);
+ }}
+ size="small"
+ checked={formPeerApprovalEnabled}
+ />
+
+
+
+ Require peers to be approved by an administrator
+
+
+
+ }