Merge pull request 'fix(auth): don't require a Nostr pubkey to be considered logged in' (#180) from fix/auth-guard-no-pubkey-requirement into dev

Reviewed-on: #180
This commit is contained in:
padreug 2026-10-08 18:46:03 +00:00
commit 54c1c990e1
2 changed files with 29 additions and 9 deletions

View file

@ -49,7 +49,8 @@ export async function createAppInstance() {
})
// Chat has no public view — every non-login route requires auth.
installStrictAuthGuard(router)
// Chat is Nostr end-to-end — it cannot work without an identity.
installStrictAuthGuard(router, { requirePubkey: true })
const pinia = createPinia()

View file

@ -14,12 +14,12 @@ import type { Router, RouteRecordRaw } from 'vue-router'
* mismatch: guards register early but await this promise before reading
* auth state. Phase 3 calls markAuthReady() once auth is initialized.
*/
type AuthUserLike = { value: { pubkey?: string } | null }
type AuthUserLike = { value: { id?: string; pubkey?: string } | null }
type AuthLike = {
isAuthenticated: { value: boolean }
// Populated after server-validated getCurrentUser() in auth.checkAuth().
// Guards require BOTH isAuthenticated and a user with a pubkey — token
// presence alone is not enough (issue #36).
// Guards require BOTH isAuthenticated and a server-populated user —
// token presence alone is not enough (issue #36).
currentUser: AuthUserLike
}
@ -33,20 +33,39 @@ export function markAuthReady(auth: AuthLike): void {
/**
* Belt-and-suspenders auth check: token presence in localStorage isn't
* sufficient — the server must have confirmed the token represents a real
* session, which is signalled by currentUser being populated with a pubkey.
* session, which is signalled by currentUser being populated.
*
* Keyed on `id`, never `pubkey`. Every account has an id; a Nostr pubkey
* is optional — Lightning-only accounts have none. Using pubkey as the
* "server answered" marker locked those accounts out of every
* strict-guard app: login succeeded, isAuthenticated flipped true, pubkey
* stayed empty, isFullyAuthed returned false, and the guard bounced them
* back to /login indefinitely. The #36 protection is unchanged — a bare
* token in localStorage still does not satisfy this.
*/
function isFullyAuthed(auth: AuthLike): boolean {
return auth.isAuthenticated.value && !!auth.currentUser?.value?.pubkey
return auth.isAuthenticated.value && !!auth.currentUser?.value?.id
}
/**
* Strict guard — every non-/login route requires auth.
* Used by wallet, chat, libra (no public view).
* Used by wallet, chat, accounting (no public view).
*
* `requirePubkey` additionally demands a Nostr identity. Only chat sets
* it: chat is Nostr end-to-end and cannot function without a key. Wallet
* and accounting are payment/ledger surfaces and must stay reachable
* without one — per ADR-0001, LNbits is a liquidity service, not an
* identity provider, so core payment flows cannot be gated on identity.
*/
export function installStrictAuthGuard(router: Router): void {
export function installStrictAuthGuard(
router: Router,
opts: { requirePubkey?: boolean } = {},
): void {
router.beforeEach(async (to) => {
const auth = await authReady
const authed = isFullyAuthed(auth)
const authed =
isFullyAuthed(auth) &&
(!opts.requirePubkey || !!auth.currentUser?.value?.pubkey)
if (to.path === '/login') {
return authed ? '/' : true
}