Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main'
Browse files Browse the repository at this point in the history
  • Loading branch information
JonasLukasczyk committed Jun 21, 2024
2 parents 43add2d + 4464766 commit a28efc2
Show file tree
Hide file tree
Showing 30 changed files with 167 additions and 1,825 deletions.
8 changes: 5 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@
"typecheck": "npm run typecheck:main && npm run typecheck:preload && npm run typecheck:renderer"
},
"dependencies": {
"@fslab/fsspreadsheet": "^5.0.0",
"@fslab/fsspreadsheet": "^6.1.3",
"@imengyu/vue3-context-menu": "^1.3.5",
"@nfdi4plants/arctrl": "^1.1.0",
"@nfdi4plants/arctrl": "^2.0.0-alpha.7",
"@nfdi4plants/exceljs": "0.2.0",
"@quasar/extras": "^1.16.9",
"chokidar": "^3.5.3",
Expand All @@ -48,6 +48,7 @@
},
"devDependencies": {
"@quasar/vite-plugin": "^1.6.0",
"@types/express": "^4.17.21",
"@typescript-eslint/eslint-plugin": "^6.18.0",
"@vitejs/plugin-vue": "^5.0.2",
"@vue/test-utils": "^2.4.3",
Expand All @@ -62,6 +63,7 @@
"typescript": "^5.3.3",
"vite": "^5.0.11",
"vitest": "^1.1.3",
"vue-tsc": "^1.8.27"
"vue-tsc": "^1.8.27",
"vue-unused-components-checker": "^1.1.2"
}
}
19 changes: 19 additions & 0 deletions packages/main/src/DataHubService.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/** GitLab App authentication with Secret */
type AuthWithSecret = {
id: string;
secret: string;
};

/** GitLab App authentication without Secret */
type AuthIdOnly = {
id: string;
secret?: never;
};

/** Store credetials for different datahubs.
Keys are adresses for the datahub.*/
export interface Credentials {
[key : string] : AuthWithSecret | AuthIdOnly ;
}

type User = {token: string; host: string}
172 changes: 125 additions & 47 deletions packages/main/src/DataHubService.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,25 @@
import {
ipcMain,
BrowserWindow,
shell
shell,
IpcMainInvokeEvent,
} from 'electron';

import {InternetService} from '/@/InternetService';

import {Credentials, User} from '/@/DataHubService.d';
import { Request, Response } from 'express';
import querystring from 'query-string';
const express = require('express');
import { createHash, randomInt} from 'crypto';
let authApp = null;
const authPort = 7890;

