Skip to content

Commit

Permalink
refactor: remove connect module
Browse files Browse the repository at this point in the history
- fixes up tests
- removes lint errors
  • Loading branch information
PhearZero committed Apr 24, 2024
1 parent aef8726 commit b2e10be
Show file tree
Hide file tree
Showing 23 changed files with 331 additions and 402 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,10 @@ class MainActivity : AppCompatActivity() {
options.put("username", account.address.toString())
options.put("displayName", "Liquid Auth User")
options.put("authenticatorSelection", JSONObject().put("userVerification", "required"))
val extensions = JSONObject()
extensions.put("liquid", true)
options.put("extensions", extensions)

// FIDO2 Server API Response for PublicKeyCredentialCreationOptions
val response = attestationApi.postAttestationOptions(msg.origin, userAgent, options).await()
val session = Cookie.fromResponse(response)
Expand Down Expand Up @@ -386,7 +390,7 @@ class MainActivity : AppCompatActivity() {
}
val msg = viewModel.message.value!!
val keyPair = KeyPairs.getKeyPair(viewModel.account.value!!.toMnemonic())
// Connect to the service and if the message is unsigned, pass in a keypair
// Connect to the service then handle state changes and messages
connectApi.connect(application, msg, {
Log.d(TAG, "onStateChange($it)")
if(it === "OPEN"){
Expand Down
33 changes: 25 additions & 8 deletions clients/liquid-auth-client-js/src/signal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ export class SignalClient extends EventEmitter {
throw new Error(REQUEST_IN_PROCESS_MESSAGE);

return new Promise(async (resolve) => {
let candidatesBuffer = [];
// Create Peer Connection
this.peerClient = new RTCPeerConnection(config);
globalThis.peerClient = this.peerClient;
Expand All @@ -174,24 +175,28 @@ export class SignalClient extends EventEmitter {
// Listen for Local Candidates
this.peerClient.onicecandidate = (event) => {
if (event.candidate) {
console.log(event.candidate);
this.emit(`${this.type}-candidate`, event.candidate.toJSON());
this.socket.emit(`${this.type}-candidate`, event.candidate.toJSON());
}
};
// Listen to Remote Candidates
this.socket.on(
`${type}-candidate`,
async (candidate: RTCIceCandidateInit) => {
await this.peerClient.addIceCandidate(new RTCIceCandidate(candidate));
if (
this.peerClient.remoteDescription &&
this.peerClient.remoteDescription
) {
this.emit(`${type}-candidate`, candidate);
await this.peerClient.addIceCandidate(
new RTCIceCandidate(candidate),
);
} else {
candidatesBuffer.push(candidate);
}
},
);

this.peerClient.onicecandidate = (event) => {
if (event.candidate) {
this.socket.emit(`${this.type}-candidate`, event.candidate.toJSON());
}
};

// Listen for Remote DataChannel and Resolve
this.peerClient.ondatachannel = (event) => {
console.log(event);
Expand All @@ -205,6 +210,18 @@ export class SignalClient extends EventEmitter {
await this.peerClient.setRemoteDescription(sdp);
const answer = await this.peerClient.createAnswer();
await this.peerClient.setLocalDescription(answer);
if (candidatesBuffer.length > 0) {
await Promise.all(
candidatesBuffer.map(async (candidate) => {
this.emit(`${type}-candidate`, candidate);
await this.peerClient.addIceCandidate(
new RTCIceCandidate(candidate),
);
}),
);
candidatesBuffer = [];
}
this.emit(`${this.type}-description`, answer.sdp);
this.socket.emit(`${this.type}-description`, answer.sdp);
} else {
const localSdp = await this.peerClient.createOffer();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ class AuthMessage @Inject constructor(
*
* uses JSON for serialization
* @todo: Use a URL for the serialization to allow for deep-links
* @note: Suggest liquid://{ORIGIN}/{REQUEST_ID}/
*/
fun fromBarcode(barcode: Barcode): AuthMessage {
Log.d(TAG, "fromBarcode(${barcode.displayValue})")
Expand All @@ -25,7 +26,6 @@ class AuthMessage @Inject constructor(
return AuthMessage(origin, requestId)
}
}

fun toJSON() : JSONObject {
val result = JSONObject()
result.put("origin", origin)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,13 @@ class AssertionApi @Inject constructor(
fun postAssertionOptions(
origin: String,
userAgent: String,
credentialId: String
credentialId: String,
liquidExt: Boolean? = true
): Call {
val payload = JSONObject()
if(liquidExt == true) {
payload.put("extensions", liquidExt)
}
val path = "$origin/assertion/request/$credentialId"
val requestBuilder = Request.Builder()
.url(path)
Expand Down
4 changes: 1 addition & 3 deletions services/liquid-auth-api-js/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ import { AndroidController } from './android/android.controller.js';
// User Endpoints
import { AuthModule } from './auth/auth.module.js';

// Connect/Signals
import { ConnectModule } from './connect/connect.module.js';
// Signals
import { SignalsModule } from './signals/signals.module.js';

@Module({
Expand All @@ -37,7 +36,6 @@ import { SignalsModule } from './signals/signals.module.js';
AuthModule,
AttestationModule,
AssertionModule,
ConnectModule,
SignalsModule,
],
controllers: [AndroidController],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@ import {
NotFoundException,
UnauthorizedException,
} from '@nestjs/common';
import { AssertionCredentialJSON, PublicKeyCredentialRequestOptions, LiquidAssertionCredentialJSON } from "./assertion.dto.js";
import {
PublicKeyCredentialRequestOptions,
LiquidAssertionCredentialJSON,
} from './assertion.dto.js';

// PublicKeyCredentialRequestOptions
const dummyPublicKeyCredentialRequestOptions = {
Expand Down Expand Up @@ -84,7 +87,6 @@ describe('AssertionController', () => {
authService.search = jest.fn().mockResolvedValue(undefined);
});


describe('Post /request/:credId', () => {
it('(FAIL) should fail if it cannot find a user', async () => {
const session = new Session();
Expand Down
2 changes: 1 addition & 1 deletion services/liquid-auth-api-js/src/assertion/assertion.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export class AssertionCredentialJSON implements AssertionCredentialJSONType {
}
export type LiquidAssertionCredentialJSON = AssertionCredentialJSON & {
clientExtensionResults: { liquid: { requestId: string } };
}
};

/**
* JSON representation of PublicKeyCredentialRequestOptions
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[
{
"username": "B7WYCZ6HRBGCH452D24TYAK7BXKNCHEXY2X7S7FWZXMHDVTDOARAOURJEU",
"displayName": "Test Wallet",
"authenticatorSelection": "AuthenticatorSelectionCriteria",
"attestationType": "AttestationConveyancePreference",
"extensions": "AttestationExtension"
}
]
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,11 @@ import { mockAttestationService } from '../__mocks__/attestation.service.mock.js
import { AppService } from '../app.service.js';
import { ConfigService } from '@nestjs/config';
import { AttestationService } from './attestation.service.js';
import { NotFoundException } from '@nestjs/common';
import {
ForbiddenException,
NotFoundException,
UnauthorizedException,
} from '@nestjs/common';
import { AttestationCredentialJSONDto, AttestationSelectorDto } from "./attestation.dto.js";
AttestationCredentialJSONDto,
AttestationSelectorDto,
} from './attestation.dto.js';

const dummyAttestationSelectorDto = {
authenticatorSelection: {},
Expand All @@ -40,7 +39,6 @@ const dummyAttestationCredentialJSON = {

describe('AttestationController', () => {
let attestationController: AttestationController;
let authService: AuthService;
let userModel: Model<User>;

beforeEach(async () => {
Expand Down Expand Up @@ -70,7 +68,6 @@ describe('AttestationController', () => {
],
}).compile();

authService = moduleRef.get<AuthService>(AuthService);
attestationController = moduleRef.get<AttestationController>(
AttestationController,
);
Expand All @@ -86,15 +83,10 @@ describe('AttestationController', () => {
session.wallet = accFixture.accs[0].addr;

const body = dummyAttestationSelectorDto;
const req = {
headers: {
host: 'meh',
},
} as Request;

await expect(
attestationController.request(session, body),
).resolves.toBe(dummyAttestationOptions);
await expect(attestationController.request(session, body)).resolves.toBe(
dummyAttestationOptions,
);
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,16 @@ import {
Req,
Session,
} from '@nestjs/common';
import type { AttestationCredentialJSON } from '@simplewebauthn/typescript-types';
import type { Request } from 'express';

import { AuthService } from '../auth/auth.service.js';

import { AttestationService } from './attestation.service.js';
import {
AttestationCredentialJSONDto,
AttestationExtension,
AttestationSelectorDto
} from "./attestation.dto.js";
AttestationSelectorDto,
} from './attestation.dto.js';
import { ClientProxy } from '@nestjs/microservices';
import { fromBase64Url } from '@liquid/core';
import { ApiOperation, ApiTags } from '@nestjs/swagger';

@Controller('attestation')
Expand All @@ -42,16 +39,19 @@ export class AttestationController {
*
* @param session - Express Session
* @param options - Attestation Selector DTO
* @param req - Express Request
*/
@Post('/request')
@ApiOperation({ summary: 'Attestation Request' })
async request(
@Session() session: Record<string, any>,
@Body() options: AttestationSelectorDto,
) {
// Force unauthenticated users to prove they own a private key
if (options.username !== session.wallet) {
this.logger.debug(options);
// Enable the liquid extension if the username is different or the liquid extension is enabled
if (
options.username !== session.wallet ||
options?.extensions?.liquid === true
) {
session.liquidExtension = options.username;
}

Expand Down Expand Up @@ -85,6 +85,15 @@ export class AttestationController {
error: 'Challenge not found',
});
}
if (
typeof session.liquidExtension !== 'undefined' &&
typeof body?.clientExtensionResults?.liquid === 'undefined'
) {
throw new NotFoundException({
reason: 'not_found',
error: 'Liquid extension not found',
});
}
this.logger.debug(
`Username: ${username} Challenge: ${expectedChallenge}`,
);
Expand All @@ -102,7 +111,9 @@ export class AttestationController {
session.wallet = username;
const { wallet } = user;
const credId = credential.credId;
if(typeof body?.clientExtensionResults?.liquid?.requestId === 'string') {
if (
typeof body?.clientExtensionResults?.liquid?.requestId !== 'undefined'
) {
this.client.emit<string>('auth', {
requestId: body.clientExtensionResults.liquid.requestId,
wallet,
Expand Down
20 changes: 13 additions & 7 deletions services/liquid-auth-api-js/src/attestation/attestation.dto.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,27 @@
import type { AttestationCredentialJSON } from "@simplewebauthn/typescript-types";
import type { AttestationCredentialJSON } from '@simplewebauthn/typescript-types';

export type AttestationSelectorDto = {
username: string;
displayName: string;
authenticatorSelection: AuthenticatorSelectionCriteria;
attestationType?: AttestationConveyancePreference;
extensions?: AttestationExtension;
extensions?: LiquidAttestationExtensionsClientInput;
};
export type AttestationCredentialJSONDto = AttestationCredentialJSON & {
clientExtensionResults: AttestationExtension;
}
export type AttestationExtension = AuthenticationExtensionsClientInputs & {
clientExtensionResults: LiquidAuthClientExtensionResults;
};

export type LiquidAuthClientExtensionResults = {
liquid: {
type: string;
type: 'algorand';
signature: string;
address: string;
requestId: number;

device?: string;
requestId?: string;
};
};
export type LiquidAttestationExtensionsClientInput =
AuthenticationExtensionsClientInputs & {
liquid: boolean;
};
21 changes: 12 additions & 9 deletions services/liquid-auth-api-js/src/auth/auth.controller.spec.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
import { Test, TestingModule } from '@nestjs/testing';
import { AuthController } from './auth.controller.js';
import { AuthService } from './auth.service.js';
import { Session } from "./session.schema.js";
import { Session } from './session.schema.js';
import mongoose, { Error, Model } from 'mongoose';
import { User, UserSchema } from './auth.schema.js';
import { getModelToken } from '@nestjs/mongoose';
import { Request, Response } from 'express';
import { Response } from 'express';
import { dummyUsers } from '../../tests/constants.js';
import { mockAuthService } from '../__mocks__/auth.service.mock.js';
//@ts-ignore, ignore for tests
import sessionFixtures from '../__fixtures__/session.fixtures.json' assert {type: 'json'}
import sessionFixtures from '../__fixtures__/session.fixtures.json' assert { type: 'json' };
import {
BadRequestException,
InternalServerErrorException,
NotFoundException,
} from '@nestjs/common';
Expand Down Expand Up @@ -78,7 +77,7 @@ describe('AuthController', () => {
it('(FAIL) should fail if it cannot find the user', async () => {
authService.find = jest.fn().mockResolvedValue(undefined);

const session = {} as Record<string, any>;
const session = {} as Record<string, any>;
await expect(authController.remove(session, `1`)).rejects.toThrow(
NotFoundException,
);
Expand All @@ -101,7 +100,6 @@ describe('AuthController', () => {
.mockRejectedValue(new Error('failed to update user'));

const session = new Session();
const req = { body: {}, params: { id: 1 } } as any as Request;

await expect(authController.remove(session, `1`)).rejects.toThrow(
InternalServerErrorException,
Expand All @@ -125,13 +123,18 @@ describe('AuthController', () => {
describe('Get /session', () => {
it('(OK) should fetch a session', async () => {
const dummyUser = dummyUsers[0];
const user = await authController.read(sessionFixtures.authorized)
await expect(user).toStrictEqual({user: dummyUser, session: sessionFixtures.authorized});
const user = await authController.read(sessionFixtures.authorized);
await expect(user).toStrictEqual({
user: dummyUser,
session: sessionFixtures.authorized,
});
});

it('(OK) should return an empty object if the user is not found', async () => {
authService.find = jest.fn().mockResolvedValue(null);
await expect(authController.read(sessionFixtures.authorized)).resolves.toEqual({session: sessionFixtures.authorized, user: null});
await expect(
authController.read(sessionFixtures.authorized),
).resolves.toEqual({ session: sessionFixtures.authorized, user: null });
});
});
});
Loading

0 comments on commit b2e10be

Please sign in to comment.