From 2644b85989403318136ff12436645ac4944e5962 Mon Sep 17 00:00:00 2001 From: Matthew Evans Date: Sat, 22 Feb 2025 11:48:42 +0000 Subject: [PATCH 1/8] WIP work generating secure QR code links --- webapp/src/components/QRCode.vue | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/webapp/src/components/QRCode.vue b/webapp/src/components/QRCode.vue index cff1c60a5..db499105f 100644 --- a/webapp/src/components/QRCode.vue +++ b/webapp/src/components/QRCode.vue @@ -35,6 +35,7 @@ + + diff --git a/webapp/src/server_fetch_utils.js b/webapp/src/server_fetch_utils.js index a41a35531..8f98d2faa 100644 --- a/webapp/src/server_fetch_utils.js +++ b/webapp/src/server_fetch_utils.js @@ -445,8 +445,13 @@ export function removeItemsFromCollection(collection_id, refcodes) { }); } -export async function getItemData(item_id) { - return fetch_get(`${API_URL}/get-item-data/${item_id}`) +export async function getItemData(item_id, accessToken = null) { + let url = `${API_URL}/get-item-data/${item_id}`; + if (accessToken) { + url += `?at=${accessToken}`; + } + + return fetch_get(url) .then((response_json) => { store.commit("createItemData", { item_id: item_id, @@ -454,7 +459,6 @@ export async function getItemData(item_id) { child_items: response_json.child_items, parent_items: response_json.parent_items, }); - return "success"; }) .catch((error) => { @@ -465,8 +469,13 @@ export async function getItemData(item_id) { }); } -export async function getItemByRefcode(refcode) { - return fetch_get(`${API_URL}/items/${refcode}`) +export async function getItemByRefcode(refcode, accessToken = null) { + let url = `${API_URL}/items/${refcode}`; + if (accessToken) { + url += `?at=${accessToken}`; + } + + return fetch_get(url) .then((response_json) => { store.commit("createItemData", { refcode: refcode, diff --git a/webapp/src/views/Admin.vue b/webapp/src/views/Admin.vue index 1625e44cf..641906c6a 100644 --- a/webapp/src/views/Admin.vue +++ b/webapp/src/views/Admin.vue @@ -23,7 +23,7 @@ export default { }, data() { return { - items: ["Users"], + items: ["Users", "Access Tokens"], selectedItem: "Users", user: null, }; diff --git a/webapp/src/views/EditPage.vue b/webapp/src/views/EditPage.vue index d674fb2b5..b62754c47 100644 --- a/webapp/src/views/EditPage.vue +++ b/webapp/src/views/EditPage.vue @@ -91,7 +91,7 @@ - + @@ -212,6 +212,9 @@ export default { itemApiUrl() { return API_URL + "/items/" + this.refcode; }, + isAuthenticated() { + return this.$store.state.currentUserID != null; + }, }, watch: { // add a warning before leaving page if unsaved @@ -300,8 +303,11 @@ export default { this.lastModified = "just now"; }, async getSampleData() { + const urlParams = new URLSearchParams(window.location.search); + const accessToken = urlParams.get("at"); + if (this.item_id == null) { - getItemByRefcode(this.refcode).then(() => { + getItemByRefcode(this.refcode, accessToken).then(() => { this.itemDataLoaded = true; this.$nextTick(() => { this.$store.commit("setItemSaved", { item_id: this.item_id, isSaved: true }); @@ -310,7 +316,7 @@ export default { this.updateBlocks(); }); } else { - getItemData(this.item_id).then(() => { + getItemData(this.item_id, accessToken).then(() => { this.itemDataLoaded = true; this.refcode = this.item_data.refcode; this.$nextTick(() => { @@ -320,7 +326,6 @@ export default { }); } }, - async updateBlocks() { if (this.itemDataLoaded) { // update each block asynchronously From c172b04f1af3a0272ffd5c0f867e8f3ef6046f56 Mon Sep 17 00:00:00 2001 From: Benjamin CHARMES Date: Thu, 4 Sep 2025 11:17:27 +0200 Subject: [PATCH 7/8] Use DialogService to generate and invalidate Public QRCode and Tokens --- pydatalab/src/pydatalab/routes/v0_1/admin.py | 1 + pydatalab/src/pydatalab/routes/v0_1/items.py | 14 ++- webapp/src/components/DialogModal.vue | 22 +++- webapp/src/components/QRCode.vue | 103 ++++++++++++------- webapp/src/components/TokenTable.vue | 28 +++-- 5 files changed, 118 insertions(+), 50 deletions(-) diff --git a/pydatalab/src/pydatalab/routes/v0_1/admin.py b/pydatalab/src/pydatalab/routes/v0_1/admin.py index a303e7221..7de4b408e 100644 --- a/pydatalab/src/pydatalab/routes/v0_1/admin.py +++ b/pydatalab/src/pydatalab/routes/v0_1/admin.py @@ -172,6 +172,7 @@ def list_access_tokens(): } }, "created_by": {"$arrayElemAt": ["$user_info.display_name", 0]}, + "created_by_info": {"$arrayElemAt": ["$user_info", 0]}, } }, {"$sort": {"created_at": -1}}, diff --git a/pydatalab/src/pydatalab/routes/v0_1/items.py b/pydatalab/src/pydatalab/routes/v0_1/items.py index efdb80806..646564812 100644 --- a/pydatalab/src/pydatalab/routes/v0_1/items.py +++ b/pydatalab/src/pydatalab/routes/v0_1/items.py @@ -1232,14 +1232,26 @@ def get_access_token_info(refcode: str): token_hash = existing_token["token"] masked_token = token_hash[:8] + "..." + token_hash[-8:] + user_info = None + if existing_token.get("user"): + user_doc = flask_mongo.db.users.find_one( + {"_id": existing_token["user"]}, {"display_name": 1, "contact_email": 1} + ) + if user_doc: + user_info = { + "display_name": user_doc.get("display_name"), + "contact_email": user_doc.get("contact_email"), + } + return jsonify( { "status": "success", "has_token": True, "token_info": { - "masked_token": masked_token, + "token": masked_token, "created_at": existing_token["created_at"], "created_by": str(existing_token["user"]), + "created_by_info": user_info, }, } ), 200 diff --git a/webapp/src/components/DialogModal.vue b/webapp/src/components/DialogModal.vue index f7e0eb87c..3566baa9f 100644 --- a/webapp/src/components/DialogModal.vue +++ b/webapp/src/components/DialogModal.vue @@ -1,8 +1,12 @@ @@ -130,3 +134,13 @@ export default { }, }; + + diff --git a/webapp/src/components/QRCode.vue b/webapp/src/components/QRCode.vue index 04e03e64c..db2d60485 100644 --- a/webapp/src/components/QRCode.vue +++ b/webapp/src/components/QRCode.vue @@ -75,7 +75,7 @@ @@ -98,6 +98,9 @@