Get Soapbox frontend attached, fix several bugs in the login sequence

master
teknomunk 1 year ago
parent 6186b427b3
commit eeb60ab71d

1
.gitignore vendored

@ -6,3 +6,4 @@ src.bin
debug
release
data/
assets/soapbox/

@ -1,50 +1,78 @@
#include "client_apps.h"
#include "form.h"
#include "json/json.h"
#include "json/layout.h"
#include "http_server/http_request.h"
#include "model/client_app.h"
#include <stdlib.h>
#include <string.h>
#include <stddef.h>
bool handle_mastodon_api_apps( struct http_request* req )
{
FILE* data = http_request_get_request_data( req );
struct form_parser* fp = form_pull_parser_new( data );
if( !fp ) { return false; }
char* client_name = NULL;
char* redirect_uris = NULL;
char* key;
while( key = form_pull_parser_read_key( fp ) ) {
if( 0 == strcmp( "client_name", key ) ) {
client_name = strdup( form_pull_parser_read_value(fp) );
} else if( 0 == strcmp( "redirect_uris", key ) ) {
redirect_uris = strdup( form_pull_parser_read_value(fp) );
} else {
printf( "key: %s\n", key );
printf( "value: %s\n", form_pull_parser_read_value(fp) );
bool result = false;
FILE* post_data = http_request_get_request_data( req );
struct client_app_data
{
char* client_name;
char* redirect_uris;
} data;
memset(&data,0,sizeof(data));
const char* content_type = http_request_get_header( req, "Content-Type" );
if( 0 == strcasecmp(content_type,"application/json") ) {
static struct json_object_field layout[] = {
{ "redirect_uris", offsetof( struct client_app_data, redirect_uris ), true, &json_field_string },
{ "client_name", offsetof( struct client_app_data, client_name ), true, &json_field_string },
{ NULL },
};
if( !json_read_object_layout_from_FILE( post_data, layout, &data ) ) {
goto failed;
}
} else {
struct form_parser* fp = form_pull_parser_new( post_data );
if( !fp ) { return false; }
char* key;
while( key = form_pull_parser_read_key( fp ) ) {
if( 0 == strcmp( "client_name", key ) ) {
data.client_name = strdup( form_pull_parser_read_value(fp) );
} else if( 0 == strcmp( "redirect_uris", key ) ) {
data.redirect_uris = strdup( form_pull_parser_read_value(fp) );
} else {
printf( "key: %s\n", key );
printf( "value: %s\n", form_pull_parser_read_value(fp) );
}
}
form_pull_parser_release(fp);
}
form_pull_parser_release(fp);
if( !data.client_name ) { goto failed; }
if( !data.redirect_uris ) { goto failed; }
struct client_app* app = client_app_new( client_name );
struct client_app* app = client_app_new( data.client_name );
app->redirect_uri = strdup(data.redirect_uris);
client_app_save(app);
http_request_send_headers( req, 200, "application/json", true );
FILE* f = http_request_get_response_body( req );
#include "controller/mastodon_api/apps.json.inc"
client_app_free(app);
#define RENDER
#include "controller/mastodon_api/apps.json.inc"
#undef RENDER
free(client_name);
free(redirect_uris);
result = true;
cleanup:
free(data.client_name);
free(data.redirect_uris);
return true;
return result;
failed:
result = false;
goto cleanup;
}
bool check_bearer_token( struct http_request* req )

@ -7,27 +7,108 @@
#include "controller/owner.h"
#include "controller/inbox.h"
#include <string.h>
#include <stdlib.h>
const char* mime_type_for_filename( const char* filename )
{
// determine MIME type
int len = strlen(filename);
struct {
const char* extension;
const char* mime_type;
} extensions[] = {
{ ".html", "text/html" },
{ ".js", "application/javascript" },
{ ".png", "image/png" },
{ ".jpg", "image/jpg" },
{ ".css", "text/css" },
{ ".json", "application/json" },
{ ".svg", "image/svg+xml" },
{ ".woff", "font/woff" },
{ ".woff2", "font/woff2" },
{ ".mp3", "audio/mp3" },
{ ".ogg", "audio/ogg" },
{ ".oga", "audio/ogg" },
{ NULL, NULL },
};
for( int i = 0; extensions[i].extension; ++i ) {
int s = strlen( extensions[i].extension );
if( 0 == strcmp( extensions[i].extension, &filename[len-s] ) ) {
return extensions[i].mime_type;
}
}
return NULL;
}
static bool send_asset( struct http_request* req, const char* fs_path )
{
const char* mime_type = mime_type_for_filename(fs_path);
if( !mime_type ) {
printf( "Unable to determine MIME type for %s\n", fs_path );
return false;
}
return http_request_send_file( req, fs_path, mime_type );
}
bool route_asset( struct http_request* req )
{
static struct allowed_t {
const char* route_path;
bool result = false;
char* full_path = NULL;
// Don't allow ".." in the path
full_path = strdup(http_request_get_full_path( req ));
if( strstr( full_path, ".." ) ) { goto failed; }
// Cut off the query string
char* res;
strtok_r( full_path, "?", &res );
// Handle index
if( full_path[ strlen(full_path)-1 ] == '/' ) {
free(full_path);
return send_asset( req, "assets/soapbox/index.html" );
}
struct {
const char* fs_path;
const char* mime_type;
} allowed[] = {
{ "style.css", "src/assets/style.css", "text/css" },
{ "jquery.js", "src/assets/jquery.js", "application/javascript" },
{ "jquery.plugins.js", "src/assets/jquery.plugins.js", "application/javascript" },
{ "background_noise.png","src/assets/background_noise.png", "image/png" },
{ "feed-icon-28x28.png", "src/assets/feed-icon-28x28.png", "image/png" },
const char* where;
} fs_mounts[] = {
{ "assets/soapbox", "/" },
{ NULL, NULL }
};
for( int i = 0; i < sizeof(allowed)/sizeof(struct allowed_t); ++i ) {
if( http_request_route_term( req, allowed[i].route_path ) ) {
return http_request_send_file( req, allowed[i].fs_path, allowed[i].mime_type );
char filename[512];
for( int i = 0; fs_mounts[i].fs_path; ++i ) {
int ws = strlen( fs_mounts[i].where );
if( 0 == strncmp( fs_mounts[i].where, full_path, ws ) ) {
snprintf( filename, sizeof(filename), "%s/%s", fs_mounts[i].fs_path, &full_path[ws] );
FILE* f = fopen(filename,"r");
if( f ) {
fclose(f);
if( send_asset( req, filename ) ) {
goto success;
} else {
goto failed;
}
}
}
}
return false;
success:
result = true;
cleanup:
free(full_path);
return result;
failed:
result = false;
goto cleanup;
}
bool route_request( struct http_request* req )
@ -49,8 +130,7 @@ bool route_request( struct http_request* req )
}
} else if( http_request_route( req, "/nodeinfo" ) ) {
return route_nodeinfo(req);
} else if( http_request_route( req, "/assets/" ) ) {
printf( "handling asset\n" );
} else {
return route_asset(req);
}

@ -170,6 +170,9 @@ bool route_mastodon_api( struct http_request* req )
} else if( http_request_route( req, "accounts/" ) ) {
if( http_request_route( req, "verify_credentials" ) ) {
http_request_send_headers( req, 200, "application/json", true );
FILE* f = http_request_get_response_body( req );
#include "src/view/api/verify_credentials.json.inc"
return true;
} else if( http_request_route( req, "relationships" ) ) {
http_request_send_headers( req, 200, "application/json", true );

@ -1,12 +1,18 @@
#include "oauth.h"
#include "http_server/http_request.h"
#include "http_server/escape.h"
#include "model/client_app.h"
#include "model/account.h"
#include "model/owner.h"
#include "form.h"
#include "json/json.h"
#include "json/layout.h"
#include <stdlib.h>
#include <string.h>
#include <stddef.h>
static bool handle_oauth_authorize( struct http_request* req )
{
@ -161,56 +167,99 @@ access_denied:
static bool handle_oauth_get_token( struct http_request* req )
{
bool result = true;
char* auth_code = NULL;
struct account* owner_account = NULL;
struct owner* owner = NULL;
struct client_app* app = NULL;
char* redirect_uri = NULL;
char* client_secret = NULL;
struct data_t
{
char* auth_code;
char* redirect_uri;
char* client_secret;
char* client_id;
char* grant_type;
char* username;
char* password;
} data;
memset(&data,0,sizeof(data));
FILE* post_data = http_request_get_request_data(req);
const char* content_type = http_request_get_header( req, "Content-Type" );
if( 0 == strcasecmp(content_type,"application/json") ) {
static struct json_object_field layout[] = {
{ "code", offsetof( struct data_t, auth_code ), false, &json_field_string },
{ "redirect_uri", offsetof( struct data_t, redirect_uri ), true, &json_field_string },
{ "grant_type", offsetof( struct data_t, grant_type ), false, &json_field_string },
{ "client_secret", offsetof( struct data_t, client_secret ), false, &json_field_string },
{ "client_id", offsetof( struct data_t, client_id ), false, &json_field_string },
{ "username", offsetof( struct data_t, username ), false, &json_field_string },
{ "password", offsetof( struct data_t, password ), false, &json_field_string },
{ NULL },
};
if( !json_read_object_layout_from_FILE( post_data, layout, &data ) ) {
printf( "A" );
goto invalid_request;
}
FILE* body = http_request_get_request_data(req);
} else {
struct form_parser* fp = form_pull_parser_new(body);
if( !fp ) {
goto invalid_request;
}
struct form_parser* fp = form_pull_parser_new(post_data);
if( !fp ) {
goto invalid_request;
}
char* key;
while( key = form_pull_parser_read_key( fp ) ) {
if( 0 == strcmp(key,"grant_type") ) {
if( 0 != strcmp(form_pull_parser_read_value(fp),"authorization_code") ) {
goto invalid_request;
}
char* key;
// TODO: extend form parser to use a data layout
while( key = form_pull_parser_read_key( fp ) ) {
if( 0 == strcmp(key,"grant_type") ) {
data.grant_type = strdup(form_pull_parser_read_value(fp));
} else if( 0 == strcmp(key,"code") ) {
auth_code = strdup(form_pull_parser_read_value(fp));
} else if( 0 == strcmp(key,"code") ) {
data.auth_code = strdup(form_pull_parser_read_value(fp));
} else if( 0 == strcmp(key,"client_id") ) {
const char* client_id = strdup(form_pull_parser_read_value(fp));
app = client_app_from_id( client_id );
if( !app ) { goto invalid_request; }
} else if( 0 == strcmp(key,"client_id") ) {
data.client_id = strdup(form_pull_parser_read_value(fp));
} else if( 0 == strcmp(key,"client_secret") ) {
client_secret = strdup(form_pull_parser_read_value(fp));
} else if( 0 == strcmp(key,"client_secret") ) {
data.client_secret = strdup(form_pull_parser_read_value(fp));
} else if( 0 == strcmp(key,"redirect_uri") ) {
redirect_uri = strdup(form_pull_parser_read_value(fp));
} else if( 0 == strcmp(key,"redirect_uri") ) {
data.redirect_uri = strdup(form_pull_parser_read_value(fp));
} else {
printf( "%s=", key );
printf( "%s\n", form_pull_parser_read_value(fp) );
} else {
printf( "%s=", key );
printf( "%s\n", form_pull_parser_read_value(fp) );
}
}
form_pull_parser_release(fp);
}
form_pull_parser_release(fp);
if( !auth_code ) { goto invalid_request; }
if( !app ) { goto invalid_request; }
printf( "2" );
if( !data.redirect_uri ) { goto invalid_request; }
printf( "3" );
if( !data.client_id ) { goto invalid_request; }
printf( "4" );
if( redirect_uri ) {
if( 0 != strcmp( app->redirect_uri, redirect_uri ) ) {
goto invalid_request;
}
app = client_app_from_id( data.client_id );
if( !app ) { goto invalid_request; }
printf( "5" );
if( !app->redirect_uri ) { goto invalid_request; }
if( 0 != strcmp( app->redirect_uri, data.redirect_uri ) ) { goto invalid_request; }
printf( "6" );
if( 0 != strcmp( app->client.secret, data.client_secret ) ) { goto access_denied; }
if( 0 == strcmp("authorization_code",data.grant_type) ) {
if( !data.auth_code ) { goto invalid_request; }
if( 0 != strcmp( app->auth_code, data.auth_code ) ) { goto access_denied; }
} else if( 0 == strcmp("password", data.grant_type ) ) {
owner_account = account_from_id(0);
owner = owner_new();
if( 0 != strcmp( owner_account->handle, data.username ) ) { goto access_denied; }
if( !owner_check_password( owner, data.password ) ) { goto access_denied; }
} else {
printf( "7" );
goto invalid_request;
}
if( 0 != strcmp( app->auth_code, auth_code ) ) { goto access_denied; }
if( 0 != strcmp( app->client.secret, client_secret ) ) { goto access_denied; }
// TODO: check code has not expired
@ -227,19 +276,25 @@ static bool handle_oauth_get_token( struct http_request* req )
#undef RENDER
cleanup:
if( app ) {
client_app_free(app);
}
free(auth_code);
free(client_secret);
free(redirect_uri);
client_app_free(app);
free(data.auth_code);
free(data.redirect_uri);
free(data.client_secret);
free(data.client_id);
free(data.grant_type);
free(data.password);
free(data.username);
owner_free(owner);
account_free(owner_account);
return result;
access_denied:
printf( "Access denied\n" );
result = false;
goto cleanup;
invalid_request:
printf( "Invalid request\n" );
result = false;
goto cleanup;

@ -1 +1 @@
Subproject commit 2aa3a9ede60164ee3e28ffa1a8ed8cdd04ff6ee6
Subproject commit 14fd8b64c8577d30c067be669b7b4a58ee309100

@ -1 +1 @@
Subproject commit cd0c17e6b9d1c524096c71e8b30d4a2ce1b9a262
Subproject commit 89205ae198eb90c631eb3d00b836f5d89b3c7f81

@ -23,11 +23,11 @@ static char* safe( char* str )
}
static struct json_object_field client_app_layout[] = {
{ "secret", offsetof( struct client_app, client.secret ), true, &json_field_string },
{ "name", offsetof( struct client_app, client.name ), false, &json_field_string },
{ "auth_code", offsetof( struct client_app, auth_code ), false, &json_field_string },
{ "access_token", offsetof( struct client_app, access_token ), false, &json_field_string },
{ "redirect_uri", offsetof( struct client_app, redirect_uri ), false, &json_field_string },
{ "secret", offsetof( struct client_app, client.secret ), true, &json_field_string },
{ NULL },
};

@ -29,6 +29,7 @@ struct owner* owner_new()
}
void owner_free( struct owner* o )
{
if( !o ) { return; }
free(o->password_salt);
free(o->password_hash);
free(o);

@ -1 +1 @@
Subproject commit cd00c4d660db3d3b3ce823f847cb148d8ec45fd9
Subproject commit 7016057c53d9dc123a474e63ddc171e4ae4855ea

@ -0,0 +1,5 @@
{
"name": "Soapbox",
"website": "https://soapbox.pub/",
"vapid_key": "%d{ rand() }"
}
Loading…
Cancel
Save