Skip to content

Commit

Permalink
feat(auth): add revocation of keys
Browse files Browse the repository at this point in the history
  • Loading branch information
rektdeckard committed Aug 10, 2024
1 parent fe0c258 commit e4ed024
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 59 deletions.
96 changes: 71 additions & 25 deletions cmd/lk/cloud.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,19 +28,30 @@ import (
"github.com/urfave/cli/v3"

"github.com/livekit/livekit-cli/pkg/config"
"github.com/livekit/protocol/auth"
"github.com/pkg/browser"
)

type ClaimAccessKeyResponse struct {
Key string
Secret string
ProjectId string
OwnerId string
Description string
URL string
}

const (
cloudAPIServerURL = "https://cloud-api.livekit.io"
cloudDashboardURL = "https://cloud.livekit.io"
createTokenEndpoint = "/cli/auth"
confirmAuthEndpoint = "/cli/confirm-auth"
claimSessionEndpoint = "/cli/claim"
cloudAPIServerURL = "https://cloud-api.livekit.io"
cloudDashboardURL = "https://cloud.livekit.io"
createTokenEndpoint = "/cli/auth"
claimKeyEndpoint = "/cli/claim"
confirmAuthEndpoint = "/cli/confirm-auth"
revokeKeyEndpoint = "/cli/revoke"
)

var (
disconnect bool
revoke bool
timeout int64
interval int64
serverURL string
Expand All @@ -61,7 +72,7 @@ var (
&cli.BoolFlag{
Name: "R",
Aliases: []string{"revoke"},
Destination: &disconnect,
Destination: &revoke,
},
&cli.IntFlag{
Name: "t",
Expand Down Expand Up @@ -139,12 +150,12 @@ func (a *AuthClient) GetVerificationToken(deviceName string) (*VerificationToken
return &a.verificationToken, nil
}

func (a *AuthClient) ClaimCliKey(ctx context.Context) (*config.AccessKey, error) {
func (a *AuthClient) ClaimCliKey(ctx context.Context) (*ClaimAccessKeyResponse, error) {
if a.verificationToken.Token == "" || time.Now().Unix() > a.verificationToken.Expires {
return nil, errors.New("session expired")
}

reqURL, err := url.Parse(a.baseURL + claimSessionEndpoint)
reqURL, err := url.Parse(a.baseURL + claimKeyEndpoint)
if err != nil {
return nil, err
}
Expand All @@ -170,7 +181,7 @@ func (a *AuthClient) ClaimCliKey(ctx context.Context) (*config.AccessKey, error)
return nil, nil
}

ak := &config.AccessKey{}
ak := &ClaimAccessKeyResponse{}
err = json.NewDecoder(resp.Body).Decode(&ak)
if err != nil {
return nil, err
Expand All @@ -179,9 +190,26 @@ func (a *AuthClient) ClaimCliKey(ctx context.Context) (*config.AccessKey, error)
return ak, nil
}

func (a *AuthClient) Deauthenticate() error {
// TODO: revoke any session token
return nil
func (a *AuthClient) Deauthenticate(ctx context.Context, projectName, token string) error {
reqURL, err := url.Parse(a.baseURL + revokeKeyEndpoint)
if err != nil {
return err
}

req, err := http.NewRequestWithContext(ctx, "DELETE", reqURL.String(), nil)
req.Header = newHeaderWithToken(token)
if err != nil {
return err
}

resp, err := a.client.Do(req)
if err != nil {
return err
}
if resp.StatusCode != 200 {
return errors.New("access denied")
}
return cliConfig.RemoveProject(projectName)
}

func NewAuthClient(client *http.Client, baseURL string) *AuthClient {
Expand All @@ -193,23 +221,41 @@ func NewAuthClient(client *http.Client, baseURL string) *AuthClient {
}

func initAuth(ctx context.Context, cmd *cli.Command) error {
if err := loadProjectConfig(ctx, cmd); err != nil {
return err
}
authClient = *NewAuthClient(&http.Client{}, serverURL)
return nil
}

func handleAuth(ctx context.Context, cmd *cli.Command) error {
if disconnect {
return authClient.Deauthenticate()
if revoke {
if err := loadProjectConfig(ctx, cmd); err != nil {
return err
}
cfg, token, err := requireToken(ctx, cmd)
if err != nil {
return err
}
return authClient.Deauthenticate(ctx, cfg.Name, token)
}
return tryAuthIfNeeded(ctx, cmd)
}

func tryAuthIfNeeded(ctx context.Context, cmd *cli.Command) error {
_, err := loadProjectDetails(cmd)
func requireToken(_ context.Context, cmd *cli.Command) (*config.ProjectConfig, string, error) {
cfg, err := loadProjectDetails(cmd)
if err != nil {
return nil, "", err
}

at := auth.NewAccessToken(cfg.APIKey, cfg.APISecret)
token, err := at.ToJWT()
if err != nil {
return nil, "", err
}

return cfg, token, nil
}

func tryAuthIfNeeded(ctx context.Context, cmd *cli.Command) error {
if err := loadProjectConfig(ctx, cmd); err != nil {
return err
}

Expand Down Expand Up @@ -242,14 +288,13 @@ func tryAuthIfNeeded(ctx context.Context, cmd *cli.Command) error {
return err
}

var key *config.AccessKey
var key *ClaimAccessKeyResponse
var pollErr error
if err := spinner.New().
Title("Awaiting confirmation...").
Action(func() {
key, pollErr = pollClaim(ctx, cmd)
}).
Context(ctx).
Run(); err != nil {
return err
}
Expand All @@ -274,8 +319,9 @@ func tryAuthIfNeeded(ctx context.Context, cmd *cli.Command) error {
Name: key.Description,
APIKey: key.Key,
APISecret: key.Secret,
URL: "ws://" + key.Project.Subdomain + ".livekit:7800",
URL: key.URL,
})

if isDefault {
cliConfig.DefaultProject = key.Description
}
Expand All @@ -298,8 +344,8 @@ func generateConfirmURL(token string) (*url.URL, error) {
return base, nil
}

func pollClaim(ctx context.Context, _ *cli.Command) (*config.AccessKey, error) {
claim := make(chan *config.AccessKey)
func pollClaim(ctx context.Context, _ *cli.Command) (*ClaimAccessKeyResponse, error) {
claim := make(chan *ClaimAccessKeyResponse)
cancel := make(chan error)

// every <interval> seconds, poll
Expand Down
22 changes: 1 addition & 21 deletions cmd/lk/project.go
Original file line number Diff line number Diff line change
Expand Up @@ -273,27 +273,7 @@ func removeProject(ctx context.Context, cmd *cli.Command) error {
return errors.New("project name is required")
}
name := cmd.Args().First()

var newProjects []config.ProjectConfig
for _, p := range cliConfig.Projects {
if p.Name == name {
continue
}
newProjects = append(newProjects, p)
}
cliConfig.Projects = newProjects

if cliConfig.DefaultProject == name {
cliConfig.DefaultProject = ""
}

if err := cliConfig.PersistIfNeeded(); err != nil {
return err
}

fmt.Println("Removed project", name)

return nil
return cliConfig.RemoveProject(name)
}

func setDefaultProject(ctx context.Context, cmd *cli.Command) error {
Expand Down
35 changes: 22 additions & 13 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,19 +37,6 @@ type ProjectConfig struct {
APISecret string `yaml:"api_secret"`
}

type Project struct {
Subdomain string
}

type AccessKey struct {
Key string
Secret string
ProjectId string
Project Project
OwnerId string
Description string
}

func LoadDefaultProject() (*ProjectConfig, error) {
conf, err := LoadOrCreate()
if err != nil {
Expand Down Expand Up @@ -114,6 +101,28 @@ func LoadOrCreate() (*CLIConfig, error) {
return c, nil
}

func (c *CLIConfig) RemoveProject(name string) error {
var newProjects []ProjectConfig
for _, p := range c.Projects {
if p.Name == name {
continue
}
newProjects = append(newProjects, p)
}
c.Projects = newProjects

if c.DefaultProject == name {
c.DefaultProject = ""
}

if err := c.PersistIfNeeded(); err != nil {
return err
}

fmt.Println("Removed project", name)
return nil
}

func (c *CLIConfig) PersistIfNeeded() error {
if len(c.Projects) == 0 && !c.hasPersisted {
// doesn't need to be persisted
Expand Down

0 comments on commit e4ed024

Please sign in to comment.