Skip to content

Commit

Permalink
feat(WP): authenticateWithPasskey method
Browse files Browse the repository at this point in the history
add a new method to support authenticating with passkey

Ticket: WP-2592
  • Loading branch information
alvin-dai-bitgo committed Sep 24, 2024
1 parent 76ffc3f commit 2e352f2
Show file tree
Hide file tree
Showing 2 changed files with 134 additions and 9 deletions.
117 changes: 108 additions & 9 deletions modules/sdk-api/src/bitgoAPI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import {
AddAccessTokenOptions,
AddAccessTokenResponse,
AuthenticateOptions,
AuthenticateWithPasskeyOptions,
AuthenticateWithAuthCodeOptions,
BitGoAPIOptions,
BitGoJson,
Expand All @@ -64,6 +65,7 @@ import {
LoginResponse,
PingOptions,
ProcessedAuthenticationOptions,
ProcessedAuthenticationPasskeyOptions,
ReconstitutedSecret,
ReconstituteSecretOptions,
RegisterPushTokenOptions,
Expand Down Expand Up @@ -427,16 +429,16 @@ export class BitGoAPI implements BitGoBase {
*/
const newOnFulfilled = onfulfilled
? (response: superagent.Response) => {
// HMAC verification is only allowed to be skipped in certain environments.
// This is checked in the constructor, but checking it again at request time
// will help prevent against tampering of this property after the object is created
if (!this._hmacVerification && !common.Environments[this.getEnv()].hmacVerificationEnforced) {
return onfulfilled(response);
}

const verifiedResponse = verifyResponse(this, this._token, method, req, response);
return onfulfilled(verifiedResponse);
// HMAC verification is only allowed to be skipped in certain environments.
// This is checked in the constructor, but checking it again at request time
// will help prevent against tampering of this property after the object is created
if (!this._hmacVerification && !common.Environments[this.getEnv()].hmacVerificationEnforced) {
return onfulfilled(response);
}

const verifiedResponse = verifyResponse(this, this._token, method, req, response);
return onfulfilled(verifiedResponse);
}
: null;
return originalThen(newOnFulfilled).catch(onrejected);
};
Expand Down Expand Up @@ -772,6 +774,58 @@ export class BitGoAPI implements BitGoBase {
return authParams;
}

/**
* Process auth passkey options into an object for bitgo authentication.
*/
preprocessAuthenticationPasskeyParams(params: AuthenticateWithPasskeyOptions): ProcessedAuthenticationPasskeyOptions {
if (!_.isString(params.username)) {
throw new Error('expected string username');
}

if (!_.isString(params.credId)) {
throw new Error('expected string credId');
}

if (!_.isObject(params.response)) {
throw new Error('required object params.response');
}

if (!_.isString(params.response.authenticatorData)) {
throw new Error('required object params.response.authenticatorData');
}

if (!_.isString(params.response.signature)) {
throw new Error('required object params.response.signature');
}

if (!_.isString(params.response.clientDataJSON)) {
throw new Error('required object params.response.clientDataJSON');
}

const processedParams: ProcessedAuthenticationPasskeyOptions = {
username: params.username,
credId: params.credId,
response: params.response,
}

if (params.otp) {
processedParams.otp = params.otp;
}

if (params.extensible) {
this._extensionKey = makeRandomKey();
processedParams.extensible = true;
processedParams.extensionAddress = getAddressP2PKH(this._extensionKey);
}

if (params.forReset2FA) {
processedParams.forReset2FA = true;
}

return params;
}


/**
* Synchronous method for activating an access token.
*/
Expand Down Expand Up @@ -915,6 +969,51 @@ export class BitGoAPI implements BitGoBase {
}
}

/**
* Login to the bitgo platform with passkey.
*/
async authenticateWithPasskey(params: AuthenticateWithPasskeyOptions): Promise<LoginResponse | any> {
try {
if (!_.isObject(params)) {
throw new Error('required object params');
}

if (this._token) {
return new Error('already logged in');
}

const authUrl = this.microservicesUrl('/api/auth/v1/session');
const authParams = this.preprocessAuthenticationPasskeyParams(params);
const request = this.post(authUrl);

const response: superagent.Response = await request.send(authParams);
// extract body and user information
const body = response.body;
this._user = body.user;

if (body.access_token) {
this._token = body.access_token;
} else {
//TODO: Issue token

// const responseDetails = this.handleTokenIssuance(response.body, password);
// this._token = responseDetails.token;
// this._ecdhXprv = responseDetails.ecdhXprv;

// // verify the response's authenticity
// verifyResponse(this, responseDetails.token, 'post', request, response);

// // add the remaining component for easier access
// response.body.access_token = this._token;
}

return handleResponseResult<LoginResponse>()(response);
} catch (e) {
handleResponseError(e);
}
}


/**
*
* @param responseBody Response body object
Expand Down
26 changes: 26 additions & 0 deletions modules/sdk-api/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,22 @@ export interface AuthenticateOptions {
forReset2FA?: boolean;
}

export interface AuthenticateWithPasskeyData {
authenticatorData: string,
signature: string,
clientDataJSON: string,
userHandle?: string
}

export interface AuthenticateWithPasskeyOptions {
username: string,
credId: string,
response: AuthenticateWithPasskeyData,
otp?: string,
extensible?: boolean;
forReset2FA?: boolean;
}

export interface ProcessedAuthenticationOptions {
email: string;
password: string;
Expand All @@ -116,6 +132,16 @@ export interface ProcessedAuthenticationOptions {
forReset2FA?: boolean;
}

export interface ProcessedAuthenticationPasskeyOptions {
username: string,
credId: string,
response: AuthenticateWithPasskeyData,
otp?: string,
extensible?: boolean;
extensionAddress?: boolean;
forReset2FA?: boolean;
}

export interface User {
username: string;
}
Expand Down

0 comments on commit 2e352f2

Please sign in to comment.