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
59 changes: 59 additions & 0 deletions apps/web/src/config/auth.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// apps/web/src/config/auth.config.ts
export const authConfig = {
MOCK_AUTH: true,

// Default user (Guest)
MOCK_USER: {
id: 'mock-guest-123',
email: 'guest@stellarent.com',
name: 'Demo Guest',
role: 'guest' as const,
hostStatus: 'none' as const,
hasProperties: false,
propertyCount: 0,
hasBookings: true,
bookingCount: 3,
publicKey: 'GBXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
authType: 'email' as const,
},

// Mock users for testing
MOCK_USERS: {
guest: {
id: 'mock-guest-123',
name: 'Demo Guest',
email: 'guest@stellarent.com',
role: 'guest' as const,
hostStatus: 'none' as const,
hasProperties: false,
propertyCount: 0,
hasBookings: true,
bookingCount: 3,
},
host: {
id: 'mock-host-456',
name: 'Demo Host',
email: 'host@stellarent.com',
role: 'host' as const,
hostStatus: 'verified' as const,
hasProperties: true,
propertyCount: 3,
hasBookings: false,
bookingCount: 0,
hostSince: '2024-01-15',
},
dual: {
id: 'mock-dual-789',
name: 'Demo Dual User',
email: 'dual@stellarent.com',
role: 'dual' as const,
hostStatus: 'verified' as const,
hasProperties: true,
propertyCount: 2,
hasBookings: true,
bookingCount: 5,
hostSince: '2024-03-20',
},
},
Comment on lines +20 to +57
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add missing fields to maintain consistency with MOCK_USER.

The MOCK_USERS objects are missing the publicKey and authType fields that are present in MOCK_USER (lines 16-17). In apps/web/src/hooks/auth/use-auth.tsx at line 62, the code accesses mockUser.authType, which will be undefined for these mock users, though it does fall back to 'email'. To maintain consistency and avoid confusion, add these fields to each mock user.

Apply this diff to add the missing fields:

   MOCK_USERS: {
     guest: {
       id: 'mock-guest-123',
       name: 'Demo Guest',
       email: 'guest@stellarent.com',
       role: 'guest' as const,
       hostStatus: 'none' as const,
       hasProperties: false,
       propertyCount: 0,
       hasBookings: true,
       bookingCount: 3,
+      publicKey: 'GBXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
+      authType: 'email' as const,
     },
     host: {
       id: 'mock-host-456',
       name: 'Demo Host',
       email: 'host@stellarent.com',
       role: 'host' as const,
       hostStatus: 'verified' as const,
       hasProperties: true,
       propertyCount: 3,
       hasBookings: false,
       bookingCount: 0,
       hostSince: '2024-01-15',
+      publicKey: 'GBXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
+      authType: 'email' as const,
     },
     dual: {
       id: 'mock-dual-789',
       name: 'Demo Dual User',
       email: 'dual@stellarent.com',
       role: 'dual' as const,
       hostStatus: 'verified' as const,
       hasProperties: true,
       propertyCount: 2,
       hasBookings: true,
       bookingCount: 5,
       hostSince: '2024-03-20',
+      publicKey: 'GBXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
+      authType: 'email' as const,
     },
   },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Mock users for testing
MOCK_USERS: {
guest: {
id: 'mock-guest-123',
name: 'Demo Guest',
email: 'guest@stellarent.com',
role: 'guest' as const,
hostStatus: 'none' as const,
hasProperties: false,
propertyCount: 0,
hasBookings: true,
bookingCount: 3,
},
host: {
id: 'mock-host-456',
name: 'Demo Host',
email: 'host@stellarent.com',
role: 'host' as const,
hostStatus: 'verified' as const,
hasProperties: true,
propertyCount: 3,
hasBookings: false,
bookingCount: 0,
hostSince: '2024-01-15',
},
dual: {
id: 'mock-dual-789',
name: 'Demo Dual User',
email: 'dual@stellarent.com',
role: 'dual' as const,
hostStatus: 'verified' as const,
hasProperties: true,
propertyCount: 2,
hasBookings: true,
bookingCount: 5,
hostSince: '2024-03-20',
},
},
// Mock users for testing
MOCK_USERS: {
guest: {
id: 'mock-guest-123',
name: 'Demo Guest',
email: 'guest@stellarent.com',
role: 'guest' as const,
hostStatus: 'none' as const,
hasProperties: false,
propertyCount: 0,
hasBookings: true,
bookingCount: 3,
publicKey: 'GBXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
authType: 'email' as const,
},
host: {
id: 'mock-host-456',
name: 'Demo Host',
email: 'host@stellarent.com',
role: 'host' as const,
hostStatus: 'verified' as const,
hasProperties: true,
propertyCount: 3,
hasBookings: false,
bookingCount: 0,
hostSince: '2024-01-15',
publicKey: 'GBXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
authType: 'email' as const,
},
dual: {
id: 'mock-dual-789',
name: 'Demo Dual User',
email: 'dual@stellarent.com',
role: 'dual' as const,
hostStatus: 'verified' as const,
hasProperties: true,
propertyCount: 2,
hasBookings: true,
bookingCount: 5,
hostSince: '2024-03-20',
publicKey: 'GBXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
authType: 'email' as const,
},
},
🤖 Prompt for AI Agents
In apps/web/src/config/auth.config.ts around lines 20 to 57, the MOCK_USERS
entries (guest, host, dual) are missing the publicKey and authType fields
present on MOCK_USER, causing undefined authType when code reads
mockUser.authType; update each mock user object to include a publicKey (use a
mock value consistent with existing id patterns, e.g., 'pk-mock-...') and an
authType (e.g., 'email') so the shape matches MOCK_USER and downstream code no
longer sees missing fields.

} as const;

