Skip to content

Commit 6aca5b8

Browse files
authored
Merge pull request #83 from nemozak1/opey-II-integration
Opey ii integration
2 parents 0ffb010 + b78daae commit 6aca5b8

15 files changed

Lines changed: 3838 additions & 209 deletions

babel.config.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
module.exports = {
2+
presets: [
3+
['@babel/preset-env', {targets: {node: 'current'}}],
4+
'@babel/preset-typescript',
5+
],
6+
};

components.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export {}
88
declare module 'vue' {
99
export interface GlobalComponents {
1010
ChatWidget: typeof import('./src/components/ChatWidget.vue')['default']
11+
ChatWidgetII: typeof import('./src/components/ChatWidgetII.vue')['default']
1112
Collections: typeof import('./src/components/Collections.vue')['default']
1213
Content: typeof import('./src/components/Content.vue')['default']
1314
ElAlert: typeof import('element-plus/es')['ElAlert']

jest.config.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
module.exports = {
2+
transform: {
3+
'^.+\\.ts?$': 'ts-jest',
4+
"^.+\\.(js)$": "babel-jest",
5+
},
6+
preset: 'ts-jest',
7+
testEnvironment: 'node',
8+
testRegex: '/tests/.*\\.(test|spec)?\\.(ts|tsx)$',
9+
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
10+
detectOpenHandles: true,
11+
};

package.json

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,17 @@
22
"name": "api-explorer",
33
"version": "1.1.3",
44
"private": true,
5+
"types": [
6+
"jest",
7+
"node"
8+
],
59
"scripts": {
610
"dev": "vite & ts-node server/app.ts",
711
"build": "run-p build-only",
812
"build-server": "tsc --project tsconfig.server.json",
913
"preview": "vite preview",
1014
"test:unit": "vitest",
15+
"test": "jest --silent=false",
1116
"build-only": "vite build",
1217
"type-check": "vue-tsc --noEmit -p tsconfig.vitest.json --composite false",
1318
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
@@ -17,6 +22,9 @@
1722
"@element-plus/icons-vue": "^2.1.0",
1823
"@fontsource/roboto": "^5.0.0",
1924
"@highlightjs/vue-plugin": "^2.1.0",
25+
"@types/jest": "^29.5.14",
26+
"@types/supertest": "^6.0.2",
27+
"ai": "^4.1.11",
2028
"axios": "^1.7.4",
2129
"cheerio": "^1.0.0",
2230
"class-transformer": "^0.5.1",
@@ -26,9 +34,12 @@
2634
"element-plus": "^2.3.9",
2735
"express": "^4.21.0",
2836
"express-session": "^1.17.3",
37+
"got": "^14.4.5",
2938
"highlight.js": "^11.8.0",
39+
"json-editor-vue": "^0.17.3",
3040
"jsonwebtoken": "^9.0.2",
3141
"markdown-it": "^14.1.0",
42+
"node-fetch": "v2.6",
3243
"oauth": "^0.10.0",
3344
"obp-typescript": "^1.0.36",
3445
"pinia": "^2.0.37",
@@ -38,32 +49,40 @@
3849
"routing-controllers": "^0.10.4",
3950
"socket.io": "^4.7.5",
4051
"socket.io-client": "^4.7.5",
52+
"supertest": "^7.0.0",
4153
"typedi": "^0.10.0",
4254
"uuid": "^9.0.1",
55+
"vanilla-jsoneditor": "^2.3.3",
4356
"vue": "^3.5.1",
4457
"vue-i18n": "^9.4.0",
4558
"vue-router": "^4.2.2",
4659
"vue-socket.io": "^3.0.10",
4760
"ws": "^8.18.0"
4861
},
4962
"devDependencies": {
63+
"@babel/core": "^7.26.8",
64+
"@babel/preset-env": "^7.26.8",
65+
"@babel/preset-typescript": "^7.26.0",
5066
"@rushstack/eslint-patch": "^1.4.0",
5167
"@types/jsdom": "^21.1.7",
5268
"@types/jsonwebtoken": "^9.0.6",
5369
"@types/markdown-it": "^14.1.1",
54-
"@types/node": "^20.5.7",
70+
"@types/node": "^20.17.17",
5571
"@vitejs/plugin-vue": "^4.3.0",
5672
"@vitejs/plugin-vue-jsx": "^3.1.0",
5773
"@vue/eslint-config-prettier": "^9.0.0",
5874
"@vue/eslint-config-typescript": "^14.0.0",
5975
"@vue/test-utils": "^2.4.0",
6076
"@vue/tsconfig": "^0.1.3",
77+
"babel-jest": "^29.7.0",
6178
"eslint": "^9.15.0",
6279
"eslint-plugin-vue": "^9.12.0",
80+
"jest": "^29.7.0",
6381
"jsdom": "^25.0.1",
6482
"npm-run-all2": "^7.0.1",
6583
"prettier": "^3.0.1",
6684
"superagent": "^9.0.0",
85+
"ts-jest": "^29.2.5",
6786
"ts-node": "^10.9.1",
6887
"typescript": "~5.2.2",
6988
"unplugin-auto-import": "^0.18.0",

server/app.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,9 +138,11 @@ console.log('Execution continues with commitId:', commitId);
138138

139139
// Error Handling to Shut Down the App
140140
server.on('error', (err) => {
141+
redisClient.disconnect();
141142
if (err.code === 'EADDRINUSE') {
142143
console.error(`Port ${port} is already in use.`);
143-
process.exit(1); // Shut down the app
144+
process.exit(1);
145+
// Shut down the app
144146
} else {
145147
console.error('An error occurred:', err);
146148
}

server/controllers/OpeyController.ts

Lines changed: 89 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ import * as fs from 'fs'
3333
import * as jwt from 'jsonwebtoken'
3434

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

48+
@Post('/consent')
49+
/**
50+
* Retrieves a consent from OBP for the current user
51+
*/
52+
async getConsent(
53+
@Session() session: any,
54+
@Req() request: Request,
55+
@Res() response: Response
56+
): Response {
57+
try {
58+
console.log("Getting consent from OBP")
59+
// Check if consent is already in session
60+
if (session['obpConsent']) {
61+
console.log("Consent found in session, returning cached consent ID")
62+
const obpConsent = session['obpConsent']
63+
// NOTE: Arguably we should not return the consent to the frontend as it could be hijacked,
64+
// we can keep everything in the backend and only return the JWT token
65+
return response.status(200).json({consent_id: obpConsent.consent_id});
66+
}
67+
68+
const oauthConfig = session['clientConfig']
69+
const version = this.obpClientService.getOBPVersion()
70+
// Obbiously this should not be hard-coded, especially the consumer_id, but for now it is
71+
const consentRequestBody = {
72+
"everything": false,
73+
"views": [],
74+
"entitlements": [],
75+
"consumer_id": "33e0a1bd-9f1d-4128-911b-8936110f802f"
76+
}
77+
78+
// Get current user, only proceed if user is logged in
79+
const currentUser = await this.obpClientService.get(`/obp/${version}/users/current`, oauthConfig)
80+
const currentResponseKeys = Object.keys(currentUser)
81+
if (!currentResponseKeys.includes('user_id')) {
82+
return response.status(400).json({ message: 'User not logged in, Authentication required' });
83+
}
84+
85+
// url needs to be changed once we get the 'bankless' consent endpoint
86+
// this creates a consent for the current logged in user, and starts SCA flow i.e. sends SMS or email OTP to user
87+
const consent = await this.obpClientService.create(`/obp/${version}/banks/gh.29.uk/my/consents/IMPLICIT`, consentRequestBody, oauthConfig)
88+
console.log("Consent: ", consent)
89+
90+
// store consent in session, return consent 200 OK
91+
session['obpConsent'] = consent
92+
return response.status(200).json({consent_id: consent.consent_id});
93+
} catch (error) {
94+
console.error("Error in consent endpoint: ", error);
95+
return response.status(500).json({ error: 'Internal Server Error '});
96+
}
97+
}
98+
99+
@Post('/consent/answer-challenge')
100+
/**
101+
* Endpoint to answer the consent challenge with code i.e. SMS or email OTP for SCA
102+
* If successful, returns a Consent-JWT for use by Opey to access endpoints/ roles that the consenting user has
103+
* This completes (i.e. is the final step in) the consent flow
104+
*/
105+
async answerConsentChallenge(
106+
@Session() session: any,
107+
@Req() request: Request,
108+
@Res() response: Response
109+
): Response {
110+
try {
111+
const oauthConfig = session['clientConfig']
112+
const version = this.obpClientService.getOBPVersion()
113+
114+
const obpConsent = session['obpConsent']
115+
if (!obpConsent) {
116+
return response.status(400).json({ message: 'Consent not found in session' });
117+
} else if (obpConsent.status === 'ACCEPTED') {
118+
return response.status(400).json({ message: 'Consent already accepted' });
119+
}
120+
const answerBody = request.body
121+
122+
const consentJWT = await this.obpClientService.create(`/obp/${version}/banks/gh.29.uk/consents/${obpConsent.consent_id}/challenge`, answerBody, oauthConfig)
123+
console.log("Consent JWT: ", consentJWT)
124+
// store consent JWT in session, return consent JWT 200 OK
125+
session['obpConsentJWT'] = consentJWT
126+
return response.status(200).json(true);
127+
128+
} catch (error) {
129+
console.error("Error in consent/answer-challenge endpoint: ", error);
130+
return response.status(500).json({ error: 'Internal Server Error' });
131+
}
132+
133+
}
134+
135+
48136
@Post('/token')
49137
/**
50138
* Retrieves a JWT token for the current user.

0 commit comments

Comments
 (0)