Skip to content
Snippets Groups Projects
Unverified Commit edc728c1 authored by Zdravko Iliev's avatar Zdravko Iliev
Browse files

feat: add basic auth with guard and middleware. Make basic auth optional and...

feat: add basic auth with guard and middleware. Make basic auth optional and only for massaging, later we should add jwt auth for the rest of the endpoints if we are going to use the project with ocm-ui

Signed-off-by: default avatarZdravko Iliev <zdravko.iliev@vereign.com>
parent e4a56fba
No related branches found
No related tags found
1 merge request!38feat: rest agent support for connection and proof change state events
Pipeline #65459 passed with stage
in 1 minute and 45 seconds
......@@ -3,13 +3,17 @@ import {
AutoAcceptCredential,
AutoAcceptProof,
ConnectionsModule,
ConnectionStateChangedEvent,
CredentialsModule,
DidExchangeRole,
DidsModule,
Key,
KeyDidResolver,
KeyType,
PeerDidResolver,
ProofsModule,
ProofState,
ProofStateChangedEvent,
TypedArrayEncoder,
V2CredentialProtocol,
V2ProofProtocol,
......@@ -19,6 +23,7 @@ import {
import {
AnonCredsCredentialFormatService,
AnonCredsModule,
AnonCredsProof,
AnonCredsProofFormatService,
} from "@aries-framework/anoncreds";
import {
......@@ -33,6 +38,8 @@ import { indyVdr } from "@hyperledger/indy-vdr-nodejs";
import { AskarModule } from "@aries-framework/askar";
import { ariesAskar } from "@hyperledger/aries-askar-nodejs";
import { Key as C, KeyAlgs } from "@hyperledger/aries-askar-shared";
import { IConfAgent } from "@ocm-engine/config";
import axios from "axios";
export const importDidsToWallet = async (
agent: Agent,
......@@ -139,3 +146,124 @@ export const getAskarAnonCredsIndyModules = (networks: any) => {
}),
} as const;
};
export const svdxProofStateChangeHandler = async (
ev: ProofStateChangedEvent,
agent: Agent,
config: IConfAgent,
) => {
if (ProofState.Done !== ev.payload.proofRecord.state) {
return;
}
const presentationMessage = await agent.proofs.findPresentationMessage(
ev.payload.proofRecord.id,
);
console.log(JSON.stringify(presentationMessage, null, 2));
if (!presentationMessage) {
console.log("No presentation message found");
return;
}
const attachmentId = presentationMessage.formats[0].attachmentId;
const attachment =
presentationMessage.getPresentationAttachmentById(attachmentId);
console.log(JSON.stringify(attachment, null, 2));
if (!attachment) {
console.log("No attachment found");
return;
}
const email =
attachment.getDataAsJson<AnonCredsProof>()?.requested_proof.revealed_attrs[
"email"
].raw;
try {
await axios.post(
config.agentSVDXWebHook,
{
email,
connectionId: ev.payload.proofRecord.connectionId,
},
{
auth: {
username: config.agentSVDXBasicUser,
password: config.agentSVDXBasicPass,
},
},
);
} catch (e) {
console.log(JSON.stringify(e, null, 2));
}
};
export const svdxConnectionStateChangeHandler = async (
ev: ConnectionStateChangedEvent,
agent: Agent,
config: IConfAgent,
) => {
if (
ev.payload.connectionRecord.role === DidExchangeRole.Responder &&
ev.payload.connectionRecord.state !== "completed"
) {
return;
}
await agent.connections.addConnectionType(
ev.payload.connectionRecord.id,
ev.payload.connectionRecord.theirLabel || "svdx",
);
console.log("connection accepted", JSON.stringify(ev, null, 2));
const connections = await agent.connections.findAllByConnectionTypes([
ev.payload.connectionRecord.theirLabel || "svdx",
]);
if (connections.length < 2) {
return;
}
connections.sort(
(a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime(),
);
while (connections.length > 1) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const con = connections.pop()!;
console.log(`deleting ${con.id}`);
await agent.connections.deleteById(con.id);
}
try {
await agent.proofs.requestProof({
protocolVersion: "v2",
connectionId: connections[0].id,
proofFormats: {
anoncreds: {
name: "proof-request",
version: "1.0",
requested_attributes: {
email: {
name: "email",
restrictions: [
{
schema_id: config.agentSVDXSchemaId,
cred_def_id: config.agentSVDXCredDefId,
},
],
},
},
},
},
});
} catch (e) {
console.log(JSON.stringify(e, null, 2));
console.log("failed to issue credential");
}
};
import { Module, ValidationPipe } from "@nestjs/common";
import {
Module,
ValidationPipe,
MiddlewareConsumer,
RequestMethod,
} from "@nestjs/common";
import { AgentService } from "../askar/agent.service";
import { ConfigModule } from "@nestjs/config";
import { ConfigModule, ConfigService } from "@nestjs/config";
import { LedgersModule } from "@ocm-engine/ledgers";
import { APP_PIPE } from "@nestjs/core";
import { RestController } from "./rest.controller";
import { PassportModule } from "@nestjs/passport";
import { BasicAuthMiddleware } from "./auth/basic.middleware";
import { IConfAgent } from "@ocm-engine/config";
@Module({
imports: [ConfigModule, LedgersModule],
imports: [ConfigModule, LedgersModule, PassportModule],
providers: [
AgentService,
{
......@@ -18,4 +26,17 @@ import { RestController } from "./rest.controller";
],
controllers: [RestController],
})
export class AskarRestModule {}
export class AskarRestModule {
constructor(private readonly configService: ConfigService) {}
configure(consumer: MiddlewareConsumer) {
const config: IConfAgent | undefined =
this.configService.get<IConfAgent>("agent");
if (config?.agentIsSVDX) {
consumer
.apply(BasicAuthMiddleware)
.forRoutes({ path: "*messages", method: RequestMethod.ALL });
}
}
}
import { CanActivate, ExecutionContext, Injectable } from "@nestjs/common";
import { AuthGuard } from "@nestjs/passport";
@Injectable()
export class BasicGuard extends AuthGuard("basic") {}
import {
Injectable,
Logger,
NestMiddleware,
UnauthorizedException,
} from "@nestjs/common";
import { Request, Response, NextFunction } from "express";
import { ConfigService } from "@nestjs/config";
import { IConfAgent } from "@ocm-engine/config";
@Injectable()
export class BasicAuthMiddleware implements NestMiddleware {
private readonly logger: Logger = new Logger(BasicAuthMiddleware.name);
constructor(private readonly configService: ConfigService) {}
use(req: Request, res: Response, next: NextFunction) {
const config: IConfAgent | undefined =
this.configService.get<IConfAgent>("agent");
if (!config?.agentIsSVDX) {
this.logger.log("Agent is REST only turning of basic auth");
return next();
}
this.logger.log("Agent is SVDX turning basic auth middleware on");
const authHeader = req.headers["authorization"];
if (!authHeader) {
return next(new UnauthorizedException());
}
const [username, password] = Buffer.from(authHeader.split(" ")[1], "base64")
.toString()
.split(":");
if (
username === config.agentSVDXBasicUser &&
password === config.agentSVDXBasicPass
) {
return next();
}
return next(new UnauthorizedException());
}
}
// basic.strategy.ts
import { ConfigService } from "@nestjs/config";
import { IConfAgent } from "@ocm-engine/config";
import { BasicStrategy as Strategy } from "passport-http";
import { PassportStrategy } from "@nestjs/passport";
import { Injectable, UnauthorizedException } from "@nestjs/common";
@Injectable()
export class BasicStrategy extends PassportStrategy(Strategy, "basic") {
constructor(private readonly configService: ConfigService) {
super();
}
async validate(username: string, password: string): Promise<boolean> {
console.log(username, password);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const config: IConfAgent = this.configService.get<IConfAgent>("agent")!;
if (
config.agentSVDXBasicUser === username &&
config.agentSVDXBasicPass === password
) {
return true;
}
throw new UnauthorizedException();
}
}
import { Body, Controller, Get, Param, Post, UseFilters } from "@nestjs/common";
import { AgentService } from "../askar/agent.service";
import {
CreateCredentialDefinitionRequsetDto,
......@@ -87,7 +88,7 @@ export class RestController {
}
@Post("/messages")
sendMeesage(@Body() message: MakeBasicMessageRequestDto) {
async sendMeesage(@Body() message: MakeBasicMessageRequestDto) {
return this.agentService.sendMessage(message);
}
}
......@@ -7,9 +7,7 @@ import {
BasicMessageStateChangedEvent,
ConnectionEventTypes,
ConnectionStateChangedEvent,
DidExchangeRole,
ProofEventTypes,
ProofState,
ProofStateChangedEvent,
} from "@aries-framework/core";
import {
......@@ -19,6 +17,10 @@ import {
} from "@ocm-engine/dtos";
import { IConfAgent } from "@ocm-engine/config";
import { ConfigService } from "@nestjs/config";
import {
svdxConnectionStateChangeHandler,
svdxProofStateChangeHandler,
} from "../agent.utils";
@Injectable()
export class AgentEventListenerServce implements OnModuleInit {
......@@ -38,112 +40,29 @@ export class AgentEventListenerServce implements OnModuleInit {
this.agentConfig = this.configService.get<IConfAgent>("agent")!;
if (this.agentConfig.agentIsSVDX && this.agentConfig.agentIsRest) {
//listen to when someone accepts a connection only when HIN webhook is set up and agent is in rest mode
//Extract to separate methods
this.askar.agent.events.on(
ConnectionEventTypes.ConnectionStateChanged,
async (ev: ConnectionStateChangedEvent) => {
if (
ev.payload.connectionRecord.role === DidExchangeRole.Responder &&
ev.payload.connectionRecord.state !== "completed"
) {
return;
}
await this.askar.agent.connections.addConnectionType(
ev.payload.connectionRecord.id,
ev.payload.connectionRecord.theirLabel || "svdx",
);
console.log("connection accepted", JSON.stringify(ev, null, 2));
const connections =
await this.askar.agent.connections.findAllByConnectionTypes([
ev.payload.connectionRecord.theirLabel || "svdx",
]);
console.log(JSON.stringify(connections, null, 2));
if (connections.length < 2) {
return;
}
connections.sort(
(a, b) =>
new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime(),
this.logger.log("connection state event received");
this.logger.debug(JSON.stringify(ev, null, 2));
return svdxConnectionStateChangeHandler(
ev,
this.askar.agent,
this.agentConfig,
);
while (connections.length > 1) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const con = connections.pop()!;
console.log(`deleting ${con.id}`);
await this.askar.agent.connections.deleteById(con.id);
}
try {
//TODO: replace with method from agent service
await this.askar.agent.proofs.requestProof({
protocolVersion: "v2",
connectionId: connections[0].id,
proofFormats: {
//TODO: replace with formatting method
anoncreds: {
name: "proof-request",
version: "1.0",
requested_attributes: {
email: {
name: "email",
restrictions: [
{
schema_id: this.agentConfig.agentSVDXSchemaId,
cred_def_id: this.agentConfig.agentSVDXCredDefId,
},
],
},
},
},
},
});
} catch (e) {
console.log(JSON.stringify(e, null, 2));
console.log("failed to issue credential");
}
},
);
this.askar.agent.events.on(
ProofEventTypes.ProofStateChanged,
async (ev: ProofStateChangedEvent) => {
console.log(
"proof event received",
JSON.stringify(ev.payload.proofRecord, null, 2),
);
//is this the correct state ??
if (ProofState.Done !== ev.payload.proofRecord.state) {
return;
}
const result = await this.askar.agent.proofs.findPresentationMessage(
ev.payload.proofRecord.id,
);
if (!result) return;
const t = await result.formats[0].attachmentId;
const d = await result.getPresentationAttachmentById(t);
if (!d) return;
//TODO: send to svdx
this.logger.log("proof state event received");
this.logger.debug(JSON.stringify(ev, null, 2));
console.log(ev.payload.proofRecord.connectionId);
console.log(
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
d.getDataAsJson()?.requested_proof.revealed_attrs.email.raw,
return svdxProofStateChangeHandler(
ev,
this.askar.agent,
this.agentConfig,
);
},
);
......
......@@ -22,5 +22,7 @@ export const agentConfig = registerAs(
agentSVDXSchemaId: process.env["AGENT_SVDX_SCHEMA_ID"]!,
agentSVDXCredDefId: process.env["AGENT_SVDX_CRED_DEF_ID"]!,
agentSVDXWebHook: process.env["AGENT_SVDX_WEBHOOK_URL"]!,
agentSVDXBasicUser: process.env["AGENT_SVDX_BASIC_USER"]!,
agentSVDXBasicPass: process.env["AGENT_SVDX_BASIC_PASS"]!,
}),
);
......@@ -12,8 +12,11 @@ export interface IConfAgent {
agentConsumerMaxMessagess: number;
agentConsumerRateLimit: number;
agentPort: number;
agentIsSVDX: boolean;
agentSVDXSchemaId: string;
agentSVDXCredDefId: string;
agentSVDXWebHook: string;
agentSVDXBasicUser: string;
agentSVDXBasicPass: string;
}
......@@ -1676,6 +1676,11 @@
iterare "1.2.1"
tslib "2.5.3"
 
"@nestjs/passport@^10.0.1":
version "10.0.1"
resolved "https://registry.yarnpkg.com/@nestjs/passport/-/passport-10.0.1.tgz#4a745cb4acf01ef8fd56b9ec1349ac74165b098f"
integrity sha512-hS22LeNj0LByS9toBPkpKyZhyKAXoHACLS1EQrjbAJJEQjhocOskVGwcMwvMlz+ohN+VU804/nMF1Zlya4+TiQ==
"@nestjs/platform-express@^9.0.0":
version "9.4.3"
resolved "https://registry.yarnpkg.com/@nestjs/platform-express/-/platform-express-9.4.3.tgz#f61b75686bdfce566be3b54fa7bb20a4d87ed619"
......@@ -2899,6 +2904,38 @@
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==
 
"@types/passport-http@^0.3.9":
version "0.3.9"
resolved "https://registry.yarnpkg.com/@types/passport-http/-/passport-http-0.3.9.tgz#268e483ade820d4f0edb3d35cec090d1990cc081"
integrity sha512-uQ4vyRdvM0jdWuKpLmi6Q6ri9Nwt8YnHmF7kE6snbthxPrsMWcjRCVc5WcPaQ356ODSZTDgiRYURMPIspCkn3Q==
dependencies:
"@types/express" "*"
"@types/passport" "*"
"@types/passport-local@^1.0.35":
version "1.0.35"
resolved "https://registry.yarnpkg.com/@types/passport-local/-/passport-local-1.0.35.tgz#233d370431b3f93bb43cf59154fb7519314156d9"
integrity sha512-K4eLTJ8R0yYW8TvCqkjB0pTKoqfUSdl5PfZdidTjV2ETV3604fQxtY6BHKjQWAx50WUS0lqzBvKv3LoI1ZBPeA==
dependencies:
"@types/express" "*"
"@types/passport" "*"
"@types/passport-strategy" "*"
"@types/passport-strategy@*":
version "0.2.35"
resolved "https://registry.yarnpkg.com/@types/passport-strategy/-/passport-strategy-0.2.35.tgz#e52f5212279ea73f02d9b06af67efe9cefce2d0c"
integrity sha512-o5D19Jy2XPFoX2rKApykY15et3Apgax00RRLf0RUotPDUsYrQa7x4howLYr9El2mlUApHmCMv5CZ1IXqKFQ2+g==
dependencies:
"@types/express" "*"
"@types/passport" "*"
"@types/passport@*":
version "1.0.12"
resolved "https://registry.yarnpkg.com/@types/passport/-/passport-1.0.12.tgz#7dc8ab96a5e895ec13688d9e3a96920a7f42e73e"
integrity sha512-QFdJ2TiAEoXfEQSNDISJR1Tm51I78CymqcBa8imbjo6dNNu+l2huDxxbDEIoFIwOSKMkOfHEikyDuZ38WwWsmw==
dependencies:
"@types/express" "*"
"@types/qs@*":
version "6.9.7"
resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb"
......@@ -8901,6 +8938,34 @@ parseurl@~1.3.2, parseurl@~1.3.3:
resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==
 
passport-http@^0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/passport-http/-/passport-http-0.3.0.tgz#8ee53d4380be9c60df2151925029826f77115603"
integrity sha512-OwK9DkqGVlJfO8oD0Bz1VDIo+ijD3c1ZbGGozIZw+joIP0U60pXY7goB+8wiDWtNqHpkTaQiJ9Ux1jE3Ykmpuw==
dependencies:
passport-strategy "1.x.x"
passport-local@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/passport-local/-/passport-local-1.0.0.tgz#1fe63268c92e75606626437e3b906662c15ba6ee"
integrity sha512-9wCE6qKznvf9mQYYbgJ3sVOHmCWoUNMVFoZzNoznmISbhnNNPhN9xfY3sLmScHMetEJeoY7CXwfhCe7argfQow==
dependencies:
passport-strategy "1.x.x"
passport-strategy@1.x.x:
version "1.0.0"
resolved "https://registry.yarnpkg.com/passport-strategy/-/passport-strategy-1.0.0.tgz#b5539aa8fc225a3d1ad179476ddf236b440f52e4"
integrity sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA==
passport@^0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/passport/-/passport-0.6.0.tgz#e869579fab465b5c0b291e841e6cc95c005fac9d"
integrity sha512-0fe+p3ZnrWRW74fe8+SvCyf4a3Pb2/h7gFkQ8yTJpAO50gDzlfjZUZTO1k5Eg9kUct22OxHLqDZoKUWRHOh9ug==
dependencies:
passport-strategy "1.x.x"
pause "0.0.1"
utils-merge "^1.0.1"
path-exists@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515"
......@@ -8966,6 +9031,11 @@ path-type@^4.0.0:
resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b"
integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==
 
pause@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/pause/-/pause-0.0.1.tgz#1d408b3fdb76923b9543d96fb4c9dfd535d9cb5d"
integrity sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==
picocolors@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"
......@@ -11063,7 +11133,7 @@ util@^0.12.4:
is-typed-array "^1.1.3"
which-typed-array "^1.1.2"
 
utils-merge@1.0.1:
utils-merge@1.0.1, utils-merge@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==
......
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