Introdução
Este tutorial demonstra como criar uma CLI (interface de linha de comando) apoiada por um GitHub App e como usar o fluxo do dispositivo para gerar um token de acesso do usuário para o aplicativo.
A CLI terá três comandos:
- help: gera as instruções de uso.
- login: gera um token de acesso do usuário que o aplicativo pode usar para fazer solicitações de API em nome do usuário.
- whoami: retorna informações sobre o usuário conectado.
Este tutorial usa o Ruby, mas você pode escrever uma CLI e usar o fluxo de dispositivo para gerar um token de acesso do usuário com qualquer linguagem de programação.
Sobre o fluxo do dispositivo e tokens de acesso do usuário
A CLI usará o fluxo do dispositivo para autenticar um usuário e gerar um token de acesso do usuário. Em seguida, a CLI pode usar o token de acesso do usuário para fazer solicitações de API em nome do usuário autenticado.
Seu aplicativo deve usar um token de acesso do usuário se você quiser atribuir as ações do aplicativo a um usuário. Para obter mais informações, confira "Autenticação com um aplicativo GitHub em nome de um usuário".
Há duas maneiras de gerar um token de acesso do usuário para um GitHub App: fluxo de aplicativo Web e fluxo de dispositivo. Você deve usar um fluxo de dispositivo para gerar um token de acesso do usuário se seu o aplicativo não tiver periféricos ou não tiver acesso a uma interface Web. Por exemplo, as ferramentas da CLI, o Raspberry Pis simples e os aplicativos da área de trabalho devem usar o fluxo do dispositivo. Se o aplicativo tiver acesso a uma interface da Web, você deverá usar o fluxo do aplicativo Web. Para obter mais informações, confira "Como gerar um token de acesso do usuário para um GitHub App" e "Criando um botão "Logon com o GitHub" com um Aplicativo GitHub."
Pré-requisitos
Este tutorial pressupõe que você já tenha registrado o GitHub App. Para obter mais informações sobre como registrar o GitHub App, confira "Registrar um Aplicativo GitHub".
Antes de seguir este tutorial, você deve habilitar o fluxo de dispositivo para seu aplicativo. Para obter mais informações sobre como habilitar o fluxo de dispositivos para seu aplicativo, confira "Modificar um registro do Aplicativo GitHub".
Este tutorial considera que você tenha uma compreensão básica do Ruby. Para obter mais informações, confira Ruby.
Obter a ID do cliente
Você precisará da ID do cliente do aplicativo para gerar um token de acesso do usuário por meio do fluxo do dispositivo.
- No canto superior direito de qualquer página do GitHub, clique na foto do seu perfil.
- Acesse as configurações da sua conta.
- Para um aplicativo de propriedade de uma conta pessoal, clique em Configurações.
- Para um aplicativo de propriedade de uma organização:
- Clique em Suas organizações.
- À direita da organização, clique em Configurações.
 
 
- Na barra lateral esquerda, clique em Configurações do desenvolvedor.
- Na barra lateral esquerda, clique em GitHub Apps .
- Ao lado do GitHub App com o qual deseja trabalhar, clique em Editar.
- Na página de configurações do aplicativo, encontre a ID do cliente para seu aplicativo. Você o usará posteriormente neste tutorial. Observe que a ID do cliente é diferente da ID do aplicativo.
Gravar a CLI
Essas etapas levam você à criação de uma CLI e ao uso do fluxo de dispositivo para obter um token de acesso do usuário. Para ir para o código final, confira "Exemplo de código completo".
Instalação
- 
Crie um arquivo Ruby para manter o código que gerará um token de acesso do usuário. Este tutorial dará ao arquivo o nome app_cli.rb.
- 
No terminal, no diretório em que app_cli.rbestá armazenado, execute o seguinte comando para tornarapp_cli.rbexecutável:Text chmod +x app_cli.rb chmod +x app_cli.rb
- 
Adicione essa linha à parte superior de app_cli.rbpara indicar que o interpretador do Ruby deve ser usado para executar o script:Ruby #!/usr/bin/env ruby #!/usr/bin/env ruby
- 
Adicione essas dependências à parte superior de app_cli.rb, depois de#!/usr/bin/env ruby:Ruby require "net/http" require "json" require "uri" require "fileutils" require "net/http" require "json" require "uri" require "fileutils"Todos eles fazem parte da biblioteca padrão Ruby, portanto, você não precisa instalar nenhuma joia. 
- 
Adicione a função maina seguir que servirá como um ponto de entrada. A função inclui uma instruçãocasepara executar ações diferentes dependendo de qual comando é especificado. Você expandirá essa instruçãocasemais tarde.Ruby def main case ARGV[0] when "help" puts "`help` is not yet defined" when "login" puts "`login` is not yet defined" when "whoami" puts "`whoami` is not yet defined" else puts "Unknown command `#{ARGV[0]}`" end enddef main case ARGV[0] when "help" puts "`help` is not yet defined" when "login" puts "`login` is not yet defined" when "whoami" puts "`whoami` is not yet defined" else puts "Unknown command `#{ARGV[0]}`" end end
- 
Na parte inferior do arquivo, adicione a linha a seguir para chamar a função de ponto de entrada. Essa chamada de função deve permanecer na parte inferior do arquivo à medida que você adiciona mais funções a esse arquivo mais adiante no tutorial. Ruby main main
- 
Opcionalmente, verifique seu progresso: Agora app_cli.rbterá esta aparência:Ruby #!/usr/bin/env ruby require "net/http" require "json" require "uri" require "fileutils" def main case ARGV[0] when "help" puts "`help` is not yet defined" when "login" puts "`login` is not yet defined" when "whoami" puts "`whoami` is not yet defined" else puts "Unknown command `#{ARGV[0]}`" end end main#!/usr/bin/env ruby require "net/http" require "json" require "uri" require "fileutils" def main case ARGV[0] when "help" puts "`help` is not yet defined" when "login" puts "`login` is not yet defined" when "whoami" puts "`whoami` is not yet defined" else puts "Unknown command `#{ARGV[0]}`" end end mainNo terminal, no diretório em que app_cli.rbestá armazenado, execute./app_cli.rb help. Você deverá ver este resultado:`help` is not yet definedVocê também pode testar seu script sem um comando ou com um comando sem tratamento. Por exemplo, ./app_cli.rb create-issuedeve gerar:Unknown command `create-issue`
Adicionar um comando help
- 
Adicionar a função helpa seguir aapp_cli.rb. Atualmente, a funçãohelpimprime uma linha para informar aos usuários que essa CLI usa um comando, "ajuda". Você expandirá essa funçãohelpmais tarde.Ruby def help puts "usage: app_cli <help>" end def help puts "usage: app_cli <help>" end
- 
Atualize a função mainpara chamar a funçãohelpquando o comandohelpfor dado:Ruby def main case ARGV[0] when "help" help when "login" puts "`login` is not yet defined" when "whoami" puts "`whoami` is not yet defined" else puts "Unknown command #{ARGV[0]}" end enddef main case ARGV[0] when "help" help when "login" puts "`login` is not yet defined" when "whoami" puts "`whoami` is not yet defined" else puts "Unknown command #{ARGV[0]}" end end
- 
Opcionalmente, verifique seu progresso: Agora app_cli.rbterá esta aparência. A ordem das funções não importa, desde que a chamada de funçãomainesteja no final do arquivo.Ruby #!/usr/bin/env ruby require "net/http" require "json" require "uri" require "fileutils" def help puts "usage: app_cli <help>" end def main case ARGV[0] when "help" help when "login" puts "`login` is not yet defined" when "whoami" puts "`whoami` is not yet defined" else puts "Unknown command #{ARGV[0]}" end end main#!/usr/bin/env ruby require "net/http" require "json" require "uri" require "fileutils" def help puts "usage: app_cli <help>" end def main case ARGV[0] when "help" help when "login" puts "`login` is not yet defined" when "whoami" puts "`whoami` is not yet defined" else puts "Unknown command #{ARGV[0]}" end end mainNo terminal, no diretório em que app_cli.rbestá armazenado, execute./app_cli.rb help. Você deverá ver este resultado:usage: app_cli <help>
Adicionar um comando login
O comando login executará o fluxo do dispositivo para obter um token de acesso do usuário. Para obter mais informações, confira "Como gerar um token de acesso do usuário para um GitHub App".
- 
Próximo à parte superior do arquivo, após as instruções require, adicione oCLIENT_IDde seus GitHub App como uma constante emapp_cli.rb. Para obter mais informações sobre como localizar a ID do cliente do aplicativo, confira "Obter a ID do cliente". SubstituaYOUR_CLIENT_IDpela ID do cliente do aplicativo:Ruby CLIENT_ID="YOUR_CLIENT_ID" CLIENT_ID="YOUR_CLIENT_ID"
- 
Adicionar a função parse_responsea seguir aapp_cli.rb. Essa função analisa uma resposta da API REST do GitHub. Quando a resposta status é200 OKou201 Created, a função retorna o corpo da resposta analisada. Caso contrário, a função imprime a resposta e o corpo e sai do programa.Ruby def parse_response(response) case response when Net::HTTPOK, Net::HTTPCreated JSON.parse(response.body) else puts response puts response.body exit 1 end enddef parse_response(response) case response when Net::HTTPOK, Net::HTTPCreated JSON.parse(response.body) else puts response puts response.body exit 1 end end
- 
Adicionar a função request_device_codea seguir aapp_cli.rb. Essa função faz uma solicitaçãoPOSTparahttp(s)://HOSTNAME/login/device/codee retorna a resposta.Ruby def request_device_code uri = URI("http(s)://HOSTNAME/login/device/code") parameters = URI.encode_www_form("client_id" => CLIENT_ID) headers = {"Accept" => "application/json"} response = Net::HTTP.post(uri, parameters, headers) parse_response(response) enddef request_device_code uri = URI("http(s)://HOSTNAME/login/device/code") parameters = URI.encode_www_form("client_id" => CLIENT_ID) headers = {"Accept" => "application/json"} response = Net::HTTP.post(uri, parameters, headers) parse_response(response) end
- 
Adicionar a função request_tokena seguir aapp_cli.rb. Essa função faz uma solicitaçãoPOSTparahttp(s)://HOSTNAME/login/oauth/access_tokene retorna a resposta.Ruby def request_token(device_code) uri = URI("http(s)://HOSTNAME/login/oauth/access_token") parameters = URI.encode_www_form({ "client_id" => CLIENT_ID, "device_code" => device_code, "grant_type" => "urn:ietf:params:oauth:grant-type:device_code" }) headers = {"Accept" => "application/json"} response = Net::HTTP.post(uri, parameters, headers) parse_response(response) enddef request_token(device_code) uri = URI("http(s)://HOSTNAME/login/oauth/access_token") parameters = URI.encode_www_form({ "client_id" => CLIENT_ID, "device_code" => device_code, "grant_type" => "urn:ietf:params:oauth:grant-type:device_code" }) headers = {"Accept" => "application/json"} response = Net::HTTP.post(uri, parameters, headers) parse_response(response) end
- 
Adicionar a função poll_for_tokena seguir aapp_cli.rb. Essa função sondahttp(s)://HOSTNAME/login/oauth/access_tokenno intervalo especificado até que GitHub responda com um parâmetroaccess_tokenem vez de um parâmetroerror. Em seguida, ele grava o token de acesso do usuário em um arquivo e restringe as permissões no arquivo.Ruby def poll_for_token(device_code, interval) loop do response = request_token(device_code) error, access_token = response.values_at("error", "access_token") if error case error when "authorization_pending" # The user has not yet entered the code. # Wait, then poll again. sleep interval next when "slow_down" # The app polled too fast. # Wait for the interval plus 5 seconds, then poll again. sleep interval + 5 next when "expired_token" # The `device_code` expired, and the process needs to restart. puts "The device code has expired. Please run `login` again." exit 1 when "access_denied" # The user cancelled the process. Stop polling. puts "Login cancelled by user." exit 1 else puts response exit 1 end end File.write("./.token", access_token) # Set the file permissions so that only the file owner can read or modify the file FileUtils.chmod(0600, "./.token") break end enddef poll_for_token(device_code, interval) loop do response = request_token(device_code) error, access_token = response.values_at("error", "access_token") if error case error when "authorization_pending" # The user has not yet entered the code. # Wait, then poll again. sleep interval next when "slow_down" # The app polled too fast. # Wait for the interval plus 5 seconds, then poll again. sleep interval + 5 next when "expired_token" # The `device_code` expired, and the process needs to restart. puts "The device code has expired. Please run `login` again." exit 1 when "access_denied" # The user cancelled the process. Stop polling. puts "Login cancelled by user." exit 1 else puts response exit 1 end end File.write("./.token", access_token) # Set the file permissions so that only the file owner can read or modify the file FileUtils.chmod(0600, "./.token") break end end
- 
Adicionar a função logina seguir.Esta função: - Chama a função request_device_codee obtém os parâmetrosverification_uri,user_code``device_codeeintervalda resposta.
- Solicita que os usuários insiram o user_codeda etapa anterior.
- Chama o poll_for_tokenpara sondar GitHub para obter um token de acesso.
- Informa ao usuário que a autenticação foi bem-sucedida.
 Ruby def login verification_uri, user_code, device_code, interval = request_device_code.values_at("verification_uri", "user_code", "device_code", "interval") puts "Please visit: #{verification_uri}" puts "and enter code: #{user_code}" poll_for_token(device_code, interval) puts "Successfully authenticated!" enddef login verification_uri, user_code, device_code, interval = request_device_code.values_at("verification_uri", "user_code", "device_code", "interval") puts "Please visit: #{verification_uri}" puts "and enter code: #{user_code}" poll_for_token(device_code, interval) puts "Successfully authenticated!" end
