Skip to content

Commit

Permalink
feat: signin and demo app
Browse files Browse the repository at this point in the history
  • Loading branch information
HadiKhai committed Sep 16, 2024
1 parent ec0e346 commit ec9947c
Show file tree
Hide file tree
Showing 67 changed files with 6,575 additions and 325 deletions.
7 changes: 7 additions & 0 deletions apps/vc-api/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,10 @@ TELEGRAM_BOT_USERNAME=<YOUR_TELEGRAM_BOT_USERNAME>

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>
9 changes: 8 additions & 1 deletion apps/vc-api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@
"express": "4.20.0",
"moment": "^2.30.1",
"web-did-resolver": "^2.0.27",
"@nestjs/swagger": "^7.4.0"
"@nestjs/swagger": "^7.4.0",
"@veramo/did-manager": "^6.0.0",
"@veramo/key-manager": "^6.0.0",
"@veramo/kms-local": "^6.0.0",
"@veramo/did-provider-ethr": "^6.0.0",
"@veramo/did-resolver": "^6.0.0",
"@veramo/credential-w3c": "^6.0.0",
"@veramo/credential-ld": "^6.0.0"
}
}
113 changes: 76 additions & 37 deletions apps/vc-api/src/api/auth/auth.controller.ts
Original file line number Diff line number Diff line change
@@ -1,51 +1,90 @@
import { Controller, Get, Inject, Param, Query, Res } from '@nestjs/common';
import {
CREDENTIAL_CREATOR_FACADE,
ICredentialCreatorFacade
} from '../../core/applications/credentials/facade/icredential.facade';
import { Response } from 'express';
import { AuthCallbackApiResponse } from './auth.callback.response.api';
import { AUTH_CONTROLLER_MAPPER, IAuthControllerMapper } from './mapper/iauth.controller.mapper';
import { AuthGetAuthUrlRequestApiRequestParam } from './auth.get-auth-url.request.api';
import { Body, Controller, Get, Inject, Post, Req, Res, UseGuards } from '@nestjs/common';
import { JustaName } from '@justaname.id/sdk';
import { ENVIRONMENT_GETTER, IEnvironmentGetter } from '../../core/applications/environment/ienvironment.getter';
import { AuthSigninApiRequest } from './requests/auth.signin.api.request';
import { Response, Request} from 'express';
import { JwtService } from '@nestjs/jwt';
import moment from 'moment';
import { JwtGuard } from '../../guards/jwt.guard';

type Siwj = { address: string, subname: string };

