feat(auth): require login on wallet, chat, and castle

These three standalone apps have no meaningful public view — wallet
needs the LNbits token to do anything, chat needs Nostr keys to
decrypt DMs, castle's accounting only makes sense for an account
holder. Their previous router guards only redirected when a route
explicitly opted in via meta.requiresAuth: an unauth user could land
on the home page and see broken / empty content with no signal.

Replaces each app's per-route guard with a strict policy: any
navigation to a path other than /login requires auth, otherwise
bounce to /login. /login itself bounces an authenticated user back
to /.

Affected guards:
  - src/wallet-app/app.ts
  - src/chat-app/app.ts
  - src/accounting-app/app.ts

Forum / market / tasks / activities keep the existing per-route
guard so they remain browseable without an account by default.
That browsing-vs-auth choice will become operator-configurable per
deployment (tracked separately).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Padreug 2026-05-02 10:48:35 +02:00
commit 4605703e20
3 changed files with 27 additions and 21 deletions

View file

@ -136,16 +136,18 @@ export async function createAppInstance() {
await auth.initialize() await auth.initialize()
// Auth guard — only redirect for routes that explicitly require auth // Auth guard — only redirect for routes that explicitly require auth
// Castle has no public view — every non-login route requires auth.
router.beforeEach(async (to, _from, next) => { router.beforeEach(async (to, _from, next) => {
const requiresAuth = to.meta.requiresAuth === true if (to.path === '/login') {
if (auth.isAuthenticated.value) next('/')
if (requiresAuth && !auth.isAuthenticated.value) { else next()
next('/login') return
} else if (to.path === '/login' && auth.isAuthenticated.value) {
next('/')
} else {
next()
} }
if (!auth.isAuthenticated.value) {
next('/login')
return
}
next()
}) })
// Global error handling // Global error handling

View file

@ -91,16 +91,18 @@ export async function createAppInstance() {
const { auth } = await import('@/composables/useAuthService') const { auth } = await import('@/composables/useAuthService')
await auth.initialize() await auth.initialize()
// Chat has no public view — every non-login route requires auth.
router.beforeEach(async (to, _from, next) => { router.beforeEach(async (to, _from, next) => {
const requiresAuth = to.meta.requiresAuth === true if (to.path === '/login') {
if (auth.isAuthenticated.value) next('/')
if (requiresAuth && !auth.isAuthenticated.value) { else next()
next('/login') return
} else if (to.path === '/login' && auth.isAuthenticated.value) {
next('/')
} else {
next()
} }
if (!auth.isAuthenticated.value) {
next('/login')
return
}
next()
}) })
app.config.errorHandler = (err, _vm, info) => { app.config.errorHandler = (err, _vm, info) => {

View file

@ -95,16 +95,18 @@ export async function createAppInstance() {
const { auth } = await import('@/composables/useAuthService') const { auth } = await import('@/composables/useAuthService')
await auth.initialize() await auth.initialize()
// Wallet has no public view — every non-login route requires auth.
router.beforeEach(async (to, _from, next) => { router.beforeEach(async (to, _from, next) => {
const requiresAuth = to.meta.requiresAuth === true if (to.path === '/login') {
if (auth.isAuthenticated.value) next('/')
if (requiresAuth && !auth.isAuthenticated.value) { else next()
next('/login') return
} else if (to.path === '/login' && auth.isAuthenticated.value) {
next('/')
} else {
next()
} }
if (!auth.isAuthenticated.value) {
next('/login')
return
}
next()
}) })
app.config.errorHandler = (err, _vm, info) => { app.config.errorHandler = (err, _vm, info) => {