- Chama a função 
- 
Atualize a função mainpara chamar a funçãologinquando o comandologinfor dado:Ruby def main case ARGV[0] when "help" help when "login" login when "whoami" puts "`whoami` is not yet defined" else puts "Unknown command #{ARGV[0]}" end enddef main case ARGV[0] when "help" help when "login" login when "whoami" puts "`whoami` is not yet defined" else puts "Unknown command #{ARGV[0]}" end end
- 
Atualize a função helppara incluir o comandologin:Ruby def help puts "usage: app_cli <login | help>" end def help puts "usage: app_cli <login | help>" end
- 
Opcionalmente, verifique seu progresso: app_cli.rbagora se parece com isso, em queYOUR_CLIENT_IDé a ID do cliente do seu aplicativo. A ordem das funções não importa, desde que a chamada de funçãomainesteja no final do arquivo.Ruby #!/usr/bin/env ruby require "net/http" require "json" require "uri" require "fileutils" CLIENT_ID="YOUR_CLIENT_ID" def help puts "usage: app_cli <login | help>" end def main case ARGV[0] when "help" help when "login" login when "whoami" puts "`whoami` is not yet defined" else puts "Unknown command #{ARGV[0]}" end end def parse_response(response) case response when Net::HTTPOK, Net::HTTPCreated JSON.parse(response.body) else puts response puts response.body exit 1 end end def request_device_code uri = URI("http(s)://HOSTNAME/login/device/code") parameters = URI.encode_www_form("client_id" => CLIENT_ID) headers = {"Accept" => "application/json"} response = Net::HTTP.post(uri, parameters, headers) parse_response(response) end def request_token(device_code) uri = URI("http(s)://HOSTNAME/login/oauth/access_token") parameters = URI.encode_www_form({ "client_id" => CLIENT_ID, "device_code" => device_code, "grant_type" => "urn:ietf:params:oauth:grant-type:device_code" }) headers = {"Accept" => "application/json"} response = Net::HTTP.post(uri, parameters, headers) parse_response(response) end def poll_for_token(device_code, interval) loop do response = request_token(device_code) error, access_token = response.values_at("error", "access_token") if error case error when "authorization_pending" # The user has not yet entered the code. # Wait, then poll again. sleep interval next when "slow_down" # The app polled too fast. # Wait for the interval plus 5 seconds, then poll again. sleep interval + 5 next when "expired_token" # The `device_code` expired, and the process needs to restart. puts "The device code has expired. Please run `login` again." exit 1 when "access_denied" # The user cancelled the process. Stop polling. puts "Login cancelled by user." exit 1 else puts response exit 1 end end File.write("./.token", access_token) # Set the file permissions so that only the file owner can read or modify the file FileUtils.chmod(0600, "./.token") break end end def login verification_uri, user_code, device_code, interval = request_device_code.values_at("verification_uri", "user_code", "device_code", "interval") puts "Please visit: #{verification_uri}" puts "and enter code: #{user_code}" poll_for_token(device_code, interval) puts "Successfully authenticated!" end main#!/usr/bin/env ruby require "net/http" require "json" require "uri" require "fileutils" CLIENT_ID="YOUR_CLIENT_ID" def help puts "usage: app_cli <login | help>" end def main case ARGV[0] when "help" help when "login" login when "whoami" puts "`whoami` is not yet defined" else puts "Unknown command #{ARGV[0]}" end end def parse_response(response) case response when Net::HTTPOK, Net::HTTPCreated JSON.parse(response.body) else puts response puts response.body exit 1 end end def request_device_code uri = URI("http(s)://HOSTNAME/login/device/code") parameters = URI.encode_www_form("client_id" => CLIENT_ID) headers = {"Accept" => "application/json"} response = Net::HTTP.post(uri, parameters, headers) parse_response(response) end def request_token(device_code) uri = URI("http(s)://HOSTNAME/login/oauth/access_token") parameters = URI.encode_www_form({ "client_id" => CLIENT_ID, "device_code" => device_code, "grant_type" => "urn:ietf:params:oauth:grant-type:device_code" }) headers = {"Accept" => "application/json"} response = Net::HTTP.post(uri, parameters, headers) parse_response(response) end def poll_for_token(device_code, interval) loop do response = request_token(device_code) error, access_token = response.values_at("error", "access_token") if error case error when "authorization_pending" # The user has not yet entered the code. # Wait, then poll again. sleep interval next when "slow_down" # The app polled too fast. # Wait for the interval plus 5 seconds, then poll again. sleep interval + 5 next when "expired_token" # The `device_code` expired, and the process needs to restart. puts "The device code has expired. Please run `login` again." exit 1 when "access_denied" # The user cancelled the process. Stop polling. puts "Login cancelled by user." exit 1 else puts response exit 1 end end File.write("./.token", access_token) # Set the file permissions so that only the file owner can read or modify the file FileUtils.chmod(0600, "./.token") break end end def login verification_uri, user_code, device_code, interval = request_device_code.values_at("verification_uri", "user_code", "device_code", "interval") puts "Please visit: #{verification_uri}" puts "and enter code: #{user_code}" poll_for_token(device_code, interval) puts "Successfully authenticated!" end main- 
No terminal, no diretório em que app_cli.rbestá armazenado, execute./app_cli.rb login. Você deverá ver uma saída semelhante a esta. O código será diferente sempre que:Please visit: http(s)://HOSTNAME/login/device and enter code: CA86-8D94
- 
Navegue até http(s)://HOSTNAME/login/device no navegador e insira o código da etapa anterior e clique em Continuar. 
- 
GitHub deve exibir uma página que solicita que você autorize seu aplicativo. Clique no botão "Autorizar". 
- 
Seu terminal agora deve dizer "Autenticado com êxito!". 
 
