objective categorical abstract machine language personal data server
65
fork

Configure Feed

Select the types of activity you want to include in your feed.

Display OAuth scopes on authorize page

futurGH d0d6a72c 3bcc7b36

+771 -101
+8
frontend/src/components/ClientOnly.mlx
··· 1 + open React 2 + 3 + let[@react.component] make ~(children : unit -> React.element) () = 4 + let is_mounted, set_mounted = useState (fun () -> false) in 5 + useEffect (fun () -> 6 + let () = set_mounted (fun _ -> true) in 7 + None ) ; 8 + if is_mounted then children () else null
-34
frontend/src/components/ReactAria.mlx
··· 5 5 end 6 6 [@@platform js] 7 7 8 - module Button = struct 9 - let make ~children ?className () = <button ?className>children</button> 10 - end 11 - [@@platform native] 12 - 13 8 module ListBox = struct 14 9 external make : children:React.element -> ?className:string -> React.element 15 10 = "ListBox" ··· 17 12 end 18 13 [@@platform js] 19 14 20 - module ListBox = struct 21 - let make ~children ?className () = <ul ?className>children</ul> 22 - end 23 - [@@platform native] 24 - 25 15 module ListBoxItem = struct 26 16 external make : 27 17 children:React.element ··· 33 23 end 34 24 [@@platform js] 35 25 36 - module ListBoxItem = struct 37 - let make ~children ?key:_key ?id ?className ?href:_href () = 38 - <li ?id ?className>children</li> 39 - end 40 - [@@platform native] 41 - 42 26 module Popover = struct 43 27 external make : 44 28 children:React.element ··· 49 33 end 50 34 [@@platform js] 51 35 52 - module Popover = struct 53 - let make ~children ?className ?style () = 54 - <div ?className ?style>children</div> 55 - end 56 - [@@platform native] 57 - 58 36 module Select = struct 59 37 external make : 60 38 children:React.element ··· 68 46 end 69 47 [@@platform js] 70 48 71 - module Select = struct 72 - let make ~children ?className ?name ?value ?defaultValue ?onChange () = 73 - <select ?className ?name ?value ?defaultValue ?onChange>children</select> 74 - end 75 - [@@platform native] 76 - 77 49 module SelectValue = struct 78 50 external make : ?children:React.element -> ?className:string -> React.element 79 51 = "SelectValue" 80 52 [@@mel.module "react-aria-components"] [@@react.component] 81 53 end 82 54 [@@platform js] 83 - 84 - module SelectValue = struct 85 - let make ?children ?className () = 86 - <span ?className>(Option.value children ~default:React.null)</span> 87 - end 88 - [@@platform native]
+25
frontend/src/icons/AtIcon.mlx
··· 1 + let[@react.component] make ?className ?(strokeWidth = "2") () = 2 + <svg 3 + ?className 4 + viewBox="0 0 24 24" 5 + fill="none" 6 + stroke="currentColor" 7 + strokeLinecap="round" 8 + strokeLinejoin="round" 9 + strokeWidth> 10 + <path 11 + d="M12 16C14.2091 16 16 14.2091 16 12C16 9.79086 14.2091 8 12 8C9.79086 \ 12 + 8 8 9.79086 8 12C8 14.2091 9.79086 16 12 16Z" 13 + /> 14 + <path 15 + d="M16 8.00001V13C16 13.7957 16.3161 14.5587 16.8787 15.1213C17.4413 \ 16 + 15.6839 18.2044 16 19 16C19.7957 16 20.5587 15.6839 21.1213 \ 17 + 15.1213C21.6839 14.5587 22 13.7957 22 13V12C22 9.74731 21.2394 \ 18 + 7.56061 19.8414 5.79418C18.4434 4.02775 16.49 2.78508 14.2975 \ 19 + 2.26752C12.1051 1.74996 9.80215 1.98782 7.76178 2.94256C5.72141 \ 20 + 3.89731 4.06318 5.513 3.05574 7.52787C2.0483 9.54274 1.75069 11.8387 \ 21 + 2.21111 14.0439C2.67154 16.249 3.86303 18.2341 5.59254 \ 22 + 19.6775C7.32205 21.1209 9.48825 21.9381 11.7402 21.9966C13.9921 \ 23 + 22.0552 16.1979 21.3516 18 20" 24 + /> 25 + </svg>
+15
frontend/src/icons/BlueskyIcon.mlx
··· 1 + let[@react.component] make ?className () = 2 + <svg ?className viewBox="0 0 24 24" fill="currentColor"> 3 + <path 4 + d="M5.76877 3.61834C8.29102 5.51189 11.0038 9.35139 12 11.4116C12.9962 \ 5 + 9.35139 15.709 5.51189 18.2312 3.61834C20.0511 2.25201 23 1.19485 23 \ 6 + 4.55884C23 5.23069 22.6148 10.2027 22.3889 11.0099C21.6036 13.8162 \ 7 + 18.742 14.532 16.1965 14.0987C20.6459 14.856 21.7778 17.3643 19.3333 \ 8 + 19.8726C14.6909 24.6364 12.6608 18.6774 12.1406 17.1505C12.0453 \ 9 + 16.8706 12.0007 16.7396 12 16.851C11.9993 16.7396 11.9547 16.8706 \ 10 + 11.8594 17.1505C11.3392 18.6774 9.30914 24.6364 4.66668 \ 11 + 19.8726C2.22224 17.3643 3.35408 14.856 7.80346 14.0987C5.25797 14.532 \ 12 + 2.39642 13.8162 1.61112 11.0099C1.38519 10.2027 1 5.23069 1 4.55884C1 \ 13 + 1.19485 3.94885 2.25201 5.76877 3.61834Z" 14 + /> 15 + </svg>
+33
frontend/src/icons/BoxesIcon.mlx
··· 1 + let[@react.component] make ?className ?(strokeWidth = "2") () = 2 + <svg 3 + ?className 4 + viewBox="0 0 24 24" 5 + fill="none" 6 + stroke="currentColor" 7 + strokeLinecap="round" 8 + strokeLinejoin="round" 9 + strokeWidth> 10 + <path 11 + d="M12 19L8.03 21.38C7.71894 21.5669 7.36289 21.6656 7 21.6656C6.63711 \ 12 + 21.6656 6.28106 21.5669 5.97 21.38L2.97 19.58C2.67476 19.4026 2.43033 \ 13 + 19.152 2.26039 18.8524C2.09045 18.5528 2.00075 18.2144 2 \ 14 + 17.87V14.63C2.00075 14.2856 2.09045 13.9472 2.26039 13.6476C2.43033 \ 15 + 13.348 2.67476 13.0974 2.97 12.92L7 10.5L12 13.5M12 19V13.5M12 \ 16 + 19L15.97 21.38C16.2811 21.5669 16.6371 21.6656 17 21.6656C17.3629 \ 17 + 21.6656 17.7189 21.5669 18.03 21.38L21.03 19.58C21.3252 19.4026 \ 18 + 21.5697 19.152 21.7396 18.8524C21.9096 18.5528 21.9992 18.2144 22 \ 19 + 17.87V14.63C21.9992 14.2856 21.9096 13.9472 21.7396 13.6476C21.5697 \ 20 + 13.348 21.3252 13.0974 21.03 12.92L17 10.5L12 13.5M12 13.5L7 16.5M12 \ 21 + 13.5L17 16.5M7 16.5L2.26 13.65M7 16.5V21.67M17 16.5L21.74 13.65M17 \ 22 + 16.5V21.67" 23 + /> 24 + <path 25 + d="M12 13.4996L7 10.4996V6.1296C7.00075 5.78518 7.09045 5.44678 7.26039 \ 26 + 5.14719C7.43033 4.84761 7.67476 4.59698 7.97 4.4196L10.97 \ 27 + 2.6196C11.2811 2.43272 11.6371 2.33398 12 2.33398C12.3629 2.33398 \ 28 + 12.7189 2.43272 13.03 2.6196L16.03 4.4196C16.3252 4.59698 16.5697 \ 29 + 4.84761 16.7396 5.14719C16.9096 5.44678 16.9992 5.78518 17 \ 30 + 6.1296V10.4996L12 13.4996ZM12 13.4996V7.9996M12 7.9996L7.26 5.1496M12 \ 31 + 7.9996L16.74 5.1496" 32 + /> 33 + </svg>
+2 -2
frontend/src/icons/CircleAlertIcon.mlx
··· 1 - let[@react.component] make ?className () = 1 + let[@react.component] make ?className ?(strokeWidth = "2") () = 2 2 <svg 3 3 ?className 4 4 viewBox="0 0 24 24" ··· 6 6 stroke="currentColor" 7 7 strokeLinecap="round" 8 8 strokeLinejoin="round" 9 - strokeWidth="2"> 9 + strokeWidth> 10 10 <circle cx="12" cy="12" r="10" /> <path d="M12 8v4M12 16h.01" /> 11 11 </svg>
+32
frontend/src/icons/FolderGitIcon.mlx
··· 1 + let[@react.component] make ?className ?(strokeWidth = "2") () = 2 + <svg 3 + ?className 4 + viewBox="0 0 24 24" 5 + fill="none" 6 + stroke="currentColor" 7 + strokeLinecap="round" 8 + strokeLinejoin="round" 9 + strokeWidth> 10 + <path 11 + d="M18 19C16.6739 19 15.4021 18.4732 14.4645 17.5355C13.5268 16.5979 13 \ 12 + 15.3261 13 14V22" 13 + /> 14 + <path 15 + d="M9 20.0001H4C3.46957 20.0001 2.96086 19.7894 2.58579 19.4143C2.21071 \ 16 + 19.0392 2 18.5305 2 18.0001V5.0001C2 4.46966 2.21071 3.96096 2.58579 \ 17 + 3.58588C2.96086 3.21081 3.46957 3.0001 4 3.0001H7.9C8.23449 2.99682 \ 18 + 8.56445 3.07748 8.8597 3.23472C9.15495 3.39195 9.40604 3.62072 9.59 \ 19 + 3.9001L10.4 5.1001C10.5821 5.37663 10.83 5.60362 11.1215 \ 20 + 5.7607C11.413 5.91778 11.7389 6.00004 12.07 6.0001H20C20.5304 6.0001 \ 21 + 21.0391 6.21081 21.4142 6.58588C21.7893 6.96096 22 7.46966 22 \ 22 + 8.0001V13.0001" 23 + /> 24 + <path 25 + d="M13 14C14.1046 14 15 13.1046 15 12C15 10.8954 14.1046 10 13 \ 26 + 10C11.8954 10 11 10.8954 11 12C11 13.1046 11.8954 14 13 14Z" 27 + /> 28 + <path 29 + d="M20 21C21.1046 21 22 20.1046 22 19C22 17.8954 21.1046 17 20 \ 30 + 17C18.8954 17 18 17.8954 18 19C18 20.1046 18.8954 21 20 21Z" 31 + /> 32 + </svg>
+18
frontend/src/icons/MailIcon.mlx
··· 1 + let[@react.component] make ?className ?(strokeWidth = "2") () = 2 + <svg 3 + ?className 4 + viewBox="0 0 24 24" 5 + fill="none" 6 + stroke="currentColor" 7 + strokeLinecap="round" 8 + strokeLinejoin="round" 9 + strokeWidth> 10 + <path 11 + d="M22 7L13.009 12.727C12.7039 12.9042 12.3573 12.9976 12.0045 \ 12 + 12.9976C11.6517 12.9976 11.3051 12.9042 11 12.727L2 7" 13 + /> 14 + <path 15 + d="M20 4H4C2.89543 4 2 4.89543 2 6V18C2 19.1046 2.89543 20 4 \ 16 + 20H20C21.1046 20 22 19.1046 22 18V6C22 4.89543 21.1046 4 20 4Z" 17 + /> 18 + </svg>
+25
frontend/src/icons/MessageIcon.mlx
··· 1 + let[@react.component] make ?className ?(strokeWidth = "2") () = 2 + <svg 3 + ?className 4 + viewBox="0 0 24 24" 5 + fill="none" 6 + stroke="currentColor" 7 + strokeLinecap="round" 8 + strokeLinejoin="round" 9 + strokeWidth> 10 + <path 11 + d="M7.99994 11.9997H8.00994M11.9999 11.9997H12.0099M15.9999 \ 12 + 11.9997H16.0099M2.99194 16.3417C3.13897 16.7126 3.17171 17.119 \ 13 + 3.08594 17.5087L2.02094 20.7987C1.98662 20.9655 1.99549 21.1384 \ 14 + 2.04671 21.3008C2.09793 21.4633 2.1898 21.61 2.3136 21.727C2.43741 \ 15 + 21.844 2.58904 21.9274 2.75413 21.9693C2.91923 22.0113 3.0923 22.0104 \ 16 + 3.25694 21.9667L6.66994 20.9687C7.03765 20.8958 7.41846 20.9276 \ 17 + 7.76894 21.0607C9.90432 22.0579 12.3233 22.2689 14.5991 \ 18 + 21.6564C16.8749 21.0439 18.8612 19.6473 20.2076 17.7131C21.5541 \ 19 + 15.7788 22.1741 13.4311 21.9582 11.0842C21.7424 8.73738 20.7046 \ 20 + 6.54216 19.028 4.88589C17.3514 3.22962 15.1436 2.21873 12.7943 \ 21 + 2.03159C10.445 1.84445 8.10507 2.49308 6.18738 3.86303C4.26968 \ 22 + 5.23299 2.89747 7.23624 2.31283 9.51933C1.72819 11.8024 1.9687 \ 23 + 14.2186 2.99194 16.3417Z" 24 + /> 25 + </svg>
+15
frontend/src/icons/UploadIcon.mlx
··· 1 + let[@react.component] make ?className ?(strokeWidth = "2") () = 2 + <svg 3 + ?className 4 + viewBox="0 0 24 24" 5 + fill="none" 6 + stroke="currentColor" 7 + strokeLinecap="round" 8 + strokeLinejoin="round" 9 + strokeWidth> 10 + <path 11 + d="M12 3V15M12 3L17 8M12 3L7 8M21 15V19C21 19.5304 20.7893 20.0391 \ 12 + 20.4142 20.4142C20.0391 20.7893 19.5304 21 19 21H5C4.46957 21 3.96086 \ 13 + 20.7893 3.58579 20.4142C3.21071 20.0391 3 19.5304 3 19V15" 14 + /> 15 + </svg>
+25
frontend/src/icons/UsersRoundIcon.mlx
··· 1 + let[@react.component] make ?className ?(strokeWidth = "2") () = 2 + <svg 3 + ?className 4 + viewBox="0 0 24 24" 5 + fill="none" 6 + stroke="currentColor" 7 + strokeLinecap="round" 8 + strokeLinejoin="round" 9 + strokeWidth> 10 + <path 11 + d="M18 21C18 18.8783 17.1571 16.8434 15.6569 15.3431C14.1566 13.8429 \ 12 + 12.1217 13 10 13C7.87827 13 5.84344 13.8429 4.34315 15.3431C2.84285 \ 13 + 16.8434 2 18.8783 2 21" 14 + /> 15 + <path 16 + d="M10 13C12.7614 13 15 10.7614 15 8C15 5.23858 12.7614 3 10 3C7.23858 3 \ 17 + 5 5.23858 5 8C5 10.7614 7.23858 13 10 13Z" 18 + /> 19 + <path 20 + d="M22 20.0002C22 16.6302 20 13.5002 18 12.0002C18.6575 11.507 19.1832 \ 21 + 10.8593 19.5306 10.1145C19.8781 9.36965 20.0366 8.55066 19.9921 \ 22 + 7.73C19.9476 6.90933 19.7015 6.11228 19.2755 5.40939C18.8496 4.70651 \ 23 + 18.2569 4.11946 17.55 3.7002" 24 + /> 25 + </svg>
+571 -63
frontend/src/templates/OauthAuthorizePage.mlx
··· 1 + [@@@ocaml.warning "-26-27"] 2 + 1 3 open Melange_json.Primitives 2 4 open React 3 5 module Aria = ReactAria ··· 19 21 ; csrf_token: string } 20 22 [@@deriving json] 21 23 24 + module ScopesTable = struct 25 + type repo_action = Create | Update | Delete 26 + 27 + type parsed_repo_scope = {collections: string list; actions: repo_action list} 28 + 29 + type parsed_rpc_scope = {lxm: string; aud: string} 30 + 31 + type parsed_scope = 32 + | Email of [`Read | `Manage] 33 + | Identity of [`Handle | `Full] 34 + | Repo of parsed_repo_scope 35 + | Rpc of parsed_rpc_scope 36 + | Blob of string list (* mimetypes *) 37 + | Bluesky (* transition:generic or app.bsky.* *) 38 + | Chat (* transition:chat.bsky or chat.bsky.* *) 39 + | Atproto 40 + | Unknown of string 41 + 42 + let parse_scope scope = 43 + if scope = "atproto" then Atproto 44 + else if scope = "transition:generic" then Bluesky 45 + else if scope = "transition:chat.bsky" then Chat 46 + else if scope = "transition:email" then Email `Read 47 + else if String.starts_with ~prefix:"account:email" scope then 48 + if String.exists (fun c -> c = '=') scope then Email `Manage 49 + else Email `Read 50 + else if String.starts_with ~prefix:"identity:" scope then 51 + let rest = String.sub scope 9 (String.length scope - 9) in 52 + if rest = "*" || String.starts_with ~prefix:"*" rest then Identity `Full 53 + else Identity `Handle 54 + else if String.starts_with ~prefix:"repo:" scope then 55 + let rest = String.sub scope 5 (String.length scope - 5) in 56 + let parts = String.split_on_char '?' rest in 57 + let collection = 58 + match parts with coll :: _ when coll <> "" -> [coll] | _ -> ["*"] 59 + in 60 + let actions = 61 + if List.length parts > 1 then 62 + let params = List.nth parts 1 in 63 + if String.contains params '=' then 64 + List.filter_map 65 + (fun a -> 66 + if 67 + String.ends_with ~suffix:a params 68 + || String.contains params ',' 69 + then 70 + match a with 71 + | "create" -> 72 + Some Create 73 + | "update" -> 74 + Some Update 75 + | "delete" -> 76 + Some Delete 77 + | _ -> 78 + None 79 + else None ) 80 + ["create"; "update"; "delete"] 81 + |> function [] -> [Create; Update; Delete] | l -> l 82 + else [Create; Update; Delete] 83 + else [Create; Update; Delete] 84 + in 85 + if 86 + List.exists 87 + (fun c -> 88 + String.starts_with ~prefix:"app.bsky." c 89 + || String.starts_with ~prefix:"chat.bsky." c ) 90 + collection 91 + then 92 + if 93 + List.exists 94 + (fun c -> String.starts_with ~prefix:"chat.bsky." c) 95 + collection 96 + then Chat 97 + else Bluesky 98 + else Repo {collections= collection; actions} 99 + else if String.starts_with ~prefix:"rpc:" scope then 100 + let rest = String.sub scope 4 (String.length scope - 4) in 101 + let parts = String.split_on_char '?' rest in 102 + let lxm = match parts with l :: _ -> l | [] -> "*" in 103 + let aud = 104 + if List.length parts > 1 then 105 + let params = List.nth parts 1 in 106 + if String.starts_with ~prefix:"aud=" params then 107 + String.sub params 4 (String.length params - 4) 108 + else "*" 109 + else "*" 110 + in 111 + if String.starts_with ~prefix:"app.bsky." lxm then Bluesky 112 + else if String.starts_with ~prefix:"chat.bsky." lxm then Chat 113 + else Rpc {lxm; aud} 114 + else if String.starts_with ~prefix:"blob:" scope then 115 + let rest = String.sub scope 5 (String.length scope - 5) in 116 + Blob [rest] 117 + else Unknown scope 118 + 119 + type collection_actions = {create: bool; update: bool; delete: bool} 120 + 121 + module StringMap = Map.Make (String) 122 + 123 + let build_collection_actions_map repos = 124 + List.fold_left 125 + (fun acc r -> 126 + List.fold_left 127 + (fun acc coll -> 128 + let existing = 129 + StringMap.find_opt coll acc 130 + |> Option.value 131 + ~default:{create= false; update= false; delete= false} 132 + in 133 + let updated = 134 + { create= existing.create || List.mem Create r.actions 135 + ; update= existing.update || List.mem Update r.actions 136 + ; delete= existing.delete || List.mem Delete r.actions } 137 + in 138 + StringMap.add coll updated acc ) 139 + acc r.collections ) 140 + StringMap.empty repos 141 + 142 + let build_aud_lxms_map rpcs = 143 + List.fold_left 144 + (fun acc r -> 145 + let existing = 146 + StringMap.find_opt r.aud acc |> Option.value ~default:[] 147 + in 148 + let lxms = 149 + if List.mem r.lxm existing then existing else r.lxm :: existing 150 + in 151 + StringMap.add r.aud lxms acc ) 152 + StringMap.empty rpcs 153 + 154 + let merge_parsed_scopes scopes = 155 + let email = ref None in 156 + let identity = ref None in 157 + let repos = ref [] in 158 + let rpcs = ref [] in 159 + let blobs = ref [] in 160 + let has_bluesky = ref false in 161 + let has_chat = ref false in 162 + let unknowns = ref [] in 163 + List.iter 164 + (fun scope -> 165 + match parse_scope scope with 166 + | Email `Manage -> 167 + email := Some `Manage 168 + | Email `Read -> 169 + if !email = None then email := Some `Read 170 + | Identity `Full -> 171 + identity := Some `Full 172 + | Identity `Handle -> 173 + if !identity = None then identity := Some `Handle 174 + | Repo r -> 175 + repos := r :: !repos 176 + | Rpc r -> 177 + rpcs := r :: !rpcs 178 + | Blob mimes -> 179 + blobs := mimes @ !blobs 180 + | Bluesky -> 181 + has_bluesky := true 182 + | Chat -> 183 + has_chat := true 184 + | Atproto -> 185 + () 186 + | Unknown s -> 187 + unknowns := s :: !unknowns ) 188 + scopes ; 189 + ( !email 190 + , !identity 191 + , !repos 192 + , !rpcs 193 + , !blobs 194 + , !has_bluesky 195 + , !has_chat 196 + , !unknowns ) 197 + 198 + let[@react.component] make ~scopes () = 199 + let email, identity, repos, rpcs, blobs, has_bluesky, has_chat, unknowns = 200 + merge_parsed_scopes scopes 201 + in 202 + <div className="w-full mt-3 space-y-3"> 203 + ( match email with 204 + | Some level -> 205 + <div className="flex items-start gap-3 p-3 rounded-lg bg-mist-10/50"> 206 + <div 207 + className="flex-shrink-0 w-8 h-8 flex items-center \ 208 + justify-center rounded-full bg-mist-20/50 \ 209 + text-mist-80"> 210 + <MailIcon className="w-4 h-4" /> 211 + </div> 212 + <div className="flex-1 min-w-0"> 213 + <div className="font-serif text-mana-100">(string "Email")</div> 214 + <div className="text-sm text-mist-100"> 215 + (string 216 + ( if level = `Manage then 217 + "Read and update your account's email address" 218 + else "Read your account's email address" ) ) 219 + </div> 220 + </div> 221 + </div> 222 + | None -> 223 + null ) 224 + ( match identity with 225 + | Some level -> 226 + <div className="flex items-start gap-3 p-3"> 227 + <div 228 + className="flex-shrink-0 w-8 h-8 flex items-center \ 229 + justify-center rounded-full bg-mist-20/50 \ 230 + text-mist-80"> 231 + <AtIcon className="w-4 h-4" /> 232 + </div> 233 + <div className="flex-1 min-w-0"> 234 + <div className="font-serif text-mana-100"> 235 + (string "Identity") 236 + </div> 237 + <div className="text-sm text-mist-100"> 238 + (string 239 + ( if level = `Full then 240 + "Manage your full identity including your @handle" 241 + else "Change your @handle" ) ) 242 + </div> 243 + </div> 244 + </div> 245 + | None -> 246 + null ) 247 + ( if has_bluesky then 248 + <div className="flex items-start gap-3 p-3 rounded-lg bg-mist-10/50"> 249 + <div 250 + className="flex-shrink-0 w-8 h-8 flex items-center \ 251 + justify-center rounded-full bg-mist-20/50 \ 252 + text-mist-80"> 253 + <BlueskyIcon className="w-4 h-4" /> 254 + </div> 255 + <div className="flex-1 min-w-0"> 256 + <div className="font-serif text-mana-100">(string "Bluesky")</div> 257 + <div className="text-sm text-mist-100"> 258 + (string "Manage your profile, posts, likes and follows") 259 + </div> 260 + </div> 261 + </div> 262 + else null ) 263 + ( if has_chat then 264 + <div className="flex items-start gap-3 p-3 rounded-lg bg-mist-10/50"> 265 + <div 266 + className="flex-shrink-0 w-8 h-8 flex items-center \ 267 + justify-center rounded-full bg-mist-20/50 \ 268 + text-mist-80"> 269 + <MessageIcon className="w-4 h-4" /> 270 + </div> 271 + <div className="flex-1 min-w-0"> 272 + <div className="font-serif text-mana-100">(string "Chat")</div> 273 + <div className="text-sm text-mist-100"> 274 + (string "Read and send messages") 275 + </div> 276 + </div> 277 + </div> 278 + else null ) 279 + ( if List.length repos > 0 && not has_bluesky then 280 + let coll_actions_map = build_collection_actions_map repos in 281 + let coll_actions_list = 282 + StringMap.bindings coll_actions_map 283 + |> List.sort (fun (a, _) (b, _) -> String.compare a b) 284 + in 285 + let star_actions = StringMap.find_opt "*" coll_actions_map in 286 + let has_full_access = 287 + match star_actions with 288 + | Some a -> 289 + a.create && a.update && a.delete 290 + | None -> 291 + false 292 + in 293 + <div className="flex items-start gap-3 p-3 rounded-lg bg-mist-10/50"> 294 + <div 295 + className="flex-shrink-0 w-8 h-8 flex items-center \ 296 + justify-center rounded-full bg-mist-20/50 \ 297 + text-mist-80"> 298 + <FolderGitIcon className="w-4 h-4" /> 299 + </div> 300 + <div className="flex-1 min-w-0"> 301 + <div className="font-serif text-mana-100"> 302 + (string "Repository") 303 + </div> 304 + <div className="text-sm text-mist-100"> 305 + (string 306 + ( if has_full_access then 307 + "Create, update, and delete any public record" 308 + else "Publish changes to your repository" ) ) 309 + </div> 310 + ( if not has_full_access then 311 + <table className="w-full mt-2 text-xs"> 312 + <thead> 313 + <tr className="text-mist-80"> 314 + <th className="text-left font-normal pb-1"> 315 + (string "Collection") 316 + </th> 317 + <th className="text-center font-normal pb-1 w-16"> 318 + (string "Create") 319 + </th> 320 + <th className="text-center font-normal pb-1 w-16"> 321 + (string "Update") 322 + </th> 323 + <th className="text-center font-normal pb-1 w-16"> 324 + (string "Delete") 325 + </th> 326 + </tr> 327 + </thead> 328 + <tbody> 329 + ( coll_actions_list 330 + |> List.map (fun (coll, actions) -> 331 + let star_create = 332 + Option.map (fun a -> a.create) star_actions 333 + |> Option.value ~default:false 334 + in 335 + let star_update = 336 + Option.map (fun a -> a.update) star_actions 337 + |> Option.value ~default:false 338 + in 339 + let star_delete = 340 + Option.map (fun a -> a.delete) star_actions 341 + |> Option.value ~default:false 342 + in 343 + <tr key=coll className="text-mist-100"> 344 + <td className="py-0.5"> 345 + <span className="font-medium"> 346 + (string 347 + ( if coll = "*" then "Any collection" 348 + else coll ) ) 349 + </span> 350 + </td> 351 + <td className="text-center"> 352 + ( if star_create || actions.create then 353 + <span className="text-mana-100"> 354 + (string {js|✓|js}) 355 + </span> 356 + else null ) 357 + </td> 358 + <td className="text-center"> 359 + ( if star_update || actions.update then 360 + <span className="text-mana-100"> 361 + (string {js|✓|js}) 362 + </span> 363 + else null ) 364 + </td> 365 + <td className="text-center"> 366 + ( if star_delete || actions.delete then 367 + <span className="text-mana-100"> 368 + (string {js|✓|js}) 369 + </span> 370 + else null ) 371 + </td> 372 + </tr> ) 373 + |> Array.of_list |> array ) 374 + </tbody> 375 + </table> 376 + else null ) 377 + </div> 378 + </div> 379 + else null ) 380 + ( if List.length rpcs > 0 then 381 + let aud_lxms_map = build_aud_lxms_map rpcs in 382 + let aud_lxms_list = 383 + StringMap.bindings aud_lxms_map 384 + |> List.map (fun (aud, lxms) -> 385 + let sorted_lxms = 386 + if List.mem "*" lxms then ["*"] 387 + else List.sort String.compare lxms 388 + in 389 + (aud, sorted_lxms) ) 390 + |> List.sort (fun (a, _) (b, _) -> String.compare a b) 391 + in 392 + let has_full_access = 393 + List.exists 394 + (fun (aud, lxms) -> aud = "*" && List.mem "*" lxms) 395 + aud_lxms_list 396 + in 397 + <div className="flex items-start gap-3 p-3 rounded-lg bg-mist-10/50"> 398 + <div 399 + className="flex-shrink-0 w-8 h-8 flex items-center \ 400 + justify-center rounded-full bg-mist-20/50 \ 401 + text-mist-80"> 402 + <UsersRoundIcon className="w-4 h-4" /> 403 + </div> 404 + <div className="flex-1 min-w-0"> 405 + <div className="font-serif text-mana-100"> 406 + (string "Authenticate") 407 + </div> 408 + <div className="text-sm text-mist-100"> 409 + (string 410 + ( if has_full_access then 411 + "Act on your behalf towards any service" 412 + else "Perform actions on your behalf" ) ) 413 + </div> 414 + ( if not has_full_access then 415 + <table className="w-full mt-2 text-xs"> 416 + <thead> 417 + <tr className="text-mist-80"> 418 + <th className="text-left font-normal pb-1"> 419 + (string "Method") 420 + </th> 421 + <th className="text-left font-normal pb-1"> 422 + (string "Service") 423 + </th> 424 + </tr> 425 + </thead> 426 + <tbody> 427 + ( aud_lxms_list 428 + |> List.concat_map (fun (aud, lxms) -> 429 + let render_aud () = 430 + if aud = "*" then 431 + <span className="text-mist-100 font-medium"> 432 + (string "Any service") 433 + </span> 434 + else if 435 + String.starts_with 436 + ~prefix:"did:web:api.bsky.app#" aud 437 + then 438 + <span className="text-mist-100" title=aud> 439 + (string "Bluesky services") 440 + </span> 441 + else if 442 + String.starts_with 443 + ~prefix:"did:web:api.bsky.chat#" aud 444 + then 445 + <span className="text-mist-100" title=aud> 446 + (string "Bluesky chat services") 447 + </span> 448 + else if 449 + String.starts_with ~prefix:"did:web:" aud 450 + && String.contains aud '#' 451 + then 452 + let domain = 453 + String.sub aud 8 (String.index aud '#' - 8) 454 + in 455 + <span className="text-mist-100" title=aud> 456 + (string ("Service by " ^ domain)) 457 + </span> 458 + else 459 + <span className="text-mist-100"> 460 + (string aud) 461 + </span> 462 + in 463 + List.map 464 + (fun lxm -> 465 + <tr key=(aud ^ lxm) className="text-mist-100"> 466 + <td className="py-0.5"> 467 + <span 468 + className="text-mist-100 font-medium"> 469 + (string 470 + ( if lxm = "*" then "Any method" 471 + else lxm ) ) 472 + </span> 473 + </td> 474 + <td className="py-0.5">(render_aud ())</td> 475 + </tr> ) 476 + lxms ) 477 + |> Array.of_list |> array ) 478 + </tbody> 479 + </table> 480 + else null ) 481 + </div> 482 + </div> 483 + else null ) 484 + ( if List.length blobs > 0 && not has_bluesky then 485 + <div className="flex items-start gap-3 p-3 rounded-lg bg-mist-10/50"> 486 + <div 487 + className="flex-shrink-0 w-8 h-8 flex items-center \ 488 + justify-center rounded-full bg-mist-20/50 \ 489 + text-mist-80"> 490 + <UploadIcon className="w-4 h-4" /> 491 + </div> 492 + <div className="flex-1 min-w-0"> 493 + <div className="font-serif text-mana-100"> 494 + (string "File Upload") 495 + </div> 496 + <div className="text-sm text-mist-100"> 497 + (string 498 + ( if List.mem "*/*" blobs then "Upload any files" 499 + else "Upload files to your repository" ) ) 500 + </div> 501 + </div> 502 + </div> 503 + else null ) 504 + ( if List.length unknowns > 0 then 505 + <div className="flex items-start gap-3 p-3 rounded-lg bg-mist-10/50"> 506 + <div 507 + className="flex-shrink-0 w-8 h-8 flex items-center \ 508 + justify-center rounded-full bg-mist-20/50 \ 509 + text-mist-80"> 510 + <BoxesIcon className="w-4 h-4" /> 511 + </div> 512 + <div className="flex-1 min-w-0"> 513 + <div className="font-serif text-mana-100"> 514 + (string "Other Permissions") 515 + </div> 516 + <div className="text-sm text-mist-100"> 517 + ( unknowns 518 + |> List.map (fun s -> 519 + <span key=s className="block">(string s)</span> ) 520 + |> Array.of_list |> array ) 521 + </div> 522 + </div> 523 + </div> 524 + else null ) 525 + </div> 526 + end 527 + 22 528 let[@react.component] make 23 529 ~props: 24 530 ({ client_url ··· 65 571 ( if favicon_url <> "" then 66 572 <img 67 573 src=favicon_url 68 - className="w-4 h-4 inline ml-1.5 mr-1 -mt-0.5" 574 + className="w-4 h-4 inline ml-0.5 mr-1 -mt-0.5" 69 575 onError=(fun _ -> set_favicon_url (fun _ -> "")) /> 70 576 else null ) 71 577 rendered_name 72 578 (string " as ") 73 - <Aria.Select name="did" className="inline" defaultValue=current_user.did> 74 - <Aria.Button 75 - className="group inline-flex flex-row items-center px-1.5 py-1 \ 76 - -mx-0.5 -my-1 rounded-lg focus-visible:outline-none \ 77 - hover:bg-mist-20/40 active:bg-mist-20/40"> 78 - <Aria.SelectValue 79 - className="text-mana-100 font-serif inline-flex items-center \ 80 - gap-x-1" 81 - /> 82 - </Aria.Button> 83 - <Aria.Popover 84 - style=(ReactDOM.Style.make 85 - ~minWidth:"calc(var(--trigger-width) + var(--spacing) * 3)" 86 - () ) 87 - className="focus-visible:outline-none"> 88 - <Aria.ListBox 89 - className="w-full flex flex-col gap-y-1 p-1.5 -ml-1.5 rounded-lg \ 90 - bg-mist-20 font-light"> 91 - ( List.map 92 - (fun user -> 93 - <Aria.ListBoxItem 94 - className="flex flex-row items-center py-1.5 px-2 gap-x-1 \ 95 - font-serif text-mist-100 rounded-md \ 96 - focus-visible:outline-none \ 97 - data-hovered:text-mist-20 \ 98 - data-focused:text-mist-20 \ 99 - data-hovered:bg-mana-100 \ 100 - data-focused:bg-mana-100" 101 - key=user.did 102 - id=user.did> 103 - ( match user.avatar_data_uri with 104 - | Some src -> 105 - <img src className="w-5 h-5 mr-1 rounded-md" /> 106 - | None -> 107 - null ) 108 - <span className="self-baseline select-none"> 109 - (string ("@" ^ user.handle)) 110 - </span> 111 - <ChevronDownIcon 112 - className="w-3 h-3 mt-0.5 text-mana-100 hidden \ 113 - group-aria-[haspopup]:inline" 114 - strokeWidth="3" 115 - /> 116 - </Aria.ListBoxItem> ) 117 - logged_in_users 118 - |> Array.of_list |> array ) 119 - <Aria.ListBoxItem 120 - className="flex flex-row items-center p-1 pl-2 text-mana-100 \ 121 - font-normal underline rounded-md \ 122 - focus-visible:outline-none data-hovered:text-mist-20 \ 123 - data-focused:text-mist-20 data-hovered:bg-mana-100 \ 124 - data-focused:bg-mana-100" 125 - href=add_account_url> 126 - (string "add account") 127 - </Aria.ListBoxItem> 128 - </Aria.ListBox> 129 - </Aria.Popover> 130 - </Aria.Select> 579 + <ClientOnly> 580 + [%browser_only (fun () -> ( 581 + <Aria.Select 582 + name="did" className="inline" defaultValue=current_user.did> 583 + <Aria.Button 584 + className="group inline-flex flex-row items-center px-1.5 py-1 \ 585 + -mx-0.75 -my-1 rounded-lg focus-visible:outline-none \ 586 + hover:bg-mist-20/40 active:bg-mist-20/40"> 587 + <Aria.SelectValue 588 + className="text-mana-100 font-serif inline-flex items-center \ 589 + gap-x-1" 590 + /> 591 + </Aria.Button> 592 + <Aria.Popover 593 + style=(ReactDOM.Style.make 594 + ~minWidth:"calc(var(--trigger-width) + var(--spacing) * 3)" 595 + () ) 596 + className="focus-visible:outline-none"> 597 + <Aria.ListBox 598 + className="w-full flex flex-col gap-y-1 p-1.5 -ml-1.5 rounded-lg \ 599 + bg-mist-20 font-light"> 600 + ( List.map 601 + (fun user -> 602 + <Aria.ListBoxItem 603 + className="flex flex-row items-center py-1.5 px-2 \ 604 + gap-x-1 font-serif text-mist-100 rounded-md \ 605 + focus-visible:outline-none \ 606 + data-hovered:text-mist-20 \ 607 + data-focused:text-mist-20 \ 608 + data-hovered:bg-mana-100 \ 609 + data-focused:bg-mana-100" 610 + key=user.did 611 + id=user.did> 612 + ( match user.avatar_data_uri with 613 + | Some src -> 614 + <img src className="w-5 h-5 mr-1 rounded-md" /> 615 + | None -> 616 + null ) 617 + <span className="self-baseline select-none"> 618 + (string ("@" ^ user.handle)) 619 + </span> 620 + <ChevronDownIcon 621 + className="w-3 h-3 mt-0.5 text-mana-100 hidden \ 622 + group-aria-[haspopup]:inline" 623 + strokeWidth="3" 624 + /> 625 + </Aria.ListBoxItem> ) 626 + logged_in_users 627 + |> Array.of_list |> array ) 628 + <Aria.ListBoxItem 629 + className="flex flex-row items-center p-1 pl-2 text-mana-100 \ 630 + font-normal underline rounded-md \ 631 + focus-visible:outline-none \ 632 + data-hovered:text-mist-20 data-focused:text-mist-20 \ 633 + data-hovered:bg-mana-100 data-focused:bg-mana-100" 634 + href=add_account_url> 635 + (string "add account") 636 + </Aria.ListBoxItem> 637 + </Aria.ListBox> 638 + </Aria.Popover> 639 + </Aria.Select> 640 + ))] 641 + </ClientOnly> 131 642 (string " and granting it the following permissions:") 132 643 </span> 133 - <ul className="w-full text-mist-100 list-disc ml-8 mt-2 space-y-1"> 134 - ( List.map (fun scope -> <li>(string scope)</li>) scopes 135 - |> Array.of_list |> array ) 136 - </ul> 644 + <ScopesTable scopes /> 137 645 <div className="w-full flex flex-row items-center justify-between mt-6"> 138 646 <input type_="hidden" name="dream.csrf" value=csrf_token /> 139 647 <input type_="hidden" name="code" value=code />
+2 -2
pegasus/lib/api/oauth_/authorize.ml
··· 93 93 Session.list_logged_in_actors ctx.req ctx.db 94 94 in 95 95 let current_user = 96 - List.find 96 + List.find_opt 97 97 (fun (user : Frontend.OauthAuthorizePage.actor) -> 98 98 user.did = did ) 99 - logged_in_users 99 + logged_in_users |> Option.value ~default:(List.hd logged_in_users) 100 100 in 101 101 Util.render_html ~title:("Authorizing " ^ host) 102 102 (module Frontend.OauthAuthorizePage)