Last active
July 2, 2017 09:45
-
-
Save chancancode/fdc58ea5e94059288ffddb4e400ddeed to your computer and use it in GitHub Desktop.
New Twiddle
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Ember from 'ember'; | |
import { task } from 'ember-concurrency'; | |
const { inject: { service } } = Ember; | |
export default Ember.Controller.extend({ | |
github: service() | |
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Ember from 'ember'; | |
import { task } from 'ember-concurrency'; | |
import { E_BAD_CREDENTIALS, E_BAD_OTP } from '../services/github'; | |
const { computed: { and, equal, not }, inject: { service } } = Ember; | |
export default Ember.Controller.extend({ | |
github: service(), | |
username: null, | |
password: null, | |
otp: null, | |
hasSignIn: and('username', 'password'), | |
disableSignIn: not('hasSignIn'), | |
disableVerify: not('otp'), | |
step: 'credentials', | |
isCredentials: equal('step', 'credentials'), | |
isOTP: equal('step', 'otp'), | |
submit: task(function * () { | |
let { | |
github, username, password, otp, isOTP | |
} = this.getProperties( | |
'github', 'username', 'password', 'otp', 'isOTP' | |
); | |
try { | |
let result = yield github.get('login').perform(username, password, otp); | |
this.setProperties({ | |
username: null, | |
password: null, | |
otp: null, | |
step: 'credentials' | |
}); | |
this.transitionToRoute('index'); | |
} catch(error) { | |
if (error.message === E_BAD_CREDENTIALS) { | |
this.setProperties({ | |
step: 'credentials', | |
password: null, | |
otp: null | |
}); | |
throw new Error('Incorrect username or password.'); | |
} else if (error.message === E_BAD_OTP) { | |
this.setProperties({ | |
step: 'otp', | |
otp: null | |
}); | |
if (isOTP) { | |
throw new Error('Two-factor authentication failed.'); | |
} else { | |
return; | |
} | |
} | |
throw error; | |
} | |
}).restartable() | |
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Ember from 'ember'; | |
import config from './config/environment'; | |
const Router = Ember.Router.extend({ | |
location: 'none', | |
rootURL: config.rootURL | |
}); | |
Router.map(function() { | |
this.route('login'); | |
}); | |
export default Router; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Ember from 'ember'; | |
const { inject: { service } } = Ember; | |
export default Ember.Route.extend({ | |
github: service(), | |
async beforeModel() { | |
try { | |
await this.get('github.validateToken').perform(); | |
} catch(error) { | |
this.replaceWith('login'); | |
} | |
} | |
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Ember from 'ember'; | |
import { isUnauthorizedError } from 'ember-ajax/errors'; | |
import { task } from 'ember-concurrency'; | |
const { inject: { service } } = Ember; | |
const TOKEN_NAME = 'Ember Core Dashboard'; | |
const GITHUB_HOST = 'https://api.github.com'; | |
const GITHUB_HEADERS = Object.freeze({ | |
'Content-Type': 'application/vnd.github.v3+json' | |
}); | |
export const E_BAD_CREDENTIALS = 'BAD CREDENTIALS'; | |
export const E_BAD_OTP = 'BAD OTP'; | |
export const E_BAD_TOKEN = 'BAD TOKEN'; | |
export default Ember.Service.extend({ | |
ajax: service(), | |
token: null, // Hardcode a token here to make development easier | |
login: task(function * (username, password, otp) { | |
this.set('token', null); | |
let ajax = this.get('ajax'); | |
try { | |
let url = `${GITHUB_HOST}/authorizations`; | |
let auth = btoa(`${username}:${password}`); | |
let headers = { | |
'Authorization': `Basic ${auth}`, | |
'X-GitHub-OTP': otp, | |
...GITHUB_HEADERS | |
}; | |
while (true) { | |
let response = yield ajax.raw(url, { headers }); | |
let token = response.payload.find(t => t.app.name === TOKEN_NAME); | |
if (token) { | |
url = `${GITHUB_HOST}/authorizations/${token.id}`; | |
yield ajax.raw(url, { | |
method: 'DELETE', | |
headers | |
}); | |
break; | |
} | |
let match = response.jqXHR.getResponseHeader("Link").match(/<(.+)>; rel="next"/); | |
if (match) { | |
url = match[1]; | |
} else { | |
break; | |
} | |
} | |
url = `${GITHUB_HOST}/authorizations`; | |
let response = yield ajax.raw(url, { | |
method: 'POST', | |
headers, | |
data: JSON.stringify({ | |
note: 'Ember Core Dashboard' | |
}) | |
}); | |
this.set('token', response.payload.token); | |
return true; | |
} catch(error) { | |
if (isUnauthorizedError(error.response)) { | |
if (error.jqXHR.getResponseHeader('X-GitHub-OTP')) { | |
throw new Error(E_BAD_OTP); | |
} else { | |
throw new Error(E_BAD_CREDENTIALS); | |
} | |
} | |
throw error; | |
} | |
}).restartable(), | |
validateToken: task(function * () { | |
let { ajax, token } = this.getProperties('ajax', 'token'); | |
try { | |
if (!token) { | |
throw new Error('Missing token'); | |
} | |
let url = `${GITHUB_HOST}/user`; | |
let headers = { | |
'Authorization': `token ${token}`, | |
...GITHUB_HEADERS | |
}; | |
yield ajax.request(url, { headers }); | |
return true; | |
} catch(error) { | |
this.set('token', null); | |
throw new Error(E_BAD_TOKEN); | |
} | |
}).restartable() | |
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
* { | |
box-sizing: border-box; | |
} | |
body { | |
margin: 12px 16px; | |
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; | |
font-size: 12pt; | |
color: #333; | |
background: #f9f9f9; | |
} | |
h1 { | |
margin: 32px 0 24px; | |
color: #999; | |
font-weight: lighter; | |
font-size: 12px; | |
text-align: center; | |
} | |
h2 { | |
margin: 0.67em 0; | |
font-weight: 300; | |
font-size: 24px; | |
text-align: center; | |
} | |
.narrow { | |
width: 340px; | |
margin: auto; | |
} | |
.error { | |
padding: 15px 20px; | |
margin: 0 auto; | |
margin-bottom: 10px; | |
font-size: 13px; | |
border-style: solid; | |
border-width: 1px; | |
border-radius: 5px; | |
color: #86181d; | |
background-color: #ffdce0; | |
border: 1px solid rgba(27,31,35,0.15); | |
} | |
.box { | |
background-color: #fff; | |
border: 1px solid #d8dee2; | |
border-radius: 5px; | |
padding: 20px; | |
font-size: 14px; | |
} | |
label { | |
display: block; | |
font-weight: 600; | |
} | |
input { | |
display: block; | |
width: 100%; | |
margin-top: 7px; | |
margin-bottom: 15px; | |
padding: 6px 8px; | |
font-size: 14px; | |
line-height: 20px; | |
color: #24292e; | |
vertical-align: middle; | |
border: 1px solid #d1d5da; | |
border-radius: 3px; | |
outline: none; | |
box-shadow: inset 0 1px 2px rgba(27,31,35,0.075); | |
} | |
input:focus { | |
border-color: #2188ff; | |
outline: none; | |
box-shadow: inset 0 1px 2px rgba(27,31,35,0.075), 0 0 0 0.2em rgba(3,102,214,0.3); | |
} | |
button { | |
display: block; | |
width: 100%; | |
text-align: center; | |
padding: 6px 12px; | |
font-size: 14px; | |
font-weight: 600; | |
line-height: 20px; | |
white-space: nowrap; | |
vertical-align: middle; | |
cursor: pointer; | |
user-select: none; | |
background-repeat: repeat-x; | |
background-position: -1px -1px; | |
background-size: 110% 110%; | |
border: 1px solid rgba(27,31,35,0.2); | |
border-radius: 0.25em; | |
outline: none; | |
-webkit-appearance: none; | |
-moz-appearance: none; | |
appearance: none; | |
margin: 6px 0px; | |
color: #24292e; | |
background-color: #eff3f6; | |
background-image: linear-gradient(-180deg, #fafbfc 0%, #eff3f6 90%); | |
} | |
button:focus { | |
box-shadow: 0 0 0 0.2em rgba(3,102,214,0.3); | |
} | |
button:disabled { | |
color: rgba(36,41,46,0.4); | |
background-color: #eff3f6; | |
background-image: none; | |
border-color: rgba(27,31,35,0.2); | |
box-shadow: none; | |
cursor: default; | |
} | |
button.primary { | |
margin-top: 20px; | |
color: #fff; | |
background-color: #28a745; | |
background-image: linear-gradient(-180deg, #34d058 0%, #28a745 90%); | |
} | |
button.primary:focus { | |
box-shadow: 0 0 0 0.2em rgba(52,208,88,0.3); | |
} | |
button.primary:disabled { | |
color: rgba(255,255,255,0.75); | |
background-color: #94d3a2; | |
background-image: none; | |
border-color: rgba(27,31,35,0.2); | |
box-shadow: none; | |
} | |
button.danger { | |
color: #cb2431; | |
background-color: #fafbfc; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"version": "0.12.1", | |
"EmberENV": { | |
"FEATURES": {} | |
}, | |
"options": { | |
"use_pods": false, | |
"enable-testing": false | |
}, | |
"dependencies": { | |
"jquery": "https://cdnjs.cloudflare.com/ajax/libs/jquery/1.11.3/jquery.js", | |
"ember": "2.12.0", | |
"ember-template-compiler": "2.12.0", | |
"ember-testing": "2.12.0" | |
}, | |
"addons": { | |
"ember-ajax": "3.0.0", | |
"ember-concurrency": "0.8.7", | |
"ember-data": "2.12.1" | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment