From d38cb1f9dbe16dbf1a5bcbc184843ac8bff6b020 Mon Sep 17 00:00:00 2001 From: Manuel Date: Thu, 12 Jan 2023 19:34:28 +0100 Subject: [PATCH] Handle CORS, Additional error cases, Simplified database --- config.example.toml | 3 +++ src/config.v | 22 ++++++++++++++--- src/database.v | 22 ++++++++--------- src/main.v | 13 +++++----- src/web.v | 58 +++++++++++++++++++++++++++++++++++++++++---- 5 files changed, 92 insertions(+), 26 deletions(-) diff --git a/config.example.toml b/config.example.toml index 95fe53d..3b881e1 100644 --- a/config.example.toml +++ b/config.example.toml @@ -4,3 +4,6 @@ token = "asd" redirect = false redirect_url = "" db_path = "./db/app.db" +origins = [ + "*" +] diff --git a/src/config.v b/src/config.v index 6759a28..82f5f26 100644 --- a/src/config.v +++ b/src/config.v @@ -1,9 +1,10 @@ module main import toml +import os const ( - path = './config.toml' + path = config_path() ) struct Config { @@ -13,14 +14,20 @@ struct Config { redirect bool redirect_url string db_path string + origins []string } fn (config Config) copy() Config { return config } -fn load_config() Config { - config := toml.parse_file(path) or { panic(err) } +fn load_config() !Config { + if !os.is_file(path) { + return error('Config file does not exist or is not a file') + } + config := toml.parse_file(path) or { + return error('Error while parsing config file:\n' + err.msg()) + } return Config{ host: config.value('host').string() @@ -29,5 +36,14 @@ fn load_config() Config { redirect: config.value('redirect').bool() redirect_url: config.value('redirect_url').string() db_path: config.value('db_path').string() + origins: config.value('origins').array().as_strings() } } + +fn config_path() string { + path_env := $env('CONFIG_PATH') + if path_env.trim_space().len != 0 { + return path_env + } + return './config.toml' +} diff --git a/src/database.v b/src/database.v index 9f56565..b61645a 100644 --- a/src/database.v +++ b/src/database.v @@ -1,20 +1,18 @@ module main +[table: 'Score'] struct ScoreRes { pub mut: - id int [primary; sql: serial] - player string - score int - time i64 + id i64 [primary; sql: serial] + player string [nonull] + score int [nonull] + time i64 [nonull] } -fn (mut app App) create_tables() int { - return app.db.exec_none('CREATE TABLE IF NOT EXISTS ScoreRes ( - id INTEGER PRIMARY KEY, - player TEXT NOT NULL, - score INTEGER NOT NULL, - time SQLITE_INT64_TYPE NOT NULL - )') +fn (mut app App) create_tables() { + sql app.db { + create table ScoreRes + } } fn (mut app App) insert_score(score ScoreRes) ScoreRes { @@ -29,7 +27,7 @@ fn (mut app App) insert_score(score ScoreRes) ScoreRes { fn (mut app App) get_scores() []ScoreRes { return sql app.db { - select from ScoreRes order by score desc + select from ScoreRes order by score desc limit 1000 } } diff --git a/src/main.v b/src/main.v index bca46c5..1787694 100644 --- a/src/main.v +++ b/src/main.v @@ -18,7 +18,7 @@ fn main() { mut app_config := Config{} lock app.config { - app.config = load_config() + app.config = load_config() or { panic('Could not load config file: ' + err.msg()) } app_config = app.config.copy() } @@ -33,12 +33,13 @@ fn main() { panic(err) } - sql_code := app.create_tables() + app.create_tables() - if sqlite.is_error(sql_code) { - println('Could not create tables!') - panic('Error code ' + sql_code.str()) - } + // orm does not yet return Results + // if sqlite.is_error(sql_code) { + // println('Could not create tables!') + // panic('Error code ' + sql_code.str()) + // } mut host := '::' if app_config.host != '' { diff --git a/src/web.v b/src/web.v index da9289b..fece7a5 100644 --- a/src/web.v +++ b/src/web.v @@ -9,21 +9,24 @@ struct Status { message string } +[get; head] pub fn (mut app App) index() vweb.Result { rlock app.config { if app.config.redirect { - app.redirect(app.config.redirect_url) + return app.redirect(app.config.redirect_url) } } - return app.text('Hello :)') + return app.not_found() } -['/api/v1/score/list'] +['/api/v1/score/list'; get; head] pub fn (mut app App) score_list() vweb.Result { + app.add_cors_headers() + if !app.auth() { app.set_status(401, '') - return app.json(Status{401, 'OAuth token is missing'}) + return app.json(Status{401, 'Access token is wrong or missing'}) } scores := app.get_scores() @@ -33,9 +36,11 @@ pub fn (mut app App) score_list() vweb.Result { ['/api/v1/score/submit'; post] pub fn (mut app App) score_submit() vweb.Result { + app.add_cors_headers() + if !app.auth() { app.set_status(401, '') - return app.json(Status{401, 'OAuth token is missing'}) + return app.json(Status{401, 'Access token is wrong or missing'}) } body := json.decode(Score, app.req.data) or { @@ -43,6 +48,18 @@ pub fn (mut app App) score_submit() vweb.Result { return app.json(Status{400, 'Bad JSON object'}) } + if body.player.trim_space() == '' { + app.set_status(400, '') + return app.json(Status{400, 'Name cannot be empty'}) + } + + if body.score <= 0 { + app.set_status(400, '') + return app.json(Status{400, 'Illegal score'}) + } + + // TODO: Bad word filter + score := app.insert_score(ScoreRes{ player: body.player score: body.score @@ -52,6 +69,37 @@ pub fn (mut app App) score_submit() vweb.Result { return app.json(score) } +pub fn (mut app App) add_cors_headers() { + origin := app.get_header('origin') + mut origins := []string{} + rlock app.config { + origins = app.config.origins.clone() + } + default_origin := origins[0] or { '*' } + allowed_origin := if origins.any(it == origin) { origin } else { default_origin } + + app.add_header('Access-Control-Allow-Origin', allowed_origin) + app.add_header('Access-Control-Allow-Methods', 'OPTIONS, HEAD, GET, POST') + app.add_header('Access-Control-Allow-Headers', 'Authorization, Content-Type') + app.add_header('Access-Control-Max-Age', '86400') +} + +pub fn (mut app App) handle_cors() vweb.Result { + app.set_status(204, '') + app.add_cors_headers() + return app.ok('') +} + +['/api/v1/score/list'; options] +pub fn (mut app App) handle_score_list_cors() vweb.Result { + return app.handle_cors() +} + +['/api/v1/score/submit'; options] +pub fn (mut app App) handle_score_submit_cors() vweb.Result { + return app.handle_cors() +} + fn (mut app App) auth() bool { auth_header := app.get_header('Authorization') token := auth_header.after('Bearer ')