Permalink
Browse files

Prompt to authenticate with Github.

  • Loading branch information...
dblock committed Aug 23, 2018
1 parent 05f5fc0 commit 6937a4cca1e5916a8181620e62ef1e99f84f8187
Showing with 182 additions and 13 deletions.
  1. +11 −4 .rubocop_todo.yml
  2. +1 −0 CHANGELOG.md
  3. +17 −6 README.md
  4. +2 −2 bin/fue
  5. +1 −0 fue.gemspec
  6. +7 −0 lib/fue.rb
  7. +97 −0 lib/fue/auth.rb
  8. +46 −0 lib/fue/security.rb
  9. +0 −1 spec/spec_helper.rb
@@ -1,6 +1,6 @@
# This configuration was generated by
# `rubocop --auto-gen-config`
# on 2018-08-22 11:14:56 -0400 using RuboCop version 0.58.2.
# on 2018-08-23 11:45:36 +0200 using RuboCop version 0.58.2.
# The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new
@@ -12,22 +12,29 @@ Lint/ShadowedArgument:
Exclude:
- 'bin/fue'

# Offense count: 2
# Offense count: 4
Naming/AccessorMethodName:
Exclude:
- 'lib/fue/auth.rb'

# Offense count: 4
Style/Documentation:
Exclude:
- 'spec/**/*'
- 'test/**/*'
- 'bin/fue'
- 'lib/fue/auth.rb'
- 'lib/fue/finder.rb'
- 'lib/fue/security.rb'

# Offense count: 2
# Configuration parameters: AllowedVariables.
Style/GlobalVars:
Exclude:
- 'bin/fue'

# Offense count: 5
# Offense count: 8
# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns.
# URISchemes: http, https
Metrics/LineLength:
Max: 103
Max: 151
@@ -1,5 +1,6 @@
### 0.2.1 (Next)