- 
Adicionar um comando whoami
Agora que seu aplicativo pode gerar um token de acesso do usuário, você pode fazer solicitações de API em nome do usuário. Adicione um comando whoami para obter o nome de usuário do usuário autenticado.
- 
Adicionar a função whoamia seguir aapp_cli.rb. Esta função obtém informações sobre o usuário com o ponto de extremidade/userda API REST. Ele gera o nome de usuário que corresponde ao token de acesso do usuário. Se o arquivo.tokennão tiver sido encontrado, ele solicitará que o usuário execute a funçãologin.Ruby def whoami uri = URI("http(s)://HOSTNAME/api/v3/user") begin token = File.read("./.token").strip rescue Errno::ENOENT => e puts "You are not authorized. Run the `login` command." exit 1 end response = Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http| body = {"access_token" => token}.to_json headers = {"Accept" => "application/vnd.github+json", "Authorization" => "Bearer #{token}"} http.send_request("GET", uri.path, body, headers) end parsed_response = parse_response(response) puts "You are #{parsed_response["login"]}" enddef whoami uri = URI("http(s)://HOSTNAME/api/v3/user") begin token = File.read("./.token").strip rescue Errno::ENOENT => e puts "You are not authorized. Run the `login` command." exit 1 end response = Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http| body = {"access_token" => token}.to_json headers = {"Accept" => "application/vnd.github+json", "Authorization" => "Bearer #{token}"} http.send_request("GET", uri.path, body, headers) end parsed_response = parse_response(response) puts "You are #{parsed_response["login"]}" end
- 
Atualize a função parse_responsepara lidar com o caso em que o token expirou ou foi revogado. Agora, se você receber uma resposta401 Unauthorized, a CLI solicitará que o usuário execute o comandologin.Ruby def parse_response(response) case response when Net::HTTPOK, Net::HTTPCreated JSON.parse(response.body) when Net::HTTPUnauthorized puts "You are not authorized. Run the `login` command." exit 1 else puts response puts response.body exit 1 end enddef parse_response(response) case response when Net::HTTPOK, Net::HTTPCreated JSON.parse(response.body) when Net::HTTPUnauthorized puts "You are not authorized. Run the `login` command." exit 1 else puts response puts response.body exit 1 end end
- 
Atualize a função mainpara chamar a funçãowhoamiquando o comandowhoamifor dado:Ruby def main case ARGV[0] when "help" help when "login" login when "whoami" whoami else puts "Unknown command #{ARGV[0]}" end enddef main case ARGV[0] when "help" help when "login" login when "whoami" whoami else puts "Unknown command #{ARGV[0]}" end end
- 
Atualize a função helppara incluir o comandowhoami:Ruby def help puts "usage: app_cli <login | whoami | help>" end def help puts "usage: app_cli <login | whoami | help>" end
- 
Verifique o código em relação ao exemplo de código completo na próxima seção. Teste o código seguindo as etapas descritas na seção "Teste" abaixo do exemplo de código completo. 
Exemplo de código completo
Este é o exemplo de código completo descrito na seção anterior. Substitua YOUR_CLIENT_ID pela ID do cliente do aplicativo.
#!/usr/bin/env ruby
require "net/http"
require "json"
require "uri"
require "fileutils"
CLIENT_ID="YOUR_CLIENT_ID"
def help
  puts "usage: app_cli <login | whoami | help>"
