summaryrefslogtreecommitdiff
path: root/src/auth.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/auth.js')
-rw-r--r--src/auth.js125
1 files changed, 125 insertions, 0 deletions
diff --git a/src/auth.js b/src/auth.js
new file mode 100644
index 0000000..8ee803d
--- /dev/null
+++ b/src/auth.js
@@ -0,0 +1,125 @@
+export default {
+
+ async logIn(graffitiURL) {
+ // Generate a random client secret and state
+ const clientSecret = crypto.randomUUID()
+ const state = crypto.randomUUID()
+
+ // The client ID is the secret's hex hash
+ const clientID = await this.sha256(clientSecret)
+
+ // Store the client secret as a local variable
+ window.localStorage.setItem('graffitiClientSecret', clientSecret)
+ window.localStorage.setItem('graffitiClientID', clientID)
+ window.localStorage.setItem('graffitiAuthState', state)
+
+ // Redirect to the login window
+ const loginURL = this.authURL(graffitiURL)
+ loginURL.searchParams.set('client_id', clientID)
+ loginURL.searchParams.set('redirect_uri', window.location.href)
+ loginURL.searchParams.set('state', state)
+ window.location.href = loginURL
+ },
+
+ async connect(graffitiURL) {
+
+ // Check to see if we are already logged in
+ let token = window.localStorage.getItem('graffitiToken')
+ let myID = window.localStorage.getItem('graffitiID')
+
+ if (!token || !myID) {
+ // Remove them both in case one exists
+ // and the other does not
+ token = myID = null
+
+ // Check to see if we are redirecting back
+ const url = new URL(window.location)
+ if (url.searchParams.has('code')) {
+
+ // Extract the code and state from the URL and strip it from the history
+ const code = url.searchParams.get('code')
+ const state = url.searchParams.get('state')
+ url.searchParams.delete('code')
+ url.searchParams.delete('state')
+ window.history.replaceState({}, '', url)
+
+ // Get stored variables and remove them
+ const clientSecret = window.localStorage.getItem('graffitiClientSecret')
+ const clientID = window.localStorage.getItem('graffitiClientID')
+ const storedState = window.localStorage.getItem('graffitiAuthState')
+ window.localStorage.removeItem('graffitiClientSecret')
+ window.localStorage.removeItem('graffitiClientID')
+ window.localStorage.removeItem('graffitiAuthState')
+
+ // Make sure state has been preserved
+ if (state != storedState) {
+ throw new Error("The state in local storage does not match the state sent by the server")
+ }
+
+ // Construct the body of the POST
+ let form = new FormData()
+ form.append('client_id', clientID)
+ form.append('client_secret', clientSecret)
+ form.append('code', code)
+
+ // Ask to exchange the code for a token
+ const tokenURL = this.authURL(graffitiURL)
+ tokenURL.pathname = '/token'
+ const response = await fetch(tokenURL, {
+ method: 'post',
+ body: form
+ })
+
+ // Make sure the response is OK
+ if (!response.ok) {
+ let reason = response.status + ": "
+ try {
+ reason += (await response.json()).detail
+ } catch (e) {
+ reason += response.statusText
+ }
+
+ throw new Error(`The authorization code could not be exchanged for a token.\n\n${reason}`)
+ }
+
+ // Parse out the token
+ const data = await response.json()
+ token = data.access_token
+ myID = data.owner_id
+
+ // And make sure that the token is valid
+ if (!token || !myID) {
+ throw new Error(`The authorization token could not be parsed from the response.\n\n${data}`)
+ }
+
+ // Store the token and ID
+ window.localStorage.setItem('graffitiToken', token)
+ window.localStorage.setItem('graffitiID', myID)
+ }
+ }
+
+ return { myID, token }
+
+ },
+
+ logOut() {
+ window.localStorage.removeItem('graffitiToken')
+ window.localStorage.removeItem('graffitiID')
+ window.location.reload()
+ },
+
+ authURL(graffitiURL) {
+ const url = new URL(graffitiURL)
+ url.host = "auth." + url.host
+ return url
+ },
+
+ async sha256(input) {
+ const encoder = new TextEncoder()
+ const inputBytes = encoder.encode(input)
+ const outputBuffer = await crypto.subtle.digest('SHA-256', inputBytes)
+ const outputArray = Array.from(new Uint8Array(outputBuffer))
+ return outputArray.map(b => b.toString(16).padStart(2, '0')).join('')
+ }
+
+}