diff --git a/clients/liquid-auth-client-js/tests/connect.test.js b/clients/liquid-auth-client-js/tests/connect.spec.js similarity index 100% rename from clients/liquid-auth-client-js/tests/connect.test.js rename to clients/liquid-auth-client-js/tests/connect.spec.js diff --git a/clients/liquid-auth-client-js/tests/index.test.js b/clients/liquid-auth-client-js/tests/index.spec.js similarity index 100% rename from clients/liquid-auth-client-js/tests/index.test.js rename to clients/liquid-auth-client-js/tests/index.spec.js diff --git a/package-lock.json b/package-lock.json index 16808ef..e0e09d6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4346,7 +4346,8 @@ }, "node_modules/algosdk": { "version": "2.7.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/algosdk/-/algosdk-2.7.0.tgz", + "integrity": "sha512-sBE9lpV7bup3rZ+q2j3JQaFAE9JwZvjWKX00vPlG8e9txctXbgLL56jZhSWZndqhDI9oI+0P4NldkuQIWdrUyg==", "dependencies": { "algo-msgpack-with-bigint": "^2.1.1", "buffer": "^6.0.3", @@ -12984,7 +12985,7 @@ "@types/ua-parser-js": "^0.7.37", "@typescript-eslint/eslint-plugin": "^6.7.3", "@typescript-eslint/parser": "^6.7.3", - "algosdk": "^2.6.0", + "algosdk": "^2.7.0", "cross-env": "^7.0.3", "eslint": "^8.50.0", "eslint-config-prettier": "^9.0.0", diff --git a/services/liquid-auth-api-js/package.json b/services/liquid-auth-api-js/package.json index 1511352..3a43478 100644 --- a/services/liquid-auth-api-js/package.json +++ b/services/liquid-auth-api-js/package.json @@ -62,7 +62,7 @@ "@types/ua-parser-js": "^0.7.37", "@typescript-eslint/eslint-plugin": "^6.7.3", "@typescript-eslint/parser": "^6.7.3", - "algosdk": "^2.6.0", + "algosdk": "^2.7.0", "cross-env": "^7.0.3", "eslint": "^8.50.0", "eslint-config-prettier": "^9.0.0", diff --git a/services/liquid-auth-api-js/src/algod/algod.service.ts b/services/liquid-auth-api-js/src/algod/algod.service.ts new file mode 100644 index 0000000..34922e7 --- /dev/null +++ b/services/liquid-auth-api-js/src/algod/algod.service.ts @@ -0,0 +1,14 @@ +import algosdk from 'algosdk'; +import { Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; + +@Injectable() +export class AlgodService extends algosdk.Algodv2 { + constructor(private configService: ConfigService) { + const token = configService.get('algod.token') || ''; + const server = configService.get('algod.server'); + const port = configService.get('algod.port'); + super(token, server, port); + } +} + diff --git a/services/liquid-auth-api-js/src/config/configuration.ts b/services/liquid-auth-api-js/src/config/configuration.ts index b2f6171..f73c327 100644 --- a/services/liquid-auth-api-js/src/config/configuration.ts +++ b/services/liquid-auth-api-js/src/config/configuration.ts @@ -15,6 +15,11 @@ export default () => ({ username: process.env.DB_USERNAME || 'algorand', password: process.env.DB_PASSWORD || 'algorand', name: process.env.DB_NAME || 'fido', - atlas: process.env.DB_ATLAS === 'true' + atlas: process.env.DB_ATLAS === 'true', + }, + algod: { + token: process.env.ALGOD_TOKEN || '', + server: process.env.ALGOD_SERVER || 'https://testnet-api.algonode.cloud', + port: process.env.ALGOD_PORT || '443', }, }); diff --git a/services/liquid-auth-api-js/src/connect/connect.controller.ts b/services/liquid-auth-api-js/src/connect/connect.controller.ts index 1c597ce..a058947 100644 --- a/services/liquid-auth-api-js/src/connect/connect.controller.ts +++ b/services/liquid-auth-api-js/src/connect/connect.controller.ts @@ -5,12 +5,14 @@ import { Session, Inject, Logger, - Res, + HttpException, + HttpStatus, } from '@nestjs/common'; import { ClientProxy } from '@nestjs/microservices'; -import { Response } from 'express'; import { AuthService } from '../auth/auth.service.js'; +import { AlgodService } from '../algod/algod.service.js'; import { AlgorandEncoder } from './AlgoEncoder.js'; +import * as nacl from 'tweetnacl'; const algoEncoder = new AlgorandEncoder(); @@ -21,7 +23,6 @@ const base64ToUint8Array = (encoded) => { .map((c) => c.charCodeAt(0)), ); }; -import nacl from 'tweetnacl'; type LinkResponseDTO = { credId?: string; @@ -37,6 +38,7 @@ export class ConnectController { constructor( private authService: AuthService, + private algodService: AlgodService, @Inject('ACCOUNT_LINK_SERVICE') private client: ClientProxy, ) {} @@ -51,57 +53,87 @@ export class ConnectController { */ @Post('response') async linkWalletResponse( - @Res() res: Response, @Session() session: Record, @Body() { requestId, wallet, challenge, signature, credId }: LinkResponseDTO, ) { - try { - this.logger.log( - `POST /connect/response for RequestId: ${requestId} Session: ${session.id} with Wallet: ${wallet}`, - ); - // Decode Address - const publicKey = algoEncoder.decodeAddress(wallet); + this.logger.log( + `POST /connect/response for RequestId: ${requestId} Session: ${session.id} with Wallet: ${wallet}`, + ); - // Decode signature - const uint8Signature = base64ToUint8Array( - signature.replace(/-/g, '+').replace(/_/g, '/').replace(/\s/g, ''), - ); + // Decode Address + const publicKey = algoEncoder.decodeAddress(wallet); + + // Decode signature + const uint8Signature = base64ToUint8Array( + signature.replace(/-/g, '+').replace(/_/g, '/').replace(/\s/g, ''), + ); + + // Validate Signature + const encoder = new TextEncoder(); + const encodedChallenge = encoder.encode(challenge); + + if ( + !nacl.sign.detached.verify(encodedChallenge, uint8Signature, publicKey) + ) { + // signature check failed, check if its rekeyed + // if it is, verify against that public key instead + let accountInfo; + try { + accountInfo = await this.algodService + .accountInformation(wallet) + .exclude('all') + .do(); + } catch (e) { + throw new HttpException( + 'Failed to fetch Account Info', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + + if (!accountInfo['auth-addr']) { + throw new HttpException('Invalid signature', HttpStatus.FORBIDDEN); + } + + const authPublicKey = algoEncoder.decodeAddress(accountInfo['auth-addr']); - // Validate Signature - const encoder = new TextEncoder(); + // Validate Auth Address Signature if ( !nacl.sign.detached.verify( - encoder.encode(challenge), + encodedChallenge, uint8Signature, - publicKey, + authPublicKey, ) ) { - return res - .status(401) - .json({ - error: 'Invalid signature', - }) - .end(); - } else { - this.logger.log('AUTH Wallet is attested'); - // Authenticated user - await this.authService.init(wallet); - - const parsedRequest = - typeof requestId === 'string' ? parseFloat(requestId) : requestId; - console.log('Request Forwarding', parsedRequest); - session.wallet = wallet; - session.active = true; - this.client.emit('auth', { - requestId, - wallet, - credId, - }); - return res.status(200).end(); + throw new HttpException('Invalid signature', HttpStatus.FORBIDDEN); } + } + + this.logger.log('AUTH Wallet is attested'); + // Authenticated user + try { + await this.authService.init(wallet); } catch (e) { - res.status(500).json({ error: e.message }); + throw new HttpException( + 'Failed to initialize wallet', + HttpStatus.INTERNAL_SERVER_ERROR, + ); } + + const parsedRequest = + typeof requestId === 'string' ? parseFloat(requestId) : requestId; + + console.log('Request Forwarding', parsedRequest); + + session.wallet = wallet; + session.active = true; + + this.client.emit('auth', { + requestId, + wallet, + credId, + }); + + return; } } diff --git a/services/liquid-auth-api-js/src/connect/connect.module.ts b/services/liquid-auth-api-js/src/connect/connect.module.ts index 93c195d..1aca4d2 100644 --- a/services/liquid-auth-api-js/src/connect/connect.module.ts +++ b/services/liquid-auth-api-js/src/connect/connect.module.ts @@ -7,6 +7,7 @@ import { Session, SessionSchema } from './session.schema.js'; import { SessionService } from './session.service.js'; import { AuthService } from '../auth/auth.service.js'; import { User, UserSchema } from '../auth/auth.schema.js'; +import { AlgodService } from '../algod/algod.service.js'; @Module({ imports: [ @@ -29,6 +30,6 @@ import { User, UserSchema } from '../auth/auth.schema.js'; ]), ], controllers: [ConnectController], - providers: [AuthService, SessionService, ConnectGateway], + providers: [AuthService, SessionService, ConnectGateway, AlgodService], }) export class ConnectModule {} diff --git a/sites/dapp-ui/src/pages/home/ConnectModal.tsx b/sites/dapp-ui/src/pages/home/ConnectModal.tsx index ad582e3..5773ef6 100644 --- a/sites/dapp-ui/src/pages/home/ConnectModal.tsx +++ b/sites/dapp-ui/src/pages/home/ConnectModal.tsx @@ -11,6 +11,7 @@ import {useSocket} from '../../hooks/useSocket'; import nacl from 'tweetnacl'; import { StateContext } from '../../Contexts'; import { useCredentialStore, Credential } from '../../store'; + const style = { position: 'absolute' as const, top: '50%', @@ -21,6 +22,7 @@ const style = { border: '2px solid #000', boxShadow: 24, }; + export function ConnectModal({color}: {color?: 'inherit' | 'primary' | 'secondary' | 'success' | 'error' | 'info' | 'warning'}) { const {socket} = useSocket(); const credentials = useCredentialStore((state)=> state.addresses); @@ -119,6 +121,7 @@ export function ConnectModal({color}: {color?: 'inherit' | 'primary' | 'secondar aria-labelledby="modal-modal-title" aria-describedby="modal-modal-description" > + + + + );