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
11 changes: 11 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,14 @@ node_modules
.vscode

.gitpod.yml

# Local development and analysis files (never commit)
/.github/agents/
/.github/instructions/
/.github/prompts/
/.github/skills/
/.github/FUNDING.yml
/.gsd/
/memory/
/ANALYZE.md

4 changes: 2 additions & 2 deletions packages/react-native/src/store/userStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ const useUserStore = create((set) => ({
isUserAuthenticated: false,
setIsUserAuthenticated: (isUserAuthenticated) => set(() => ({ isUserAuthenticated })),

password: null,
setPassword: (password) => set(() => ({ password })),
// SECURITY FIX (Issue #1263): Removed password storage from global state
// Passwords should never be stored in client-side state (CWE-312)

emailoruser: null,
setEmailorUser: (emailoruser) => set(() => ({ emailoruser })),
Expand Down
15 changes: 11 additions & 4 deletions packages/react/src/hooks/useRCAuth.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useContext } from 'react';
import { useToastBarDispatch } from '@embeddedchat/ui-elements';
import RCContext from '../context/RCInstance';
import { useUserStore, totpModalStore, useLoginStore } from '../store';
import { useUserStore, totpModalStore, useLoginStore, useTotpCredentialsStore } from '../store';

export const useRCAuth = () => {
const { RCInstance } = useContext(RCContext);
Expand All @@ -18,7 +18,9 @@ export const useRCAuth = () => {
const setIsUserAuthenticated = useUserStore(
(state) => state.setIsUserAuthenticated
);
const setPassword = useUserStore((state) => state.setPassword);
// SECURITY FIX (Issue #1263): Use ephemeral TOTP credentials store
const setTotpCredentials = useTotpCredentialsStore((state) => state.setTotpCredentials);
const clearTotpCredentials = useTotpCredentialsStore((state) => state.clearTotpCredentials);
const setEmailorUser = useUserStore((state) => state.setEmailorUser);
const dispatchToastMessage = useToastBarDispatch();

Expand All @@ -33,7 +35,9 @@ export const useRCAuth = () => {
});
} else {
if (res.error === 'totp-required') {
setPassword(password);
// SECURITY FIX (Issue #1263): Store credentials temporarily in ephemeral TOTP store
// These are cleared immediately after TOTP completes
setTotpCredentials(userOrEmail, password);
setEmailorUser(userOrEmail);
setIsLoginModalOpen(false);
setIsTotpModalOpen(true);
Expand All @@ -55,7 +59,8 @@ export const useRCAuth = () => {
setIsUserAuthenticated(true);
setIsTotpModalOpen(false);
setEmailorUser(null);
setPassword(null);
// SECURITY FIX (Issue #1263): Clear ephemeral TOTP credentials after success
clearTotpCredentials();
dispatchToastMessage({
type: 'success',
message: 'Successfully logged in',
Expand All @@ -64,6 +69,8 @@ export const useRCAuth = () => {
}
} catch (e) {
console.error('An error occurred while setting up user', e);
// SECURITY FIX (Issue #1263): Clear ephemeral TOTP credentials on error
clearTotpCredentials();
dispatchToastMessage({
type: 'error',
message:
Expand Down
1 change: 1 addition & 0 deletions packages/react/src/store/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export { default as useMessageStore } from './messageStore';
export { default as useUserStore } from './userStore';
export { default as useMemberStore } from './memberStore';
export { default as totpModalStore } from './totpmodalStore';
export { default as useTotpCredentialsStore } from './totpCredentialsStore';
export { default as useSearchMessageStore } from './searchMessageStore';
export { default as useLoginStore } from './loginStore';
export { default as useChannelStore } from './channelStore';
Expand Down
21 changes: 21 additions & 0 deletions packages/react/src/store/totpCredentialsStore.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { create } from 'zustand';

// SECURITY FIX (Issue #1263): Temporary TOTP credentials store
// This store holds credentials ONLY during the TOTP flow (seconds)
// and is cleared immediately after authentication completes or fails.
// Unlike the global userStore, this is intentionally ephemeral.

const useTotpCredentialsStore = create((set) => ({
tempEmailOrUser: null,
tempPassword: null,

// Store credentials temporarily during TOTP flow
setTotpCredentials: (emailOrUser, password) =>
set({ tempEmailOrUser: emailOrUser, tempPassword: password }),

// Clear credentials after TOTP completes
clearTotpCredentials: () =>
set({ tempEmailOrUser: null, tempPassword: null }),
}));

export default useTotpCredentialsStore;
5 changes: 3 additions & 2 deletions packages/react/src/store/userStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,9 @@ const useUserStore = create((set) => ({
setIsUserAuthenticated: (isUserAuthenticated) =>
set(() => ({ isUserAuthenticated })),
setCanSendMsg: (canSendMsg) => set(() => ({ canSendMsg })),
password: null,
setPassword: (password) => set(() => ({ password })),
// SECURITY FIX (Issue #1263): Removed password storage from global state
// Passwords should never be stored in client-side state (CWE-312)
// TOTP flow now uses component-level state instead
emailoruser: null,
setEmailorUser: (emailoruser) => set(() => ({ emailoruser })),
roles: [],
Expand Down
15 changes: 11 additions & 4 deletions packages/react/src/views/TotpModal/TwoFactorTotpModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,32 +8,39 @@ import {
Input,
Button,
} from '@embeddedchat/ui-elements';
import { totpModalStore, useUserStore } from '../../store';
import { totpModalStore, useUserStore, useTotpCredentialsStore } from '../../store';

// SECURITY FIX (Issue #1263): TOTP modal now uses ephemeral credentials store
export default function TotpModal({ handleLogin }) {
const [accessCode, setAccessCode] = useState(null);
const isTotpModalOpen = totpModalStore((state) => state.isTotpModalOpen);
const setIsTotpModalOpen = totpModalStore(
(state) => state.setIsTotpModalOpen
);
const password = useUserStore((state) => state.password);
// SECURITY FIX (Issue #1263): Retrieve credentials from ephemeral TOTP store
const { tempEmailOrUser, tempPassword } = useTotpCredentialsStore();
const clearTotpCredentials = useTotpCredentialsStore((state) => state.clearTotpCredentials);
const emailoruser = useUserStore((state) => state.emailoruser);

const handleSubmit = (e) => {
e.preventDefault();

if (password !== null && emailoruser !== null) {
handleLogin(emailoruser, password, accessCode);
if (tempPassword && (tempEmailOrUser || emailoruser)) {
handleLogin(tempEmailOrUser || emailoruser, tempPassword, accessCode);
}
setAccessCode(undefined);
};

const handleClose = () => {
// SECURITY FIX (Issue #1263): Clear ephemeral credentials when modal closes
clearTotpCredentials();
setIsTotpModalOpen(false);
};

const handleEdit = (e) => {
setAccessCode(e.target.value);
};

return isTotpModalOpen ? (
<>
<GenericModal
Expand Down