Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
17 changes: 15 additions & 2 deletions misc/test-client-rust/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,26 @@ The main binary is a CLI tool for testing Brave Accounts API endpoints. Common u
- `cargo run -- --register --email user@example.com --password pass123` - Registration flow
- `cargo run -- --logout --token <auth_token>` - Logout with auth token
- `cargo run -- --enable-totp --token <auth_token>` - Enable TOTP 2FA
- `cargo run -- --add-webauthn-credential --token <auth_token>` - Add WebAuthn credential (opens browser)

## Architecture

### Core Structure
- `src/main.rs` - Main CLI entry point with argument parsing and flow control
- `src/util.rs` - Shared utilities for HTTP requests and response handling
- `src/webauthn.rs` - WebAuthn credential registration and authentication module with embedded web server
- `assets/webauthn.html` - Static HTML page for WebAuthn registration and login browser interaction

### Key Components
1. **OPAQUE Protocol Integration**: Uses `opaque-ke` crate for password-authenticated key exchange
2. **CLI Interface**: Built with `clap` for command-line argument parsing
3. **HTTP Client**: Uses `reqwest` blocking client for API communication
4. **2FA Support**: Integrates TOTP authentication and recovery keys
4. **2FA Support**: Integrates TOTP authentication, WebAuthn credentials, and recovery keys
5. **Session Management**: Handles auth tokens, sessions, and logout functionality
6. **WebAuthn Server**: Temporary tiny_http server for browser-based WebAuthn registration and login flows
- `/` route - Serves unified WebAuthn page for both registration and login
- `/request` route - Provides WebAuthn challenge with operation type (register/login)
- `/response` route - Receives WebAuthn credential response

### Authentication Flow
The client implements the OPAQUE protocol for secure password authentication:
Expand All @@ -41,13 +48,19 @@ The client implements the OPAQUE protocol for secure password authentication:
### API Endpoints
Base URL defaults to `http://localhost:8080` with these main endpoints:
- `/v2/accounts/password/*` - Password operations
- `/v2/accounts/2fa/totp/*` - TOTP 2FA operations
- `/v2/accounts/2fa/webauthn/*` - WebAuthn 2FA operations
- `/v2/auth/*` - Authentication and validation
- `/v2/verify/*` - Email verification flows
- `/v2/sessions/*` - Session management
- `/v2/keys/*` - User key storage operations

### Dependencies
- `opaque-ke` - OPAQUE protocol implementation (local path dependency)
- `argon2` - Key stretching function for OPAQUE
- `reqwest` - HTTP client library
- `totp-rs` - TOTP code generation for 2FA
- `clap` - CLI argument parsing
- `clap` - CLI argument parsing
- `tiny_http` - Lightweight HTTP server for WebAuthn flow
- `open` - Opens URLs in default browser
- `dialoguer` - Interactive CLI prompts and selection menus
110 changes: 104 additions & 6 deletions misc/test-client-rust/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions misc/test-client-rust/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,5 @@ hex = "0.4"
serde_json = "1.0"
totp-rs = { version = "5.7", features = ["otpauth"] }
open = "5.3"
tiny_http = "0.12"
dialoguer = { version = "0.12", default-features = false }
100 changes: 100 additions & 0 deletions misc/test-client-rust/assets/webauthn.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
<!DOCTYPE html>
<html>

<head>
<meta charset="UTF-8">
<title>WebAuthn</title>
</head>

<body>
<pre id="status">Starting WebAuthn...</pre>

<script>
const statusEl = document.getElementById('status');

function log(message) {
statusEl.textContent += '\n' + message;
}

async function performRegistration(requestData) {
log('Prompting user for credential registration...');

// Parse the JSON options into the format expected by the API
const options = PublicKeyCredential.parseCreationOptionsFromJSON(requestData.publicKey);

const credential = await navigator.credentials.create({ publicKey: options });

if (!credential) {
throw new Error('No credential returned');
}

log('Credential created');
return credential;
}

async function performLogin(requestData) {
log('Prompting user for authentication...');

// Parse the JSON options into the format expected by the API
const options = PublicKeyCredential.parseRequestOptionsFromJSON(requestData.publicKey);

const credential = await navigator.credentials.get({ publicKey: options });

if (!credential) {
throw new Error('No credential returned');
}

log('Authentication successful');
return credential;
}

async function performWebAuthn() {
try {
log('Fetching request...');

const initResponse = await fetch('/request');
if (!initResponse.ok) {
throw new Error('Failed to get request');
}

const requestData = await initResponse.json();
const operation = requestData.operation;
log(`Request received (${operation})`);

let credential;
if (operation === 'register') {
credential = await performRegistration(requestData.request);
} else if (operation === 'login') {
credential = await performLogin(requestData.request);
} else {
throw new Error('Unknown operation: ' + operation);
}

log('Sending credential to server...');

const finalizeResponse = await fetch('/response', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(credential)
});

if (!finalizeResponse.ok) {
throw new Error('Failed to send credential');
}

log(`WebAuthn ${operation} completed successfully!`);
window.close();

} catch (error) {
log('Error: ' + error.message);
console.error(error);
}
}

performWebAuthn();
</script>
</body>

</html>
Loading