change naming of opeyConsent to obpConsent

This commit is contained in:
Nemo Godebski-Pedersen 2025-03-17 14:58:42 +00:00
parent fd69736eb0
commit 43c7934e0c
12 changed files with 164 additions and 73 deletions

View File

@ -1,5 +1,5 @@
import { Controller, Session, Req, Res, Post, Get } from 'routing-controllers'
import { Request, Response } from 'express'
import { Request, Response} from 'express'
import { Readable } from "node:stream"
import { ReadableStream as WebReadableStream } from "stream/web"
import { Service } from 'typedi'
@ -7,7 +7,7 @@ import OBPClientService from '../services/OBPClientService'
import OpeyClientService from '../services/OpeyClientService'
import OBPConsentsService from '../services/OBPConsentsService'
import { UserInput } from '../schema/OpeySchema'
import { UserInput, OpeyConfig} from '../schema/OpeySchema'
import { APIApi, Configuration, ConsentApi, ConsumerConsentrequestsBody, InlineResponse20151 } from 'obp-api-typescript'
@Service()
@ -46,6 +46,13 @@ export class OpeyController {
@Res() response: Response,
): Promise<Response> {
// Check if the consent is in the session, and can be added to the headers
const opeyConfig = session['opeyConfig']
if (!opeyConfig) {
console.error("Opey config not found in session")
return response.status(500).json({ error: 'Internal Server Error' })
}
// Read user input from request body
let user_input: UserInput
try {
@ -83,7 +90,7 @@ export class OpeyController {
try {
// Read web stream from OpeyClientService
console.log("Calling OpeyClientService.stream")
stream = await this.opeyClientService.stream(user_input)
stream = await this.opeyClientService.stream(user_input, opeyConfig)
} catch (error) {
console.error("Error reading stream: ", error)
@ -184,6 +191,14 @@ export class OpeyController {
@Res() response: Response
): Promise<Response | any> {
// Check if the consent is in the session, and can be added to the headers
const opeyConfig = session['opeyConfig']
if (!opeyConfig) {
console.error("Opey config not found in session")
return response.status(500).json({ error: 'Internal Server Error' })
}
let user_input: UserInput
try {
user_input = {
@ -197,7 +212,7 @@ export class OpeyController {
}
try {
const opey_response = await this.opeyClientService.invoke(user_input)
const opey_response = await this.opeyClientService.invoke(user_input, opeyConfig)
//console.log("Opey response: ", opey_response)
return response.status(200).json(opey_response)
@ -279,12 +294,17 @@ export class OpeyController {
): Promise<Response | any> {
try {
// create consent as logged in user
const obpConsent = await this.obpConsentsService.createConsent(session)
const opeyConfig = await this.opeyClientService.getOpeyConfig()
session['opeyConfig'] = opeyConfig
console.log("Consent: ", obpConsent)
// Either here or in this method, we should check if there is already a consent stored in the session
await this.obpConsentsService.createConsent(session)
session['obpConsent'] = obpConsent
return response.status(200).json({consent_id: obpConsent?.consent_id});
console.log("Consent at controller: ", session['opeyConfig'])
const authConfig = session['opeyConfig']['authConfig']
return response.status(200).json({consent_id: authConfig?.obpConsent.consent_id});
} catch (error) {
console.error("Error in consent endpoint: ", error);

View File

@ -21,7 +21,8 @@ export interface OBPConsent {
status: string;
}
export interface AuthConfig {
opeyConsent: OBPConsent;
obpConsent: OBPConsent;
// Add more auth config fields here if needed
}
export interface OpeyConfig {

View File

@ -85,6 +85,13 @@ export default class OBPConsentsService {
try {
const consentResponse = await client.oBPv510CreateConsentImplicit(body, {headers: {'Content-Type': 'application/json',}})
// Save the consent in the session
session['opeyConfig'] = {
authConfig: {
obpConsent: consentResponse.data
}
}
return consentResponse.data
} catch (error: any) {

View File

@ -1,5 +1,5 @@
import { Service } from 'typedi'
import { UserInput, StreamInput, OpeyConfig, AuthConfig, ConsentRequestResponse } from '../schema/OpeySchema'
import { UserInput, StreamInput, OpeyConfig, ConsentRequestResponse } from '../schema/OpeySchema'
import OBPClientService from './OBPClientService'
@Service()
@ -55,11 +55,11 @@ export default class OpeyClientService {
...partialConfig.authConfig
};
// If opeyConsent is provided, merge it too
if (partialConfig.authConfig.opeyConsent && mergedConfig.authConfig.opeyConsent) {
mergedConfig.authConfig.opeyConsent = {
...mergedConfig.authConfig.opeyConsent,
...partialConfig.authConfig.opeyConsent
// If obpConsent is provided, merge it too
if (partialConfig.authConfig.obpConsent && mergedConfig.authConfig.obpConsent) {
mergedConfig.authConfig.obpConsent = {
...mergedConfig.authConfig.obpConsent,
...partialConfig.authConfig.obpConsent
};
}
}
@ -116,15 +116,20 @@ export default class OpeyClientService {
* @throws Error if there's any issue streaming from Opey
*/
async stream(user_input: UserInput, opeyConfig?: Partial<OpeyConfig>): Promise<ReadableStream> {
console.log("OpeyConfig: ", opeyConfig) //DEBUG
const config = await this.getOpeyConfig(opeyConfig)
console.log("OpeyConfig after getting: ", config) //DEBUG
// Check if we have the consent for Opey
const auth = await this.checkAuthConfig(config)
if (!auth.valid) {
throw new Error(`AuthConfig not valid: ${auth.reason}`)
}
// Should check here if the consent status is 'ACCEPTED' before streaming
// Get auth headers
const authHeaders = await this.getConsentAuthHeaders(config)
try {
@ -138,10 +143,7 @@ export default class OpeyClientService {
const response = await fetch(url, {
method: 'POST',
headers: {
"Authorization": `Bearer ${config.authConfig.opeyConsent.jwt}`, // Should not be undefined as we already checked authConfig
"Content-Type": "application/json"
},
headers: authHeaders,
body: JSON.stringify(stream_input)
})
if (!response.body) {
@ -181,6 +183,9 @@ export default class OpeyClientService {
throw new Error(`AuthConfig not valid: ${auth.reason}`)
}
// Get auth headers
const authHeaders = await this.getConsentAuthHeaders(config)
const url = `${config.baseUri}${config.paths.invoke}`
console.log(`Posting to Opey, STREAMING OFF: ${JSON.stringify(user_input)}\n URL: ${url}`) //DEBUG
@ -188,10 +193,7 @@ export default class OpeyClientService {
try {
const response = await fetch(url, {
method: 'POST',
headers: {
"Authorization": `Bearer ${config.authConfig.opeyConsent.jwt}`, // not undefined as we checked authConfig
"Content-Type": "application/json"
},
headers: authHeaders,
body: JSON.stringify(user_input)
})
if (response.status === 200) {
@ -210,7 +212,7 @@ export default class OpeyClientService {
* Checks if the authentication configuration in the OpeyConfig is valid.
*
* This method validates that:
* - authConfig exists and contains opeyConsent
* - authConfig exists and contains obpConsent
* - the OBP consent object has a status of 'ACCEPTED'
*
* @param opeyConfig - The configuration object to validate
@ -220,29 +222,29 @@ export default class OpeyClientService {
*/
async checkAuthConfig(opeyConfig: OpeyConfig): Promise<{ valid: boolean; reason: string }> {
if (!opeyConfig.authConfig || !opeyConfig.authConfig.opeyConsent) {
console.log("Checking auth config: ", opeyConfig) //DEBUG
if (!opeyConfig.authConfig || !opeyConfig.authConfig.obpConsent) {
return { valid: false, reason: 'No authConfig set in opeyConfig, authentication required' }
} else if (!opeyConfig.authConfig.opeyConsent) {
} else if (!opeyConfig.authConfig.obpConsent) {
return { valid: false, reason: 'Opey consent missing in opeyConfig.authConfig' }
}
if (!(opeyConfig.authConfig.opeyConsent.status === 'ACCEPTED')) {
if (!(opeyConfig.authConfig.obpConsent.status === 'ACCEPTED')) {
return { valid: false, reason: 'Opey consent status is not ACCEPTED' }
}
return { valid: true, reason: 'AuthConfig is valid' }
}
// async createConsentRequest(): Promise<ConsentRequestResponse | Error> {
// // Create a consent request for the current user
// const oauthConfig = session['clientConfig']
// try {
// this.obpClientService.create('/obp/v5.0.0/consumer/consent-requests', )
// } catch (error) {
// throw new Error(`Error creating consent request: ${error}`)
// }
// }
async getConsentAuthHeaders(opeyConfig: OpeyConfig): Promise<{ [key: string]: string } | undefined> {
if (!opeyConfig.authConfig || !opeyConfig.authConfig.obpConsent) {
throw new Error('AuthConfig not found or obpConsent missing')
}
return {
'Consent-JWT': opeyConfig.authConfig.obpConsent.jwt,
'Content-Type': 'application/json'
}
}
}

View File

@ -129,4 +129,30 @@ describe('OBPConsentsService.createConsent', () => {
expect(consentRequest).toHaveProperty('status', 'INITIATED');
expect(mockOBPv310CreateConsentImplicit).toHaveBeenCalled();
})
it('should update the session with a valid OpeyConfig with auth', async () => {
// Create mock response function for consent IMPLICIT
mockOBPv310CreateConsentImplicit = vi.fn().mockResolvedValue({
data: {
consent_id: '12345678',
jwt: "asdjfawieofaowbfaowhh2084h02pefhh0.20fh02h0h29eyf09q3h09h.2-hf4-8h284hf0h0h0284r0",
status: 'INITIATED',
},
} as AxiosResponse<InlineResponse2017>);
mockConsentApi = {
oBPv510CreateConsentImplicit: mockOBPv310CreateConsentImplicit,
} as unknown as ConsentApi;
// Mock the createConsentClient method
vi.spyOn(obpConsentsService, 'createUserConsentsClient').mockResolvedValue(mockConsentApi);
await obpConsentsService.createConsent(mockSession);
expect(mockSession).toHaveProperty('opeyConfig');
expect(mockSession.opeyConfig).toHaveProperty('authConfig');
expect(mockSession.opeyConfig.authConfig).toHaveProperty('obpConsent');
expect(mockSession.opeyConfig.authConfig.obpConsent).toHaveProperty('status', 'INITIATED');
expect(mockSession.opeyConfig.authConfig.obpConsent).toHaveProperty('jwt', 'asdjfawieofaowbfaowhh2084h02pefhh0.20fh02h0h29eyf09q3h09h.2-hf4-8h284hf0h0h0284r0');
});
})

View File

@ -73,7 +73,7 @@ describe('stream', async () => {
opeyConfig = {
authConfig: {
opeyConsent: {
obpConsent: {
consent_id: 'test-consent-id',
status: 'ACCEPTED',
jwt: 'test-jwt-token',
@ -83,7 +83,7 @@ describe('stream', async () => {
})
it('should add the opeyConsent jwt to the Authorization header', async () => {
it('should add the obpConsent jwt to the Authorization header', async () => {
const user_input: UserInput = {
message: 'test message',
@ -95,7 +95,7 @@ describe('stream', async () => {
expect(global.fetch).toHaveBeenCalledWith(expect.any(String), expect.objectContaining({
method: 'POST',
headers: expect.objectContaining({
"Authorization": `Bearer ${opeyConfig.authConfig?.opeyConsent.jwt}`,
"Consent-JWT": `${opeyConfig.authConfig?.obpConsent.jwt}`,
}),
}))
@ -197,7 +197,7 @@ describe('getOpeyConfig', async () => {
it('should merge authConfig when provided', async () => {
const partialConfig: Partial<OpeyConfig> = {
authConfig: {
opeyConsent: {
obpConsent: {
consent_id: 'test-consent-id',
status: 'ACCEPTED',
jwt: 'test-jwt-token',
@ -209,7 +209,7 @@ describe('getOpeyConfig', async () => {
// Verify authConfig was added with correct values
expect(resultConfig.authConfig).toBeDefined();
expect(resultConfig.authConfig!.opeyConsent).toEqual({
expect(resultConfig.authConfig!.obpConsent).toEqual({
consent_id: 'test-consent-id',
status: 'ACCEPTED',
jwt: 'test-jwt-token',
@ -272,14 +272,14 @@ describe('checkAuthConfig', async () => {
expect(result.reason).toBe('No authConfig set in opeyConfig, authentication required');
});
it('should return invalid when opeyConsent is missing', async () => {
it('should return invalid when obpConsent is missing', async () => {
const opeyConfig: OpeyConfig = {
baseUri: 'http://localhost:5000',
paths: {
status: '/status',
},
authConfig: {
// opeyConsent intentionally missing
// obpConsent intentionally missing
}
};
@ -296,7 +296,7 @@ describe('checkAuthConfig', async () => {
status: '/status',
},
authConfig: {
opeyConsent: {
obpConsent: {
status: 'INITIATED',
jwt: 'test-token',
consent_id: '12345',
@ -318,7 +318,7 @@ describe('checkAuthConfig', async () => {
status: '/status',
},
authConfig: {
opeyConsent: {
obpConsent: {
status: 'ACCEPTED',
jwt: 'test-token',
consent_id: '12345',
@ -339,7 +339,7 @@ describe('checkAuthConfig', async () => {
status: '/status',
},
authConfig: {
opeyConsent: {
obpConsent: {
status: 'ACCEPTED',
jwt: 'test-token',
consent_id: '12345',

View File

@ -3,11 +3,13 @@ import { OpeyController } from "../controllers/OpeyIIController";
import OpeyClientService from '../services/OpeyClientService';
import OBPClientService from '../services/OBPClientService';
import OBPConsentsService from '../services/OBPConsentsService';
import { OpeyConfig } from '../schema/OpeySchema';
import Stream, { Readable } from 'stream';
import { Request, Response } from 'express';
import httpMocks from 'node-mocks-http'
import { EventEmitter } from 'events';
import { InlineResponse2017 } from 'obp-api-typescript';
import { c } from 'vitest/dist/reporters-5f784f42.js';
vi.mock("../../server/services/OpeyClientService", () => {
return {
@ -169,7 +171,16 @@ describe('OpeyController consents', () => {
const MockOpeyClientService = {
authConfig: {},
opeyConfig: {},
opeyConfig: {
baseUri: 'http://localhost:8080',
paths: {
invoke: '/invoke',
status: '/status',
stream: '/stream',
approve_tool: '/approve_tool/{thread_id}',
feedback: '/feedback',
}
},
getOpeyStatus: vi.fn(async () => {
return {status: 'running'}
}),
@ -189,17 +200,36 @@ describe('OpeyController consents', () => {
return {
content: 'Hi this is Opey',
}
}),
getOpeyConfig: vi.fn(async (partialConfig?) => {
return {
baseUri: 'http://localhost:8080',
paths: {
invoke: '/invoke',
status: '/status',
stream: '/stream',
approve_tool: '/approve_tool/{thread_id}',
feedback: '/feedback',
}
}
})
} as unknown as OpeyClientService
const MockOBPConsentsService = {
createConsent: vi.fn(async () => {
return {
createConsent: vi.fn(async (session) => {
const mockConsentResponse = {
"consent_id": "8ca8a7e4-6d02-40e3-a129-0b2bf89de9f0",
"jwt": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6Ik9CUCBDb25zZW50IFRva2VuIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE2MTYyMzkwMjJ9.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c",
"status": "INITIATED",
} as InlineResponse2017
})
session['opeyConfig'] = {
authConfig: { obpConsent: mockConsentResponse }
}
return mockConsentResponse
}),
} as unknown as OBPConsentsService
// Instantiate OpeyController with the mocked OpeyClientService
@ -235,9 +265,14 @@ describe('OpeyController consents', () => {
})
// Expect that the consent object was saved in the session
expect(session).toHaveProperty('obpConsent')
expect(session['obpConsent']).toHaveProperty('consent_id', "8ca8a7e4-6d02-40e3-a129-0b2bf89de9f0")
expect(session['obpConsent']).toHaveProperty('jwt', "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6Ik9CUCBDb25zZW50IFRva2VuIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE2MTYyMzkwMjJ9.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c")
expect(session['obpConsent']).toHaveProperty('status', "INITIATED")
expect(session).toHaveProperty('opeyConfig')
const opeyConfig = session['opeyConfig']
console.log(opeyConfig)
expect(opeyConfig).toHaveProperty('authConfig')
expect(session['opeyConfig']).toHaveProperty('authConfig')
expect(session['opeyConfig']['authConfig']).toHaveProperty('obpConsent')
expect(session['opeyConfig']['authConfig']['obpConsent']).toHaveProperty('consent_id', "8ca8a7e4-6d02-40e3-a129-0b2bf89de9f0")
expect(session['opeyConfig']['authConfig']['obpConsent']).toHaveProperty('jwt', "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6Ik9CUCBDb25zZW50IFRva2VuIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE2MTYyMzkwMjJ9.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c")
expect(session['opeyConfig']['authConfig']['obpConsent']).toHaveProperty('status', "INITIATED")
})
})

View File

@ -8,7 +8,7 @@ import { Close, Top as ElTop } from '@element-plus/icons-vue'
import { ElMessage } from 'element-plus'
import ChatMessage from './ChatMessage.vue';
import { v4 as uuidv4 } from 'uuid';
import { OpeyStreamContext, OpeyMessage, UserMessage, sendOpeyMessage, getOpeyConsent } from '@/obp/opey-functions';
import { OpeyStreamContext, OpeyMessage, UserMessage, sendOpeyMessage, getobpConsent } from '@/obp/opey-functions';
import { getCurrentUser } from '@/obp';
export default {
@ -64,7 +64,7 @@ export default {
},
async initiateConsentFlow() {
// get consent for Opey from user
const consentResponse = await getOpeyConsent()
const consentResponse = await getobpConsent()
if (consentResponse) {
const consentId = consentResponse.consent_id

View File

@ -34,7 +34,7 @@
import { inject } from 'vue';
import { obpApiHostKey } from '@/obp/keys';
import { getCurrentUser } from '../obp';
import { getOpeyJWT, getOpeyConsent, answerOpeyConsentChallenge } from '@/obp/common-functions'
import { getOpeyJWT, getobpConsent, answerobpConsentChallenge } from '@/obp/common-functions'
import { storeToRefs } from "pinia";
import { socket } from '@/socket';
import { useConnectionStore } from '@/stores/connection';
@ -126,7 +126,7 @@
// Check if the user already has a token in the cookies
try {
const consentResponse = await getOpeyConsent()
const consentResponse = await getobpConsent()
console.log('Consent response: ', consentResponse)
if (consentResponse.status === 200 && consentResponse.data.consent_id) {
this.consentId = consentResponse.data.consent_id

View File

@ -85,12 +85,12 @@ export async function getOpeyJWT() {
return token
}
export async function getOpeyConsent() {
export async function getobpConsent() {
await axios.post('/api/opey/consent').catch((error) => {
if (error.response) {
throw new Error(`getOpeyConsent returned an error: ${error.toJSON()}`);
throw new Error(`getobpConsent returned an error: ${error.toJSON()}`);
} else {
throw new Error(`getOpeyConsent returned an error: ${error.message}`);
throw new Error(`getobpConsent returned an error: ${error.message}`);
}
}).then((response) => {
console.log(response)
@ -98,12 +98,12 @@ export async function getOpeyConsent() {
});
}
export async function answerOpeyConsentChallenge(answerBody: any) {
export async function answerobpConsentChallenge(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()}`);
throw new Error(`answerobpConsentChallenge returned an error: ${error.toJSON()}`);
} else {
throw new Error(`answerOpeyConsentChallenge returned an error: ${error.message}`);
throw new Error(`answerobpConsentChallenge returned an error: ${error.message}`);
}
});
return response

View File

@ -19,7 +19,7 @@ export interface OpeyStreamContext {
status: string;
}
export interface OpeyConsentObject {
export interface obpConsentObject {
consent_id: string;
}
@ -135,7 +135,7 @@ export async function sendOpeyMessage(
}
export async function getOpeyConsent(): Promise<OpeyConsentObject> {
export async function getobpConsent(): Promise<obpConsentObject> {
// Get consent from the Opey API
try {
const consentResponse = await fetch('/api/opey/consent', {

View File

@ -244,7 +244,7 @@ describe('sendOpeyMessage', () => {
})
})
describe('getOpeyConsent', () => {
describe('getobpConsent', () => {
beforeEach(() => {
global.fetch = vi.fn(() =>
@ -256,12 +256,12 @@ describe('getOpeyConsent', () => {
})
it('should call fetch', async () => {
await OpeyModule.getOpeyConsent()
await OpeyModule.getobpConsent()
expect(global.fetch).toHaveBeenCalled()
})
it('should return a consent id', async () => {
const consentId = await OpeyModule.getOpeyConsent()
const consentId = await OpeyModule.getobpConsent()
expect(consentId).toStrictEqual({consent_id: 1234})
})
})