-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #8 from TomCasavant/oauth
Oauth Authentication improvements v0.0.3
- Loading branch information
Showing
5 changed files
with
211 additions
and
43 deletions.
There are no files selected for viewing
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,30 +1,165 @@ | ||
// Message listener for various actions | ||
browser.runtime.onMessage.addListener((message, sender, sendResponse) => { | ||
if (message.action === 'searchMastodon') { | ||
// Load saved settings from localStorage | ||
const mastodonDomain = localStorage.getItem('domain'); | ||
const apiKey = localStorage.getItem('apiKey'); | ||
const numPosts = localStorage.getItem('numPosts') || 5; | ||
const searchTerm = message.searchTerm; | ||
// Load saved settings from browser.storage.local | ||
browser.storage.local.get(['client_id', 'client_secret', 'access_token', 'domain', 'numPosts']) | ||
.then(({ client_id, client_secret, access_token, domain, numPosts = 5 }) => { | ||
console.log(access_token); | ||
console.log(domain); | ||
if (!access_token || !domain) { | ||
sendResponse({ success: false, error: 'Missing access token or domain.' }); | ||
return; | ||
} | ||
|
||
const searchTerm = message.searchTerm; | ||
fetch(`https://${domain}/api/v2/search?q=${encodeURIComponent(searchTerm)}&resolve=true&limit=${numPosts}`, { | ||
headers: { | ||
'Authorization': `Bearer ${access_token}` | ||
} | ||
}) | ||
.then(response => response.json()) | ||
.then(data => { | ||
console.log(data.statuses); //TODO: For some reason this sendResponse does not work unless I log data.statuses. I have no idea why. Best guess is there's some sort of race condition going on? Though I'm not familiar enough with javascript to know if that's true | ||
sendResponse({ success: true, results: data.statuses }); | ||
}) | ||
.catch(error => { | ||
sendResponse({ success: false, error: error.message }); | ||
}); | ||
|
||
fetch(`https://${mastodonDomain}/api/v2/search?q=${encodeURIComponent(searchTerm)}&resolve=true&limit=${numPosts}`, { | ||
headers: { | ||
'Authorization': `Bearer ${apiKey}` | ||
} | ||
}) | ||
.then(response => response.json()) | ||
.then(data => { | ||
sendResponse({ success: true, results: data.statuses }); | ||
}) | ||
.catch(error => { | ||
return true; | ||
}) | ||
.catch(error => { | ||
sendResponse({ success: false, error: 'Failed to retrieve saved settings.' }); | ||
}); | ||
return true; | ||
} else if (message.action === 'getSettings') { | ||
browser.storage.local.get(['domain', 'numPosts']) | ||
.then(({ domain, numPosts = 5 }) => { | ||
sendResponse({ domain, numPosts }); | ||
}) | ||
.catch(error => { | ||
sendResponse({ success: false, error: 'Failed to retrieve settings.' }); | ||
}); | ||
|
||
return true; | ||
} else if (message.action === 'authorize') { | ||
const domain = message.domain; | ||
let appRegistrate = null; | ||
|
||
// Register the app and start the OAuth flow | ||
registerApp(domain).then(appRegistration => { | ||
console.log('App registration successful:', appRegistration); | ||
appRegistrate = appRegistration; | ||
return launchOAuthFlow(appRegistration, domain); | ||
}).then(redirectUrl => { | ||
return validate(redirectUrl, domain, appRegistrate); // Pass appRegistration here | ||
}).then(() => { | ||
sendResponse({ success: true }); | ||
}).catch(error => { | ||
console.error('Error during OAuth process:', error); | ||
sendResponse({ success: false, error: error.message }); | ||
}); | ||
|
||
return true; | ||
} | ||
else if (message.action === 'getSettings') { | ||
const domain = localStorage.getItem('domain'); | ||
const numPosts = parseInt(localStorage.getItem('numPosts')); | ||
sendResponse({ domain, numPosts }); | ||
} | ||
}); | ||
|
||
// Registers the app with a mastodon server | ||
async function registerApp(domain) { | ||
try { | ||
const response = await fetch(`https://${domain}/api/v1/apps`, { | ||
method: 'POST', | ||
headers: { | ||
'Content-Type': 'application/json' | ||
}, | ||
body: JSON.stringify({ | ||
client_name: 'DuckDuckSocial', | ||
redirect_uris: browser.identity.getRedirectURL(), // Use the add-on's redirect URL | ||
scopes: 'read write', | ||
website: 'https://tomcasavant.com' | ||
}) | ||
}); | ||
|
||
if (!response.ok) { | ||
const errorText = await response.text(); | ||
console.error('Error registering app:', errorText); | ||
throw new Error('Failed to register app'); | ||
} | ||
|
||
const appData = await response.json(); | ||
await browser.storage.local.set({ | ||
client_id: appData.client_id, | ||
client_secret: appData.client_secret, | ||
redirect_uri: appData.redirect_uri | ||
}); | ||
|
||
return appData; | ||
} catch (error) { | ||
console.error('Error during app registration:', error); | ||
throw error; | ||
} | ||
} | ||
|
||
// Launches OAuth flow | ||
function launchOAuthFlow(appRegistration, domain) { | ||
const authorizationUrl = `https://${domain}/oauth/authorize?client_id=${appRegistration.client_id}&redirect_uri=${encodeURIComponent(appRegistration.redirect_uri)}&response_type=code&scope=read`; | ||
|
||
return browser.identity.launchWebAuthFlow({ | ||
interactive: true, | ||
url: authorizationUrl | ||
}); | ||
} | ||
|
||
// Get access token from Authorization | ||
async function exchangeCodeForToken(code, domain, appRegistration) { | ||
try { | ||
const response = await fetch(`https://${domain}/oauth/token`, { | ||
method: 'POST', | ||
headers: { | ||
'Content-Type': 'application/json' | ||
}, | ||
body: JSON.stringify({ | ||
client_id: appRegistration.client_id, | ||
client_secret: appRegistration.client_secret, | ||
redirect_uri: appRegistration.redirect_uri, | ||
grant_type: 'authorization_code', | ||
code: code | ||
}) | ||
}); | ||
|
||
if (!response.ok) { | ||
const errorText = await response.text(); | ||
console.error('Error exchanging code for token:', errorText); | ||
throw new Error('Failed to exchange authorization code for access token'); | ||
} | ||
|
||
const tokenData = await response.json(); | ||
await browser.storage.local.set({ access_token: tokenData.access_token }); | ||
} catch (error) { | ||
console.error('Error during token exchange:', error); | ||
throw error; | ||
} | ||
} | ||
|
||
// Validates the redirect URL | ||
async function validate(redirectUrl, domain, appRegistration) { | ||
try { | ||
console.log('Redirect URL:', redirectUrl); | ||
|
||
if (redirectUrl) { | ||
const code = new URL(redirectUrl).searchParams.get('code'); | ||
|
||
if (code) { | ||
await exchangeCodeForToken(code, domain, appRegistration); | ||
console.log('Access token saved successfully.'); | ||
} else { | ||
throw new Error('Authorization code not found in redirect URL.'); | ||
} | ||
} else { | ||
throw new Error('No redirect URL returned.'); | ||
} | ||
} catch (error) { | ||
console.error('Validation error:', error); | ||
throw error; | ||
} | ||
} |
This file contains 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
This file contains 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
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,37 +1,68 @@ | ||
document.addEventListener('DOMContentLoaded', function() { | ||
const form = document.getElementById('settingsForm'); | ||
const domainInput = document.getElementById('domain'); | ||
const apiKeyInput = document.getElementById('apiKey'); | ||
const numPostsInput = document.getElementById('numPosts'); | ||
const messageElement = document.querySelector('.message'); | ||
|
||
// Load saved settings | ||
loadSettings(); | ||
|
||
// Handle form submission | ||
form.addEventListener('submit', function(event) { | ||
event.preventDefault(); | ||
saveSettings(); | ||
}); | ||
|
||
document.getElementById('connectMastodon').addEventListener('click', function(event) { | ||
const domain = domainInput.value; | ||
|
||
if (!domain) { | ||
updateMessage('Mastodon server domain is required!'); | ||
return; | ||
} | ||
|
||
browser.runtime.sendMessage({ | ||
action: 'authorize', | ||
domain: domain | ||
}).then(response => { | ||
if (response.success) { | ||
updateMessage('Connected to Mastodon successfully!'); | ||
} else { | ||
updateMessage('Failed to connect to Mastodon: ' + response.error); | ||
} | ||
}).catch(error => { | ||
console.error('Error during OAuth process:', error); | ||
updateMessage('Failed to connect to Mastodon. Please try again.'); | ||
}); | ||
}); | ||
|
||
function saveSettings() { | ||
const domain = domainInput.value; | ||
const apiKey = apiKeyInput.value; | ||
const numPosts = numPostsInput.value; | ||
|
||
localStorage.setItem('domain', domain); | ||
localStorage.setItem('apiKey', apiKey); | ||
localStorage.setItem('numPosts', numPosts); | ||
|
||
alert('Settings saved successfully!'); | ||
browser.storage.local.set({ | ||
domain: domain, | ||
numPosts: numPosts | ||
}).then(() => { | ||
updateMessage('Settings saved successfully!'); | ||
}).catch(error => { | ||
console.error('Failed to save settings:', error); | ||
updateMessage('Failed to save settings.'); | ||
}); | ||
} | ||
|
||
function loadSettings() { | ||
const savedDomain = localStorage.getItem('domain'); | ||
const savedApiKey = localStorage.getItem('apiKey'); | ||
const savedNumPosts = localStorage.getItem('numPosts'); | ||
|
||
if (savedDomain) domainInput.value = savedDomain; | ||
if (savedApiKey) apiKeyInput.value = savedApiKey; | ||
if (savedNumPosts) numPostsInput.value = savedNumPosts; | ||
browser.storage.local.get(['domain', 'numPosts']) | ||
.then(({ domain, numPosts = 5 }) => { | ||
if (domain) domainInput.value = domain; | ||
if (numPosts) numPostsInput.value = numPosts; | ||
}) | ||
.catch(error => { | ||
console.error('Failed to load settings:', error); | ||
}); | ||
} | ||
|
||
function updateMessage(message) { | ||
messageElement.textContent = message; | ||
messageElement.style.color = 'red'; | ||
} | ||
}); |
This file contains 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