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

Develop #34

Merged
merged 13 commits into from
Oct 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions apps/vc-api/.env.example
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
INFURA_PROJECT_ID=<YOUR_INFURA_PROJECT_ID>
SIGNING_PRIVATE_KEY=<YOUR_SIGNING_PRIVATE_KEY>
ENVIRONMENT=<YOUR_ENVIRONMENT># (development, production, staging, test)

SIGNING_PRIVATE_KEY=<YOUR_SIGNING_PRIVATE_KEY>
SIGNING_PRIVATE_KEY_SEPOLIA_DOMAIN=<YOUR_SIGNING_PRIVATE_KEY_FOR_THE_SEPOLIA_DOMAIN>

ENS_DOMAIN=<YOUR_ENS_DOMAIN>
CHAIN_ID=<YOUR_CHAIN_ID> # 1 or 11155111
ENS_DOMAIN_SEPOLIA=<YOUR_ENS_DOMAIN_SEPOLIA>

API_DOMAIN=<YOUR_API_DOMAIN>

GITHUB_CLIENT_ID=<YOUR_GITHUB_CLIENT_ID>
Expand All @@ -18,8 +22,7 @@ DISCORD_CLIENT_ID=<YOUR_DISCORD_CLIENT_ID>
DISCORD_CLIENT_SECRET=<YOUR_DISCORD_CLIENT_SECRET>

JWT_SECRET=<YOUR_JWT_SECRET>
SIWE_DOMAIN=<YOUR_SIWE_DOMAIN>
SIWE_ORIGIN=<YOUR_SIWE_ORIGIN>

