├── LICENSE ├── README.md ├── src ├── .DS_Store ├── app │ └── auth-id.hoon ├── desk.bill ├── desk.ship ├── lib │ ├── dbug.hoon │ ├── default-agent.hoon │ ├── docket.hoon │ ├── graph-store.hoon │ ├── graph-utils.hoon │ ├── migrate.hoon │ ├── resource.hoon │ ├── server.hoon │ ├── signatures.hoon │ └── skeleton.hoon ├── mar │ ├── authenticate-with-urbit-id │ │ └── action.hoon │ ├── bill.hoon │ ├── docket-0.hoon │ ├── hoon.hoon │ ├── kelvin.hoon │ ├── mime.hoon │ ├── noun.hoon │ └── ship.hoon ├── sur │ ├── auth-id.hoon │ ├── docket.hoon │ ├── graph-store.hoon │ ├── hark-store.hoon │ ├── post.hoon │ ├── pull-hook.hoon │ └── resource.hoon └── sys.kelvin └── tutorial.md /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 dcSpark 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Authenticate With Urbit ID 2 | 3 | A Gall agent which enables webservers and service providers outside of Urbit to authenticate users, thereby providing a “Login with Urbit ID” experience. `%authenticate-with-urbit-id` affords a website running a backend ship to authenticate that a website user does in fact control a particular Urbit ship (thereby supporting hosted ships, L2 ships, and other potential edge cases as well). The authentication protocol is similar to email token-based authentication schemes. 4 | 5 | When paired with Urbit Visor, this application will allows users to easily authenticate themselves on classical web2 sites which integrate Authenticate With Urbit ID. 6 | 7 | Do note, the website/backend ship running `%authenticate-with-urbit-id` must be trusted (meaning run by the website provider, or a trusted 3rd party) and secured on a server with a strong firewall which only allows the website backend (ip) to interact with it. This was chosen to simplify the setup process and reuse the existing networking tech stack so that integration would be easier for implementors with little knowledge of Urbit. 8 | 9 | Check out [the announcement youtube video](https://www.youtube.com/watch?v=M0j7maBfRmA) for an easy-to-follow summary of Authenticate With Urbit ID. 10 | After the youtube video, [this deep-dive medium post](https://medium.com/dcspark/authenticate-website-users-using-urbit-id-e6dc8c4cb4fa) is recommended as well. 11 | 12 | ## Installation 13 | 14 | ### From Repo (As Host) 15 | 16 | 1. Boot the host ship. 17 | 2. On that ship, `|merge %authenticate-with-urbit-id our %base`. 18 | 3. On that ship, `|mount %authenticate-with-urbit-id`. 19 | 4. Outside the ship, `rm -rf ship/authenticate-with-urbit-id/*`. 20 | 5. Outside the ship, `cp -r git-repo/src/* ship/authenticate-with-urbit-id`. 21 | 6. Inside the ship, `|commit %authenticate-with-urbit-id`. 22 | 7. Inside the ship, `|install our %authenticate-with-urbit-id`. You should see a success message and a %no-docket-file-for warning. 23 | 8. Inside the ship, `|public %authenticate-with-urbit-id` (note that this is different because there is no docket file or tile). 24 | 9. From another ship, install at the command line: `|install ~sampel-palnet %authenticate-with-urbit-id` (without a docket file, which we don't need, it currently won't show at the GUI). 25 | 26 | ## API 27 | 28 | `%authenticate-with-urbit-id` exposes the following endpoints: 29 | 30 | - `/~initiateAuth` 31 | - Input: An Airlock-standard JSON containing the user ship `ship` as a string. 32 | - Output: An Airlock-standard JSON containing the website ship `source` as a string, the user ship `ship` as a string, and the token for the user as a string. 33 | - Example: 34 | 35 | ```sh 36 | curl --header "Content-Type: application/json" \ 37 | --request PUT \ 38 | --data '{"ship":"sampel-talled","json":"sampel-palnet"}' \ 39 | http://localhost:8080/~initiateAuth 40 | ``` 41 | 42 | - `/~checkAuth` 43 | - Input: A JSON containing the user ship `ship` as a string. 44 | - Output: A JSON containing the requesting website ship `source` as a string, the user ship `target` as a string, and the status of the user ship `status` as a string. 45 | - Example: 46 | 47 | ```sh 48 | curl --header "Content-Type: application/json" \ 49 | --request PUT \ 50 | --data '{"ship":"sampel-talled","json":"sampel-palnet"}' \ 51 | http://localhost:8080/~checkAuth 52 | ``` 53 | 54 | In between the website hitting each endpoint, the user's ship should emit a DM containing the secure token to the website ship. `%authenticate-with-urbit-id` has subscribed to the `%dm-inbox` and will update the authorization status to `true` as soon as a DM containing the token has been received. 55 | 56 | In the case of multiple initiations, earlier tokens are instantly invalidated. 57 | 58 | As soon as a successful check has been made, `%authenticate-with-urbit-id` clears the authorization status of the user ship. 59 | 60 | 61 | ## Example Workflow 62 | 63 | _This example assumes that the developer is a running a “website ship” `~sampel-talled` and a “user ship” `~sampel-palnet`. (Do note: DMs do not work particularly well between fakezod galaxies.)_ 64 | 65 | 1. Start `%authenticate-with-urbit-id` on website ship `~sampel-talled`. 66 | 2. Make a request to `%authenticate-with-urbit-id` on `~sampel-talled` to generate a token (typically done via the website backend) for user ship `~sampel-palnet` (this token is then returned to the end user from the backend to the frontend, and would be fed through Urbit Visor's API in a DM, as is specified in step 4). 67 | 68 | ```sh 69 | curl --header "Content-Type: application/json" \ 70 | --request PUT \ 71 | --data '{"ship":"sampel-talled","json":"sampel-palnet"}' \ 72 | http://localhost:8080/~initiateAuth 73 | ``` 74 | 75 | 3. Check the authentication status of `~sampel-palnet` and confirm that the user ship is not authorized yet. 76 | 77 | ```sh 78 | curl --header "Content-Type: application/json" \ 79 | --request PUT \ 80 | --data '{"ship":"sampel-talled","json":"sampel-palnet"}' \ 81 | http://localhost:8080/~checkAuth 82 | ``` 83 | 84 | 4. Send a DM from `~sampel-palnet` to `~sampel-talled` at which contains the token returned in the first step (this is the authentication step, which in our text workflow is send a dm via dojo, however typically would be done via Urbit Visor). 85 | 86 | ```sh 87 | :dm-hook|dm ~sampel-talled ~[[%text 'RENV~jjr1W-ICCIlBr9ZVIxg']] 88 | ``` 89 | 90 | 5. Check the authentication status of `~sampel-palnet` from `~sampel-talled` and confirm that the user ship has been authorized. 91 | 92 | ```sh 93 | curl --header "Content-Type: application/json" \ 94 | --request PUT \ 95 | --data '{"ship":"sampel-talled","json":"sampel-palnet"}' \ 96 | http://localhost:8080/~checkAuth 97 | ``` 98 | 99 | 6. Reheck the authentication status of `~sampel-palnet` from `~sampel-talled` and confirm that the user ship is once again not authorized (`/~checkAuth` is a one-time consume check, meaning that users must reauthorize themselves every time they want to login.) 100 | 101 | ```sh 102 | curl --header "Content-Type: application/json" \ 103 | --request PUT \ 104 | --data '{"ship":"sampel-talled","json":"sampel-palnet"}' \ 105 | http://localhost:8080/~checkAuth 106 | ``` 107 | -------------------------------------------------------------------------------- /src/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dcSpark/authenticate-with-urbit-id/6d1def27b02b2d90926d722f98c59fcebc0eb4c1/src/.DS_Store -------------------------------------------------------------------------------- /src/app/auth-id.hoon: -------------------------------------------------------------------------------- 1 | :: 2 | :::: auth-id.hoon 3 | :: 4 | :: %gall agent to authenticate a ship for Urbit Visor. 5 | :: 6 | :: The basic architecture is that Visor requests a website running the agent 7 | :: to produce a token and wait for a DM containing the token from the 8 | :: authenticating ship. 9 | :: 10 | /- graph-store 11 | /+ server, default-agent, dbug, graph-store, graph-utils 12 | %- agent:dbug 13 | ^- agent:gall 14 | =| key=@uv 15 | |_ =bowl:gall 16 | +* this . 17 | card card:agent:gall 18 | default ~(. (default-agent this %|) bowl) 19 | gu ~(. graph-utils bowl) 20 | :: 21 | ++ on-init 22 | ^- (quip card _this) 23 | ~& > '%auth-id initialized successfully' 24 | =. key `@uv`(end 7 eny.bowl) 25 | ~& >> "Your api key is {}" 26 | :_ this 27 | :~ [%pass /bind %arvo %e %connect [~ /'~initiateAuth'] %auth-id] 28 | == 29 | ++ on-save !>(key) 30 | ++ on-load 31 | |= old-key=vase 32 | =. key !<(@uv old-key) 33 | `this 34 | ++ on-poke 35 | |= [=mark =vase] 36 | ^- (quip card _this) 37 | |^ 38 | ?+ mark `this 39 | %noun 40 | =/ arg !<(* vase) 41 | ?+ arg `this 42 | [%approve @t] 43 | =/ c=card [%pass /cors-approve %arvo %e %approve-origin +.arg] 44 | :_ this 45 | ~[c] 46 | %cycle-key 47 | =. key `@uv`(end 7 eny.bowl) 48 | ~& > "Your new api key is {}" 49 | `this 50 | %print-key 51 | ~& > "Your api key is {}" 52 | `this 53 | == 54 | %handle-http-request 55 | =+ !<([id=@ta =inbound-request:eyre] vase) 56 | ?> =(url.request.inbound-request '/~initiateAuth') 57 | =/ valid-key=? (validate-key request.inbound-request) 58 | ?. valid-key ~& >>> "invalid api-key" 59 | (bail id 'invalid-api-key') 60 | :: 61 | :: Inputs: 62 | :: Urbit ship to authenticate @p: String 63 | :: 64 | :: Outputs: 65 | :: Auth code: String 66 | :: Source ship @p (our): String 67 | :: Target ship @p: String 68 | :: 69 | :: Side Effects: none 70 | :: 71 | :: Description: 72 | :: This generates a random @p encoded auth code, sends it as a 73 | :: graph-store DM to the ship given in the input, and returns 74 | :: the auth-code as an HTTP response for the requester to save. 75 | :: 76 | ~& > "%auth-id request hit /~initiateAuth" 77 | =/ target=(unit @p) (parse-target inbound-request) 78 | ?~ target (bail id 'invalid ship name') 79 | =/ token `@p`(end 6 eny.bowl) 80 | =/ dm-poke=card (send-dm u.target token) 81 | =/ http-response=(list card) 82 | %+ give-simple-payload:app:server id 83 | (handle-auth-request u.target token) 84 | :_ this 85 | (snoc http-response dm-poke) 86 | == 87 | ++ validate-key 88 | |= r=request:http ^- ? 89 | =/ header=header-list:http %+ skim header-list.r 90 | |= [key=@t value=@t] 91 | =(key 'auth') 92 | ?~ header %| 93 | =/ format (slaw %uv value.i.header) 94 | ?~ format %| =(u.format key) 95 | ++ parse-target 96 | |= [ir=inbound-request:eyre] 97 | ^- (unit @p) 98 | ?. ?=(%'POST' method.request.ir) ~ 99 | (slaw %p +511:ir) 100 | ++ send-dm 101 | |= [target=@p auth-code=@p] 102 | ^- card 103 | =/ t "Your urbit auth code is \0a {}" 104 | =/ contents 105 | :~ 106 | [%text text=(crip t)] 107 | == 108 | =/ update (build-dm:gu target contents) 109 | [%pass /send-auth-code %agent [our.bowl %dm-hook] %poke %graph-update-3 !>(update)] 110 | ++ handle-auth-request 111 | |= [target=@p token=@p] 112 | ^- simple-payload:http 113 | =, enjs:format 114 | %- json-response:gen:server 115 | %- pairs 116 | :~ 117 | [%target [%s (scot %p target)]] 118 | [%token [%s (scot %p token)]] 119 | == 120 | ++ error-response 121 | |= error=@t 122 | ^- simple-payload:http 123 | =, enjs:format 124 | %- json-response:gen:server 125 | %+ frond 126 | %error s+error 127 | ++ bail 128 | |= [id=@ta error=@t] 129 | ^- (quip card _this) 130 | :_ this 131 | %+ give-simple-payload:app:server id 132 | (error-response error) 133 | -- 134 | ++ on-arvo 135 | |= [=wire =sign-arvo] 136 | ^- (quip card _this) 137 | `this 138 | ++ on-watch 139 | |= =path 140 | `this 141 | ++ on-leave on-leave:default 142 | ++ on-peek on-peek:default 143 | ++ on-agent on-agent:default 144 | ++ on-fail on-fail:default 145 | -- -------------------------------------------------------------------------------- /src/desk.bill: -------------------------------------------------------------------------------- 1 | :~ %auth-id 2 | == 3 | -------------------------------------------------------------------------------- /src/desk.ship: -------------------------------------------------------------------------------- 1 | ~dister-dozzod-havbex -------------------------------------------------------------------------------- /src/lib/dbug.hoon: -------------------------------------------------------------------------------- 1 | :: dbug: agent wrapper for generic debugging tools 2 | :: 3 | :: usage: %-(agent:dbug your-agent) 4 | :: 5 | |% 6 | +$ poke 7 | $% [%bowl ~] 8 | [%state grab=cord] 9 | [%incoming =about] 10 | [%outgoing =about] 11 | == 12 | :: 13 | +$ about 14 | $@ ~ 15 | $% [%ship =ship] 16 | [%path =path] 17 | [%wire =wire] 18 | [%term =term] 19 | == 20 | :: 21 | ++ agent 22 | |= =agent:gall 23 | ^- agent:gall 24 | !. 25 | |_ =bowl:gall 26 | +* this . 27 | ag ~(. agent bowl) 28 | :: 29 | ++ on-poke 30 | |= [=mark =vase] 31 | ^- (quip card:agent:gall agent:gall) 32 | ?. ?=(%dbug mark) 33 | =^ cards agent (on-poke:ag mark vase) 34 | [cards this] 35 | =/ dbug 36 | !<(poke vase) 37 | =; =tang 38 | ((%*(. slog pri 1) tang) [~ this]) 39 | ?- -.dbug 40 | %bowl [(sell !>(bowl))]~ 41 | :: 42 | %state 43 | =? grab.dbug =('' grab.dbug) '-' 44 | =; product=^vase 45 | [(sell product)]~ 46 | =/ state=^vase 47 | :: if the underlying app has implemented a /dbug/state scry endpoint, 48 | :: use that vase in place of +on-save's. 49 | :: 50 | =/ result=(each ^vase tang) 51 | (mule |.(q:(need (need (on-peek:ag /x/dbug/state))))) 52 | ?:(?=(%& -.result) p.result on-save:ag) 53 | %+ slap 54 | (slop state !>([bowl=bowl ..zuse])) 55 | (ream grab.dbug) 56 | :: 57 | %incoming 58 | =; =tang 59 | ?^ tang tang 60 | [%leaf "no matching subscriptions"]~ 61 | %+ murn 62 | %+ sort ~(tap by sup.bowl) 63 | |= [[* a=[=ship =path]] [* b=[=ship =path]]] 64 | (aor [path ship]:a [path ship]:b) 65 | |= [=duct [=ship =path]] 66 | ^- (unit tank) 67 | =; relevant=? 68 | ?. relevant ~ 69 | `>[path=path from=ship duct=duct]< 70 | ?: ?=(~ about.dbug) & 71 | ?- -.about.dbug 72 | %ship =(ship ship.about.dbug) 73 | %path ?=(^ (find path.about.dbug path)) 74 | %wire %+ lien duct 75 | |=(=wire ?=(^ (find wire.about.dbug wire))) 76 | %term !! 77 | == 78 | :: 79 | %outgoing 80 | =; =tang 81 | ?^ tang tang 82 | [%leaf "no matching subscriptions"]~ 83 | %+ murn 84 | %+ sort ~(tap by wex.bowl) 85 | |= [[[a=wire *] *] [[b=wire *] *]] 86 | (aor a b) 87 | |= [[=wire =ship =term] [acked=? =path]] 88 | ^- (unit tank) 89 | =; relevant=? 90 | ?. relevant ~ 91 | `>[wire=wire agnt=[ship term] path=path ackd=acked]< 92 | ?: ?=(~ about.dbug) & 93 | ?- -.about.dbug 94 | %ship =(ship ship.about.dbug) 95 | %path ?=(^ (find path.about.dbug path)) 96 | %wire ?=(^ (find wire.about.dbug wire)) 97 | %term =(term term.about.dbug) 98 | == 99 | == 100 | :: 101 | ++ on-peek 102 | |= =path 103 | ^- (unit (unit cage)) 104 | ?. ?=([@ %dbug *] path) 105 | (on-peek:ag path) 106 | ?+ path [~ ~] 107 | [%u %dbug ~] ``noun+!>(&) 108 | [%x %dbug %state ~] ``noun+!>(on-save:ag) 109 | [%x %dbug %subscriptions ~] ``noun+!>([wex sup]:bowl) 110 | == 111 | :: 112 | ++ on-init 113 | ^- (quip card:agent:gall agent:gall) 114 | =^ cards agent on-init:ag 115 | [cards this] 116 | :: 117 | ++ on-save on-save:ag 118 | :: 119 | ++ on-load 120 | |= old-state=vase 121 | ^- (quip card:agent:gall agent:gall) 122 | =^ cards agent (on-load:ag old-state) 123 | [cards this] 124 | :: 125 | ++ on-watch 126 | |= =path 127 | ^- (quip card:agent:gall agent:gall) 128 | =^ cards agent (on-watch:ag path) 129 | [cards this] 130 | :: 131 | ++ on-leave 132 | |= =path 133 | ^- (quip card:agent:gall agent:gall) 134 | =^ cards agent (on-leave:ag path) 135 | [cards this] 136 | :: 137 | ++ on-agent 138 | |= [=wire =sign:agent:gall] 139 | ^- (quip card:agent:gall agent:gall) 140 | =^ cards agent (on-agent:ag wire sign) 141 | [cards this] 142 | :: 143 | ++ on-arvo 144 | |= [=wire =sign-arvo] 145 | ^- (quip card:agent:gall agent:gall) 146 | =^ cards agent (on-arvo:ag wire sign-arvo) 147 | [cards this] 148 | :: 149 | ++ on-fail 150 | |= [=term =tang] 151 | ^- (quip card:agent:gall agent:gall) 152 | =^ cards agent (on-fail:ag term tang) 153 | [cards this] 154 | -- 155 | -- 156 | -------------------------------------------------------------------------------- /src/lib/default-agent.hoon: -------------------------------------------------------------------------------- 1 | /+ skeleton 2 | |* [agent=* help=*] 3 | ?: ?=(%& help) 4 | ~| %default-agent-helpfully-crashing 5 | skeleton 6 | |_ =bowl:gall 7 | ++ on-init 8 | `agent 9 | :: 10 | ++ on-save 11 | !>(~) 12 | :: 13 | ++ on-load 14 | |= old-state=vase 15 | `agent 16 | :: 17 | ++ on-poke 18 | |= =cage 19 | ~| "unexpected poke to {} with mark {}" 20 | !! 21 | :: 22 | ++ on-watch 23 | |= =path 24 | ~| "unexpected subscription to {} on path {}" 25 | !! 26 | :: 27 | ++ on-leave 28 | |= path 29 | `agent 30 | :: 31 | ++ on-peek 32 | |= =path 33 | ~| "unexpected scry into {} on path {}" 34 | !! 35 | :: 36 | ++ on-agent 37 | |= [=wire =sign:agent:gall] 38 | ^- (quip card:agent:gall _agent) 39 | ?- -.sign 40 | %poke-ack 41 | ?~ p.sign 42 | `agent 43 | %- (slog leaf+"poke failed from {} on wire {}" u.p.sign) 44 | `agent 45 | :: 46 | %watch-ack 47 | ?~ p.sign 48 | `agent 49 | =/ =tank leaf+"subscribe failed from {} on wire {}" 50 | %- (slog tank u.p.sign) 51 | `agent 52 | :: 53 | %kick `agent 54 | %fact 55 | ~| "unexpected subscription update to {} on wire {}" 56 | ~| "with mark {}" 57 | !! 58 | == 59 | :: 60 | ++ on-arvo 61 | |= [=wire =sign-arvo] 62 | ~| "unexpected system response {<-.sign-arvo>} to {} on wire {}" 63 | !! 64 | :: 65 | ++ on-fail 66 | |= [=term =tang] 67 | %- (slog leaf+"error in {}" >term< tang) 68 | `agent 69 | -- 70 | -------------------------------------------------------------------------------- /src/lib/docket.hoon: -------------------------------------------------------------------------------- 1 | /- *docket 2 | |% 3 | :: 4 | ++ mime 5 | |% 6 | +$ draft 7 | $: title=(unit @t) 8 | info=(unit @t) 9 | color=(unit @ux) 10 | glob-http=(unit [=url hash=@uvH]) 11 | glob-ames=(unit [=ship hash=@uvH]) 12 | base=(unit term) 13 | site=(unit path) 14 | image=(unit url) 15 | version=(unit version) 16 | website=(unit url) 17 | license=(unit cord) 18 | == 19 | :: 20 | ++ finalize 21 | |= =draft 22 | ^- (unit docket) 23 | ?~ title.draft ~ 24 | ?~ info.draft ~ 25 | ?~ color.draft ~ 26 | ?~ version.draft ~ 27 | ?~ website.draft ~ 28 | ?~ license.draft ~ 29 | =/ href=(unit href) 30 | ?^ site.draft `[%site u.site.draft] 31 | ?~ base.draft ~ 32 | ?^ glob-http.draft 33 | `[%glob u.base hash.u.glob-http %http url.u.glob-http]:draft 34 | ?~ glob-ames.draft 35 | ~ 36 | `[%glob u.base hash.u.glob-ames %ames ship.u.glob-ames]:draft 37 | ?~ href ~ 38 | =, draft 39 | :- ~ 40 | :* %1 41 | u.title 42 | u.info 43 | u.color 44 | u.href 45 | image 46 | u.version 47 | u.website 48 | u.license 49 | == 50 | :: 51 | ++ from-clauses 52 | =| =draft 53 | |= cls=(list clause) 54 | ^- (unit docket) 55 | =* loop $ 56 | ?~ cls (finalize draft) 57 | =* clause i.cls 58 | =. draft 59 | ?- -.clause 60 | %title draft(title `title.clause) 61 | %info draft(info `info.clause) 62 | %color draft(color `color.clause) 63 | %glob-http draft(glob-http `[url hash]:clause) 64 | %glob-ames draft(glob-ames `[ship hash]:clause) 65 | %base draft(base `base.clause) 66 | %site draft(site `path.clause) 67 | %image draft(image `url.clause) 68 | %version draft(version `version.clause) 69 | %website draft(website `website.clause) 70 | %license draft(license `license.clause) 71 | == 72 | loop(cls t.cls) 73 | :: 74 | ++ to-clauses 75 | |= d=docket 76 | ^- (list clause) 77 | %- zing 78 | :~ :~ title+title.d 79 | info+info.d 80 | color+color.d 81 | version+version.d 82 | website+website.d 83 | license+license.d 84 | == 85 | ?~ image.d ~ ~[image+u.image.d] 86 | ?: ?=(%site -.href.d) ~[site+path.href.d] 87 | =/ ref=glob-reference glob-reference.href.d 88 | :~ base+base.href.d 89 | ?- -.location.ref 90 | %http [%glob-http url.location.ref hash.ref] 91 | %ames [%glob-ames ship.location.ref hash.ref] 92 | == == == 93 | :: 94 | ++ spit-clause 95 | |= =clause 96 | ^- tape 97 | %+ weld " {(trip -.clause)}+" 98 | ?+ -.clause "'{(trip +.clause)}'" 99 | %color (scow %ux color.clause) 100 | %site (spud path.clause) 101 | :: 102 | %glob-http 103 | "['{(trip url.clause)}' {(scow %uv hash.clause)}]" 104 | :: 105 | %glob-ames 106 | "[{(scow %p ship.clause)} {(scow %uv hash.clause)}]" 107 | :: 108 | %version 109 | =, version.clause 110 | "[{(scow %ud major)} {(scow %ud minor)} {(scow %ud patch)}]" 111 | == 112 | :: 113 | ++ spit-docket 114 | |= dock=docket 115 | ^- tape 116 | ;: welp 117 | ":~\0a" 118 | `tape`(zing (join "\0a" (turn (to-clauses dock) spit-clause))) 119 | "\0a==" 120 | == 121 | -- 122 | :: 123 | ++ enjs 124 | =, enjs:format 125 | |% 126 | :: 127 | ++ charge-update 128 | |= u=^charge-update 129 | ^- json 130 | %+ frond -.u 131 | ^- json 132 | ?- -.u 133 | %del-charge s+desk.u 134 | :: 135 | %initial 136 | %- pairs 137 | %+ turn ~(tap by initial.u) 138 | |=([=desk c=^charge] [desk (charge c)]) 139 | :: 140 | %add-charge 141 | %- pairs 142 | :~ desk+s+desk.u 143 | charge+(charge charge.u) 144 | == 145 | == 146 | :: 147 | ++ num 148 | |= a=@u 149 | ^- ^tape 150 | =/ p=json (numb a) 151 | ?> ?=(%n -.p) 152 | (trip p.p) 153 | :: 154 | ++ version 155 | |= v=^version 156 | ^- json 157 | :- %s 158 | %- crip 159 | "{(num major.v)}.{(num minor.v)}.{(num patch.v)}" 160 | :: 161 | ++ merge 162 | |= [a=json b=json] 163 | ^- json 164 | ?> &(?=(%o -.a) ?=(%o -.b)) 165 | [%o (~(uni by p.a) p.b)] 166 | :: 167 | ++ href 168 | |= h=^href 169 | %+ frond -.h 170 | ?- -.h 171 | %site s+(spat path.h) 172 | %glob 173 | %- pairs 174 | :~ base+s+base.h 175 | glob-reference+(glob-reference glob-reference.h) 176 | == 177 | == 178 | :: 179 | ++ glob-reference 180 | |= ref=^glob-reference 181 | %- pairs 182 | :~ hash+s+(scot %uv hash.ref) 183 | location+(glob-location location.ref) 184 | == 185 | :: 186 | ++ glob-location 187 | |= loc=^glob-location 188 | ^- json 189 | %+ frond -.loc 190 | ?- -.loc 191 | %http s+url.loc 192 | %ames s+(scot %p ship.loc) 193 | == 194 | :: 195 | ++ charge 196 | |= c=^charge 197 | %+ merge (docket docket.c) 198 | %- pairs 199 | :~ chad+(chad chad.c) 200 | == 201 | :: 202 | ++ docket 203 | |= d=^docket 204 | ^- json 205 | %- pairs 206 | :~ title+s+title.d 207 | info+s+info.d 208 | color+s+(scot %ux color.d) 209 | href+(href href.d) 210 | image+?~(image.d ~ s+u.image.d) 211 | version+(version version.d) 212 | license+s+license.d 213 | website+s+website.d 214 | == 215 | :: 216 | ++ chad 217 | |= c=^chad 218 | %+ frond -.c 219 | ?+ -.c ~ 220 | %hung s+err.c 221 | == 222 | -- 223 | -- 224 | 225 | -------------------------------------------------------------------------------- /src/lib/graph-store.hoon: -------------------------------------------------------------------------------- 1 | /- sur=graph-store, pos=post, pull-hook, hark=hark-store 2 | /+ res=resource, migrate 3 | =< [sur .] 4 | =< [pos .] 5 | =, sur 6 | =, pos 7 | |% 8 | ++ hark-content 9 | |= =content 10 | ^- content:hark 11 | ?- -.content 12 | %text content 13 | %mention ship+ship.content 14 | %url text+url.content 15 | %code text+'A code excerpt' 16 | %reference text+'A reference' 17 | == 18 | :: 19 | ++ hark-contents 20 | |= cs=(list content) 21 | (turn cs hark-content) 22 | :: NOTE: move these functions to zuse 23 | ++ nu :: parse number as hex 24 | |= jon=json 25 | ?> ?=([%s *] jon) 26 | (rash p.jon hex) 27 | :: 28 | ++ re :: recursive reparsers 29 | |* [gar=* sef=_|.(fist:dejs-soft:format)] 30 | |= jon=json 31 | ^- (unit _gar) 32 | =- ~! gar ~! (need -) - 33 | ((sef) jon) 34 | :: 35 | ++ dank :: tank 36 | ^- $-(json (unit tank)) 37 | =, ^? dejs-soft:format 38 | %+ re *tank |. ~+ 39 | %- of :~ 40 | leaf+sa 41 | palm+(ot style+(ot mid+sa cap+sa open+sa close+sa ~) lines+(ar dank) ~) 42 | rose+(ot style+(ot mid+sa open+sa close+sa ~) lines+(ar dank) ~) 43 | == 44 | :: 45 | ++ orm ((on atom node) gth) 46 | ++ orm-log ((on time logged-update) gth) 47 | :: 48 | ++ enjs 49 | =, enjs:format 50 | |% 51 | :: 52 | ++ signatures 53 | |= s=^signatures 54 | ^- json 55 | [%a (turn ~(tap in s) signature)] 56 | :: 57 | ++ signature 58 | |= s=^signature 59 | ^- json 60 | %- pairs 61 | :~ [%signature s+(scot %ux p.s)] 62 | [%ship (ship q.s)] 63 | [%life (numb r.s)] 64 | == 65 | :: 66 | ++ index 67 | |= ind=^index 68 | ^- json 69 | :- %s 70 | ?: =(~ ind) 71 | '/' 72 | %+ roll ind 73 | |= [cur=@ acc=@t] 74 | ^- @t 75 | =/ num (numb cur) 76 | ?> ?=(%n -.num) 77 | (rap 3 acc '/' p.num ~) 78 | :: 79 | ++ uid 80 | |= u=^uid 81 | ^- json 82 | %- pairs 83 | :~ [%resource (enjs:res resource.u)] 84 | [%index (index index.u)] 85 | == 86 | :: 87 | ++ content 88 | |= c=^content 89 | ^- json 90 | ?- -.c 91 | %mention (frond %mention (ship ship.c)) 92 | %text (frond %text s+text.c) 93 | %url (frond %url s+url.c) 94 | %reference (frond %reference (reference +.c)) 95 | %code 96 | %+ frond %code 97 | %- pairs 98 | :- [%expression s+expression.c] 99 | :_ ~ 100 | :- %output 101 | :: virtualize output rendering, +tank:enjs:format might crash 102 | :: 103 | =/ result=(each (list json) tang) 104 | (mule |.((turn output.c tank))) 105 | ?- -.result 106 | %& a+p.result 107 | %| a+[a+[%s '[[output rendering error]]']~]~ 108 | == 109 | == 110 | :: 111 | ++ reference 112 | |= ref=^reference 113 | |^ 114 | %+ frond -.ref 115 | ?- -.ref 116 | %graph (graph +.ref) 117 | %group (group +.ref) 118 | %app (app +.ref) 119 | == 120 | :: 121 | ++ graph 122 | |= [grp=res gra=res idx=^index] 123 | %- pairs 124 | :~ graph+s+(enjs-path:res gra) 125 | group+s+(enjs-path:res grp) 126 | index+(index idx) 127 | == 128 | :: 129 | ++ group 130 | |= grp=res 131 | s+(enjs-path:res grp) 132 | :: 133 | ++ app 134 | |= [=^ship =desk p=^path] 135 | %- pairs 136 | :~ ship+s+(scot %p ship) 137 | desk+s+desk 138 | path+(path p) 139 | == 140 | -- 141 | :: 142 | ++ maybe-post 143 | |= mp=^maybe-post 144 | ^- json 145 | ?- -.mp 146 | %| s+(scot %ux p.mp) 147 | %& (post p.mp) 148 | == 149 | :: 150 | ++ post 151 | |= p=^post 152 | ^- json 153 | %- pairs 154 | :~ [%author (ship author.p)] 155 | [%index (index index.p)] 156 | [%time-sent (time time-sent.p)] 157 | [%contents [%a (turn contents.p content)]] 158 | [%hash ?~(hash.p ~ s+(scot %ux u.hash.p))] 159 | [%signatures (signatures signatures.p)] 160 | == 161 | :: 162 | ++ update 163 | |= upd=^update 164 | ^- json 165 | |^ (frond %graph-update (pairs ~[(encode q.upd)])) 166 | :: 167 | ++ encode 168 | |= upd=action 169 | ^- [cord json] 170 | ?- -.upd 171 | %add-graph 172 | :- %add-graph 173 | %- pairs 174 | :~ [%resource (enjs:res resource.upd)] 175 | [%graph (graph graph.upd)] 176 | [%mark ?~(mark.upd ~ s+u.mark.upd)] 177 | [%overwrite b+overwrite.upd] 178 | == 179 | :: 180 | %remove-graph 181 | [%remove-graph (enjs:res resource.upd)] 182 | :: 183 | %add-nodes 184 | :- %add-nodes 185 | %- pairs 186 | :~ [%resource (enjs:res resource.upd)] 187 | [%nodes (nodes nodes.upd)] 188 | == 189 | :: 190 | %remove-posts 191 | :- %remove-posts 192 | %- pairs 193 | :~ [%resource (enjs:res resource.upd)] 194 | [%indices (indices indices.upd)] 195 | == 196 | :: 197 | %add-signatures 198 | :- %add-signatures 199 | %- pairs 200 | :~ [%uid (uid uid.upd)] 201 | [%signatures (signatures signatures.upd)] 202 | == 203 | :: 204 | %remove-signatures 205 | :- %remove-signatures 206 | %- pairs 207 | :~ [%uid (uid uid.upd)] 208 | [%signatures (signatures signatures.upd)] 209 | == 210 | :: 211 | %add-tag 212 | :- %add-tag 213 | %- pairs 214 | :~ [%term s+term.upd] 215 | [%uid (uid uid.upd)] 216 | == 217 | :: 218 | %remove-tag 219 | :- %remove-tag 220 | %- pairs 221 | :~ [%term s+term.upd] 222 | [%uid (uid uid.upd)] 223 | == 224 | :: 225 | %archive-graph 226 | [%archive-graph (enjs:res resource.upd)] 227 | :: 228 | %unarchive-graph 229 | [%unarchive-graph (enjs:res resource.upd)] 230 | :: 231 | %keys 232 | [%keys [%a (turn ~(tap in resources.upd) enjs:res)]] 233 | :: 234 | %tags 235 | [%tags [%a (turn ~(tap in tags.upd) |=(=term s+term))]] 236 | :: 237 | %run-updates 238 | [%run-updates ~] 239 | :: 240 | %tag-queries 241 | :- %tag-queries 242 | %- pairs 243 | %+ turn ~(tap by tag-queries.upd) 244 | |= [=term uids=(set ^uid)] 245 | ^- [cord json] 246 | [term [%a (turn ~(tap in uids) uid)]] 247 | == 248 | :: 249 | ++ graph 250 | |= g=^graph 251 | ^- json 252 | %- pairs 253 | %+ turn 254 | (tap:orm g) 255 | |= [a=atom n=^node] 256 | ^- [@t json] 257 | :_ (node n) 258 | =/ idx (numb a) 259 | ?> ?=(%n -.idx) 260 | p.idx 261 | :: 262 | ++ node 263 | |= n=^node 264 | ^- json 265 | %- pairs 266 | :~ [%post (maybe-post post.n)] 267 | :- %children 268 | ?- -.children.n 269 | %empty ~ 270 | %graph (graph +.children.n) 271 | == 272 | == 273 | :: 274 | ++ nodes 275 | |= m=(map ^index ^node) 276 | ^- json 277 | %- pairs 278 | %+ turn ~(tap by m) 279 | |= [n=^index o=^node] 280 | ^- [@t json] 281 | :_ (node o) 282 | =/ idx (index n) 283 | ?> ?=(%s -.idx) 284 | p.idx 285 | :: 286 | ++ indices 287 | |= i=(set ^index) 288 | ^- json 289 | [%a (turn ~(tap in i) index)] 290 | :: 291 | -- 292 | -- 293 | :: 294 | ++ dejs 295 | =, dejs:format 296 | |% 297 | ++ update 298 | |= jon=json 299 | ^- ^update 300 | :- *time 301 | ^- action 302 | =< (decode jon) 303 | |% 304 | ++ decode 305 | %- of 306 | :~ [%add-nodes add-nodes] 307 | [%remove-posts remove-posts] 308 | [%add-signatures add-signatures] 309 | [%remove-signatures remove-signatures] 310 | :: 311 | [%add-graph add-graph] 312 | [%remove-graph remove-graph] 313 | :: 314 | [%add-tag add-tag] 315 | [%remove-tag remove-tag] 316 | :: 317 | [%archive-graph archive-graph] 318 | [%unarchive-graph unarchive-graph] 319 | [%run-updates run-updates] 320 | :: 321 | [%keys keys] 322 | [%tags tags] 323 | [%tag-queries tag-queries] 324 | == 325 | :: 326 | ++ add-graph 327 | %- ot 328 | :~ [%resource dejs:res] 329 | [%graph graph] 330 | [%mark (mu so)] 331 | [%overwrite bo] 332 | == 333 | :: 334 | ++ graph 335 | |= a=json 336 | ^- ^graph 337 | =/ or-mp ((on atom ^node) gth) 338 | %+ gas:or-mp ~ 339 | %+ turn ~(tap by ((om node) a)) 340 | |* [b=cord c=*] 341 | ^- [atom ^node] 342 | => .(+< [b c]=+<) 343 | [(rash b dem) c] 344 | :: 345 | ++ remove-graph (ot [%resource dejs:res]~) 346 | ++ archive-graph (ot [%resource dejs:res]~) 347 | ++ unarchive-graph (ot [%resource dejs:res]~) 348 | :: 349 | ++ add-nodes 350 | %- ot 351 | :~ [%resource dejs:res] 352 | [%nodes nodes] 353 | == 354 | :: 355 | ++ nodes (op ;~(pfix fas (more fas dem)) node) 356 | :: 357 | ++ node 358 | %- ot 359 | :~ [%post maybe-post] 360 | [%children internal-graph] 361 | == 362 | :: 363 | ++ internal-graph 364 | |= jon=json 365 | ^- ^internal-graph 366 | ?~ jon 367 | [%empty ~] 368 | [%graph (graph jon)] 369 | :: 370 | ++ maybe-post 371 | |= jon=json 372 | ^- ^maybe-post 373 | ?~ jon !! 374 | ?+ -.jon !! 375 | %s [%| (nu jon)] 376 | %o [%& (post jon)] 377 | == 378 | :: 379 | ++ post 380 | %- ot 381 | :~ [%author (su ;~(pfix sig fed:ag))] 382 | [%index index] 383 | [%time-sent di] 384 | [%contents (ar content)] 385 | [%hash (mu nu)] 386 | [%signatures (as signature)] 387 | == 388 | :: 389 | ++ content 390 | %- of 391 | :~ [%mention (su ;~(pfix sig fed:ag))] 392 | [%text so] 393 | [%url so] 394 | [%reference reference] 395 | [%code eval] 396 | == 397 | :: 398 | ++ reference 399 | |^ 400 | %- of 401 | :~ graph+graph 402 | group+dejs-path:res 403 | app+app 404 | == 405 | :: 406 | ++ graph 407 | %- ot 408 | :~ group+dejs-path:res 409 | graph+dejs-path:res 410 | index+index 411 | == 412 | :: 413 | ++ app 414 | %- ot 415 | :~ ship+(su ;~(pfix sig fed:ag)) 416 | desk+so 417 | path+pa 418 | == 419 | -- 420 | :: 421 | ++ tang 422 | |= jon=^json 423 | ^- ^tang 424 | ?> ?=(%a -.jon) 425 | %- zing 426 | %+ turn 427 | p.jon 428 | |= jo=^json 429 | ^- (list tank) 430 | ?> ?=(%a -.jo) 431 | %+ turn 432 | p.jo 433 | |= j=^json 434 | ?> ?=(%s -.j) 435 | ^- tank 436 | leaf+(trip p.j) 437 | :: 438 | ++ eval 439 | %- ot 440 | :~ expression+so 441 | output+tang 442 | == 443 | :: 444 | ++ remove-posts 445 | %- ot 446 | :~ [%resource dejs:res] 447 | [%indices (as index)] 448 | == 449 | :: 450 | ++ add-signatures 451 | %- ot 452 | :~ [%uid uid] 453 | [%signatures (as signature)] 454 | == 455 | :: 456 | ++ remove-signatures 457 | %- ot 458 | :~ [%uid uid] 459 | [%signatures (as signature)] 460 | == 461 | :: 462 | ++ signature 463 | %- ot 464 | :~ [%hash nu] 465 | [%ship (su ;~(pfix sig fed:ag))] 466 | [%life ni] 467 | == 468 | :: 469 | ++ uid 470 | %- ot 471 | :~ [%resource dejs:res] 472 | [%index index] 473 | == 474 | :: 475 | ++ index (su ;~(pfix fas (more fas dem))) 476 | :: 477 | ++ add-tag 478 | %- ot 479 | :~ [%term so] 480 | [%uid uid] 481 | == 482 | :: 483 | ++ remove-tag 484 | %- ot 485 | :~ [%term so] 486 | [%uid uid] 487 | == 488 | :: 489 | ++ keys 490 | |= =json 491 | *resources 492 | :: 493 | ++ tags 494 | |= =json 495 | *(set term) 496 | :: 497 | ++ tag-queries 498 | |= =json 499 | *^tag-queries 500 | :: 501 | ++ run-updates 502 | |= a=json 503 | ^- [resource update-log] 504 | [*resource *update-log] 505 | -- 506 | ++ pa 507 | |= j=json 508 | ^- path 509 | ?> ?=(%s -.j) 510 | ?: =('/' p.j) / 511 | (stab p.j) 512 | :: 513 | -- 514 | :: 515 | ++ create 516 | |_ [our=ship now=time] 517 | ++ post 518 | |= [=index contents=(list content)] 519 | ^- ^post 520 | :* our 521 | index 522 | now 523 | contents 524 | ~ 525 | *signatures 526 | == 527 | -- 528 | :: 529 | ++ upgrade 530 | |% 531 | :: 532 | :: +two 533 | :: 534 | ++ marked-graph-to-two 535 | |= [=graph:one m=(unit mark)] 536 | [(graph-to-two graph) m] 537 | :: 538 | ++ graph-to-two 539 | |= =graph:one 540 | (graph:(upgrade ,post:one ,maybe-post:two) graph post-to-two) 541 | :: 542 | ++ post-to-two 543 | |= p=post:one 544 | ^- maybe-post:two 545 | [%& p] 546 | :: 547 | :: 548 | :: +one 549 | :: 550 | ++ update-log-to-one 551 | |= =update-log:zero 552 | ^- update-log:one 553 | %+ gas:orm-log:one *update-log:one 554 | %+ turn (tap:orm-log:zero update-log) 555 | |= [=time =logged-update:zero] 556 | ^- [^time logged-update:one] 557 | :- time 558 | :- p.logged-update 559 | (logged-update-to-one q.logged-update) 560 | :: 561 | ++ logged-update-to-one 562 | |= upd=logged-update-0:zero 563 | ^- logged-action:one 564 | ?+ -.upd upd 565 | %add-graph upd(graph (graph-to-one graph.upd)) 566 | %add-nodes upd(nodes (~(run by nodes.upd) node-to-one)) 567 | == 568 | :: 569 | ++ node-to-one 570 | |= =node:zero 571 | (node:(upgrade ,post:zero ,post:one) node post-to-one) 572 | :: 573 | ++ graph-to-one 574 | |= =graph:zero 575 | (graph:(upgrade ,post:zero ,post:one) graph post-to-one) 576 | :: 577 | ++ marked-graph-to-one 578 | |= [=graph:zero m=(unit mark)] 579 | [(graph-to-one graph) m] 580 | :: 581 | ++ post-to-one 582 | |= p=post:zero 583 | ^- post:one 584 | p(contents (contents-to-one contents.p)) 585 | :: 586 | ++ contents-to-one 587 | |= cs=(list content:zero) 588 | ^- (list content:one) 589 | %+ murn cs 590 | |= =content:zero 591 | ^- (unit content:one) 592 | ?: ?=(%reference -.content) ~ 593 | `content 594 | :: 595 | ++ upgrade 596 | |* [in-pst=mold out-pst=mold] 597 | => 598 | |% 599 | ++ in-orm 600 | ((on atom in-node) gth) 601 | +$ in-node 602 | [post=in-pst children=in-internal-graph] 603 | +$ in-graph 604 | ((mop atom in-node) gth) 605 | +$ in-internal-graph 606 | $~ [%empty ~] 607 | $% [%graph p=in-graph] 608 | [%empty ~] 609 | == 610 | :: 611 | ++ out-orm 612 | ((on atom out-node) gth) 613 | +$ out-node 614 | [post=out-pst children=out-internal-graph] 615 | +$ out-graph 616 | ((mop atom out-node) gth) 617 | +$ out-internal-graph 618 | $~ [%empty ~] 619 | $% [%graph p=out-graph] 620 | [%empty ~] 621 | == 622 | -- 623 | |% 624 | :: 625 | ++ graph 626 | |= $: gra=in-graph 627 | fn=$-(in-pst out-pst) 628 | == 629 | ^- out-graph 630 | %+ gas:out-orm *out-graph 631 | ^- (list [atom out-node]) 632 | %+ turn (tap:in-orm gra) 633 | |= [a=atom n=in-node] 634 | ^- [atom out-node] 635 | [a (node n fn)] 636 | :: 637 | ++ node 638 | |= [nod=in-node fn=$-(in-pst out-pst)] 639 | ^- out-node 640 | :- (fn post.nod) 641 | ^- out-internal-graph 642 | ?: ?=(%empty -.children.nod) 643 | [%empty ~] 644 | [%graph (graph p.children.nod fn)] 645 | -- 646 | :: 647 | ++ zero-load 648 | :: =* infinitely recurses 649 | =, store=zero 650 | =, orm=orm:zero 651 | =, orm-log=orm-log:zero 652 | |% 653 | ++ change-revision-graph 654 | |= [=graph:store q=(unit mark)] 655 | ^- [graph:store (unit mark)] 656 | |^ 657 | :_ q 658 | ?+ q graph 659 | [~ %graph-validator-link] convert-links 660 | [~ %graph-validator-publish] convert-publish 661 | == 662 | :: 663 | ++ convert-links 664 | %+ gas:orm *graph:store 665 | %+ turn (tap:orm graph) 666 | |= [=atom =node:store] 667 | ^- [^atom node:store] 668 | :: top-level 669 | :: 670 | :+ atom post.node 671 | ?: ?=(%empty -.children.node) 672 | [%empty ~] 673 | :- %graph 674 | %+ gas:orm *graph:store 675 | %+ turn (tap:orm p.children.node) 676 | |= [=^atom =node:store] 677 | ^- [^^atom node:store] 678 | :: existing comments get turned into containers for revisions 679 | :: 680 | :^ atom 681 | post.node(contents ~, hash ~) 682 | %graph 683 | %+ gas:orm *graph:store 684 | :_ ~ :- %0 685 | :_ [%empty ~] 686 | post.node(index (snoc index.post.node atom), hash ~) 687 | :: 688 | ++ convert-publish 689 | %+ gas:orm *graph:store 690 | %+ turn (tap:orm graph) 691 | |= [=atom =node:store] 692 | ^- [^atom node:store] 693 | :: top-level 694 | :: 695 | :+ atom post.node 696 | ?: ?=(%empty -.children.node) 697 | [%empty ~] 698 | :- %graph 699 | %+ gas:orm *graph:store 700 | %+ turn (tap:orm p.children.node) 701 | |= [=^atom =node:store] 702 | ^- [^^atom node:store] 703 | :: existing container for publish note revisions 704 | :: 705 | ?+ atom !! 706 | %1 [atom node] 707 | %2 708 | :+ atom post.node 709 | ?: ?=(%empty -.children.node) 710 | [%empty ~] 711 | :- %graph 712 | %+ gas:orm *graph:store 713 | %+ turn (tap:orm p.children.node) 714 | |= [=^^atom =node:store] 715 | ^- [^^^atom node:store] 716 | :+ atom post.node(contents ~, hash ~) 717 | :- %graph 718 | %+ gas:orm *graph:store 719 | :_ ~ :- %1 720 | :_ [%empty ~] 721 | post.node(index (snoc index.post.node atom), hash ~) 722 | == 723 | -- 724 | :: 725 | ++ maybe-unix-to-da 726 | |= =atom 727 | ^- @ 728 | :: (bex 127) is roughly 226AD 729 | ?. (lte atom (bex 127)) 730 | atom 731 | (add ~1970.1.1 (div (mul ~s1 atom) 1.000)) 732 | :: 733 | ++ convert-unix-timestamped-node 734 | |= =node:store 735 | ^- node:store 736 | =. index.post.node 737 | (convert-unix-timestamped-index index.post.node) 738 | ?. ?=(%graph -.children.node) 739 | node 740 | :+ post.node 741 | %graph 742 | (convert-unix-timestamped-graph p.children.node) 743 | :: 744 | ++ convert-unix-timestamped-index 745 | |= =index:store 746 | (turn index maybe-unix-to-da) 747 | :: 748 | ++ convert-unix-timestamped-graph 749 | |= =graph:store 750 | %+ gas:orm *graph:store 751 | %+ turn 752 | (tap:orm graph) 753 | |= [=atom =node:store] 754 | ^- [^atom node:store] 755 | :- (maybe-unix-to-da atom) 756 | (convert-unix-timestamped-node node) 757 | -- 758 | -- 759 | ++ import 760 | |= [arc=* our=ship] 761 | ^- (quip card:agent:gall [%5 network]) 762 | |^ 763 | =/ sty [%5 (remake-network ;;(tree-network +.arc))] 764 | :_ sty 765 | %+ turn ~(tap by graphs.sty) 766 | |= [rid=resource =marked-graph] 767 | ^- card:agent:gall 768 | ?: =(our entity.rid) 769 | =/ =cage [%push-hook-action !>([%add rid])] 770 | [%pass / %agent [our %graph-push-hook] %poke cage] 771 | (try-rejoin rid 0) 772 | :: 773 | +$ tree-network 774 | $: graphs=tree-graphs 775 | tag-queries=(tree [term (tree uid)]) 776 | update-logs=tree-update-logs 777 | archive=tree-graphs 778 | ~ 779 | == 780 | +$ tree-graphs (tree [resource tree-marked-graph]) 781 | +$ tree-marked-graph [p=tree-graph q=(unit ^mark)] 782 | +$ tree-graph (tree [atom tree-node]) 783 | +$ tree-node [post=tree-maybe-post children=tree-internal-graph] 784 | +$ tree-internal-graph 785 | $~ [%empty ~] 786 | $% [%graph p=tree-graph] 787 | [%empty ~] 788 | == 789 | +$ tree-update-logs (tree [resource tree-update-log]) 790 | +$ tree-update-log (tree [time tree-logged-update]) 791 | +$ tree-logged-update 792 | $: p=time 793 | $= q 794 | $% [%add-graph =resource =tree-graph mark=(unit ^mark) ow=?] 795 | [%add-nodes =resource nodes=(tree [index tree-node])] 796 | [%remove-posts =resource indices=(tree index)] 797 | [%add-signatures =uid signatures=tree-signatures] 798 | [%remove-signatures =uid signatures=tree-signatures] 799 | == 800 | == 801 | +$ tree-signatures (tree signature) 802 | +$ tree-maybe-post (each tree-post hash) 803 | +$ tree-post 804 | $: author=ship 805 | =index 806 | time-sent=time 807 | contents=(list content) 808 | hash=(unit hash) 809 | signatures=tree-signatures 810 | == 811 | :: 812 | ++ remake-network 813 | |= t=tree-network 814 | ^- network 815 | :* (remake-graphs graphs.t) 816 | (remake-jug:migrate tag-queries.t) 817 | (remake-update-logs update-logs.t) 818 | (remake-graphs archive.t) 819 | ~ 820 | == 821 | :: 822 | ++ remake-graphs 823 | |= t=tree-graphs 824 | ^- graphs 825 | %- remake-map:migrate 826 | (~(run by t) remake-marked-graph) 827 | :: 828 | ++ remake-marked-graph 829 | |= t=tree-marked-graph 830 | ^- marked-graph 831 | [(remake-graph p.t) q.t] 832 | :: 833 | ++ remake-graph 834 | |= t=tree-graph 835 | ^- graph 836 | %+ gas:orm *graph 837 | %+ turn ~(tap by t) 838 | |= [a=atom tn=tree-node] 839 | ^- [atom node] 840 | [a (remake-node tn)] 841 | :: 842 | ++ remake-internal-graph 843 | |= t=tree-internal-graph 844 | ^- internal-graph 845 | ?: ?=(%empty -.t) 846 | [%empty ~] 847 | [%graph (remake-graph p.t)] 848 | :: 849 | ++ remake-node 850 | |= t=tree-node 851 | ^- node 852 | :- (remake-post post.t) 853 | (remake-internal-graph children.t) 854 | :: 855 | ++ remake-update-logs 856 | |= t=tree-update-logs 857 | ^- update-logs 858 | %- remake-map:migrate 859 | (~(run by t) remake-update-log) 860 | :: 861 | ++ remake-update-log 862 | |= t=tree-update-log 863 | ^- update-log 864 | =/ ulm ((on time logged-update) gth) 865 | %+ gas:ulm *update-log 866 | %+ turn ~(tap by t) 867 | |= [=time tlu=tree-logged-update] 868 | ^- [^time logged-update] 869 | [time (remake-logged-update tlu)] 870 | :: 871 | ++ remake-logged-update 872 | |= t=tree-logged-update 873 | ^- logged-update 874 | :- p.t 875 | ?- -.q.t 876 | %add-graph 877 | :* %add-graph 878 | resource.q.t 879 | (remake-graph tree-graph.q.t) 880 | mark.q.t 881 | ow.q.t 882 | == 883 | :: 884 | %add-nodes 885 | :- %add-nodes 886 | :- resource.q.t 887 | %- remake-map:migrate 888 | (~(run by nodes.q.t) remake-node) 889 | :: 890 | %remove-posts 891 | [%remove-posts resource.q.t (remake-set:migrate indices.q.t)] 892 | :: 893 | %add-signatures 894 | [%add-signatures uid.q.t (remake-set:migrate signatures.q.t)] 895 | :: 896 | %remove-signatures 897 | [%remove-signatures uid.q.t (remake-set:migrate signatures.q.t)] 898 | == 899 | :: 900 | ++ remake-post 901 | |= t=tree-maybe-post 902 | ^- maybe-post 903 | ?- -.t 904 | %| t 905 | %& t(signatures.p (remake-set:migrate signatures.p.t)) 906 | == 907 | :: 908 | ++ try-rejoin 909 | |= [rid=resource nack-count=@] 910 | ^- card:agent:gall 911 | =/ res-path (en-path:res rid) 912 | =/ wire [%try-rejoin (scot %ud nack-count) res-path] 913 | =/ =cage 914 | :- %pull-hook-action 915 | !> ^- action:pull-hook 916 | [%add [entity .]:rid] 917 | [%pass wire %agent [our %graph-pull-hook] %poke cage] 918 | -- 919 | -- 920 | -------------------------------------------------------------------------------- /src/lib/graph-utils.hoon: -------------------------------------------------------------------------------- 1 | /- *resource, gs=graph-store, gspost=post 2 | /+ default-agent, dbug, sig=signatures 3 | |_ =bowl:gall 4 | +$ add-nodes-action [%add-nodes =resource nodes=(map index:gspost node:gs)] 5 | ++ build-dm 6 | |= [target=@p contents=(list content:gspost)] 7 | ^- update:gs 8 | =/ i=(list atom) ~[`@`target +(now.bowl)] 9 | =/ p (build-post i contents) 10 | =/ n (build-node p) 11 | =/ a (build-action n [our.bowl %dm-inbox]) 12 | (build-update a) 13 | ++ build-post 14 | |= [index=(list atom) contents=(list content:gspost)] 15 | ^- post:gspost 16 | =/ author our.bowl 17 | =/ time-sent now.bowl 18 | =/ hash `@ux`(sham [~ author time-sent contents]) 19 | =/ signature (sign:sig our.bowl now.bowl hash) 20 | [author=author index=index time-sent=time-sent contents=contents hash=(some hash) signatures=(sy signature^~)] 21 | ++ build-node 22 | |= [post=post:gspost] 23 | ^- node:gs 24 | [post=[%& p=post] children=[%empty ~]] 25 | ++ build-action 26 | |= [node=node:gs =resource] 27 | ^- action:gs 28 | ?> ?=(%& -.post.node) 29 | =/ post `post:gs`+.post.node 30 | =/ index index.post 31 | =/ map (my ~[[index node]]) 32 | [%add-nodes resource=resource nodes=map] 33 | ++ build-update 34 | |= [action=action:gs] 35 | ^- update:gs 36 | [p=now.bowl q=action] 37 | ++ build-poke-card 38 | |= [reply=update:gs =resource] 39 | ^- card 40 | =/ cage `cage`[%graph-update-3 !>(reply)] 41 | =/ task `task:agent:gall`[%poke cage] 42 | =/ ship 43 | ?: .=(our.bowl entity.resource) 44 | `[@p @tas]`[our.bowl %graph-store] 45 | `[@p @tas]`[our.bowl %graph-push-hook] 46 | :: =/ ship `[@p @tas]`[our.bowl %graph-push-hook] :: this fucks things up if the bot is hosting the group 47 | =/ note `note:agent:gall`[%agent ship task] 48 | =/ wire `wire`/graph-store-bottest :: apparently must be the same as the subscription wire 49 | =/ card `card`[%pass wire note] 50 | card 51 | ++ update-from-cage 52 | |= =cage 53 | ^- update:gs 54 | =/ mark p.cage 55 | =/ vase q.cage 56 | `update:gs`!<(=update:gs vase) 57 | ++ action-from-update 58 | |= =update:gs 59 | ^- (unit add-nodes-action) 60 | =/ action=action:gs q.update 61 | ?+ action ~ 62 | [%add-nodes *] 63 | `action 64 | == 65 | ++ starts-with 66 | |= [str=tape nedl=tape] 67 | ^- ? 68 | ::TODO find vs scag & compare? 69 | =((find nedl str) [~ 0]) 70 | ++ resource-from-action 71 | |= =add-nodes-action 72 | ^- resource 73 | resource.add-nodes-action 74 | ++ node-from-action 75 | |= =add-nodes-action 76 | ^- (unit node:gs) 77 | =/ nodes nodes.add-nodes-action 78 | =/ values ~(val by nodes) 79 | ?~ values ~ 80 | `i.values 81 | ++ post-from-node 82 | |= =node:gs 83 | ^- (unit post:gspost) 84 | ?: ?=(%& -.post.node) :: this checks for maybe-post, i.e. deleted posts 85 | `+.post.node 86 | ~ 87 | ++ index-from-post 88 | |= =post:gspost 89 | ^- index:gspost 90 | index.post 91 | ++ author-from-post 92 | |= =post:gs 93 | ^- ship 94 | author.post 95 | ++ contents-from-post 96 | |= =post:gs 97 | ^- (list content:gspost) 98 | contents.post 99 | ++ time-from-post 100 | |= =post:gs 101 | ^- time 102 | time-sent.post 103 | ++ extract-first-text 104 | |= contents=(list content:gspost) 105 | ^- (unit @t) 106 | ?+ i.-.contents ~ 107 | [%text *] 108 | `text.i.-.contents 109 | == 110 | :: with thanks to ~hosted-fornet crunch library 111 | ++ contents-to-cord 112 | |= contents=(list content:gspost) 113 | ^- @t 114 | ?~ contents 115 | '' 116 | %+ join-cords 117 | ' ' 118 | (turn contents content-to-cord) 119 | :: 120 | ++ content-to-cord 121 | |= =content:gspost 122 | ^- @t 123 | ?- -.content 124 | %text text.content 125 | %mention (scot %p ship.content) 126 | %url url.content 127 | %code expression.content :: TODO: also print output? 128 | %reference 'reference'::(reference-content-to-cord reference.content) 129 | :: references must be scried and displayed as such 130 | == 131 | ++ join-cords 132 | |= [delimiter=@t cords=(list @t)] 133 | ^- @t 134 | %+ roll cords 135 | |= [cord=@t out=@t] 136 | ^- @t 137 | ?: =('' out) 138 | :: don't put delimiter before first element 139 | :: 140 | cord 141 | (rap 3 out delimiter cord ~) 142 | :: 143 | ++ reference-content-to-cord 144 | |= =reference:gspost 145 | ^- @t 146 | ?- -.reference 147 | %group (resource-to-cord group.reference) 148 | %graph (rap 3 (resource-to-cord group.reference) ': ' (resource-to-cord resource.uid.reference) ~) 149 | %app (rap 3 'app: ' (scot %p ship.reference) ' %' (scot %tas desk.reference) ' ' "{}") 150 | == 151 | ++ resource-to-cord 152 | |= =resource ^- @t 153 | =/ t=tape "{}/{}" 154 | (crip t) 155 | ++ scry-node 156 | |= [=bowl:gall =resource index=@] 157 | .^ 158 | update:gs 159 | %gx 160 | (scot %p our.bowl) 161 | %graph-store 162 | (scot %da now.bowl) 163 | %graph 164 | (scot %p entity.resource) 165 | name.resource 166 | %node 167 | %index 168 | %kith 169 | (scot %ud index) 170 | /noun 171 | == 172 | -- -------------------------------------------------------------------------------- /src/lib/migrate.hoon: -------------------------------------------------------------------------------- 1 | ^? |% 2 | ++ remake-set 3 | |* s=(tree) 4 | (sy ~(tap in s)) 5 | :: 6 | ++ remake-map 7 | |* m=(tree) 8 | (my ~(tap by m)) 9 | :: 10 | ++ remake-jug 11 | |* j=(tree [* (tree)]) 12 | %- remake-map 13 | (~(run by j) remake-set) 14 | :: 15 | ++ remake-map-of-map 16 | |* mm=(tree [* (tree)]) 17 | %- remake-map 18 | (~(run by mm) remake-map) 19 | -- 20 | -------------------------------------------------------------------------------- /src/lib/resource.hoon: -------------------------------------------------------------------------------- 1 | /- sur=resource 2 | =< resource 3 | |% 4 | +$ resource resource:sur 5 | ++ en-path 6 | |= =resource 7 | ^- path 8 | ~[%ship (scot %p entity.resource) name.resource] 9 | :: 10 | ++ de-path 11 | |= =path 12 | ^- resource 13 | (need (de-path-soft path)) 14 | :: 15 | ++ de-path-soft 16 | |= =path 17 | ^- (unit resource) 18 | ?. ?=([%ship @ @ *] path) 19 | ~ 20 | =/ ship 21 | (slaw %p i.t.path) 22 | ?~ ship 23 | ~ 24 | `[u.ship i.t.t.path] 25 | :: 26 | ++ enjs 27 | |= =resource 28 | ^- json 29 | =, enjs:format 30 | %- pairs 31 | :~ ship+(ship entity.resource) 32 | name+s+name.resource 33 | == 34 | :: 35 | ++ enjs-path 36 | |= =resource 37 | %- spat 38 | (en-path resource) 39 | :: 40 | ++ dejs-path 41 | %- su:dejs:format 42 | ;~ pfix 43 | (jest '/ship/') 44 | ;~((glue fas) ;~(pfix sig fed:ag) urs:ab) 45 | == 46 | :: 47 | ++ dejs 48 | =, dejs:format 49 | ^- $-(json resource) 50 | |= jon=json 51 | ~| dejs+%resource 52 | %. jon 53 | %- ot 54 | :~ ship+(su ;~(pfix sig fed:ag)) 55 | name+so 56 | == 57 | -- 58 | -------------------------------------------------------------------------------- /src/lib/server.hoon: -------------------------------------------------------------------------------- 1 | =, eyre 2 | |% 3 | +$ request-line 4 | $: [ext=(unit @ta) site=(list @t)] 5 | args=(list [key=@t value=@t]) 6 | == 7 | :: +parse-request-line: take a cord and parse out a url 8 | :: 9 | ++ parse-request-line 10 | |= url=@t 11 | ^- request-line 12 | (fall (rush url ;~(plug apat:de-purl:html yque:de-purl:html)) [[~ ~] ~]) 13 | :: 14 | ++ manx-to-octs 15 | |= man=manx 16 | ^- octs 17 | (as-octt:mimes:html (en-xml:html man)) 18 | :: 19 | ++ json-to-octs 20 | |= jon=json 21 | ^- octs 22 | (as-octt:mimes:html (en-json:html jon)) 23 | :: 24 | ++ app 25 | |% 26 | :: 27 | :: +require-authorization: 28 | :: redirect to the login page when unauthenticated 29 | :: otherwise call handler on inbound request 30 | :: 31 | ++ require-authorization 32 | |= $: =inbound-request:eyre 33 | handler=$-(inbound-request:eyre simple-payload:http) 34 | == 35 | ^- simple-payload:http 36 | :: 37 | ?: authenticated.inbound-request 38 | ~! this 39 | ~! +:*handler 40 | (handler inbound-request) 41 | :: 42 | =- [[307 ['location' -]~] ~] 43 | %^ cat 3 44 | '/~/login?redirect=' 45 | url.request.inbound-request 46 | :: 47 | :: +require-authorization-simple: 48 | :: redirect to the login page when unauthenticated 49 | :: otherwise pass through simple-paylod 50 | :: 51 | ++ require-authorization-simple 52 | |= [=inbound-request:eyre =simple-payload:http] 53 | ^- simple-payload:http 54 | :: 55 | ?: authenticated.inbound-request 56 | ~! this 57 | simple-payload 58 | :: 59 | =- [[307 ['location' -]~] ~] 60 | %^ cat 3 61 | '/~/login?redirect=' 62 | url.request.inbound-request 63 | :: 64 | ++ give-simple-payload 65 | |= [eyre-id=@ta =simple-payload:http] 66 | ^- (list card:agent:gall) 67 | =/ header-cage 68 | [%http-response-header !>(response-header.simple-payload)] 69 | =/ data-cage 70 | [%http-response-data !>(data.simple-payload)] 71 | :~ [%give %fact ~[/http-response/[eyre-id]] header-cage] 72 | [%give %fact ~[/http-response/[eyre-id]] data-cage] 73 | [%give %kick ~[/http-response/[eyre-id]] ~] 74 | == 75 | -- 76 | ++ gen 77 | |% 78 | :: 79 | ++ max-1-da ['cache-control' 'max-age=86400'] 80 | ++ max-1-wk ['cache-control' 'max-age=604800'] 81 | :: 82 | ++ html-response 83 | =| cache=? 84 | |= =octs 85 | ^- simple-payload:http 86 | :_ `octs 87 | [200 [['content-type' 'text/html'] ?:(cache [max-1-wk ~] ~)]] 88 | :: 89 | ++ css-response 90 | =| cache=? 91 | |= =octs 92 | ^- simple-payload:http 93 | :_ `octs 94 | [200 [['content-type' 'text/css'] ?:(cache [max-1-wk ~] ~)]] 95 | :: 96 | ++ js-response 97 | =| cache=? 98 | |= =octs 99 | ^- simple-payload:http 100 | :_ `octs 101 | [200 [['content-type' 'text/javascript'] ?:(cache [max-1-wk ~] ~)]] 102 | :: 103 | ++ png-response 104 | =| cache=? 105 | |= =octs 106 | ^- simple-payload:http 107 | :_ `octs 108 | [200 [['content-type' 'image/png'] ?:(cache [max-1-wk ~] ~)]] 109 | :: 110 | ++ svg-response 111 | =| cache=? 112 | |= =octs 113 | ^- simple-payload:http 114 | :_ `octs 115 | [200 [['content-type' 'image/svg+xml'] ?:(cache [max-1-wk ~] ~)]] 116 | :: 117 | ++ ico-response 118 | |= =octs 119 | ^- simple-payload:http 120 | [[200 [['content-type' 'image/x-icon'] max-1-wk ~]] `octs] 121 | :: 122 | ++ woff2-response 123 | =| cache=? 124 | |= =octs 125 | ^- simple-payload:http 126 | [[200 [['content-type' 'font/woff2'] max-1-wk ~]] `octs] 127 | :: 128 | ++ json-response 129 | =| cache=_| 130 | |= =json 131 | ^- simple-payload:http 132 | :_ `(json-to-octs json) 133 | [200 [['content-type' 'application/json'] ?:(cache [max-1-da ~] ~)]] 134 | :: 135 | ++ manx-response 136 | =| cache=_| 137 | |= man=manx 138 | ^- simple-payload:http 139 | :_ `(manx-to-octs man) 140 | [200 [['content-type' 'text/html'] ?:(cache [max-1-da ~] ~)]] 141 | :: 142 | ++ not-found 143 | ^- simple-payload:http 144 | [[404 ~] ~] 145 | :: 146 | ++ login-redirect 147 | |= =request:http 148 | ^- simple-payload:http 149 | =- [[307 ['location' -]~] ~] 150 | %^ cat 3 151 | '/~/login?redirect=' 152 | url.request 153 | :: 154 | ++ redirect 155 | |= redirect=cord 156 | ^- simple-payload:http 157 | [[307 ['location' redirect]~] ~] 158 | -- 159 | -- 160 | -------------------------------------------------------------------------------- /src/lib/signatures.hoon: -------------------------------------------------------------------------------- 1 | /- post 2 | ^? 3 | =< [post .] 4 | =, post 5 | |% 6 | ++ jael-scry 7 | |* [=mold our=ship desk=term now=time =path] 8 | .^ mold 9 | %j 10 | (scot %p our) 11 | desk 12 | (scot %da now) 13 | path 14 | == 15 | ++ sign 16 | |= [our=ship now=time =hash] 17 | ^- signature 18 | =+ (jael-scry ,=life our %life now /(scot %p our)) 19 | =+ (jael-scry ,=ring our %vein now /(scot %ud life)) 20 | :+ `@ux`(sign:as:(nol:nu:crub:crypto ring) hash) 21 | our 22 | life 23 | :: 24 | ++ is-signature-valid 25 | |= [our=ship =signature =hash now=time] 26 | ^- ? 27 | =+ (jael-scry ,lyf=(unit @) our %lyfe now /(scot %p q.signature)) 28 | :: we do not have a public key from ship at this life 29 | :: 30 | ?~ lyf %.y 31 | ?. =(u.lyf r.signature) %.y 32 | =+ %: jael-scry 33 | ,deed=[a=life b=pass c=(unit @ux)] 34 | our %deed now /(scot %p q.signature)/(scot %ud r.signature) 35 | == 36 | :: if signature is from a past life, skip validation 37 | :: XX: should be visualised on frontend, not great. 38 | ?. =(a.deed r.signature) %.y 39 | :: verify signature from ship at life 40 | :: 41 | =/ them 42 | (com:nu:crub:crypto b.deed) 43 | =(`hash (sure:as.them p.signature)) 44 | :: 45 | ++ are-signatures-valid 46 | |= [our=ship =signatures =hash now=time] 47 | ^- ? 48 | =/ signature-list ~(tap in signatures) 49 | |- 50 | ?~ signature-list 51 | %.y 52 | ?: (is-signature-valid our i.signature-list hash now) 53 | $(signature-list t.signature-list) 54 | %.n 55 | -- 56 | -------------------------------------------------------------------------------- /src/lib/skeleton.hoon: -------------------------------------------------------------------------------- 1 | :: Similar to default-agent except crashes everywhere 2 | ^- agent:gall 3 | |_ bowl:gall 4 | ++ on-init 5 | ^- (quip card:agent:gall agent:gall) 6 | !! 7 | :: 8 | ++ on-save 9 | ^- vase 10 | !! 11 | :: 12 | ++ on-load 13 | |~ old-state=vase 14 | ^- (quip card:agent:gall agent:gall) 15 | !! 16 | :: 17 | ++ on-poke 18 | |~ in-poke-data=cage 19 | ^- (quip card:agent:gall agent:gall) 20 | !! 21 | :: 22 | ++ on-watch 23 | |~ path 24 | ^- (quip card:agent:gall agent:gall) 25 | !! 26 | :: 27 | ++ on-leave 28 | |~ path 29 | ^- (quip card:agent:gall agent:gall) 30 | !! 31 | :: 32 | ++ on-peek 33 | |~ path 34 | ^- (unit (unit cage)) 35 | !! 36 | :: 37 | ++ on-agent 38 | |~ [wire sign:agent:gall] 39 | ^- (quip card:agent:gall agent:gall) 40 | !! 41 | :: 42 | ++ on-arvo 43 | |~ [wire =sign-arvo] 44 | ^- (quip card:agent:gall agent:gall) 45 | !! 46 | :: 47 | ++ on-fail 48 | |~ [term tang] 49 | ^- (quip card:agent:gall agent:gall) 50 | !! 51 | -- 52 | -------------------------------------------------------------------------------- /src/mar/authenticate-with-urbit-id/action.hoon: -------------------------------------------------------------------------------- 1 | /- authenticate-with-urbit-id 2 | |_ act=action:authenticate-with-urbit-id 3 | ++ grab 4 | |% 5 | ++ noun action:authenticate-with-urbit-id 6 | -- 7 | ++ grow 8 | |% 9 | ++ noun act 10 | -- 11 | ++ grad %noun 12 | -- 13 | -------------------------------------------------------------------------------- /src/mar/bill.hoon: -------------------------------------------------------------------------------- 1 | |_ bil=(list dude:gall) 2 | ++ grow 3 | |% 4 | ++ mime `^mime`[/text/x-bill (as-octs:mimes:html hoon)] 5 | ++ noun bil 6 | ++ hoon 7 | ^- @t 8 | |^ (crip (of-wall:format (wrap-lines (spit-duz bil)))) 9 | :: 10 | ++ wrap-lines 11 | |= taz=wall 12 | ^- wall 13 | ?~ taz ["~"]~ 14 | :- (weld ":~ " i.taz) 15 | %- snoc :_ "==" 16 | (turn t.taz |=(t=tape (weld " " t))) 17 | :: 18 | ++ spit-duz 19 | |= duz=(list dude:gall) 20 | ^- wall 21 | (turn duz |=(=dude:gall ['%' (trip dude)])) 22 | -- 23 | ++ txt (to-wain:format hoon) 24 | -- 25 | ++ grab 26 | |% 27 | ++ noun (list dude:gall) 28 | ++ mime 29 | |= [=mite len=@ud tex=@] 30 | ~_ tex 31 | !<((list dude:gall) (slap !>(~) (ream tex))) 32 | -- 33 | ++ grad %noun 34 | -- 35 | 36 | -------------------------------------------------------------------------------- /src/mar/docket-0.hoon: -------------------------------------------------------------------------------- 1 | /+ dock=docket 2 | |_ =docket:dock 3 | ++ grow 4 | |% 5 | ++ mime 6 | ^- ^mime 7 | [/text/x-docket (as-octt:mimes:html (spit-docket:mime:dock docket))] 8 | ++ noun docket 9 | ++ json (docket:enjs:dock docket) 10 | -- 11 | ++ grab 12 | |% 13 | :: 14 | ++ mime 15 | |= [=mite len=@ud tex=@] 16 | ^- docket:dock 17 | %- need 18 | %- from-clauses:mime:dock 19 | !<((list clause:dock) (slap !>(~) (ream tex))) 20 | 21 | :: 22 | ++ noun docket:dock 23 | -- 24 | ++ grad %noun 25 | -- 26 | 27 | -------------------------------------------------------------------------------- /src/mar/hoon.hoon: -------------------------------------------------------------------------------- 1 | :::: /hoon/hoon/mar 2 | :: 3 | /? 310 4 | :: 5 | =, eyre 6 | |_ own=@t 7 | :: 8 | ++ grow :: convert to 9 | |% 10 | ++ mime `^mime`[/text/x-hoon (as-octs:mimes:html own)] :: convert to %mime 11 | ++ elem :: convert to %html 12 | ;div:pre(urb_codemirror "", mode "hoon"):"{(trip own)}" 13 | :: =+ gen-id="src-{<`@ui`(mug own)>}" 14 | :: ;div 15 | :: ;textarea(id "{gen-id}"):"{(trip own)}" 16 | :: ;script:""" 17 | :: CodeMirror.fromTextArea( 18 | :: window[{}], 19 | :: \{lineNumbers:true, readOnly:true} 20 | :: ) 21 | :: """ 22 | :: == 23 | ++ hymn 24 | :: ;html:(head:title:"Source" "+{elem}") 25 | ;html 26 | ;head 27 | ;title:"Source" 28 | ;script@"//cdnjs.cloudflare.com/ajax/libs/codemirror/4.3.0/codemirror.js"; 29 | ;script@"/lib/syntax/hoon.js"; 30 | ;link(rel "stylesheet", href "//cdnjs.cloudflare.com/ajax/libs/". 31 | "codemirror/4.3.0/codemirror.min.css"); 32 | ;link/"/lib/syntax/codemirror.css"(rel "stylesheet"); 33 | == 34 | ;body 35 | ;textarea#src:"{(trip own)}" 36 | ;script:'CodeMirror.fromTextArea(src, {lineNumbers:true, readOnly:true})' 37 | == 38 | == 39 | ++ txt 40 | (to-wain:format own) 41 | -- 42 | ++ grab 43 | |% :: convert from 44 | ++ mime |=([p=mite q=octs] q.q) 45 | ++ noun @t :: clam from %noun 46 | ++ txt of-wain:format 47 | -- 48 | ++ grad %txt 49 | -- 50 | -------------------------------------------------------------------------------- /src/mar/kelvin.hoon: -------------------------------------------------------------------------------- 1 | =/ weft ,[lal=@tas num=@ud] :: TODO remove after merge 2 | |_ kel=weft 3 | ++ grow 4 | |% 5 | ++ mime `^mime`[/text/x-kelvin (as-octs:mimes:html hoon)] 6 | ++ noun kel 7 | ++ hoon (crip "{<[lal num]:kel>}\0a") 8 | ++ txt (to-wain:format hoon) 9 | -- 10 | ++ grab 11 | |% 12 | ++ noun weft 13 | ++ mime 14 | |= [=mite len=@ud tex=@] 15 | !<(weft (slap !>(~) (ream tex))) 16 | -- 17 | ++ grad %noun 18 | -- 19 | -------------------------------------------------------------------------------- /src/mar/mime.hoon: -------------------------------------------------------------------------------- 1 | :: 2 | :::: /hoon/mime/mar 3 | :: 4 | /? 310 5 | :: 6 | |_ own=mime 7 | ++ grow 8 | ^? 9 | |% 10 | ++ jam `@`q.q.own 11 | -- 12 | :: 13 | ++ grab :: convert from 14 | ^? 15 | |% 16 | ++ noun mime :: clam from %noun 17 | ++ tape 18 | |=(a=_"" [/application/x-urb-unknown (as-octt:mimes:html a)]) 19 | -- 20 | ++ grad 21 | ^? 22 | |% 23 | ++ form %mime 24 | ++ diff |=(mime +<) 25 | ++ pact |=(mime +<) 26 | ++ join |=([mime mime] `(unit mime)`~) 27 | ++ mash 28 | |= [[ship desk mime] [ship desk mime]] 29 | ^- mime 30 | ~|(%mime-mash !!) 31 | -- 32 | -- 33 | 34 | -------------------------------------------------------------------------------- /src/mar/noun.hoon: -------------------------------------------------------------------------------- 1 | :: 2 | :::: /hoon/noun/mar 3 | :: 4 | /? 310 5 | !: 6 | :::: A minimal noun mark 7 | |_ non=* 8 | ++ grab |% 9 | ++ noun * 10 | -- 11 | ++ grad 12 | |% 13 | ++ form %noun 14 | ++ diff |=(* +<) 15 | ++ pact |=(* +<) 16 | ++ join |=([* *] *(unit *)) 17 | ++ mash |=([[ship desk *] [ship desk *]] `*`~|(%noun-mash !!)) 18 | -- 19 | -- 20 | -------------------------------------------------------------------------------- /src/mar/ship.hoon: -------------------------------------------------------------------------------- 1 | |_ s=ship 2 | ++ grad %noun 3 | ++ grow 4 | |% 5 | ++ noun s 6 | ++ json s+(scot %p s) 7 | ++ mime 8 | ^- ^mime 9 | [/text/x-ship (as-octt:mimes:html (scow %p s))] 10 | 11 | -- 12 | ++ grab 13 | |% 14 | ++ noun ship 15 | ++ json (su:dejs:format ;~(pfix sig fed:ag)) 16 | ++ mime 17 | |= [=mite len=@ tex=@] 18 | (slav %p (snag 0 (to-wain:format tex))) 19 | -- 20 | -- 21 | -------------------------------------------------------------------------------- /src/sur/auth-id.hoon: -------------------------------------------------------------------------------- 1 | |% 2 | +$ action 3 | $% [%http-get url=@t] 4 | [%disconnect bind=binding:eyre] 5 | == 6 | -- 7 | -------------------------------------------------------------------------------- /src/sur/docket.hoon: -------------------------------------------------------------------------------- 1 | |% 2 | :: 3 | +$ version 4 | [major=@ud minor=@ud patch=@ud] 5 | :: 6 | +$ glob (map path mime) 7 | :: 8 | +$ url cord 9 | :: $glob-location: How to retrieve a glob 10 | :: 11 | +$ glob-reference 12 | [hash=@uvH location=glob-location] 13 | :: 14 | +$ glob-location 15 | $% [%http =url] 16 | [%ames =ship] 17 | == 18 | :: $href: Where a tile links to 19 | :: 20 | +$ href 21 | $% [%glob base=term =glob-reference] 22 | [%site =path] 23 | == 24 | :: $chad: State of a docket 25 | :: 26 | +$ chad 27 | $~ [%install ~] 28 | $% :: Done 29 | [%glob =glob] 30 | [%site ~] 31 | :: Waiting 32 | [%install ~] 33 | [%suspend glob=(unit glob)] 34 | :: Error 35 | [%hung err=cord] 36 | == 37 | :: 38 | :: $charge: A realized $docket 39 | :: 40 | +$ charge 41 | $: =docket 42 | =chad 43 | == 44 | :: 45 | :: $clause: A key and value, as part of a docket 46 | :: 47 | :: Only used to parse $docket 48 | :: 49 | +$ clause 50 | $% [%title title=@t] 51 | [%info info=@t] 52 | [%color color=@ux] 53 | [%glob-http url=cord hash=@uvH] 54 | [%glob-ames =ship hash=@uvH] 55 | [%image =url] 56 | [%site =path] 57 | [%base base=term] 58 | [%version =version] 59 | [%website website=url] 60 | [%license license=cord] 61 | == 62 | :: 63 | :: $docket: A description of JS bundles for a desk 64 | :: 65 | +$ docket 66 | $: %1 67 | title=@t 68 | info=@t 69 | color=@ux 70 | =href 71 | image=(unit url) 72 | =version 73 | website=url 74 | license=cord 75 | == 76 | :: 77 | +$ charge-update 78 | $% [%initial initial=(map desk charge)] 79 | [%add-charge =desk =charge] 80 | [%del-charge =desk] 81 | == 82 | -- 83 | 84 | -------------------------------------------------------------------------------- /src/sur/graph-store.hoon: -------------------------------------------------------------------------------- 1 | /- *post 2 | |% 3 | +$ graph ((mop atom node) gth) 4 | +$ marked-graph [p=graph q=(unit mark)] 5 | :: 6 | +$ maybe-post (each post hash) 7 | +$ node [post=maybe-post children=internal-graph] 8 | +$ graphs (map resource marked-graph) 9 | :: 10 | +$ tag-queries (jug term uid) 11 | :: 12 | +$ update-log ((mop time logged-update) gth) 13 | +$ update-logs (map resource update-log) 14 | :: 15 | +$ internal-graph 16 | $~ [%empty ~] 17 | $% [%graph p=graph] 18 | [%empty ~] 19 | == 20 | :: 21 | +$ network 22 | $: =graphs 23 | =tag-queries 24 | =update-logs 25 | archive=graphs 26 | ~ 27 | == 28 | :: 29 | +$ update [p=time q=action] 30 | :: 31 | +$ logged-update [p=time q=logged-action] 32 | 33 | :: 34 | +$ logged-action 35 | $% [%add-graph =resource =graph mark=(unit mark) overwrite=?] 36 | [%add-nodes =resource nodes=(map index node)] 37 | [%remove-posts =resource indices=(set index)] 38 | [%add-signatures =uid =signatures] 39 | [%remove-signatures =uid =signatures] 40 | == 41 | :: 42 | +$ action 43 | $% logged-action 44 | [%remove-graph =resource] 45 | :: 46 | [%add-tag =term =uid] 47 | [%remove-tag =term =uid] 48 | :: 49 | [%archive-graph =resource] 50 | [%unarchive-graph =resource] 51 | [%run-updates =resource =update-log] 52 | :: 53 | :: NOTE: cannot be sent as pokes 54 | :: 55 | [%keys =resources] 56 | [%tags tags=(set term)] 57 | [%tag-queries =tag-queries] 58 | == 59 | :: 60 | +$ permissions 61 | [admin=permission-level writer=permission-level reader=permission-level] 62 | :: 63 | :: $permission-level: levels of permissions in increasing order 64 | :: 65 | :: %no: May not add/remove node 66 | :: %self: May only nodes beneath nodes that were added by 67 | :: the same pilot, may remove nodes that the pilot 'owns' 68 | :: %yes: May add a node or remove node 69 | +$ permission-level 70 | ?(%no %self %yes) 71 | :: 72 | :: %graph-store types version 2 73 | :: 74 | ++ two 75 | =< [. post-one] 76 | =, post-one 77 | |% 78 | +$ maybe-post (each post hash) 79 | ++ orm ((on atom node) gth) 80 | ++ orm-log ((on time logged-update) gth) 81 | :: 82 | +$ graph ((mop atom node) gth) 83 | +$ marked-graph [p=graph q=(unit mark)] 84 | :: 85 | +$ node [post=maybe-post children=internal-graph] 86 | +$ graphs (map resource marked-graph) 87 | :: 88 | +$ tag-queries (jug term resource) 89 | :: 90 | +$ update-log ((mop time logged-update) gth) 91 | +$ update-logs (map resource update-log) 92 | :: 93 | +$ internal-graph 94 | $~ [%empty ~] 95 | $% [%graph p=graph] 96 | [%empty ~] 97 | == 98 | :: 99 | +$ network 100 | $: =graphs 101 | =tag-queries 102 | =update-logs 103 | archive=graphs 104 | validators=(set mark) 105 | == 106 | :: 107 | +$ update [p=time q=action] 108 | :: 109 | +$ logged-update [p=time q=logged-action] 110 | :: 111 | +$ logged-action 112 | $% [%add-graph =resource =graph mark=(unit mark) overwrite=?] 113 | [%add-nodes =resource nodes=(map index node)] 114 | [%remove-nodes =resource indices=(set index)] 115 | [%add-signatures =uid =signatures] 116 | [%remove-signatures =uid =signatures] 117 | == 118 | :: 119 | +$ action 120 | $% logged-action 121 | [%remove-graph =resource] 122 | :: 123 | [%add-tag =term =resource] 124 | [%remove-tag =term =resource] 125 | :: 126 | [%archive-graph =resource] 127 | [%unarchive-graph =resource] 128 | [%run-updates =resource =update-log] 129 | :: 130 | :: NOTE: cannot be sent as pokes 131 | :: 132 | [%keys =resources] 133 | [%tags tags=(set term)] 134 | [%tag-queries =tag-queries] 135 | == 136 | -- 137 | :: 138 | :: %graph-store types version 1 139 | :: 140 | ++ one 141 | =< [. post-one] 142 | =, post-one 143 | |% 144 | ++ orm ((on atom node) gth) 145 | ++ orm-log ((on time logged-update) gth) 146 | :: 147 | +$ graph ((mop atom node) gth) 148 | +$ marked-graph [p=graph q=(unit mark)] 149 | :: 150 | +$ node [=post children=internal-graph] 151 | +$ graphs (map resource marked-graph) 152 | :: 153 | +$ tag-queries (jug term resource) 154 | :: 155 | +$ update-log ((mop time logged-update) gth) 156 | +$ update-logs (map resource update-log) 157 | :: 158 | +$ internal-graph 159 | $~ [%empty ~] 160 | $% [%graph p=graph] 161 | [%empty ~] 162 | == 163 | :: 164 | +$ network 165 | $: =graphs 166 | =tag-queries 167 | =update-logs 168 | archive=graphs 169 | validators=(set mark) 170 | == 171 | :: 172 | +$ update [p=time q=action] 173 | :: 174 | +$ logged-update [p=time q=logged-action] 175 | :: 176 | +$ logged-action 177 | $% [%add-graph =resource =graph mark=(unit mark) overwrite=?] 178 | [%add-nodes =resource nodes=(map index node)] 179 | [%remove-nodes =resource indices=(set index)] 180 | [%add-signatures =uid =signatures] 181 | [%remove-signatures =uid =signatures] 182 | == 183 | :: 184 | +$ action 185 | $% logged-action 186 | [%remove-graph =resource] 187 | :: 188 | [%add-tag =term =resource] 189 | [%remove-tag =term =resource] 190 | :: 191 | [%archive-graph =resource] 192 | [%unarchive-graph =resource] 193 | [%run-updates =resource =update-log] 194 | :: 195 | :: NOTE: cannot be sent as pokes 196 | :: 197 | [%keys =resources] 198 | [%tags tags=(set term)] 199 | [%tag-queries =tag-queries] 200 | == 201 | -- 202 | :: 203 | :: %graph-store types version 0 204 | :: 205 | ++ zero 206 | =< [. post-zero] 207 | =, post-zero 208 | |% 209 | ++ orm ((ordered-map atom node) gth) 210 | ++ orm-log ((ordered-map time logged-update) gth) 211 | :: 212 | +$ graph ((mop atom node) gth) 213 | +$ marked-graph [p=graph q=(unit mark)] 214 | :: 215 | +$ node [=post children=internal-graph] 216 | +$ graphs (map resource marked-graph) 217 | :: 218 | +$ tag-queries (jug term resource) 219 | :: 220 | +$ update-log ((mop time logged-update) gth) 221 | +$ update-logs (map resource update-log) 222 | :: 223 | :: 224 | +$ internal-graph 225 | $~ [%empty ~] 226 | $% [%graph p=graph] 227 | [%empty ~] 228 | == 229 | :: 230 | +$ network 231 | $: =graphs 232 | =tag-queries 233 | =update-logs 234 | archive=graphs 235 | validators=(set mark) 236 | == 237 | :: 238 | +$ update 239 | $% [%0 p=time q=update-0] 240 | == 241 | :: 242 | +$ logged-update 243 | $% [%0 p=time q=logged-update-0] 244 | == 245 | :: 246 | +$ logged-update-0 247 | $% [%add-graph =resource =graph mark=(unit mark) overwrite=?] 248 | [%add-nodes =resource nodes=(map index node)] 249 | [%remove-nodes =resource indices=(set index)] 250 | [%add-signatures =uid =signatures] 251 | [%remove-signatures =uid =signatures] 252 | == 253 | :: 254 | +$ update-0 255 | $% logged-update-0 256 | [%remove-graph =resource] 257 | :: 258 | [%add-tag =term =resource] 259 | [%remove-tag =term =resource] 260 | :: 261 | [%archive-graph =resource] 262 | [%unarchive-graph =resource] 263 | [%run-updates =resource =update-log] 264 | :: 265 | :: NOTE: cannot be sent as pokes 266 | :: 267 | [%keys =resources] 268 | [%tags tags=(set term)] 269 | [%tag-queries =tag-queries] 270 | == 271 | -- 272 | -- 273 | 274 | -------------------------------------------------------------------------------- /src/sur/hark-store.hoon: -------------------------------------------------------------------------------- 1 | ^? 2 | :: 3 | :: %hark-store: Notification, unreads store 4 | :: 5 | :: Timeboxing & binning: 6 | :: 7 | :: Unread notifications accumulate in $unreads. They are grouped by 8 | :: their $bin. A notification may become read by either: 9 | :: a) being read by a %read-count or %read-each or %read-note 10 | :: b) being read by a %seen 11 | :: 12 | :: If a) then we insert the corresponding bin into $reads at the 13 | :: current timestamp 14 | :: If b) then we empty $unreads and move all bins to $reads at the 15 | :: current timestamp 16 | :: 17 | :: Unread tracking: 18 | :: Unread tracking has two 'modes' which may be used concurrently, 19 | :: if necessary. 20 | :: 21 | :: count: 22 | :: This stores the unreads as a simple atom, describing the number 23 | :: of unread items. May be increased with %unread-count and 24 | :: set to zero with %read-count. Ideal for high-frequency linear 25 | :: datastructures, e.g. chat 26 | :: each: 27 | :: This stores the unreads as a set of paths, describing the set of 28 | :: unread items. Unreads may be added to the set with %unread-each 29 | :: and removed with %read-each. Ideal for non-linear, low-frequency 30 | :: datastructures, e.g. blogs 31 | :: 32 | |% 33 | :: $place: A location, under which landscape stores stats 34 | :: 35 | :: .desk must match q.byk.bowl 36 | :: Examples: 37 | :: A chat: 38 | :: [%landscape /~dopzod/urbit-help] 39 | :: A note in a notebook: 40 | :: [%landscape /~darrux-landes/feature-requests/12374893234232] 41 | :: A group: 42 | :: [%hark-group-hook /~bitbet-bolbel/urbit-community] 43 | :: Comments on a link 44 | :: [%landscape /~dabben-larbet/urbit-in-the-news/17014118450499614194868/2] 45 | :: 46 | +$ place [=desk =path] 47 | :: 48 | :: $bin: Identifier for grouping notifications 49 | :: 50 | :: Examples 51 | :: A mention in a chat: 52 | :: [/mention %landscape /~dopzod/urbit-help] 53 | :: New messages in a chat 54 | :: [/message %landscape /~dopzod/urbit-help] 55 | :: A new comment in a notebook: 56 | :: [/comment %landscape /~darrux-landes/feature-requests/12374893234232/2] 57 | :: 58 | +$ bin [=path =place] 59 | :: 60 | :: $lid: Reference to a timebox 61 | :: 62 | +$ lid 63 | $% [%archive =time] 64 | [%seen ~] 65 | [%unseen ~] 66 | == 67 | :: $content: Notification content 68 | +$ content 69 | $% [%ship =ship] 70 | [%text =cord] 71 | == 72 | :: 73 | :: $body: A notification body 74 | :: 75 | +$ body 76 | $: title=(list content) 77 | content=(list content) 78 | =time 79 | binned=path 80 | link=path 81 | == 82 | :: 83 | +$ notification 84 | [date=@da =bin body=(list body)] 85 | :: $timebox: Group of notificatons 86 | +$ timebox 87 | (map bin notification) 88 | :: $archive: Archived notifications, ordered by time 89 | +$ archive 90 | ((mop @da timebox) gth) 91 | :: 92 | +$ action 93 | $% :: hook actions 94 | :: 95 | :: %add-note: add a notification 96 | [%add-note =bin =body] 97 | :: 98 | :: %del-place: Underlying resource disappeared, remove all 99 | :: associated notifications 100 | [%del-place =place] 101 | :: %unread-count: Change unread count by .count 102 | [%unread-count =place inc=? count=@ud] 103 | :: %unread-each: Add .path to list of unreads for .place 104 | [%unread-each =place =path] 105 | :: %saw-place: Update last-updated for .place to now.bowl 106 | [%saw-place =place time=(unit time)] 107 | :: store actions 108 | :: 109 | :: %archive: archive single notification 110 | :: if .time is ~, then archiving unread notification 111 | :: else, archiving read notification 112 | [%archive =lid =bin] 113 | :: %read-count: set unread count to zero 114 | [%read-count =place] 115 | :: %read-each: remove path from unreads for .place 116 | [%read-each =place =path] 117 | :: %read-note: Read note at .bin 118 | [%read-note =bin] 119 | :: %archive-all: Archive all notifications 120 | [%archive-all ~] 121 | :: %opened: User opened notifications, reset timeboxing logic. 122 | :: 123 | [%opened ~] 124 | :: 125 | :: XX: previously in hark-store, now deprecated 126 | :: the hooks responsible for creating notifications may offer pokes 127 | :: similar to this 128 | :: [%read-graph =resource] 129 | :: [%read-group =resource] 130 | :: [%remove-graph =resource] 131 | :: 132 | == 133 | :: .stats: Statistics for a .place 134 | :: 135 | +$ stats 136 | $: count=@ud 137 | each=(set path) 138 | last=@da 139 | timebox=(unit @da) 140 | == 141 | :: 142 | +$ update 143 | $% action 144 | :: %more: more updates 145 | [%archived =time =lid =notification] 146 | [%more more=(list update)] 147 | :: %note-read: note has been read with timestamp 148 | [%note-read =time =bin] 149 | [%added =notification] 150 | :: %timebox: description of timebox. 151 | :: 152 | [%timebox =lid =(list notification)] 153 | :: %place-stats: description of .stats for a .place 154 | [%place-stats =place =stats] 155 | :: %place-stats: stats for all .places 156 | [%all-stats places=(map place stats)] 157 | == 158 | -- 159 | 160 | -------------------------------------------------------------------------------- /src/sur/post.hoon: -------------------------------------------------------------------------------- 1 | /- *resource 2 | |% 3 | +$ index (list atom) 4 | +$ uid [=resource =index] 5 | :: 6 | :: +sham (half sha-256) hash of +validated-portion 7 | +$ hash @ux 8 | :: 9 | +$ signature [p=@ux q=ship r=life] 10 | +$ signatures (set signature) 11 | +$ post 12 | $: author=ship 13 | =index 14 | time-sent=time 15 | contents=(list content) 16 | hash=(unit hash) 17 | =signatures 18 | == 19 | :: 20 | +$ indexed-post [a=atom p=post] 21 | :: 22 | +$ validated-portion 23 | $: parent-hash=(unit hash) 24 | author=ship 25 | time-sent=time 26 | contents=(list content) 27 | == 28 | :: 29 | +$ reference 30 | $% [%graph group=resource =uid] 31 | [%group group=resource] 32 | [%app =ship =desk =path] 33 | == 34 | :: 35 | +$ content 36 | $% [%text text=cord] 37 | [%mention =ship] 38 | [%url url=cord] 39 | [%code expression=cord output=(list tank)] 40 | [%reference =reference] 41 | == 42 | :: 43 | ++ post-one 44 | |% 45 | :: 46 | +$ indexed-post [a=atom p=post] 47 | :: 48 | +$ post 49 | $: author=ship 50 | =index 51 | time-sent=time 52 | contents=(list content) 53 | hash=(unit hash) 54 | =signatures 55 | == 56 | :: 57 | +$ content 58 | $% [%text text=cord] 59 | [%mention =ship] 60 | [%url url=cord] 61 | [%code expression=cord output=(list tank)] 62 | [%reference =reference] 63 | == 64 | :: 65 | +$ reference 66 | $% [%graph group=resource =uid] 67 | [%group group=resource] 68 | == 69 | -- 70 | :: 71 | ++ post-zero 72 | |% 73 | :: 74 | +$ content 75 | $% [%text text=cord] 76 | [%mention =ship] 77 | [%url url=cord] 78 | [%code expression=cord output=(list tank)] 79 | [%reference =uid] 80 | == 81 | :: 82 | +$ post 83 | $: author=ship 84 | =index 85 | time-sent=time 86 | contents=(list content) 87 | hash=(unit hash) 88 | =signatures 89 | == 90 | -- 91 | -- 92 | 93 | -------------------------------------------------------------------------------- /src/sur/pull-hook.hoon: -------------------------------------------------------------------------------- 1 | /- *resource 2 | |% 3 | +$ action 4 | $% [%add =ship =resource] 5 | [%remove =resource] 6 | == 7 | :: 8 | +$ update 9 | $% [%tracking tracking=(map resource ship)] 10 | == 11 | -- 12 | -------------------------------------------------------------------------------- /src/sur/resource.hoon: -------------------------------------------------------------------------------- 1 | ^? 2 | |% 3 | +$ resource [=entity name=term] 4 | +$ resources (set resource) 5 | :: 6 | +$ entity 7 | $@ ship 8 | $% !! 9 | == 10 | -- 11 | 12 | -------------------------------------------------------------------------------- /src/sys.kelvin: -------------------------------------------------------------------------------- 1 | [%zuse 419] 2 | -------------------------------------------------------------------------------- /tutorial.md: -------------------------------------------------------------------------------- 1 | # Authenticating with Urbit ID 2 | 3 | Urbit provides a platform guaranteeing cryptographically secure identity and authentication. If a website can demonstrate that a user owns and operates the ship he claims to, then the website can use that fact of authentication as a direct login and additionally use the Urbit ship as a backend data store. How can a website verify Urbit user identity without having to check the blockchain? The same way many websites use email to identify a user: send a unique message to her inbox and check for a match. 4 | 5 | ![](https://i.pinimg.com/474x/61/b0/fb/61b0fb32a56660b5415bf546c72a8312--mercury-retrograde-greek-pottery.jpg) 6 | 7 | The `%authenticate-with-urbit-id` Gall agent plays the role of messenger and guardian, ensuring that any user who requests authorization for a particular ship does in fact control that ship. Similar to email-based or SMS-based token authentication, `%authenticate-with-urbit-id` utilizes direct messages to demonstrate that a secret token passed to the user is also received from the client ship. 8 | 9 | In this tutorial, we will demonstrate how a website can use `%authenticate-with-urbit-id` to authenticate a user, and we will show how the agent itself is constructed. (The latter assumes some knowledge of Hoon, the Urbit programming language.) 10 | 11 | 12 | ## Website Authentication 13 | 14 | A traditional webserver runs applications which interact with a client-side user's browser on the one hand and a database on the other. The website must authenticate the client browser session using a password, a token, or a cookie. Once this has taken place, future interactions are considered secure (modulo a variety of attacks) since the user is “known” to be who he or she claims to be. 15 | 16 | Urbit acts as a personal server, complementary to a personal client (or web browser). Besides its more exotic features, such as an event log, Urbit affords the owner of a “ship” (or unique instance) a persistent database and a cryptographic identity. Given these facts, a website can use Urbit to both uniquely identify a user and to store user-side data. `%authenticate-with-urbit-id` facilitates the first of these. 17 | 18 | Since Urbit's branding as a “personal server” can create some confusion in terms, we need to define the following elements of website authentication: 19 | 20 | 1. Website (classically the server-side application). 21 | 2. User (classically the client-side application, typically browser-based). 22 | 3. Website ship (which runs the `%authenticate-with-urbit-id` agent and authenticates for the website). 23 | 4. User ship (which is run by the user and needs to be authenticated). 24 | 25 | A website server that wishes to authenticate via `%authenticate-with-urbit-id` needs to run an Urbit ID of its own. Free transient IDs, called [comets](https://urbit.org/docs/glossary/comet), are available upon startup, but for most purposes a website should prefer to run a stable secure [planet](https://urbit.org/docs/glossary/planet) or [moon](https://urbit.org/docs/glossary/moon). We assume that you are able to run a planet or moon ship, but reach out if you require assistance in this step. This is the “website ship.” 26 | 27 | Once the website ship is running, the `%authenticate-with-urbit-id` agent should be installed and started. This exposes two public HTTP endpoints, `/~initiateAuth` and `/~checkAuth`. (That is, public to the server machine running the ship at `localhost` unless configured as a public-facing server.) 28 | 29 | - `/~initiateAuth` accepts a JSON including the unique identity or `@p` of the user ship to be authenticated and returns a JSON including a generated token. The agent also sets up a subscription to the direct message inbox and watches for the token to be received from the user ship. 30 | - `/~checkAuth` accepts a JSON including the `@p` of the user ship and returns a JSON indicating whether the token has been received from the user ship yet. If the user ship has authenticated successfully, the agent also clears the status as a security measure. 31 | 32 | Using `curl`, one can submit a JSON to the ship and receive a token in reply: 33 | 34 | ```sh 35 | curl --header "Content-Type: application/json" \ 36 | --request PUT \ 37 | --data '{"ship":"sampel-talled","json":"sampel-palnet"}' \ 38 | http://localhost:8080/~initiateAuth 39 | ``` 40 | 41 | With this token, the website should have the user send the token (and only the token text itself) to the website ship. (This may be done programmatically, as by Urbit Visor, or manually.) 42 | 43 | One can also query the status with `curl`: 44 | 45 | ```sh 46 | curl --header "Content-Type: application/json" \ 47 | --request PUT \ 48 | --data '{"ship":"sampel-talled","json":"sampel-palnet"}' \ 49 | http://localhost:8080/~checkAuth 50 | ``` 51 | 52 | Any website can use this method to match a client session to authenticated ownership of an Urbit ship. 53 | 54 | 55 | ## The `%authenticate-with-urbit-id` Agent 56 | 57 | The Urbit hosted operating system consists of the core system loop or event log, surrounded by system services each having a characteristic structure. System services provide network events, instrument the filesystem, build software, etc. Gall runs user _agents_, which act like system daemons and play the role of applications. Every Gall agent has a similar structure which enables the Urbit OS to consistently route events and data between agents. 58 | 59 | > If you are interested in understanding how Gall works to instrument Urbit's userspace via agents, we recommend [`~timluc-miptev`'s Gall Guide](https://github.com/timlucmiptev/gall-guide) for a deeper dive. 60 | 61 | `%authenticate-with-urbit-id` is a Gall agent. The source code is available on GitHub at [`dcSpark/authenticate-with-urbit-id`](https://github.com/dcSpark/authenticate-with-urbit-id) under the MIT License. This section of the tutorial walks through the structure and logic of `%authenticate-with-urbit-id`. 62 | 63 | Every Gall agent has ten arms, frequently with a “helper core” providing other necessary operations. 64 | 65 | ### Starting the Agent 66 | 67 | Upon startup, `%authenticate-with-urbit-id` registers the two HTTP endpoints and remains available to handle any requests sent via those paths. It also initializes the internal state, which consists of a token map (associative array) from ship name `@p` to token `tape` (string) and an authentication map from ship name `@p` to status as a `true`/`false` quantity. 68 | 69 | ```hoon 70 | ++ on-init 71 | ^- (quip card _this) 72 | ~& > '%authenticate-with-urbit-id initialized successfully' 73 | =. state [%0 *(map @p tape) *(map @p ?(%.y %.n))] 74 | :_ this 75 | :~ [%pass /bind %arvo %e %connect [~ /'~initiateAuth'] %authenticate-with-urbit-id] 76 | [%pass /bind %arvo %e %connect [~ /'~checkAuth'] %authenticate-with-urbit-id] 77 | == 78 | ``` 79 | 80 | The HTTP endpoint registration `%pass`es a message to the `%eyre` server vane connecting each endpoint to `%authenticate-with-urbit-id`. Any HTTP `PUT` or `GET` requests sent to those endpoints are redirected to this Gall agent. 81 | 82 | ### Handling Pokes 83 | 84 | A poke is a one-time command. Pokes are responsible to change agent state. A poke receives a `mark` (or data structure rule) and a `vase` (or value wrapped with its type). Here, a switch statement ([`?+` wutlus](https://urbit.org/docs/hoon/reference/rune/wut#-wutlus)) defaults to the `on-poke` failure response and otherwise handles only `%handle-http-request` values generated by Eyre from the website or `curl` JSON data. 85 | 86 | ```hoon 87 | ++ on-poke 88 | |= [=mark =vase] 89 | ^- (quip card _this) 90 | |^ 91 | =^ cards state 92 | ?+ mark (on-poke:default mark vase) 93 | %handle-http-request 94 | :: ... endpoint handlers here ... 95 | == 96 | [cards this] 97 | ``` 98 | 99 | 1. If the requested URL matches `/~initiateAuth`, then the state must be modified to produce the tokens, subscribe to the `%dm-inbox` (the direct message path), and only then return the token (from `++handle-auth-request`). 100 | 101 | ```hoon 102 | ?: =(url.request.inbound-request '/~initiateAuth') 103 | ~& > "%authenticate-with-urbit-id request hit /~initiateAuth" 104 | =/ st (get-source-target:main inbound-request) 105 | =/ source -.st 106 | =/ target +.st 107 | =^ cards state (produce-token:main source target) 108 | =^ cards state (subscribe-dms:main source target) 109 | :_ state 110 | ^- (list card) 111 | %+ weld cards 112 | %+ give-simple-payload:app:server id 113 | (handle-auth-request:main source target) 114 | ... code in case of other URL ... 115 | ``` 116 | 117 | 1. A `card` enables each vane of the Urbit OS to communicate with each other. It is basically a discrete event consisting of destination and necessary data. We see a few cards in the `%authenticate-with-urbit-id` code, such as: 118 | 119 | ```hoon 120 | [%give %fact ~[/tokens] [%atom !>(tokens.state)]] 121 | [%give %fact ~[/status] [%atom !>(status.state)]] 122 | ``` 123 | 124 | which update the state `tokens` and `status` maps for the requested ship; and 125 | 126 | ```hoon 127 | [%pass /graph-store %agent [our.bowl %graph-store] %watch /updates] 128 | ``` 129 | 130 | which subscribes to the `%dm-inbox` to check for incoming messages. Urbit prefers a data-reactive model in which subscriptions are activated and events are only processed when updates are received. 131 | 132 | These are ultimately returned by the standard `++on-XXX` arms of the agent to the Urbit OS, which uses them to update the event log and thereby the system state. 133 | 134 | 2. A series of functions at the bottom produces a list of `card`s to be effected on the `state`. For instance, `++handle-auth-request` is located in the helper core `main`: 135 | 136 | ```hoon 137 | ++ handle-auth-request 138 | |= [source=@p target=@p] 139 | ^- simple-payload:http 140 | =, enjs:format 141 | =/ auth (~(gut by status.state) target %.n) 142 | =/ token (~(got by tokens.state) target) 143 | %- json-response:gen:server 144 | %- pairs 145 | :~ 146 | [%source [%s (scot %p our.bowl)]] 147 | [%target [%s (scot %p target)]] 148 | [%token [%s (crip token)]] 149 | == 150 | ``` 151 | 152 | This gate wraps the target ship and the corresponding token in a JSON response which will be emitted after the poke concludes. 153 | 154 | The target ship was obtained from a JSON `PUT` input of the form 155 | 156 | ```json 157 | { 158 | "ship":"sampel-palnet", 159 | "json":"~sampel-talled", 160 | } 161 | ``` 162 | 163 | (We are intentionally being inconsistent with the tilde `~` here! We compensate for this below.) 164 | 165 | JSON parsing in Hoon requires two stages: one step which yields a tagged data structure and another which extracts the particular values of interest. These take place in the helper arm `++get-source-target`. 166 | 167 | ```hoon 168 | ++ get-source-target 169 | |= [req=inbound-request:eyre] 170 | ^- [@p @p] 171 | =/ payload (de-json:html `@t`+511:req) 172 | ?~ payload !! 173 | =/ payload-array u:+.payload 174 | =/ st u:+:(req-parser-ot payload-array) 175 | =/ source-t (trip -.st) 176 | =/ source 177 | ?: =('~' (snag 0 source-t)) u:+:`(unit @p)`(slaw %p (crip source-t)) 178 | u:+:`(unit @p)`(slaw %p (crip (weld "~" source-t))) 179 | =/ target-t (trip +.st) 180 | =/ target 181 | ?: =('~' (snag 0 target-t)) u:+:`(unit @p)`(slaw %p (crip target-t)) 182 | u:+:`(unit @p)`(slaw %p (crip (weld "~" target-t))) 183 | [source target] 184 | ``` 185 | 186 | 1. Here, the parser yields `payload` from `de-json:html`. This is the entire JSON string itself converted into the Hoon internal JSON representation, which must be processed and checked to make sure that the result isn't `~` (failure to parse). 187 | 188 | ```hoon 189 | =/ payload (de-json:html `@t`+511:req) 190 | ?~ payload !! 191 | =/ payload-array u:+.payload 192 | ``` 193 | 194 | The original JSON 195 | 196 | ```json 197 | { 198 | "ship":"sampel-palnet", 199 | "json":"~sampel-talled" 200 | } 201 | ``` 202 | 203 | 204 | is converted into Hoon representation 205 | 206 | ```hoon 207 | [ ~ 208 | [ %o 209 | p={ [ 210 | p='ship' 211 | q=[%s p='sampel-palnet'] 212 | ] 213 | [ 214 | p='json' 215 | q=[%s p='~sampel-talled'] 216 | ] 217 | } 218 | ] 219 | ] 220 | ``` 221 | 222 | with type tags on each value indicating the source JSON type. 223 | 224 | 2. That resulting data structure is then fed into a reparser, which must be constructed for the particular expected entries: 225 | 226 | ```hoon 227 | ++ req-parser-ot 228 | %- ot:dejs-soft:format 229 | :~ [%ship so:dejs-soft:format] 230 | [%json so:dejs-soft:format] 231 | == 232 | ``` 233 | 234 | ```hoon 235 | =/ st u:+:(req-parser-ot payload-array) 236 | ``` 237 | 238 | In this case, the only information required by the arm is the user ship `target` and the website ship `source`. These are extracted from the Airlock-specified JSON structure. 239 | 240 | 3. However, the Urbit API is not completely consistent about specifying tildes in front of ship names! So we check for both possibilities (present and absent) and parse accordingly, using `++slaw` to convert the result text into a `@p` identity. 241 | 242 | ```hoon 243 | =/ source-t (trip -.st) 244 | =/ source 245 | ?: =('~' (snag 0 source-t)) u:+:`(unit @p)`(slaw %p (crip source-t)) 246 | u:+:`(unit @p)`(slaw %p (crip (weld "~" source-t))) 247 | ``` 248 | 249 | The output of this gate passes through two other gates which ultimately yield a `list` of `card`s. 250 | 251 | 2. Back to the big picture: if the URL request does not match `/~initiateAuth` then it _must_ match `/~checkAuth` (else an assertion error is raised by [`?>` wutgar](https://urbit.org/docs/hoon/reference/rune/wut#-wutgar)). 252 | 253 | ```hoon 254 | ?> =(url.request.inbound-request '/~checkAuth') 255 | ~& > "%authenticate-with-urbit-id request hit /~checkAuth" 256 | =/ st (get-source-target:main inbound-request) 257 | =/ source -.st 258 | =/ target +.st 259 | =/ status (~(gut by status.state) target %.n) 260 | ~& > status 261 | =^ cards state (clear-auth:main source target) 262 | :_ state 263 | %+ weld cards 264 | %+ give-simple-payload:app:server id 265 | (handle-auth-check:main source target status) 266 | ``` 267 | 268 | 1. As before, we have two chains of events which need to resolve in order. We need to check the state and then clear it if it has been set. We also need to return the status of the check. First, the state must be checked and the reply JSON formed: 269 | 270 | ```hoon 271 | ++ handle-auth-check 272 | |= [source=@p target=@p status=?(%.y %.n)] 273 | ^- simple-payload:http 274 | =, enjs:format 275 | %- json-response:gen:server 276 | %- pairs 277 | :~ 278 | [%source [%s (scot %p our.bowl)]] 279 | [%target [%s (scot %p target)]] 280 | [%status [%s ?:(status 'true' 'false')]] 281 | == 282 | ``` 283 | 284 | 2. The token must then be cleared in sequence if it is active. (Notably, the relative isolation of Gall agent arms from each other here makes it necessary to extricate the information more than once from the active request.) 285 | 286 | ```hoon 287 | ++ clear-auth 288 | |= [source=@p target=@p] 289 | ^- (quip card _state) 290 | =/ auth-status (~(gut by status.state) target %.n) 291 | :: only clear the tokens if the status is %.y, else it's too soon 292 | =. tokens.state ?:(auth-status (~(del by tokens.state) target) tokens.state) 293 | =. status.state ?:(auth-status (~(del by status.state) target) status.state) 294 | :_ state 295 | :~ [%give %fact ~[/tokens] [%atom !>(tokens.state)]] 296 | [%give %fact ~[/status] [%atom !>(status.state)]] 297 | == 298 | ``` 299 | 300 | `%authenticate-with-urbit-id` is a straightforward agent which demonstrates internal state updates, API endpoint exposure, `graph-store` subscriptions, and card structure and order. 301 | 302 | Since every DM is examined for content, it is recommended that this agent run on a designated ship (such as a moon), rather than on a ship used for social purposes. 303 | 304 | > At this point, it is worth considering how one would construct an agent that can receive more than one fact from the input. 305 | > 306 | > - One can wrap the JSON in the `json` entry of the request JSON. This may require escaping internal JSON elements and gets messy to construct. 307 | > 308 | > ```json 309 | > '{"ship":"sampel-talled","json":"sampel-palnet"}' \ 310 | > ``` 311 | > 312 | > - One can pass in more arguments to the request JSON. 313 | > 314 | > ```json 315 | > '{"ship":"sampel-talled","json":"sampel-palnet","foo":"bar"}' 316 | > ``` 317 | > 318 | > In either case, additional reparsing will need to be written for each step. 319 | --------------------------------------------------------------------------------