diff --git a/components.d.ts b/components.d.ts
index 62a87ee..06a6e91 100644
--- a/components.d.ts
+++ b/components.d.ts
@@ -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']
diff --git a/server/controllers/OpeyController.ts b/server/controllers/OpeyController.ts
index 45676f0..a181318 100644
--- a/server/controllers/OpeyController.ts
+++ b/server/controllers/OpeyController.ts
@@ -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")
+ // 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(true);
+ }
+
+ 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 consentBody = {
+ "everything": false,
+ "views": [],
+ "entitlements": [],
+ "consumer_id": "33e0a1bd-9f1d-4128-911b-8936110f802f"
+ }
+ // 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`, consentBody, oauthConfig)
+ console.log("Consent: ", consent)
+
+ // store consent in session, return consent 200 OK
+ session['obpConsent'] = consent
+ return response.status(200).json(true);
+ } 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.
diff --git a/src/components/ChatWidget.vue b/src/components/ChatWidget.vue
index d746e3e..d0784fb 100644
--- a/src/components/ChatWidget.vue
+++ b/src/components/ChatWidget.vue
@@ -33,7 +33,7 @@
import { inject } from 'vue';
import { obpApiHostKey } from '@/obp/keys';
import { getCurrentUser } from '../obp';
- import { getOpeyJWT } from '@/obp/common-functions'
+ import { getOpeyJWT, getOpeyConsent, answerOpeyConsentChallenge } from '@/obp/common-functions'
import { storeToRefs } from "pinia";
import { socket } from '@/socket';
import { useConnectionStore } from '@/stores/connection';
@@ -80,6 +80,8 @@
userInput: '',
sessionId: uuidv4(),
awaitingConnection: !this.isConnected,
+ awaitingConsentChallengeAnswer: false,
+ consentChallengeAnswer: '',
isLoading: false,
obpApiHost: null,
isLoggedIn: null,
@@ -128,6 +130,20 @@
});
}
+
+ // try to get a consent token
+ try {
+ token = await getOpeyConsent()
+ this.awaitingConsentChallengeAnswer = true
+ } catch (error) {
+ console.log('Error getting consent for opey from OBP: ', error)
+ this.errorState = true
+ ElMessage({
+ message: 'Error getting consent for opey from OBP',
+ type: 'error'
+ });
+
+ }
// Establish the WebSocket connection
console.log('Establishing WebSocket connection');
@@ -143,6 +159,33 @@
}
},
+ async answerConsentChallenge() {
+ const challengeAnswer = this.consentChallengeAnswer
+ if (!challengeAnswer) {
+ console.error("empty challenge answer")
+ return
+ }
+
+ try {
+ const answerBody = {
+ answer: challengeAnswer
+ }
+ const response = await answerOpeyConsentChallenge(answerBody)
+ if (response.status === 200) {
+ console.log('Consent challenge answered successfully, Consent approved')
+ this.awaitingConsentChallengeAnswer = false
+ }
+ } catch (error) {
+
+ console.log('Error answering consent challenge: ', error)
+ this.errorState = true
+ ElMessage({
+ message: 'Error answering consent challenge',
+ type: 'error'
+ });
+ }
+ },
+
async sendMessage() {
if (this.userInput.trim()) {
// Message in OpenAI standard format for user message
@@ -269,7 +312,15 @@
Chat with Opey
-