Skip to content
Snippets Groups Projects
Unverified Commit 18d9298c authored by Konstantin Tsabolov's avatar Konstantin Tsabolov
Browse files

chore: remove principal-manager service

parent be89b5b8
No related branches found
No related tags found
2 merge requests!9feat(ssi): Establish a trusted connection with yourself,!8Project house-keeping, refactoring and reorganizing
Showing
with 0 additions and 12972 deletions
Dockerfile
Jenkinsfile
coverage
docker-compose.yml
docs
node_modules
yarn-error.log
*.md
!README.md
.circle*
.codecov*
.coveralls*
.dockerignore
.drone*
.editorconfig
# .env
.git*
.huskyrc*
.lintstagedrc*
.npmignore
.prettierrc*
dist
\ No newline at end of file
PORT=3008
DATABASE_URL=postgresql://root:password@localhost:5432/postgres?schema=principal
ECSURL=http://localhost:9200/
CONNECTION_MANAGER_URL=http://3.111.77.38:3003
ATTESTATION_MANAGER_URL=http://3.111.77.38:3005
NATS_URL=nats://localhost:4222
USE_AUTH=false
OAUTH_CLIENT_ID=clientid
OAUTH_CLIENT_SECRET=clientsecret
OAUTH_TOKEN_URL=https://tokenurl
\ No newline at end of file
module.exports = {
root: true,
parser: '@typescript-eslint/parser',
parserOptions: {
sourceType: 'module',
ecmaVersion: 2021,
},
env: {
node: true,
},
plugins: ['prettier', '@typescript-eslint/eslint-plugin', 'jest'],
extends: [
'prettier',
'plugin:@typescript-eslint/recommended',
'plugin:jest/recommended',
],
ignorePatterns: ['.eslintrc.js'],
overrides: [],
settings: {
jest: {
version: '29',
},
},
rules: {
'no-unused-vars': 0,
'@typescript-eslint/no-unused-vars': [1, { argsIgnorePattern: '^_' }],
},
overrides: [
{
files: [
'*.spec.ts',
'*.e2e-spec.ts',
'__mocks__/*.ts',
'__mocks__/**/*.ts',
],
rules: {
'@typescript-eslint/no-explicit-any': 0,
'jest/no-mocks-import': 0,
},
},
],
};
{
"jsc": {
"preserveAllComments": true,
"parser": {
"syntax": "typescript",
"tsx": false,
"decorators": true
},
"transform": {
"legacyDecorator": true,
"decoratorMetadata": true
},
"target": "es2022"
},
"module": {
"type": "es6"
},
"sourceMaps": true,
"exclude": [".spec.ts", ".e2e-spec.ts"]
}
# GDPR Compliance Document
The objective of this document is to detail, the data being stored and proccessed by the Organization Credential Manager's, Principal Manger.
## What information is stored
### Source User Information
No personal data is accessed or processed
### Technical User Information (Public)
- Refrence id
## How is the information stored
The Technical User Information is encrypted using the Private Key of the Organizations SSI Agent and stored internally (on the agent) on PostgreSQL and externally/ metadata (shared between the OCM services) on PostgreSQL of Organization.
## Who can access the information
The Technical User Information both are accessible only by the Organization specific SSI agent's private key.
## How long will the information stay
The Technical User Information is wiped out according to the retention periods (not defined yet).
{
"openapi": "3.0.0",
"paths": {
"/v1/health": {
"get": {
"operationId": "HealthController_getHealth",
"parameters": [],
"responses": {
"200": {
"description": ""
}
}
}
},
"/v1/map-user-info": {
"post": {
"operationId": "PrincipalController_mapUserInfo",
"parameters": [],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MapUserInfoDTO"
}
}
}
},
"responses": {
"201": {
"description": ""
}
}
}
}
},
"info": {
"title": "XFSC Principal Manager API",
"description": "API documentation for XFSC Principal Manager",
"version": "1.0",
"contact": {}
},
"tags": [],
"servers": [{
"url": "http://localhost:3008/",
"description": "Localhost with docker configuration."
}
],
"components": {
"schemas": {
"MapUserInfoDTO": {
"type": "object",
"properties": {
"userInfoURL": {
"type": "string"
}
},
"required": [
"userInfoURL"
]
}
}
}
}
\ No newline at end of file
This diff is collapsed.
# OCM Principal Manager
## Description
<hr/>
The Principal Manager is the microservice responsible for handling the authentication and credential issuance for an individual PCM user.
## Usage
<hr/>
### Swagger Documentation:
[Swagger/OpenAPI](swagger.json)
## Installation
<hr/>
### Pre-requisite
* pnpm
* docker
* docker-compose
* Postgres
### OCM Services Dependencies
* SSI Abstraction
## Running the app
<hr/>
**Each service in the Organizational Credential Manager can be run from the infrastructure repository with Docker.**
**The .env files are in the infrastructure repository under /env**
```bash
## production in:
./deployment/ci
## development in:
./deployment/dev
```
* (optional) Edit docker-compose.yml in "infrastructure" to use either **/ci/** or **/dev/** Dockerfiles.
* Run while in **"infrastructure"** project:
```bash
$ docker-compose up --build attestation-m
```
to run only Attestation Manager or
```bash
$ docker-compose up --build
```
to run all the services.
## Build
```
pnpm build
```
## Run
```
pnpm start
```
### Environment variable required
```
1. PORT
2. DATABASE_URL
3. ECSURL
4. NATS_URL
5. AGENT_URL
```
### Outgoing communication services
```
1. CONNECTION MANAGER
```
### Incoming communication services
```
1. ATTESTATION MANAGER
```
## Features supported
```
1. Issue Membership credential
```
## Test
```bash
# unit tests
$ pnpm test
# e2e tests
$ pnpm test:e2e
# test coverage
$ pnpm test:cov
```
## GDPR
<hr/>
[GDPR](GDPR.md)
## Dependencies
<hr/>
[Dependencies](package.json)
## License
<hr/>
[Apache 2.0 license](LICENSE)
FROM node:16-slim AS builder
RUN apt-get update
RUN apt-get install -y openssl
WORKDIR /app
RUN npm i -g pnpm
COPY . .
RUN pnpm install
RUN pnpm -F principal-manager prisma:generate
RUN pnpm -F principal-manager build
FROM node:16-slim
RUN apt-get update
RUN apt-get install -y openssl
ENV PATH /usr/src/app/node_modules/.bin:$PATH
WORKDIR /usr/src/app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/start.sh ./start.sh
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/src/prisma prisma
EXPOSE 3008
RUN chmod +x ./start.sh
CMD ["./start.sh"]
/** @type {import('jest').Config} */
import { readFileSync } from 'node:fs';
const swcConfig = JSON.parse(readFileSync('./.swcrc', 'utf8'));
export default {
moduleFileExtensions: ['js', 'ts'],
testEnvironment: 'node',
transform: {
'^.+\\.ts$': [
'@swc/jest',
{
...swcConfig,
sourceMaps: false,
exclude: [],
swcrc: false,
},
],
},
extensionsToTreatAsEsm: ['.ts'],
moduleNameMapper: {
// ESM modules require `.js` extension to be specified, but Jest doesn't work with them
// Removing `.js` extension from module imports
'^uuid$': 'uuid',
'^(.*)/(.*)\\.js$': '$1/$2',
},
collectCoverageFrom: ['src/**/*.(t|j)s'],
coverageReporters:
process.env.CI === 'true'
? ['text-summary', 'json-summary']
: ['text-summary', 'html'],
coveragePathIgnorePatterns: [
'<rootDir>/node_modules/',
'<rootDir>/test/',
'<rootDir>/coverage/',
'<rootDir>/dist/',
'<rootDir>/**/test',
'@types',
'.dto.(t|j)s',
'.enum.ts',
'.interface.ts',
'.type.ts',
'.spec.ts',
],
coverageDirectory: './coverage',
// With v8 coverage provider it's much faster, but
// with this enabled it's not possible to ignore whole files' coverage
coverageProvider: 'v8',
};
{
"collection": "@nestjs/schematics",
"sourceRoot": "src",
"compilerOptions": {
"builder": "swc",
"typeCheck": true
}
}
This diff is collapsed.
{
"name": "principal-manager",
"version": "0.0.1",
"description": "",
"author": "Sagar",
"private": true,
"license": "Apache 2.0",
"type": "module",
"scripts": {
"clean": "rm -r dist",
"prebuild": "rimraf dist",
"build": "nest build",
"dbSchema": "npx prisma db push --schema=./src/prisma/schema.prisma",
"prisma:generate": "prisma generate --schema=./src/prisma/schema.prisma",
"prisma:migrate": "npx prisma migrate deploy --schema=./src/prisma/schema.prisma",
"prismaStudio": "npx prisma studio",
"start": "nest start",
"start:dev": "nest start --watch --preserveWatchOutput",
"start:docker": "pnpm prisma:migrate && pnpm start",
"test": "jest",
"test:watch": "jest --watch",
"test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "jest --config ./test/jest-e2e.json"
},
"dependencies": {
"@elastic/ecs-winston-format": "^1.5.0",
"@nestjs/axios": "^3.0.1",
"@nestjs/common": "^10.2.8",
"@nestjs/config": "^3.1.1",
"@nestjs/core": "^10.2.8",
"@nestjs/mapped-types": "*",
"@nestjs/microservices": "^10.2.8",
"@nestjs/platform-express": "^10.2.8",
"@nestjs/schedule": "^4.0.0",
"@nestjs/swagger": "^7.1.16",
"@nestjs/terminus": "^10.1.1",
"@prisma/client": "^5.6.0",
"class-validator": "^0.14.0",
"joi": "^17.11.0",
"jsonwebtoken": "^9.0.2",
"jwks-rsa": "^3.1.0",
"liquibase": "^4.4.0",
"moment": "^2.29.4",
"nats": "^2.18.0",
"openid-client": "^5.6.1",
"pg": "^8.11.3",
"reflect-metadata": "^0.1.13",
"rxjs": "^7.8.1",
"swagger-ui-express": "^5.0.0",
"winston": "^3.11.0",
"winston-elasticsearch": "^0.17.4"
},
"devDependencies": {
"@nestjs/cli": "^10.2.1",
"@nestjs/schematics": "^10.0.3",
"@nestjs/testing": "^10.2.8",
"@swc/cli": "^0.1.62",
"@swc/core": "^1.3.96",
"@swc/jest": "^0.2.29",
"@types/express": "^4.17.13",
"@types/jest": "27.0.2",
"@types/jsonwebtoken": "^9.0.5",
"@types/node": "^20.9.0",
"@types/supertest": "^2.0.16",
"dotenv-cli": "^7.3.0",
"jest": "^29.7.0",
"prisma": "^5.6.0",
"rimraf": "^5.0.5",
"source-map-support": "^0.5.21",
"supertest": "^6.3.3",
"ts-jest": "^29.1.1",
"ts-loader": "^9.5.1",
"ts-node": "^10.9.1",
"tsconfig-paths": "^4.2.0",
"typescript": "^5.2.2"
}
}
process.env.PORT = 3000
\ No newline at end of file
import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import AppModule from './app.module';
describe('App Module', () => {
let app: INestApplication;
beforeAll(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleFixture.createNestApplication();
await app.init();
});
it('should work', () => {
expect(true).toBe(true);
});
afterAll(async () => {
await app.close();
});
});
import { HttpModule } from '@nestjs/axios';
import {
MiddlewareConsumer,
Module,
NestModule,
RequestMethod,
} from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { APP_FILTER } from '@nestjs/core';
import { ScheduleModule } from '@nestjs/schedule';
import { TerminusModule } from '@nestjs/terminus';
import ExceptionHandler from './common/exception.handler.js';
import config from './config/config.js';
import validationSchema from './config/validation.js';
import HealthController from './health/health.controller.js';
import { AuthMiddleware } from './middleware/auth.middleware.js';
import PrincipalModule from './principal/module.js';
import PrismaService from './prisma/prisma.service.js';
import PrismaModule from './prisma/prisma.module.js';
@Module({
imports: [
ScheduleModule.forRoot(),
TerminusModule,
ConfigModule.forRoot({
isGlobal: true,
load: [config],
validationSchema,
}),
PrismaModule,
PrincipalModule,
HttpModule,
],
controllers: [HealthController],
providers: [
{
provide: APP_FILTER,
useClass: ExceptionHandler,
},
],
})
export default class AppModule implements NestModule {
// eslint-disable-next-line class-methods-use-this
configure(consumer: MiddlewareConsumer) {
consumer
.apply(AuthMiddleware)
.exclude({
path: 'v1/health',
method: RequestMethod.GET,
})
.forRoutes('*');
}
}
import { Inject, Injectable } from '@nestjs/common';
import { ClientProxy } from '@nestjs/microservices';
import { ResponseType } from 'openid-client';
import { lastValueFrom } from 'rxjs';
import { Attestation, NATSServices } from '../common/constants.js';
import OfferMembershipCredentialDto from '../principal/entities/offerMembershipCredentialDto.entity.js';
import logger from '../utils/logger.js';
@Injectable()
export default class NatsClientService {
constructor(@Inject(NATSServices.SERVICE_NAME) private client: ClientProxy) {}
OfferMembershipCredential(data: OfferMembershipCredentialDto) {
const pattern = {
endpoint: `${Attestation.NATS_ENDPOINT}/${Attestation.OFFER_MEMBERSHIP_CREDENTIALS}`,
};
const payload = { ...data };
logger.info(`before sending data to Attestation manager ${payload}`);
return lastValueFrom(this.client.send<ResponseType>(pattern, payload));
}
}
import NatsClientService from './nats.client';
describe('Check if the nats client is working', () => {
// let natsClient: NatsClientService;
// let client: ClientProxy;
beforeEach(() => {
// natsClient = new NatsClientService(client);
});
jest.mock('rxjs', () => {
const original = jest.requireActual('rxjs');
return {
...original,
lastValueFrom: () =>
new Promise((resolve) => {
resolve(true);
}),
};
});
it('should be defined', () => {
expect(NatsClientService).toBeDefined();
});
// it('should call the offer membership credential endpoint', async () => {
// const data = {
// status: 'complete',
// connectionId: 'connectionId',
// theirLabel: 'theirLabel',
// participantId: 'participantId',
// participantDID: 'participantDID'
// };
// jest.spyOn(client, 'send').mockReturnValue(of(data));
// const response = await natsClient.OfferMembershipCredential(data);
// expect(response).toBeTruthy();
// });
});
export enum NATSServices {
SERVICE_NAME = 'PRINCIPAL_MANAGER_SERVICE',
}
export enum LoggerConfig {
FILE_PATH = 'logs/log.json',
lOG_DIR = './logs',
}
export enum Attestation {
NATS_ENDPOINT = 'ATTESTATION_MANAGER_SERVICE',
OFFER_MEMBERSHIP_CREDENTIALS = 'offerMemberShipCredentials',
}
export const ConnectionManagerUrl = process.env.CONNECTION_MANAGER_URL;
export const CreateMemberConnection = 'v1/invitation-url?alias=member';
export const AttestationManagerUrl = process.env.ATTESTATION_MANAGER_URL;
export const SaveUserInfo = 'v1/userInfo';
import {
ArgumentsHost,
Catch,
ExceptionFilter,
HttpException,
HttpStatus,
} from '@nestjs/common';
import { HttpAdapterHost } from '@nestjs/core';
import ResponseType from './response.js';
@Catch()
export default class ExceptionHandler implements ExceptionFilter {
constructor(private readonly httpAdapterHost: HttpAdapterHost) {}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
catch(exception: any, host: ArgumentsHost): void {
// In certain situations `httpAdapter` might not be available in the
// constructor method, thus we should resolve it here.
const { httpAdapter } = this.httpAdapterHost;
const ctx = host.switchToHttp();
const response = ctx.getResponse();
let statusCode = HttpStatus.INTERNAL_SERVER_ERROR;
let message =
exception.message.error || exception.message || 'Something went wrong!';
if (exception instanceof HttpException) {
const errorResponse: string | object = exception.getResponse();
statusCode = exception.getStatus();
message =
(typeof errorResponse === 'object' &&
Reflect.get(errorResponse, 'error')) ||
message;
}
const responseBody: ResponseType = {
statusCode,
message,
error: exception.message,
};
httpAdapter.reply(response, responseBody, statusCode);
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment