Compare commits

..

1 Commits

Author SHA1 Message Date
Jef Roosens fc5650fe78
WIP: move auth to web
ci/woodpecker/pr/docs Pipeline was successful Details
ci/woodpecker/pr/lint Pipeline was successful Details
ci/woodpecker/pr/build Pipeline was successful Details
ci/woodpecker/pr/docker Pipeline was successful Details
ci/woodpecker/pr/man Pipeline was successful Details
ci/woodpecker/pr/test Pipeline was successful Details
2022-09-02 23:03:10 +02:00
7 changed files with 79 additions and 20 deletions

View File

@ -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. // 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 { 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() } log := app.db.get_build_log(id) or { return app.not_found() }
return app.json(http.Status.ok, new_data_response(log)) return app.json(http.Status.ok, new_data_response(log))
} }
// v1_get_log_content returns the actual build log file for the given id. // 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 { 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() } 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') 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, 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. // 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 { 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 // Parse query params
start_time_int := app.query['startTime'].int() start_time_int := app.query['startTime'].int()

View File

@ -7,8 +7,12 @@ import db
import models { Target, TargetArch, TargetFilter } import models { Target, TargetArch, TargetFilter }
// v1_get_targets returns the current list of targets. // 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 { 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<TargetFilter>(app.query) or { filter := models.from_params<TargetFilter>(app.query) or {
return app.json(http.Status.bad_request, new_response('Invalid query parameters.')) 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. // 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 { 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() } repo := app.db.get_target(id) or { return app.not_found() }
return app.json(http.Status.ok, new_data_response(repo)) return app.json(http.Status.ok, new_data_response(repo))
} }
// v1_post_target creates a new target from the provided query string. // 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 { 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() mut params := app.query.clone()
// If a repo is created without specifying the arch, we assume it's meant // 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. // 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 { 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) app.db.delete_target(id)
return app.json(http.Status.ok, new_response('Repo removed successfully.')) return app.json(http.Status.ok, new_response('Repo removed successfully.'))
} }
// v1_patch_target updates a target's data with the given query params. // 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 { 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) app.db.update_target(id, app.query)
if 'arch' in app.query { if 'arch' in app.query {

12
src/server/auth.v 100644
View File

@ -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
}

View File

@ -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. // 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 { 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 := '' mut pkg_path := ''
if length := app.req.header.get(.content_length) { if length := app.req.header.get(.content_length) {

View File

@ -5,8 +5,12 @@ import net.http
import web.response { new_response } import web.response { new_response }
// delete_package tries to remove the given package. // 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 { 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 { res := app.repo.remove_pkg_from_arch_repo(repo, arch, pkg, true) or {
app.lerror('Error while deleting package: $err.msg()') 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. // 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 { 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 { res := app.repo.remove_arch_repo(repo, arch) or {
app.lerror('Error while deleting arch-repo: $err.msg()') 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. // delete_repo tries to remove the given repo.
['/:repo'; auth; delete] ['/:repo'; delete]
fn (mut app App) delete_repo(repo string) web.Result { 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 { res := app.repo.remove_repo(repo) or {
app.lerror('Error while deleting repo: $err.msg()') app.lerror('Error while deleting repo: $err.msg()')

View File

@ -3,7 +3,7 @@ module web
import net.urllib import net.urllib
import net.http 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. // elsewhere.
const attrs_to_ignore = ['auth'] const attrs_to_ignore = ['auth']

View File

@ -15,11 +15,11 @@ import log
// The Context struct represents the Context which hold the HTTP request and response. // The Context struct represents the Context which hold the HTTP request and response.
// It has fields for the query, form, files. // It has fields for the query, form, files.
pub struct Context { pub struct Context {
// API key used when authenticating requests
api_key string
pub: pub:
// HTTP Request // HTTP Request
req http.Request req http.Request
// API key used when authenticating requests
api_key string
// TODO Response // TODO Response
pub mut: pub mut:
// TCP connection to client. // 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 & // send_response_header constructs a valid HTTP response with an empty body &
// sends it to the client. // sends it to the client.
pub fn (mut ctx Context) send_response_header() ? { pub fn (mut ctx Context) send_response_header() ? {
mut resp := http.new_response( mut resp := http.Response{
header: ctx.header.join(headers_close) header: ctx.header.join(headers_close)
) }
resp.header.add(.content_type, ctx.content_type)
resp.set_status(ctx.status) resp.set_status(ctx.status)
ctx.send_custom_response(resp)? 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. // is_authenticated checks whether the request passes a correct API key.
pub fn (ctx &Context) is_authenticated() bool { pub fn (ctx &Context) is_authenticated() bool {
if provided_key := ctx.req.header.get_custom('X-Api-Key') { if provided_key := ctx.req.header.get_custom('X-Api-Key') {
println(provided_key)
println(ctx.api_key)
return provided_key == ctx.api_key return provided_key == ctx.api_key
} }
@ -391,7 +392,6 @@ fn handle_conn<T>(mut conn net.TcpConn, mut app T, routes map[string]Route) {
static_mime_types: app.static_mime_types static_mime_types: app.static_mime_types
reader: reader reader: reader
logger: app.logger logger: app.logger
api_key: app.api_key
} }
// Calling middleware... // Calling middleware...
@ -423,7 +423,6 @@ fn handle_conn<T>(mut conn net.TcpConn, mut app T, routes map[string]Route) {
// We found a match // We found a match
app.$method() app.$method()
return
} else if params := route_matches(url_words, route_words) { } else if params := route_matches(url_words, route_words) {
// Check whether the request is authorised // Check whether the request is authorised
if 'auth' in method.attrs && !app.is_authenticated() { if 'auth' in method.attrs && !app.is_authenticated() {