ENCRYPT_KEY`=<YOUR_ENCRYPTION_PASSWORD>
ENCRYPT_SALT=<YOUR_ENCRYPTION_SALT>

RESEND_API_KEY=<YOUR_RESEND_API_KEY>
8 changes: 5 additions & 3 deletions apps/vc-api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,16 @@
"@veramo/did-resolver": "^6.0.0",
"@veramo/credential-w3c": "^6.0.0",
"@veramo/credential-ld": "^6.0.0",
"@justaname.id/sdk": "^0.2.76",
"@nestjs/jwt": "^10.2.0",
"uuid": "^10.0.0",
"rxjs": "^7.8.0",
"vitest": "^1.3.1",
"@react-email/components": "^0.0.25",
"react": "18.3.1",
"resend": "^4.0.0",
"react-dom": "18.3.1"
"react-dom": "18.3.1",
"@nestjs/testing": "^10.0.2",
"@golevelup/ts-jest": "^0.5.6",
"supertest": "^7.0.0",
"@justaname.id/sdk": "0.2.98"
}
}
13 changes: 6 additions & 7 deletions apps/vc-api/src/api/auth/auth.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ import { JwtService } from '@nestjs/jwt';
import moment from 'moment';
import { JwtGuard } from '../../guards/jwt.guard';
import { ENS_MANAGER_SERVICE, IEnsManagerService } from '../../core/applications/ens-manager/iens-manager.service';
import { ChainId } from '../../core/domain/entities/environment';

type Siwens = { address: string, ens: string };
type Siwens = { address: string, ens: string, chainId: ChainId };

@Controller('auth')
export class AuthController {
Expand All @@ -28,20 +29,18 @@ export class AuthController {
@Res() res: Response,
@Req() req: Request
) {
const { address, ens } = await this.ensManagerService.signIn({
const { address, ens, chainId } = await this.ensManagerService.signIn({
message: body.message,
signature: body.signature
})


const token = this.jwtService.sign({ ens, address }, {
const token = this.jwtService.sign({ ens, address, chainId }, {
expiresIn: moment().add(1, 'hour').unix()
});


res.cookie('justverifiedtoken', token, { httpOnly: true, secure: true, sameSite: 'none' });

return res.status(200).send({ ens, address });
return res.status(200).send({ ens, address, chainId });

}

Expand All @@ -56,7 +55,7 @@ export class AuthController {
return;
}
res.setHeader('Content-Type', 'text/plain');
res.status(200).send({ ens: req.user?.ens, address: req.user?.address });
res.status(200).send({ ens: req.user?.ens, address: req.user?.address, chainId: req.user?.chainId });
}

@UseGuards(JwtGuard)
Expand Down
39 changes: 19 additions & 20 deletions apps/vc-api/src/api/credentials/credentials.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ import {CredentialsResendOtpRequestApi} from "./requests/credentials.resend-otp.
import {CredentialsVerifyOtpRequestApi} from "./requests/credentials.verify-otp.request.api";
import {CredentialsClearEmailRequestApi} from "./requests/credentials.clear-email.request.api";
import {AuthCallbackApiResponse} from "./responses/credentials.callback.response.api";
import { ChainId } from '../../core/domain/entities/environment';

type Siwens = { address: string, ens: string };
type Siwens = { address: string, ens: string, chainId: ChainId };

@Controller('credentials')
export class CredentialsController {
Expand Down Expand Up @@ -47,6 +48,7 @@ export class CredentialsController {
const redirectUrl = await this.credentialCreatorFacade.getSocialAuthUrl(
authGetAuthUrlRequestApi.authName,
req.user?.ens,
req.user?.chainId,
authId
)

Expand Down Expand Up @@ -82,27 +84,23 @@ export class CredentialsController {
@Query() authGetAuthUrlRequestApiQuery: any,
@Res() res: Response
): Promise<void> {
try {
const verifiedEthereumEip712Signature2021 = await this.credentialCreatorFacade.socialCallback(
this.authControllerMapper.mapAuthCallbackApiRequestToCredentialCallbackRequest(
authGetAuthUrlRequestApiQuery,
authGetAuthUrlRequestApiParam)
)

const { authId, dataKey, verifiableCredential } = verifiedEthereumEip712Signature2021

const subject = this.authSubjects.get(authId);
subject?.next({
authId,
result: {
verifiableCredential,
dataKey
}
});
}catch(e){

}
const verifiedEthereumEip712Signature2021 = await this.credentialCreatorFacade.socialCallback(
this.authControllerMapper.mapAuthCallbackApiRequestToCredentialCallbackRequest(
authGetAuthUrlRequestApiQuery,
authGetAuthUrlRequestApiParam)
)

const { authId, dataKey, verifiableCredential } = verifiedEthereumEip712Signature2021

const subject = this.authSubjects.get(authId);
subject?.next({
authId,
result: {
verifiableCredential,
dataKey
}
});

res.send(`
<html>
Expand Down Expand Up @@ -131,6 +129,7 @@ export class CredentialsController {
const state = await this.credentialCreatorFacade.getEmailOTP(
query.email,
req.user?.ens,
req.user?.chainId,
authId
)

Expand Down
16 changes: 16 additions & 0 deletions apps/vc-api/src/api/filters/auth/authentication.filter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { BaseExceptionFilter } from '@nestjs/core';
import { ArgumentsHost, Catch, HttpStatus } from '@nestjs/common';
import { AuthenticationException } from '../../../core/domain/exceptions/Authentication.exception';

@Catch(AuthenticationException)
export class AuthenticationExceptionFilter extends BaseExceptionFilter {
catch(exception: AuthenticationException, host: ArgumentsHost) {
const context = host.switchToHttp();
const response = context.getResponse();
const httpStatus = HttpStatus.UNAUTHORIZED;

response.status(httpStatus).json({
message: exception.message,
});
}
}
16 changes: 16 additions & 0 deletions apps/vc-api/src/api/filters/auth/justaName-intializer.filter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { BaseExceptionFilter } from '@nestjs/core';
import { ArgumentsHost, Catch, HttpStatus } from '@nestjs/common';
import { JustaNameInitializerException } from '../../../core/domain/exceptions/JustaNameInitializer.exception';

@Catch(JustaNameInitializerException)
export class JustaNameInitializerExceptionFilter extends BaseExceptionFilter {
catch(exception: JustaNameInitializerException, host: ArgumentsHost) {
const context = host.switchToHttp();
const response = context.getResponse();
const httpStatus = HttpStatus.BAD_GATEWAY;

response.status(httpStatus).json({
message: exception.message,
});
}
}
16 changes: 16 additions & 0 deletions apps/vc-api/src/api/filters/credentials/credentials.filter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { BaseExceptionFilter } from '@nestjs/core';
import { ArgumentsHost, Catch, HttpStatus } from '@nestjs/common';
import { CredentialsException } from '../../../core/domain/exceptions/Credentials.exception';

@Catch(CredentialsException)
export class CredentialsExceptionFilter extends BaseExceptionFilter {
catch(exception: CredentialsException, host: ArgumentsHost) {
const context = host.switchToHttp();
const response = context.getResponse();
const httpStatus = HttpStatus.BAD_REQUEST;

response.status(httpStatus).json({
message: exception.message,
});
}
}
16 changes: 16 additions & 0 deletions apps/vc-api/src/api/filters/credentials/email-sender.filter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { BaseExceptionFilter } from '@nestjs/core';
import { ArgumentsHost, Catch, HttpStatus } from '@nestjs/common';
import { EmailSenderException } from '../../../core/domain/exceptions/EmailSender.exception';

@Catch(EmailSenderException)
export class EmailSenderExceptionFilter extends BaseExceptionFilter {
catch(exception: EmailSenderException, host: ArgumentsHost) {
const context = host.switchToHttp();
const response = context.getResponse();
const httpStatus = HttpStatus.BAD_GATEWAY;

response.status(httpStatus).json({
message: exception.message,
});
}
}
16 changes: 16 additions & 0 deletions apps/vc-api/src/api/filters/credentials/otp.filter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { BaseExceptionFilter } from '@nestjs/core';
import { ArgumentsHost, Catch, HttpStatus } from '@nestjs/common';
import { OTPException } from '../../../core/domain/exceptions/OTP.exception';

@Catch(OTPException)
export class OTPExceptionFilter extends BaseExceptionFilter {
catch(exception: OTPException, host: ArgumentsHost) {
const context = host.switchToHttp();
const response = context.getResponse();
const httpStatus = HttpStatus.BAD_REQUEST;

response.status(httpStatus).json({
message: exception.message,
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { BaseExceptionFilter } from '@nestjs/core';
import { ArgumentsHost, Catch, HttpStatus } from '@nestjs/common';
import { SocialResolverNotFoundException } from '../../../core/domain/exceptions/SocialResolverNotFound.exception';

@Catch(SocialResolverNotFoundException)
export class SocialResolverNotFoundExceptionFilter extends BaseExceptionFilter {
catch(exception: SocialResolverNotFoundException, host: ArgumentsHost) {
const context = host.switchToHttp();
const response = context.getResponse();
const httpStatus = HttpStatus.NOT_FOUND;

response.status(httpStatus).json({
message: exception.message,
});
}
}
17 changes: 17 additions & 0 deletions apps/vc-api/src/api/filters/vc.api.filters.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { OTPExceptionFilter } from './credentials/otp.filter';
import { EmailSenderExceptionFilter } from './credentials/email-sender.filter';
import { ChainIdInvalidExceptionFilter } from './verify-records/chainId.invalid.filter';
import { SocialResolverNotFoundExceptionFilter } from './credentials/social-resolver.not-found.filter';
import { CredentialsExceptionFilter } from './credentials/credentials.filter';
import { AuthenticationExceptionFilter } from './auth/authentication.filter';
import { JustaNameInitializerExceptionFilter } from './auth/justaName-intializer.filter';

export const VCManagementApiFilters = [
OTPExceptionFilter,
EmailSenderExceptionFilter,
AuthenticationExceptionFilter,
JustaNameInitializerExceptionFilter,
ChainIdInvalidExceptionFilter,
CredentialsExceptionFilter,
SocialResolverNotFoundExceptionFilter,
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { BaseExceptionFilter } from '@nestjs/core';
import { ArgumentsHost, Catch, HttpStatus } from '@nestjs/common';
import { ChainIdInvalidException } from '../../../core/domain/exceptions/ChainIdInvalid.exception';

@Catch(ChainIdInvalidException)
export class ChainIdInvalidExceptionFilter extends BaseExceptionFilter {
catch(exception: ChainIdInvalidException, host: ArgumentsHost) {
const context = host.switchToHttp();
const response = context.getResponse();
const httpStatus = HttpStatus.BAD_REQUEST;

response.status(httpStatus).json({
message: exception.message,
});
}
}
33 changes: 9 additions & 24 deletions apps/vc-api/src/config/env.validation.ts
Original file line number Diff line number Diff line change
@@ -1,42 +1,33 @@
import { plainToClass, Transform } from 'class-transformer';
import { plainToClass } from 'class-transformer';
import {
IsEnum,
IsNumber, IsNumberString,
IsString,
registerDecorator,
validateSync,
ValidationOptions
} from 'class-validator';
import { ethers } from 'ethers';
import { ChainId, Environment, EnvironmentType, SupportedChainIds } from '../core/domain/entities/environment';
import { x } from 'vitest/dist/reporters-yx5ZTtEV';
import { Environment, EnvironmentType } from '../core/domain/entities/environment';

class EnvironmentVariables implements Environment{
@IsString({ message: 'SIGNING_PRIVATE_KEY must be a string' })
@IsEthereumPrivateKey({ message: 'SIGNING_PRIVATE_KEY must be a valid private key' })
SIGNING_PRIVATE_KEY!: string;

@IsString({ message: 'SIGNING_PRIVATE_KEY_SEPOLIA_DOMAIN must be a string' })
@IsEthereumPrivateKey({ message: 'SIGNING_PRIVATE_KEY_SEPOLIA_DOMAIN must be a valid private key' })
SIGNING_PRIVATE_KEY_SEPOLIA_DOMAIN!: string;

@IsString({ message: 'ENVIROMENT must be a string' })
@IsEnum(EnvironmentType, { message: 'ENVIRONMENT must be a valid environment' })
ENVIRONMENT!: EnvironmentType;

@Transform(({ value }) => {
const intValue = parseInt(value)
if (isNaN(intValue)) {
throw new Error('CHAIN_ID must be a number')
}

const isValidChainId = (x: any): x is ChainId => SupportedChainIds.includes(x);

if (!isValidChainId(intValue)) {
throw new Error('CHAIN_ID must be a valid chain id (1, 11155111)')
}
})
CHAIN_ID!: ChainId;

@IsString({ message: 'ENS_DOMAIN must be a string' })
ENS_DOMAIN!: string;

@IsString({ message: 'ENS_DOMAIN_SEPOLIA must be a string' })
ENS_DOMAIN_SEPOLIA!: string;

@IsString({message: 'INFURA_PROJECT_ID must be a string'})
INFURA_PROJECT_ID!: string;

Expand Down Expand Up @@ -70,12 +61,6 @@ class EnvironmentVariables implements Environment{
@IsString({message: 'JWT_SECRET must be a string'})
JWT_SECRET: string;

@IsString({message: 'SIWE_DOMAIN must be a string'})
SIWE_DOMAIN: string;

@IsString({message: 'SIWE_ORIGIN must be a string'})
SIWE_ORIGIN: string;

@IsString({message: 'ENCRYPT_KEY must be a string'})
ENCRYPT_KEY: string;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ import {
EthereumEip712Signature2021,
VerifiableEthereumEip712Signature2021
} from '../../../domain/entities/ethereumEip712Signature';
import { ChainId } from '../../../domain/entities/environment';

export const CREDENTIAL_CREATOR = 'CREDENTIAL_CREATOR';

export interface ICredentialCreator {
createCredential(credential: EthereumEip712Signature2021): Promise<VerifiableEthereumEip712Signature2021>
createCredential(credential: EthereumEip712Signature2021, chainId: ChainId): Promise<VerifiableEthereumEip712Signature2021>
}
Loading
Loading