162 changes: 75 additions & 87 deletions apps/web/src/hooks/auth/use-auth.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,21 @@ import { signTransactionWithFreighter } from '~/lib/freighter-utils';
import { getNetworkName, getNetworkPassphrase, logNetworkInfo } from '~/lib/network-utils';
import { apiUtils, authAPI } from '../../services/api';
import { useWallet } from '../useWallet';
import { authConfig } from '~/config/auth.config';

export type Role = 'guest' | 'host' | 'dual';

interface User {
id: string;
email?: string;
name: string;
role?: Role;
hostStatus?: 'none' | 'pending' | 'verified';
hasProperties?: boolean;
propertyCount?: number;
hasBookings?: boolean;
bookingCount?: number;
hostSince?: string;
publicKey?: string;
authType?: 'email' | 'wallet';
}
Expand All @@ -23,6 +33,7 @@ interface AuthContextType {
logout: () => Promise<void>;
isAuthenticated: boolean;
authType: 'email' | 'wallet' | null;
loginAsMockUser: (role: Role) => void; // NEW: login as mock user
}

const AuthContext = createContext<AuthContextType>({
Expand All @@ -34,6 +45,7 @@ const AuthContext = createContext<AuthContextType>({
logout: async () => {},
isAuthenticated: false,
authType: null,
loginAsMockUser: () => {},
});

export function AuthProvider({ children }: { children: ReactNode }) {
Expand All @@ -42,26 +54,37 @@ export function AuthProvider({ children }: { children: ReactNode }) {
const [authType, setAuthType] = useState<'email' | 'wallet' | null>(null);
const { network, networkPassphrase, getPublicKey } = useWallet();

// --- MOCK USER LOGIN ---
const loginAsMockUser = (role: Role) => {
if (!authConfig.MOCK_USERS[role]) return;
const mockUser = authConfig.MOCK_USERS[role];
setUser(mockUser);
setAuthType(mockUser.authType || 'email');
localStorage.setItem('user', JSON.stringify(mockUser));
localStorage.setItem('authType', mockUser.authType || 'email');
console.log('✅ Logged in as mock user:', role);
};
Comment on lines +57 to +66
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add console logs to list available mock users.

The PR objectives specify: "Add console logs to list available mock users and current role in use-auth hook." Currently, only a confirmation log is emitted after login. Consider adding a log that lists all available mock roles when mock auth is initialized.

Add this code at the start of the loginAsMockUser function:

   const loginAsMockUser = (role: Role) => {
+    console.log('📋 Available mock roles:', Object.keys(authConfig.MOCK_USERS));
     if (!authConfig.MOCK_USERS[role]) return;
     const mockUser = authConfig.MOCK_USERS[role];
     setUser(mockUser);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// --- MOCK USER LOGIN ---
const loginAsMockUser = (role: Role) => {
if (!authConfig.MOCK_USERS[role]) return;
const mockUser = authConfig.MOCK_USERS[role];
setUser(mockUser);
setAuthType(mockUser.authType || 'email');
localStorage.setItem('user', JSON.stringify(mockUser));
localStorage.setItem('authType', mockUser.authType || 'email');
console.log('✅ Logged in as mock user:', role);
};
// --- MOCK USER LOGIN ---
const loginAsMockUser = (role: Role) => {
console.log('📋 Available mock roles:', Object.keys(authConfig.MOCK_USERS));
if (!authConfig.MOCK_USERS[role]) return;
const mockUser = authConfig.MOCK_USERS[role];
setUser(mockUser);
setAuthType(mockUser.authType || 'email');
localStorage.setItem('user', JSON.stringify(mockUser));
localStorage.setItem('authType', mockUser.authType || 'email');
console.log('✅ Logged in as mock user:', role);
};
🤖 Prompt for AI Agents
In apps/web/src/hooks/auth/use-auth.tsx around lines 57 to 66, the
loginAsMockUser function only logs a confirmation after logging in; add an
initial console log that lists all available mock user roles and the currently
selected role when mock auth is initialized or when loginAsMockUser is invoked.
Specifically, at the start of loginAsMockUser log
Object.keys(authConfig.MOCK_USERS) (or a mapped list of roles) and the incoming
role value, then proceed with the existing checks and login flow; keep logs
concise and guard against missing MOCK_USERS to avoid runtime errors.


// --- CHECK LOCAL STORAGE ON INIT ---
useEffect(() => {
const checkAuth = () => {
if (authConfig.MOCK_AUTH) {
const storedUser = localStorage.getItem('user');
const storedAuthType = localStorage.getItem('authType') as 'email' | 'wallet' | null;

if (storedUser) {
try {
const parsedUser = JSON.parse(storedUser);
setUser(parsedUser);
setUser(JSON.parse(storedUser));
setAuthType(storedAuthType);
console.log('🔥 MOCK AUTH MODE ENABLED');
} catch (error) {
console.error('Error parsing stored user:', error);
apiUtils.clearAuth();
}
}
};

checkAuth();
}
}, []);
Comment on lines +68 to 85
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Log available mock users on initialization.

The console log at line 78 only fires when a stored user is found. Per the PR objectives, you should log the available mock users when MOCK_AUTH is enabled, regardless of whether a stored user exists. This helps developers understand what mock roles are available.

Apply this diff:

   useEffect(() => {
     if (authConfig.MOCK_AUTH) {
+      console.log('🔥 MOCK AUTH MODE ENABLED');
+      console.log('📋 Available mock roles:', Object.keys(authConfig.MOCK_USERS));
+      
       const storedUser = localStorage.getItem('user');
       const storedAuthType = localStorage.getItem('authType') as 'email' | 'wallet' | null;
 
       if (storedUser) {
         try {
           setUser(JSON.parse(storedUser));
           setAuthType(storedAuthType);
-          console.log('🔥 MOCK AUTH MODE ENABLED');
         } catch (error) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// --- CHECK LOCAL STORAGE ON INIT ---
useEffect(() => {
const checkAuth = () => {
if (authConfig.MOCK_AUTH) {
const storedUser = localStorage.getItem('user');
const storedAuthType = localStorage.getItem('authType') as 'email' | 'wallet' | null;
if (storedUser) {
try {
const parsedUser = JSON.parse(storedUser);
setUser(parsedUser);
setUser(JSON.parse(storedUser));
setAuthType(storedAuthType);
console.log('🔥 MOCK AUTH MODE ENABLED');
} catch (error) {
console.error('Error parsing stored user:', error);
apiUtils.clearAuth();
}
}
};
checkAuth();
}
}, []);
// --- CHECK LOCAL STORAGE ON INIT ---
useEffect(() => {
if (authConfig.MOCK_AUTH) {
console.log('🔥 MOCK AUTH MODE ENABLED');
console.log('📋 Available mock roles:', Object.keys(authConfig.MOCK_USERS));
const storedUser = localStorage.getItem('user');
const storedAuthType = localStorage.getItem('authType') as 'email' | 'wallet' | null;
if (storedUser) {
try {
setUser(JSON.parse(storedUser));
setAuthType(storedAuthType);
} catch (error) {
console.error('Error parsing stored user:', error);
apiUtils.clearAuth();
}
}
}
}, []);
🤖 Prompt for AI Agents
In apps/web/src/hooks/auth/use-auth.tsx around lines 68 to 85, when MOCK_AUTH is
enabled the current console.log only runs if a stored user exists; update the
effect so it logs the available mock users immediately when authConfig.MOCK_AUTH
is true (before or regardless of checking localStorage). Retrieve the mock users
from the existing config (e.g., authConfig.MOCK_USERS or the module that defines
mock roles), and call console.log with a clear message and the list of mock
users; keep the existing stored-user parsing/setting logic intact and avoid
logging any sensitive tokens.


// --- EXISTING LOGIN ---
const login = async (email: string, password: string) => {
setIsLoading(true);
try {
Expand All @@ -75,7 +98,6 @@ export function AuthProvider({ children }: { children: ReactNode }) {
name: response.user.name,
authType: 'email',
};

setUser(userData);
setAuthType('email');
localStorage.setItem('authToken', response.token);
Expand All @@ -89,6 +111,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
}
};

// --- EXISTING REGISTER ---
const register = async (email: string, password: string, fullName: string) => {
setIsLoading(true);
try {
Expand All @@ -102,7 +125,6 @@ export function AuthProvider({ children }: { children: ReactNode }) {
name: response.user.name,
authType: 'email',
};

setUser(userData);
setAuthType('email');
localStorage.setItem('authToken', response.token);
Expand All @@ -116,96 +138,60 @@ export function AuthProvider({ children }: { children: ReactNode }) {
}
};

// --- EXISTING WALLET LOGIN ---
const loginWithWallet = async () => {
setIsLoading(true);
try {
// Get public key (this handles connection if needed)
const walletPublicKey = await getPublicKey();
if (!walletPublicKey) {
throw new Error('Failed to get public key from wallet');
}
if (!walletPublicKey) throw new Error('Failed to get public key from wallet');

console.log('🔑 Using public key:', walletPublicKey);

// Debug network information
logNetworkInfo(network, 'TESTNET');

// Request challenge from backend
const challengeResponse = await authAPI.requestChallenge(walletPublicKey);

try {
const { TransactionBuilder, Account, Memo, BASE_FEE } = await import(
'@stellar/stellar-sdk'
);
const { TransactionBuilder, Account, Memo, BASE_FEE } = await import('@stellar/stellar-sdk');
const challengeText = challengeResponse.challenge;
if (challengeText.length > 28) throw new Error('Challenge too long for transaction memo');

const walletNetworkPassphrase = networkPassphrase || getNetworkPassphrase(network);
const targetNetworkName = getNetworkName(network);

const account = new Account(walletPublicKey, '0');
const transaction = new TransactionBuilder(account, {
fee: BASE_FEE,
networkPassphrase: walletNetworkPassphrase,
})
.addMemo(Memo.text(challengeText))
.setTimeout(30)
.build();
Comment on lines +152 to +166
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Validate memo length in bytes, not characters.

At line 154, the validation challengeText.length > 28 checks the string length (UTF-16 code units), but Memo.text() enforces a 28-byte limit. If the challenge contains multi-byte UTF-8 characters, the byte count could exceed 28 even when the string length is ≤28, causing the transaction to fail later. Validate the byte length instead.

Apply this diff:

       const { TransactionBuilder, Account, Memo, BASE_FEE } = await import('@stellar/stellar-sdk');
       const challengeText = challengeResponse.challenge;
-      if (challengeText.length > 28) throw new Error('Challenge too long for transaction memo');
+      const byteLength = new TextEncoder().encode(challengeText).length;
+      if (byteLength > 28) throw new Error('Challenge too long for transaction memo (max 28 bytes)');
 
       const walletNetworkPassphrase = networkPassphrase || getNetworkPassphrase(network);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const { TransactionBuilder, Account, Memo, BASE_FEE } = await import('@stellar/stellar-sdk');
const challengeText = challengeResponse.challenge;
if (challengeText.length > 28) throw new Error('Challenge too long for transaction memo');
const walletNetworkPassphrase = networkPassphrase || getNetworkPassphrase(network);
const targetNetworkName = getNetworkName(network);
const account = new Account(walletPublicKey, '0');
const transaction = new TransactionBuilder(account, {
fee: BASE_FEE,
networkPassphrase: walletNetworkPassphrase,
})
.addMemo(Memo.text(challengeText))
.setTimeout(30)
.build();
const { TransactionBuilder, Account, Memo, BASE_FEE } = await import('@stellar/stellar-sdk');
const challengeText = challengeResponse.challenge;
const byteLength = new TextEncoder().encode(challengeText).length;
if (byteLength > 28) throw new Error('Challenge too long for transaction memo (max 28 bytes)');
const walletNetworkPassphrase = networkPassphrase || getNetworkPassphrase(network);
const targetNetworkName = getNetworkName(network);
const account = new Account(walletPublicKey, '0');
const transaction = new TransactionBuilder(account, {
fee: BASE_FEE,
networkPassphrase: walletNetworkPassphrase,
})
.addMemo(Memo.text(challengeText))
.setTimeout(30)
.build();
🤖 Prompt for AI Agents
In apps/web/src/hooks/auth/use-auth.tsx around lines 152 to 166, the code
currently checks challengeText.length > 28 (characters) but Memo.text() limits
the memo to 28 bytes; change the validation to measure UTF-8 byte length (e.g.,
use TextEncoder().encode(challengeText).length or
Buffer.byteLength(challengeText, 'utf8')) and throw the same error if the byte
length exceeds 28 so multi-byte characters are correctly validated before
creating the transaction.


const signResult = await signTransactionWithFreighter(transaction.toXDR(), {
network: targetNetworkName,
networkPassphrase: walletNetworkPassphrase,
address: walletPublicKey,
});

if (signResult.error) throw new Error(signResult.error);
if (!signResult.signedTxXdr) throw new Error('No signed transaction returned');

const authResponse = await authAPI.authenticateWallet(
walletPublicKey,
signResult.signedTxXdr,
challengeResponse.challenge
);

const challengeText = challengeResponse.challenge;
if (challengeText.length > 28) {
throw new Error('Challenge too long for transaction memo');
}

// Use the wallet's current network, but ensure it matches what we expect
const walletNetworkPassphrase = networkPassphrase || getNetworkPassphrase(network);
const targetNetworkName = getNetworkName(network);

console.log('🌐 Transaction Network Info:');
console.log(' Wallet Network:', network);
console.log(' Wallet Passphrase:', networkPassphrase);
console.log(' Using Passphrase:', walletNetworkPassphrase);
console.log(' Target Network Name:', targetNetworkName);

// Create memo-only transaction (no operations as backend expects)
const account = new Account(walletPublicKey, '0');
const transaction = new TransactionBuilder(account, {
fee: BASE_FEE,
networkPassphrase: walletNetworkPassphrase, // Use wallet's actual network
})
.addMemo(Memo.text(challengeText)) // Only memo, no operations
.setTimeout(30)
.build();

console.log('📝 Transaction created for network:', walletNetworkPassphrase);

// Sign transaction - use the wallet's current network
const signResult = await signTransactionWithFreighter(transaction.toXDR(), {
network: targetNetworkName,
networkPassphrase: walletNetworkPassphrase,
address: walletPublicKey,
});

if (signResult.error) {
throw new Error(signResult.error);
}

if (!signResult.signedTxXdr) {
throw new Error('No signed transaction returned');
}
const userData: User = {
id: (authResponse.user as { id: string; name?: string }).id,
name: (authResponse.user as { id: string; name?: string }).name || 'Wallet User',
publicKey: walletPublicKey,
authType: 'wallet',
};

console.log('✅ Transaction signed successfully');

// Authenticate with backend
const authResponse = await authAPI.authenticateWallet(
walletPublicKey,
signResult.signedTxXdr,
challengeResponse.challenge
);

const userData: User = {
id: (authResponse.user as { id: string; name?: string }).id,
name: (authResponse.user as { id: string; name?: string }).name || 'Wallet User',
publicKey: walletPublicKey,
authType: 'wallet',
};

setUser(userData);
setAuthType('wallet');
localStorage.setItem('user', JSON.stringify(userData));
localStorage.setItem('authType', 'wallet');

console.log('🎉 Wallet authentication successful!');
} catch (signError) {
console.error('Error creating or signing transaction:', signError);
throw new Error('Failed to sign authentication transaction');
}
setUser(userData);
setAuthType('wallet');
localStorage.setItem('user', JSON.stringify(userData));
localStorage.setItem('authType', 'wallet');
console.log('🎉 Wallet authentication successful!');
} catch (error) {
console.error('Wallet login failed:', error);
throw error;
Expand All @@ -214,6 +200,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
}
};

// --- EXISTING LOGOUT ---
const logout = async () => {
setIsLoading(true);
try {
Expand Down Expand Up @@ -241,6 +228,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
logout,
isAuthenticated,
authType,
loginAsMockUser, // mock user method
}}
>
{children}
Expand Down