Skip to content

Commit

Permalink
Merge pull request #35 from JustaName-id/ghadi/support-multiple-chains
Browse files Browse the repository at this point in the history
feat: support for sepolia and mainnet
  • Loading branch information
Ghadi8 authored Oct 14, 2024
2 parents 3c410ea + 51989a2 commit 7c6701c
Show file tree
Hide file tree
Showing 33 changed files with 225 additions and 205 deletions.
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>
6 changes: 3 additions & 3 deletions apps/vc-api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +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",
"@nestjs/testing": "^10.0.2",
"@golevelup/ts-jest": "^0.5.6"
"@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
7 changes: 5 additions & 2 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,7 +84,7 @@ export class CredentialsController {
@Query() authGetAuthUrlRequestApiQuery: any,
@Res() res: Response
): Promise<void> {

const verifiedEthereumEip712Signature2021 = await this.credentialCreatorFacade.socialCallback(
this.authControllerMapper.mapAuthCallbackApiRequestToCredentialCallbackRequest(
authGetAuthUrlRequestApiQuery,
Expand Down Expand Up @@ -127,6 +129,7 @@ export class CredentialsController {
const state = await this.credentialCreatorFacade.getEmailOTP(
query.email,
req.user?.ens,
req.user?.chainId,
authId
)

Expand Down
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>
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {ENS_MANAGER_SERVICE, IEnsManagerService} from "../../ens-manager/iens-ma
import {HttpService} from "@nestjs/axios";
import {CredentialCallbackResponse} from "./credential.callback.response";
import {BaseCallback} from "./base.callback";
import { ChainId } from '../../../domain/entities/environment';


export abstract class AbstractResolver<
Expand Down Expand Up @@ -60,39 +61,41 @@ export abstract class AbstractResolver<

async successfulCredentialGeneration(
vc: VerifiableEthereumEip712Signature2021,
subname: string
subname: string,
chainId: ChainId
): Promise<void> {
const checkIfMAppEnabled = await this.ensManagerService.checkIfMAppEnabled(subname);
const checkIfMAppEnabled = await this.ensManagerService.checkIfMAppEnabled(subname, chainId);
if (checkIfMAppEnabled) {
await this.ensManagerService.appendVcInMAppEnabledEns(
subname,
chainId,
vc,
this.getKey()
);
}
}

encryptState({ens, authId}: { ens: string; authId: string }): string {
const stateObject = { ens, authId };
encryptState({ens, chainId, authId}: { ens: string; chainId: ChainId; authId: string }): string {
const stateObject = { ens, chainId, authId };
return this.cryptoEncryption.encrypt(JSON.stringify(stateObject));
}

decryptState(state: string): { ens: string; authId: string } {
decryptState(state: string): { ens: string; chainId: ChainId; authId: string } {
return JSON.parse(this.cryptoEncryption.decrypt(state));
}

getEnsAndAuthId(data: T): { ens: string; authId: string } {
const { ens, authId } = this.decryptState(data.state);
return { ens, authId };
getEnsAndAuthId(data: T): { ens: string; chainId: ChainId; authId: string } {
const { ens, chainId, authId } = this.decryptState(data.state);
return { ens, chainId, authId };
}

async generateCredential(
data: T,
): Promise<CredentialCallbackResponse> {
const credentialSubject = await this.extractCredentialSubject(data);
const { ens , authId} = this.getEnsAndAuthId(data);
const { ens , chainId, authId} = this.getEnsAndAuthId(data);

const did = await this.didResolver.getEnsDid(ens)
const did = await this.didResolver.getEnsDid(ens, chainId)
const ethereumEip712Signature2021 = new EthereumEip712Signature2021<K>({
type: this.getType(),
context: this.getContext(),
Expand All @@ -110,10 +113,11 @@ export abstract class AbstractResolver<
});

const verifiableCredential = (await this.credentialCreator.createCredential(
ethereumEip712Signature2021
ethereumEip712Signature2021,
chainId
)) as VerifiableEthereumEip712Signature2021<K>;

await this.successfulCredentialGeneration(verifiableCredential, ens);
await this.successfulCredentialGeneration(verifiableCredential, ens, chainId);

return {
dataKey: this.getDataKey(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {EmailResolver} from "./email-resolver/email.resolver";
import {EMAIL_RESOLVER, IEmailResolver} from "./email-resolver/iemail.resolver";
import {EmailCallback} from "./email-resolver/email.callback";
import { SocialResolverNotFoundException } from '../../../domain/exceptions/SocialResolverNotFound.exception';
import { ChainId } from '../../../domain/entities/environment';

@Injectable()
export class CredentialCreatorFacade implements ICredentialCreatorFacade {
Expand All @@ -30,16 +31,16 @@ export class CredentialCreatorFacade implements ICredentialCreatorFacade {
throw SocialResolverNotFoundException.forCredentialName(credentialName);
}

async getSocialAuthUrl(credentialName: string, ens: string, authId: string): Promise<string> {
return this.getSocialResolver(credentialName).getAuthUrl({ens, authId});
async getSocialAuthUrl(credentialName: string, ens: string, chainId: ChainId, authId: string): Promise<string> {
return this.getSocialResolver(credentialName).getAuthUrl({ens, chainId, authId});
}

async socialCallback(credentialCallbackRequest: CredentialCallbackRequest): Promise<CredentialCallbackResponse> {
return await this.getSocialResolver(credentialCallbackRequest.credentialName).generateCredential(credentialCallbackRequest.callbackData);
}

async getEmailOTP(email: string, ens: string, authId:string): Promise<string> {
const state = await this.emailResolver.generateEmailOtp({email, authId, ens})
async getEmailOTP(email: string, ens: string, chainId: ChainId, authId:string): Promise<string> {
const state = await this.emailResolver.generateEmailOtp({email, authId, ens, chainId})
return state.state
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ EmailCredential

async generateEmailOtp(emailOtpGenerateRequest: EmailOtpGenerateRequest): Promise<EmailOtpGenerateResponse> {

const { email, ens, authId } = emailOtpGenerateRequest;
const { email, ens, chainId, authId } = emailOtpGenerateRequest;
const { otp, expiresAt } = this.generateOtp(email);

if (this.otpStore.has(authId)) {
Expand All @@ -85,6 +85,7 @@ EmailCredential

const encryptedState = this.encryptState({
ens,
chainId,
authId,
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { ChainId } from '../../../../../domain/entities/environment';

export interface EmailOtpGenerateRequest {
email: string;
authId: string;
ens: string;
chainId: ChainId
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ import {AbstractSocialResolver} from "./social-credential-resolver/social-resolv
import { CredentialCallbackRequest } from './credential.callback.request';
import { CredentialCallbackResponse } from './credential.callback.response';
import {EmailCallback} from "./email-resolver/email.callback";
import { ChainId } from '../../../domain/entities/environment';

export const CREDENTIAL_CREATOR_FACADE = 'CREDENTIAL_CREATOR_FACADE';

export interface ICredentialCreatorFacade {
getSocialResolver(credentialName: string): AbstractSocialResolver ;
getSocialAuthUrl(credentialName: string, ens: string, authId: string): Promise<string>;
getSocialAuthUrl(credentialName: string, ens: string, chainId: ChainId, authId: string): Promise<string>;
socialCallback(credentialCallbackRequest: CredentialCallbackRequest): Promise<CredentialCallbackResponse>;
getEmailOTP(email: string, ens: string, authId:string): Promise<string>;
getEmailOTP(email: string, ens: string, chainId: ChainId, authId:string): Promise<string>;
resendOtp(state: string): Promise<void>;
callbackEmailOTP(callbackData: EmailCallback ): Promise<CredentialCallbackResponse>;
clearState(state: string): string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { GithubCallback } from './callback/github.callback';
import { GithubToken } from './token/github.token';
import { GithubAuth } from './auth/github.auth';
import { VerifiableEthereumEip712Signature2021 } from '../../../../../domain/entities/ethereumEip712Signature';
import { ChainId } from '../../../../../domain/entities/environment';

export class GithubSocialResolver extends AbstractSocialResolver<
GithubCallback,
Expand All @@ -27,8 +28,8 @@ export class GithubSocialResolver extends AbstractSocialResolver<
return ['VerifiableGithubAccount'];
}

getAuthUrl({ens, authId}): string {
const stateObject = { ens, authId };
getAuthUrl({ens, chainId, authId}: {ens: string; chainId: ChainId; authId: string}): string {
const stateObject = { ens, chainId, authId };
const encryptedState = this.cryptoEncryption.encrypt(JSON.stringify(stateObject));
const clientId = this.environmentGetter.getGithubClientId();
const scope = 'read:user';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { ChainId } from '../../../../../../domain/entities/environment';

export class GetAuthUrlRequest {
ens: string;
chainId: ChainId;
authId: string;
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import {VerifiableEthereumEip712Signature2021} from "../../../domain/entities/ethereumEip712Signature";
import { ChainId } from '../../../domain/entities/environment';

export const CREDENTIAL_VERIFIER = 'CREDENTIAL_VERIFIER';

export interface ICredentialVerifier {
verifyCredential(verifiedEthereumEip712Signature2021: VerifiableEthereumEip712Signature2021): Promise<boolean>;
verifyCredential(verifiedEthereumEip712Signature2021: VerifiableEthereumEip712Signature2021, chainId: ChainId): Promise<boolean>;
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ChainId } from '../../../domain/entities/environment';

export const DID_RESOLVER = 'DID_RESOLVER'

export interface IDIDResolver {
getEnsDid(ens: string): Promise<string>
getEnsDid(ens: string, chainId: ChainId): Promise<string>
}
Loading

0 comments on commit 7c6701c

Please sign in to comment.