Skip to content
Draft
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
1 change: 1 addition & 0 deletions autocomplete/fish_autocomplete
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ complete -c lk -n '__fish_lk_no_subcommand' -f -l curl -d 'Print curl commands f
complete -c lk -n '__fish_lk_no_subcommand' -f -l verbose
complete -c lk -n '__fish_lk_no_subcommand' -f -l yes -s y -d 'Assume yes for confirmations; fail or use default for other prompts (use in CI/non-interactive)'
complete -c lk -n '__fish_lk_no_subcommand' -f -l quiet -s q -s silent -d 'Suppress informational output to stderr (warnings and errors still print)'
complete -c lk -n '__fish_lk_no_subcommand' -f -l experimental-auth -d 'EXPERIMENTAL: use user-based (session) auth against the LiveKit Public API instead of API-key auth. Most commands are not yet supported under this mode.'
complete -c lk -n '__fish_lk_no_subcommand' -f -l help -s h -d 'show help'
complete -c lk -n '__fish_lk_no_subcommand' -f -l version -s v -d 'print the version'
complete -c lk -n '__fish_lk_no_subcommand' -xa '(lk --generate-shell-completion 2>/dev/null)'
Expand Down
5 changes: 4 additions & 1 deletion cmd/lk/app.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2024 LiveKit, Inc.
// Copyright 2024-2026 LiveKit, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -133,6 +133,9 @@ func requireProject(ctx context.Context, cmd *cli.Command) (context.Context, err
}

