finish whole oauth logic

This commit is contained in:
Nolan Lawson 2018-01-07 22:00:16 -08:00
parent 965826a360
commit 9753b3d1c6
7 changed files with 152 additions and 4 deletions

5
package-lock.json generated
View File

@ -2487,6 +2487,11 @@
} }
} }
}, },
"idb-keyval": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/idb-keyval/-/idb-keyval-2.3.0.tgz",
"integrity": "sha1-TURLgMP4b8vNUTIbTcvJJHxZSMA="
},
"ieee754": { "ieee754": {
"version": "1.1.8", "version": "1.1.8",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.8.tgz", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.8.tgz",

View File

@ -18,6 +18,7 @@
"extract-text-webpack-plugin": "^3.0.2", "extract-text-webpack-plugin": "^3.0.2",
"font-awesome-svg-png": "^1.2.2", "font-awesome-svg-png": "^1.2.2",
"glob": "^7.1.2", "glob": "^7.1.2",
"idb-keyval": "^2.3.0",
"node-fetch": "^1.7.3", "node-fetch": "^1.7.3",
"npm-run-all": "^4.1.2", "npm-run-all": "^4.1.2",
"sapper": "^0.3.1", "sapper": "^0.3.1",

27
routes/_utils/binary.js Normal file
View File

@ -0,0 +1,27 @@
// from blob-util
function blobToBinaryString(blob) {
return new Promise(function (resolve, reject) {
var reader = new FileReader();
var hasBinaryString = typeof reader.readAsBinaryString === 'function';
reader.onloadend = function (e) {
var result = e.target.result || '';
if (hasBinaryString) {
return resolve(result);
}
resolve(arrayBufferToBinaryString(result));
};
reader.onerror = reject;
if (hasBinaryString) {
reader.readAsBinaryString(blob);
} else {
reader.readAsArrayBuffer(blob);
}
});
}
export function blobToBase64(blob) {
return blobToBinaryString(blob).then(function (binary) {
// web-safe variant
return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
});
}

21
routes/_utils/database.js Normal file
View File

@ -0,0 +1,21 @@
import idbKeyVal from 'idb-keyval'
import { blobToBase64 } from '../_utils/binary'
let databasePromise
if (process.browser) {
databasePromise = Promise.resolve().then(async () => {
let token = await idbKeyVal.get('secure_token')
if (!token) {
let array = new Uint32Array(1028)
crypto.getRandomValues(array);
let token = await blobToBase64(new Blob([array]))
await idbKeyVal.set('secure_token', token)
}
return idbKeyVal
})
} else {
databasePromise = Promise.resolve()
}
export { databasePromise }

51
routes/_utils/mastodon.js Normal file
View File

@ -0,0 +1,51 @@
const WEBSITE = 'https://pinafore.social'
const REDIRECT_URI = (typeof location !== 'undefined' ? location.origin : 'https://pinafore.social') + '/settings/add-instance'
const SCOPES = 'read write follow'
const CLIENT_NAME = 'Pinafore'
export function registerApplication(instanceName) {
const url = `https://${instanceName}/api/v1/apps`
return fetch(url, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({
client_name: CLIENT_NAME,
redirect_uris: REDIRECT_URI,
scopes: SCOPES,
website: WEBSITE
})
})
}
export function generateAuthLink(instanceName, clientId) {
let url = `https://${instanceName}/oauth/authorize`
let params = new URLSearchParams()
params.set('client_id', clientId)
params.set('redirect_uri', REDIRECT_URI)
params.set('response_type', 'code')
params.set('scope', SCOPES)
url += '?' + params.toString()
return url
}
export function getAccessTokenFromAuthCode(instanceName, clientId, clientSecret, code) {
let url = `https://${instanceName}/oauth/token`
return fetch(url, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({
client_id: clientId,
client_secret: clientSecret,
redirect_uri: REDIRECT_URI,
grant_type: 'authorization_code',
code: code
})
})
}

View File

@ -16,7 +16,14 @@
<p>Don't have an instance? <a href="https://joinmastodon.org">Join Mastodon!</a></p> <p>Don't have an instance? <a href="https://joinmastodon.org">Join Mastodon!</a></p>
</Layout> </Layout>
<style> <style>
@media (max-width: 767px) {
input { input {
width: 90%;
}
}
input {
width: 250px;
} }
form { form {
@ -30,11 +37,39 @@
display: block; display: block;
margin: 20px 5px; margin: 20px 5px;
} }
</style> </style>
<script> <script>
import Layout from '../_components/Layout.html'; import Layout from '../_components/Layout.html';
import { registerApplication, generateAuthLink, getAccessTokenFromAuthCode } from '../_utils/mastodon'
import { databasePromise } from '../_utils/database'
export default { export default {
oncreate: function () {
if (process.browser) {
(async () => {
let params = new URLSearchParams(location.search)
if (params.has('code')) {
let db = await databasePromise
let instanceData = await db.get('instance')
this.set({instanceName: instanceData.instanceName})
let code = params.get('code')
instanceData.code = code
let response = await (await getAccessTokenFromAuthCode(
instanceData.instanceName,
instanceData.client_id,
instanceData.client_secret,
instanceData.code
)).json()
instanceData = Object.assign(instanceData, response)
await db.set(`instance`, instanceData)
console.log('response', response)
}
})()
}
},
components: { components: {
Layout Layout
}, },
@ -42,11 +77,17 @@
instanceName: '' instanceName: ''
}), }),
methods: { methods: {
handleSubmit(event) { handleSubmit: async function(event) {
event.preventDefault() event.preventDefault()
let instanceName = this.get('instanceName') let instanceName = this.get('instanceName')
alert(instanceName) instanceName = instanceName.replace(/^https?:\/\//, '').replace('/$', '')
} let data = await (await registerApplication(instanceName)).json()
let db = await databasePromise
data.instanceName = instanceName
await db.set(`instance`, data)
let oauthUrl = generateAuthLink(instanceName, data.client_id)
document.location.href = oauthUrl
},
} }
} }
</script> </script>

View File

@ -10,7 +10,9 @@
<link rel='icon' type='image/png' href='/favicon.png'> <link rel='icon' type='image/png' href='/favicon.png'>
<script> <script>
if (!location.origin.match('localhost') && 'serviceWorker' in navigator) { if (!location.origin.match('localhost') &&
!location.origin.match('127.0.0.1') &&
'serviceWorker' in navigator) {
navigator.serviceWorker.register('/service-worker.js'); navigator.serviceWorker.register('/service-worker.js');
} }
</script> </script>