Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix: saving logbook token in memory #725

Merged
3 changes: 1 addition & 2 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ LDAP_SEARCH_BASE=<SEARCH_BASE>
LDAP_SEARCH_FILTER="(LDAPUsername={{username}})"
LOGBOOK_ENABLED="no"
LOGBOOK_BASE_URL="http://localhost:3030/scichatapi"
LOGBOOK_USERNAME="<LOGBOOK_USERNAME>"
LOGBOOK_PASSWORD="<LOGBOOK_PASSWORD>"

METADATA_KEYS_RETURN_LIMIT=100
METADATA_PARENT_INSTANCES_RETURN_LIMIT=100
MONGODB_URI="mongodb://<USERNAME>:<PASSWORD>@<HOST>:27017/<DB_NAME>"
Expand Down
2 changes: 1 addition & 1 deletion CI/E2E/.env.backend-next
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ LDAP_SEARCH_BASE=<SEARCH_BASE>
LDAP_SEARCH_FILTER="(LDAPUsername={{username}})"
LOGBOOK_ENABLED="yes"
LOGBOOK_BASE_URL="http://scichat-loopback:3000/scichatapi"
LOGBOOK_USERNAME="logbookReader"

LOGBOOK_PASSWORD="<LOGBOOK_PASSWORD>"
METADATA_KEYS_RETURN_LIMIT=100
METADATA_PARENT_INSTANCES_RETURN_LIMIT=100
Expand Down
9 changes: 1 addition & 8 deletions CI/E2E/.env.scichat-loopback
Original file line number Diff line number Diff line change
@@ -1,14 +1,7 @@
JWT_SECRET = "myjwts3cr3t"
JWT_EXPIRES_IN = "21600"
MONGODB_HOST="mongodb"
MONGODB_PORT=27017
MONGODB_DB_NAME="scichat"
MONGODB_USER=""
MONGODB_PASSWORD=""
PORT=3030
SCICHAT_USER="testUser"
SCICHAT_PASSWORD="password"
SYNAPSE_SERVER_NAME="ess"
SYNAPSE_SERVER_HOST="https://scitest.esss.lu.se"
SYNAPSE_SERVER_HOST="https://server-scichat.swap.ess.eu"
SYNAPSE_BOT_NAME=""
SYNAPSE_BOT_PASSWORD=""
3 changes: 1 addition & 2 deletions CI/E2E/backend.env
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@ LDAP_SEARCH_BASE=<SEARCH_BASE>
LDAP_SEARCH_FILTER="(LDAPUsername={{username}})"
LOGBOOK_ENABLED="no"
LOGBOOK_BASE_URL="http://scichat-loopback:3000/scichatapi"
LOGBOOK_USERNAME="logbookReader"
LOGBOOK_PASSWORD="<LOGBOOK_PASSWORD>"

METADATA_KEYS_RETURN_LIMIT=100
METADATA_PARENT_INSTANCES_RETURN_LIMIT=100
MONGODB_URI="mongodb://mongodb:27017/scicat-backend-nestjs-e2e-testing"
Expand Down
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,7 @@ Valid environment variables for the .env file. See [.env.example](/.env.example)
- `OIDC_ACCESS_GROUPS` [string] _Optional_ Functionality is still unclear.
- `LOGBOOK_ENABLED` [string] _Optional_ Flag to enable/disable the Logbook endpoints. Values "yes" or "no". Defaults to "no".
- `LOGBOOK_BASE_URL` [string] _Optional_ The base URL to the Logbook API. Only required if Logbook is enabled.
- `LOGBOOK_USERNAME` [string] _Optional_ The username used to authenticate to the Logbook API. Only required if Logbook is enabled.
- `LOGBOOK_PASSWORD` [string] _Optional_ The password used to authenticate to the Logbook API. Only required if Logbook is enabled.

- `METADATA_KEYS_RETURN_LIMIT` [number] _Optional_ The return limit for the `/Datasets/metadataKeys` endpoint.
- `METADATA_PARENT_INSTANCES_RETURN_LIMIT` _Optional_ The return limit of Datasets to extract metadata keys from for the `/Datasets/metadataKeys` endpoint.
- `MONGODB_URI` [string] The URI for your MongoDB instance.
Expand Down
2 changes: 0 additions & 2 deletions src/config/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,8 +137,6 @@ const configuration = () => {
: false,
baseUrl:
process.env.LOGBOOK_BASE_URL ?? "http://localhost:3030/scichatapi",
username: process.env.LOGBOOK_USERNAME,
password: process.env.LOGBOOK_PASSWORD,
},
metadataKeysReturnLimit: process.env.METADATA_KEYS_RETURN_LIMIT
? parseInt(process.env.METADATA_KEYS_RETURN_LIMIT, 10)
Expand Down
47 changes: 45 additions & 2 deletions src/logbooks/logbooks.service.spec.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,35 @@
import { HttpService } from "@nestjs/axios";
import { ConfigService } from "@nestjs/config";
import { Test, TestingModule } from "@nestjs/testing";
import { of } from "rxjs";
import { LogbooksService } from "./logbooks.service";

