I was thinking through how to do the link shortening thing and here's the best solution I think: use AES-128 CBC to encrypt the ship and content ID and make a URL with it, then decode such requests and test whether the given ship has permission for the given resource. If it does, 301 redirect to the actual resource and if it doesn't, 403 forbidden.
This way you can give every subscribed ship its own unique urls for files you've shared, and you can revoke or change them individually. You also don't need to actually store these unique urls because you can just construct them when you send out updates to each ship, cos AES is jetted and very fast.
The approach for subscriptions would be to have a unique subscription path for
each subscribed ship like /updates/[ship] or whatever so you can give the
correct unique urls to the correct ship
Here's a basic set of data structures the app could use:
|%
+$ id @
+$ file
$: title=@t
note=(unit @t)
url=@t
ext=(unit @t)
==
+$ perms
$: white=(set ship)
black=(set ship)
==
+$ user
$: rev=@
files=(set id)
==
+$ decoded
$: =ship
rev=@
=id
==
+$ secret [k=@ iv=@]
+$ host (unit @t)
+$ users (map ship user)
+$ public (set id)
+$ local (map id [=file =perms])
+$ remote (map ship (map id file))And state can look something like this:
+$ state-0 [%0 =secret =host =users =public =local =remote]The content $id can be a random 64-bit atom. The rev field for the $user
is also a 64-bit number and is used to salt the encrypted urls, so you can
change them for that user down the line if you want to by changing that number.
The $secret structure in state is a 128-bit key and initialisation vector for
the AES-128 CBC algo, to construct and decrypt the unique urls.
The unique url is made by concatenating [128-bit ship address][64-bit user revision number][64-bit content ID], encrypting it with ~(en cbca:aes:crypto k.secret iv.secret), and then encoding it in a cord in base32.
Here's how the encoding function might work:
++ encode
|= [=host =ship =user =id =file =secret]
^- (unit @t)
?~ host ~
=/ raw=@ (can 3 ~[8^id 8^rev.user 16^ship])
=/ hash=tape
%- (v-co:co 32)
(~(en cbca:aes:crypto k.secret iv.secret) raw)
=/ query=tape
?~ ext.file ""
"?ext={(trip u.ext.file)}"
`(crip "http://{(trip u.host)}/share/{hash}{query}")
::
++ sample-encode-args
:* host=[~ 'example.com']
ship=~tinnus-napbus
user=[0 ~]
id=9.595.219.608.286.410.566
file=['foo' ~ 'xxx' ~ 'txt']
secret=[0w3J.V~dU2.Um-6~.0TghF.MJLmv 0w1n.Yz4ZH.CBhuM.hdsft.LFkM~]
==In the dojo:
> (encode:test sample-encode-args:test)
[~ 'http://example.com/share/18gboma04mas2tg23tl0vvs6viesn598e6ccqo8ib5n6i0ol8bdh?ext=txt']
And here's the decoding that'll happen when it receives a GET request for that url:
++ sample-decode-args
:* url='http://example.com/share/18gboma04mas2tg23tl0vvs6viesn598e6ccqo8ib5n6i0ol8bdh?ext=.txt'
secret=[0w3J.V~dU2.Um-6~.0TghF.MJLmv 0w1n.Yz4ZH.CBhuM.hdsft.LFkM~]
==
::
++ decode
|= [url=@t =secret]
^- (unit decoded)
=/ purl=(unit purl:eyre) (de-purl:html url)
?~ purl ~
=/ =pork:eyre q.u.purl
?. ?=([@ @ ~] q.pork) ~
=/ hash=@ (rash i.t.q.pork vum:ag)
=/ raw=@ (~(de cbca:aes:crypto k.secret iv.secret) hash)
:^ ~
(cut 3 [16 16] raw)
(cut 3 [8 8] raw)
(end 6 raw)
--in the dojo:
> (decode:test sample-decode-args:test)
[~ [ship=~tinnus-napbus rev=0 id=9.595.219.608.286.410.566]]
Upon receiving the get request and successfully decoding, it can check that the given user has permission to access the file with the given content ID, and if it does, it can return a 301 redirect to the actual url.
Could you give me an idea of how you see the different structures in state working together (if you have one)?
I started implementing some coordination between them, but I wondered if you had something in mind when you wrote up the structures. E.g. 'local' has the perms[whitelist blacklist] for each file/id, but there's also a set of file/id for each user. An id could be added to the user when that ship was added to the whitelist of the id, for instance. There's also 'public'; where the idea could be that public files are shared without the encoding.
There's so many possibilities, it would be helpful to get your input before I get too deep into the details.