Skip to content

Commit

Permalink
Refactor structure (#541)
Browse files Browse the repository at this point in the history
  • Loading branch information
lanedirt committed Jan 24, 2025
1 parent 536b482 commit be30752
Show file tree
Hide file tree
Showing 4 changed files with 150 additions and 185 deletions.
42 changes: 36 additions & 6 deletions browser-extensions/chrome/src/components/Login.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { useState } from 'react';
import Button from './Button';
import { srpService } from '../services/SrpService';
import { srpUtility } from '../utilities/SrpUtility';
import EncryptionUtility from '../utilities/EncryptionUtility';

const Login: React.FC = () => {
const [credentials, setCredentials] = useState({
Expand All @@ -16,19 +17,48 @@ const Login: React.FC = () => {

try {
// 1. Initiate login to get salt and server ephemeral
const loginResponse = await srpService.initiateLogin(credentials.username);
const loginResponse = await srpUtility.initiateLogin(credentials.username);

console.log(credentials);
// 1. Derive key from password using Argon2id
const passwordHashString = await EncryptionUtility.deriveKeyFromPassword(
credentials.password,
loginResponse.salt,
loginResponse.encryptionType,
loginResponse.encryptionSettings
);

// 2. Validate login with SRP protocol
const validationResponse = await srpService.validateLogin(
const validationResponse = await srpUtility.validateLogin(
credentials.username,
credentials.password,
passwordHashString,
rememberMe,
loginResponse
);

console.log(validationResponse);
// Store access and refresh token
if (validationResponse.token) {
localStorage.setItem('accessToken', validationResponse.token!.token);
localStorage.setItem('refreshToken', validationResponse.token!.refreshToken);
}
else {
throw new Error('Login failed -- no token returned');
}

// Make another API call trying to get latest vault
const vaultResponse = await fetch('https://localhost:7223/v1/Vault', {
headers: {
'Authorization': `Bearer ${localStorage.getItem('accessToken')}`
}
});

const vaultResponseJson = await vaultResponse.json();

console.log('Vault response:')
console.log('--------------------------------');
console.log(vaultResponseJson);
console.log('Encrypted blob:');
console.log(vaultResponseJson.vault.blob);


// 3. Handle 2FA if required
/*if (validationResponse.requiresTwoFactor) {
Expand Down
179 changes: 0 additions & 179 deletions browser-extensions/chrome/src/services/SrpService.tsx

This file was deleted.

38 changes: 38 additions & 0 deletions browser-extensions/chrome/src/utilities/EncryptionUtility.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import argon2 from 'argon2-browser/dist/argon2-bundled.min.js';

/**
* Utility class for encryption operations which includes Argon2id hashing.
*/
class EncryptionUtility {
public static async deriveKeyFromPassword(
password: string,
salt: string,
encryptionType: string = 'Argon2id',
encryptionSettings: string = '{"Iterations":1,"MemorySize":1024,"DegreeOfParallelism":4}'
): Promise<string> {
const settings = JSON.parse(encryptionSettings);

try {
if (encryptionType !== 'Argon2Id') {
throw new Error('Unsupported encryption type');
}

const hash = await argon2.hash({
pass: password,
salt: salt,
time: settings.Iterations,
mem: settings.MemorySize,
parallelism: settings.DegreeOfParallelism,
hashLen: 32,
type: 2, // 0 = Argon2d, 1 = Argon2i, 2 = Argon2id
});

return hash.hashHex.toUpperCase();
} catch (error) {
console.error('Argon2 hashing failed:', error);
throw error;
}
}
}

export default EncryptionUtility;
76 changes: 76 additions & 0 deletions browser-extensions/chrome/src/utilities/SrpUtility.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import srp from 'secure-remote-password/client'

interface LoginInitiateResponse {
salt: string;
serverEphemeral: string;
encryptionType: string;
encryptionSettings: string;
}

interface ValidateLoginResponse {
requiresTwoFactor: boolean;
token?: {
token: string;
refreshToken: string;
};
serverSessionProof: string;
}

/**
* Utility class for SRP authentication operations.
*/
class SrpUtility {
public async initiateLogin(username: string): Promise<LoginInitiateResponse> {
// TODO: make base API URL configurable. The extension will have to support both official
// and self-hosted instances.
const response = await fetch('https://localhost:7223/v1/Auth/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ username: username.toLowerCase().trim() })
});

if (!response.ok) {
throw new Error('Login initiation failed');
}

return await response.json();
}

public async validateLogin(
username: string,
passwordHashString: string,
rememberMe: boolean,
loginResponse: LoginInitiateResponse
): Promise<ValidateLoginResponse> {
// 2. Generate client ephemeral
const clientEphemeral = srp.generateEphemeral()

// 3. Derive private key
const privateKey = srp.derivePrivateKey(loginResponse.salt, username, passwordHashString);

// 4. Derive session (simplified for example)
const sessionProof = srp.deriveSession(clientEphemeral.secret, loginResponse.serverEphemeral, loginResponse.salt, username, privateKey);

// 5. Send validation request
// TODO: make base API URL configurable. The extension will have to support both official
// and self-hosted instances.
const response = await fetch('https://localhost:7223/v1/Auth/validate', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
username: username.toLowerCase().trim(),
rememberMe: rememberMe,
clientPublicEphemeral: clientEphemeral.public,
clientSessionProof: sessionProof.proof,
})
});

return await response.json();
}
}

export const srpUtility = new SrpUtility();

0 comments on commit be30752

Please sign in to comment.