end
def main
  case ARGV[0]
  when "help"
    help
  when "login"
    login
  when "whoami"
    whoami
  else
    puts "Unknown command #{ARGV[0]}"
  end
end
def parse_response(response)
  case response
  when Net::HTTPOK, Net::HTTPCreated
    JSON.parse(response.body)
  when Net::HTTPUnauthorized
    puts "You are not authorized. Run the `login` command."
    exit 1
  else
    puts response
    puts response.body
    exit 1
  end
end
def request_device_code
  uri = URI("http(s)://HOSTNAME/login/device/code")
  parameters = URI.encode_www_form("client_id" => CLIENT_ID)
  headers = {"Accept" => "application/json"}
  response = Net::HTTP.post(uri, parameters, headers)
  parse_response(response)
end
def request_token(device_code)
  uri = URI("http(s)://HOSTNAME/login/oauth/access_token")
  parameters = URI.encode_www_form({
    "client_id" => CLIENT_ID,
    "device_code" => device_code,
    "grant_type" => "urn:ietf:params:oauth:grant-type:device_code"
  })
  headers = {"Accept" => "application/json"}
  response = Net::HTTP.post(uri, parameters, headers)
  parse_response(response)
end
def poll_for_token(device_code, interval)
  loop do
    response = request_token(device_code)
    error, access_token = response.values_at("error", "access_token")
    if error
      case error
      when "authorization_pending"
        # The user has not yet entered the code.
        # Wait, then poll again.
        sleep interval
        next
      when "slow_down"
        # The app polled too fast.
        # Wait for the interval plus 5 seconds, then poll again.
        sleep interval + 5
        next
      when "expired_token"
        # The `device_code` expired, and the process needs to restart.
        puts "The device code has expired. Please run `login` again."
        exit 1
      when "access_denied"
        # The user cancelled the process. Stop polling.
        puts "Login cancelled by user."
        exit 1
      else
        puts response
        exit 1
      end
    end
    File.write("./.token", access_token)
    # Set the file permissions so that only the file owner can read or modify the file
    FileUtils.chmod(0600, "./.token")
    break
  end
