mirror of
https://github.com/netbirdio/netbird.git
synced 2026-10-21 12:59:08 +02:00
Address CodeRabbit review and fix CI on embedded-vnc
This commit is contained in:
@@ -863,6 +863,7 @@ func TestAddConfig_AllFieldsCovered(t *testing.T) {
|
|||||||
RosenpassPermissive: true,
|
RosenpassPermissive: true,
|
||||||
ServerSSHAllowed: &bTrue,
|
ServerSSHAllowed: &bTrue,
|
||||||
ServerVNCAllowed: &bTrue,
|
ServerVNCAllowed: &bTrue,
|
||||||
|
DisableVNCApproval: &bTrue,
|
||||||
EnableSSHRoot: &bTrue,
|
EnableSSHRoot: &bTrue,
|
||||||
EnableSSHSFTP: &bTrue,
|
EnableSSHSFTP: &bTrue,
|
||||||
EnableSSHLocalPortForwarding: &bTrue,
|
EnableSSHLocalPortForwarding: &bTrue,
|
||||||
|
|||||||
@@ -433,7 +433,7 @@ func (config *Config) apply(input ConfigInput) (updated bool, err error) {
|
|||||||
updated = true
|
updated = true
|
||||||
}
|
}
|
||||||
} else if config.ServerVNCAllowed == nil {
|
} else if config.ServerVNCAllowed == nil {
|
||||||
config.ServerVNCAllowed = util.True()
|
config.ServerVNCAllowed = util.False()
|
||||||
updated = true
|
updated = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ func (s *Server) StartCapture(req *proto.StartCaptureRequest, stream proto.Daemo
|
|||||||
return status.Errorf(codes.Internal, "create capture session: %v", err)
|
return status.Errorf(codes.Internal, "create capture session: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
engine, err := s.claimCapture(sess)
|
engine, err := s.claimCapture(sess, func() { pw.Close() })
|
||||||
if err != nil {
|
if err != nil {
|
||||||
sess.Stop()
|
sess.Stop()
|
||||||
pw.Close()
|
pw.Close()
|
||||||
@@ -305,7 +305,7 @@ func (s *Server) cleanupBundleCapture() {
|
|||||||
// capture is already running it is evicted: a previous streaming session
|
// capture is already running it is evicted: a previous streaming session
|
||||||
// whose gRPC client died and never freed the slot stays stuck otherwise,
|
// whose gRPC client died and never freed the slot stays stuck otherwise,
|
||||||
// and a bundle capture is just informational state.
|
// and a bundle capture is just informational state.
|
||||||
func (s *Server) claimCapture(sess *capture.Session) (*internal.Engine, error) {
|
func (s *Server) claimCapture(sess *capture.Session, cancel func()) (*internal.Engine, error) {
|
||||||
s.mutex.Lock()
|
s.mutex.Lock()
|
||||||
defer s.mutex.Unlock()
|
defer s.mutex.Unlock()
|
||||||
|
|
||||||
@@ -315,6 +315,7 @@ func (s *Server) claimCapture(sess *capture.Session) (*internal.Engine, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
s.activeCapture = sess
|
s.activeCapture = sess
|
||||||
|
s.activeCaptureCancel = cancel
|
||||||
return engine, nil
|
return engine, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -331,13 +332,18 @@ func (s *Server) evictActiveCaptureLocked() {
|
|||||||
}
|
}
|
||||||
log.Infof("evicting previous streaming capture to start a new one")
|
log.Infof("evicting previous streaming capture to start a new one")
|
||||||
prev := s.activeCapture
|
prev := s.activeCapture
|
||||||
|
cancel := s.activeCaptureCancel
|
||||||
if engine, err := s.getCaptureEngineLocked(); err == nil {
|
if engine, err := s.getCaptureEngineLocked(); err == nil {
|
||||||
if err := engine.SetCapture(nil); err != nil {
|
if err := engine.SetCapture(nil); err != nil {
|
||||||
log.Debugf("clear previous capture: %v", err)
|
log.Debugf("clear previous capture: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
s.activeCapture = nil
|
s.activeCapture = nil
|
||||||
|
s.activeCaptureCancel = nil
|
||||||
prev.Stop()
|
prev.Stop()
|
||||||
|
if cancel != nil {
|
||||||
|
cancel()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// releaseCapture clears the active-capture owner if it still matches sess.
|
// releaseCapture clears the active-capture owner if it still matches sess.
|
||||||
@@ -346,6 +352,7 @@ func (s *Server) releaseCapture(sess *capture.Session) {
|
|||||||
defer s.mutex.Unlock()
|
defer s.mutex.Unlock()
|
||||||
if s.activeCapture == sess {
|
if s.activeCapture == sess {
|
||||||
s.activeCapture = nil
|
s.activeCapture = nil
|
||||||
|
s.activeCaptureCancel = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -360,6 +367,7 @@ func (s *Server) clearCaptureIfOwner(sess *capture.Session, engine *internal.Eng
|
|||||||
log.Debugf("clear capture: %v", err)
|
log.Debugf("clear capture: %v", err)
|
||||||
}
|
}
|
||||||
s.activeCapture = nil
|
s.activeCapture = nil
|
||||||
|
s.activeCaptureCancel = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) getCaptureEngineLocked() (*internal.Engine, error) {
|
func (s *Server) getCaptureEngineLocked() (*internal.Engine, error) {
|
||||||
|
|||||||
@@ -93,8 +93,12 @@ type Server struct {
|
|||||||
captureEnabled bool
|
captureEnabled bool
|
||||||
bundleCapture *bundleCapture
|
bundleCapture *bundleCapture
|
||||||
// activeCapture is the session currently installed on the engine; guarded by s.mutex.
|
// activeCapture is the session currently installed on the engine; guarded by s.mutex.
|
||||||
activeCapture *capture.Session
|
activeCapture *capture.Session
|
||||||
networksDisabled bool
|
// activeCaptureCancel tears down the streaming pipe/cancel for the
|
||||||
|
// active streaming capture so eviction unblocks the StartCapture RPC
|
||||||
|
// handler. Nil for bundle captures (they own their own context).
|
||||||
|
activeCaptureCancel func()
|
||||||
|
networksDisabled bool
|
||||||
|
|
||||||
sleepHandler *sleephandler.SleepHandler
|
sleepHandler *sleephandler.SleepHandler
|
||||||
|
|
||||||
|
|||||||
@@ -113,15 +113,17 @@ func (s *serviceClient) showApprovalUI(req approvalRequest) {
|
|||||||
decide(outcome{})
|
decide(outcome{})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
updateCountdown()
|
fyne.Do(updateCountdown)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
o := <-decided
|
o := <-decided
|
||||||
s.sendApprovalResponse(req.requestID, o.accept, o.viewOnly)
|
s.sendApprovalResponse(req.requestID, o.accept, o.viewOnly)
|
||||||
w.Close()
|
fyne.Do(func() {
|
||||||
s.app.Quit()
|
w.Close()
|
||||||
|
s.app.Quit()
|
||||||
|
})
|
||||||
}()
|
}()
|
||||||
|
|
||||||
w.Show()
|
w.Show()
|
||||||
|
|||||||
@@ -47,8 +47,6 @@ var (
|
|||||||
procWTSEnumerateSessionsW = wtsapi32.NewProc("WTSEnumerateSessionsW")
|
procWTSEnumerateSessionsW = wtsapi32.NewProc("WTSEnumerateSessionsW")
|
||||||
procWTSFreeMemory = wtsapi32.NewProc("WTSFreeMemory")
|
procWTSFreeMemory = wtsapi32.NewProc("WTSFreeMemory")
|
||||||
procWTSQuerySessionInformation = wtsapi32.NewProc("WTSQuerySessionInformationW")
|
procWTSQuerySessionInformation = wtsapi32.NewProc("WTSQuerySessionInformationW")
|
||||||
|
|
||||||
iphlpapi = windows.NewLazySystemDLL("iphlpapi.dll")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetCurrentSessionID returns the session ID of the current process.
|
// GetCurrentSessionID returns the session ID of the current process.
|
||||||
@@ -514,6 +512,8 @@ func (m *sessionManager) reapExitedAgent() {
|
|||||||
log.Debugf("close agent handle: %v", err)
|
log.Debugf("close agent handle: %v", err)
|
||||||
}
|
}
|
||||||
m.agentProc = 0
|
m.agentProc = 0
|
||||||
|
m.authToken = ""
|
||||||
|
m.socketPath = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// scheduleNextSpawn applies an exponential backoff on fast crashes (<5s) and
|
// scheduleNextSpawn applies an exponential backoff on fast crashes (<5s) and
|
||||||
@@ -586,6 +586,8 @@ func (m *sessionManager) killAgent() {
|
|||||||
_ = windows.TerminateProcess(m.agentProc, 0)
|
_ = windows.TerminateProcess(m.agentProc, 0)
|
||||||
_ = windows.CloseHandle(m.agentProc)
|
_ = windows.CloseHandle(m.agentProc)
|
||||||
m.agentProc = 0
|
m.agentProc = 0
|
||||||
|
m.authToken = ""
|
||||||
|
m.socketPath = ""
|
||||||
log.Info("killed old agent")
|
log.Info("killed old agent")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,19 +1,10 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import "errors"
|
// interactiveUserError returns nil when a user is logged into the console
|
||||||
|
// (i.e. an Aqua session is active). At the loginwindow there is nobody to
|
||||||
// consoleHasInteractiveUser returns true when a user is logged into the
|
// display an approval prompt to, so callers can decline without waiting on
|
||||||
// console (i.e. an Aqua session is active). At the loginwindow there is
|
// the broker. Any error (including errNoConsoleUser) is treated as decline.
|
||||||
// nobody to display an approval prompt to, so callers can decline
|
func interactiveUserError() error {
|
||||||
// without waiting on the broker.
|
_, err := consoleUserID()
|
||||||
func consoleHasInteractiveUser() bool {
|
return err
|
||||||
if _, err := consoleUserID(); err != nil {
|
|
||||||
if errors.Is(err, errNoConsoleUser) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
// Unknown error: fail closed so a probe-time glitch does not
|
|
||||||
// silently let an unattended console accept VNC sessions.
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,6 @@
|
|||||||
|
|
||||||
package server
|
package server
|
||||||
|
|
||||||
// consoleHasInteractiveUser is unused outside service mode (darwin/windows)
|
// interactiveUserError is unused outside service mode (darwin/windows) but
|
||||||
// but the symbol must exist so gateApproval compiles on all platforms.
|
// the symbol must exist so gateApproval compiles on all platforms.
|
||||||
func consoleHasInteractiveUser() bool { return true }
|
func interactiveUserError() error { return nil }
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
// consoleHasInteractiveUser returns true when there is a logged-in user
|
// interactiveUserError returns nil when there is a logged-in user session
|
||||||
// session on the box. At the lock/login screen WTSQueryUserName is empty,
|
// on the box. At the lock/login screen WTSQueryUserName is empty, which
|
||||||
// which means there is nobody to display an approval prompt to. Callers
|
// means there is nobody to display an approval prompt to.
|
||||||
// should decline without waiting on the broker in that case.
|
func interactiveUserError() error {
|
||||||
func consoleHasInteractiveUser() bool {
|
|
||||||
sid := getActiveSessionID()
|
sid := getActiveSessionID()
|
||||||
if sid == 0 {
|
if sid == 0 {
|
||||||
return false
|
return errNoConsoleUser
|
||||||
}
|
}
|
||||||
return wtsSessionHasUser(sid)
|
if !wtsSessionHasUser(sid) {
|
||||||
|
return errNoConsoleUser
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -431,10 +431,12 @@ func (s *Server) gateApproval(conn net.Conn, header *connectionHeader, connLog *
|
|||||||
connLog.Warn("VNC connection rejected: approval required but no approver")
|
connLog.Warn("VNC connection rejected: approval required but no approver")
|
||||||
return false, ApprovalDecision{}
|
return false, ApprovalDecision{}
|
||||||
}
|
}
|
||||||
if s.serviceMode && !consoleHasInteractiveUser() {
|
if s.serviceMode {
|
||||||
rejectConnection(conn, codeMessage(RejectCodeNoConsoleUser, "no interactive user session"))
|
if err := interactiveUserError(); err != nil {
|
||||||
connLog.Info("VNC connection rejected: no interactive user session to approve")
|
rejectConnection(conn, codeMessage(RejectCodeNoConsoleUser, "no interactive user session"))
|
||||||
return false, ApprovalDecision{}
|
connLog.Infof("VNC connection rejected: no interactive user session to approve: %v", err)
|
||||||
|
return false, ApprovalDecision{}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
info := ApprovalInfo{
|
info := ApprovalInfo{
|
||||||
SourceIP: sourceIPString(conn.RemoteAddr()),
|
SourceIP: sourceIPString(conn.RemoteAddr()),
|
||||||
@@ -449,7 +451,7 @@ func (s *Server) gateApproval(conn net.Conn, header *connectionHeader, connLog *
|
|||||||
}
|
}
|
||||||
decision, err := s.approver.Request(s.ctx, info)
|
decision, err := s.approver.Request(s.ctx, info)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rejectConnection(conn, codeMessage(RejectCodeApprovalDenied, err.Error()))
|
rejectConnection(conn, codeMessage(RejectCodeApprovalDenied, "approval denied"))
|
||||||
connLog.Infof("VNC connection rejected: approval %v", err)
|
connLog.Infof("VNC connection rejected: approval %v", err)
|
||||||
return false, ApprovalDecision{}
|
return false, ApprovalDecision{}
|
||||||
}
|
}
|
||||||
@@ -737,9 +739,13 @@ func (s *Server) validateCapturer(capturer ScreenCapturer) error {
|
|||||||
func (s *Server) isAllowedSource(addr net.Addr) bool {
|
func (s *Server) isAllowedSource(addr net.Addr) bool {
|
||||||
// Unix-socket remotes (the agent path) are local IPC, gated by the
|
// Unix-socket remotes (the agent path) are local IPC, gated by the
|
||||||
// token, not by overlay membership.
|
// token, not by overlay membership.
|
||||||
|
if _, ok := addr.(*net.UnixAddr); ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
tcpAddr, ok := addr.(*net.TCPAddr)
|
tcpAddr, ok := addr.(*net.TCPAddr)
|
||||||
if !ok {
|
if !ok {
|
||||||
return true
|
s.log.Warnf("connection rejected: unsupported remote address type %T", addr)
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
remoteIP, ok := netip.AddrFromSlice(tcpAddr.IP)
|
remoteIP, ok := netip.AddrFromSlice(tcpAddr.IP)
|
||||||
|
|||||||
@@ -269,4 +269,3 @@ func (s *Server) serviceAcceptLoop() {
|
|||||||
}(conn)
|
}(conn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -545,6 +545,9 @@ func (s *session) handleFBUpdateRequest() error {
|
|||||||
// in sync with the active session (e.g. username changes after login on
|
// in sync with the active session (e.g. username changes after login on
|
||||||
// a virtual session).
|
// a virtual session).
|
||||||
func (s *session) SendDesktopName(name string) error {
|
func (s *session) SendDesktopName(name string) error {
|
||||||
|
if s.viewOnly {
|
||||||
|
name = ViewOnlyDesktopNamePrefix + name
|
||||||
|
}
|
||||||
s.encMu.RLock()
|
s.encMu.RLock()
|
||||||
supported := s.clientSupportsDesktopName
|
supported := s.clientSupportsDesktopName
|
||||||
s.encMu.RUnlock()
|
s.encMu.RUnlock()
|
||||||
@@ -629,13 +632,13 @@ func (s *session) handlePointerEvent() error {
|
|||||||
mask = (mask & 0x7f) | uint16(hi[0])<<7
|
mask = (mask & 0x7f) | uint16(hi[0])<<7
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if s.viewOnly {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
s.pointerMu.Lock()
|
s.pointerMu.Lock()
|
||||||
s.lastPointerX = x
|
s.lastPointerX = x
|
||||||
s.lastPointerY = y
|
s.lastPointerY = y
|
||||||
s.pointerMu.Unlock()
|
s.pointerMu.Unlock()
|
||||||
if s.viewOnly {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
s.injector.InjectPointer(mask, x, y, s.serverW, s.serverH)
|
s.injector.InjectPointer(mask, x, y, s.serverW, s.serverH)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -661,6 +664,9 @@ var stickyModifierKeysyms = [...]uint32{
|
|||||||
// when the client disconnects mid-press. Mouse coordinates are reused
|
// when the client disconnects mid-press. Mouse coordinates are reused
|
||||||
// from the last PointerEvent so we don't warp the cursor.
|
// from the last PointerEvent so we don't warp the cursor.
|
||||||
func (s *session) releaseStickyInput() {
|
func (s *session) releaseStickyInput() {
|
||||||
|
if s.viewOnly {
|
||||||
|
return
|
||||||
|
}
|
||||||
for _, ks := range stickyModifierKeysyms {
|
for _, ks := range stickyModifierKeysyms {
|
||||||
s.injector.InjectKey(ks, false)
|
s.injector.InjectKey(ks, false)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -475,6 +475,37 @@ func (h *Handler) CreateTemporaryAccess(w http.ResponseWriter, r *http.Request)
|
|||||||
newPeer := &nbpeer.Peer{}
|
newPeer := &nbpeer.Peer{}
|
||||||
newPeer.FromAPITemporaryAccessRequest(&req)
|
newPeer.FromAPITemporaryAccessRequest(&req)
|
||||||
|
|
||||||
|
parsedRules := make([]struct {
|
||||||
|
raw string
|
||||||
|
protocol types.PolicyRuleProtocolType
|
||||||
|
portRange types.RulePortRange
|
||||||
|
}, 0, len(req.Rules))
|
||||||
|
needsVNCKey := false
|
||||||
|
for _, rule := range req.Rules {
|
||||||
|
protocol, portRange, err := types.ParseRuleString(rule)
|
||||||
|
if err != nil {
|
||||||
|
util.WriteError(r.Context(), err, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if protocol == types.PolicyRuleProtocolNetbirdVNC {
|
||||||
|
needsVNCKey = true
|
||||||
|
}
|
||||||
|
parsedRules = append(parsedRules, struct {
|
||||||
|
raw string
|
||||||
|
protocol types.PolicyRuleProtocolType
|
||||||
|
portRange types.RulePortRange
|
||||||
|
}{rule, protocol, portRange})
|
||||||
|
}
|
||||||
|
|
||||||
|
var vncSessionPubKey string
|
||||||
|
if needsVNCKey {
|
||||||
|
vncSessionPubKey, err = validateVNCSessionPubKey(req.SessionPubKey)
|
||||||
|
if err != nil {
|
||||||
|
util.WriteError(r.Context(), err, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
targetPeer, err := h.accountManager.GetPeer(r.Context(), userAuth.AccountId, peerID, userAuth.UserId)
|
targetPeer, err := h.accountManager.GetPeer(r.Context(), userAuth.AccountId, peerID, userAuth.UserId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.WriteError(r.Context(), err, w)
|
util.WriteError(r.Context(), err, w)
|
||||||
@@ -487,12 +518,7 @@ func (h *Handler) CreateTemporaryAccess(w http.ResponseWriter, r *http.Request)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, rule := range req.Rules {
|
for _, pr := range parsedRules {
|
||||||
protocol, portRange, err := types.ParseRuleString(rule)
|
|
||||||
if err != nil {
|
|
||||||
util.WriteError(r.Context(), err, w)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
policy := &types.Policy{
|
policy := &types.Policy{
|
||||||
AccountID: userAuth.AccountId,
|
AccountID: userAuth.AccountId,
|
||||||
Description: "Temporary access policy for peer " + peer.Name,
|
Description: "Temporary access policy for peer " + peer.Name,
|
||||||
@@ -512,34 +538,28 @@ func (h *Handler) CreateTemporaryAccess(w http.ResponseWriter, r *http.Request)
|
|||||||
ID: targetPeer.ID,
|
ID: targetPeer.ID,
|
||||||
},
|
},
|
||||||
Bidirectional: false,
|
Bidirectional: false,
|
||||||
Protocol: protocol,
|
Protocol: pr.protocol,
|
||||||
PortRanges: []types.RulePortRange{portRange},
|
PortRanges: []types.RulePortRange{pr.portRange},
|
||||||
}},
|
}},
|
||||||
}
|
}
|
||||||
if protocol == types.PolicyRuleProtocolNetbirdSSH || protocol == types.PolicyRuleProtocolNetbirdVNC {
|
if pr.protocol == types.PolicyRuleProtocolNetbirdSSH || pr.protocol == types.PolicyRuleProtocolNetbirdVNC {
|
||||||
policy.Rules[0].AuthorizedUser = userAuth.UserId
|
policy.Rules[0].AuthorizedUser = userAuth.UserId
|
||||||
}
|
}
|
||||||
if protocol == types.PolicyRuleProtocolNetbirdVNC {
|
if pr.protocol == types.PolicyRuleProtocolNetbirdVNC {
|
||||||
pubKey, err := validateVNCSessionPubKey(req.SessionPubKey)
|
policy.Rules[0].SessionPubKey = vncSessionPubKey
|
||||||
if err != nil {
|
|
||||||
util.WriteError(r.Context(), err, w)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
policy.Rules[0].SessionPubKey = pubKey
|
|
||||||
policy.Rules[0].SessionDisplayName = h.displayNameForUser(r.Context(), userAuth)
|
policy.Rules[0].SessionDisplayName = h.displayNameForUser(r.Context(), userAuth)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = h.accountManager.SavePolicy(r.Context(), userAuth.AccountId, userAuth.UserId, policy, true)
|
if _, err = h.accountManager.SavePolicy(r.Context(), userAuth.AccountId, userAuth.UserId, policy, true); err != nil {
|
||||||
if err != nil {
|
|
||||||
util.WriteError(r.Context(), err, w)
|
util.WriteError(r.Context(), err, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
resp := &api.PeerTemporaryAccessResponse{
|
resp := &api.PeerTemporaryAccessResponse{
|
||||||
Id: peer.ID,
|
Id: peer.ID,
|
||||||
Name: peer.Name,
|
Name: peer.Name,
|
||||||
Rules: req.Rules,
|
Rules: req.Rules,
|
||||||
TargetPubKey: targetPeer.Key,
|
TargetPubKey: targetPeer.Key,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user