func requireProjectWithOpts(ctx context.Context, cmd *cli.Command, opts ...loadOption) (context.Context, error) {
if err := experimentalAuthGate(cmd); err != nil {
return ctx, err
}
if project != nil {
// already resolved (and announced) earlier in this command
return ctx, nil
Expand Down
92 changes: 92 additions & 0 deletions cmd/lk/experimental_auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// Copyright 2026 LiveKit, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
"errors"
"fmt"

"github.com/urfave/cli/v3"

"github.com/livekit/livekit-cli/v2/pkg/config"
"github.com/livekit/livekit-cli/v2/pkg/public"
)

// experimentalAuthEnabled reports whether the user opted into user-based
// (session) auth via the global --experimental-auth flag.
func experimentalAuthEnabled(cmd *cli.Command) bool {
return cmd.Bool("experimental-auth")
}

// experimentalAuthGate refuses a command that only supports API-key auth when
// the user requested experimental user-based auth. User auth routes through the
// Public API, which does not yet implement most operations; rather than
// silently fall back to API-key auth — a different security model than the user
// asked for — we fail clearly. Command paths that DO support user auth branch on
// experimentalAuthEnabled before reaching this gate.
func experimentalAuthGate(cmd *cli.Command) error {
if experimentalAuthEnabled(cmd) {
return errors.New("this command is not yet available under --experimental-auth (user-based auth); re-run without it to use API-key authentication")
}
return nil
}

// requireUserSession loads the CLI config and resolves the default user with a
// valid (unexpired) session, for commands running under --experimental-auth.
// The returned *CLIConfig is the same instance the user was read from, so
// callers may cache data on it (e.g. via SetUserProjects) and persist.
func requireUserSession(cmd *cli.Command) (*config.CLIConfig, *config.UserConfig, error) {
conf, err := config.LoadOrCreate()
if err != nil {
return nil, nil, err
}
if conf.DefaultUser == "" {
return nil, nil, errors.New("no user is signed in (run `lk cloud auth` to sign in)")
}
user := conf.GetUser(conf.DefaultUser)
if user == nil {
return nil, nil, fmt.Errorf("default user %q not found in config", conf.DefaultUser)
}
if !user.SessionValid() {
return nil, nil, fmt.Errorf("session for %s has expired (run `lk cloud auth` to sign in again)", userLabel(user))
}
return conf, user, nil
}

// newCloudAPIClient builds a Public API client authenticated as the default
// user, honoring --experimental-api-url.
func newCloudAPIClient(cmd *cli.Command) (*public.Client, *config.CLIConfig, *config.UserConfig, error) {
conf, user, err := requireUserSession(cmd)
if err != nil {
return nil, nil, nil, err
}
client, err := public.New(experimentalAPIURL, user.SessionToken)
if err != nil {
return nil, nil, nil, err
}
return client, conf, user, nil
}

// userLabel is a human-friendly identifier for a user, preferring email.
func userLabel(u *config.UserConfig) string {
switch {
case u.Email != "":
return u.Email
case u.Name != "":
return u.Name
default:
return u.Id
}
}
47 changes: 46 additions & 1 deletion cmd/lk/project.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2022-2024 LiveKit, Inc.
// Copyright 2022-2026 LiveKit, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand All @@ -17,6 +17,7 @@ package main
import (
"context"
"errors"
"fmt"
"net/url"
"regexp"

Expand All @@ -26,6 +27,7 @@ import (
"github.com/urfave/cli/v3"

"github.com/livekit/livekit-cli/v2/pkg/config"
"github.com/livekit/livekit-cli/v2/pkg/public"
"github.com/livekit/livekit-cli/v2/pkg/util"
)

Expand Down Expand Up @@ -269,6 +271,10 @@ func addProject(ctx context.Context, cmd *cli.Command) error {
}

func listProjects(ctx context.Context, cmd *cli.Command) error {
if experimentalAuthEnabled(cmd) {
return listUserProjects(ctx, cmd)
}

if len(cliConfig.Projects) == 0 {
out.Status("No projects configured, use `lk cloud auth` to authenticate a new project.")
return nil
Expand Down Expand Up @@ -308,6 +314,45 @@ func listProjects(ctx context.Context, cmd *cli.Command) error {
return nil
}

// listUserProjects lists the projects accessible to the signed-in user via the
// Public API (user-based auth). Invoked by `lk project list --experimental-auth`.
//
// NOTE: it fetches live on each call. The per-user project cache
// (config.UserConfig.Projects) exists for project *resolution* by scoped
// commands and is populated there; a read-only list stays quiet and does not
// persist config.
func listUserProjects(ctx context.Context, cmd *cli.Command) error {
client, _, _, err := newCloudAPIClient(cmd)
if err != nil {
return err
}

projects, err := client.ListProjects(ctx)
if err != nil {
if public.IsUnauthenticated(err) {
return fmt.Errorf("%w (run `lk cloud auth` to sign in again)", err)
}
return err
}

if cmd.Bool("json") {
util.PrintJSON(projects)
return nil
}

if len(projects) == 0 {
out.Status("No projects found for this account.")
return nil
}

table := util.CreateTable().Headers("Project ID")
for _, p := range projects {
table.Row(p.ID)
}
out.Result(table)
return nil
}

func removeProject(ctx context.Context, cmd *cli.Command) error {
if cmd.NArg() == 0 {
_ = cli.ShowSubcommandHelp(cmd)
Expand Down
33 changes: 27 additions & 6 deletions cmd/lk/utils.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2021-2024 LiveKit, Inc.
// Copyright 2021-2026 LiveKit, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -38,14 +38,20 @@ import (
const (
cloudAPIServerURL = "https://cloud-api.livekit.io"
cloudDashboardURL = "https://cloud.livekit.io"
// publicAPIBaseURL is the production base URL of the user-authenticated
// LiveKit Public API (the OpenAPI REST service). Used only under
// --experimental-auth; override with --experimental-api-url for dev
// (e.g. http://localhost:8000/v1).
publicAPIBaseURL = "https://api.livekit.cloud/v1"
)

var (
printCurl bool
workingDir string = "."
tomlFilename string = config.LiveKitTOMLFile
serverURL string = cloudAPIServerURL
dashboardURL string = cloudDashboardURL
printCurl bool
workingDir string = "."
tomlFilename string = config.LiveKitTOMLFile
serverURL string = cloudAPIServerURL
dashboardURL string = cloudDashboardURL
experimentalAPIURL string = publicAPIBaseURL

roomFlag = &TemplateStringFlag{
Name: "room",
Expand Down Expand Up @@ -152,6 +158,18 @@ var (
Usage: "Assume yes for confirmations; fail or use default for other prompts (use in CI/non-interactive)",
},
quietFlag,
&cli.BoolFlag{
Name: "experimental-auth",
Usage: "EXPERIMENTAL: use user-based (session) auth against the LiveKit Public API instead of API-key auth. Most commands are not yet supported under this mode.",
},
&cli.StringFlag{
Name: "experimental-api-url",
Usage: "Base `URL` of the LiveKit Public API used with --experimental-auth",
Value: publicAPIBaseURL,
Destination: &experimentalAPIURL,
Sources: cli.EnvVars("LIVEKIT_API_URL"),
Hidden: true,
},
&cli.StringFlag{
Name: "server-url",
Value: cloudAPIServerURL,
Expand Down Expand Up @@ -424,6 +442,9 @@ func resolveProject(c *cli.Command, p loadParams) (*resolvedProject, error) {
// the package-level `project` (app/agent) go through requireProject instead, which layers
// interactive selection on top of the same resolver before announcing.
func loadProjectDetails(c *cli.Command, opts ...loadOption) (*config.ProjectConfig, error) {
if err := experimentalAuthGate(c); err != nil {
return nil, err
}
p := loadParams{requireURL: true}
for _, opt := range opts {
opt(&p)
Expand Down
37 changes: 29 additions & 8 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ require (
github.com/mattn/go-isatty v0.0.22
github.com/moby/patternmatcher v0.6.1
github.com/modelcontextprotocol/go-sdk v1.6.1
github.com/oapi-codegen/runtime v1.4.2
github.com/pelletier/go-toml v1.9.5
github.com/pion/rtcp v1.2.16
github.com/pion/rtp v1.10.2
Expand All @@ -33,7 +34,7 @@ require (
github.com/twitchtv/twirp v8.1.3+incompatible
github.com/urfave/cli/v3 v3.9.0
go.uber.org/atomic v1.11.0
golang.org/x/sync v0.20.0
golang.org/x/sync v0.21.0
golang.org/x/time v0.15.0
google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af
gopkg.in/yaml.v3 v3.0.1
Expand Down Expand Up @@ -62,6 +63,7 @@ require (
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/alecthomas/chroma/v2 v2.23.1 // indirect
github.com/antlr4-go/antlr/v4 v4.13.1 // indirect
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
github.com/aws/aws-sdk-go-v2 v1.41.7 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8 // indirect
github.com/aws/aws-sdk-go-v2/config v1.32.17 // indirect
Expand Down Expand Up @@ -113,13 +115,14 @@ require (
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/dennwc/iters v1.2.2 // indirect
github.com/distribution/reference v0.6.0 // indirect
github.com/dlclark/regexp2 v1.11.5 // indirect
github.com/dlclark/regexp2 v1.12.0 // indirect
github.com/docker/cli v29.4.3+incompatible // indirect
github.com/docker/distribution v2.8.3+incompatible // indirect
github.com/docker/docker-credential-helpers v0.9.5 // indirect
github.com/docker/go-connections v0.7.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/dominikbraun/graph v0.23.0 // indirect
github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/elliotchance/orderedmap/v3 v3.1.0 // indirect
github.com/envoyproxy/go-control-plane/envoy v1.37.0 // indirect
Expand All @@ -129,8 +132,11 @@ require (
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
github.com/gammazero/deque v1.2.1 // indirect
github.com/getkin/kin-openapi v0.135.0 // indirect
github.com/go-jose/go-jose/v4 v4.1.4 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/jsonpointer v0.22.5 // indirect
github.com/go-openapi/swag/jsonname v0.25.5 // indirect
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
github.com/go-task/template v0.2.0 // indirect
github.com/gofrs/flock v0.13.0 // indirect
Expand All @@ -154,6 +160,7 @@ require (
github.com/hashicorp/go-version v1.9.0 // indirect
github.com/in-toto/attestation v1.1.2 // indirect
github.com/in-toto/in-toto-golang v0.11.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/jxskiss/base62 v1.1.0 // indirect
github.com/klauspost/compress v1.18.6 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
Expand All @@ -164,6 +171,7 @@ require (
github.com/livekit/psrpc v0.7.2 // indirect
github.com/lucasb-eyer/go-colorful v1.4.0 // indirect
github.com/magefile/mage v1.17.2 // indirect
github.com/mailru/easyjson v0.9.1 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect
github.com/mattn/go-runewidth v0.0.23 // indirect
Expand All @@ -174,6 +182,7 @@ require (
github.com/moby/locker v1.0.1 // indirect
github.com/moby/sys/signal v0.7.1 // indirect
github.com/moby/term v0.5.2 // indirect
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
github.com/morikuni/aec v1.1.0 // indirect
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
Expand All @@ -182,8 +191,12 @@ require (
github.com/nats-io/nats.go v1.52.0 // indirect
github.com/nats-io/nkeys v0.4.16 // indirect
github.com/nats-io/nuid v1.0.1 // indirect
github.com/oapi-codegen/oapi-codegen/v2 v2.7.1 // indirect
github.com/oasdiff/yaml v0.0.9 // indirect
github.com/oasdiff/yaml3 v0.0.9 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.1 // indirect
github.com/perimeterx/marshmallow v1.1.5 // indirect
github.com/pierrec/lz4/v4 v4.1.26 // indirect
github.com/pion/datachannel v1.6.0 // indirect
github.com/pion/dtls/v3 v3.1.4 // indirect
Expand All @@ -210,11 +223,13 @@ require (
github.com/rivo/uniseg v0.4.7 // indirect
github.com/sajari/fuzzy v1.0.0 // indirect
github.com/secure-systems-lab/go-securesystemslib v0.10.0 // indirect
github.com/segmentio/asm v1.1.3 // indirect
github.com/segmentio/asm v1.2.1 // indirect
github.com/segmentio/encoding v0.5.4 // indirect
github.com/sergi/go-diff v1.4.0 // indirect
github.com/shibumi/go-pathspec v1.3.0 // indirect
github.com/sirupsen/logrus v1.9.4 // indirect
github.com/speakeasy-api/jsonpath v0.6.3 // indirect
github.com/speakeasy-api/openapi v1.19.2 // indirect
github.com/spiffe/go-spiffe/v2 v2.6.0 // indirect
github.com/stretchr/objx v0.5.3 // indirect
github.com/tonistiigi/fsutil v0.0.0-20251211185533-a2aa163d723f // indirect
Expand All @@ -225,7 +240,9 @@ require (
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 // indirect
github.com/ulikunitz/xz v0.5.15 // indirect
github.com/vbatts/tar-split v0.12.2 // indirect
github.com/vmware-labs/yaml-jsonpath v0.3.2 // indirect
github.com/wlynxg/anet v0.0.5 // indirect
github.com/woodsbury/decimal128 v1.4.0 // indirect
github.com/x448/float16 v0.8.4 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
Expand All @@ -246,13 +263,15 @@ require (
go.uber.org/zap v1.28.0 // indirect
go.uber.org/zap/exp v0.3.0 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/crypto v0.52.0 // indirect
golang.org/x/crypto v0.53.0 // indirect
golang.org/x/exp v0.0.0-20260603202125-055de637280b // indirect
golang.org/x/net v0.55.0 // indirect
golang.org/x/mod v0.37.0 // indirect
golang.org/x/net v0.56.0 // indirect
golang.org/x/oauth2 v0.36.0 // indirect
golang.org/x/sys v0.45.0 // indirect
golang.org/x/term v0.43.0 // indirect
golang.org/x/text v0.37.0 // indirect
golang.org/x/sys v0.46.0 // indirect
golang.org/x/term v0.44.0 // indirect
golang.org/x/text v0.38.0 // indirect
golang.org/x/tools v0.46.0 // indirect
google.golang.org/api v0.275.0 // indirect
google.golang.org/genproto v0.0.0-20260406210006-6f92a3bedf2d // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa // indirect
Expand All @@ -268,3 +287,5 @@ require (
// TEMP (local dev): use local protocol with the WorkerInfo dev message.
// Drop once github.com/livekit/protocol publishes it.
// replace github.com/livekit/protocol => ../protocol

tool github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen
Loading
Loading