Skip to content

Commit

Permalink
Merge pull request #83 from nemozak1/opey-II-integration
Browse files Browse the repository at this point in the history
Opey ii integration
  • Loading branch information
simonredfern authored Feb 12, 2025
2 parents 0ffb010 + b78daae commit 6aca5b8
Show file tree
Hide file tree
Showing 15 changed files with 3,838 additions and 209 deletions.
6 changes: 6 additions & 0 deletions babel.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module.exports = {
presets: [
['@babel/preset-env', {targets: {node: 'current'}}],
'@babel/preset-typescript',
],
};
1 change: 1 addition & 0 deletions components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export {}
declare module 'vue' {
export interface GlobalComponents {
ChatWidget: typeof import('./src/components/ChatWidget.vue')['default']
ChatWidgetII: typeof import('./src/components/ChatWidgetII.vue')['default']
Collections: typeof import('./src/components/Collections.vue')['default']
Content: typeof import('./src/components/Content.vue')['default']
ElAlert: typeof import('element-plus/es')['ElAlert']
Expand Down
11 changes: 11 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module.exports = {
transform: {
'^.+\\.ts?$': 'ts-jest',
"^.+\\.(js)$": "babel-jest",
},
preset: 'ts-jest',
testEnvironment: 'node',
testRegex: '/tests/.*\\.(test|spec)?\\.(ts|tsx)$',
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
detectOpenHandles: true,
};
21 changes: 20 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,17 @@
"name": "api-explorer",
"version": "1.1.3",
"private": true,
"types": [
"jest",
"node"
],
"scripts": {
"dev": "vite & ts-node server/app.ts",
"build": "run-p build-only",
"build-server": "tsc --project tsconfig.server.json",
"preview": "vite preview",
"test:unit": "vitest",
"test": "jest --silent=false",
"build-only": "vite build",
"type-check": "vue-tsc --noEmit -p tsconfig.vitest.json --composite false",
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
Expand All @@ -17,6 +22,9 @@
"@element-plus/icons-vue": "^2.1.0",
"@fontsource/roboto": "^5.0.0",
"@highlightjs/vue-plugin": "^2.1.0",
"@types/jest": "^29.5.14",
"@types/supertest": "^6.0.2",
"ai": "^4.1.11",
"axios": "^1.7.4",
"cheerio": "^1.0.0",
"class-transformer": "^0.5.1",
Expand All @@ -26,9 +34,12 @@
"element-plus": "^2.3.9",
"express": "^4.21.0",
"express-session": "^1.17.3",
"got": "^14.4.5",
"highlight.js": "^11.8.0",
"json-editor-vue": "^0.17.3",
"jsonwebtoken": "^9.0.2",
"markdown-it": "^14.1.0",
"node-fetch": "v2.6",
"oauth": "^0.10.0",
"obp-typescript": "^1.0.36",
"pinia": "^2.0.37",
Expand All @@ -38,32 +49,40 @@
"routing-controllers": "^0.10.4",
"socket.io": "^4.7.5",
"socket.io-client": "^4.7.5",
"supertest": "^7.0.0",
"typedi": "^0.10.0",
"uuid": "^9.0.1",
"vanilla-jsoneditor": "^2.3.3",
"vue": "^3.5.1",
"vue-i18n": "^9.4.0",
"vue-router": "^4.2.2",
"vue-socket.io": "^3.0.10",
"ws": "^8.18.0"
},
"devDependencies": {
"@babel/core": "^7.26.8",
"@babel/preset-env": "^7.26.8",
"@babel/preset-typescript": "^7.26.0",
"@rushstack/eslint-patch": "^1.4.0",
"@types/jsdom": "^21.1.7",
"@types/jsonwebtoken": "^9.0.6",
"@types/markdown-it": "^14.1.1",
"@types/node": "^20.5.7",
"@types/node": "^20.17.17",
"@vitejs/plugin-vue": "^4.3.0",
"@vitejs/plugin-vue-jsx": "^3.1.0",
"@vue/eslint-config-prettier": "^9.0.0",
"@vue/eslint-config-typescript": "^14.0.0",
"@vue/test-utils": "^2.4.0",
"@vue/tsconfig": "^0.1.3",
"babel-jest": "^29.7.0",
"eslint": "^9.15.0",
"eslint-plugin-vue": "^9.12.0",
"jest": "^29.7.0",
"jsdom": "^25.0.1",
"npm-run-all2": "^7.0.1",
"prettier": "^3.0.1",
"superagent": "^9.0.0",
"ts-jest": "^29.2.5",
"ts-node": "^10.9.1",
"typescript": "~5.2.2",
"unplugin-auto-import": "^0.18.0",
Expand Down
4 changes: 3 additions & 1 deletion server/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,9 +138,11 @@ console.log('Execution continues with commitId:', commitId);

// Error Handling to Shut Down the App
server.on('error', (err) => {
redisClient.disconnect();
if (err.code === 'EADDRINUSE') {
console.error(`Port ${port} is already in use.`);
process.exit(1); // Shut down the app
process.exit(1);
// Shut down the app
} else {
console.error('An error occurred:', err);
}
Expand Down
90 changes: 89 additions & 1 deletion server/controllers/OpeyController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import * as fs from 'fs'
import * as jwt from 'jsonwebtoken'

@Service()
@Controller('/opey')
@Controller('/opey-old')
/**
* Controller class for handling Opey related operations.
* This used to hold the /chat endpoint, but that endpoint has become obsolete since using websockets.
Expand All @@ -45,6 +45,94 @@ export class OpeyController {
private obpClientService: OBPClientService,
) {}

@Post('/consent')
/**
* Retrieves a consent from OBP for the current user
*/
async getConsent(
@Session() session: any,
@Req() request: Request,
@Res() response: Response
): Response {
try {
console.log("Getting consent from OBP")
// Check if consent is already in session
if (session['obpConsent']) {
console.log("Consent found in session, returning cached consent ID")
const obpConsent = session['obpConsent']
// NOTE: Arguably we should not return the consent to the frontend as it could be hijacked,
// we can keep everything in the backend and only return the JWT token
return response.status(200).json({consent_id: obpConsent.consent_id});
}

const oauthConfig = session['clientConfig']
const version = this.obpClientService.getOBPVersion()
// Obbiously this should not be hard-coded, especially the consumer_id, but for now it is
const consentRequestBody = {
"everything": false,
"views": [],
"entitlements": [],
"consumer_id": "33e0a1bd-9f1d-4128-911b-8936110f802f"
}

// Get current user, only proceed if user is logged in
const currentUser = await this.obpClientService.get(`/obp/${version}/users/current`, oauthConfig)
const currentResponseKeys = Object.keys(currentUser)
if (!currentResponseKeys.includes('user_id')) {
return response.status(400).json({ message: 'User not logged in, Authentication required' });
}

// url needs to be changed once we get the 'bankless' consent endpoint
// this creates a consent for the current logged in user, and starts SCA flow i.e. sends SMS or email OTP to user
const consent = await this.obpClientService.create(`/obp/${version}/banks/gh.29.uk/my/consents/IMPLICIT`, consentRequestBody, oauthConfig)
console.log("Consent: ", consent)

// store consent in session, return consent 200 OK
session['obpConsent'] = consent
return response.status(200).json({consent_id: consent.consent_id});
} catch (error) {
console.error("Error in consent endpoint: ", error);
return response.status(500).json({ error: 'Internal Server Error '});
}
}

@Post('/consent/answer-challenge')
/**
* Endpoint to answer the consent challenge with code i.e. SMS or email OTP for SCA
* If successful, returns a Consent-JWT for use by Opey to access endpoints/ roles that the consenting user has
* This completes (i.e. is the final step in) the consent flow
*/
async answerConsentChallenge(
@Session() session: any,
@Req() request: Request,
@Res() response: Response
): Response {
try {
const oauthConfig = session['clientConfig']
const version = this.obpClientService.getOBPVersion()

const obpConsent = session['obpConsent']
if (!obpConsent) {
return response.status(400).json({ message: 'Consent not found in session' });
} else if (obpConsent.status === 'ACCEPTED') {
return response.status(400).json({ message: 'Consent already accepted' });
}
const answerBody = request.body

const consentJWT = await this.obpClientService.create(`/obp/${version}/banks/gh.29.uk/consents/${obpConsent.consent_id}/challenge`, answerBody, oauthConfig)
console.log("Consent JWT: ", consentJWT)
// store consent JWT in session, return consent JWT 200 OK
session['obpConsentJWT'] = consentJWT
return response.status(200).json(true);

} catch (error) {
console.error("Error in consent/answer-challenge endpoint: ", error);
return response.status(500).json({ error: 'Internal Server Error' });
}

}


@Post('/token')
/**
* Retrieves a JWT token for the current user.
Expand Down
Loading

0 comments on commit 6aca5b8

Please # to comment.