diff --git a/README.md b/README.md index 72ae202..a2ed886 100644 --- a/README.md +++ b/README.md @@ -98,6 +98,16 @@ Edit your [global git config](https://git-scm.com/docs/git-config#FILES) `~/.git helper = oauth ``` +### Browserless systems + +On systems without a web browser, set the `-device` flag to authenticate on another device using [OAuth device flow](https://www.rfc-editor.org/rfc/rfc8628). Currently only GitHub supports this flow. + +```ini +[credential] + helper = cache --timeout 7200 # two hours + helper = oauth -device +``` + ### Unconfiguration Edit `~/.gitconfig` manually, or run: diff --git a/go.mod b/go.mod index 3bceac4..a063eac 100644 --- a/go.mod +++ b/go.mod @@ -2,12 +2,12 @@ module github.com/hickford/git-credential-oauth go 1.19 -require golang.org/x/oauth2 v0.8.0 +require golang.org/x/oauth2 v0.12.1-0.20230906163520-e3fb0fb3af0e require ( github.com/golang/protobuf v1.5.3 // indirect github.com/google/go-cmp v0.5.9 // indirect - golang.org/x/net v0.10.0 // indirect + golang.org/x/net v0.15.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/protobuf v1.30.0 // indirect + google.golang.org/protobuf v1.31.0 // indirect ) diff --git a/go.sum b/go.sum index a2d533d..8beb5c2 100644 --- a/go.sum +++ b/go.sum @@ -5,12 +5,18 @@ github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiu 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/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/hickford/oauth2 v0.0.0-20230316201659-ccad9d9a87ba h1:y/9DK3216v9KehgzJ5qVvZkF62uPUTX9j+1Y4PSuLq0= +github.com/hickford/oauth2 v0.0.0-20230316201659-ccad9d9a87ba/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8= golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE= +golang.org/x/oauth2 v0.12.1-0.20230906163520-e3fb0fb3af0e h1:e2rSb+C/PeY/uOtHybFAuOLcdwGezLHUKSeEPjQuDV8= +golang.org/x/oauth2 v0.12.1-0.20230906163520-e3fb0fb3af0e/go.mod h1:A74bZ3aGXgCY0qaIC9Ahg6Lglin4AMAco8cIv9baba4= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= @@ -22,3 +28,5 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 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= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= diff --git a/main.go b/main.go index 8a84b29..fc0ad2c 100644 --- a/main.go +++ b/main.go @@ -139,6 +139,8 @@ func parse(input string) map[string]string { func main() { flag.BoolVar(&verbose, "verbose", false, "log debug information to stderr") + var device bool + flag.BoolVar(&device, "device", false, "instead of opening a web browser locally, print a code to enter on another device") flag.Usage = func() { printVersion() fmt.Fprintln(os.Stderr, "usage: git credential-oauth [] ") @@ -253,7 +255,11 @@ func main() { if token == nil { // Generate new token (opens browser, may require user input) - token, err = getToken(c) + if device { + token, err = getDeviceToken(c) + } else { + token, err = getToken(c) + } if err != nil { log.Fatalln(err) } @@ -397,6 +403,22 @@ func getToken(c oauth2.Config) (*oauth2.Token, error) { return c.Exchange(context.Background(), code, verifierOption(verifier)) } +func getDeviceToken(c oauth2.Config) (*oauth2.Token, error) { + if c.Endpoint.DeviceAuthURL == "" { + fmt.Fprintln(os.Stderr, "host doesn't support device auth") + os.Exit(0) + } + deviceAuth, err := c.DeviceAuth(context.Background()) + if err != nil { + log.Fatalln(err) + } + if verbose { + fmt.Fprintln(os.Stderr, deviceAuth) + } + fmt.Fprintf(os.Stderr, "Please enter code %s at %s\n", deviceAuth.UserCode, deviceAuth.VerificationURI) + return c.DeviceAccessToken(context.Background(), deviceAuth) +} + func randomString(n int) string { data := make([]byte, n) if _, err := io.ReadFull(rand.Reader, data); err != nil {