diff --git a/go.mod b/go.mod index dcad622..8f29d20 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/go-chi/jwtauth/v5 v5.3.1 github.com/go-chi/render v1.0.3 github.com/golang-migrate/migrate/v4 v4.17.1 + github.com/google/uuid v1.4.0 github.com/jackc/pgx/v5 v5.6.0 github.com/rs/zerolog v1.33.0 github.com/spf13/cobra v1.8.1 diff --git a/go.sum b/go.sum index da575be..ba67e75 100644 --- a/go.sum +++ b/go.sum @@ -41,6 +41,8 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-migrate/migrate/v4 v4.17.1 h1:4zQ6iqL6t6AiItphxJctQb3cFqWiSpMnX7wLTPnnYO4= github.com/golang-migrate/migrate/v4 v4.17.1/go.mod h1:m8hinFyWBn0SA4QKHuKh175Pm9wjmxj3S2Mia7dbXzM= +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= diff --git a/pkg/gordio/database/calls.sql.go b/pkg/gordio/database/calls.sql.go index 3a13edd..4ae32bc 100644 --- a/pkg/gordio/database/calls.sql.go +++ b/pkg/gordio/database/calls.sql.go @@ -8,6 +8,7 @@ package database import ( "context" + "github.com/google/uuid" "github.com/jackc/pgx/v5/pgtype" ) @@ -88,7 +89,7 @@ const setCallTranscript = `-- name: SetCallTranscript :exec UPDATE calls SET transcript = $2 WHERE id = $1 ` -func (q *Queries) SetCallTranscript(ctx context.Context, iD pgtype.UUID, transcript pgtype.Text) error { +func (q *Queries) SetCallTranscript(ctx context.Context, iD uuid.UUID, transcript pgtype.Text) error { _, err := q.db.Exec(ctx, setCallTranscript, iD, transcript) return err } diff --git a/pkg/gordio/database/models.go b/pkg/gordio/database/models.go index 82ede4f..0b83f76 100644 --- a/pkg/gordio/database/models.go +++ b/pkg/gordio/database/models.go @@ -5,6 +5,7 @@ package database import ( + "github.com/google/uuid" "github.com/jackc/pgx/v5/pgtype" ) @@ -14,11 +15,11 @@ type ApiKey struct { CreatedAt pgtype.Timestamp Expires pgtype.Timestamp Disabled pgtype.Bool - ApiKey pgtype.UUID + ApiKey uuid.UUID } type Call struct { - ID pgtype.UUID + ID uuid.UUID Submitter pgtype.Int4 System int32 Talkgroup int32 @@ -36,7 +37,7 @@ type Call struct { } type Incident struct { - ID pgtype.UUID + ID uuid.UUID Name string Description pgtype.Text StartTime pgtype.Timestamp @@ -46,8 +47,8 @@ type Incident struct { } type IncidentsCall struct { - IncidentID pgtype.UUID - CallID pgtype.UUID + IncidentID uuid.UUID + CallID uuid.UUID Notes []byte } diff --git a/pkg/gordio/database/users.sql.go b/pkg/gordio/database/users.sql.go index ab3d64c..3d03a5f 100644 --- a/pkg/gordio/database/users.sql.go +++ b/pkg/gordio/database/users.sql.go @@ -8,6 +8,7 @@ package database import ( "context" + "github.com/google/uuid" "github.com/jackc/pgx/v5/pgtype" ) @@ -76,7 +77,7 @@ const deleteAPIKey = `-- name: DeleteAPIKey :exec DELETE FROM api_keys WHERE api_key = $1 ` -func (q *Queries) DeleteAPIKey(ctx context.Context, apiKey pgtype.UUID) error { +func (q *Queries) DeleteAPIKey(ctx context.Context, apiKey uuid.UUID) error { _, err := q.db.Exec(ctx, deleteAPIKey, apiKey) return err } @@ -90,6 +91,24 @@ func (q *Queries) DeleteUser(ctx context.Context, username string) error { return err } +const getAPIKey = `-- name: GetAPIKey :one +SELECT id, owner, created_at, expires, disabled, api_key FROM api_keys WHERE api_key = $1 +` + +func (q *Queries) GetAPIKey(ctx context.Context, apiKey uuid.UUID) (ApiKey, error) { + row := q.db.QueryRow(ctx, getAPIKey, apiKey) + var i ApiKey + err := row.Scan( + &i.ID, + &i.Owner, + &i.CreatedAt, + &i.Expires, + &i.Disabled, + &i.ApiKey, + ) + return i, err +} + const getUserByID = `-- name: GetUserByID :one SELECT id, username, password, email, is_admin, prefs FROM users WHERE id = $1 LIMIT 1 diff --git a/pkg/gordio/server/calls.go b/pkg/gordio/server/calls.go index 9eab255..8e4dcbd 100644 --- a/pkg/gordio/server/calls.go +++ b/pkg/gordio/server/calls.go @@ -8,9 +8,10 @@ import ( "strconv" "strings" "time" -) -//const timeFormat = "2006-01-02T15:04:05.999Z07:00" + "dynatron.me/x/stillbox/pkg/gordio/database" + "github.com/google/uuid" +) type callUploadRequest struct { Audio []byte `form:"audio"` @@ -21,9 +22,9 @@ type callUploadRequest struct { Frequency int `form:"frequency"` Key string `form:"key"` Patches []string `form:"patches"` - Source string `form:"source"` + Source int `form:"source"` Sources []string `form:"sources"` - System string `form:"system"` + System int `form:"system"` SystemLabel string `form:"systemLabel"` Talkgroup int `form:"talkgroup"` TalkgroupGroup string `form:"talkgroupGroup"` @@ -32,8 +33,35 @@ type callUploadRequest struct { } func (s *Server) routeCallUpload(w http.ResponseWriter, r *http.Request) { + err := r.ParseMultipartForm(1024 * 1024 * 2) // 2MB + if err != nil { + http.Error(w, "cannot parse form "+err.Error(), http.StatusBadRequest) + return + } + + keyUuid, err := uuid.Parse(r.Form.Get("key")) + if err != nil { + http.Error(w, "cannot parse key "+err.Error(), http.StatusBadRequest) + return + } + apik, err := s.db.GetAPIKey(r.Context(), keyUuid) + if err != nil { + if database.IsNoRows(err) { + http.Error(w, "bad key", http.StatusUnauthorized) + return + } + + http.Error(w, "Internal server error", http.StatusInternalServerError) + return + } + + if apik.Disabled.Bool || (apik.Expires.Valid && time.Now().After(apik.Expires.Time)) { + http.Error(w, "disabled", http.StatusUnauthorized) + return + } + call := new(callUploadRequest) - err := call.fill(r) + err = call.fill(r) if err != nil { http.Error(w, "cannot bind upload "+err.Error(), 500) return @@ -43,11 +71,6 @@ func (s *Server) routeCallUpload(w http.ResponseWriter, r *http.Request) { } func (car *callUploadRequest) fill(r *http.Request) error { - err := r.ParseMultipartForm(1024 * 1024 * 2) // 2MB - if err != nil { - return fmt.Errorf("multipart parse: %w", err) - } - rv := reflect.ValueOf(car).Elem() rt := rv.Type() @@ -73,7 +96,7 @@ func (car *callUploadRequest) fill(r *http.Request) error { return fmt.Errorf("parse time: %w", err) } f.Set(reflect.ValueOf(t)) - case "frequencies": + case "frequencies", "patches", "sources": val := strings.Trim(r.Form.Get(ff), "[]") if val == "" { continue @@ -87,20 +110,12 @@ func (car *callUploadRequest) fill(r *http.Request) error { } } f.Set(reflect.ValueOf(ar)) - case "frequency", "talkgroup": + case "frequency", "talkgroup", "system", "source": val, err := strconv.Atoi(r.Form.Get(ff)) if err != nil { return fmt.Errorf("atoi('%s'): %w", ff, err) } f.SetInt(int64(val)) - case "patches", "sources": - val := strings.Trim(r.Form.Get(ff), "[]") - if val == "" { - continue - } - vals := strings.Split(val, ",") - f.Set(reflect.ValueOf(vals)) - default: f.SetString(r.Form.Get(ff)) } diff --git a/sql/postgres/queries/users.sql b/sql/postgres/queries/users.sql index 625800e..85481a9 100644 --- a/sql/postgres/queries/users.sql +++ b/sql/postgres/queries/users.sql @@ -40,3 +40,6 @@ RETURNING *; -- name: DeleteAPIKey :exec DELETE FROM api_keys WHERE api_key = $1; + +-- name: GetAPIKey :one +SELECT * FROM api_keys WHERE api_key = $1; diff --git a/sql/sqlc.yaml b/sql/sqlc.yaml index 42a81a7..d4ae746 100644 --- a/sql/sqlc.yaml +++ b/sql/sqlc.yaml @@ -9,3 +9,9 @@ sql: out: "../pkg/gordio/database" sql_package: "pgx/v5" query_parameter_limit: 3 + overrides: + - db_type: "uuid" + go_type: + import: "github.com/google/uuid" + type: "UUID" +