diff --git a/package.json b/package.json index 0f5dc755e..a7dc29d4c 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "nak": "0.2.12", "spellchecker": "0.3.0", "pathwatcher": "0.3.0", + "keytar": "0.2.0", "plist": "git://github.com/nathansobo/node-plist.git", "space-pen": "git://github.com/nathansobo/space-pen.git", "less": "git://github.com/nathansobo/less.js.git", diff --git a/src/packages/github-sign-in/keymaps/github-sign-in.cson b/src/packages/github-sign-in/keymaps/github-sign-in.cson new file mode 100644 index 000000000..432c041fb --- /dev/null +++ b/src/packages/github-sign-in/keymaps/github-sign-in.cson @@ -0,0 +1,9 @@ +'.sign-in-view': + 'esc': 'core:cancel' +'.sign-in-view input': + 'tab': 'next-field' + 'meta-enter': 'core:confirm' +'.sign-in-view button': + 'tab': 'next-field' + 'enter': 'core:confirm' + 'meta-enter': 'core:confirm' diff --git a/src/packages/github-sign-in/lib/sign-in-view.coffee b/src/packages/github-sign-in/lib/sign-in-view.coffee new file mode 100644 index 000000000..50357bb8b --- /dev/null +++ b/src/packages/github-sign-in/lib/sign-in-view.coffee @@ -0,0 +1,111 @@ +$ = require 'jquery' +_ = require 'underscore' +ScrollView = require 'scroll-view' +keytar = require 'keytar' + +module.exports = +class SignInView extends ScrollView + @activate: -> + new SignInView() + + @content: -> + @div class: 'sign-in-view overlay from-top', => + @h4 'Sign in to GitHub' + @p 'Your password will only be used to generate a token that will be stored in your keychain.' + @div class: 'form-inline', => + @input outlet: 'username', type: 'text', placeholder: 'Username or Email' + @input outlet: 'password', type: 'password', placeholder: 'Password' + @button outlet: 'signIn', class: 'btn', disabled: 'disabled', 'Sign in' + @button outlet: 'cancel', class: 'btn', 'Cancel' + @div outlet: 'alert', class: 'alert alert-error' + + initialize: -> + rootView.command 'github:sign-in', => @attach() + + @username.on 'next-field', => @password.focus() + @username.on 'core:confirm', => @generateOAuth2Token() + @username.on 'input', => @validate() + + @password.on 'next-field', => + if @isElementEnabled(@signIn) + @signIn.focus() + else + @cancel.focus() + @password.on 'core:confirm', => @generateOAuth2Token() + @password.on 'input', => @validate() + + @signIn.on 'next-field', => @cancel.focus() + @signIn.on 'core:confirm', => @generateOAuth2Token() + @signIn.on 'click', => @generateOAuth2Token() + + @cancel.on 'next-field', => @username.focus() + @cancel.on 'core:confirm', => @generateOAuth2Token() + + @cancel.on 'click', => @detach() + @on 'core:cancel', => @detach() + + @subscribe $(document.body), 'click', (e) => + @detach() unless $.contains(this[0], e.target) + + @subscribe $(document.body), 'focusin', (e) => + @detach() unless $.contains(this[0], e.target) + + validate: -> + canSignIn = $.trim(@username.val()).length > 0 and @password.val().length > 0 + @setElementEnabled(@signIn, canSignIn) + + setElementEnabled: (element, enabled=true) -> + if enabled + element.removeAttr('disabled') + else + element.attr('disabled', 'disabled') + + isElementEnabled: (element) -> + element.attr('disabled') isnt 'disabled' + + generateOAuth2Token: -> + return unless @isElementEnabled(@signIn) + + @alert.hide() + @setElementEnabled(@username, false) + @setElementEnabled(@password, false) + @setElementEnabled(@signIn, false) + + credentials = btoa("#{$.trim(@username.val())}:#{@password.val()}") + request = + scopes: ['user', 'repo', 'gist'] + note: 'GitHub Atom' + note_url: 'https://github.com/github/atom' + $.ajax + url: 'https://api.github.com/authorizations' + type: 'POST' + dataType: 'json' + contentType: 'application/json; charset=UTF-8' + data: JSON.stringify(request) + + beforeSend: (xhr) -> + xhr.setRequestHeader('Authorization', "Basic #{credentials}") + + success: ({token}={}) => + if token?.length > 0 + unless keytar.replacePassword('GitHub.com', 'github', token) + console.log 'Unable to save GitHub.com token to keychain' + @detach() + + error: (response) => + if _.isString(response.responseText) + try + message = JSON.parse(response.responseText)?.message + catch e + message = '' + else + message = response.responseText?.message + message ?= '' + @alert.text(message).show() + + attach: -> + @username.val('') + @password.val('') + @alert.hide() + rootView.append(this) + @username.focus() diff --git a/src/packages/github-sign-in/package.cson b/src/packages/github-sign-in/package.cson new file mode 100644 index 000000000..ca98e93d8 --- /dev/null +++ b/src/packages/github-sign-in/package.cson @@ -0,0 +1,4 @@ +'main': 'lib/sign-in-view' +'activationEvents': [ + 'github:sign-in' +] diff --git a/src/packages/github-sign-in/stylesheets/github-sign-in.less b/src/packages/github-sign-in/stylesheets/github-sign-in.less new file mode 100644 index 000000000..762b7a1b7 --- /dev/null +++ b/src/packages/github-sign-in/stylesheets/github-sign-in.less @@ -0,0 +1,14 @@ +.sign-in-view { + input, button, .alert { + margin-bottom: 10px; + } + + button { + margin-right: 10px; + } + + .alert:before { + content: 'Sign in failed. '; + font-weight: bold; + } +}