@Controller('auth')
export class AuthController {

justaname: JustaName
constructor(
@Inject(CREDENTIAL_CREATOR_FACADE)
private readonly credentialCreatorFacade: ICredentialCreatorFacade,
@Inject(ENVIRONMENT_GETTER) private readonly environmentGetter: IEnvironmentGetter,

private readonly jwtService: JwtService
) {
this.justaname = JustaName.init({
config: {
chainId: this.environmentGetter.getChainId(),
domain: this.environmentGetter.getSiweDomain(),
origin:this.environmentGetter.getSiweOrigin(),
},
ensDomain: this.environmentGetter.getEnsDomain(),
providerUrl: 'https://sepolia.infura.io/v3/' +this.environmentGetter.getInfuraProjectId()
})
}

@Get('nonce')
async getNonce() {
return this.justaname.signIn.generateNonce()
}

@Post('signin')
async signInChallenge(
@Body() body: AuthSigninApiRequest,
@Res() res: Response,
@Req() req: Request
) {
const { data: message, subname } = await this.justaname.signIn.signIn(body.message, body.signature)

if (!message) {
res.status(500).json({ message: 'No message returned.' });
return;
}

if (!message.expirationTime) {
res.status(500).json({ message: 'No expirationTime returned.' });
return;
}


@Inject(AUTH_CONTROLLER_MAPPER)
private readonly authControllerMapper: IAuthControllerMapper
) {}
const token = this.jwtService.sign({ subname, address: message.address }, {
expiresIn: moment(message.expirationTime).diff(moment(), 'seconds')
});


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

return res.status(200).send({ subname, address: message.address });

@Get('')
async welcomeToJustaNameVerifications(): Promise<string[]> {
return ['Welcome to JustaName Verifications! Please use the /auth/:authName endpoint to get started.']
}

@Get(':authName')
async getAuthUrl(
@Param() authGetAuthUrlRequestApi: AuthGetAuthUrlRequestApiRequestParam,
@UseGuards(JwtGuard)
@Get('session')
async getSession(
@Req() req: Request & { user: Siwj },
@Res() res: Response
): Promise<void> {
const redirect = await this.credentialCreatorFacade.getAuthUrl(authGetAuthUrlRequestApi.authName)
if(redirect.startsWith('http')) {
res.redirect(redirect)
return
) {
if (!req.user) {
res.status(401).json({ message: 'You have to first sign_in' });
return;
}
res.send(redirect)
res.setHeader('Content-Type', 'text/plain');
res.status(200).send({ subname: req.user?.subname, address: req.user?.address });
}

@Get(':authName/callback')
async callback(
@Param() authGetAuthUrlRequestApiParam: AuthGetAuthUrlRequestApiRequestParam,
@Query() authGetAuthUrlRequestApiQuery: any,
): Promise<AuthCallbackApiResponse> {
const verifiedEthereumEip712Signature2021 = await this.credentialCreatorFacade.callback(
this.authControllerMapper.mapAuthCallbackApiRequestToCredentialCallbackRequest(
authGetAuthUrlRequestApiQuery,
authGetAuthUrlRequestApiParam)
)
return this.authControllerMapper.mapCredentialCallbackResponseToAuthCallbackApiResponse(verifiedEthereumEip712Signature2021)
@UseGuards(JwtGuard)
@Get('signout')
async signOut(
@Req() req: Request,
@Res() res: Response
) {
res.clearCookie('justanidtoken');
res.status(200).send({ message: 'You have been signed out' });
}
}
12 changes: 12 additions & 0 deletions apps/vc-api/src/api/auth/requests/auth.signin.api.request.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { IsString } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';

export class AuthSigninApiRequest {
@ApiProperty()
@IsString()
message: string;

@ApiProperty()
@IsString()
signature: string;
}
115 changes: 115 additions & 0 deletions apps/vc-api/src/api/credentials/credentials.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import { Controller, Get, Inject, Param, Query, Req, Res, Session, UseGuards } from '@nestjs/common';
import {
CREDENTIAL_CREATOR_FACADE,
ICredentialCreatorFacade
} from '../../core/applications/credentials/facade/icredential.facade';
import { Response } from 'express';
import { AUTH_CONTROLLER_MAPPER, IcredentialsControllerMapper } from './mapper/icredentials.controller.mapper';
import { CredentialsGetAuthUrlRequestApiRequestParam } from './credentials.get-auth-url.request.api';
import { v4 as uuidv4 } from 'uuid';
import { filter, Subject, take } from 'rxjs';
import { SubjectData } from './isubject.data';
import { JwtGuard } from '../../guards/jwt.guard';

type Siwj = { address: string, subname: string };

@Controller('credentials')
export class CredentialsController {

private authSubjects: Map<string, Subject<SubjectData>> = new Map();

constructor(
@Inject(CREDENTIAL_CREATOR_FACADE)
private readonly credentialCreatorFacade: ICredentialCreatorFacade,

@Inject(AUTH_CONTROLLER_MAPPER)
private readonly authControllerMapper: IcredentialsControllerMapper
) {}

@Get('')
async welcomeToJustaNameVerifications(): Promise<string[]> {
return ['Welcome to JustaName Verifications! Please use the /auth/:authName endpoint to get started.']
}

@UseGuards(JwtGuard)
@Get(':authName')
async getAuthUrl(
@Param() authGetAuthUrlRequestApi: CredentialsGetAuthUrlRequestApiRequestParam,
@Res() res: Response,
@Req() req: Request & { user: Siwj }
): Promise<any> {

const authId = uuidv4();
const subject = new Subject<SubjectData>();
this.authSubjects.set(authId, subject);

const redirectUrl = await this.credentialCreatorFacade.getAuthUrl(
authGetAuthUrlRequestApi.authName,
req.user?.subname,
authId
)


res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
});

res.write(`data: ${JSON.stringify({ redirectUrl })}\n\n`);

subject.pipe(
filter(data => data.authId === authId),
take(1)
).subscribe(
(data) => {
res.write(`data: ${JSON.stringify(data.result)}\n\n`);
res.end();
this.authSubjects.delete(authId);
},
(error) => {
res.write(`data: ${JSON.stringify({ error: error.message })}\n\n`);
res.end();
this.authSubjects.delete(authId);
}
);
}

@Get(':authName/callback')
async callback(
@Param() authGetAuthUrlRequestApiParam: CredentialsGetAuthUrlRequestApiRequestParam,
@Query() authGetAuthUrlRequestApiQuery: any,
@Res() res: Response
): Promise<void> {
const verifiedEthereumEip712Signature2021 = await this.credentialCreatorFacade.callback(
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>
<body>
<script>
window.close();
// In case window.close() doesn't work (e.g., in some browsers where the window wasn't opened by script)
document.body.innerHTML = "Authentication successful. You can close this tab.";
</script>
</body>
</html>
`);

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ enum AuthName {
Telegram = 'telegram',
}

export class AuthGetAuthUrlRequestApiRequestParam
export class CredentialsGetAuthUrlRequestApiRequestParam
{
@IsNotEmpty()
@IsEnum(AuthName)
authName: AuthName
}

export class AuthGetAuthUrlApiRequestQuery
export class CredentialsGetAuthUrlApiRequestQuery
{
@IsOptional()
@IsString()
Expand Down
9 changes: 9 additions & 0 deletions apps/vc-api/src/api/credentials/isubject.data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { VerifiedEthereumEip712Signature2021 } from '../../core/domain/entities/eip712';

export interface SubjectData {
authId: string;
result: {
verifiableCredential: VerifiedEthereumEip712Signature2021;
dataKey: string;
};
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { Injectable } from '@nestjs/common';
import { IAuthControllerMapper } from './iauth.controller.mapper';
import { AuthCallbackApiResponse } from '../auth.callback.response.api';
import { IcredentialsControllerMapper } from './icredentials.controller.mapper';
import { AuthCallbackApiResponse } from '../credentials.callback.response.api';
import { CredentialCallbackResponse } from '../../../core/applications/credentials/facade/credential.callback.response';
import { AuthGetAuthUrlApiRequestQuery, AuthGetAuthUrlRequestApiRequestParam } from '../auth.get-auth-url.request.api';
import { CredentialsGetAuthUrlApiRequestQuery, CredentialsGetAuthUrlRequestApiRequestParam } from '../credentials.get-auth-url.request.api';
import { CredentialCallbackRequest } from '../../../core/applications/credentials/facade/credential.callback.request';

@Injectable()
export class AuthControllerMapper implements IAuthControllerMapper {
export class CredentialsControllerMapper implements IcredentialsControllerMapper {

constructor() {}

Expand All @@ -28,8 +28,8 @@ export class AuthControllerMapper implements IAuthControllerMapper {
}

mapAuthCallbackApiRequestToCredentialCallbackRequest(
authCallbackApiRequestQuery: AuthGetAuthUrlApiRequestQuery,
authCallbackApiRequestParams: AuthGetAuthUrlRequestApiRequestParam
authCallbackApiRequestQuery: CredentialsGetAuthUrlApiRequestQuery,
authCallbackApiRequestParams: CredentialsGetAuthUrlRequestApiRequestParam
): CredentialCallbackRequest {
return {
credentialName: authCallbackApiRequestParams.authName,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import { AuthCallbackApiResponse } from '../auth.callback.response.api';
import { AuthCallbackApiResponse } from '../credentials.callback.response.api';
import { CredentialCallbackResponse } from '../../../core/applications/credentials/facade/credential.callback.response';
import { AuthGetAuthUrlApiRequestQuery, AuthGetAuthUrlRequestApiRequestParam } from '../auth.get-auth-url.request.api';
import { CredentialsGetAuthUrlApiRequestQuery, CredentialsGetAuthUrlRequestApiRequestParam } from '../credentials.get-auth-url.request.api';
import { CredentialCallbackRequest } from '../../../core/applications/credentials/facade/credential.callback.request';

export const AUTH_CONTROLLER_MAPPER = 'AUTH_CONTROLLER_MAPPER';

export interface IAuthControllerMapper {
export interface IcredentialsControllerMapper {
mapCredentialCallbackResponseToAuthCallbackApiResponse(
credentialCallbackResponse: CredentialCallbackResponse,
): AuthCallbackApiResponse;

mapAuthCallbackApiRequestToCredentialCallbackRequest(
authCallbackApiRequestQuery: AuthGetAuthUrlApiRequestQuery,
authCallbackApiRequestParams: AuthGetAuthUrlRequestApiRequestParam
authCallbackApiRequestQuery: CredentialsGetAuthUrlApiRequestQuery,
authCallbackApiRequestParams: CredentialsGetAuthUrlRequestApiRequestParam
): CredentialCallbackRequest;
}
16 changes: 16 additions & 0 deletions apps/vc-api/src/config/env.validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
} from 'class-validator';
import { ethers } from 'ethers';
import { ChainId, Environment, EnvironmentType, SupportedChainIds } from '../core/domain/entities/environment';
import { x } from 'vitest/dist/reporters-yx5ZTtEV';

class EnvironmentVariables implements Environment{
@IsString({ message: 'SIGNING_PRIVATE_KEY must be a string' })
Expand Down Expand Up @@ -65,6 +66,21 @@ class EnvironmentVariables implements Environment{

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

@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;

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

function IsEthereumPrivateKey(validationOptions?: ValidationOptions) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ import { VerifiedEthereumEip712Signature2021 } from '../../../domain/entities/ei
export class CredentialCallbackResponse {
dataKey: string;
verifiableCredential: VerifiedEthereumEip712Signature2021;
authId: string;
}
Loading

0 comments on commit ec9947c

Please sign in to comment.