module main // import profanity import vweb import json import time import crypto.sha256 [get; head] pub fn (mut app App) index() vweb.Result { if app.config.redirect { return app.redirect(app.config.redirect_url) } return app.not_found() } ['/api/v2/score/list/:game'; get; head] pub fn (mut app App) score_list(game string) vweb.Result { app.add_cors_headers() if !app.auth() { return app.status(Status{401, 'Access token is wrong or missing'}) } key := app.query['key'] or { default_score_key } offset := (app.query['offset'] or { '0' }).int() limit := (app.query['limit'] or { '-1' }).int() scores := app.get_scores_limit(game, key, offset, limit) or { app.logger.error('A database error occurred: ${err.str()}') return app.status(Status{500, 'An error occurred while retrieving score list'}) } return app.json(scores) } ['/api/v2/score/submit/:game'; post] pub fn (mut app App) score_submit(game string) vweb.Result { app.add_cors_headers() if !app.auth() { return app.status(Status{401, 'Access token is wrong or missing'}) } body := json.decode(Score, app.req.data) or { app.logger.error('Bad JSON object was retrieved: ${err.str()}') return app.status(Status{400, 'Bad JSON object'}) } if (body.score != none && body.scores != none) || (body.score == none && body.scores == none) { return app.status(Status{400, 'Either score field or scores field should be used'}) } if !app.valid_key(game, body) { app.logger.error('${body.player} tried to use invalid verification key for game ${game}') return app.status(Status{401, 'Verification key is wrong or missing'}) } if game !in app.config.games { app.logger.error('${body.player} tried to submit score(s) for invalid game ${game}') return app.status(Status{400, 'Unknown game'}) } if body.player.trim_space().len == 0 { app.logger.error('Somebody (${body.player}) tried to submit an empty name for game ${game}') return app.status(Status{400, 'Name cannot be empty'}) } // TODO: Bad word filter mut scores := []ScoreTable{} if score_num := body.score { if score_num <= 0 { app.logger.error('${body.player} tried to submit an illegal score for game ${game}') return app.status(Status{400, 'Illegal score'}) } scores << ScoreTable{ player: body.player game: game key: default_score_key score: score_num time: time.now().unix_time() } } else if scores_option := body.scores { score_list := scores_option.clone() for key, score in score_list { valid_key := app.config.valid_keys[game] or { [key] } if key !in valid_key { app.logger.error('${body.player} tried to use an invalid key for game ${game}') return app.status(Status{400, 'Unknown key'}) } if score <= 0 { app.logger.error('${body.player} tried to submit illegal score for game ${game}') return app.status(Status{400, 'Illegal score for "${key}"'}) } scores << ScoreTable{ player: body.player game: game key: key score: score time: time.now().unix_time() } } } else { app.logger.error('Bad JSON object was retrieved (Neither score and scores is an Option)') return app.status(Status{400, 'Bad JSON object'}) } score_res := app.insert_score_res(scores) or { app.logger.error('A database error occurred: ${err.str()}') return app.status(Status{500, 'An error occurred while inserting score'}) } app.logger.info('New score(s) submitted by ${body.player} for game ${game}') return app.json(score_res) } ['/api/v2/score/list'; options] pub fn (mut app App) handle_score_list_options() vweb.Result { return app.handle_options() } ['/api/v2/score/submit'; options] pub fn (mut app App) handle_score_submit_options() vweb.Result { return app.handle_options() } fn (mut app App) handle_options() vweb.Result { app.set_status(204, '') app.add_cors_headers() return app.ok('') } fn (mut app App) add_cors_headers() { origin := app.get_header('origin') // Only return headers if actual cross-origin request if origin.len == 0 { return } origins := app.config.origins 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') } fn (mut app App) auth() bool { auth_header := app.get_header('Authorization') token := auth_header.after('Bearer ') return token == app.config.token } fn (mut app App) valid_key(game string, score &Score) bool { mut scorestr := '' if score_num := score.score { scorestr += score_num.str() } else if scores_option := score.scores { score_list := scores_option.clone() for k, v in score_list { scorestr += k + v.str() } } else { return false } valid_key := sha256.hexhash(game + score.player + scorestr + app.config.valkey) return score.valkey == valid_key } fn (mut app App) status(status &Status) vweb.Result { app.set_status(status.status, '') return app.json(status) }