Initial support for getting SFO config using OIDC
* Change `jwt_service_url` to `livekit_service_url` * Make it a POST so we can send the openID token sensibly * Get an OIDC token & pass it with the request * Read the SFU URL from there too and convert the auth server accordingly, althugh with no actual OIDC support yet, it just issues tokens blindly just as before and ignores the openid token completely. We'll need to update configs & the JWT service before merging this.
This commit is contained in:
parent
2a819b95e2
commit
8996aa772c
5 changed files with 74 additions and 70 deletions
|
@ -15,41 +15,74 @@ type Handler struct {
|
||||||
key, secret string
|
key, secret string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type OpenIDTokenType struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
type SFURequest struct {
|
||||||
|
Room string `json:"room"`
|
||||||
|
OpenIDToken OpenIDTokenType `json:"openid_token"`
|
||||||
|
DeviceID string `json:"device_id"`
|
||||||
|
RemoveMeUserID string `json:"remove_me_user_id"` // we'll get this from OIDC
|
||||||
|
}
|
||||||
|
|
||||||
|
type SFUResponse struct {
|
||||||
|
URL string `json:"url"`
|
||||||
|
JWT string `json:"jwt"`
|
||||||
|
}
|
||||||
|
|
||||||
func (h *Handler) handle(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) handle(w http.ResponseWriter, r *http.Request) {
|
||||||
log.Printf("Request from %s", r.RemoteAddr)
|
log.Printf("Request from %s", r.RemoteAddr)
|
||||||
|
|
||||||
// Set the CORS headers
|
// Set the CORS headers
|
||||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||||
w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
|
w.Header().Set("Access-Control-Allow-Methods", "POST")
|
||||||
w.Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization")
|
w.Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token")
|
||||||
|
|
||||||
// Handle preflight request (CORS)
|
// Handle preflight request (CORS)
|
||||||
if r.Method == "OPTIONS" {
|
if r.Method == "OPTIONS" {
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
return
|
return
|
||||||
|
} else if r.Method == "POST" {
|
||||||
|
var body SFURequest
|
||||||
|
err := json.NewDecoder(r.Body).Decode(&body)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error decoding JSON: %v", err)
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if body.Room == "" {
|
||||||
|
log.Printf("Request missing room")
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
token, err := getJoinToken(h.key, h.secret, body.Room, body.RemoveMeUserID+":"+body.DeviceID)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
res := SFUResponse{URL: "http://localhost:7880/", JWT: token}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
json.NewEncoder(w).Encode(res)
|
||||||
|
} else {
|
||||||
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||||
}
|
}
|
||||||
|
|
||||||
roomName := r.URL.Query().Get("roomName")
|
/*
|
||||||
name := r.URL.Query().Get("name")
|
roomName := r.URL.Query().Get("roomName")
|
||||||
identity := r.URL.Query().Get("identity")
|
name := r.URL.Query().Get("name")
|
||||||
|
identity := r.URL.Query().Get("identity")
|
||||||
|
|
||||||
log.Printf("roomName: %s, name: %s, identity: %s", roomName, name, identity)
|
log.Printf("roomName: %s, name: %s, identity: %s", roomName, name, identity)
|
||||||
|
|
||||||
if roomName == "" || name == "" || identity == "" {
|
if roomName == "" || name == "" || identity == "" {
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
token, err := getJoinToken(h.key, h.secret, roomName, identity, name)
|
|
||||||
if err != nil {
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
res := Response{token}
|
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
json.NewEncoder(w).Encode(res)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
@ -68,15 +101,11 @@ func main() {
|
||||||
secret: secret,
|
secret: secret,
|
||||||
}
|
}
|
||||||
|
|
||||||
http.HandleFunc("/token", handler.handle)
|
http.HandleFunc("/sfu/get", handler.handle)
|
||||||
log.Fatal(http.ListenAndServe(":8080", nil))
|
log.Fatal(http.ListenAndServe(":8080", nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
type Response struct {
|
func getJoinToken(apiKey, apiSecret, room, identity string) (string, error) {
|
||||||
Token string `json:"accessToken"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func getJoinToken(apiKey, apiSecret, room, identity, name string) (string, error) {
|
|
||||||
at := auth.NewAccessToken(apiKey, apiSecret)
|
at := auth.NewAccessToken(apiKey, apiSecret)
|
||||||
|
|
||||||
canPublish := true
|
canPublish := true
|
||||||
|
@ -91,8 +120,7 @@ func getJoinToken(apiKey, apiSecret, room, identity, name string) (string, error
|
||||||
|
|
||||||
at.AddGrant(grant).
|
at.AddGrant(grant).
|
||||||
SetIdentity(identity).
|
SetIdentity(identity).
|
||||||
SetValidFor(time.Hour).
|
SetValidFor(time.Hour)
|
||||||
SetName(name)
|
|
||||||
|
|
||||||
return at.ToJWT()
|
return at.ToJWT()
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,10 +55,8 @@ export interface ConfigOptions {
|
||||||
|
|
||||||
// Describes the LiveKit configuration to be used.
|
// Describes the LiveKit configuration to be used.
|
||||||
livekit?: {
|
livekit?: {
|
||||||
// The LiveKit server URL to connect to.
|
// The link to the service that returns a livekit url and token to use it
|
||||||
server_url: string;
|
livekit_service_url: string;
|
||||||
// The link to the service that generates JWT tokens to join LiveKit rooms.
|
|
||||||
jwt_service_url: string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import { Room, RoomOptions } from "livekit-client";
|
import { Room, RoomOptions } from "livekit-client";
|
||||||
import { useLiveKitRoom, useToken } from "@livekit/components-react";
|
import { useLiveKitRoom } from "@livekit/components-react";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
import { defaultLiveKitOptions } from "./options";
|
import { defaultLiveKitOptions } from "./options";
|
||||||
|
import { SFUConfig } from "./openIDSFU";
|
||||||
|
|
||||||
export type UserChoices = {
|
export type UserChoices = {
|
||||||
audio?: DeviceChoices;
|
audio?: DeviceChoices;
|
||||||
|
@ -14,29 +15,10 @@ export type DeviceChoices = {
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type LiveKitConfig = {
|
|
||||||
sfuUrl: string;
|
|
||||||
jwtUrl: string;
|
|
||||||
roomName: string;
|
|
||||||
userDisplayName: string;
|
|
||||||
userIdentity: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function useLiveKit(
|
export function useLiveKit(
|
||||||
userChoices: UserChoices,
|
userChoices: UserChoices,
|
||||||
config: LiveKitConfig
|
sfuConfig: SFUConfig
|
||||||
): Room | undefined {
|
): Room | undefined {
|
||||||
const tokenOptions = React.useMemo(
|
|
||||||
() => ({
|
|
||||||
userInfo: {
|
|
||||||
name: config.userDisplayName,
|
|
||||||
identity: config.userIdentity,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
[config.userDisplayName, config.userIdentity]
|
|
||||||
);
|
|
||||||
const token = useToken(config.jwtUrl, config.roomName, tokenOptions);
|
|
||||||
|
|
||||||
const roomOptions = React.useMemo((): RoomOptions => {
|
const roomOptions = React.useMemo((): RoomOptions => {
|
||||||
const options = defaultLiveKitOptions;
|
const options = defaultLiveKitOptions;
|
||||||
options.videoCaptureDefaults = {
|
options.videoCaptureDefaults = {
|
||||||
|
@ -51,8 +33,8 @@ export function useLiveKit(
|
||||||
}, [userChoices.video, userChoices.audio]);
|
}, [userChoices.video, userChoices.audio]);
|
||||||
|
|
||||||
const { room } = useLiveKitRoom({
|
const { room } = useLiveKitRoom({
|
||||||
token,
|
token: sfuConfig.jwt,
|
||||||
serverUrl: config.sfuUrl,
|
serverUrl: sfuConfig.url,
|
||||||
audio: userChoices.audio?.enabled ?? false,
|
audio: userChoices.audio?.enabled ?? false,
|
||||||
video: userChoices.video?.enabled ?? false,
|
video: userChoices.video?.enabled ?? false,
|
||||||
options: roomOptions,
|
options: roomOptions,
|
||||||
|
|
|
@ -28,7 +28,6 @@ import { useGroupCall } from "./useGroupCall";
|
||||||
import { ErrorView, FullScreenView } from "../FullScreenView";
|
import { ErrorView, FullScreenView } from "../FullScreenView";
|
||||||
import { LobbyView } from "./LobbyView";
|
import { LobbyView } from "./LobbyView";
|
||||||
import { MatrixInfo } from "./VideoPreview";
|
import { MatrixInfo } from "./VideoPreview";
|
||||||
import { ActiveCall } from "./InCallView";
|
|
||||||
import { CallEndedView } from "./CallEndedView";
|
import { CallEndedView } from "./CallEndedView";
|
||||||
import { useSentryGroupCallHandler } from "./useSentryGroupCallHandler";
|
import { useSentryGroupCallHandler } from "./useSentryGroupCallHandler";
|
||||||
import { PosthogAnalytics } from "../analytics/PosthogAnalytics";
|
import { PosthogAnalytics } from "../analytics/PosthogAnalytics";
|
||||||
|
@ -36,6 +35,7 @@ import { useProfile } from "../profile/useProfile";
|
||||||
import { UserChoices } from "../livekit/useLiveKit";
|
import { UserChoices } from "../livekit/useLiveKit";
|
||||||
import { findDeviceByName } from "../media-utils";
|
import { findDeviceByName } from "../media-utils";
|
||||||
import { useRoomAvatar } from "./useRoomAvatar";
|
import { useRoomAvatar } from "./useRoomAvatar";
|
||||||
|
import { OpenIDLoader } from "../livekit/OpenIDLoader";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
|
@ -225,9 +225,10 @@ export function GroupCallView({
|
||||||
return <ErrorView error={error} />;
|
return <ErrorView error={error} />;
|
||||||
} else if (state === GroupCallState.Entered && userChoices) {
|
} else if (state === GroupCallState.Entered && userChoices) {
|
||||||
return (
|
return (
|
||||||
<ActiveCall
|
<OpenIDLoader
|
||||||
groupCall={groupCall}
|
|
||||||
client={client}
|
client={client}
|
||||||
|
roomName={matrixInfo.roomName}
|
||||||
|
groupCall={groupCall}
|
||||||
participants={participants}
|
participants={participants}
|
||||||
onLeave={onLeave}
|
onLeave={onLeave}
|
||||||
unencryptedEventsFromUsers={unencryptedEventsFromUsers}
|
unencryptedEventsFromUsers={unencryptedEventsFromUsers}
|
||||||
|
|
|
@ -70,7 +70,6 @@ import { MatrixInfo } from "./VideoPreview";
|
||||||
import { useJoinRule } from "./useJoinRule";
|
import { useJoinRule } from "./useJoinRule";
|
||||||
import { ParticipantInfo } from "./useGroupCall";
|
import { ParticipantInfo } from "./useGroupCall";
|
||||||
import { ItemData, TileContent } from "../video-grid/VideoTile";
|
import { ItemData, TileContent } from "../video-grid/VideoTile";
|
||||||
import { Config } from "../config/Config";
|
|
||||||
import { NewVideoGrid } from "../video-grid/NewVideoGrid";
|
import { NewVideoGrid } from "../video-grid/NewVideoGrid";
|
||||||
import { OTelGroupCallMembership } from "../otel/OTelGroupCallMembership";
|
import { OTelGroupCallMembership } from "../otel/OTelGroupCallMembership";
|
||||||
import { SettingsModal } from "../settings/SettingsModal";
|
import { SettingsModal } from "../settings/SettingsModal";
|
||||||
|
@ -80,6 +79,7 @@ import { RageshakeRequestModal } from "./RageshakeRequestModal";
|
||||||
import { VideoTile } from "../video-grid/VideoTile";
|
import { VideoTile } from "../video-grid/VideoTile";
|
||||||
import { UserChoices, useLiveKit } from "../livekit/useLiveKit";
|
import { UserChoices, useLiveKit } from "../livekit/useLiveKit";
|
||||||
import { useMediaDevices } from "../livekit/useMediaDevices";
|
import { useMediaDevices } from "../livekit/useMediaDevices";
|
||||||
|
import { SFUConfig } from "../livekit/openIDSFU";
|
||||||
|
|
||||||
const canScreenshare = "getDisplayMedia" in (navigator.mediaDevices ?? {});
|
const canScreenshare = "getDisplayMedia" in (navigator.mediaDevices ?? {});
|
||||||
// There is currently a bug in Safari our our code with cloning and sending MediaStreams
|
// There is currently a bug in Safari our our code with cloning and sending MediaStreams
|
||||||
|
@ -87,23 +87,18 @@ const canScreenshare = "getDisplayMedia" in (navigator.mediaDevices ?? {});
|
||||||
// For now we can disable screensharing in Safari.
|
// For now we can disable screensharing in Safari.
|
||||||
const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
|
const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
|
||||||
|
|
||||||
interface ActiveCallProps extends Omit<Props, "livekitRoom"> {
|
export interface ActiveCallProps extends Omit<InCallViewProps, "livekitRoom"> {
|
||||||
userChoices: UserChoices;
|
userChoices: UserChoices;
|
||||||
|
sfuConfig: SFUConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ActiveCall(props: ActiveCallProps) {
|
export function ActiveCall(props: ActiveCallProps) {
|
||||||
const livekitRoom = useLiveKit(props.userChoices, {
|
const livekitRoom = useLiveKit(props.userChoices, props.sfuConfig);
|
||||||
sfuUrl: Config.get().livekit!.server_url,
|
|
||||||
jwtUrl: `${Config.get().livekit!.jwt_service_url}/token`,
|
|
||||||
roomName: props.matrixInfo.roomName,
|
|
||||||
userDisplayName: props.matrixInfo.displayName,
|
|
||||||
userIdentity: `${props.client.getUserId()}:${props.client.getDeviceId()}`,
|
|
||||||
});
|
|
||||||
|
|
||||||
return livekitRoom && <InCallView {...props} livekitRoom={livekitRoom} />;
|
return livekitRoom && <InCallView {...props} livekitRoom={livekitRoom} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Props {
|
export interface InCallViewProps {
|
||||||
client: MatrixClient;
|
client: MatrixClient;
|
||||||
groupCall: GroupCall;
|
groupCall: GroupCall;
|
||||||
livekitRoom: Room;
|
livekitRoom: Room;
|
||||||
|
@ -125,7 +120,7 @@ export function InCallView({
|
||||||
hideHeader,
|
hideHeader,
|
||||||
matrixInfo,
|
matrixInfo,
|
||||||
otelGroupCallMembership,
|
otelGroupCallMembership,
|
||||||
}: Props) {
|
}: InCallViewProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
usePreventScroll();
|
usePreventScroll();
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue