Skip to content
Merged
53 changes: 53 additions & 0 deletions cmd/auth-code.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package cmd

import (
"fmt"

"github.com/opentdf/otdfctl/pkg/cli"
"github.com/opentdf/otdfctl/pkg/handlers"
"github.com/opentdf/otdfctl/pkg/man"
"github.com/spf13/cobra"
)

var noCacheCreds bool

func auth_codeLogin(cmd *cobra.Command, args []string) {
flagHelper := cli.NewFlagHelper(cmd)
host := flagHelper.GetRequiredString("host")
clientID := flagHelper.GetOptionalString("client-id")
tlsNoVerify := flagHelper.GetOptionalBool("tls-no-verify")

printer := cli.NewPrinter(!noCacheCreds)

tok, err := handlers.LoginWithPKCE(host, clientID, tlsNoVerify, noCacheCreds)
if err != nil {
cli.ExitWithError("could not authenticate", err)
}
if noCacheCreds {
fmt.Print(tok.AccessToken)
}
// TODO: set to the keyring/profile here
printer.Println(cli.SuccessMessage("Successfully logged in with auth code PKCE flow. Credentials cached on native OS."))
}

var codeLoginCmd *man.Doc

func init() {
codeLoginCmd = man.Docs.GetCommand("auth/code-login",
man.WithRun(auth_codeLogin),
)
codeLoginCmd.Flags().StringP(
codeLoginCmd.GetDocFlag("client-id").Name,
codeLoginCmd.GetDocFlag("client-id").Shorthand,
codeLoginCmd.GetDocFlag("client-id").Default,
codeLoginCmd.GetDocFlag("client-id").Description,
)
codeLoginCmd.Flags().BoolVarP(
&noCacheCreds,
codeLoginCmd.GetDocFlag("no-cache").Name,
codeLoginCmd.GetDocFlag("no-cache").Shorthand,
codeLoginCmd.GetDocFlag("no-cache").DefaultAsBool(),
codeLoginCmd.GetDocFlag("no-cache").Description,
)
authCmd.AddCommand(&codeLoginCmd.Command)
}
3 changes: 2 additions & 1 deletion cmd/auth-printAccessToken.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ import (
)

var auth_printAccessTokenCmd = man.Docs.GetCommand("auth/print-access-token",
man.WithRun(auth_printAccessToken))
man.WithRun(auth_printAccessToken),
)

func auth_printAccessToken(cmd *cobra.Command, args []string) {
flagHelper := cli.NewFlagHelper(cmd)
Expand Down
1 change: 1 addition & 0 deletions cmd/dev.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ func NewHandler(cmd *cobra.Command) handlers.Handler {
clientCredsFile := flag.GetOptionalString("with-client-creds-file")
clientCredsJSON := flag.GetOptionalString("with-client-creds")

// Get any credentials we can from the cache or flags
creds, err := handlers.GetClientCreds(host, clientCredsFile, []byte(clientCredsJSON))
if err != nil {
cli.ExitWithError("Failed to get client credentials", err)
Expand Down
21 changes: 21 additions & 0 deletions docs/man/auth/code-login.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
title: Open a browser and login with Auth Code PKCE

command:
name: code-login
flags:
- name: client-id
description: A clientId for use in auth code flow (default = platform well-known public_client_id)
shorthand: i
required: false
- name: no-cache
description: Do not cache credentials on the native OS (print access token to stdout)
default: false
---

Authenticate for use of the OpenTDF Platform through a browser (required).

Provide a specific public 'client-id' known to support the Auth Code PKCE flow and recognized
by the OpenTDF Platform, or use the default public client in the platform well-known configuration if not specified.

The OIDC Access Token will be stored in the OS-specific keychain by default, otherwise printed to `stdout` if `--no-cache` is passed.
9 changes: 4 additions & 5 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,15 @@ require (
github.com/evertras/bubble-table v0.16.1
github.com/gabriel-vasile/mimetype v1.4.5
github.com/golang-jwt/jwt v3.2.2+incompatible
github.com/golang-jwt/jwt/v4 v4.5.0
github.com/google/uuid v1.6.0
github.com/itchyny/gojq v0.12.16
github.com/opentdf/platform/protocol/go v0.2.11
github.com/opentdf/platform/sdk v0.3.5
github.com/opentdf/platform/protocol/go v0.2.13
github.com/opentdf/platform/sdk v0.3.8
github.com/spf13/cobra v1.8.1
github.com/spf13/viper v1.19.0
github.com/stretchr/testify v1.9.0
github.com/zalando/go-keyring v0.2.5
github.com/zitadel/oidc/v3 v3.27.0
golang.org/x/oauth2 v0.22.0
golang.org/x/term v0.22.0
google.golang.org/grpc v1.65.0
Expand Down Expand Up @@ -53,7 +54,6 @@ require (
github.com/go-logr/stdr v1.2.2 // indirect
github.com/goccy/go-json v0.10.3 // indirect
github.com/godbus/dbus/v5 v5.1.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/css v1.0.0 // indirect
github.com/gorilla/securecookie v1.1.2 // indirect
github.com/gowebpki/jcs v1.0.1 // indirect
Expand Down Expand Up @@ -104,7 +104,6 @@ require (
github.com/yuin/goldmark v1.5.4 // indirect
github.com/yuin/goldmark-emoji v1.0.2 // indirect
github.com/zitadel/logging v0.6.0 // indirect
github.com/zitadel/oidc/v3 v3.27.0 // indirect
github.com/zitadel/schema v1.3.0 // indirect
go.opentelemetry.io/otel v1.28.0 // indirect
go.opentelemetry.io/otel/metric v1.28.0 // indirect
Expand Down
32 changes: 16 additions & 16 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiE
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
github.com/bmatcuk/doublestar/v4 v4.6.1 h1:FH9SifrbvJhnlQpztAx++wlkk70QBf0iBWDwNy7PA4I=
github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
github.com/catppuccin/go v0.2.0 h1:ktBeIrIP42b/8FGiScP9sgrWOss3lw0Z5SktRoithGA=
github.com/catppuccin/go v0.2.0/go.mod h1:8IHJuMGaUUjQM82qBrGNBv7LFq6JI3NnQCF6MOlZjpc=
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
Expand Down Expand Up @@ -95,11 +97,11 @@ github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nos
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/gabriel-vasile/mimetype v1.4.5 h1:J7wGKdGu33ocBOhGy0z653k/lFKLFDPJMG8Gql0kxn4=
github.com/gabriel-vasile/mimetype v1.4.5/go.mod h1:ibHel+/kbxn9x2407k1izTA1S81ku1z/DlgOW2QE0M4=
github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw=
github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-jose/go-jose/v4 v4.0.4 h1:VsjPI33J0SB9vQM6PLmNjoHqMQNGPiZ0rHL7Ni7Q6/E=
github.com/go-jose/go-jose/v4 v4.0.4/go.mod h1:NKb5HO1EZccyMpiZNbdUw/14tiXNyUJh188dfnMCAfc=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
Expand All @@ -116,12 +118,12 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
Expand All @@ -144,6 +146,8 @@ github.com/itchyny/gojq v0.12.16 h1:yLfgLxhIr/6sJNVmYfQjTIv0jGctu6/DgDoivmxTr7g=
github.com/itchyny/gojq v0.12.16/go.mod h1:6abHbdC2uB9ogMS38XsErnfqJ94UlngIJGlRAIj4jTM=
github.com/itchyny/timefmt-go v0.1.6 h1:ia3s54iciXDdzWzwaVKXZPbiXzxxnv1SPGFfM/myJ5Q=
github.com/itchyny/timefmt-go v0.1.6/go.mod h1:RRDZYC5s9ErkjQvTvvU7keJjxUYzIISJGxm9/mAERQg=
github.com/jeremija/gosubmit v0.2.7 h1:At0OhGCFGPXyjPYAsCchoBUhE099pcBXmsb4iZqROIc=
github.com/jeremija/gosubmit v0.2.7/go.mod h1:Ui+HS073lCFREXBbdfrJzMB57OI/bdxTiLtrDHHhFPI=
github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg=
github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
Expand Down Expand Up @@ -204,6 +208,8 @@ github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
github.com/muhlemmer/gu v0.3.1 h1:7EAqmFrW7n3hETvuAdmFmn4hS8W+z3LgKtrnow+YzNM=
github.com/muhlemmer/gu v0.3.1/go.mod h1:YHtHR+gxM+bKEIIs7Hmi9sPT3ZDUvTN/i88wQpZkrdM=
github.com/muhlemmer/httpforwarded v0.1.0 h1:x4DLrzXdliq8mprgUMR0olDvHGkou5BJsK/vWUetyzY=
github.com/muhlemmer/httpforwarded v0.1.0/go.mod h1:yo9czKedo2pdZhoXe+yDkGVbU0TJ0q9oQ90BVoDEtw0=
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
Expand All @@ -214,10 +220,10 @@ github.com/opentdf/platform/lib/fixtures v0.2.7 h1:2LxWmLBBISONVJnVDH8yMsV72VHQy
github.com/opentdf/platform/lib/fixtures v0.2.7/go.mod h1:8yCSe+oUzW9jbM573r9qgE68rjwDMNzktObiGVsO/W8=
github.com/opentdf/platform/lib/ocrypto v0.1.5 h1:Gv5dAmZEVQeD9w1Fg9fix8gdZIsPDHJ96jG9GdtnYhc=
github.com/opentdf/platform/lib/ocrypto v0.1.5/go.mod h1:ne+l8Q922OdzA0xesK3XJmfECBnn5vLSGYU3/3OhiHM=
github.com/opentdf/platform/protocol/go v0.2.11 h1:Jeazb4mNCz6UIhpBQTn6oS8HI7JtrI+OLqjj4FTwm/A=
github.com/opentdf/platform/protocol/go v0.2.11/go.mod h1:WqDcnFQJb0v8ivRQPidbehcL8ils5ZSZYXkuv0nyvsI=
github.com/opentdf/platform/sdk v0.3.5 h1:7v24ztGBKqgRDeDfTC1nUi+ht3P64Pr1Yrnk2S4/NfM=
github.com/opentdf/platform/sdk v0.3.5/go.mod h1:vJJ0bWaDiA2giU6CmEDW0OHZGFfSE+nk2lPCz7ZSeAg=
github.com/opentdf/platform/protocol/go v0.2.13 h1:nyMBTa4v2K498EaWTqjsWq6nJ6QiiDso+aAoxuPrXOs=
github.com/opentdf/platform/protocol/go v0.2.13/go.mod h1:WqDcnFQJb0v8ivRQPidbehcL8ils5ZSZYXkuv0nyvsI=
github.com/opentdf/platform/sdk v0.3.8 h1:vTeXpK0MvNCykv/EnDXMNxegWiF44+b5bwe/BZP4zt0=
github.com/opentdf/platform/sdk v0.3.8/go.mod h1:FaClb6h+cHDT3dRYN1pneqDU4QU7ldpYYd+Vyh9Zq6M=
github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
Expand All @@ -235,6 +241,8 @@ github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/rs/cors v1.11.0 h1:0B9GE/r9Bc2UxRMMtymBkHTenPkHDv0CW4Y98GBY+po=
github.com/rs/cors v1.11.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
Expand Down Expand Up @@ -311,16 +319,10 @@ github.com/zitadel/schema v1.3.0 h1:kQ9W9tvIwZICCKWcMvCEweXET1OcOyGEuFbHs4o5kg0=
github.com/zitadel/schema v1.3.0/go.mod h1:NptN6mkBDFvERUCvZHlvWmmME+gmZ44xzwRXwhzsbtc=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw=
go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo=
go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo=
go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo=
go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4=
go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI=
go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco=
go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q=
go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s=
go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI=
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g=
go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
Expand All @@ -333,8 +335,6 @@ golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
golang.org/x/oauth2 v0.20.0 h1:4mQdhULixXKP1rwYBW0vAijoXnkTG0BLCDRzfe1idMo=
golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA=
golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
Expand Down
109 changes: 99 additions & 10 deletions pkg/handlers/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,26 @@ package handlers

import (
"context"
"crypto/rand"
"encoding/json"
"errors"
"fmt"
"net/url"
"os"
"time"

"github.com/google/uuid"
"github.com/zalando/go-keyring"
oidcrp "github.com/zitadel/oidc/v3/pkg/client/rp"
oidcCLI "github.com/zitadel/oidc/v3/pkg/client/rp/cli"
httphelper "github.com/zitadel/oidc/v3/pkg/http"
"github.com/zitadel/oidc/v3/pkg/oidc"
"golang.org/x/oauth2"
)

const (
authCallbackPath = "/callback"
authCodeFlowPort = "9000"
OTDFCTL_KEYRING_SERVICE = "otdfctl"
OTDFCTL_CLIENT_ID_CACHE_KEY = "OTDFCTL_DEFAULT_CLIENT_ID"
OTDFCTL_KEYRING_CLIENT_CREDENTIALS = "OTDFCTL_CLIENT_CREDENTIALS"
Expand Down Expand Up @@ -59,19 +69,14 @@ func GetClientCreds(endpoint string, file string, credsJSON []byte) (ClientCrede
return NewKeyring(endpoint).GetClientCredentials()
}

func getPlatformIssuer(endpoint string, tlsNoVerify bool) (string, error) {
// Create a new handler with the provided endpoint and no credentials (empty strings is required by the SDK)
h, err := NewWithCredentials(endpoint, "", "", tlsNoVerify)
// Uses the OAuth2 client credentials flow to obtain a token.
func GetTokenWithClientCreds(ctx context.Context, endpoint string, c ClientCredentials, tlsNoVerify bool) (*oauth2.Token, error) {
h, err := New(endpoint, tlsNoVerify)
if err != nil {
return "", err
return nil, err
}

return h.sdk.PlatformIssuer(), nil
}

// Uses the OAuth2 client credentials flow to obtain a token.
func GetTokenWithClientCreds(ctx context.Context, endpoint string, c ClientCredentials, tlsNoVerify bool) (*oauth2.Token, error) {
issuer, err := getPlatformIssuer(endpoint, tlsNoVerify)
issuer, err := h.Direct().PlatformConfiguration.Issuer()
if err != nil {
return nil, err
}
Expand All @@ -83,3 +88,87 @@ func GetTokenWithClientCreds(ctx context.Context, endpoint string, c ClientCrede

return oidcrp.ClientCredentials(ctx, rp, url.Values{})
}

// Facilitates an auth code PKCE flow to obtain OIDC tokens.
// Spawns a local server to handle the callback and opens a browser window in each respective OS.
func Login(platformEndpoint, tokenURL, authURL, publicClientID string, noPrint bool) (*oauth2.Token, error) {
// Generate random hash and encryption keys for cookie handling
hashKey := make([]byte, 16)
encryptKey := make([]byte, 16)

_, err := rand.Read(hashKey)
if err != nil {
return nil, err
}

_, err = rand.Read(encryptKey)
if err != nil {
return nil, err
}

conf := &oauth2.Config{
ClientID: publicClientID,
Scopes: []string{"openid", "profile", "email"},
RedirectURL: fmt.Sprintf("http://localhost:%s%s", authCodeFlowPort, authCallbackPath),
Endpoint: oauth2.Endpoint{
AuthURL: authURL,
TokenURL: tokenURL,
},
}

ctx := context.Background()
cookiehandler := httphelper.NewCookieHandler(hashKey, encryptKey)

relyingParty, err := oidcrp.NewRelyingPartyOAuth(conf,
// allow cookie handling for PKCE
oidcrp.WithCookieHandler(cookiehandler),
// use PKCE
oidcrp.WithPKCE(cookiehandler),
// allow IAT claim offset of 5 seconds
oidcrp.WithVerifierOpts(oidcrp.WithIssuedAtOffset(5*time.Second)),
)
if err != nil {
return nil, fmt.Errorf("failed to create relying party: %v", err)
}
stateProvider := func() string {
return uuid.New().String()
}
tok := oidcCLI.CodeFlow[*oidc.IDTokenClaims](ctx, relyingParty, authCallbackPath, authCodeFlowPort, stateProvider)
return tok.Token, nil
}

// Logs in using the auth code PKCE flow driven by the platform well-known idP OIDC configuration.
func LoginWithPKCE(host, publicClientID string, tlsNoVerify bool, noCache bool) (*oauth2.Token, error) {
// retrieve idP well-known configuration values via unauthenticated SDK
h, err := New(host, tlsNoVerify)
if err != nil {
return nil, fmt.Errorf("failed to create handler: %w", err)
}

tokenURL, err := h.Direct().PlatformConfiguration.TokenEndpoint()
if err != nil || tokenURL == "" {
return nil, fmt.Errorf("failed to retrieve well-known token endpoint: %w", err)
}
authURL, err := h.Direct().PlatformConfiguration.AuthzEndpoint()
if err != nil || authURL == "" {
return nil, fmt.Errorf("failed to retrieve well-known authz endpoint: %w", err)
}
if publicClientID == "" {
publicClientID, err = h.Direct().PlatformConfiguration.PublicClientID()
if err != nil || publicClientID == "" {
return nil, fmt.Errorf("failed to retrieve well-known public client ID: %w", err)
}
}

tok, err := Login(h.platformEndpoint, tokenURL, authURL, publicClientID, noCache)
if err != nil {
return nil, fmt.Errorf("failed to login: %w", err)
}

if !noCache {
if err := keyring.Set(h.platformEndpoint, OTDFCTL_OIDC_TOKEN_KEY, tok.AccessToken); err != nil {
return nil, fmt.Errorf("failed to store token in keyring: %w", err)
}
}
return tok, nil
}
13 changes: 11 additions & 2 deletions pkg/handlers/sdk.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,17 @@ type Handler struct {
platformEndpoint string
}

func NewWithCredentials(endpoint string, clientId string, clientSecret string, tlsNoVerify bool) (Handler, error) {
return New(endpoint, tlsNoVerify, sdk.WithClientCredentials(clientId, clientSecret, []string{"email"}))
func NewWithCredentials(endpoint, clientID, clientSecret string, tlsNoVerify bool) (Handler, error) {
if clientID == "" || clientSecret == "" {
// try to get token from cache
// tok, err := GetOIDCTokenFromCache(endpoint)
// if err != nil {
// return Handler{}, err
// }
// source := buildTokenSource(tok)
return New(endpoint, tlsNoVerify /*sdk.WithCustomAccessTokenSource(source)*/)
}
return New(endpoint, tlsNoVerify, sdk.WithClientCredentials(clientID, clientSecret, []string{"email"}))
}

// Creates a new handler wrapping the SDK, which is authenticated through the cached client-credentials flow tokens
Expand Down