* [#2](https://github.com/dblock/fue/issues/2): Prompt to authenticate with Github - [@dblock](https://github.com/dblock).
* [#7](https://github.com/dblock/fue/issues/7): Display version - [@dblock](https://github.com/dblock).
* Your contribution here.

@@ -16,16 +16,12 @@ Fue is short for "Finding Unicorn Engineers".
gem install fue
```

#### Get a Github Access Token

Obtain a Github access token from [here](https://github.com/settings/tokens) with `public_repo` permissions. See [help](https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line) for more information. Set the token as `GITHUB_ACCESS_TOKEN`.

#### Find Someone's Email

The `find` command looks through user's initial repo commits.

```
GITHUB_ACCESS_TOKEN=token fue find defunkt
$ fue find defunkt
Chris Wanstrath <chris@ozmm.org>
Chris Wanstrath <chris@github.com>
@@ -36,7 +32,7 @@ Chris Wanstrath <chris@github.com>
By default the code looks at 1 commit from the last 10 repos. You can look at more repositories (breadth) and more commits (depth). The maximum value for depth is 100, enforced by Github. Fue will iterate over a number of repositories larger than 100.

```
GITHUB_ACCESS_TOKEN=token fue find --breadth=100 --depth=5 defunkt
$ fue find --breadth=100 --depth=5 defunkt
Chris Wanstrath <chris@ozmm.org>
Chris Wanstrath <chris@github.com>
@@ -51,6 +47,21 @@ fue help

Displays additional options.

#### Access Tokens

Fue will prompt you for Github credentials and 2FA, if enabled.

```
$ fue find defunkt
Enter dblock's GitHub password (never stored): ******************
Enter GitHub 2FA code: ******
Token saved to keychain.
```

The access token will be generated with `public_repo` scope and stored in the keychain. It can be later deleted from [here](https://github.com/settings/tokens). You can also skip the prompts and use a previously obtained token with `-t` or by setting the `GITHUB_ACCESS_TOKEN` environment variable.

See [Creating a Personal Access Token for the Command Line](https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line) for more information about personal tokens.

## Contributing

There're [a few feature requests and known issues](https://github.com/dblock/fue/issues). Please contribute! See [CONTRIBUTING](CONTRIBUTING.md).
@@ -1,7 +1,6 @@
#!/usr/bin/env ruby
require 'gli'
require 'fue'
require 'graphlient'

class App
extend GLI::App
@@ -11,13 +10,14 @@ class App

switch %i[v verbose], desc: 'Produce verbose output.', default_value: false
flag %i[t token], desc: 'Github access token.', default_value: ENV['GITHUB_ACCESS_TOKEN']
flag %i[u username], desc: 'Guthub username.', default_value: Fue::Auth.username

arguments :strict
subcommand_option_handling :normal

pre do |global_options, _command, options, _args|
options = global_options.dup
token = options.delete(:token)
token = options.delete(:token) || Fue::Auth.token
$fue = Fue::Finder.new(token, options)
end

@@ -15,6 +15,7 @@ Gem::Specification.new do |s|
s.homepage = 'http://github.com/dblock/fue'
s.licenses = ['MIT']
s.summary = 'Find an e-mail address of a Github user.'
s.add_dependency 'github_api'
s.add_dependency 'gli'
s.add_dependency 'graphlient', '~> 0.3.2'
end
@@ -1,2 +1,9 @@
require 'graphlient'
require 'github_api'
require 'open3'
require 'English'

require 'fue/version'
require 'fue/finder'
require 'fue/security'
require 'fue/auth'
@@ -0,0 +1,97 @@
module Fue
module Auth
class << self
def token
stored_options = { username: username, server: 'github.com', label: 'fue' }
stored_token = Fue::Security.get(stored_options)
unless stored_token
stored_token = github_token
Fue::Security.store!(stored_options.merge(password: stored_token))
puts 'Token saved to keychain.'
end
stored_token
end

def username
@username ||= begin
`git config github.user`.chomp
rescue StandardError
get_username
end
end

private

def github_token(code = nil)
github(code).auth.create(scopes: ['public_repo'], note: note).token
rescue Github::Error::Unauthorized => e
case e.response_headers['X-GitHub-OTP']
when /required/ then
github_token(get_code)
else
raise e
end
rescue Github::Error::UnprocessableEntity => e
raise e, 'A fue token already exists! Please revoke all previously-generated fue personal access tokens at https://github.com/settings/tokens.'
end

def password
@password ||= get_password
end

def note
"Fui (https://github.com/dblock/fue) on #{Socket.gethostname}"
end

def github(code = nil)
Github.new do |config|
config.basic_auth = [username, password].join(':')
if code
config.connection_options = {
headers: {
'X-GitHub-OTP' => code
}
}
end
end
end

def get_username
print 'Enter GithHub username: '
$stdin.gets.chomp
rescue Interrupt => e
raise e, 'ctrl + c'
end

def get_password
print "Enter #{username}'s GitHub password (never stored): "
get_secure
end

def get_code
print 'Enter GitHub 2FA code: '
get_secure
end

def get_secure
current_tty = `stty -g`
system 'stty raw -echo -icanon isig' if $CHILD_STATUS.success?
input = ''
while (char = $stdin.getbyte) && !((char == 13) || (char == 10))
if (char == 127) || (char == 8)
input[-1, 1] = '' unless input.empty?
else
$stdout.write '*'
input << char.chr
end
end
print "\r\n"
input
rescue Interrupt => e
raise e, 'ctrl + c'
ensure
system "stty #{current_tty}" unless current_tty.empty?
end
end
end
end
@@ -0,0 +1,46 @@
module Fue
module Security
class << self
def store!(options)
system!(security('add', options))
end

def get(options)
system!(security('find', options))
rescue RuntimeError
nil
end

private

def security(command = nil, options = nil)
run = [security_path]
run << "#{command}-internet-password"
run << "-a #{options[:username]}"
run << "-s #{options[:server]}"
if command == 'add'
run << "-l #{options[:label]}"
run << '-U'
run << "-w #{options[:password]}" if options.key?(:password)
else
run << '-w'
end
run.join ' '
end

def security_path
@security_path ||= begin
`which security`.chomp
rescue StandardError
'security'
end
end

def system!(*cmd)
stdout, _, status = Open3.capture3(*cmd)
raise "failed with exit code #{status}" unless status.success?
stdout.slice!(0..-(1 + $INPUT_RECORD_SEPARATOR.size))
end
end
end
end
@@ -8,7 +8,6 @@
require 'fue'
require 'English'
require 'webmock/rspec'
require 'graphlient'
require 'recursive-open-struct'

RSpec.configure(&:raise_errors_for_deprecations!)

0 comments on commit 6937a4c

Please sign in to comment.