Skip to content
Merged
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,4 @@ typesense-data/
# Hermit (toolchain manager cache)
.hermit/
doc/
repos/
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 8 additions & 4 deletions crates/git-credential-nostr/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,10 +180,14 @@ fn main() {
let method = parse_method(wwwauth)
.unwrap_or_else(|| fail("server did not include method hint in WWW-Authenticate"));

// Sign the repo root URL, not the full endpoint URL.
// Git's credential helper is invoked once (for info/refs) and the token is reused
// for subsequent requests (upload-pack, receive-pack). The server verifies against
// the canonical repo root, so we strip endpoint suffixes here.
// Sign the repo root URL — strip endpoint suffixes to get the canonical form.
//
// Git's credential helper is invoked once (for the initial info/refs GET) and the
// token is reused for subsequent requests (upload-pack, receive-pack POST). The
// server verifies against the bare repo root URL.
//
// Git's credential protocol does NOT pass query strings in the `path` field, so
// we never see `?service=...` here — just the path component.
let repo_path = path
.split_once("/info/refs")
.map(|(prefix, _)| prefix)
Expand Down
27 changes: 26 additions & 1 deletion crates/sprout-core/src/channel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,10 @@ impl FromStr for ChannelType {
// ── Member role ──────────────────────────────────────────────────────────────

/// A member's role within a channel.
///
/// The hierarchy for permission checks is: Owner > Admin > Member > Guest.
/// Bot is a **separate designation** — it is not part of the linear hierarchy.
/// Use [`MemberRole::permission_level`] for numeric comparisons in authorization.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MemberRole {
/// Full control — can manage members and delete the channel.
Expand All @@ -106,7 +110,7 @@ pub enum MemberRole {
Member,
/// Read-only external participant.
Guest,
/// Automated agent or integration.
/// Automated agent or integration (not in the role hierarchy).
Bot,
}

Expand All @@ -126,6 +130,27 @@ impl MemberRole {
pub fn is_elevated(&self) -> bool {
matches!(self, Self::Owner | Self::Admin)
}

/// Numeric permission level for authorization comparisons.
///
/// Higher = more privileged. Bot returns 0 (must use explicit grants).
/// Use `role.permission_level() >= required.permission_level()` for checks.
pub fn permission_level(self) -> u8 {
match self {
Self::Owner => 4,
Self::Admin => 3,
Self::Member => 2,
Self::Guest => 1,
Self::Bot => 0,
}
}

/// Returns true if this role meets or exceeds the required role's permission level.
///
/// Bot never meets any requirement (returns false for all non-Bot requirements).
pub fn has_at_least(self, required: MemberRole) -> bool {
self.permission_level() >= required.permission_level()
}
}

impl fmt::Display for MemberRole {
Expand Down
Loading