diff --git a/.env.example b/.env.example index 596de722..f03e8821 100644 --- a/.env.example +++ b/.env.example @@ -3,6 +3,9 @@ # Environment files are documented here: # https://vitejs.dev/guide/env-and-mode.html#env-files #### +# Develop backend settings: +LIVEKIT_KEY="devkey" +LIVEKIT_SECRET="secret" # Used for determining the homeserver to use for short urls etc. # VITE_FALLBACK_STUN_ALLOWED=false diff --git a/README.md b/README.md index cd4b65de..c137b212 100644 --- a/README.md +++ b/README.md @@ -74,6 +74,37 @@ You're now ready to launch the development server: yarn dev ``` +### Local Backend + +#### JWT keys + +Add in you `.env` in root dir with: + +```yaml +# Develop backend settings: +LIVEKIT_KEY="devkey" +LIVEKIT_SECRET="secret" +``` + +#### Add SFU parameter in your local config `./public/config.yml` + +```yaml +"livekit": { + "server_url": "ws://localhost:7880", + "jwt_service_url": "http:/localhost:8881" + }, +``` + +#### Run Backend + +``` +yarn backend +``` + +##### Services + +JWT Service: http://localhost:8881/token?name=Name&identity=1234&roomName=room + ## Configuration There are currently two different config files. `.env` holds variables that are used at build time, while `public/config.json` holds variables that are used at runtime. Documentation and default values for `public/config.json` can be found in [ConfigOptions.ts](src/config/ConfigOptions.ts). diff --git a/backend-docker-compose.yml b/backend-docker-compose.yml new file mode 100644 index 00000000..d575cf18 --- /dev/null +++ b/backend-docker-compose.yml @@ -0,0 +1,47 @@ +# LiveKit requires host networking, which is only available on Linux +# This compose will not function correctly on Mac or Windows +version: "3.9" + +networks: + lkbackend: + +services: + auth-service: + build: + context: ./backend/auth + container_name: auth-server + hostname: auth-server + ports: + - 8881:8080 + environment: + - LIVEKIT_KEY=${LIVEKIT_KEY} + - LIVEKIT_SECRET=${LIVEKIT_SECRET} + deploy: + restart_policy: + condition: on-failure + networks: + - lkbackend + + livekit: + image: livekit/livekit-server:latest + command: --dev --config /etc/livekit.yaml + restart: unless-stopped + ports: + - "7880:7880" + - "7881:7881" + - "7882:7882" + - "50100-50200:50100-50200" + volumes: + - ./backend/livekit.yaml:/etc/livekit.yaml + networks: + - lkbackend + + redis: + image: redis:6-alpine + command: redis-server /etc/redis.conf + ports: + - 6379:6379 + volumes: + - ./backend/redis.conf:/etc/redis.conf + networks: + - lkbackend diff --git a/backend/auth/Dockerfile b/backend/auth/Dockerfile new file mode 100644 index 00000000..c7bbd955 --- /dev/null +++ b/backend/auth/Dockerfile @@ -0,0 +1,15 @@ +FROM golang:1.20-alpine + +WORKDIR /app + +COPY go.mod ./ +COPY go.sum ./ +RUN go mod download + +COPY *.go ./ + +RUN go build -o /auth-server + +EXPOSE 8080 + +CMD [ "/auth-server" ] diff --git a/backend/auth/go.mod b/backend/auth/go.mod new file mode 100644 index 00000000..75d1a533 --- /dev/null +++ b/backend/auth/go.mod @@ -0,0 +1,20 @@ +module vector-auth-server + +go 1.20 + +require github.com/livekit/protocol v1.5.7 + +require ( + github.com/go-jose/go-jose/v3 v3.0.0 // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/twitchtv/twirp v8.1.3+incompatible // indirect + golang.org/x/crypto v0.9.0 // indirect + golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc // indirect + golang.org/x/net v0.10.0 // indirect + golang.org/x/sys v0.8.0 // indirect + golang.org/x/text v0.9.0 // indirect + google.golang.org/genproto v0.0.0-20230403163135-c38d8f061ccd // indirect + google.golang.org/grpc v1.55.0 // indirect + google.golang.org/protobuf v1.30.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/backend/auth/go.sum b/backend/auth/go.sum new file mode 100644 index 00000000..e8419741 --- /dev/null +++ b/backend/auth/go.sum @@ -0,0 +1,94 @@ +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/eapache/channels v1.1.0 h1:F1taHcn7/F0i8DYqKXJnyhJcVpp2kgFcNePxXtnyu4k= +github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= +github.com/frostbyte73/core v0.0.9 h1:AmE9GjgGpPsWk9ZkmY3HsYUs2hf2tZt+/W6r49URBQI= +github.com/gammazero/deque v0.2.1 h1:qSdsbG6pgp6nL7A0+K/B7s12mcCY/5l5SIUpMOl+dC0= +github.com/go-jose/go-jose/v3 v3.0.0 h1:s6rrhirfEP/CGIoc6p+PZAeogN2SxKav6Wp7+dyMWVo= +github.com/go-jose/go-jose/v3 v3.0.0/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8= +github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/jxskiss/base62 v1.1.0 h1:A5zbF8v8WXx2xixnAKD2w+abC+sIzYJX+nxmhA6HWFw= +github.com/lithammer/shortuuid/v4 v4.0.0 h1:QRbbVkfgNippHOS8PXDkti4NaWeyYfcBTHtw7k08o4c= +github.com/livekit/mageutil v0.0.0-20230125210925-54e8a70427c1 h1:jm09419p0lqTkDaKb5iXdynYrzB84ErPPO4LbRASk58= +github.com/livekit/protocol v1.5.7 h1:jZeFQEmLuIhFblXDGPRCBbfjVJHb+YU7AsO+SMoXF70= +github.com/livekit/protocol v1.5.7/go.mod h1:ZaOnsvP+JS4s7vI1UO+JVdBagvvLp/lBXDAl2hkDS0I= +github.com/livekit/psrpc v0.3.0 h1:giBZsfM3CWA0oIYXofsMITbVQtyW7u/ES9sQmVspHPM= +github.com/mackerelio/go-osstat v0.2.4 h1:qxGbdPkFo65PXOb/F/nhDKpF2nGmGaCFDLXoZjJTtUs= +github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= +github.com/nats-io/nats.go v1.25.0 h1:t5/wCPGciR7X3Mu8QOi4jiJaXaWM8qtkLu4lzGZvYHE= +github.com/nats-io/nkeys v0.4.4 h1:xvBJ8d69TznjcQl9t6//Q5xXuVhyYiSos6RPtvQNTwA= +github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= +github.com/pion/datachannel v1.5.5 h1:10ef4kwdjije+M9d7Xm9im2Y3O6A6ccQb0zcqZcJew8= +github.com/pion/dtls/v2 v2.2.6 h1:yXMxKr0Skd+Ub6A8UqXTRLSywskx93ooMRHsQUtd+Z4= +github.com/pion/ice/v2 v2.3.4 h1:tjYjTLpWyZzUjpDnzk6T1y3oQyhyY2DiM2t095iDhyQ= +github.com/pion/interceptor v0.1.16 h1:0GDZrfNO+BmVNWymS31fMlVtPO2IJVBzy2Qq5XCYMIg= +github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY= +github.com/pion/mdns v0.0.7 h1:P0UB4Sr6xDWEox0kTVxF0LmQihtCbSAdW0H2nEgkA3U= +github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA= +github.com/pion/rtcp v1.2.10 h1:nkr3uj+8Sp97zyItdN60tE/S6vk4al5CPRR6Gejsdjc= +github.com/pion/rtp v1.7.13 h1:qcHwlmtiI50t1XivvoawdCGTP4Uiypzfrsap+bijcoA= +github.com/pion/sctp v1.8.7 h1:JnABvFakZueGAn4KU/4PSKg+GWbF6QWbKTWZOSGJjXw= +github.com/pion/sdp/v3 v3.0.6 h1:WuDLhtuFUUVpTfus9ILC4HRyHsW6TdugjEX/QY9OiUw= +github.com/pion/srtp/v2 v2.0.14 h1:Glt0MqEvINrDxL+aanmK4DiFjvs+uN2iYc6XD/iKpoY= +github.com/pion/stun v0.5.2 h1:J/8glQnDV91dfk2+ZnGN0o9bUJgABhTNljwfQWByoXE= +github.com/pion/transport/v2 v2.2.0 h1:u5lFqFHkXLMXMzai8tixZDfVjb8eOjH35yCunhPeb1c= +github.com/pion/turn/v2 v2.1.0 h1:5wGHSgGhJhP/RpabkUb/T9PdsAjkGLS6toYz5HNzoSI= +github.com/pion/udp/v2 v2.0.1 h1:xP0z6WNux1zWEjhC7onRA3EwwSliXqu1ElUZAQhUP54= +github.com/pion/webrtc/v3 v3.2.4 h1:gWSx4dqQb77051qBT9ipDrOyP6/sGYcAQP3UPjM8pU8= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.15.1 h1:8tXpTmJbyH5lydzFPoxSIJ0J46jdh3tylbvM1xCv0LI= +github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= +github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM= +github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= +github.com/redis/go-redis/v9 v9.0.4 h1:FC82T+CHJ/Q/PdyLW++GeCO+Ol59Y4T7R4jbgjvktgc= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= +github.com/twitchtv/twirp v8.1.3+incompatible h1:+F4TdErPgSUbMZMwp13Q/KgDVuI7HJXP61mNV3/7iuU= +github.com/twitchtv/twirp v8.1.3+incompatible/go.mod h1:RRJoFSAmTEh2weEqWtpPE3vFK5YBhA6bqp2l1kfCC5A= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= +go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= +golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= +golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc h1:mCRnTeVUjcrhlRmO0VK8a6k6Rrf6TF9htwo2pJVSjIU= +golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/genproto v0.0.0-20230403163135-c38d8f061ccd h1:sLpv7bNL1AsX3fdnWh9WVh7ejIzXdOc1RRHGeAmeStU= +google.golang.org/genproto v0.0.0-20230403163135-c38d8f061ccd/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= +google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag= +google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/backend/auth/server.go b/backend/auth/server.go new file mode 100644 index 00000000..b3721e2e --- /dev/null +++ b/backend/auth/server.go @@ -0,0 +1,98 @@ +package main + +import ( + "encoding/json" + "log" + "net/http" + "os" + + "time" + + "github.com/livekit/protocol/auth" +) + +type Handler struct { + key, secret string +} + +func (h *Handler) handle(w http.ResponseWriter, r *http.Request) { + log.Printf("Request from %s", r.RemoteAddr) + + // Set the CORS headers + 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-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization") + + // Handle preflight request (CORS) + if r.Method == "OPTIONS" { + w.WriteHeader(http.StatusOK) + return + } + + roomName := r.URL.Query().Get("roomName") + name := r.URL.Query().Get("name") + identity := r.URL.Query().Get("identity") + + log.Printf("roomName: %s, name: %s, identity: %s", roomName, name, identity) + + if roomName == "" || name == "" || identity == "" { + w.WriteHeader(http.StatusBadRequest) + 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() { + key := os.Getenv("LIVEKIT_KEY") + secret := os.Getenv("LIVEKIT_SECRET") + + // Check if the key and secret are empty. + if key == "" || secret == "" { + log.Fatal("LIVEKIT_KEY and LIVEKIT_SECRET environment variables must be set") + } + + log.Printf("LIVEKIT_KEY: %s and LIVEKIT_SECRET %s", key, secret) + + handler := &Handler{ + key: key, + secret: secret, + } + + http.HandleFunc("/token", handler.handle) + log.Fatal(http.ListenAndServe(":8080", nil)) +} + +type Response struct { + Token string `json:"accessToken"` +} + +func getJoinToken(apiKey, apiSecret, room, identity, name string) (string, error) { + at := auth.NewAccessToken(apiKey, apiSecret) + + canPublish := true + canSubscribe := true + grant := &auth.VideoGrant{ + RoomJoin: true, + RoomCreate: true, + CanPublish: &canPublish, + CanSubscribe: &canSubscribe, + Room: room, + } + + at.AddGrant(grant). + SetIdentity(identity). + SetValidFor(time.Hour). + SetName(name) + + return at.ToJWT() +} diff --git a/backend/livekit.yaml b/backend/livekit.yaml new file mode 100644 index 00000000..da24c76b --- /dev/null +++ b/backend/livekit.yaml @@ -0,0 +1,26 @@ +port: 7880 +environment: dev +bind_addresses: + - "0.0.0.0" +rtc: + tcp_port: 7881 + port_range_start: 50100 + port_range_end: 50200 + use_external_ip: false +#redis: +# address: redis:6379 +# username: "" +# password: "" +# db: 0 +turn: + enabled: false + domain: localhost + cert_file: "" + key_file: "" + tls_port: 5349 + udp_port: 443 + external_tls: true +keys: + devkey: secret +signal_relay: + enabled: true diff --git a/backend/redis.conf b/backend/redis.conf new file mode 100644 index 00000000..32a39f7b --- /dev/null +++ b/backend/redis.conf @@ -0,0 +1,5 @@ +bind 0.0.0.0 +protected-mode yes +port 6379 +timeout 0 +tcp-keepalive 300 diff --git a/package.json b/package.json index 6f616f44..7d930352 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,8 @@ "lint:types": "tsc", "i18n": "node_modules/i18next-parser/bin/cli.js", "i18n:check": "node_modules/i18next-parser/bin/cli.js --fail-on-warnings --fail-on-update", - "test": "jest" + "test": "jest", + "backend": "docker-compose -f backend-docker-compose.yml up" }, "dependencies": { "@juggle/resize-observer": "^3.3.1",