end
def login
  verification_uri, user_code, device_code, interval = request_device_code.values_at("verification_uri", "user_code", "device_code", "interval")
  puts "Please visit: #{verification_uri}"
  puts "and enter code: #{user_code}"
  poll_for_token(device_code, interval)
  puts "Successfully authenticated!"
end
def whoami
  uri = URI("http(s)://HOSTNAME/api/v3/user")
  begin
    token = File.read("./.token").strip
  rescue Errno::ENOENT => e
    puts "You are not authorized. Run the `login` command."
    exit 1
  end
  response = Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
    body = {"access_token" => token}.to_json
    headers = {"Accept" => "application/vnd.github+json", "Authorization" => "Bearer #{token}"}
    http.send_request("GET", uri.path, body, headers)
  end
  parsed_response = parse_response(response)
  puts "You are #{parsed_response["login"]}"
end
main
#!/usr/bin/env ruby
require "net/http"
require "json"
require "uri"
require "fileutils"
CLIENT_ID="YOUR_CLIENT_ID"
def help
  puts "usage: app_cli <login | whoami | help>"
end
def main
  case ARGV[0]
  when "help"
    help
  when "login"
    login
  when "whoami"
    whoami
  else
    puts "Unknown command #{ARGV[0]}"
  end
end
def parse_response(response)
  case response
  when Net::HTTPOK, Net::HTTPCreated
    JSON.parse(response.body)
  when Net::HTTPUnauthorized
    puts "You are not authorized. Run the `login` command."
    exit 1
  else
    puts response
    puts response.body
    exit 1
  end
