简介
本教程演示如何生成由 GitHub App 提供支持的命令行接口 (CLI),以及如何使用设备流为应用生成用户访问令牌。
CLI 将有三个命令:
- :输出使用说明。
- :生成一个用户访问令牌,应用可以使用该令牌代表用户发出 API 请求。
- :返回有关登录用户的信息。
本教程使用 Ruby,但你可以编写 CLI 并使用设备流通过任何编程语言生成用户访问令牌。
关于设备流和用户访问令牌
CLI 将使用设备流对用户进行身份验证并生成用户访问令牌。 然后,CLI 可以使用用户访问令牌代表经过身份验证的用户发出 API 请求。
如果你想要将应用操作归因于用户,应用应使用用户访问令牌。 有关详细信息,请参阅“AUTOTITLE”。
可通过两种方式为 GitHub App 生成用户访问令牌:Web 应用程序流和设备流。 如果应用是无界面应用或无法访问 Web 接口,你应使用设备流来生成用户访问令牌。 例如,CLI 工具、简单的 Raspberry Pi 和桌面应用程序应使用设备流。 如果应用有权访问 Web 接口,则应改用 Web 应用流。 有关详细信息,请参阅 AUTOTITLE 和 AUTOTITLE。
先决条件
本教程假定你已注册 GitHub App。 如果您想了解如何注册 GitHub App,请参阅 AUTOTITLE。
在按照本教程操作之前,必须为应用启用设备流。 有关为应用启用设备流的详细信息,请参阅“AUTOTITLE”。
本教程假定你对 Ruby 有基本的了解。 有关详细信息,请参阅 Ruby。
获取客户端 ID
需要应用的客户端 ID 才能通过设备流生成用户访问令牌。
- 在 GitHub 上任意页的右上角,单击你的个人资料图片。
- 导航到你的帐户设置。
- 对于由个人帐户拥有的应用,请单击“设置”****。
- 对于组织拥有的应用:
- 单击“你的组织”。
- 在组织的右侧,单击设置。 1. 在左边栏中,单击 “Developer settings”****。
- 在左侧边栏中,单击“GitHub Apps”。
- 在要使用的 GitHub App 旁边,单击“编辑”。
- 在应用的“设置”页上,找到应用的客户端 ID。 本教程后面部分将使用它。 请注意,客户端 ID 不同于应用程序 ID。
编写命令行界面(CLI)
这些步骤将引导你生成 CLI 并使用设备流获取用户访问令牌。 若要跳到最终代码,请参阅完整代码示例。
安装
-
创建 Ruby 文件以保存将生成用户访问令牌的代码。 本教程将该文件命名为 。
-
在终端中,从存储 的目录中运行以下命令,使 可执行:
Text chmod +x app_cli.rb
chmod +x app_cli.rb -
将以下行添加到 顶部,以指示应使用 Ruby 解释器运行脚本:
Ruby #!/usr/bin/env ruby
#!/usr/bin/env ruby -
将这些依赖项添加到 顶部,如下所示 :
Ruby require "net/http" require "json" require "uri" require "fileutils"
require "net/http" require "json" require "uri" require "fileutils"这些都是 Ruby 标准库的一部分,因此无需安装任何 gem。
-
添加下面的 函数作为入口点。 该函数包含一个 语句,用于根据指定的命令执行不同操作。 稍后您将展开此语句。
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 -
在文件底部,添加以下行以调用入口点函数。 在本教程后面向此文件添加更多函数时,此函数调用应保留在文件底部。
Ruby main
main -
(可选)检查进度:
现在看起来是这样的:
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 main在终端中,从存储 的目录中运行 。 你应该会看到以下输出:
`help` is not yet defined还可以在不使用命令或使用未经处理的命令的情况下测试脚本。 例如, 应输出:
Unknown command `create-issue`
添加 命令
-
向 添加以下的函数。 目前, 函数会打印一行,告知用户此 CLI 需要一个命令“help”。 稍后将扩展此函数。
Ruby def help puts "usage: app_cli <help>" end
def help puts "usage: app_cli <help>" end -
更新 函数,以在给定 命令时调用 函数:
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 -
(可选)检查进度:
现在看起来是这样的。 只要 函数调用位于文件末尾,函数的顺序就无关紧要。
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 main在终端中,从存储 的目录中运行 。 你应该会看到以下输出:
usage: app_cli <help>
添加 命令
命令将运行设备流以获取用户访问令牌。 有关详细信息,请参阅“AUTOTITLE”。
-
在文件顶部附近,在 语句之后,将 GitHub App 的 添加为 中的常量。 有关查找应用客户端 ID 的详细信息,请参阅获取客户端 ID。 将 替换为应用的客户端 ID:
Ruby CLIENT_ID="YOUR_CLIENT_ID"
CLIENT_ID="YOUR_CLIENT_ID" -
向 添加以下 函数。 此函数分析来自 GitHub REST API 的响应。 当响应状态为 或 时,函数将返回分析的响应正文。 否则,函数将输出响应和正文,并退出程序。
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 -
向 添加以下 函数。 此函数向 发出 请求并返回响应。
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 -
向 添加以下 函数。 此函数向 发出 请求并返回响应。
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 -
向……添加以下函数。 此函数按指定的间隔进行轮询,直到 GitHub 使用某个参数响应,而不是使用另一个参数响应。 然后,它将用户访问令牌写入文件并限制该文件的权限。
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 -
添加以下 函数。
此函数:
- 调用 函数并从响应中获取 、、 和 参数。
- 提示用户输入上一步中的 。
- 调用 轮询 GitHub 以获取访问令牌。
- 让用户知道身份验证成功。
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 -
更新 函数,以在给定 命令时调用 函数:
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 -
更新 函数以包含 命令:
Ruby def help puts "usage: app_cli <login | help>" end
def help puts "usage: app_cli <login | help>" end -
(可选)检查进度:
现在如下所示,其中 是应用的客户端 ID。 只要 函数调用位于文件末尾,函数的顺序就无关紧要。
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-
在终端中,从存储 的目录中运行 。 应该会看到如下所示的输出。 每次代码都会有所不同:
Please visit: http(s)://HOSTNAME/login/device and enter code: CA86-8D94 -
在浏览器中导航到 http(s)://HOSTNAME/login/device,输入上一步中的代码,然后单击“继续”。
-
GitHub 应该会显示一个页面,提示你授权应用。 单击“授权”按钮。
-
终端现在应显示“已成功进行身份验证!”。
-
添加 命令
现在,应用可以生成用户访问令牌,你可以代表用户发出 API 请求。 添加 命令以获取经过身份验证的用户的用户名。
-
向……添加以下函数。 此函数获取有关使用 REST API 终结点的用户的信息。 它输出与用户访问令牌对应的用户名。 如果未找到 文件,它将提示用户运行 函数。
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 -
更新 函数以处理令牌已过期或已吊销的情况。 现在,如果收到 响应,CLI 将提示用户运行 命令。
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 -
更新 函数,以在给定 命令时调用 函数:
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 -
更新 函数以包含 命令:
Ruby def help puts "usage: app_cli <login | whoami | help>" end
def help puts "usage: app_cli <login | whoami | help>" end -
请将您的代码与下一部分的完整示例代码进行比对。 可以按照完整代码示例下方测试一节中概述的步骤测试代码。
完整代码示例
这是上一部分概述的完整代码示例。 将 替换为应用的客户端 ID。
#!/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
测试
本教程假定应用代码存储在名为 的文件中。
-
在终端中,从存储 的目录中运行 。 应该会看到如下所示的输出。
usage: app_cli <login | whoami | help> -
在终端中,从存储 的目录中运行 。 应该会看到如下所示的输出。 每次代码都会有所不同:
Please visit: http(s)://HOSTNAME/login/device and enter code: CA86-8D94 -
在浏览器中导航到 http(s)://HOSTNAME/login/device,输入上一步中的代码,然后单击“继续”。
-
GitHub 应该会显示一个页面,提示你授权应用。 单击“授权”按钮。
-
终端现在应显示“已成功进行身份验证!”。
-
在终端中,从存储 的目录中运行 。 你应该看到类似这样的输出,其中是你的用户名。
You are octocat -
在编辑器中打开 文件,并修改令牌。 现在,令牌无效。
-
在终端中,从存储 的目录中运行 。 应该会看到如下所示的输出:
You are not authorized. Run the `login` command. -
删除 文件,
-
在终端中,从存储 的目录中运行 。 应该会看到如下所示的输出:
You are not authorized. Run the `login` command.
后续步骤
调整代码以满足应用的需求
本教程演示如何编写使用设备流生成用户访问令牌的 CLI。 可以展开此 CLI 以接受其他命令。 例如,您可以添加一个命令来打开一个问题。 针对你要发出的 API 请求,如果应用需要其他权限,请记得更新应用的权限。 有关详细信息,请参阅“AUTOTITLE”。
安全地存储令牌
本教程将生成用户访问令牌并将其保存在本地文件中。 切勿提交此文件或公开令牌。
根据设备,可以选择不同的方法来存储令牌。 应检查在设备上存储令牌的最佳做法。
有关详细信息,请参阅“AUTOTITLE”。
遵循最佳做法
你的目标应该是遵循 GitHub App 的最佳做法。 有关详细信息,请参阅“AUTOTITLE”。