Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions internal/websecure/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,51 @@ func (s *CertStore) ValidateAndSaveCertificate(hostname string, cert string, key
return nil, nil
}

// GetCertificate returns the certificate for the given hostname
// returns nil if the certificate is not found
func (s *CertStore) GetCertificate(hostname string) *tls.Certificate {
s.certLock.Lock()
defer s.certLock.Unlock()

return s.certificates[hostname]
}

// ValidateAndSaveCertificate validates the certificate and saves it to the store
// returns are:
// - error: if the certificate is invalid or if there's any error during saving the certificate
// - error: if there's any warning or error during saving the certificate
func (s *CertStore) ValidateAndSaveCertificate(hostname string, cert string, key string, ignoreWarning bool) (error, error) {
tlsCert, err := tls.X509KeyPair([]byte(cert), []byte(key))
if err != nil {
return fmt.Errorf("Failed to parse certificate: %w", err), nil
}

// this can be skipped as current implementation supports one custom certificate only
if tlsCert.Leaf != nil {
// add recover to avoid panic
defer func() {
if r := recover(); r != nil {
s.log.Errorf("Failed to verify hostname: %v", r)
}
}()

if err = tlsCert.Leaf.VerifyHostname(hostname); err != nil {
if !ignoreWarning {
return nil, fmt.Errorf("Certificate does not match hostname: %w", err)
}
s.log.Warnf("Certificate does not match hostname: %v", err)
}
}

s.certLock.Lock()
s.certificates[hostname] = &tlsCert
s.certLock.Unlock()

s.saveCertificate(hostname)

return nil, nil
}

func (s *CertStore) saveCertificate(hostname string) {
// check if certificate already exists
tlsCert := s.certificates[hostname]
Expand Down
65 changes: 51 additions & 14 deletions ui/src/components/WebRTCVideo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
useMouseStore,
useRTCStore,
useSettingsStore,
useUiStore,
useVideoStore,
} from "@/hooks/stores";
import { keys, modifiers } from "@/keyboardMappings";
Expand All @@ -17,6 +18,7 @@ import MacroBar from "@/components/MacroBar";
import InfoBar from "@components/InfoBar";
import useKeyboard from "@/hooks/useKeyboard";
import { useJsonRpc } from "@/hooks/useJsonRpc";
import notifications from "../notifications";

import {
HDMIErrorOverlay,
Expand Down Expand Up @@ -61,6 +63,7 @@ export default function WebRTCVideo() {

// Misc states and hooks
const [blockWheelEvent, setBlockWheelEvent] = useState(false);
const disableVideoFocusTrap = useUiStore(state => state.disableVideoFocusTrap);
const [send] = useJsonRpc();

// Video-related
Expand Down Expand Up @@ -97,6 +100,40 @@ export default function WebRTCVideo() {
[setVideoClientSize, updateVideoSizeStore, setVideoSize],
);

const checkNavigatorPermissions = async (permissionName: string) => {
const name = permissionName as PermissionName;
const { state } = await navigator.permissions.query({ name });
return state === "granted";
}

const requestPointerLock = useCallback(async () => {
if (document.pointerLockElement) return;

const isPointerLockGranted = await checkNavigatorPermissions("pointer-lock");
if (isPointerLockGranted && settings.mouseMode === "relative") {
notifications.success("Pointer lock enabled, to exit it, press the escape key for a few seconds");
videoElm.current?.requestPointerLock();
}
}, [settings.mouseMode]);

const requestFullscreen = useCallback(async () => {
videoElm.current?.requestFullscreen({
navigationUI: "show",
});

// we do not care about pointer lock if it's for fullscreen
await requestPointerLock();

const isKeyboardLockGranted = await checkNavigatorPermissions("keyboard-lock");
if (isKeyboardLockGranted) {
if ('keyboard' in navigator) {
// @ts-ignore
await navigator.keyboard.lock();
}
}

}, [disableVideoFocusTrap]);

// Mouse-related
const calcDelta = (pos: number) => (Math.abs(pos) < 10 ? pos * 2 : pos);
const sendRelMouseMovement = useCallback(
Expand Down Expand Up @@ -525,6 +562,13 @@ export default function WebRTCVideo() {
const containerElm = containerRef.current;
if (!containerElm) return;


const videoElmRefValue = videoElm.current;
if (videoElmRefValue) containerElm.addEventListener("click", () => {
if (disableVideoFocusTrap) return;
requestPointerLock();
}, { signal });

containerElm.addEventListener("mousemove", relMouseMoveHandler, { signal });
containerElm.addEventListener("pointerdown", relMouseMoveHandler, { signal });
containerElm.addEventListener("pointerup", relMouseMoveHandler, { signal });
Expand All @@ -541,7 +585,7 @@ export default function WebRTCVideo() {
abortController.abort();
};
},
[settings.mouseMode, relMouseMoveHandler, mouseWheelHandler],
[settings.mouseMode, relMouseMoveHandler, mouseWheelHandler, requestPointerLock, disableVideoFocusTrap]
);

const hasNoAutoPlayPermissions = useMemo(() => {
Expand All @@ -554,19 +598,12 @@ export default function WebRTCVideo() {

return (
<div className="grid h-full w-full grid-rows-layout">
<div className="min-h-[39.5px] flex flex-col">
<div className="flex flex-col">
<fieldset disabled={peerConnection?.connectionState !== "connected"} className="contents">
<Actionbar
requestFullscreen={async () =>
videoElm.current?.requestFullscreen({
navigationUI: "show",
})
}
/>
<MacroBar />
</fieldset>
</div>
<div className="min-h-[39.5px]">
<fieldset disabled={peerConnection?.connectionState !== "connected"}>
<Actionbar
requestFullscreen={requestFullscreen}
/>
</fieldset>
</div>

<div
Expand Down