const CREDENTIALS = {


// credentials for different repositories
// if secret is left out pkce will be used for authentification
// that requires the confidential setting in the gitlab app to be turned off
const CREDENTIALS: Credentials = {
'git.nfdi4plants.org': {
id: 'af897fa1ef8474855feff07186adc6f26dee06971ee9ce4027f8f9c709a84c73',
secret: 'd578e4df6370f219b9d55b04fbaf90315bdf655fb11405a16a43505c032650de',
Expand All @@ -26,11 +34,41 @@ const CREDENTIALS = {
},
};

// alphabets for random string generation
const ALPHABET = 'abcdefghijklmnopqrstuvwxyz';
const ALPHANUMERIC = (ALPHABET+ALPHABET.toUpperCase()+'0123456789').split('')

/** generates sha256 hash of input word and base64 encodes it urlsafe */
function sha256Base64UrlsafeEncode(word: string){
// sha256 hash
let sha256Digest = createHash('sha256').update(word).digest();
// base64 encoding
return sha256Digest.toString('base64')
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=+$/, '');
}

/** generates a random string for a given set of strings of a given length */
function randomString(alphabet: Array<string>, string_length: number): string {
return Array.from({length: string_length}, (_, i) => alphabet[randomInt(alphabet.length)]).join('');
}


export const DataHubService = {

auth_host: null,
code_verifier: '',
code_challenge: '',
pkce_state: '',

getPublicationByDOI: async (e,doi)=>{
generateCodeChallenge: (): void => {
DataHubService.pkce_state = randomString(ALPHANUMERIC, 48);
DataHubService.code_verifier = randomString(ALPHANUMERIC.concat(['-','.','_','~']), 128);
DataHubService.code_challenge = sha256Base64UrlsafeEncode(DataHubService.code_verifier);
},

getPublicationByDOI: async (e: IpcMainInvokeEvent, doi: string ) => {
return await InternetService.getWebPageAsJson(
null,
{
Expand All @@ -41,18 +79,18 @@ export const DataHubService = {
);
},

getPublicationByPubMedID: async (e,id)=>{
getPublicationByPubMedID: async ( e: IpcMainInvokeEvent, id: string ) => {
return await InternetService.getWebPageAsJson(
null,
{
host: 'api.ncbi.nlm.nih.gov',
path: '/lit/ctxp/v1/pubmed/?format=csl&id='+ id,
path: '/lit/ctxp/v1/pubmed/?format=csl&id=' + id,
method: 'GET'
}
);
},

getPersonByORCID: async (e,id)=>{
getPersonByORCID: async (e: IpcMainInvokeEvent, id: string)=>{
return await InternetService.getWebPageAsJson(
null,
{
Expand All @@ -63,7 +101,7 @@ export const DataHubService = {
);
},

getArcs: async (e,[host,token])=>{
getArcs: async (e: IpcMainInvokeEvent, [host, token]:[string, string]) => {
return await InternetService.getWebPageAsJson(
null,
{
Expand All @@ -74,52 +112,79 @@ export const DataHubService = {
);
},

inspectArc: async (e,url)=>{
inspectArc: async (e: IpcMainInvokeEvent, url: string) => {
shell.openExternal(url);
return;
},

authenticate: async (e,host)=>{
authenticate: async (e: IpcMainInvokeEvent, host: any) => {
if(!CREDENTIALS[host]) return false;

DataHubService.auth_host = host;
const url_params = {
response_type: 'code',
redirect_uri: 'http://localhost:7890',
client_id: CREDENTIALS[host].id,
scope: `openid read_api email profile read_repository write_repository`
};
const auth_url = `https://${host}/oauth/authorize?${querystring.stringify(url_params)}`;
shell.openExternal(auth_url);
DataHubService.auth_host = host;
let auth_url = '';

if ('secret' in CREDENTIALS[host]) {
const url_params = {
response_type: 'code',
redirect_uri: 'http://localhost:7890',
client_id: CREDENTIALS[host].id,
scope: `openid read_api email profile read_repository write_repository`
};
auth_url = `https://${host}/oauth/authorize?${querystring.stringify(url_params)}`;
} else {
DataHubService.generateCodeChallenge();
const url_params = {
response_type: 'code',
redirect_uri: 'http://localhost:7890',
client_id: CREDENTIALS[host].id,
scope: `openid read_api email profile read_repository write_repository`,
state: DataHubService.pkce_state,
code_challenge: DataHubService.code_challenge,
code_challenge_method:"S256"
};
auth_url = `https://${host}/oauth/authorize?${querystring.stringify(url_params)}`;
}
shell.openExternal(auth_url);
return true;
},

getToken: async (e,code,host)=>{
if(!CREDENTIALS[host]) return null;
getToken: async ( e: IpcMainInvokeEvent | null, code: string, host: string ) => {
if (!CREDENTIALS[host]) return null;
let path = '';

const url_params = {
code: code,
client_id: CREDENTIALS[host].id,
client_secret: CREDENTIALS[host].secret,
grant_type: 'authorization_code',
redirect_uri: 'http://localhost:7890'
};
if ('secret' in CREDENTIALS[host]) {
const url_params = {
code: code,
client_id: CREDENTIALS[host].id,
client_secret: CREDENTIALS[host].secret,
grant_type: 'authorization_code',
redirect_uri: 'http://localhost:7890'
};
path = `/oauth/token?${querystring.stringify(url_params)}`;
} else {
const url_params = {
code: code,
client_id: CREDENTIALS[host].id,
grant_type: 'authorization_code',
redirect_uri: 'http://localhost:7890',
code_verifier: DataHubService.code_verifier
};
path = `/oauth/token?${querystring.stringify(url_params)}`;
}

const path = `/oauth/token?${querystring.stringify(url_params)}`;

return await InternetService.getWebPageAsJson(
null,
{
host: host,
path: path,
port: 443,
method: 'POST'
}
);
let returnvalue = await InternetService.getWebPageAsJson(
null,
{
host: host,
path: path,
port: 443,
method: 'POST'
}
);
return returnvalue
},

getGroups: async (e,[host,token])=>{
getGroups: async ( e: IpcMainInvokeEvent, [host, token]: [string, string] ) => {
return await InternetService.getWebPageAsJson(
null,
{
Expand All @@ -134,7 +199,7 @@ export const DataHubService = {
);
},

getUser: async (e,token,host)=>{
getUser: async ( e: IpcMainInvokeEvent | null, token: string, host: string ): Promise<User> => {
return await InternetService.getWebPageAsJson(
null,
{
Expand All @@ -156,21 +221,34 @@ export const DataHubService = {
ipcMain.handle('DataHubService.getPersonByORCID', DataHubService.getPersonByORCID );

authApp = express()
authApp.get('/', async (req, res) => {
authApp.get('/', async (req: Request, res: Response) => {
if(!req.url || !req.url.startsWith('/?code=')){
return res.send('Invalid Request.');
}

const code = req.url.split('/?code=')[1];
const token = await DataHubService.getToken(null,code,DataHubService.auth_host);
const user = await DataHubService.getUser(null,token.access_token,DataHubService.auth_host);
if(DataHubService.auth_host === null){
return res.send('<h1>Authentification Failed.</h1>');
}

const code = req.url.split('/?code=')[1].split('&')[0];
const token = await DataHubService.getToken(null, code, DataHubService.auth_host);

if (token === null) {
return res.send('<h1>Authentification Failed.</h1>');
}

const user = await DataHubService.getUser(null, token.access_token, DataHubService.auth_host);
user.token = token;
user.host = DataHubService.auth_host;

res.send('<h1>Login and Authorization Complete. You can now return to ARCitect.</h1><script>window.close()</script>');
let window = BrowserWindow.getAllWindows().find(w => !w.isDestroyed());
window.webContents.send('DataHubService.authentificationData', user);
window.focus();

if( window !== undefined){
window.webContents.send('DataHubService.authentificationData', user);
window.focus();
}

});
authApp.listen(authPort, () => {
console.log(`Authentification service listening on port ${authPort}`);
Expand Down
2 changes: 1 addition & 1 deletion packages/main/src/InternetService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const default_header = {

export const InternetService = {

getWebPageAsJson: (e,options) => {
getWebPageAsJson: (e,options): Promise<any> => {
return new Promise(
(resolve, reject) => {
try {
Expand Down
6 changes: 2 additions & 4 deletions packages/renderer/src/ArcControlService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@ import { reactive } from 'vue'

import AppProperties from './AppProperties.ts';

import { ARC } from "@nfdi4plants/arctrl/ARC.js";
import { ArcInvestigation } from "@nfdi4plants/arctrl/ISA/ISA/ArcTypes/ArcTypes.js";
import { gitignoreContract } from "@nfdi4plants/arctrl/Contracts/Contracts.Git.js";
import { ARC, ArcInvestigation } from "@nfdi4plants/arctrl";
import { gitignoreContract } from "@nfdi4plants/arctrl/Contract/Git";
import { Xlsx } from '@fslab/fsspreadsheet/Xlsx.js';
import {FileSystemTree} from '@nfdi4plants/arctrl/FileSystem/FileSystemTree.js'
import {Contract} from '@nfdi4plants/arctrl/Contract/Contract.js'

import pDebounce from 'p-debounce';
Expand Down
4 changes: 2 additions & 2 deletions packages/renderer/src/Property.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { reactive, watch, ref } from 'vue';

import {OntologyAnnotation} from '@nfdi4plants/arctrl/ISA/ISA/JsonTypes/OntologyAnnotation.js';
import {OntologyAnnotation} from '@nfdi4plants/arctrl';

const autoLabel = property => {
let words = property
Expand All @@ -26,7 +26,7 @@ const Property = (model: any,property: string, config?: any)=>{
// console.log(property, model[property]);
config = config || {};
if(config.type==='ontology' && !model[property])
model[property] = OntologyAnnotation.fromString('');
model[property] = new OntologyAnnotation('');

const p = reactive({
model: model,
Expand Down
Loading

0 comments on commit a28efc2

Please sign in to comment.