finish whole oauth logic
This commit is contained in:
parent
965826a360
commit
9753b3d1c6
5
package-lock.json
generated
5
package-lock.json
generated
|
@ -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",
|
||||||
|
|
|
@ -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
27
routes/_utils/binary.js
Normal 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
21
routes/_utils/database.js
Normal 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
51
routes/_utils/mastodon.js
Normal 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
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
|
@ -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>
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in a new issue