diff --git a/src/server/api_logs.v b/src/server/api_logs.v index 021c1ac..718c6de 100644 --- a/src/server/api_logs.v +++ b/src/server/api_logs.v @@ -23,16 +23,24 @@ fn (mut app App) v1_get_logs() web.Result { } // v1_get_single_log returns the build log with the given id. -['/api/v1/logs/:id'; auth; get] +['/api/v1/logs/:id'; get] fn (mut app App) v1_get_single_log(id int) web.Result { + if !app.is_authorized() { + return app.json(http.Status.unauthorized, new_response('Unauthorized.')) + } + log := app.db.get_build_log(id) or { return app.not_found() } return app.json(http.Status.ok, new_data_response(log)) } // v1_get_log_content returns the actual build log file for the given id. -['/api/v1/logs/:id/content'; auth; get] +['/api/v1/logs/:id/content'; get] fn (mut app App) v1_get_log_content(id int) web.Result { + if !app.is_authorized() { + return app.json(http.Status.unauthorized, new_response('Unauthorized.')) + } + log := app.db.get_build_log(id) or { return app.not_found() } file_name := log.start_time.custom_format('YYYY-MM-DD_HH-mm-ss') full_path := os.join_path(app.conf.data_dir, logs_dir_name, log.target_id.str(), log.arch, @@ -51,8 +59,12 @@ fn parse_query_time(query string) ?time.Time { } // v1_post_log adds a new log to the database. -['/api/v1/logs'; auth; post] +['/api/v1/logs'; post] fn (mut app App) v1_post_log() web.Result { + if !app.is_authorized() { + return app.json(http.Status.unauthorized, new_response('Unauthorized.')) + } + // Parse query params start_time_int := app.query['startTime'].int() diff --git a/src/server/api_targets.v b/src/server/api_targets.v index c9e7963..4cc3a58 100644 --- a/src/server/api_targets.v +++ b/src/server/api_targets.v @@ -7,8 +7,12 @@ import db import models { Target, TargetArch, TargetFilter } // v1_get_targets returns the current list of targets. -['/api/v1/targets'; auth; get] +['/api/v1/targets'; get] fn (mut app App) v1_get_targets() web.Result { + if !app.is_authorized() { + return app.json(http.Status.unauthorized, new_response('Unauthorized.')) + } + filter := models.from_params(app.query) or { return app.json(http.Status.bad_request, new_response('Invalid query parameters.')) } @@ -18,16 +22,24 @@ fn (mut app App) v1_get_targets() web.Result { } // v1_get_single_target returns the information for a single target. -['/api/v1/targets/:id'; auth; get] +['/api/v1/targets/:id'; get] fn (mut app App) v1_get_single_target(id int) web.Result { + if !app.is_authorized() { + return app.json(http.Status.unauthorized, new_response('Unauthorized.')) + } + repo := app.db.get_target(id) or { return app.not_found() } return app.json(http.Status.ok, new_data_response(repo)) } // v1_post_target creates a new target from the provided query string. -['/api/v1/targets'; auth; post] +['/api/v1/targets'; post] fn (mut app App) v1_post_target() web.Result { + if !app.is_authorized() { + return app.json(http.Status.unauthorized, new_response('Unauthorized.')) + } + mut params := app.query.clone() // If a repo is created without specifying the arch, we assume it's meant @@ -51,16 +63,24 @@ fn (mut app App) v1_post_target() web.Result { } // v1_delete_target removes a given target from the server's list. -['/api/v1/targets/:id'; auth; delete] +['/api/v1/targets/:id'; delete] fn (mut app App) v1_delete_target(id int) web.Result { + if !app.is_authorized() { + return app.json(http.Status.unauthorized, new_response('Unauthorized.')) + } + app.db.delete_target(id) return app.json(http.Status.ok, new_response('Repo removed successfully.')) } // v1_patch_target updates a target's data with the given query params. -['/api/v1/targets/:id'; auth; patch] +['/api/v1/targets/:id'; patch] fn (mut app App) v1_patch_target(id int) web.Result { + if !app.is_authorized() { + return app.json(http.Status.unauthorized, new_response('Unauthorized.')) + } + app.db.update_target(id, app.query) if 'arch' in app.query { diff --git a/src/server/auth.v b/src/server/auth.v new file mode 100644 index 0000000..7c8a676 --- /dev/null +++ b/src/server/auth.v @@ -0,0 +1,12 @@ +module server + +import net.http + +// is_authorized checks whether the provided API key is correct. +fn (mut app App) is_authorized() bool { + x_header := app.req.header.get_custom('X-Api-Key', http.HeaderQueryConfig{ exact: true }) or { + return false + } + + return x_header.trim_space() == app.conf.api_key +} diff --git a/src/server/repo.v b/src/server/repo.v index 5ed5d15..242fd2d 100644 --- a/src/server/repo.v +++ b/src/server/repo.v @@ -49,8 +49,12 @@ fn (mut app App) get_repo_file(repo string, arch string, filename string) web.Re } // put_package handles publishing a package to a repository. -['/:repo/publish'; auth; post] +['/:repo/publish'; post] fn (mut app App) put_package(repo string) web.Result { + if !app.is_authorized() { + return app.json(http.Status.unauthorized, new_response('Unauthorized.')) + } + mut pkg_path := '' if length := app.req.header.get(.content_length) { diff --git a/src/server/repo_remove.v b/src/server/repo_remove.v index fdc40e8..316b387 100644 --- a/src/server/repo_remove.v +++ b/src/server/repo_remove.v @@ -5,8 +5,12 @@ import net.http import web.response { new_response } // delete_package tries to remove the given package. -['/:repo/:arch/:pkg'; auth; delete] +['/:repo/:arch/:pkg'; delete] fn (mut app App) delete_package(repo string, arch string, pkg string) web.Result { + if !app.is_authorized() { + return app.json(.unauthorized, new_response('Unauthorized.')) + } + res := app.repo.remove_pkg_from_arch_repo(repo, arch, pkg, true) or { app.lerror('Error while deleting package: $err.msg()') @@ -25,8 +29,12 @@ fn (mut app App) delete_package(repo string, arch string, pkg string) web.Result } // delete_arch_repo tries to remove the given arch-repo. -['/:repo/:arch'; auth; delete] +['/:repo/:arch'; delete] fn (mut app App) delete_arch_repo(repo string, arch string) web.Result { + if !app.is_authorized() { + return app.json(http.Status.unauthorized, new_response('Unauthorized.')) + } + res := app.repo.remove_arch_repo(repo, arch) or { app.lerror('Error while deleting arch-repo: $err.msg()') @@ -45,8 +53,12 @@ fn (mut app App) delete_arch_repo(repo string, arch string) web.Result { } // delete_repo tries to remove the given repo. -['/:repo'; auth; delete] +['/:repo'; delete] fn (mut app App) delete_repo(repo string) web.Result { + if !app.is_authorized() { + return app.json(http.Status.unauthorized, new_response('Unauthorized.')) + } + res := app.repo.remove_repo(repo) or { app.lerror('Error while deleting repo: $err.msg()') diff --git a/src/web/parse.v b/src/web/parse.v index ee7a72c..d0deec2 100644 --- a/src/web/parse.v +++ b/src/web/parse.v @@ -3,7 +3,7 @@ module web import net.urllib import net.http -// Method attributes that should be ignored when parsing, as they're used +// Method attributes that should be ignored when parsing, as they're using // elsewhere. const attrs_to_ignore = ['auth'] diff --git a/src/web/web.v b/src/web/web.v index 1d1480f..e4b1689 100644 --- a/src/web/web.v +++ b/src/web/web.v @@ -15,11 +15,11 @@ import log // The Context struct represents the Context which hold the HTTP request and response. // It has fields for the query, form, files. pub struct Context { + // API key used when authenticating requests + api_key string pub: // HTTP Request req http.Request - // API key used when authenticating requests - api_key string // TODO Response pub mut: // TCP connection to client. @@ -103,10 +103,9 @@ fn (mut ctx Context) send_custom_response(resp &http.Response) ? { // send_response_header constructs a valid HTTP response with an empty body & // sends it to the client. pub fn (mut ctx Context) send_response_header() ? { - mut resp := http.new_response( + mut resp := http.Response{ header: ctx.header.join(headers_close) - ) - resp.header.add(.content_type, ctx.content_type) + } resp.set_status(ctx.status) ctx.send_custom_response(resp)? @@ -139,6 +138,8 @@ pub fn (mut ctx Context) send_reader_response(mut reader io.Reader, size u64) bo // is_authenticated checks whether the request passes a correct API key. pub fn (ctx &Context) is_authenticated() bool { if provided_key := ctx.req.header.get_custom('X-Api-Key') { + println(provided_key) + println(ctx.api_key) return provided_key == ctx.api_key } @@ -391,7 +392,6 @@ fn handle_conn(mut conn net.TcpConn, mut app T, routes map[string]Route) { static_mime_types: app.static_mime_types reader: reader logger: app.logger - api_key: app.api_key } // Calling middleware... @@ -423,7 +423,6 @@ fn handle_conn(mut conn net.TcpConn, mut app T, routes map[string]Route) { // We found a match app.$method() - return } else if params := route_matches(url_words, route_words) { // Check whether the request is authorised if 'auth' in method.attrs && !app.is_authenticated() {