diff --git a/packages/services/identity-instrument/package.json b/packages/services/identity-instrument/package.json index fade5639a..3accab80e 100644 --- a/packages/services/identity-instrument/package.json +++ b/packages/services/identity-instrument/package.json @@ -25,6 +25,7 @@ "vitest": "^3.2.1" }, "dependencies": { + "json-canonicalize": "^2.0.0", "jwt-decode": "^4.0.0", "ox": "^0.7.2" } diff --git a/packages/services/identity-instrument/src/identity-instrument.gen.ts b/packages/services/identity-instrument/src/identity-instrument.gen.ts index c4a0fe9aa..6ee9d5d59 100644 --- a/packages/services/identity-instrument/src/identity-instrument.gen.ts +++ b/packages/services/identity-instrument/src/identity-instrument.gen.ts @@ -1,5 +1,5 @@ /* eslint-disable */ -// identity-instrument v0.1.0 f482d220692b4c5e41797f4e8dddb70dab930ed3 +// identity-instrument v0.1.0 b0ca08fbbd2e98d269d745176d4de5cbfa8960d6 // -- // Code generated by webrpc-gen@v0.23.1 with typescript generator. DO NOT EDIT. // @@ -16,7 +16,7 @@ export const WebRPCVersion = 'v1' export const WebRPCSchemaVersion = 'v0.1.0' // Schema hash generated from your RIDL schema -export const WebRPCSchemaHash = 'f482d220692b4c5e41797f4e8dddb70dab930ed3' +export const WebRPCSchemaHash = 'b0ca08fbbd2e98d269d745176d4de5cbfa8960d6' type WebrpcGenVersions = { webrpcGenVersion: string @@ -71,18 +71,16 @@ function parseWebrpcGenVersions(header: string): WebrpcGenVersions { // export enum KeyType { - Secp256k1 = 'Secp256k1', - Secp256r1 = 'Secp256r1', + WebCrypto_Secp256r1 = 'WebCrypto_Secp256r1', + Ethereum_Secp256k1 = 'Ethereum_Secp256k1', } export enum IdentityType { - Guest = 'Guest', Email = 'Email', OIDC = 'OIDC', } export enum AuthMode { - Guest = 'Guest', OTP = 'OTP', IDToken = 'IDToken', AccessToken = 'AccessToken', @@ -92,7 +90,6 @@ export enum AuthMode { export interface CommitVerifierParams { scope?: string - authKey: Key identityType: IdentityType authMode: AuthMode metadata: { [key: string]: string } @@ -102,20 +99,19 @@ export interface CommitVerifierParams { export interface CompleteAuthParams { scope?: string - authKey: Key identityType: IdentityType signerType: KeyType authMode: AuthMode verifier: string answer: string + lifetime?: number } export interface SignParams { scope?: string signer: Key + nonce: string digest: string - authKey: Key - signature: string } export interface Identity { @@ -173,6 +169,8 @@ export interface IdentityInstrument { export interface CommitVerifierArgs { params: CommitVerifierParams + authKey: Key + signature: string } export interface CommitVerifierReturn { @@ -182,6 +180,8 @@ export interface CommitVerifierReturn { } export interface CompleteAuthArgs { params: CompleteAuthParams + authKey: Key + signature: string } export interface CompleteAuthReturn { @@ -190,6 +190,8 @@ export interface CompleteAuthReturn { } export interface SignArgs { params: SignParams + authKey: Key + signature: string } export interface SignReturn { @@ -660,6 +662,32 @@ export class TooManyAttemptsError extends WebrpcError { } } +export class OAuthErrorError extends WebrpcError { + constructor( + name: string = 'OAuthError', + code: number = 7006, + message: string = `Failed to exchange OAuth credentials`, + status: number = 0, + cause?: string, + ) { + super(name, code, message, status, cause) + Object.setPrototypeOf(this, OAuthErrorError.prototype) + } +} + +export class AccessErrorError extends WebrpcError { + constructor( + name: string = 'AccessError', + code: number = 7007, + message: string = `Access error`, + status: number = 0, + cause?: string, + ) { + super(name, code, message, status, cause) + Object.setPrototypeOf(this, AccessErrorError.prototype) + } +} + export enum errors { WebrpcEndpoint = 'WebrpcEndpoint', WebrpcRequestFailed = 'WebrpcRequestFailed', @@ -686,6 +714,8 @@ export enum errors { AnswerIncorrect = 'AnswerIncorrect', ChallengeExpired = 'ChallengeExpired', TooManyAttempts = 'TooManyAttempts', + OAuthError = 'OAuthError', + AccessError = 'AccessError', } export enum WebrpcErrorCodes { @@ -714,6 +744,8 @@ export enum WebrpcErrorCodes { AnswerIncorrect = 7003, ChallengeExpired = 7004, TooManyAttempts = 7005, + OAuthError = 7006, + AccessError = 7007, } export const webrpcErrorByCode: { [code: number]: any } = { @@ -742,6 +774,8 @@ export const webrpcErrorByCode: { [code: number]: any } = { [7003]: AnswerIncorrectError, [7004]: ChallengeExpiredError, [7005]: TooManyAttemptsError, + [7006]: OAuthErrorError, + [7007]: AccessErrorError, } export type Fetch = (input: RequestInfo, init?: RequestInit) => Promise diff --git a/packages/services/identity-instrument/src/index.ts b/packages/services/identity-instrument/src/index.ts index f43cd0fc2..ca61a8d98 100644 --- a/packages/services/identity-instrument/src/index.ts +++ b/packages/services/identity-instrument/src/index.ts @@ -1,4 +1,5 @@ import { Hex, Bytes } from 'ox' +import { canonicalize } from 'json-canonicalize' import { CommitVerifierReturn, CompleteAuthReturn, @@ -21,44 +22,50 @@ export class IdentityInstrument { } async commitVerifier(authKey: AuthKey, challenge: Challenge) { + const params = challenge.getCommitParams() + const signature = await authKey.sign(Bytes.fromString(canonicalize(params))) return this.rpc.commitVerifier({ - params: { - ...challenge.getCommitParams(), - authKey: { - address: authKey.address, - keyType: authKey.keyType, - }, + params, + authKey: { + address: authKey.address, + keyType: authKey.keyType, }, + signature, }) } async completeAuth(authKey: AuthKey, challenge: Challenge) { + const params = { + ...challenge.getCompleteParams(), + signerType: KeyType.Ethereum_Secp256k1, + } + const signature = await authKey.sign(Bytes.fromString(canonicalize(params))) return this.rpc.completeAuth({ - params: { - ...challenge.getCompleteParams(), - signerType: KeyType.Secp256k1, - authKey: { - address: authKey.address, - keyType: authKey.keyType, - }, + params, + authKey: { + address: authKey.address, + keyType: authKey.keyType, }, + signature, }) } async sign(authKey: AuthKey, digest: Bytes.Bytes) { + const params = { + signer: { + address: authKey.signer, + keyType: KeyType.Ethereum_Secp256k1, + }, + digest: Hex.fromBytes(digest), + nonce: Hex.random(16), + } const res = await this.rpc.sign({ - params: { - signer: { - address: authKey.signer, - keyType: KeyType.Secp256k1, - }, - digest: Hex.fromBytes(digest), - authKey: { - address: authKey.address, - keyType: authKey.keyType, - }, - signature: await authKey.sign(digest), + params, + authKey: { + address: authKey.address, + keyType: authKey.keyType, }, + signature: await authKey.sign(Bytes.fromString(canonicalize(params))), }) Hex.assert(res.signature) return res.signature diff --git a/packages/services/identity-instrument/test/challenge.test.ts b/packages/services/identity-instrument/test/challenge.test.ts index e3d610d34..015def473 100644 --- a/packages/services/identity-instrument/test/challenge.test.ts +++ b/packages/services/identity-instrument/test/challenge.test.ts @@ -32,7 +32,7 @@ describe('IdTokenChallenge', () => { describe('AuthCodeChallenge', () => { const authCode = '1234567890' - const signer = { address: '0x26F5B2b3Feed8f02051c0b1c5b40cc088107935e', keyType: KeyType.Secp256k1 } + const signer = { address: '0x26F5B2b3Feed8f02051c0b1c5b40cc088107935e', keyType: KeyType.Ethereum_Secp256k1 } it('returns correct commit params', () => { const challenge = new AuthCodeChallenge('https://example.com', 'audience', 'https://dapp.com/redirect', authCode) @@ -77,7 +77,7 @@ describe('AuthCodePkceChallenge', () => { const challenge = new AuthCodePkceChallenge('https://example.com', 'audience', 'https://dapp.com/redirect') const authCode = '1234567890' const verifier = 'verifier' - const signer = { address: '0x26F5B2b3Feed8f02051c0b1c5b40cc088107935e', keyType: KeyType.Secp256k1 } + const signer = { address: '0x26F5B2b3Feed8f02051c0b1c5b40cc088107935e', keyType: KeyType.Ethereum_Secp256k1 } it('returns correct commit params', () => { const params = challenge.getCommitParams() @@ -172,7 +172,7 @@ describe('OtpChallenge', () => { }) describe('fromSigner', () => { - const signer = { address: '0x26F5B2b3Feed8f02051c0b1c5b40cc088107935e', keyType: KeyType.Secp256k1 } + const signer = { address: '0x26F5B2b3Feed8f02051c0b1c5b40cc088107935e', keyType: KeyType.Ethereum_Secp256k1 } describe('getCommitParams', () => { it('returns correct commit params', () => { @@ -186,7 +186,10 @@ describe('OtpChallenge', () => { }) it('throws if signer is not provided', () => { - const challenge = OtpChallenge.fromSigner(IdentityType.Email, { address: '', keyType: KeyType.Secp256k1 }) + const challenge = OtpChallenge.fromSigner(IdentityType.Email, { + address: '', + keyType: KeyType.Ethereum_Secp256k1, + }) expect(() => challenge.getCommitParams()).toThrow() }) }) diff --git a/packages/wallet/wdk/src/identity/signer.ts b/packages/wallet/wdk/src/identity/signer.ts index 3da8d1301..9b39c5efd 100644 --- a/packages/wallet/wdk/src/identity/signer.ts +++ b/packages/wallet/wdk/src/identity/signer.ts @@ -8,7 +8,7 @@ import * as Identity from '@0xsequence/identity-instrument' export function toIdentityAuthKey(authKey: AuthKey): Identity.AuthKey { return { address: authKey.address, - keyType: Identity.KeyType.Secp256r1, + keyType: Identity.KeyType.WebCrypto_Secp256r1, signer: authKey.identitySigner, async sign(digest: Bytes.Bytes) { const authKeySignature = await window.crypto.subtle.sign( diff --git a/packages/wallet/wdk/src/sequence/handlers/authcode-pkce.ts b/packages/wallet/wdk/src/sequence/handlers/authcode-pkce.ts index 1208b3077..6029f4114 100644 --- a/packages/wallet/wdk/src/sequence/handlers/authcode-pkce.ts +++ b/packages/wallet/wdk/src/sequence/handlers/authcode-pkce.ts @@ -22,7 +22,7 @@ export class AuthCodePkceHandler extends AuthCodeHandler implements Handler { public async commitAuth(target: string, isSignUp: boolean, state?: string, signer?: string) { let challenge = new Identity.AuthCodePkceChallenge(this.issuer, this.audience, this.redirectUri) if (signer) { - challenge = challenge.withSigner({ address: signer, keyType: Identity.KeyType.Secp256k1 }) + challenge = challenge.withSigner({ address: signer, keyType: Identity.KeyType.Ethereum_Secp256k1 }) } const { verifier, loginHint, challenge: codeChallenge } = await this.nitroCommitVerifier(challenge) if (!state) { diff --git a/packages/wallet/wdk/src/sequence/handlers/authcode.ts b/packages/wallet/wdk/src/sequence/handlers/authcode.ts index 87919d364..bb8a1b315 100644 --- a/packages/wallet/wdk/src/sequence/handlers/authcode.ts +++ b/packages/wallet/wdk/src/sequence/handlers/authcode.ts @@ -62,7 +62,7 @@ export class AuthCodeHandler extends IdentityHandler implements Handler { ): Promise<[IdentitySigner, { [key: string]: string }]> { let challenge = new Identity.AuthCodeChallenge(this.issuer, this.audience, this.redirectUri, code) if (commitment.signer) { - challenge = challenge.withSigner({ address: commitment.signer, keyType: Identity.KeyType.Secp256k1 }) + challenge = challenge.withSigner({ address: commitment.signer, keyType: Identity.KeyType.Ethereum_Secp256k1 }) } await this.nitroCommitVerifier(challenge) const { signer, email } = await this.nitroCompleteAuth(challenge) diff --git a/packages/wallet/wdk/src/sequence/handlers/identity.ts b/packages/wallet/wdk/src/sequence/handlers/identity.ts index cbe09963f..f18245222 100644 --- a/packages/wallet/wdk/src/sequence/handlers/identity.ts +++ b/packages/wallet/wdk/src/sequence/handlers/identity.ts @@ -8,8 +8,6 @@ import { IdentitySigner, toIdentityAuthKey } from '../../identity/signer.js' export const identityTypeToHex = (identityType?: Identity.IdentityType): Hex.Hex => { // Bytes4 switch (identityType) { - case Identity.IdentityType.Guest: - return '0x00000000' case Identity.IdentityType.Email: return '0x00000001' case Identity.IdentityType.OIDC: diff --git a/packages/wallet/wdk/src/sequence/handlers/otp.ts b/packages/wallet/wdk/src/sequence/handlers/otp.ts index efda47a63..3b375f30c 100644 --- a/packages/wallet/wdk/src/sequence/handlers/otp.ts +++ b/packages/wallet/wdk/src/sequence/handlers/otp.ts @@ -91,7 +91,7 @@ export class OtpHandler extends IdentityHandler implements Handler { new Promise(async (resolve, reject) => { const challenge = Identity.OtpChallenge.fromSigner(this.identityType, { address, - keyType: Identity.KeyType.Secp256k1, + keyType: Identity.KeyType.Ethereum_Secp256k1, }) const { loginHint, challenge: codeChallenge } = await this.nitroCommitVerifier(challenge) diff --git a/packages/wallet/wdk/test/authcode-pkce.test.ts b/packages/wallet/wdk/test/authcode-pkce.test.ts index 6613fc61e..dc6ecfc73 100644 --- a/packages/wallet/wdk/test/authcode-pkce.test.ts +++ b/packages/wallet/wdk/test/authcode-pkce.test.ts @@ -159,7 +159,7 @@ describe('AuthCodePkceHandler', () => { // Verify nitroCommitVerifier was called with signer in challenge expect(handler['nitroCommitVerifier']).toHaveBeenCalledWith( expect.objectContaining({ - signer: { address: signer, keyType: Identity.KeyType.Secp256k1 }, + signer: { address: signer, keyType: Identity.KeyType.Ethereum_Secp256k1 }, }), ) }) diff --git a/packages/wallet/wdk/test/authcode.test.ts b/packages/wallet/wdk/test/authcode.test.ts index 204a51d34..87b1f50e4 100644 --- a/packages/wallet/wdk/test/authcode.test.ts +++ b/packages/wallet/wdk/test/authcode.test.ts @@ -571,7 +571,7 @@ describe('AuthCodeHandler', () => { expect(mockCommitVerifier).toHaveBeenCalledWith( expect.objectContaining({ address: mockAuthKey.address, - keyType: KeyType.Secp256r1, + keyType: KeyType.WebCrypto_Secp256r1, signer: mockAuthKey.identitySigner, }), mockChallenge, diff --git a/packages/wallet/wdk/test/identity-signer.test.ts b/packages/wallet/wdk/test/identity-signer.test.ts index 14d021973..fe6da5c8b 100644 --- a/packages/wallet/wdk/test/identity-signer.test.ts +++ b/packages/wallet/wdk/test/identity-signer.test.ts @@ -73,7 +73,7 @@ describe('Identity Signer', () => { const result = toIdentityAuthKey(testAuthKey) expect(result.address).toBe(testAuthKey.address) - expect(result.keyType).toBe(KeyType.Secp256r1) + expect(result.keyType).toBe(KeyType.WebCrypto_Secp256r1) expect(result.signer).toBe(testAuthKey.identitySigner) expect(typeof result.sign).toBe('function') }) diff --git a/packages/wallet/wdk/test/otp.test.ts b/packages/wallet/wdk/test/otp.test.ts index d9095cef4..a820e0ca0 100644 --- a/packages/wallet/wdk/test/otp.test.ts +++ b/packages/wallet/wdk/test/otp.test.ts @@ -406,7 +406,7 @@ describe('OtpHandler', () => { expect(handleResult).toBe(true) expect(MockedOtpChallenge.fromSigner).toHaveBeenCalledWith(IdentityType.Email, { address: testWallet, - keyType: KeyType.Secp256k1, + keyType: KeyType.Ethereum_Secp256k1, }) expect(mockCallback).toHaveBeenCalledWith('user@example.com', expect.any(Function)) }) @@ -478,7 +478,7 @@ describe('OtpHandler', () => { expect(mockCommitVerifier).toHaveBeenCalledWith( expect.objectContaining({ address: mockAuthKey.address, - keyType: KeyType.Secp256r1, + keyType: KeyType.WebCrypto_Secp256r1, signer: mockAuthKey.identitySigner, }), mockChallenge, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c5c87a9f0..cba9a88c9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -133,6 +133,9 @@ importers: packages/services/identity-instrument: dependencies: + json-canonicalize: + specifier: ^2.0.0 + version: 2.0.0 jwt-decode: specifier: ^4.0.0 version: 4.0.0 @@ -2445,6 +2448,9 @@ packages: json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + json-canonicalize@2.0.0: + resolution: {integrity: sha512-yyrnK/mEm6Na3ChbJUWueXdapueW0p380RUyTW87XGb1ww8l8hU0pRrGC3vSWHe9CxrbPHX2fGUOZpNiHR0IIg==} + json-schema-traverse@0.4.1: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} @@ -5903,6 +5909,8 @@ snapshots: json-buffer@3.0.1: {} + json-canonicalize@2.0.0: {} + json-schema-traverse@0.4.1: {} json-stable-stringify-without-jsonify@1.0.1: {}