Browse Source

Enhance user role management in App.svelte by adding fetchUserRole function; update UI to display user role badge upon login. Modify Follows struct to include owners and adjust access level logic in acl package for improved permission handling.

main
mleku 3 months ago
parent
commit
332b9b05f7
No known key found for this signature in database
  1. 66
      app/web/src/App.svelte
  2. 22
      pkg/acl/follows.go
  3. 4
      pkg/interfaces/acl/acl.go

66
app/web/src/App.svelte

@ -2,14 +2,13 @@
import LoginModal from './LoginModal.svelte'; import LoginModal from './LoginModal.svelte';
import { initializeNostrClient, fetchUserProfile } from './nostr.js'; import { initializeNostrClient, fetchUserProfile } from './nostr.js';
export let name;
let isDarkTheme = false; let isDarkTheme = false;
let showLoginModal = false; let showLoginModal = false;
let isLoggedIn = false; let isLoggedIn = false;
let userPubkey = ''; let userPubkey = '';
let authMethod = ''; let authMethod = '';
let userProfile = null; let userProfile = null;
let userRole = '';
let showSettingsDrawer = false; let showSettingsDrawer = false;
let selectedTab = 'export'; let selectedTab = 'export';
let isSearchMode = false; let isSearchMode = false;
@ -45,6 +44,8 @@
isLoggedIn = true; isLoggedIn = true;
userPubkey = storedPubkey; userPubkey = storedPubkey;
authMethod = storedAuthMethod; authMethod = storedAuthMethod;
// Fetch user role for already logged in users
fetchUserRole();
} }
} }
@ -92,6 +93,9 @@
} catch (error) { } catch (error) {
console.error('Failed to load profile:', error); console.error('Failed to load profile:', error);
} }
// Fetch user role/permissions
await fetchUserRole();
} }
function handleLogout() { function handleLogout() {
@ -99,6 +103,7 @@
userPubkey = ''; userPubkey = '';
authMethod = ''; authMethod = '';
userProfile = null; userProfile = null;
userRole = '';
showSettingsDrawer = false; showSettingsDrawer = false;
// Clear stored authentication // Clear stored authentication
@ -187,6 +192,28 @@
console.error('Failed to auto-load profile:', error); console.error('Failed to auto-load profile:', error);
} }
} }
async function fetchUserRole() {
if (!isLoggedIn || !userPubkey) {
userRole = '';
return;
}
try {
const response = await fetch(`/api/permissions/${userPubkey}`);
if (response.ok) {
const data = await response.json();
userRole = data.permission || '';
console.log('User role loaded:', userRole);
} else {
console.error('Failed to fetch user role:', response.status);
userRole = '';
}
} catch (error) {
console.error('Error fetching user role:', error);
userRole = '';
}
}
</script> </script>
<!-- Header --> <!-- Header -->
@ -201,12 +228,16 @@
bind:value={searchQuery} bind:value={searchQuery}
on:keydown={handleSearchKeydown} on:keydown={handleSearchKeydown}
placeholder="Search..." placeholder="Search..."
autofocus
/> />
</div> </div>
{:else} {:else}
<div class="header-title"> <div class="header-title">
<span class="app-title">ORLY?</span> <span class="app-title">
ORLY? dashboard
{#if isLoggedIn && userRole}
<span class="permission-badge">{userRole}</span>
{/if}
</span>
</div> </div>
{/if} {/if}
<button class="search-btn" on:click={toggleSearchMode}> <button class="search-btn" on:click={toggleSearchMode}>
@ -258,7 +289,7 @@
<!-- Main Content --> <!-- Main Content -->
<main class="main-content"> <main class="main-content">
<h1>Hello {name}!</h1> <p>Log in to access your user dashboard</p>
</main> </main>
</div> </div>
@ -394,6 +425,20 @@
font-size: 1em; font-size: 1em;
font-weight: 600; font-weight: 600;
color: var(--text-color); color: var(--text-color);
display: flex;
align-items: center;
gap: 0.5rem;
}
.permission-badge {
font-size: 0.7em;
font-weight: 500;
padding: 0.2em 0.5em;
border-radius: 0.3em;
background-color: var(--primary);
color: white;
text-transform: uppercase;
letter-spacing: 0.05em;
} }
.search-input-container { .search-input-container {
@ -581,14 +626,6 @@
color: var(--text-color); color: var(--text-color);
} }
.main-content h1 {
color: #ff3e00;
text-transform: uppercase;
font-size: 4em;
font-weight: 100;
text-align: center;
}
@media (max-width: 640px) { @media (max-width: 640px) {
.header-content { .header-content {
padding: 0; padding: 0;
@ -603,9 +640,6 @@
padding: 1rem; padding: 1rem;
} }
.main-content h1 {
font-size: 2.5em;
}
} }
/* User Info Styles */ /* User Info Styles */

22
pkg/acl/follows.go

@ -40,6 +40,7 @@ type Follows struct {
pubs *publish.S pubs *publish.S
followsMx sync.RWMutex followsMx sync.RWMutex
admins [][]byte admins [][]byte
owners [][]byte
follows [][]byte follows [][]byte
updated chan struct{} updated chan struct{}
subsCancel context.CancelFunc subsCancel context.CancelFunc
@ -69,6 +70,16 @@ func (f *Follows) Configure(cfg ...any) (err error) {
err = errorf.E("both config and database must be set") err = errorf.E("both config and database must be set")
return return
} }
// add owners list
for _, owner := range f.cfg.Owners {
var own []byte
if o, e := bech32encoding.NpubOrHexToPublicKeyBinary(owner); chk.E(e) {
continue
} else {
own = o
}
f.owners = append(f.owners, own)
}
// find admin follow lists // find admin follow lists
f.followsMx.Lock() f.followsMx.Lock()
defer f.followsMx.Unlock() defer f.followsMx.Unlock()
@ -129,11 +140,13 @@ func (f *Follows) Configure(cfg ...any) (err error) {
} }
func (f *Follows) GetAccessLevel(pub []byte, address string) (level string) { func (f *Follows) GetAccessLevel(pub []byte, address string) (level string) {
if f.cfg == nil {
return "write"
}
f.followsMx.RLock() f.followsMx.RLock()
defer f.followsMx.RUnlock() defer f.followsMx.RUnlock()
for _, v := range f.owners {
if utils.FastEqual(v, pub) {
return "owner"
}
}
for _, v := range f.admins { for _, v := range f.admins {
if utils.FastEqual(v, pub) { if utils.FastEqual(v, pub) {
return "admin" return "admin"
@ -144,6 +157,9 @@ func (f *Follows) GetAccessLevel(pub []byte, address string) (level string) {
return "write" return "write"
} }
} }
if f.cfg == nil {
return "write"
}
return "read" return "read"
} }

4
pkg/interfaces/acl/acl.go

@ -6,6 +6,7 @@ import (
) )
const ( const (
None = "none"
// Read means read only // Read means read only
Read = "read" Read = "read"
// Write means read and write // Write means read and write
@ -14,9 +15,6 @@ const (
Admin = "admin" Admin = "admin"
// Owner means read, write, import/export, arbitrary delete and wipe // Owner means read, write, import/export, arbitrary delete and wipe
Owner = "owner" Owner = "owner"
// Group applies to communities and other groups; the content afterwards a
// set of comma separated <permission>:<pubkey> pairs designating permissions to groups.
Group = "group:"
) )
type I interface { type I interface {

Loading…
Cancel
Save