commit 5386a3fd0da9573f7165dbbe60ab9db1f547f96a Author: Rudis Muiznieks Date: Thu Jul 16 20:48:53 2020 -0500 started reference implementation diff --git a/README.md b/README.md new file mode 100644 index 0000000..881fa9b --- /dev/null +++ b/README.md @@ -0,0 +1,30 @@ +# dotplan.online + +## The un-social network. + +- User-provided content tied to an email address. +- Text only, limited to 4kb. +- No retweets, shares, @s, likes, or boosting of any kind. +- Authenticity verified by public PGP keys. +- Accessed via public APIs. +- Self-hostable, discovery via SRV records. + +## API + +- `POST /users` to register an email address. + - Request data: `{"email":"whatever","password":"whatever"}` + - Will require validation. Email with token link will be sent. +- `GET /users/{email}?token={token}` to validate an email. +- Token-based authentication. + - `GET /users/{email}/token` with basic auth validation to get a token. + - `DELETE /users/{email}/token` to manually invalidate any token. +- `PUT /plan/{email}` to update a .plan + - Request data: `{"plan":"whatever","signature":"whatever"}` + - Signature is optional PGP digital signature for the plan. +- `GET /plan/{email}` to retrieve a .plan without verification + - Plain text by default, or based on `accept` header, or force: + - `?format=html` will html-escape special characters. + - `?format=json` response data: `{"plan":"whatever","signature":"whatever"}` +- `POST /verify/{email}` to retrieve and verify the signature of a .plan + - Request data: `{"pgpkey":"public key"}` + - Response data: `{"plan":"whatever","verified":(true|false)}` diff --git a/server.pl b/server.pl new file mode 100644 index 0000000..a8f459d --- /dev/null +++ b/server.pl @@ -0,0 +1,142 @@ +#!/usr/bin/env perl + +use utf8; +use strict; +use warnings; +use open qw(:std :utf8); + +###################### +# Server Configuration +###################### + +my $server_port = 4227; + +######################################### +# dotplan.online Reference Implementation +######################################### + +{ + package DotplanApi; + use base qw(HTTP::Server::Simple::CGI); + use POSIX qw(strftime); + use JSON qw(encode_json decode_json); + use HTML::Entities qw(encode_entities); + + ############### + # Common Errors + ############### + + my $not_found = encode_json({error => 'Not found.'}); + my $not_implemented = encode_json({error => 'Not implemented yet.'}); + + ################# + # Request Routing + ################# + + sub handle_request { + my ($self, $cgi) = @_; + my $path = $cgi->path_info(); + my $method = $cgi->request_method(); + + if ($method eq 'GET') { + if ($path =~ /^\/plan\/(.*)$/) { + get_plan($1, $cgi); + } elsif ($path =~ /^\/users\/([^\/]*)$/) { + verify_email($1, $cgi); + } elsif ($path =~ /^\/users\/([^\/]*)\/token$/) { + get_token($1, $cgi); + } else { + print_response(404, $not_found); + } + } elsif ($method eq 'POST') { + if ($path =~ /^\/users\/?$/) { + create_user($cgi); + } else { + print_response(404, $not_found); + } + } elsif ($method eq 'PUT') { + if ($path =~ /^\/plan\/(.*)$/) { + update_plan($cgi); + } else { + print_response(404, $not_found); + } + } elsif ($method eq 'DELETE') { + if ($path =~ /^\/users\/([^\/]*)\/token$/) { + delete_token($1, $cgi); + } else { + print_response(404, $not_found); + } + } else { + print_response(405, encode_json({error => 'Not supported.'})); + } + } + + ################## + # Response Handler + ################## + + sub print_response { + my ($code, $body, $type) = @_; + if (!defined $type) { + $type = 'application/json'; + } + my $length = length($body); + my $date = strftime("%a, %d %b %Y %H:%M:%S %z", localtime(time())); + print < 'my plan & goals in life '}; + + # found plan, render response + + my $accept = $cgi->http('Accept'); + my $format = lc($cgi->param('format') || $cgi->http('Accept')); + my $body; + if ($format eq 'json' || $format eq 'application/json') { + $format = 'application/json'; + $body = encode_json($plan); + } elsif ($format eq 'html' || $format eq 'text/html') { + $format = 'text/html'; + $body = encode_entities($plan->{'plan'}); + } else { + $format = 'text/plain'; + $body = $plan->{'plan'}; + } + + print_response(200, $body, $format); + } + + ##### GET /users/{email}?token={token} + sub verify_email { print_response(501, $not_implemented); } + + ##### GET /users/{email}/token + sub get_token { print_response(501, $not_implemented); } + + ##### POST /users + sub create_user { print_response(501, $not_implemented); } + + ##### PUT /plan/{email} + sub update_plan { print_response(501, $not_implemented); } + + ##### DELETE /users/{email}/token + sub delete_token { print_response(501, $not_implemented); } +} + +# start server in background +my $pid = DotplanApi->new($server_port)->background(); +print "Use 'kill $pid' to stop server.\n";