class HttpServiceMock {}
class HttpServiceMock {
get() {
const responseData = [{ messages: [] }, { messages: [] }];

// Create an object that mimics the structure of an HTTP response
const response = {
data: responseData,
status: 200,
statusText: "OK",
headers: {},
};
return of(response);
}
}

describe("LogbooksService", () => {
let service: LogbooksService;
const filters = JSON.stringify({
textSearch: "",
showBotMessages: true,
showUserMessages: true,
showImages: true,
});
const configureService = (logbook: boolean) => {
service["logbookEnabled"] = logbook;
};

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
Expand All @@ -20,7 +43,27 @@ describe("LogbooksService", () => {
service = module.get<LogbooksService>(LogbooksService);
});

it("should be defined", () => {
it("[LogBooks-1]should be defined", () => {
expect(service).toBeDefined();
});

it("[LogBooks-2]logbook services should not return null if loogbook is enabled ", async () => {
configureService(true);

const findAllResult = await service.findAll();
expect(findAllResult).not.toEqual(null);

const findByNameResult = await service.findByName("111111", filters);
expect(findByNameResult).not.toEqual(null);
});

it("[LogBooks-3]logbook services should return null or [] if logbook is not enabled", async () => {
configureService(false);

const findAllResult = await service.findAll();
expect(findAllResult).toEqual([]);

const findByNameResult = await service.findByName("111111", filters);
expect(findByNameResult).toEqual(null);
});
});
189 changes: 75 additions & 114 deletions src/logbooks/logbooks.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,112 +14,80 @@ import { Message } from "./schemas/message.schema";
export class LogbooksService {
private logbookEnabled;
private baseUrl;
private username;
private password;

constructor(
private readonly configService: ConfigService,
private readonly httpService: HttpService,
) {
this.logbookEnabled = this.configService.get<boolean>("logbook.enabled");
this.baseUrl = this.configService.get<string>("logbook.baseUrl");
this.username = this.configService.get<string>("logbook.username");
this.password = this.configService.get<string>("logbook.password");
}

async login(username: string, password: string): Promise<{ token: string }> {
const credentials = { username, password };
try {
const res = await firstValueFrom(
this.httpService.post(this.baseUrl + "/Users/login", credentials),
);
return res.data;
} catch (error) {
handleAxiosRequestError(error, "LogbooksService.login");
throw new InternalServerErrorException("Logbook login failed");
}
}

async findAll(): Promise<Logbook[] | null> {
if (this.logbookEnabled) {
if (this.username && this.password) {
try {
const accessToken = await this.login(this.username, this.password);
Logger.log("Fetching Logbooks", "LogbooksService.findAll");
const res = await firstValueFrom(
this.httpService.get<Logbook[]>(this.baseUrl + "/Logbooks", {
headers: { Authorization: `Bearer ${accessToken.token}` },
}),
);
const nonEmptyLogbooks = res.data.filter(
(logbook) => logbook.messages.length !== 0,
);
const emptyLogbooks = res.data.filter(
(logbook) => logbook.messages.length === 0,
);
nonEmptyLogbooks
.sort(
(a, b) =>
a.messages[a.messages.length - 1].origin_server_ts -
b.messages[b.messages.length - 1].origin_server_ts,
)
.reverse();
const logbooks = nonEmptyLogbooks.concat(emptyLogbooks);
Logger.log("Found logbooks", "LogbooksService.findAll");
return logbooks;
} catch (error) {
handleAxiosRequestError(error, "LogbooksService.findAll");
throw new InternalServerErrorException("Fetching Logbooks failed");
}
} else {
throw new InternalServerErrorException(
"Logbook username and/or password not configured",
try {
Logger.log("Fetching Logbooks", "LogbooksService.findAll");
const res = await firstValueFrom(
this.httpService.get<Logbook[]>(this.baseUrl + "/Logbooks"),
);

const nonEmptyLogbooks = res.data.filter(
(logbook) => logbook.messages.length !== 0,
);
const emptyLogbooks = res.data.filter(
(logbook) => logbook.messages.length === 0,
);
nonEmptyLogbooks
.sort(
(a, b) =>
a.messages[a.messages.length - 1].origin_server_ts -
b.messages[b.messages.length - 1].origin_server_ts,
)
.reverse();
const logbooks = nonEmptyLogbooks.concat(emptyLogbooks);
Logger.log("Found logbooks", "LogbooksService.findAll");
return logbooks;
} catch (error) {
handleAxiosRequestError(error, "LogbooksService.findAll");
throw new InternalServerErrorException("Fetching Logbooks failed");
}
}
return [];
}

async findByName(name: string, filters: string): Promise<Logbook | null> {
if (this.logbookEnabled) {
if (this.username && this.password) {
try {
const accessToken = await this.login(this.username, this.password);
Logger.log("Fetching logbook " + name, "LogbooksService.findByName");
Logger.log(filters, "LogbooksService.findByName");
const res = await firstValueFrom(
this.httpService.get<Logbook>(
this.baseUrl + `/Logbooks/${name}?filter=${filters}`,
{ headers: { Authorization: `Bearer ${accessToken.token}` } },
),
);
Logger.log("Found logbook " + name, "LogbooksService.findByName");
const { skip, limit, sortField } = JSON.parse(filters);
Logger.log(
"Applying filters skip: " +
skip +
", limit: " +
limit +
", sortField: " +
sortField,
"LogbooksService.findByName",
);
if (!!sortField && sortField.indexOf(":") > 0) {
res.data.messages = sortMessages(res.data.messages, sortField);
}
if (skip >= 0 && limit >= 0) {
const end = skip + limit;
const messages = res.data.messages.slice(skip, end);
return { ...res.data, messages };
}
return res.data;
} catch (error) {
handleAxiosRequestError(error, "LogbooksService.findByName");
}
} else {
throw new InternalServerErrorException(
"Logbook username and/or password not configured",
try {
Logger.log("Fetching logbook " + name, "LogbooksService.findByName");
Logger.log(filters, "LogbooksService.findByName");
const res = await firstValueFrom(
this.httpService.get<Logbook>(
this.baseUrl + `/Logbooks/${name}?filter=${filters}`,
),
);

Logger.log("Found logbook " + name, "LogbooksService.findByName");
const { skip, limit, sortField } = JSON.parse(filters);
Logger.log(
"Applying filters skip: " +
skip +
", limit: " +
limit +
", sortField: " +
sortField,
"LogbooksService.findByName",
);
if (!!sortField && sortField.indexOf(":") > 0) {
res.data.messages = sortMessages(res.data.messages, sortField);
}
if (skip >= 0 && limit >= 0) {
const end = skip + limit;
const messages = res.data.messages.slice(skip, end);
return { ...res.data, messages };
}
return res.data;
} catch (error) {
handleAxiosRequestError(error, "LogbooksService.findByName");
}
}
return null;
Expand All @@ -130,36 +98,29 @@ export class LogbooksService {
data: { message: string },
): Promise<{ event_id: string } | null> {
if (this.logbookEnabled) {
if (this.username && this.password) {
try {
const accessToken = await this.login(this.username, this.password);
Logger.log(
"Sending message to room " + name,
"LogbooksService.sendMessage",
);
const res = await firstValueFrom(
this.httpService.post<{ event_id: string }>(
this.baseUrl + `/Logbooks/${name}/message`,
data,
{ headers: { Authorization: `Bearer ${accessToken.token}` } },
),
);
Logger.log(
"Message with eventId " +
res.data.event_id +
" sent to room " +
name,
"LogbooksService.sendMessage",
);
return res.data;
} catch (error) {
handleAxiosRequestError(error, "LogbooksService.sendMessage");
}
} else {
throw new InternalServerErrorException(
"Logbook username and/or password not configured",
try {
Logger.log(
"Sending message to room " + name,
"LogbooksService.sendMessage",
);
const res = await firstValueFrom(
this.httpService.post<{ event_id: string }>(
this.baseUrl + `/Logbooks/${name}/message`,
data,
),
);
Logger.log(
"Message with eventId " + res.data.event_id + " sent to room " + name,
"LogbooksService.sendMessage",
);
return res.data;
} catch (error) {
handleAxiosRequestError(error, "LogbooksService.sendMessage");
}
} else {
throw new InternalServerErrorException(
"Logbook username and/or password not configured",
);
}
return null;
}
Expand Down
Loading