Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: added pairIdentifiers service #4269

Merged
merged 7 commits into from
May 15, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import { AuthType } from './types';
export const NONCE_URL = (env: Env) =>
`${getEnvUrls(env).authApiUrl}/api/v2/nonce`;

export const PAIR_IDENTIFIERS = (env: Env) =>
`${getEnvUrls(env).authApiUrl}/api/v2/identifiers/pair`;

export const OIDC_TOKEN_URL = (env: Env) =>
`${getEnvUrls(env).oidcApiUrl}/oauth2/token`;

Expand Down Expand Up @@ -36,6 +39,57 @@ type NonceResponse = {
expiresIn: number;
};

type PairRequest = {
signature: string;
raw_message: string;
encrypted_storage_key: string;
identifier_type: 'SIWE' | 'SRP';
};

/**
* Pair multiple identifiers under a single profile
*
* @param nonce - session nonce
* @param logins - pairing request payload
* @param accessToken - JWT access token used to access protected resources
* @param env - server environment
* @returns void.
*/
export async function pairIdentifiers(
nonce: string,
logins: PairRequest[],
accessToken: string,
env: Env,
): Promise<void> {
const pairUrl = new URL(PAIR_IDENTIFIERS(env));

try {
const response = await fetch(pairUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${accessToken}`,
},
body: JSON.stringify({
nonce,
logins,
}),
});

if (!response.ok) {
const responseBody = (await response.json()) as ErrorMessage;
throw new Error(
`HTTP error message: ${responseBody.message}, error: ${responseBody.error}`,
);
}
} catch (e) {
/* istanbul ignore next */
const errorMessage =
e instanceof Error ? e.message : JSON.stringify(e ?? '');
throw new SignInError(`unable to pair identifiers: ${errorMessage}`);
}
}

/**
* Service to Get Nonce for JWT Bearer Flow
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,9 @@ export type ErrorMessage = {
message: string;
error: string;
};

export type Pair = {
identifier: string;
encryptedStorageKey: string;
Prithpal-Sooriya marked this conversation as resolved.
Show resolved Hide resolved
identifierType: 'SIWE' | 'SRP';
};
33 changes: 32 additions & 1 deletion packages/profile-sync-controller/src/sdk/authentication.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import { SIWEJwtBearerAuth } from './authentication-jwt-bearer/flow-siwe';
import { SRPJwtBearerAuth } from './authentication-jwt-bearer/flow-srp';
import type { UserProfile } from './authentication-jwt-bearer/types';
import {
getNonce,
pairIdentifiers,
} from './authentication-jwt-bearer/services';
import type { UserProfile, Pair } from './authentication-jwt-bearer/types';
import { AuthType } from './authentication-jwt-bearer/types';
import type { Env } from './env';
import { UnsupportedAuthTypeError } from './errors';

// Computing the Classes, so we only get back the public methods for the interface.
Expand All @@ -16,10 +21,13 @@ type JwtBearerAuthParams = SiweParams | SRPParams;
export class JwtBearerAuth implements SIWEInterface, SRPInterface {
#type: AuthType;

#env: Env;
dovydas55 marked this conversation as resolved.
Show resolved Hide resolved

#sdk: SIWEJwtBearerAuth | SRPJwtBearerAuth;

constructor(...args: JwtBearerAuthParams) {
this.#type = args[0].type;
this.#env = args[0].env;

if (args[0].type === AuthType.SRP) {
this.#sdk = new SRPJwtBearerAuth(args[0], args[1]);
Expand Down Expand Up @@ -50,6 +58,29 @@ export class JwtBearerAuth implements SIWEInterface, SRPInterface {
return await this.#sdk.signMessage(message);
}

async pairIdentifiers(
pairing: Pair[],
signMessage: (message: string) => Promise<string>,
dovydas55 marked this conversation as resolved.
Show resolved Hide resolved
): Promise<void> {
const profile = await this.getUserProfile();
const n = await getNonce(profile.profileId, this.#env);
const logins = await Promise.all(
dovydas55 marked this conversation as resolved.
Show resolved Hide resolved
pairing.map(async (p) => {
Prithpal-Sooriya marked this conversation as resolved.
Show resolved Hide resolved
const raw = `metamask:${n.nonce}:${p.identifier}`;
const sig = await signMessage(raw);
return {
signature: sig,
raw_message: raw,
encrypted_storage_key: p.encryptedStorageKey,
identifier_type: p.identifierType,
};
}),
);

const accessToken = await this.getAccessToken();
return pairIdentifiers(n.nonce, logins, accessToken, this.#env);
}

prepare(signer: {
address: string;
chainId: number;
Expand Down
Loading