mirror of
https://github.com/OpenBankProject/API-Explorer-II.git
synced 2026-02-06 10:47:04 +00:00
opey consents WIP
This commit is contained in:
parent
6c4676cc87
commit
1ca686b892
1
components.d.ts
vendored
1
components.d.ts
vendored
@ -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']
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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 @@
|
||||
<span>Chat with Opey</span>
|
||||
<img alt="Powered by OpenAI" src="@/assets/powered-by-openai-badge-outlined-on-dark.svg" height="32">
|
||||
</div>
|
||||
<div v-if="this.isLoggedIn" v-loading="this.awaitingConnection" element-loading-text="Awaiting Connection..." class="chat-messages" ref="messages">
|
||||
<div v-show="this.awaitingConsentChallengeAnswer">
|
||||
<el-input
|
||||
v-model="consentChallengeAnswer"
|
||||
placeholder="Enter the challenge answer"
|
||||
>
|
||||
</el-input>
|
||||
<el-button @click="answerConsentChallenge">Submit</el-button>
|
||||
</div>
|
||||
<div v-if="this.isLoggedIn" v-loading="this.awaitingConnection && !this.awaitingConsentChallengeAnswer" element-loading-text="Awaiting Connection..." class="chat-messages" ref="messages">
|
||||
<div v-for="(message, index) in chatMessages" :key="index" :class="['chat-message', message.role]">
|
||||
<div v-if="(this.isStreaming)&&(index === this.chatMessages.length -1)">
|
||||
<div v-html="renderMarkdown(this.currentMessageSnapshot)"></div>
|
||||
|
||||
3
src/components/ChatWidgetII.vue
Normal file
3
src/components/ChatWidgetII.vue
Normal file
@ -0,0 +1,3 @@
|
||||
<!--
|
||||
placeholder for Opey II Chat widget
|
||||
-->
|
||||
@ -85,6 +85,29 @@ export async function getOpeyJWT() {
|
||||
return token
|
||||
}
|
||||
|
||||
export async function getOpeyConsent() {
|
||||
const response = await axios.post('/api/opey/consent').catch((error) => {
|
||||
if (error.response) {
|
||||
throw new Error(`getOpeyConsent returned an error: ${error.toJSON()}`);
|
||||
} else {
|
||||
throw new Error(`getOpeyConsent returned an error: ${error.message}`);
|
||||
}
|
||||
});
|
||||
const consent = String(response?.data?.consent)
|
||||
return consent
|
||||
}
|
||||
|
||||
export async function answerOpeyConsentChallenge(answerBody: any) {
|
||||
const response = await axios.post('/api/opey/consent/answer-challenge', answerBody).catch((error) => {
|
||||
if (error.response) {
|
||||
throw new Error(`answerOpeyConsentChallenge returned an error: ${error.toJSON()}`);
|
||||
} else {
|
||||
throw new Error(`answerOpeyConsentChallenge returned an error: ${error.message}`);
|
||||
}
|
||||
});
|
||||
return response
|
||||
}
|
||||
|
||||
export function clearCacheByName(cacheName: string) {
|
||||
if ('caches' in window) {
|
||||
caches.delete(cacheName).then(function(success) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user