From c955ae4988604e63c8800516d0b23da7d8fcd866 Mon Sep 17 00:00:00 2001 From: dexploarer Date: Tue, 17 Feb 2026 21:30:56 -0500 Subject: [PATCH] fix(typecheck): close auth/sqlit/wallet type and API gaps --- .../web/components/auth/LinkedAccounts.tsx | 10 ++- .../src/credentials/verifiable-credentials.ts | 78 ++++++++++++------- packages/auth/src/sdk/client.ts | 51 ++++++++++-- packages/auth/src/types.ts | 4 +- packages/cli/src/commands/login.ts | 62 +++++++++++++-- packages/monitoring/package.json | 1 + packages/sqlit/src/client.ts | 7 +- 7 files changed, 167 insertions(+), 46 deletions(-) diff --git a/apps/wallet/web/components/auth/LinkedAccounts.tsx b/apps/wallet/web/components/auth/LinkedAccounts.tsx index d62b60523..fdb932eaf 100644 --- a/apps/wallet/web/components/auth/LinkedAccounts.tsx +++ b/apps/wallet/web/components/auth/LinkedAccounts.tsx @@ -33,7 +33,9 @@ type ProviderInfo = { description: string } -const PROVIDER_INFO: Record = { +type SupportedAuthProvider = Exclude + +const PROVIDER_INFO: Record = { [AuthProvider.WALLET]: { name: 'Wallet', icon: Wallet, @@ -85,13 +87,13 @@ const PROVIDER_INFO: Record = { } type LinkedProvider = { - provider: AuthProvider + provider: SupportedAuthProvider providerId: string handle?: string linkedAt: number } -const SUPPORTED_PROVIDERS: AuthProvider[] = [ +const SUPPORTED_PROVIDERS: SupportedAuthProvider[] = [ AuthProvider.WALLET, AuthProvider.GOOGLE, AuthProvider.APPLE, @@ -102,7 +104,7 @@ const SUPPORTED_PROVIDERS: AuthProvider[] = [ AuthProvider.PASSKEY, ] -function toAuthProvider(type: string): AuthProvider | null { +function toAuthProvider(type: string): SupportedAuthProvider | null { switch (type) { case 'wallet': return AuthProvider.WALLET diff --git a/packages/auth/src/credentials/verifiable-credentials.ts b/packages/auth/src/credentials/verifiable-credentials.ts index d11c05e71..755b0d679 100644 --- a/packages/auth/src/credentials/verifiable-credentials.ts +++ b/packages/auth/src/credentials/verifiable-credentials.ts @@ -309,19 +309,28 @@ export class VerifiableCredentialIssuer { } private getCredentialTypeForProvider(provider: AuthProvider): string { - const typeMap: Record = { - wallet: 'WalletOwnershipCredential', - farcaster: 'FarcasterAccountCredential', - google: 'GoogleAccountCredential', - apple: 'AppleAccountCredential', - twitter: 'TwitterAccountCredential', - github: 'GitHubAccountCredential', - discord: 'DiscordAccountCredential', - email: 'EmailAccountCredential', - phone: 'PhoneAccountCredential', - } - - return typeMap[provider] ?? 'OAuth3IdentityCredential' + switch (provider) { + case 'wallet': + return 'WalletOwnershipCredential' + case 'passkey': + return 'PasskeyAccountCredential' + case 'farcaster': + return 'FarcasterAccountCredential' + case 'google': + return 'GoogleAccountCredential' + case 'apple': + return 'AppleAccountCredential' + case 'twitter': + return 'TwitterAccountCredential' + case 'github': + return 'GitHubAccountCredential' + case 'discord': + return 'DiscordAccountCredential' + case 'email': + return 'EmailAccountCredential' + case 'phone': + return 'PhoneAccountCredential' + } } private createJWS(hash: Hex, challenge: string, domain?: string): string { @@ -594,6 +603,35 @@ export class VerifiableCredentialVerifier { } } +export function getOnChainProviderId(provider: AuthProvider): number { + switch (provider) { + case 'wallet': + return 0 + case 'farcaster': + return 1 + case 'google': + return 2 + case 'apple': + return 3 + case 'twitter': + return 4 + case 'github': + return 5 + case 'discord': + return 6 + case 'email': + return 7 + case 'phone': + return 8 + case 'passkey': + throw new Error('Passkey credentials are not yet supported on-chain') + default: { + const _exhaustive: never = provider + return _exhaustive + } + } +} + export function createCredentialHash(credential: VerifiableCredential): Hex { const essential = { type: credential.type, @@ -613,20 +651,8 @@ export function credentialToOnChainAttestation( issuedAt: number expiresAt: number } { - const providerMap: Record = { - wallet: 0, - farcaster: 1, - google: 2, - apple: 3, - twitter: 4, - github: 5, - discord: 6, - email: 7, - phone: 8, - } - return { - provider: providerMap[credential.credentialSubject.provider], + provider: getOnChainProviderId(credential.credentialSubject.provider), providerId: keccak256(toBytes(credential.credentialSubject.providerId)), credentialHash: createCredentialHash(credential), issuedAt: Math.floor(new Date(credential.issuanceDate).getTime() / 1000), diff --git a/packages/auth/src/sdk/client.ts b/packages/auth/src/sdk/client.ts index 160981f59..02e255b46 100644 --- a/packages/auth/src/sdk/client.ts +++ b/packages/auth/src/sdk/client.ts @@ -35,7 +35,7 @@ function generateUUID(): string { return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}` } -function arrayBufferToBase64url(buffer: ArrayBuffer): string { +function arrayBufferToBase64url(buffer: ArrayBuffer | SharedArrayBuffer): string { const bytes = new Uint8Array(buffer) let binary = '' for (const byte of bytes) { @@ -56,6 +56,28 @@ function isPasskeyRequestOptions( return 'challenge' in options && !('user' in options) } +function isAuthenticatorAssertionResponse( + response: AuthenticatorResponse, +): response is AuthenticatorAssertionResponse { + return 'authenticatorData' in response && 'signature' in response +} + +function safeArrayBufferToBase64url( + value: + | ArrayBuffer + | SharedArrayBuffer + | ArrayBufferView + | null + | undefined, +): string | undefined { + if (!value) return undefined + const buffer = + value instanceof ArrayBuffer || value instanceof SharedArrayBuffer + ? value + : value.buffer.slice(value.byteOffset, value.byteOffset + value.byteLength) + return arrayBufferToBase64url(buffer) +} + // OAuth callback data schema const OAuthCallbackSchema = z.object({ code: z.string().optional(), @@ -622,21 +644,34 @@ export class OAuth3Client { if (!('attestationObject' in response)) { throw new Error('Invalid passkey registration response') } + const registrationResponse = response as AuthenticatorAttestationResponse & { + attestationObject: ArrayBuffer | SharedArrayBuffer + clientDataJSON: ArrayBuffer + } responsePayload = { clientDataJSON, - attestationObject: arrayBufferToBase64url(response.attestationObject), + attestationObject: safeArrayBufferToBase64url( + registrationResponse.attestationObject, + ), } } else { - if (!('authenticatorData' in response) || !('signature' in response)) { + if (!isAuthenticatorAssertionResponse(response)) { throw new Error('Invalid passkey authentication response') } + const assertionResponse = response as AuthenticatorAssertionResponse & { + authenticatorData: ArrayBuffer | SharedArrayBuffer + signature: ArrayBuffer | SharedArrayBuffer + userHandle?: ArrayBuffer | SharedArrayBuffer | null + } responsePayload = { clientDataJSON, - authenticatorData: arrayBufferToBase64url(response.authenticatorData), - signature: arrayBufferToBase64url(response.signature), - userHandle: response.userHandle - ? arrayBufferToBase64url(response.userHandle) - : undefined, + authenticatorData: safeArrayBufferToBase64url( + assertionResponse.authenticatorData, + ), + signature: safeArrayBufferToBase64url(assertionResponse.signature), + userHandle: safeArrayBufferToBase64url( + assertionResponse.userHandle, + ), } } diff --git a/packages/auth/src/types.ts b/packages/auth/src/types.ts index 52cf6e77c..f10cea210 100644 --- a/packages/auth/src/types.ts +++ b/packages/auth/src/types.ts @@ -5,9 +5,11 @@ * threshold MPC signing, and W3C Verifiable Credentials. */ -import type { JsonRecord, TEEAttestation } from '@jejunetwork/types' +import type { TEEAttestation } from '@jejunetwork/types' import type { Address, Hex } from 'viem' +export type { JsonRecord } from '@jejunetwork/types' + export const AuthProvider = { WALLET: 'wallet', PASSKEY: 'passkey', diff --git a/packages/cli/src/commands/login.ts b/packages/cli/src/commands/login.ts index b7b21b8b7..130e86995 100644 --- a/packages/cli/src/commands/login.ts +++ b/packages/cli/src/commands/login.ts @@ -212,6 +212,11 @@ async function authenticateWithDWS( export const loginCommand = new Command('login') .description('Authenticate with Jeju Network using your wallet') .option('-n, --network ', 'Network to authenticate with', 'testnet') + .option( + '--address
', + 'Wallet address to authenticate (required for --external mode)', + ) + .option('--signature ', 'Wallet signature from --external flow') .option( '-k, --private-key ', 'Private key (or use DEPLOYER_PRIVATE_KEY env)', @@ -256,6 +261,15 @@ export const loginCommand = new Command('login') } if (options.external) { + if (!options.address) { + logger.error('External auth requires --address.') + logger.info( + 'Example: jeju login --external --address 0xYourAddress --network localnet', + ) + return + } + + const address = options.address as Address // External signing - output message for user to sign elsewhere const nonce = bytesToHex(randomBytes(32)) const timestamp = Date.now() @@ -266,14 +280,50 @@ export const loginCommand = new Command('login') timestamp, ) - logger.info('Sign the following message with your wallet:\n') - console.log('---') - console.log(message) - console.log('---\n') + if (!options.signature) { + logger.info('Sign the following message with your wallet:\n') + console.log('---') + console.log(message) + console.log('---\n') + + logger.info('Then run:') + logger.info( + `jeju login --network ${network} --address ${address} --signature `, + ) + return + } + + // Complete external login with provided signature + const signature = options.signature + const isValid = await verifyMessage({ address, message, signature }) + if (!isValid) { + logger.error('Signature verification failed') + return + } + + const authResult = await authenticateWithDWS( + address, + signature, + message, + network, + ) + + const credentials: Credentials = { + version: 1, + network, + address, + keyType: 'external', + authToken: authResult.token, + createdAt: Date.now(), + expiresAt: authResult.expiresAt, + } - logger.info('Then run:') + saveCredentials(credentials) + logger.success(`Logged in as ${address}`) + logger.info(`Network: ${network}`) + logger.info(`Expires at: ${new Date(authResult.expiresAt).toLocaleDateString()}`) logger.info( - `jeju login --network ${network} --signature --address `, + 'Use `jeju login` again if your token expires or you change wallets.', ) return } diff --git a/packages/monitoring/package.json b/packages/monitoring/package.json index c4aca5921..fee33f5f9 100644 --- a/packages/monitoring/package.json +++ b/packages/monitoring/package.json @@ -3,6 +3,7 @@ "version": "0.1.0", "dependencies": { "@elysiajs/cors": "^1.4.0", + "@jejunetwork/auth": "workspace:*", "@jejunetwork/cache": "workspace:*", "@jejunetwork/config": "workspace:*", "@jejunetwork/types": "workspace:*", diff --git a/packages/sqlit/src/client.ts b/packages/sqlit/src/client.ts index b447eb7a7..d1d97470e 100644 --- a/packages/sqlit/src/client.ts +++ b/packages/sqlit/src/client.ts @@ -296,7 +296,12 @@ export class SQLitClient { return rows.filter(isRecordRow) } - if (isRecordRow(rows[0])) { + const firstRow = rows[0] + if (firstRow === undefined) { + return [] + } + + if (isRecordRow(firstRow)) { return rows.filter(isRecordRow) }