end
def request_device_code
  uri = URI("http(s)://HOSTNAME/login/device/code")
  parameters = URI.encode_www_form("client_id" => CLIENT_ID)
  headers = {"Accept" => "application/json"}
  response = Net::HTTP.post(uri, parameters, headers)
  parse_response(response)
end
def request_token(device_code)
  uri = URI("http(s)://HOSTNAME/login/oauth/access_token")
  parameters = URI.encode_www_form({
    "client_id" => CLIENT_ID,
    "device_code" => device_code,
    "grant_type" => "urn:ietf:params:oauth:grant-type:device_code"
  })
  headers = {"Accept" => "application/json"}
  response = Net::HTTP.post(uri, parameters, headers)
  parse_response(response)
end
def poll_for_token(device_code, interval)
  loop do
    response = request_token(device_code)
    error, access_token = response.values_at("error", "access_token")
    if error
      case error
      when "authorization_pending"
        # The user has not yet entered the code.
        # Wait, then poll again.
        sleep interval
        next
      when "slow_down"
        # The app polled too fast.
        # Wait for the interval plus 5 seconds, then poll again.
        sleep interval + 5
        next
      when "expired_token"
        # The `device_code` expired, and the process needs to restart.
        puts "The device code has expired. Please run `login` again."
        exit 1
      when "access_denied"
        # The user cancelled the process. Stop polling.
        puts "Login cancelled by user."
        exit 1
      else
        puts response
        exit 1
      end
    end
    File.write("./.token", access_token)
    # Set the file permissions so that only the file owner can read or modify the file
    FileUtils.chmod(0600, "./.token")
    break
  end
