mirror of
https://github.com/bellingcat/auto-archiver-extension.git
synced 2026-06-08 03:28:34 +03:00
fixing authentication + minor issues
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# Auto Archiver Extension
|
||||
|
||||
Chromium browser extension that connects to an API that calls [Belligncat's auto-archiver](https://github.com/bellingcat/auto-archiver).
|
||||
Chromium browser extension that connects to an API that calls [Bellingcat's auto-archiver](https://github.com/bellingcat/auto-archiver).
|
||||
|
||||
> Beta deployment: only authorized emails can use it. Available on [chrome web store](https://chrome.google.com/webstore/detail/auto-archiver-extension/ojcimmjndnlmmlgnjaeojoebaceokpdp)
|
||||
|
||||
@@ -21,7 +21,7 @@ Using [web-ext](https://extensionworkshop.com/documentation/develop/getting-star
|
||||
1. Run `npm run watch` to watch for file changes and build continuously
|
||||
1. Run `npm install --global web-ext` (only only for the first time)
|
||||
1. In another terminal, run `web-ext run -t chromium`
|
||||
1. Check that the extension is loaded by opening the extension options ([in Firefox](media/extension_options_firefox.png) or [in Chrome](media/extension_options_chrome.png)).
|
||||
1. Check that the extension is loaded by opening the extension options.
|
||||
|
||||
|
||||
<!-- > You need to connect to a backend of the [bellingcat/auto-archiver-api](https://github.com/bellingcat/auto-archiver-api) make sure to setup that too. -->
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
<!-- <!doctype html>
|
||||
<meta charset="utf-8">
|
||||
<title>Options</title>
|
||||
|
||||
<form id="options-form" class="detail-view-container">
|
||||
<h3>Color Picker</h3>
|
||||
<div class="color-picker">
|
||||
<div class="color-inputs">
|
||||
<label class="color-input">
|
||||
<span>R</span>
|
||||
<input type="range" min="0" max="255" name="colorRed">
|
||||
<input type="number" name="colorRed" min="0" max="255">
|
||||
</label>
|
||||
<label class="color-input">
|
||||
<span>G</span>
|
||||
<input type="range" min="0" max="255" name="colorGreen">
|
||||
<input type="number" name="colorGreen" min="0" max="255">
|
||||
</label>
|
||||
<label class="color-input">
|
||||
<span>B</span>
|
||||
<input type="range" min="0" max="255" name="colorBlue">
|
||||
<input type="number" name="colorBlue" min="0" max="255">
|
||||
</label>
|
||||
</div>
|
||||
<div class="color-output"></div>
|
||||
</div>
|
||||
<h3>Notice Content</h3>
|
||||
<div class="text-inputs">
|
||||
<label class="text-input">
|
||||
<span>Text</span>
|
||||
<input type="text" name="text">
|
||||
</label>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<script src="../js/options.js" type="module"></script> -->
|
||||
@@ -1,18 +1,15 @@
|
||||
|
||||
// Import './options-storage.js';
|
||||
import optionsStorage from './options-storage.js';
|
||||
import { getReasonPhrase } from 'http-status-codes';
|
||||
|
||||
// const API_ENDPOINT = process.env.API_ENDPOINT || 'http://134.122.58.133:8004/tasks';
|
||||
const API_ENDPOINT = process.env.API_ENDPOINT || 'http://localhost:8004/tasks';
|
||||
|
||||
console.log(`using API_ENDPOINT=${API_ENDPOINT}`)
|
||||
|
||||
const LOGIN_FAILED = `Could not login, make sure your google account email has been granted access by the developers.`;
|
||||
const LOGIN_FAILED = `Please login before using this feature.`;
|
||||
|
||||
chrome.runtime.onMessage.addListener(((r, s, sendResponse) => {
|
||||
processMessages(r, s)
|
||||
//TODO: improve body
|
||||
.then(response => {
|
||||
console.log(`SUCCESS (${r.action}): ${JSON.stringify(response)}`)
|
||||
sendResponse({ status: "success", result: response })
|
||||
@@ -74,12 +71,12 @@ function processMessages(request, sender) {
|
||||
getUrl(resolve, reject);
|
||||
break;
|
||||
}
|
||||
case 'getProfileEmail': {
|
||||
getProfileEmail(resolve, reject);
|
||||
case 'oauthLogin': {
|
||||
oauthLogin(resolve, reject, request.interactive);
|
||||
break;
|
||||
}
|
||||
case 'oauthLogin': {
|
||||
oauthLogin(resolve, reject);
|
||||
case 'logout': {
|
||||
logout(resolve, reject);
|
||||
break;
|
||||
}
|
||||
// No default
|
||||
@@ -99,28 +96,32 @@ function getUrl(resolve, reject) {
|
||||
});
|
||||
}
|
||||
|
||||
function getProfileEmail(resolve, reject) {
|
||||
chrome.identity.getProfileUserInfo({ accountStatus: 'ANY' }, async userInfo => {
|
||||
resolve(userInfo);
|
||||
//TODO: reject if bad user info?
|
||||
});
|
||||
}
|
||||
|
||||
function oauthLogin(resolve, reject) {
|
||||
function oauthLogin(resolve, reject, interactive) {
|
||||
try {
|
||||
chrome.identity.getAuthToken({ interactive: true }, async accessToken => {
|
||||
console.error(`GOT token ${accessToken}`);
|
||||
resolve(true);
|
||||
// force re-auth if interactive is needed
|
||||
if (interactive) {
|
||||
chrome.identity.clearAllCachedAuthTokens();
|
||||
}
|
||||
chrome.identity.getAuthToken({ interactive: interactive }, async accessToken => {
|
||||
console.warn(`GOT token ${accessToken}`);
|
||||
if (accessToken === undefined) { resolve({ success: false, message: "Could not get access token." }); }
|
||||
resolve({ success: true });
|
||||
});
|
||||
} catch (e) {
|
||||
// reject(new Error(`LOGIN FAILED: ${e}`));
|
||||
console.error(`LOGIN FAILED: ${e}`);
|
||||
resolve(false);
|
||||
resolve({ success: false, message: `Login failed: ${e}` });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function logout(resolve, reject) {
|
||||
chrome.identity.clearAllCachedAuthTokens();
|
||||
resolve(true);
|
||||
}
|
||||
|
||||
function archiveUrl(resolve, reject, optionalUrl) {
|
||||
chrome.identity.getAuthToken({ interactive: true }, async accessToken => {
|
||||
chrome.identity.getAuthToken({ interactive: false }, async accessToken => {
|
||||
if (accessToken == undefined) {
|
||||
reject(new Error(LOGIN_FAILED));
|
||||
return;
|
||||
@@ -148,6 +149,7 @@ function submitUrlArchive(url, accessToken) {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${accessToken}`
|
||||
},
|
||||
body: JSON.stringify({ url, access_token: accessToken }),
|
||||
})
|
||||
@@ -160,7 +162,7 @@ function submitUrlArchive(url, accessToken) {
|
||||
function checkTaskStatus(resolve, reject, task) {
|
||||
console.log('API: STATUS');
|
||||
return new Promise((InnerResolve, innerReject) => {
|
||||
chrome.identity.getAuthToken({ interactive: true }, async accessToken => {
|
||||
chrome.identity.getAuthToken({ interactive: false }, async accessToken => {
|
||||
if (accessToken == undefined) {
|
||||
reject(new Error(LOGIN_FAILED));
|
||||
return;
|
||||
@@ -169,6 +171,7 @@ function checkTaskStatus(resolve, reject, task) {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${accessToken}`
|
||||
},
|
||||
})
|
||||
.then(getJsonOrError)
|
||||
@@ -192,7 +195,7 @@ function checkTaskStatus(resolve, reject, task) {
|
||||
|
||||
function search(resolve, reject, url) {
|
||||
console.log('API: SEARCH');
|
||||
chrome.identity.getAuthToken({ interactive: true }, async accessToken => {
|
||||
chrome.identity.getAuthToken({ interactive: false }, async accessToken => {
|
||||
if (accessToken == undefined) {
|
||||
reject(new Error(LOGIN_FAILED));
|
||||
return;
|
||||
@@ -201,6 +204,7 @@ function search(resolve, reject, url) {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${accessToken}`
|
||||
},
|
||||
})
|
||||
.then(getJsonOrError)
|
||||
@@ -211,7 +215,7 @@ function search(resolve, reject, url) {
|
||||
|
||||
async function syncLocalTasks(resolve, reject) {
|
||||
console.log('API: SYNC');
|
||||
chrome.identity.getAuthToken({ interactive: true }, async accessToken => {
|
||||
chrome.identity.getAuthToken({ interactive: false }, async accessToken => {
|
||||
if (accessToken == undefined) {
|
||||
reject(new Error(LOGIN_FAILED));
|
||||
return;
|
||||
@@ -220,6 +224,7 @@ async function syncLocalTasks(resolve, reject) {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${accessToken}`
|
||||
},
|
||||
})
|
||||
.then(getJsonOrError)
|
||||
@@ -242,7 +247,7 @@ async function syncLocalTasks(resolve, reject) {
|
||||
|
||||
async function deleteTask(resolve, reject, taskId) {
|
||||
console.log('API: DELETE TASK');
|
||||
chrome.identity.getAuthToken({ interactive: true }, async accessToken => {
|
||||
chrome.identity.getAuthToken({ interactive: false }, async accessToken => {
|
||||
if (accessToken == undefined) {
|
||||
reject(new Error(LOGIN_FAILED));
|
||||
return;
|
||||
@@ -251,11 +256,12 @@ async function deleteTask(resolve, reject, taskId) {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${accessToken}`
|
||||
},
|
||||
})
|
||||
.then(getJsonOrError)
|
||||
.then(async deleteOp => {
|
||||
if(deleteOp.deleted){
|
||||
if (deleteOp.deleted) {
|
||||
const storage = await optionsStorage.getAll();
|
||||
delete storage.archivedUrls[taskId];
|
||||
await optionsStorage.set(storage);
|
||||
|
||||
@@ -6,7 +6,7 @@ export default new OptionsSync({
|
||||
errorMessage: ""
|
||||
},
|
||||
migrations: [
|
||||
// OptionsSync.migrations.removeUnused,
|
||||
OptionsSync.migrations.removeUnused,
|
||||
],
|
||||
logging: true,
|
||||
storageType: "local"
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
// eslint-disable-next-line import/no-unassigned-import
|
||||
// import 'webext-base-css';
|
||||
// import '../css/options.css';
|
||||
|
||||
// import optionsStorage from './options-storage.js';
|
||||
|
||||
// const rangeInputs = [...document.querySelectorAll('input[type="range"][name^="color"]')];
|
||||
// const numberInputs = [...document.querySelectorAll('input[type="number"][name^="color"]')];
|
||||
// const output = document.querySelector('.color-output');
|
||||
|
||||
// function updateOutputColor() {
|
||||
// output.style.backgroundColor = `rgb(${rangeInputs[0].value}, ${rangeInputs[1].value}, ${rangeInputs[2].value})`;
|
||||
// }
|
||||
|
||||
// function updateInputField(event) {
|
||||
// numberInputs[rangeInputs.indexOf(event.currentTarget)].value = event.currentTarget.value;
|
||||
// }
|
||||
|
||||
// for (const input of rangeInputs) {
|
||||
// input.addEventListener('input', updateOutputColor);
|
||||
// input.addEventListener('input', updateInputField);
|
||||
// }
|
||||
|
||||
// async function init() {
|
||||
// await optionsStorage.syncForm('#options-form');
|
||||
// updateOutputColor();
|
||||
// }
|
||||
|
||||
// init();
|
||||
@@ -6,17 +6,7 @@ import 'material-design-icons/iconfont/material-icons.css';
|
||||
const app = createApp(Popup);
|
||||
app.mount('#app');
|
||||
|
||||
// Import browser from 'webextension-polyfill';
|
||||
// import optionsStorage from './options-storage.js';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
// TODO: uncomment if using options
|
||||
// listenForOptionsClick();
|
||||
M.Tooltip.init(document.querySelectorAll('.tooltipped'), { enterDelay: 500 }); // enable tooltips
|
||||
});
|
||||
|
||||
// Function listenForOptionsClick() {
|
||||
// document.querySelector('#optionsBtn').addEventListener('click', () => {
|
||||
// browser.runtime.openOptionsPage();
|
||||
// });
|
||||
// }
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
// /**
|
||||
// *
|
||||
// *
|
||||
// */
|
||||
// export async function callBackground(parameters) {
|
||||
// try {
|
||||
// const answer = await chrome.runtime.sendMessage(parameters);
|
||||
// if (answer.status == "error") {
|
||||
// console.error(`error: ${answer.result}`)
|
||||
// //TODO: modal/errors
|
||||
// return null;
|
||||
// } else {
|
||||
// return answer.result;
|
||||
// }
|
||||
// } catch (e) {
|
||||
// console.error(e);
|
||||
// return null;
|
||||
// }
|
||||
// }
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "Auto-archiver extension",
|
||||
"version": "0.0.7",
|
||||
"version": "0.0.8",
|
||||
"description": "A gateway to effective archiving of online content, including behind private platforms. ",
|
||||
"homepage_url": "https://github.com/bellingcat/auto-archiver-extension",
|
||||
"manifest_version": 3,
|
||||
@@ -27,10 +27,6 @@
|
||||
"default_popup": "html/popup.html"
|
||||
},
|
||||
"content_scripts": [],
|
||||
"options_ui": {
|
||||
"browser_style": true,
|
||||
"page": "html/options.html"
|
||||
},
|
||||
"oauth2": {
|
||||
"client_id": "572076445849-4cb2a8be1nfi46l80jm741k56s7cjkd0.apps.googleusercontent.com",
|
||||
"scopes": ["https://www.googleapis.com/auth/userinfo.email"]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<p v-if="!login">please <a v-on:click="oauthLogin" href="#">login</a> into your google account</p>
|
||||
<p v-if="!login">please <a v-on:click="oauthLogin($event, true)" href="#" class="green-text">login</a> into your google account</p>
|
||||
<div v-if="errorMessage.length" class="red darken-1 white-text">Error: {{ errorMessage }}</div>
|
||||
<h5>
|
||||
<img src="../img/ben-archiver.png" alt="icon" id="icon">
|
||||
@@ -32,18 +32,22 @@
|
||||
</tbody>
|
||||
</table>
|
||||
<div v-if="noSearchResults">
|
||||
No results... do you want to <a v-on:click="archiveFromSearch" href="#">archive</a>?
|
||||
No results... do you want to <a v-on:click="archiveFromSearch($event, search)" href="#">archive</a>?
|
||||
</div>
|
||||
<p v-show="login">
|
||||
<a href="#" v-on:click="syncLocalTasks" class="tooltipped"
|
||||
data-tooltip="updates local database with entries submitted by the current user" data-position="top">Sync</a>
|
||||
my cloud archives.
|
||||
<p>
|
||||
<span v-if="login">
|
||||
<a href="#" v-on:click="syncLocalTasks" class="tooltipped"
|
||||
data-tooltip="updates local database with entries submitted by the current user"
|
||||
data-position="top">Sync</a>
|
||||
my cloud archives
|
||||
|
|
||||
<a v-on:click="logout" href="#" class="orange-text">logout</a>
|
||||
</span>
|
||||
<span class="right">
|
||||
<a href="https://github.com/bellingcat/auto-archiver-extension/issues" target="_blank">Issue tracker</a>
|
||||
version {{ version }}
|
||||
</span>
|
||||
</p>
|
||||
<small>
|
||||
<span v-if="login">Hello {{ login }}!</span>
|
||||
<span class="right"><a href="https://github.com/bellingcat/auto-archiver-extension/issues" target="_blank">Issue
|
||||
tracker</a> version {{ version }}</span>
|
||||
</small>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -63,19 +67,9 @@ export default {
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
archive: function () {
|
||||
archive: function (_, searchTerm) {
|
||||
(async () => {
|
||||
const response = await this.callBackground({ action: "archive" });
|
||||
if (!response) return;
|
||||
this.url = response.url;
|
||||
this.id = response.id;
|
||||
this.addTask(response)
|
||||
})();
|
||||
},
|
||||
archiveFromSearch: function () {
|
||||
//TODO: how to deduplicate? calling archive(this.search) is bad because of default injections into archive
|
||||
(async () => {
|
||||
const response = await this.callBackground({ action: "archive", optionalUrl: this.search });
|
||||
const response = await this.callBackground({ action: "archive", optionalUrl: searchTerm });
|
||||
if (!response) return;
|
||||
this.url = response.url;
|
||||
this.id = response.id;
|
||||
@@ -100,7 +94,6 @@ export default {
|
||||
},
|
||||
syncLocalTasks: function () {
|
||||
(async () => {
|
||||
console.log("SYNC")
|
||||
const tasks = await this.callBackground({ action: "syncLocalTasks" });
|
||||
console.log(`TASKS: ${JSON.stringify(tasks)}`)
|
||||
if (!tasks) return;
|
||||
@@ -108,13 +101,30 @@ export default {
|
||||
M.toast({ html: `sync complete: ${this.localTasksLength} task${this.localTasksLength != 1 ? 's' : ''} available`, classes: "green accent-4" });
|
||||
})();
|
||||
},
|
||||
displayLogin: function () {
|
||||
oauthLogin: function (_, interactive) {
|
||||
(async () => {
|
||||
const response = await this.callBackground({ action: "getProfileEmail" });
|
||||
if (!response) {
|
||||
if (interactive) {
|
||||
M.toast({ html: "please complete the login on the popup window" });
|
||||
}
|
||||
const loginResult = await this.callBackground({ action: "oauthLogin", interactive });
|
||||
if (loginResult === null) return;
|
||||
this.login = loginResult.success;
|
||||
if (interactive) {
|
||||
if (loginResult.success) {
|
||||
M.toast({ html: "login success", classes: "green accent-4" });
|
||||
} else {
|
||||
M.toast({ html: `login failed: ${loginResult.message}`, classes: "red darken-1" });
|
||||
}
|
||||
}
|
||||
})();
|
||||
},
|
||||
logout: function () {
|
||||
(async () => {
|
||||
const logoutResult = await this.callBackground({ action: "logout" });
|
||||
if (logoutResult === null) return;
|
||||
if (logoutResult) {
|
||||
this.login = false;
|
||||
} else {
|
||||
this.login = response.email;
|
||||
this.tasks = {};
|
||||
}
|
||||
})();
|
||||
},
|
||||
@@ -130,14 +140,6 @@ export default {
|
||||
this.clearErrorMessage()
|
||||
})();
|
||||
},
|
||||
oauthLogin: function () {
|
||||
(async () => {
|
||||
const loginSuccessful = await this.callBackground({ action: "oauthLogin" });
|
||||
if (loginSuccessful === null) return;
|
||||
M.toast({ html: loginSuccessful ? "login success" : "login failed", classes: loginSuccessful ? "green accent-4" : "red darken-1" });
|
||||
if (loginSuccessful) { this.displayLogin(); }
|
||||
})();
|
||||
},
|
||||
addTask: function (task) {
|
||||
this.tasks[task.id] = task;
|
||||
},
|
||||
@@ -207,7 +209,7 @@ export default {
|
||||
mounted() {
|
||||
M.AutoInit();
|
||||
this.displayAllTasks();
|
||||
this.displayLogin();
|
||||
this.oauthLogin(false);
|
||||
this.displayErrorMessage();
|
||||
},
|
||||
created() { },
|
||||
|
||||
@@ -78,7 +78,7 @@ export default {
|
||||
const answer = await chrome.runtime.sendMessage(parameters);
|
||||
if (answer.status == "error") {
|
||||
console.error(`error: ${answer.result}`)
|
||||
//TODO: modal/errors
|
||||
M.toast({ html: `Error: ${answer.result}`, classes: "red darken-1" })
|
||||
return null;
|
||||
} else {
|
||||
return answer.result;
|
||||
|
||||
Reference in New Issue
Block a user