this repo has no description
1use std::collections::HashMap;
2use std::sync::LazyLock;
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
5pub enum ScopeCategory {
6 Core,
7 Transition,
8 Repo,
9 Blob,
10 Rpc,
11 Account,
12}
13
14impl ScopeCategory {
15 pub fn display_name(&self) -> &'static str {
16 match self {
17 ScopeCategory::Core => "Core Access",
18 ScopeCategory::Transition => "Transition",
19 ScopeCategory::Repo => "Repository",
20 ScopeCategory::Blob => "Media",
21 ScopeCategory::Rpc => "API Access",
22 ScopeCategory::Account => "Account",
23 }
24 }
25}
26
27#[derive(Debug, Clone)]
28pub struct ScopeDefinition {
29 pub scope: &'static str,
30 pub category: ScopeCategory,
31 pub required: bool,
32 pub description: &'static str,
33 pub display_name: &'static str,
34}
35
36pub static SCOPE_DEFINITIONS: LazyLock<HashMap<&'static str, ScopeDefinition>> =
37 LazyLock::new(|| {
38 let definitions = vec![
39 ScopeDefinition {
40 scope: "atproto",
41 category: ScopeCategory::Core,
42 required: true,
43 description: "Use AT Protocol OAuth (required for all sessions)",
44 display_name: "AT Protocol",
45 },
46 ScopeDefinition {
47 scope: "transition:generic",
48 category: ScopeCategory::Transition,
49 required: false,
50 description: "Generic transition scope for compatibility",
51 display_name: "Transition Access",
52 },
53 ScopeDefinition {
54 scope: "transition:chat.bsky",
55 category: ScopeCategory::Transition,
56 required: false,
57 description: "Access to Bluesky chat features",
58 display_name: "Chat Access",
59 },
60 ScopeDefinition {
61 scope: "transition:email",
62 category: ScopeCategory::Account,
63 required: false,
64 description: "Read your account email address",
65 display_name: "Email Access",
66 },
67 ScopeDefinition {
68 scope: "repo:*?action=create",
69 category: ScopeCategory::Repo,
70 required: false,
71 description: "Create new records in your repository",
72 display_name: "Create Records",
73 },
74 ScopeDefinition {
75 scope: "repo:*?action=update",
76 category: ScopeCategory::Repo,
77 required: false,
78 description: "Update existing records in your repository",
79 display_name: "Update Records",
80 },
81 ScopeDefinition {
82 scope: "repo:*?action=delete",
83 category: ScopeCategory::Repo,
84 required: false,
85 description: "Delete records from your repository",
86 display_name: "Delete Records",
87 },
88 ScopeDefinition {
89 scope: "blob:*/*",
90 category: ScopeCategory::Blob,
91 required: false,
92 description: "Upload images, videos, and other media files",
93 display_name: "Upload Media",
94 },
95 ];
96
97 definitions.into_iter().map(|d| (d.scope, d)).collect()
98 });
99
100#[allow(dead_code)]
101pub fn get_scope_definition(scope: &str) -> Option<&'static ScopeDefinition> {
102 SCOPE_DEFINITIONS.get(scope)
103}
104
105#[allow(dead_code)]
106pub fn is_valid_scope(scope: &str) -> bool {
107 if SCOPE_DEFINITIONS.contains_key(scope) {
108 return true;
109 }
110 if scope.starts_with("ref:") {
111 return true;
112 }
113 false
114}
115
116#[allow(dead_code)]
117pub fn get_required_scopes() -> Vec<&'static str> {
118 SCOPE_DEFINITIONS
119 .values()
120 .filter(|d| d.required)
121 .map(|d| d.scope)
122 .collect()
123}
124
125#[allow(dead_code)]
126pub fn format_scope_for_display(scope: &str) -> String {
127 if let Some(def) = get_scope_definition(scope) {
128 def.description.to_string()
129 } else if scope.starts_with("ref:") {
130 "Referenced scope".to_string()
131 } else {
132 format!("Access to {}", scope)
133 }
134}