Compare commits
2 Commits
fc5650fe78
...
272f14b264
Author | SHA1 | Date |
---|---|---|
|
272f14b264 | |
|
4887af26d3 |
|
@ -12,12 +12,8 @@ import models { BuildLog, BuildLogFilter }
|
|||
|
||||
// v1_get_logs returns all build logs in the database. A 'target' query param can
|
||||
// optionally be added to limit the list of build logs to that repository.
|
||||
['/api/v1/logs'; get]
|
||||
['/api/v1/logs'; auth; get]
|
||||
fn (mut app App) v1_get_logs() web.Result {
|
||||
if !app.is_authorized() {
|
||||
return app.json(http.Status.unauthorized, new_response('Unauthorized.'))
|
||||
}
|
||||
|
||||
filter := models.from_params<BuildLogFilter>(app.query) or {
|
||||
return app.json(http.Status.bad_request, new_response('Invalid query parameters.'))
|
||||
}
|
||||
|
@ -27,24 +23,16 @@ 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'; get]
|
||||
['/api/v1/logs/:id'; auth; 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'; get]
|
||||
['/api/v1/logs/:id/content'; auth; 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,
|
||||
|
@ -63,12 +51,8 @@ fn parse_query_time(query string) ?time.Time {
|
|||
}
|
||||
|
||||
// v1_post_log adds a new log to the database.
|
||||
['/api/v1/logs'; post]
|
||||
['/api/v1/logs'; auth; 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()
|
||||
|
||||
|
|
|
@ -7,12 +7,8 @@ import db
|
|||
import models { Target, TargetArch, TargetFilter }
|
||||
|
||||
// v1_get_targets returns the current list of targets.
|
||||
['/api/v1/targets'; get]
|
||||
['/api/v1/targets'; auth; 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<TargetFilter>(app.query) or {
|
||||
return app.json(http.Status.bad_request, new_response('Invalid query parameters.'))
|
||||
}
|
||||
|
@ -22,24 +18,16 @@ fn (mut app App) v1_get_targets() web.Result {
|
|||
}
|
||||
|
||||
// v1_get_single_target returns the information for a single target.
|
||||
['/api/v1/targets/:id'; get]
|
||||
['/api/v1/targets/:id'; auth; 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'; post]
|
||||
['/api/v1/targets'; auth; 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
|
||||
|
@ -63,24 +51,16 @@ 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'; delete]
|
||||
['/api/v1/targets/:id'; auth; 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'; patch]
|
||||
['/api/v1/targets/:id'; auth; 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 {
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
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
|
||||
}
|
|
@ -49,12 +49,8 @@ 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'; post]
|
||||
['/:repo/publish'; auth; 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) {
|
||||
|
|
|
@ -5,12 +5,8 @@ import net.http
|
|||
import web.response { new_response }
|
||||
|
||||
// delete_package tries to remove the given package.
|
||||
['/:repo/:arch/:pkg'; delete]
|
||||
['/:repo/:arch/:pkg'; auth; 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()')
|
||||
|
||||
|
@ -29,12 +25,8 @@ 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'; delete]
|
||||
['/:repo/:arch'; auth; 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()')
|
||||
|
||||
|
@ -53,12 +45,8 @@ fn (mut app App) delete_arch_repo(repo string, arch string) web.Result {
|
|||
}
|
||||
|
||||
// delete_repo tries to remove the given repo.
|
||||
['/:repo'; delete]
|
||||
['/:repo'; auth; 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()')
|
||||
|
||||
|
|
|
@ -73,6 +73,7 @@ pub fn server(conf Config) ? {
|
|||
|
||||
web.run(&App{
|
||||
logger: logger
|
||||
api_key: conf.api_key
|
||||
conf: conf
|
||||
repo: repo
|
||||
db: db
|
||||
|
|
|
@ -26,6 +26,14 @@ pub const (
|
|||
value: 'text/plain'
|
||||
).join(headers_close)
|
||||
)
|
||||
http_401 = http.new_response(
|
||||
status: .unauthorized
|
||||
body: '401 Unauthorized'
|
||||
header: http.new_header(
|
||||
key: .content_type
|
||||
value: 'text/plain'
|
||||
).join(headers_close)
|
||||
)
|
||||
http_404 = http.new_response(
|
||||
status: .not_found
|
||||
body: '404 Not Found'
|
||||
|
|
|
@ -3,6 +3,10 @@ module web
|
|||
import net.urllib
|
||||
import net.http
|
||||
|
||||
// Method attributes that should be ignored when parsing, as they're used
|
||||
// elsewhere.
|
||||
const attrs_to_ignore = ['auth']
|
||||
|
||||
// Parsing function attributes for methods and path.
|
||||
fn parse_attrs(name string, attrs []string) ?([]http.Method, string) {
|
||||
if attrs.len == 0 {
|
||||
|
@ -32,7 +36,7 @@ fn parse_attrs(name string, attrs []string) ?([]http.Method, string) {
|
|||
}
|
||||
i++
|
||||
}
|
||||
if x.len > 0 {
|
||||
if x.len > 0 && x.any(!web.attrs_to_ignore.contains(it)) {
|
||||
return IError(http.UnexpectedExtraAttributeError{
|
||||
attributes: x
|
||||
})
|
||||
|
|
|
@ -18,6 +18,8 @@ pub struct Context {
|
|||
pub:
|
||||
// HTTP Request
|
||||
req http.Request
|
||||
// API key used when authenticating requests
|
||||
api_key string
|
||||
// TODO Response
|
||||
pub mut:
|
||||
// TCP connection to client.
|
||||
|
@ -101,9 +103,10 @@ 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.Response{
|
||||
mut resp := http.new_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)?
|
||||
|
@ -133,6 +136,15 @@ pub fn (mut ctx Context) send_reader_response(mut reader io.Reader, size u64) bo
|
|||
return true
|
||||
}
|
||||
|
||||
// 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') {
|
||||
return provided_key == ctx.api_key
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// json<T> HTTP_OK with json_s as payload with content-type `application/json`
|
||||
pub fn (mut ctx Context) json<T>(status http.Status, j T) Result {
|
||||
ctx.status = status
|
||||
|
@ -177,9 +189,12 @@ pub fn (mut ctx Context) file(f_path string) Result {
|
|||
file.close()
|
||||
}
|
||||
|
||||
// Currently, this only supports a single provided range, e.g.
|
||||
// bytes=0-1023, and not multiple ranges, e.g. bytes=0-50, 100-150
|
||||
if range_str := ctx.req.header.get(.range) {
|
||||
mut parts := range_str.split_nth('=', 2)
|
||||
|
||||
// We only support the 'bytes' range type
|
||||
if parts[0] != 'bytes' {
|
||||
ctx.status = .requested_range_not_satisfiable
|
||||
ctx.header.delete(.content_length)
|
||||
|
@ -376,6 +391,7 @@ fn handle_conn<T>(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...
|
||||
|
@ -394,31 +410,27 @@ fn handle_conn<T>(mut conn net.TcpConn, mut app T, routes map[string]Route) {
|
|||
// Used for route matching
|
||||
route_words := route.path.split('/').filter(it != '')
|
||||
|
||||
// Route immediate matches first
|
||||
// Route immediate matches & index files first
|
||||
// For example URL `/register` matches route `/:user`, but `fn register()`
|
||||
// should be called first.
|
||||
if !route.path.contains('/:') && url_words == route_words {
|
||||
// We found a match
|
||||
if head.method == .post && method.args.len > 0 {
|
||||
// TODO implement POST requests
|
||||
// Populate method args with form values
|
||||
// mut args := []string{cap: method.args.len}
|
||||
// for param in method.args {
|
||||
// args << form[param.name]
|
||||
// }
|
||||
// app.$method(args)
|
||||
} else {
|
||||
app.$method()
|
||||
if (!route.path.contains('/:') && url_words == route_words)
|
||||
|| (url_words.len == 0 && route_words == ['index'] && method.name == 'index') {
|
||||
// Check whether the request is authorised
|
||||
if 'auth' in method.attrs && !app.is_authenticated() {
|
||||
conn.write(http_401.bytes()) or {}
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if url_words.len == 0 && route_words == ['index'] && method.name == 'index' {
|
||||
// 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() {
|
||||
conn.write(http_401.bytes()) or {}
|
||||
return
|
||||
}
|
||||
|
||||
if params := route_matches(url_words, route_words) {
|
||||
method_args := params.clone()
|
||||
if method_args.len != method.args.len {
|
||||
eprintln('warning: uneven parameters count ($method.args.len) in `$method.name`, compared to the web route `$method.attrs` ($method_args.len)')
|
||||
|
|
Loading…
Reference in New Issue