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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

## 🚀 New Features in This Fork

*Features coming soon...*
- User authentication with workspace isolation and multi-user support

### Docker (Recommended)
```bash
Expand Down
1 change: 0 additions & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,3 @@ services:
container_name: Restfox
ports:
- "${CUSTOM_PORT_ON_HOST:-4004}:4004"
detach: true
14 changes: 10 additions & 4 deletions packages/ui/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@
import WorkspacesFrame from '@/components/WorkspacesFrame.vue'
import Frame from '@/components/Frame.vue'
import ReloadPrompt from '@/components/ReloadPrompt.vue'
import AuthInterceptor from '@/components/AuthInterceptor.vue'
</script>

<template>
<WorkspacesFrame v-if="appLoaded && !activeWorkspaceLoaded" />
<Frame v-if="appLoaded && activeWorkspaceLoaded" />
<ReloadPrompt />
<alert-confirm-prompt attach-to-window="true" />
<AuthInterceptor>
<WorkspacesFrame v-if="appLoaded && !activeWorkspaceLoaded" />
<Frame v-if="appLoaded && activeWorkspaceLoaded" />
<ReloadPrompt />
<alert-confirm-prompt attach-to-window="true" />
</AuthInterceptor>
</template>

<script>
Expand Down Expand Up @@ -444,6 +447,9 @@ export default {
}

initStoragePersistence()

// Inicializar autenticación
this.$store.dispatch('initializeAuth')
},
beforeUnmount() {
window.removeEventListener('keydown', this.handleGlobalKeydown)
Expand Down
175 changes: 175 additions & 0 deletions packages/ui/src/components/AuthDebug.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
<template>
<div class="auth-debug" v-if="showDebug">
<div class="auth-debug-header">
🔐 Auth Debug
<button @click="toggleDebug" class="debug-close">×</button>
</div>
<div class="auth-debug-content">
<div><strong>Current User:</strong> {{ currentUser ? currentUser.username : 'None' }}</div>
<div><strong>Is Authenticated:</strong> {{ isAuthenticated ? 'Yes' : 'No' }}</div>
<div><strong>Session:</strong> {{ currentSession ? 'Active' : 'None' }}</div>
<div><strong>Modal State:</strong> {{ showAuthModal ? 'Open' : 'Closed' }}</div>
<button @click="openAuthModal" class="debug-button">Open Auth Modal</button>
</div>
</div>
<button v-else @click="toggleDebug" class="debug-toggle">🔐 Debug</button>

<AuthModal
v-model:showModal="showAuthModal"
@authSuccess="handleAuthSuccess"
/>

<!-- Simple test modal -->
<div v-if="showAuthModal" class="test-modal-overlay" @click="closeAuthModal">
<div class="test-modal-content" @click.stop>
<h3>Test Modal Working!</h3>
<p>Modal State: {{ showAuthModal }}</p>
<button @click="closeAuthModal">Close Modal</button>
</div>
</div>
</template>

<script>
import { mapState } from 'vuex'
import AuthModal from './modals/AuthModal.vue'

export default {
name: 'AuthDebug',
components: {
AuthModal
},
data() {
return {
showDebug: false,
showAuthModal: false
}
},
computed: {
...mapState(['currentUser', 'currentSession']),
isAuthenticated() {
return !!this.currentUser && !!this.currentSession
}
},
methods: {
toggleDebug() {
this.showDebug = !this.showDebug
},
openAuthModal() {
console.log('Opening auth modal...')
this.showAuthModal = true
console.log('showAuthModal is now:', this.showAuthModal)
},
closeAuthModal() {
console.log('Closing auth modal...')
this.showAuthModal = false
},
async handleAuthSuccess(authData) {
try {
await this.$store.dispatch('setCurrentUser', authData.user)
await this.$store.dispatch('setCurrentSession', authData.session)

this.$toast.success(
authData.isNewUser
? `¡Bienvenido ${authData.user.username}! Tu cuenta ha sido creada.`
: `¡Bienvenido de vuelta, ${authData.user.username}!`
)
this.showAuthModal = false
} catch (error) {
console.error('Error setting user session:', error)
this.$toast.error('Autenticación exitosa pero hubo un error configurando tu sesión.')
}
}
}
}
</script>

<style scoped>
.debug-toggle {
position: fixed;
bottom: 20px;
right: 20px;
z-index: 10000;
background: #007acc;
color: white;
border: none;
padding: 8px 12px;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
}

.auth-debug {
position: fixed;
bottom: 20px;
right: 20px;
z-index: 10000;
background: white;
border: 2px solid #007acc;
border-radius: 8px;
padding: 12px;
min-width: 250px;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
font-size: 12px;
}

.auth-debug-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
font-weight: bold;
color: #007acc;
}

.debug-close {
background: none;
border: none;
font-size: 16px;
cursor: pointer;
color: #999;
}

.auth-debug-content div {
margin-bottom: 4px;
}

.debug-button {
background: #007acc;
color: white;
border: none;
padding: 4px 8px;
border-radius: 4px;
cursor: pointer;
font-size: 11px;
margin-top: 8px;
}

.debug-button:hover {
background: #005a9e;
}

.test-modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(255, 0, 0, 0.5);
display: flex;
align-items: flex-end;
justify-content: flex-end;
z-index: 999999;
padding: 20px;
}

.test-modal-content {
background: white;
padding: 20px;
border-radius: 8px;
border: 3px solid red;
text-align: center;
margin-bottom: 80px;
margin-right: 0;
}
</style>
127 changes: 127 additions & 0 deletions packages/ui/src/components/AuthInterceptor.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
<template>
<div>
<!-- El contenido del slot se renderiza normalmente -->
<slot />

<!-- Modal de autenticación que se muestra cuando se requiere -->
<AuthModal
v-model:showModal="showAuthModal"
@authSuccess="handleAuthSuccess"
/>

<!-- Modal de información sobre autenticación requerida -->
<modal
v-if="showAuthInfoModal"
title="Authentication Required"
v-model="showAuthInfoModal"
width="400px"
>
<div style="text-align: center; padding: 1rem;">
<div style="font-size: 3rem; color: var(--primary-color, #007bff); margin-bottom: 1rem;">
<i class="fas fa-shield-alt"></i>
</div>
<h3 style="margin: 0 0 1rem 0;">Sign In Required</h3>
<p style="margin-bottom: 1.5rem; color: var(--text-color-secondary); line-height: 1.5;">
{{ authMessage }}
</p>
<div style="display: flex; gap: 0.5rem; justify-content: center;">
<button
type="button"
class="button"
@click="showAuthInfoModal = false"
>
Cancel
</button>
<button
type="button"
class="button button-primary"
@click="openSignIn"
>
Sign In
</button>
</div>
</div>
</modal>
</div>
</template>

<script>
import { ref, onMounted, onUnmounted } from 'vue'
import { useStore } from 'vuex'
import AuthModal from './modals/AuthModal.vue'
import Modal from './Modal.vue'
import { authMiddleware, getAuthMessage } from '../composables/useAuth'

export default {
name: 'AuthInterceptor',
components: {
AuthModal,
Modal
},
setup() {
const store = useStore()
const showAuthModal = ref(false)
const showAuthInfoModal = ref(false)
const authMessage = ref('')
const pendingAction = ref(null)

// Registrar callback para cuando se requiere autenticación
const handleAuthRequired = (action = 'default', callback = null) => {
authMessage.value = getAuthMessage(action)
pendingAction.value = callback

// Mostrar modal de información primero
showAuthInfoModal.value = true
}

// Abrir modal de sign in
const openSignIn = () => {
showAuthInfoModal.value = false
showAuthModal.value = true
}

// Manejar éxito de autenticación
const handleAuthSuccess = (authData) => {
showAuthModal.value = false

// Si había una acción pendiente, ejecutarla
if (pendingAction.value && typeof pendingAction.value === 'function') {
try {
pendingAction.value()
} catch (error) {
console.error('Error executing pending action:', error)
}
pendingAction.value = null
}
}

// Registrar el interceptor globalmente
onMounted(() => {
authMiddleware.onAuthRequired('global-interceptor', handleAuthRequired)
})

onUnmounted(() => {
authMiddleware.removeAuthCallback('global-interceptor')
})

return {
showAuthModal,
showAuthInfoModal,
authMessage,
handleAuthSuccess,
openSignIn
}
}
}
</script>

<style scoped>
.button-primary {
background-color: var(--primary-color, #007bff);
color: white;
}

.button-primary:hover {
background-color: var(--primary-hover-color, #0056b3);
}
</style>
6 changes: 5 additions & 1 deletion packages/ui/src/components/NavBar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@
Logs
</a>
</div>
<!-- User Menu Component -->
<UserMenu />
<span class="spacer"></span>
<div class="github-star">
<a class="gh-button-container" href="https://github.com/flawiddsouza/Restfox" rel="noopener" target="_blank" title="Star Restfox" aria-label="Star Restfox on GitHub">
Expand Down Expand Up @@ -122,6 +124,7 @@ import SettingsModal from './modals/SettingsModal.vue'
import EnvironmentModal from './modals/EnvironmentModal.vue'
import BackupAndRestoreModal from './modals/BackupAndRestoreModal.vue'
import LogsModal from './modals/LogsModal.vue'
import UserMenu from './UserMenu.vue'
import {
exportRestfoxCollection,
applyTheme,
Expand All @@ -144,7 +147,8 @@ export default {
SettingsModal,
EnvironmentModal,
BackupAndRestoreModal,
LogsModal
LogsModal,
UserMenu
},
props: {
nav: String,
Expand Down
Loading