diff --git a/apps/authentication/src/app.module.ts b/apps/authentication/src/app.module.ts index 7177362..c3a13f5 100644 --- a/apps/authentication/src/app.module.ts +++ b/apps/authentication/src/app.module.ts @@ -5,9 +5,11 @@ import { AppService } from './app.service'; import { AuthModule } from './auth/auth.module'; import { TerminusModule } from '@nestjs/terminus'; import { HttpModule } from '@nestjs/axios'; +import { UserModule } from './user/user.module'; @Module({ imports: [ AuthModule, + UserModule, ConfigModule.forRoot({ isGlobal: true, }), @@ -17,4 +19,4 @@ import { HttpModule } from '@nestjs/axios'; controllers: [AppController], providers: [AppService], }) -export class AppModule {} +export class AppModule { } diff --git a/apps/authentication/src/auth/auth.controller.ts b/apps/authentication/src/auth/auth.controller.ts index 48d0870..316ce96 100644 --- a/apps/authentication/src/auth/auth.controller.ts +++ b/apps/authentication/src/auth/auth.controller.ts @@ -1,5 +1,6 @@ import { HttpService } from '@nestjs/axios'; -import { Body, Controller, Get, Param, Patch, Post, UseGuards } from '@nestjs/common'; +import { Body, Controller, Get, Param, Patch, Post, Req, Request, UseGuards } from '@nestjs/common'; +import { parseJwt } from '../helpers/decodeToken'; import { JwtAuthGuard } from './auth-jwt.guard'; import { AuthService } from './auth.service'; import { AuthDto } from './dto/auth.dto'; @@ -11,31 +12,31 @@ export class AuthController { @Post('/verify') @UseGuards(JwtAuthGuard) - hadleAuth(@Body() body: AuthDto) { - return this.authService.handleAuth(body); + hadleAuth(@Body() body: AuthDto, @Request() req) { + return this.authService.handleAuth(body, req?.headers?.authorization); } @Post('/register') @UseGuards(JwtAuthGuard) - handleRegister(@Body() ca: CA) { - return this.authService.handleRegister(ca); + handleRegister(@Body() ca: CA, @Request() req) { + return this.authService.handleRegister(ca, req?.headers?.authorization); } @Patch('/:caId/accept') @UseGuards(JwtAuthGuard) - handleAccept(@Param('caId') caId: string) { - return this.authService.handleConsent(caId, 'accept'); + handleAccept(@Param('caId') caId: string, @Request() req) { + return this.authService.handleConsent(caId, 'accept', req?.headers?.authorization); } @Patch('/:caId/reject') @UseGuards(JwtAuthGuard) - handleReject(@Param('caId') caId: string) { - return this.authService.handleConsent(caId, 'reject'); + handleReject(@Param('caId') caId: string, @Request() req) { + return this.authService.handleConsent(caId, 'reject', req?.headers?.authorization); } @Patch('/:caId/revoke') @UseGuards(JwtAuthGuard) - handleRevoke(@Param('caId') caId: string) { - return this.authService.handleConsent(caId, 'revoke'); + handleRevoke(@Param('caId') caId: string, @Request() req) { + return this.authService.handleConsent(caId, 'revoke', req?.headers?.authorization); } } diff --git a/apps/authentication/src/auth/auth.service.ts b/apps/authentication/src/auth/auth.service.ts index 4aa9d12..83494c9 100644 --- a/apps/authentication/src/auth/auth.service.ts +++ b/apps/authentication/src/auth/auth.service.ts @@ -1,6 +1,7 @@ import { HttpService } from '@nestjs/axios'; -import { Injectable, InternalServerErrorException } from '@nestjs/common'; +import { HttpException, HttpStatus, Injectable, InternalServerErrorException } from '@nestjs/common'; import { lastValueFrom, map } from 'rxjs'; +import { parseJwt } from '../helpers/decodeToken'; import { AuthDto } from './dto/auth.dto'; import { CA } from './dto/ca.dto'; @@ -8,128 +9,66 @@ import { CA } from './dto/ca.dto'; export class AuthService { constructor(private readonly httpService: HttpService) { } - async handleAuth(authDTO: AuthDto) { - //TODO: add consent artifact processin + async handleAuth(authDTO: AuthDto, token: string) { try { - // const myHeaders = new Headers(); - // myHeaders.append('Content-Type', 'application/json'); - - // var raw = JSON.stringify({ - // "id": "927d81cf-77ee-4528-94d1-2d98a2595740", - // "caId": "036232e5-0ac7-4863-bad2-c70e70ef2d2f", - // "consent_artifact": { - // "id": "036232e5-0ac7-4863-bad2-c70e70ef2d2f", - // "log": { - // "consent_use": { - // "url": "https://sample-log/api/v1/log" - // }, - // "data_access": { - // "url": "https://sample-log/api/v1/log" - // } - // }, - // "data": "", - // "user": { - // "id": "farmer-1@gmail.com" - // }, - // "proof": { - // "jws": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjAzNjIzMmU1LTBhYzctNDg2My1iYWQyLWM3MGU3MGVmMmQyZiIsImxvZyI6eyJjb25zZW50X3VzZSI6eyJ1cmwiOiJodHRwczovL3NhbXBsZS1sb2cvYXBpL3YxL2xvZyJ9LCJkYXRhX2FjY2VzcyI6eyJ1cmwiOiJodHRwczovL3NhbXBsZS1sb2cvYXBpL3YxL2xvZyJ9fSwiZGF0YSI6IjxWYWxpZCBzdXBlcnNldCBHcmFwaFFMIHF1ZXJ5IG9mIGNvbnNlbnRlZCBkYXRhPiIsInVzZXIiOnsiaWQiOiJmYXJtZXItMUBnbWFpbC5jb20ifSwiY3JlYXRlZCI6IllZWVktTU0tRERUaGg6bW06c3Nabi5uIiwiZXhwaXJlcyI6IllZWVktTU0tRERUaGg6bW06c3Nabi5uIiwicHVycG9zZSI6IiIsInJldm9rZXIiOnsiaWQiOiJkaWQ6dXNlcjoxMjMiLCJ1cmwiOiJodHRwczovL3NhbXBsZS1yZXZva2VyL2FwaS92MS9yZXZva2UifSwiY29uc3VtZXIiOnsiaWQiOiJkaWQ6Y29uc3VtZXI6MTIzIiwidXJsIjoiaHR0cHM6Ly9zYW1wbGUtY29uc3VtZXIvYXBpL3YxL2NvbnN1bWUifSwicHJvdmlkZXIiOnsiaWQiOiJkaWQ6cHJvaWRlcjoxMjMiLCJ1cmwiOiJodHRwczovL3NhbXBsZS1jb25zdW1lci9hcGkvdjEifSwiY29sbGVjdG9yIjp7ImlkIjoiZGlkOmNvbGxlY3RvcjoxMjMiLCJ1cmwiOiJodHRwczovL2ExMTItMTAzLTIxMi0xNDctMTMwLmluLm5ncm9rLmlvIn0sImZyZXF1ZW5jeSI6eyJ0dGwiOjUsImxpbWl0IjoyfSwicmV2b2NhYmxlIjpmYWxzZSwic2lnbmF0dXJlIjoiIiwidXNlcl9zaWduIjoiIiwiY29sbGVjdG9yX3NpZ24iOiIiLCJ0b3RhbF9xdWVyaWVzX2FsbG93ZWQiOjEwLCJpYXQiOjE2Njk5Mzk1OTYsImV4cCI6MTY3MDM3MTU5NiwiYXVkIjoiZGlkOmNvbnN1bWVyOjEyMyIsImlzcyI6ImNvbnNlbnQtbWFuYWdlciIsInN1YiI6ImZhcm1lci0xQGdtYWlsLmNvbSJ9.UNyoXDgMxbIVaBoK0J7OBX7ybUlZNx309KdbetoeJLqGaZbfFav3rZyoPnQNpQyAFHp8MaNczzlI0JlTSStqJl0E-Z1oGK6M-hREE1261zSxZMAueIgpNEVpNiUH4gRhleTBaKPH0EoZT27ORqZmULb2UMDfw1Gy9RuH7cHzJYdBDmi5fkePhsN8T3Z03OgnUWHHPTxwS4_szS3fLGMmJvUTyrK-UBwkMslajdoWN3vcp4MERv60F8yIk7GqGGkNHEiaLe_g_Zi73KOKDbdWOLapQiO8kwpAyblu6maNF8w4VdIft4zFT4SiloJWxeYNZUeT0ROHscTbdLOaTCn-Ag", - // "type": "RS256", - // "created": "12/2/2022, 12:06:36 AM", - // "proofPurpose": "jwtVerify", - // "verificationMethod": "https://auth.konnect.samagra.io/.well-known/jwks" - // }, - // "created": "YYYY-MM-DDThh:mm:ssZn.n", - // "expires": "YYYY-MM-DDThh:mm:ssZn.n", - // "purpose": "", - // "revoker": { - // "id": "did:user:123", - // "url": "https://sample-revoker/api/v1/revoke" - // }, - // "consumer": { - // "id": "did:consumer:123", - // "url": "https://sample-consumer/api/v1/consume" - // }, - // "provider": { - // "id": "did:proider:123", - // "url": "https://sample-consumer/api/v1" - // }, - // "collector": { - // "id": "did:collector:123", - // "url": "https://a112-103-212-147-130.in.ngrok.io" - // }, - // "frequency": { - // "ttl": 5, - // "limit": 2 - // }, - // "revocable": false, - // "signature": "", - // "user_sign": "", - // "collector_sign": "", - // "total_queries_allowed": 10 - // }, - // "userId": "farmer-1@gmail.com", - // "state": "ACCEPT", - // "created_at": "2022-12-02T00:05:46.090Z", - // "created_by": "API", - // "updated_at": "2022-12-02T00:05:46.090Z", - // "updated_by": null, - // "webhook_url": "https://sample-consumer/api/v1/consume", - // "total_attempts": 0 - // }); - // const reqOptions = { - // method: 'POST', - // headers: { - // 'Content-Type': 'application/json', - // }, - // redirect: 'follow', - // }; - // fetch('https://api.consent-manager.konnect.samagra.io/verify', reqOptions) - // .then((response) => response.text()) - // .then((result) => console.log(result)) - // .catch((error) => { - // console.log('error', error); - // throw new InternalServerErrorException(error); - // }); - - // this.httpService.post( - // 'https://api.consent-manager.konnect.samagra.io/verify', - // raw, - // reqOptions, - // ); + const tokenPayload = parseJwt(token?.split(" ")?.[1]); + const userEmail = tokenPayload.email; - const caRes = await lastValueFrom( + const permissionRes = await lastValueFrom( this.httpService - .get( - `${process.env.CONSENT_MANAGER_URI}/${authDTO.caId}/verify` + .post( + `http://localhost:6000/user/checkPermission`, + { + caId: authDTO.caId, + userEmail + }, { headers: { Authorization: token } } ) .pipe(map((response) => response.data)), ); - if (!caRes.caId) { - return "An error occured while verifying Consent Artifact"; - } - console.log("CA RES---->", caRes) + if (permissionRes) { + const caRes = await lastValueFrom( + this.httpService + .get( + `${process.env.CONSENT_MANAGER_URI}/${authDTO.caId}/verify` + ) + .pipe(map((response) => response.data)), + ); + if (!caRes.caId) { + return "An error occured while verifying Consent Artifact"; + } - const responseData = await lastValueFrom( - this.httpService - .post( - process.env.LINK_TO_AUTHORIZATION_SERVICE, - { consentArtifact: caRes, gql: authDTO.gql } - ) - .pipe(map((response) => response.data)), - ); + console.log("CA RES---->", caRes) - return responseData; + const responseData = await lastValueFrom( + this.httpService + .post( + process.env.LINK_TO_AUTHORIZATION_SERVICE, + { consentArtifact: caRes, gql: authDTO.gql } + ) + .pipe(map((response) => response.data)), + ); + + return responseData; + + } else { + throw new HttpException({ + status: HttpStatus.FORBIDDEN, + error: `You can only access or modify Consents which are associated with you.`, + }, HttpStatus.FORBIDDEN); + } } catch (err) { console.log('err: ', err); - if (err?.response?.data) - return err?.response?.data; + if (err?.response?.data || err?.response) + return err?.response?.data || err?.response; throw new InternalServerErrorException(); } } - handleRegister = async (data: CA) => { + handleRegister = async (data: CA, token: string) => { try { + const tokenPayload = parseJwt(token?.split(" ")?.[1]); + const userEmail = tokenPayload.email; let ca = data.consentArtifact const caRes = await lastValueFrom( this.httpService @@ -143,6 +82,19 @@ export class AuthService { return "An error occured while creating Consent Artifact"; } + // Adding registered CA to user's data on FA. + const userRes = await lastValueFrom( + this.httpService + .patch( + `http://localhost:6000/user/updateUserCaIds`, + { + caId: caRes.caId, + userEmail + }, { headers: { Authorization: token } } + ) + .pipe(map((response) => response.data)), + ); + return caRes; } catch (err) { @@ -153,27 +105,50 @@ export class AuthService { } } - handleConsent = async (caId: string, type: string) => { + handleConsent = async (caId: string, type: string, token: string) => { try { - const caRes = await lastValueFrom( + const tokenPayload = parseJwt(token?.split(" ")?.[1]); + const userEmail = tokenPayload.email; + + const permissionRes = await lastValueFrom( this.httpService - .patch( - `${process.env.CONSENT_MANAGER_URI}/${caId}/${type}` + .post( + `http://localhost:6000/user/checkPermission`, + { + caId, + userEmail + }, { headers: { Authorization: token } } ) .pipe(map((response) => response.data)), ); - if (!caRes.caId) { - return "An error occured while performing the requested action"; - } + if (permissionRes) { + const caRes = await lastValueFrom( + this.httpService + .patch( + `${process.env.CONSENT_MANAGER_URI}/${caId}/${type}` + ) + .pipe(map((response) => response.data)), + ); - return caRes; + if (!caRes.caId) { + return "An error occured while performing the requested action"; + } + + return caRes; + + } else { + throw new HttpException({ + status: HttpStatus.FORBIDDEN, + error: `You can only access or modify Consents which are associated with you.`, + }, HttpStatus.FORBIDDEN); + } } catch (err) { console.log('err: ', err); - if (err?.response?.data) - return err?.response?.data; - throw new InternalServerErrorException(); + if (err?.response) + return err?.response; + throw new InternalServerErrorException; } } } diff --git a/apps/authentication/src/auth/dto/auth.dto.ts b/apps/authentication/src/auth/dto/auth.dto.ts index 80318e6..4381ce0 100644 --- a/apps/authentication/src/auth/dto/auth.dto.ts +++ b/apps/authentication/src/auth/dto/auth.dto.ts @@ -1,5 +1,4 @@ export class AuthDto { caId: string - gql: string; - token: string; + gql: string } diff --git a/apps/authentication/src/helpers/decodeToken.ts b/apps/authentication/src/helpers/decodeToken.ts new file mode 100644 index 0000000..196afdc --- /dev/null +++ b/apps/authentication/src/helpers/decodeToken.ts @@ -0,0 +1,10 @@ +import { JwtPayload } from "jsonwebtoken"; + +export function parseJwt(signedJwtAccessToken: string) { + if (!signedJwtAccessToken) return null; + const base64Payload = signedJwtAccessToken.split('.')[1]; + const payloadBuffer = Buffer.from(base64Payload, 'base64'); + const updatedJwtPayload: JwtPayload = JSON.parse(payloadBuffer.toString()) as JwtPayload; + const expires = updatedJwtPayload.exp; + return updatedJwtPayload +} \ No newline at end of file diff --git a/apps/authentication/src/user/user.controller.ts b/apps/authentication/src/user/user.controller.ts new file mode 100644 index 0000000..745373f --- /dev/null +++ b/apps/authentication/src/user/user.controller.ts @@ -0,0 +1,27 @@ +import { Body, Controller, Get, Param, Patch, Post, Request } from '@nestjs/common'; +import { UserService } from './user.service'; +import { ConfigService } from '@nestjs/config'; +import { ApiOperation, ApiResponse } from '@nestjs/swagger'; + +@Controller('user') +export class UserController { + constructor( + private readonly userService: UserService, + private configService: ConfigService, + ) { } + + @Get('/:email') + getUserDetails(@Param('email') email: string) { + return this.userService.getUserDetails(email); + } + + @Patch('/updateUserCaIds') + updateUserCaIds(@Body() data, @Request() req) { + return this.userService.updateUserCaIds(data, req?.headers?.authorization) + } + + @Post('/checkPermission') + checkPermission(@Body() data, @Request() req) { + return this.userService.checkPermission(data, req?.headers?.authorization) + } +} diff --git a/apps/authentication/src/user/user.module.ts b/apps/authentication/src/user/user.module.ts new file mode 100644 index 0000000..fb402e5 --- /dev/null +++ b/apps/authentication/src/user/user.module.ts @@ -0,0 +1,18 @@ +import { Module } from '@nestjs/common'; +import { ConfigModule } from '@nestjs/config'; +import { UserController } from './user.controller'; +import { UserService } from './user.service'; +import { TerminusModule } from '@nestjs/terminus'; +import { HttpModule } from '@nestjs/axios'; +@Module({ + imports: [ + ConfigModule.forRoot({ + isGlobal: true, + }), + TerminusModule, + HttpModule, + ], + controllers: [UserController], + providers: [UserService], +}) +export class UserModule { } diff --git a/apps/authentication/src/user/user.service.ts b/apps/authentication/src/user/user.service.ts new file mode 100644 index 0000000..23417f9 --- /dev/null +++ b/apps/authentication/src/user/user.service.ts @@ -0,0 +1,114 @@ +import { HttpService } from '@nestjs/axios'; +import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; +import { lastValueFrom, map } from 'rxjs'; + +@Injectable() +export class UserService { + constructor(private readonly httpService: HttpService) { } + + async getUserDetails(email: string) { + try { + const userRes = await lastValueFrom( + this.httpService + .get( + `https://auth.konnect.samagra.io/api/user?email=${email}`, + { + headers: { + Authorization: 'S2lTEmQZHrIjWUq30-4UJQDq0FYqSXOUhak24zcYZHYFbXTKawwCu0dr' + } + } + ) + .pipe(map((response) => response.data)), + ); + return userRes + } + catch (error) { + console.log("ERROR --->", error.message, error.code) + if (error.message == 'Request failed with status code 404') + throw new HttpException({ + status: HttpStatus.NOT_FOUND, + error: `User with the email ${email} doesn't exist.`, + }, HttpStatus.NOT_FOUND); + } + return null; + } + + async updateUserCaIds(data, token) { + try { + + // Getting user data from Fusion Auth + let userRes = await lastValueFrom( + this.httpService + .get( + `http://localhost:6000/user/${data.userEmail}`, { + headers: { + Authorization: token + } + } + ) + .pipe(map((response) => response.data)), + ); + userRes = userRes.user; + if (userRes?.data?.registeredCAs?.length) { + userRes.data = { ...userRes.data, registeredCAs: [...userRes.data.registeredCAs, data.caId] } + } else { + userRes.data = { + registeredCAs: [data.caId] + } + } + + // Updating user data in fusion auth + const userUpdateRes = await lastValueFrom( + this.httpService + .put( + `https://auth.konnect.samagra.io/api/user/${userRes.id}`, { + user: userRes + }, + { + headers: { + Authorization: 'S2lTEmQZHrIjWUq30-4UJQDq0FYqSXOUhak24zcYZHYFbXTKawwCu0dr' + } + } + ) + .pipe(map((response) => response.data)), + ); + + console.log("Updated User Response----> ", userUpdateRes) + return userUpdateRes; + } + catch (error) { + console.log(error) + } + return null; + } + + async checkPermission(data, token) { + try { + + // Getting user data from Fusion Auth + let userRes = await lastValueFrom( + this.httpService + .get( + `http://localhost:6000/user/${data.userEmail}`, { + headers: { + Authorization: token + } + } + ) + .pipe(map((response) => response.data)), + ); + + userRes = userRes.user; + // Check if caId exists in the list of registered CAs or not. + if (userRes?.data?.registeredCAs?.length) { + return userRes?.data?.registeredCAs?.includes(data.caId); + } + return false; + + } + catch (error) { + console.log(error) + } + return null; + } +}