@sjcrh/proteinpaint-server 2.191.3-0 → 2.191.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sjcrh/proteinpaint-server",
3
- "version": "2.191.3-0",
3
+ "version": "2.191.3",
4
4
  "type": "module",
5
5
  "description": "a genomics visualization tool for exploring a cohort's genotype and phenotype data",
6
6
  "main": "src/app.js",
@@ -63,7 +63,7 @@
63
63
  "@sjcrh/proteinpaint-r": "2.190.0",
64
64
  "@sjcrh/proteinpaint-rust": "2.191.1",
65
65
  "@sjcrh/proteinpaint-shared": "2.191.2",
66
- "@sjcrh/proteinpaint-types": "2.191.3-0",
66
+ "@sjcrh/proteinpaint-types": "2.191.3",
67
67
  "@types/express": "^5.0.0",
68
68
  "@types/express-session": "^1.18.1",
69
69
  "better-sqlite3": "^12.4.1",
@@ -11,13 +11,23 @@ function init({ genomes }) {
11
11
  const ds = g.datasets[query.dslabel];
12
12
  if (!ds.queries?.WSImages?.db) throw new Error("WSImages database not found.");
13
13
  const connection = getDbConnection(ds);
14
+ const aiHalAuth = ds.queries?.AIHalAuth;
15
+ if (!aiHalAuth) throw new Error("AIHalAuth queries not found in dataset.");
16
+ const userEmail = req.query.__protected__.clientAuthResult?.email || "";
14
17
  if (query.for === "list") {
15
- const projects = getProjects(connection);
18
+ let projects = getProjects(connection);
19
+ if (!aiHalAuth.checkAuthorization(req, "listAllProjects")) {
20
+ projects = projects.filter((p) => aiHalAuth.getUsers(connection, p.id).includes(userEmail));
21
+ }
16
22
  res.send(projects);
17
23
  } else if (query.for === "admin") {
18
- if (req.method === "PUT" || query.project.type === "new") addProject(connection, query.project);
19
- else if (req.method === "POST") editProject(connection, query.project);
20
- else if (req.method === "DELETE") deleteProject(connection, query.project.id);
24
+ if (req.method === "PUT" || query.project.type === "new" && aiHalAuth.checkAuthorization(req, "addProject"))
25
+ addProject(connection, query.project);
26
+ else if (req.method === "POST" && aiHalAuth.checkAuthorization(req, "editProject")) {
27
+ editProject(connection, query.project);
28
+ aiHalAuth.setUser(connection, query.project.id, userEmail || "admin", 12);
29
+ } else if (req.method === "DELETE" && aiHalAuth.checkAuthorization(req, "deleteProject"))
30
+ deleteProject(connection, query.project.id);
21
31
  else throw new Error('Invalid request method for="admin" in aiProjectAdmin route.');
22
32
  let projectId = query.project.id;
23
33
  if (!projectId) {
@@ -33,6 +43,8 @@ function init({ genomes }) {
33
43
  message: `Project ${query.project.name} processed successfully`
34
44
  });
35
45
  } else if (query.for === "filterImages") {
46
+ const parsedLogin = aiHalAuth.parseLogin(req, connection);
47
+ if (parsedLogin.status === "error") throw new Error("Authentication failed: " + parsedLogin.error);
36
48
  const q = ds.cohort.termdb.q;
37
49
  const data = await q.getFilteredImages(query.project.filter);
38
50
  data.selectedImages = await ds.queries?.WSImages?.selectWSIImages();
@@ -41,8 +53,23 @@ function init({ genomes }) {
41
53
  data
42
54
  });
43
55
  } else if (query.for === "images") {
56
+ const parsedLogin = aiHalAuth.parseLogin(req, connection);
57
+ if (parsedLogin.status === "error") throw new Error("Authentication failed: " + parsedLogin.error);
44
58
  const images = getImages(connection, query.project);
45
- res.send({ images });
59
+ res.send({ images, status: "ok" });
60
+ } else if (query.for === "logout") {
61
+ aiHalAuth.setUser(connection, query.project.id, null);
62
+ res.status(200).send({
63
+ status: "ok",
64
+ message: "User logged out successfully"
65
+ });
66
+ } else if (query.for === "auth") {
67
+ const authorizations = {};
68
+ const actions = query.auth || [];
69
+ for (const action of actions) {
70
+ authorizations[action] = aiHalAuth.checkAuthorization(req, action);
71
+ }
72
+ res.status(200).send(authorizations);
46
73
  } else {
47
74
  res.send({
48
75
  status: "error",
@@ -59,7 +86,7 @@ function init({ genomes }) {
59
86
  };
60
87
  }
61
88
  function getProjects(connection) {
62
- const sql = "SELECT name, id FROM project";
89
+ const sql = "SELECT name, id, current_user FROM project";
63
90
  return runSQL(connection, sql);
64
91
  }
65
92
  function getImages(connection, project) {
@@ -14,7 +14,7 @@ function init({ genomes }) {
14
14
  const ds = g.datasets[query.dslabel];
15
15
  if (!ds) throw new Error("invalid dataset name");
16
16
  if (typeof ds.queries?.WSImages?.deleteAnnotation === "function") {
17
- const result = await ds.queries.WSImages.deleteAnnotation(query);
17
+ const result = await ds.queries.WSImages.deleteAnnotation(query, req);
18
18
  if (result?.status === "error") {
19
19
  return res.status(500).send(result);
20
20
  }
@@ -38,8 +38,16 @@ async function validate_query_deleteWSIAnnotation(ds) {
38
38
  validateQuery(ds, connection);
39
39
  }
40
40
  function validateQuery(ds, connection) {
41
- ds.queries.WSImages.deleteAnnotation = async (query) => {
41
+ ds.queries.WSImages.deleteAnnotation = async (query, req) => {
42
42
  const zoomCoordinates = JSON.stringify(query.tileSelection.zoomCoordinates);
43
+ const { current_user: currentUser } = connection.prepare("SELECT current_user FROM project WHERE id = ?").get(query.projectId);
44
+ if (!await ds.queries?.AIHalAuth?.checkAuthorization(req, "annotate", currentUser)) {
45
+ return {
46
+ status: "error",
47
+ error: "logout"
48
+ };
49
+ }
50
+ const email = req.query.__protected__.clientAuthResult?.email || "";
43
51
  if (checkSelectionType(query.tileSelection, SelectionPrefixes.Prediction) && query.tileSelection.flag !== FlagStatus.Normal) {
44
52
  try {
45
53
  const projectId = query.projectId;
@@ -80,10 +88,10 @@ function validateQuery(ds, connection) {
80
88
  connection.prepare(
81
89
  `
82
90
  INSERT INTO project_flagged_predictions
83
- (project_id, prediction_class_id, coordinates, flag_type, timestamp, image_id)
84
- VALUES (?, ?, ?, ?, ?, ?)
91
+ (project_id, user_email, prediction_class_id, coordinates, flag_type, timestamp, image_id)
92
+ VALUES (?, ?, ?, ?, ?, ?, ?)
85
93
  `
86
- ).run(projectId, predictionId, zoomCoordinates, flagType, (/* @__PURE__ */ new Date()).toISOString(), imageId);
94
+ ).run(projectId, email, predictionId, zoomCoordinates, flagType, (/* @__PURE__ */ new Date()).toISOString(), imageId);
87
95
  return { status: "ok" };
88
96
  } catch (error) {
89
97
  console.error("Error inserting flagged prediction:", error);
@@ -11,7 +11,7 @@ function init({ genomes }) {
11
11
  const ds = g.datasets[query.dslabel];
12
12
  if (!ds) throw new Error("invalid dataset name");
13
13
  if (typeof ds.queries?.WSImages?.saveWSIAnnotation === "function") {
14
- const result = await ds.queries.WSImages.saveWSIAnnotation(query);
14
+ const result = await ds.queries.WSImages.saveWSIAnnotation(query, req);
15
15
  if (result?.status === "error") {
16
16
  return res.status(500).send(result);
17
17
  }
@@ -35,8 +35,9 @@ async function validate_query_saveWSIAnnotation(ds) {
35
35
  validateQuery(ds, connection);
36
36
  }
37
37
  function validateQuery(ds, connection) {
38
- ds.queries.WSImages.saveWSIAnnotation = async (annotation) => {
38
+ ds.queries.WSImages.saveWSIAnnotation = async (annotation, req) => {
39
39
  try {
40
+ const { email = "" } = req.query.__protected__.clientAuthResult ?? {};
40
41
  const tileSelection = annotation.tileSelection;
41
42
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
42
43
  const projectId = annotation.projectId;
@@ -46,6 +47,13 @@ function validateQuery(ds, connection) {
46
47
  const classId = annotation.classId;
47
48
  const isAnnotation = checkSelectionType(tileSelection, SelectionPrefixes.Annotation);
48
49
  const isPrediction = checkSelectionType(tileSelection, SelectionPrefixes.Prediction);
50
+ const currentUser = connection.prepare("SELECT current_user FROM project WHERE id = ?").get(projectId);
51
+ if (!await ds.queries?.AIHalAuth?.checkAuthorization(req, "annotate", currentUser?.current_user ?? null)) {
52
+ return {
53
+ status: "error",
54
+ error: "logout"
55
+ };
56
+ }
49
57
  if (!isAnnotation && !isPrediction) {
50
58
  return {
51
59
  status: "error",
@@ -65,8 +73,7 @@ function validateQuery(ds, connection) {
65
73
  AND image_path = ?
66
74
  LIMIT 1
67
75
  `;
68
- const getImageStmt = connection.prepare(getImageIdSql);
69
- const imageRow = getImageStmt.get(projectId, wsimageFilename);
76
+ const imageRow = connection.prepare(getImageIdSql).get(projectId, wsimageFilename);
70
77
  if (!imageRow?.id) {
71
78
  return {
72
79
  status: "error",
@@ -93,19 +100,6 @@ function validateQuery(ds, connection) {
93
100
  AND coordinates = ?`
94
101
  ).run(projectId, imageId, coords);
95
102
  if (isAnnotation) {
96
- const userRow = connection.prepare(
97
- `SELECT id
98
- FROM project_users
99
- ORDER BY id
100
- LIMIT 1`
101
- ).get();
102
- const userId = userRow?.id;
103
- if (userId === void 0) {
104
- return {
105
- status: "error",
106
- error: "No users found in project_users table."
107
- };
108
- }
109
103
  if (tileSelection.flag === FlagStatus.Normal) {
110
104
  connection.prepare(
111
105
  `
@@ -113,7 +107,7 @@ function validateQuery(ds, connection) {
113
107
  project_id, user_id, coordinates, timestamp,class_id, image_id
114
108
  ) VALUES (?, ?, ?, ?, ?, ?)
115
109
  `
116
- ).run(projectId, userId, coords, timestamp, classId, imageId);
110
+ ).run(projectId, email, coords, timestamp, classId, imageId);
117
111
  } else {
118
112
  connection.prepare(
119
113
  `
@@ -121,17 +115,18 @@ function validateQuery(ds, connection) {
121
115
  project_id, user_id, coordinates, timestamp, flagged,class_id, image_id
122
116
  ) VALUES (?, ?, ?, ?, ?, ?, ?)
123
117
  `
124
- ).run(projectId, userId, coords, timestamp, flag, classId, imageId);
118
+ ).run(projectId, email, coords, timestamp, flag, classId, imageId);
125
119
  }
126
120
  } else if (isPrediction) {
127
121
  if (tileSelection.flag !== FlagStatus.Normal) {
128
122
  const insertSql = `
129
- INSERT INTO project_flagged_predictions (project_id, prediction_class_id, coordinates, flag_type,image_id,timestamp)
130
- VALUES (?, ?, ?, ?,?,?)
123
+ INSERT INTO project_flagged_predictions (project_id, user_email, prediction_class_id, coordinates, flag_type,image_id,timestamp)
124
+ VALUES (?, ?, ?, ?, ?, ?, ?)
131
125
  `;
132
126
  const insertStmt = connection.prepare(insertSql);
133
127
  insertStmt.run(
134
128
  annotation.projectId,
129
+ email,
135
130
  annotation.classId,
136
131
  JSON.stringify(tileSelection.zoomCoordinates),
137
132
  tileSelection.flag,