Last week I wrote a tool to find a Github user’s e-mail address from their commits. One of the annoyances of the original implementation was the need to manually generate a personal Github access token and store it. However, I’ve seen other tools (eg. ghi) ask for credentials and store the token in the OSX keychain. How does one accomplish that? We’re going to improve a bit upon ghi’s code.
Prompt for a Username and Password
Reading a username is fairly straightforward with $stdin.gets.chomp. We can improve a bit upon it and fetch it from git config instead.
We don’t want to echo passwords.
Note how we change stty, support backspace, echo * and bail on Ctrl + C.
Authenticate Against Github with 2FA
We can use github_api to authenticate against Github with a username and password.
However, most users now hopefully have two-factor authentication enabled. Github auth will fail with Github::Error::Unauthorized and return a X-GitHub-OTP header with the value of required; app to signal that a 2FA code is required. The latter will need to be sent back in the X-GitHub-OTP header.
Create a Github Token
To create a token we supply a note that uniquely identifies it on the Github personal tokens page. Once created tokens cannot be retrieved, so we will store the value locally. To uniquely identify tokens we include the local host name in the note.
We recurse with 2FA until a token can be successfully created with auth.create or an error occurs. One such interesting error is when trying to create a token that already exists. Since token values cannot be obtained after creation, we must tell the user to delete the token manually. And we don’t want to delete the token automatically because it will possibly break another app instance that has created it.
Storing in Keychain
We use the command-line security add-internet-password tool to store Internet passwords in Keychain and security find-internet-password to retrieve one.
Putting It Together
Running the tool the second time no longer prompts for credentials!
See fue@6937a4 for the rest of implementation details.