Skip to content

Commit

Permalink
Merge pull request #51 from JustaName-id/develop
Browse files Browse the repository at this point in the history
feat: nonce and auth
  • Loading branch information
Ghadi8 authored Oct 21, 2024
2 parents 74748bb + 6081027 commit f65e617
Show file tree
Hide file tree
Showing 11 changed files with 239 additions and 43 deletions.
4 changes: 1 addition & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,7 @@ The application will be available at http://localhost:3000/verifications/v1.
**Response**:

```json
{
"nonce": "1234567890abcdef"
}
"1234567890abcdef"
```
#### Sign In

Expand Down
43 changes: 30 additions & 13 deletions apps/vc-api/src/api/auth/auth.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ 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';
import {JwtNonceGuard} from "../../guards/jwt-nonce.guard";

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

Expand All @@ -19,28 +20,43 @@ export class AuthController {
) {}

@Get('nonce')
async getNonce() {
return this.ensManagerService.generateNonce()
async getNonce(
@Res() res: Response,
@Req() req: Request
) {
const nonce = this.ensManagerService.generateNonce()
const token = this.jwtService.sign({ nonce }, {
expiresIn: 3600
});
res.cookie('justverifiednonce', token, { httpOnly: true, secure: true, sameSite: 'none' });
res.status(200).send( nonce );
}

@UseGuards(JwtNonceGuard)
@Post('signin')
async signInChallenge(
@Body() body: AuthSigninApiRequest,
@Res() res: Response,
@Req() req: Request
@Req() req: Request & { nonce: { nonce: string } }
) {
const { address, ens, chainId } = await this.ensManagerService.signIn({
message: body.message,
signature: body.signature
})
res.clearCookie('justverifiednonce', { httpOnly: true, secure: true, sameSite: 'none' });
try {
const {address, ens, chainId} = await this.ensManagerService.signIn({
message: body.message,
signature: body.signature,
nonce: req.nonce.nonce,
})

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

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

return res.status(200).send({ ens, address, chainId });
return res.status(200).send({ens, address, chainId});
}catch (e) {
res.status(422).send({message: e.message});
}

}

Expand All @@ -64,7 +80,8 @@ export class AuthController {
@Req() req: Request,
@Res() res: Response
) {
res.clearCookie('justverifiedtoken');
res.clearCookie('justverifiedtoken', { httpOnly: true, secure: true, sameSite: 'none' });
res.clearCookie('justverifiednonce', { httpOnly: true, secure: true, sameSite: 'none' });
res.status(200).send({ message: 'You have been signed out' });
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export class SignInRequest {
message: string
signature: string
nonce?: string
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ import { Subname } from '../../domain/entities/subname';
export const SUBNAME_RECORDS_FETCHER = 'SUBNAME_RECORDS_FETCHER';

export interface ISubnameRecordsFetcher {
fetchRecords(subname: string, chainId: number): Promise<Subname>;
fetchRecords(subname: string, chainId: number, texts?: string[]): Promise<Subname>;
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export class VerifyRecordsService implements IVerifyRecordsService {
throw ChainIdInvalidException.withId(chainId);
}

const subnameRecords = await this.subnameRecordsFetcher.fetchRecords(ens, chainId);
const subnameRecords = await this.subnameRecordsFetcher.fetchRecords(ens, chainId, [...credentials, ...credentials.map((record) => `${record}_${validIssuer}`)]);

let responses: VerifyRecordsResponse = {}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,9 @@ export class JustaNameInitializerService implements IEnsManagerService {
try {
const sign = await this.justaname.signIn.signIn({
message: params.message,
signature: params.signature
}
);
signature: params.signature,
nonce: params.nonce
});

return {
address: sign.data.address,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import { ENVIRONMENT_GETTER, IEnvironmentGetter } from '../../core/applications/
import { ENS_MANAGER_SERVICE, IEnsManagerService } from '../../core/applications/ens-manager/iens-manager.service';
import { ChainId } from '../../core/domain/entities/environment';
import { GetRecordsResponse } from '../../core/applications/ens-manager/responses/get-records.response';
import { getRecords } from '@ensdomains/ensjs/public';
import {createClient, http} from "viem";
import {mainnet, sepolia} from "viem/chains";

@Injectable()
export class SubnameRecordsFetcher implements ISubnameRecordsFetcher {
Expand All @@ -13,12 +16,35 @@ export class SubnameRecordsFetcher implements ISubnameRecordsFetcher {
@Inject(ENS_MANAGER_SERVICE) private readonly ensManagerService: IEnsManagerService
) {}

async fetchRecords(subname: string, chainId: ChainId): Promise<Subname> {
async fetchRecords(subname: string, chainId: ChainId, texts?: string[]): Promise<Subname> {

const records = await this.ensManagerService.getRecords({
ens: subname,
chainId: chainId,
})
let records: GetRecordsResponse;
const providerUrl = (chainId === 1 ? 'https://mainnet.infura.io/v3/' :'https://sepolia.infura.io/v3/') + this.environmentGetter.getInfuraProjectId()

if(texts) {
const client = createClient({
chain: chainId === 1 ? mainnet : sepolia,
transport: http(providerUrl)
});

const recordsFromEns = await getRecords(client,{
name: subname,
texts: texts,
coins: ['eth'],
contentHash: true
});

records = {
...recordsFromEns,
isJAN: false
}
}
else{
records = await this.ensManagerService.getRecords({
ens: subname,
chainId: chainId,
})
}

return this.mapSubnameRecordsResponseToSubname(subname, records);
}
Expand Down
33 changes: 33 additions & 0 deletions apps/vc-api/src/guards/jwt-nonce.guard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { Reflector } from '@nestjs/core';
import { Request } from 'express';

@Injectable()
export class JwtNonceGuard implements CanActivate {
constructor(
private jwtService: JwtService,
private reflector: Reflector,
) {}

async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
const nonce = this.extractTokenFromHeader(request);
if (!nonce) {
throw new UnauthorizedException();
}
try {
const payload = await this.jwtService.verifyAsync(nonce, {
secret: process.env.JWT_SECRET,
});
request['nonce'] = payload;
} catch {
throw new UnauthorizedException();
}
return true;
}

private extractTokenFromHeader(request: Request): string | undefined {
return request.headers.cookie?.split(';').find((cookie) => cookie.trim().startsWith('justverifiednonce='))?.split('=')[1];
}
}
12 changes: 0 additions & 12 deletions apps/vc-api/src/guards/session.guard.ts

This file was deleted.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"license": "MIT",
"private": true,
"dependencies": {
"@ensdomains/ensjs": "^4.0.1-alpha.0",
"@justaname.id/sdk": "0.2.98",
"@nestjs/common": "^10.0.2",
"@nestjs/config": "^3.2.3",
Expand Down
Loading

0 comments on commit f65e617

Please sign in to comment.