end
def login
  verification_uri, user_code, device_code, interval = request_device_code.values_at("verification_uri", "user_code", "device_code", "interval")
  puts "Please visit: #{verification_uri}"
  puts "and enter code: #{user_code}"
  poll_for_token(device_code, interval)
  puts "Successfully authenticated!"
end
def whoami
  uri = URI("http(s)://HOSTNAME/api/v3/user")
  begin
    token = File.read("./.token").strip
  rescue Errno::ENOENT => e
    puts "You are not authorized. Run the `login` command."
    exit 1
  end
  response = Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
    body = {"access_token" => token}.to_json
    headers = {"Accept" => "application/vnd.github+json", "Authorization" => "Bearer #{token}"}
    http.send_request("GET", uri.path, body, headers)
  end
  parsed_response = parse_response(response)
  puts "You are #{parsed_response["login"]}"
end
main
Testando
Este tutorial pressupõe que o código do aplicativo esteja armazenado em um arquivo chamado app_cli.rb.
- 
No terminal, no diretório em que app_cli.rbestá armazenado, execute./app_cli.rb help. Você deverá ver uma saída semelhante a esta.usage: app_cli <login | whoami | help>
- 
No terminal, no diretório em que app_cli.rbestá armazenado, execute./app_cli.rb login. Você deverá ver uma saída semelhante a esta. O código será diferente sempre que:Please visit: http(s)://HOSTNAME/login/device and enter code: CA86-8D94
- 
Navegue até http(s)://HOSTNAME/login/device no navegador e insira o código da etapa anterior e clique em Continuar. 
- 
GitHub deve exibir uma página que solicita que você autorize seu aplicativo. Clique no botão "Autorizar". 
- 
Seu terminal agora deve dizer "Autenticado com êxito!". 
- 
No terminal, no diretório em que app_cli.rbestá armazenado, execute./app_cli.rb whoami. Você deverá ver uma saída semelhante a esta, em queoctocaté seu nome de usuário.You are octocat
- 
Abra o arquivo .tokenno editor e modifique o token. Agora, o token é inválido.
- 
No terminal, no diretório em que app_cli.rbestá armazenado, execute./app_cli.rb whoami. Você deverá ver uma saída parecida com esta:You are not authorized. Run the `login` command.
- 
Exclua o arquivo .token.
- 
No terminal, no diretório em que app_cli.rbestá armazenado, execute./app_cli.rb whoami. Você deverá ver uma saída semelhante a esta:You are not authorized. Run the `login` command.
Próximas etapas
Ajustar o código de acordo com as necessidades do aplicativo
Este tutorial demonstrou como escrever uma CLI que usa o fluxo do dispositivo para gerar um token de acesso do usuário. Você pode expandir essa CLI para aceitar comandos adicionais. Por exemplo, você pode adicionar um comando create-issue que abre um problema. Lembre-se de atualizar as permissões do aplicativo se o aplicativo precisar de permissões adicionais para as solicitações de API que você deseja fazer. Para obter mais informações, confira "Escolhendo permissões para um Aplicativo GitHub".
Armazenar tokens com segurança
Este tutorial gera um token de acesso do usuário e o salva em um arquivo local. Você nunca deve confirmar esse arquivo ou divulgar o token.
Dependendo do seu dispositivo, você pode escolher diferentes maneiras de armazenar o token. Você deve marcar as práticas recomendadas para armazenar tokens em seu dispositivo.
Para obter mais informações, confira "Práticas recomendadas para criar um aplicativo do GitHub".
Seguir as práticas recomendadas
Você deve ter como objetivo seguir as melhores práticas com seu GitHub App. Para obter mais informações, confira "Práticas recomendadas para criar um aplicativo do GitHub".