Skip to content

Commit

Permalink
Wire up initiate_identity_link and approve_identity_link in the FE (
Browse files Browse the repository at this point in the history
  • Loading branch information
hpeebles authored Jul 15, 2024
1 parent 126ed2b commit a2b7fa3
Show file tree
Hide file tree
Showing 7 changed files with 200 additions and 20 deletions.
10 changes: 8 additions & 2 deletions frontend/openchat-agent/src/services/identity/candid/idl.d.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
import type { IDL } from "@dfinity/candid";
import {
ApproveIdentityLinkResponse,
CheckAuthPrincipalResponse,
CreateIdentityResponse,
GenerateChallengeResponse,
GetDelegationResponse,
InitiateIdentityLinkResponse,
PrepareDelegationResponse,
GenerateChallengeResponse,
SignedDelegation,
_SERVICE,
} from "./types";
export {
ApproveIdentityLinkResponse as ApiApproveIdentityLinkResponse,
CheckAuthPrincipalResponse as ApiCheckAuthPrincipalResponse,
CreateIdentityResponse as ApiCreateIdentityResponse,
GenerateChallengeResponse as ApiGenerateChallengeResponse,
GetDelegationResponse as ApiGetDelegationResponse,
InitiateIdentityLinkResponse as ApiInitiateIdentityLinkResponse,
PrepareDelegationResponse as ApiPrepareDelegationResponse,
GenerateChallengeResponse as ApiGenerateChallengeResponse,
SignedDelegation as ApiSignedDelegation,
_SERVICE as IdentityService,
};

Expand Down
51 changes: 42 additions & 9 deletions frontend/openchat-agent/src/services/identity/candid/idl.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,30 @@
export const idlFactory = ({ IDL }) => {
const PublicKey = IDL.Vec(IDL.Nat8);
const TimestampNanoseconds = IDL.Nat64;
const SignedDelegation = IDL.Record({
'signature' : IDL.Vec(IDL.Nat8),
'delegation' : IDL.Record({
'pubkey' : PublicKey,
'expiration' : TimestampNanoseconds,
}),
});
const ApproveIdentityLinkArgs = IDL.Record({
'link_initiated_by' : IDL.Principal,
'public_key' : IDL.Vec(IDL.Nat8),
'delegation' : SignedDelegation,
});
const ApproveIdentityLinkResponse = IDL.Variant({
'LinkRequestNotFound' : IDL.Null,
'InvalidSignature' : IDL.Null,
'Success' : IDL.Null,
'MalformedSignature' : IDL.Text,
'DelegationTooOld' : IDL.Null,
'CallerNotRecognised' : IDL.Null,
});
const CheckAuthPrincipalResponse = IDL.Variant({
'NotFound' : IDL.Null,
'Success' : IDL.Null,
});
const PublicKey = IDL.Vec(IDL.Nat8);
const Nanoseconds = IDL.Nat64;
const CreateIdentityArgs = IDL.Record({
'public_key' : PublicKey,
Expand All @@ -13,7 +34,6 @@ export const idlFactory = ({ IDL }) => {
IDL.Record({ 'key' : IDL.Nat32, 'chars' : IDL.Text })
),
});
const TimestampNanoseconds = IDL.Nat64;
const PrepareDelegationSuccess = IDL.Record({
'user_key' : PublicKey,
'expiration' : TimestampNanoseconds,
Expand All @@ -34,17 +54,20 @@ export const idlFactory = ({ IDL }) => {
'session_key' : PublicKey,
'expiration' : TimestampNanoseconds,
});
const SignedDelegation = IDL.Record({
'signature' : IDL.Vec(IDL.Nat8),
'delegation' : IDL.Record({
'pubkey' : PublicKey,
'expiration' : TimestampNanoseconds,
}),
});
const GetDelegationResponse = IDL.Variant({
'NotFound' : IDL.Null,
'Success' : SignedDelegation,
});
const InitiateIdentityLinkArgs = IDL.Record({
'public_key' : IDL.Vec(IDL.Nat8),
'link_to_principal' : IDL.Principal,
});
const InitiateIdentityLinkResponse = IDL.Variant({
'AlreadyRegistered' : IDL.Null,
'Success' : IDL.Null,
'TargetUserNotFound' : IDL.Null,
'PublicKeyInvalid' : IDL.Text,
});
const PrepareDelegationArgs = IDL.Record({
'session_key' : PublicKey,
'max_time_to_live' : IDL.Opt(Nanoseconds),
Expand All @@ -54,6 +77,11 @@ export const idlFactory = ({ IDL }) => {
'Success' : PrepareDelegationSuccess,
});
return IDL.Service({
'approve_identity_link' : IDL.Func(
[ApproveIdentityLinkArgs],
[ApproveIdentityLinkResponse],
[],
),
'check_auth_principal' : IDL.Func(
[IDL.Record({})],
[CheckAuthPrincipalResponse],
Expand All @@ -74,6 +102,11 @@ export const idlFactory = ({ IDL }) => {
[GetDelegationResponse],
['query'],
),
'initiate_identity_link' : IDL.Func(
[InitiateIdentityLinkArgs],
[InitiateIdentityLinkResponse],
[],
),
'prepare_delegation' : IDL.Func(
[PrepareDelegationArgs],
[PrepareDelegationResponse],
Expand Down
27 changes: 27 additions & 0 deletions frontend/openchat-agent/src/services/identity/candid/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,17 @@ import type { Principal } from '@dfinity/principal';
import type { ActorMethod } from '@dfinity/agent';
import type { IDL } from '@dfinity/candid';

export interface ApproveIdentityLinkArgs {
'link_initiated_by' : Principal,
'public_key' : Uint8Array | number[],
'delegation' : SignedDelegation,
}
export type ApproveIdentityLinkResponse = { 'LinkRequestNotFound' : null } |
{ 'InvalidSignature' : null } |
{ 'Success' : null } |
{ 'MalformedSignature' : string } |
{ 'DelegationTooOld' : null } |
{ 'CallerNotRecognised' : null };
export type CheckAuthPrincipalResponse = { 'NotFound' : null } |
{ 'Success' : null };
export interface CreateIdentityArgs {
Expand All @@ -24,6 +35,14 @@ export interface GetDelegationArgs {
}
export type GetDelegationResponse = { 'NotFound' : null } |
{ 'Success' : SignedDelegation };
export interface InitiateIdentityLinkArgs {
'public_key' : Uint8Array | number[],
'link_to_principal' : Principal,
}
export type InitiateIdentityLinkResponse = { 'AlreadyRegistered' : null } |
{ 'Success' : null } |
{ 'TargetUserNotFound' : null } |
{ 'PublicKeyInvalid' : string };
export type Nanoseconds = bigint;
export interface PrepareDelegationArgs {
'session_key' : PublicKey,
Expand All @@ -42,10 +61,18 @@ export interface SignedDelegation {
}
export type TimestampNanoseconds = bigint;
export interface _SERVICE {
'approve_identity_link' : ActorMethod<
[ApproveIdentityLinkArgs],
ApproveIdentityLinkResponse
>,
'check_auth_principal' : ActorMethod<[{}], CheckAuthPrincipalResponse>,
'create_identity' : ActorMethod<[CreateIdentityArgs], CreateIdentityResponse>,
'generate_challenge' : ActorMethod<[{}], GenerateChallengeResponse>,
'get_delegation' : ActorMethod<[GetDelegationArgs], GetDelegationResponse>,
'initiate_identity_link' : ActorMethod<
[InitiateIdentityLinkArgs],
InitiateIdentityLinkResponse
>,
'prepare_delegation' : ActorMethod<
[PrepareDelegationArgs],
PrepareDelegationResponse
Expand Down
47 changes: 45 additions & 2 deletions frontend/openchat-agent/src/services/identity/identity.client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,29 @@ import type { Identity, SignIdentity } from "@dfinity/agent";
import { idlFactory, type IdentityService } from "./candid/idl";
import { CandidService } from "../candidService";
import type {
ApproveIdentityLinkResponse,
ChallengeAttempt,
CheckAuthPrincipalResponse,
CreateIdentityResponse,
GenerateChallengeResponse,
GetDelegationResponse,
InitiateIdentityLinkResponse,
PrepareDelegationResponse,
} from "openchat-shared";
import {
approveIdentityLinkResponse,
checkAuthPrincipalResponse,
createIdentityResponse,
generateChallengeResponse,
getDelegationResponse,
initiateIdentityLinkResponse,
prepareDelegationResponse,
} from "./mappers";
import type { CreateIdentityArgs } from "./candid/types";
import type { CreateIdentityArgs, SignedDelegation } from "./candid/types";
import { apiOptional } from "../common/chatMappers";
import { identity } from "../../utils/mapping";
import { Principal } from "@dfinity/principal";
import type { DelegationIdentity } from "@dfinity/identity";

export class IdentityClient extends CandidService {
private service: IdentityService;
Expand All @@ -40,7 +46,7 @@ export class IdentityClient extends CandidService {
challengeAttempt: ChallengeAttempt | undefined,
): Promise<CreateIdentityResponse> {
const args: CreateIdentityArgs = {
public_key: new Uint8Array((this.identity as SignIdentity).getPublicKey().toDer()),
public_key: this.publicKey(),
session_key: sessionKey,
max_time_to_live: [] as [] | [bigint],
challenge_attempt: apiOptional(identity, challengeAttempt),
Expand Down Expand Up @@ -87,4 +93,41 @@ export class IdentityClient extends CandidService {
generateChallenge(): Promise<GenerateChallengeResponse> {
return this.handleResponse(this.service.generate_challenge({}), generateChallengeResponse);
}

initiateIdentityLink(linkToPrincipal: string): Promise<InitiateIdentityLinkResponse> {
return this.handleResponse(
this.service.initiate_identity_link({
link_to_principal: Principal.fromText(linkToPrincipal),
public_key: this.publicKey(),
}),
initiateIdentityLinkResponse,
);
}

approveIdentityLink(linkInitiatedBy: string): Promise<ApproveIdentityLinkResponse> {
return this.handleResponse(
this.service.approve_identity_link({
link_initiated_by: Principal.fromText(linkInitiatedBy),
public_key: this.publicKey(),
delegation: this.delegation(),
}),
approveIdentityLinkResponse,
);
}

private publicKey(): Uint8Array {
return new Uint8Array((this.identity as SignIdentity).getPublicKey().toDer());
}

private delegation(): SignedDelegation {
const delegation = (this.identity as DelegationIdentity).getDelegation().delegations[0];

return {
signature: new Uint8Array(delegation.signature),
delegation: {
pubkey: new Uint8Array(delegation.delegation.pubkey),
expiration: delegation.delegation.expiration,
},
};
}
}
51 changes: 50 additions & 1 deletion frontend/openchat-agent/src/services/identity/mappers.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
import type {
ApiApproveIdentityLinkResponse,
ApiCheckAuthPrincipalResponse,
ApiCreateIdentityResponse,
ApiGenerateChallengeResponse,
ApiGetDelegationResponse,
ApiInitiateIdentityLinkResponse,
ApiPrepareDelegationResponse,
} from "./candid/idl";
import {
type ApproveIdentityLinkResponse,
type CheckAuthPrincipalResponse,
type CreateIdentityResponse,
type GenerateChallengeResponse,
type GetDelegationResponse,
type InitiateIdentityLinkResponse,
type PrepareDelegationResponse,
type PrepareDelegationSuccess,
UnsupportedValueError,
type GenerateChallengeResponse,
} from "openchat-shared";
import { consolidateBytes } from "../../utils/mapping";
import type { Signature } from "@dfinity/agent";
Expand Down Expand Up @@ -121,3 +125,48 @@ export function generateChallengeResponse(
candid,
);
}

export function initiateIdentityLinkResponse(
candid: ApiInitiateIdentityLinkResponse,
): InitiateIdentityLinkResponse {
if ("Success" in candid) {
return "success";
}
if ("AlreadyRegistered" in candid) {
return "already_registered";
}
if ("TargetUserNotFound" in candid) {
return "target_user_not_found";
}
if ("PublicKeyInvalid" in candid) {
return "public_key_invalid";
}
throw new UnsupportedValueError(
"Unexpected ApiInitiateIdentityLinkResponse type received",
candid,
);
}

export function approveIdentityLinkResponse(
candid: ApiApproveIdentityLinkResponse,
): ApproveIdentityLinkResponse {
if ("Success" in candid) {
return "success";
}
if ("CallerNotRecognised" in candid) {
return "caller_not_recognised";
}
if ("LinkRequestNotFound" in candid) {
return "link_request_not_found";
}
if ("MalformedSignature" in candid || "InvalidSignature" in candid) {
return "invalid_signature";
}
if ("DelegationTooOld" in candid) {
return "delegation_too_old";
}
throw new UnsupportedValueError(
"Unexpected ApiApproveIdentityLinkResponse type received",
candid,
);
}
21 changes: 15 additions & 6 deletions frontend/openchat-agent/src/services/identityAgent.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { IdentityClient } from "./identity/identity.client";
import type { Identity, SignIdentity } from "@dfinity/agent";
import { DelegationIdentity } from "@dfinity/identity";
import type { GenerateChallengeResponse } from "openchat-shared";
import {
buildDelegationIdentity,
type ChallengeAttempt,
type CreateOpenChatIdentityError,
toDer,
import type {
ApproveIdentityLinkResponse,
ChallengeAttempt,
CreateOpenChatIdentityError,
GenerateChallengeResponse,
InitiateIdentityLinkResponse,
} from "openchat-shared";
import { buildDelegationIdentity, toDer } from "openchat-shared";

export class IdentityAgent {
private _identityClient: IdentityClient;
Expand Down Expand Up @@ -64,6 +65,14 @@ export class IdentityAgent {
return this._identityClient.generateChallenge();
}

initiateIdentityLink(linkToPrincipal: string): Promise<InitiateIdentityLinkResponse> {
return this._identityClient.initiateIdentityLink(linkToPrincipal);
}

approveIdentityLink(linkInitiatedBy: string): Promise<ApproveIdentityLinkResponse> {
return this._identityClient.approveIdentityLink(linkInitiatedBy);
}

private async getDelegation(
userKey: Uint8Array,
sessionKey: SignIdentity,
Expand Down
13 changes: 13 additions & 0 deletions frontend/openchat-shared/src/domain/identity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,16 @@ export type Challenge = {
};

export type CreateOpenChatIdentityResponse = "success" | CreateOpenChatIdentityError;

export type InitiateIdentityLinkResponse =
| "success"
| "already_registered"
| "target_user_not_found"
| "public_key_invalid";

export type ApproveIdentityLinkResponse =
| "success"
| "caller_not_recognised"
| "link_request_not_found"
| "invalid_signature"
| "delegation_too_old";

0 comments on commit a2b7fa3

Please sign in to comment.