RBAC #102
23 changed files with 170 additions and 71 deletions
1
go.mod
1
go.mod
|
@ -39,6 +39,7 @@ require (
|
|||
github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect
|
||||
github.com/el-mike/restrict/v2 v2.0.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.8.0 // indirect
|
||||
github.com/go-audio/audio v1.0.0 // indirect
|
||||
github.com/go-audio/riff v1.0.0 // indirect
|
||||
|
|
18
go.sum
18
go.sum
|
@ -8,6 +8,12 @@ github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
|
|||
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
|
||||
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de h1:FxWPpzIjnTlhPwqqXc4/vE0f7GvRjuAsbW+HOIe8KnA=
|
||||
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de/go.mod h1:DCaWoUhZrYW9p1lxo/cm8EmUOOzAPSEZNGF2DK1dJgw=
|
||||
github.com/bmatcuk/doublestar/v4 v4.6.1 h1:FH9SifrbvJhnlQpztAx++wlkk70QBf0iBWDwNy7PA4I=
|
||||
github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
|
||||
github.com/casbin/casbin/v2 v2.103.0 h1:dHElatNXNrr8XcseUov0ZSiWjauwmZZE6YMV3eU1yic=
|
||||
github.com/casbin/casbin/v2 v2.103.0/go.mod h1:Ee33aqGrmES+GNL17L0h9X28wXuo829wnNUnS0edAco=
|
||||
github.com/casbin/govaluate v1.3.0 h1:VA0eSY0M2lA86dYd5kPPuNZMUD9QkWnOCnavGrw9myc=
|
||||
github.com/casbin/govaluate v1.3.0/go.mod h1:G/UnbIjZk/0uMNaLwZZmFQrR72tYRZWQkO70si/iR7A=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
|
@ -28,6 +34,8 @@ github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj
|
|||
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
|
||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/el-mike/restrict/v2 v2.0.0 h1:OuVBseAejSHyfHMUr15c4Gz3WRCEKuuD8IOR/mOIV/o=
|
||||
github.com/el-mike/restrict/v2 v2.0.0/go.mod h1:ClycXfCKWIZRU1qi2CJIOpHEuonBOj/2GKc+w1lZtrQ=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
|
||||
|
@ -61,6 +69,7 @@ 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.18.1 h1:JML/k+t4tpHCpQTCAD62Nu43NUFzHY4CV3uAuvHGC+Y=
|
||||
github.com/golang-migrate/migrate/v4 v4.18.1/go.mod h1:HAX6m3sQgcdO81tdjn5exv20+3Kb13cmGli1hrD6hks=
|
||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
|
@ -157,12 +166,16 @@ github.com/scylladb/termtables v0.0.0-20191203121021-c4c0b6d42ff4/go.mod h1:C1a7
|
|||
github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
|
||||
github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
|
@ -183,6 +196,7 @@ go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HY
|
|||
go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A=
|
||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ=
|
||||
golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
|
@ -194,8 +208,11 @@ golang.org/x/image v0.22.0/go.mod h1:9hPFhljd4zZ1GNSIZJ49sqbp45GKK9t6w+iXvGqZUz4
|
|||
golang.org/x/mobile v0.0.0-20190415191353-3e0bab5405d6/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/mobile v0.0.0-20241108191957-fa514ef75a0f h1:23H/YlmTHfmmvpZ+ajKZL0qLz0+IwFOIqQA0mQbmLeM=
|
||||
golang.org/x/mobile v0.0.0-20241108191957-fa514ef75a0f/go.mod h1:UbSUP4uu/C9hw9R2CkojhXlAxvayHjBdU9aRvE+c1To=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ=
|
||||
golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190429190828-d89cdac9e872/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
|
@ -208,6 +225,7 @@ golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E=
|
|||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug=
|
||||
golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4=
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io=
|
||||
google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
|
|
@ -18,6 +18,7 @@ import (
|
|||
"dynatron.me/x/stillbox/pkg/auth"
|
||||
"dynatron.me/x/stillbox/pkg/calls"
|
||||
"dynatron.me/x/stillbox/pkg/sources"
|
||||
"dynatron.me/x/stillbox/pkg/users"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
@ -62,16 +63,16 @@ func TestMarshal(t *testing.T) {
|
|||
|
||||
tests := []struct {
|
||||
name string
|
||||
submitter auth.UserID
|
||||
submitter users.UserID
|
||||
apiKey string
|
||||
call calls.Call
|
||||
}{
|
||||
{
|
||||
name: "base",
|
||||
submitter: auth.UserID(1),
|
||||
submitter: users.UserID(1),
|
||||
call: calls.Call{
|
||||
ID: uuid.UUID([16]byte{0x52, 0xfd, 0xfc, 0x07, 0x21, 0x82, 0x45, 0x4f, 0x96, 0x3f, 0x5f, 0x0f, 0x9a, 0x62, 0x1d, 0x72}),
|
||||
Submitter: common.PtrTo(auth.UserID(1)),
|
||||
Submitter: common.PtrTo(users.UserID(1)),
|
||||
System: 197,
|
||||
Talkgroup: 10101,
|
||||
DateTime: time.Date(2024, 11, 10, 23, 33, 02, 0, time.Local),
|
||||
|
|
|
@ -7,18 +7,19 @@ import (
|
|||
"time"
|
||||
|
||||
"dynatron.me/x/stillbox/pkg/database"
|
||||
"dynatron.me/x/stillbox/pkg/users"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type apiKeyAuth interface {
|
||||
// CheckAPIKey validates the provided key and returns the API owner's UserID.
|
||||
// CheckAPIKey validates the provided key and returns the API owner's users.UserID.
|
||||
// An error is returned if validation fails for any reason.
|
||||
CheckAPIKey(ctx context.Context, key string) (*UserID, error)
|
||||
CheckAPIKey(ctx context.Context, key string) (*users.UserID, error)
|
||||
}
|
||||
|
||||
func (a *Auth) CheckAPIKey(ctx context.Context, key string) (*UserID, error) {
|
||||
func (a *Auth) CheckAPIKey(ctx context.Context, key string) (*users.UserID, error) {
|
||||
keyUuid, err := uuid.Parse(key)
|
||||
if err != nil {
|
||||
log.Error().Str("apikey", key).Msg("cannot parse key")
|
||||
|
@ -44,7 +45,7 @@ func (a *Auth) CheckAPIKey(ctx context.Context, key string) (*UserID, error) {
|
|||
return nil, ErrUnauthorized
|
||||
}
|
||||
|
||||
owner := UserID(apik.Owner)
|
||||
owner := users.UserID(apik.Owner)
|
||||
|
||||
return &owner, nil
|
||||
}
|
||||
|
|
|
@ -13,18 +13,6 @@ import (
|
|||
"github.com/go-chi/jwtauth/v5"
|
||||
)
|
||||
|
||||
type UserID int
|
||||
|
||||
func (u *UserID) Int32Ptr() *int32 {
|
||||
if u == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
i := int32(*u)
|
||||
|
||||
return &i
|
||||
}
|
||||
|
||||
// Authenticator performs API key and user JWT authentication.
|
||||
type Authenticator interface {
|
||||
jwtAuth
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
"golang.org/x/crypto/bcrypt"
|
||||
|
||||
"dynatron.me/x/stillbox/pkg/database"
|
||||
"dynatron.me/x/stillbox/pkg/users"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/jwtauth/v5"
|
||||
|
@ -44,7 +45,8 @@ type jwtAuth interface {
|
|||
|
||||
type claims map[string]interface{}
|
||||
|
||||
func UIDFrom(ctx context.Context) *int32 {
|
||||
// TODO: change this to UserFrom() *users.User
|
||||
func UIDFrom(ctx context.Context) *users.UserID {
|
||||
tok, _, err := jwtauth.FromContext(ctx)
|
||||
if err != nil {
|
||||
return nil
|
||||
|
@ -56,7 +58,7 @@ func UIDFrom(ctx context.Context) *int32 {
|
|||
return nil
|
||||
}
|
||||
|
||||
uid := int32(uidInt)
|
||||
uid := users.UserID(int32(uidInt))
|
||||
|
||||
return &uid
|
||||
}
|
||||
|
|
|
@ -7,9 +7,9 @@ import (
|
|||
|
||||
"dynatron.me/x/stillbox/internal/audio"
|
||||
"dynatron.me/x/stillbox/internal/jsontypes"
|
||||
"dynatron.me/x/stillbox/pkg/auth"
|
||||
"dynatron.me/x/stillbox/pkg/pb"
|
||||
"dynatron.me/x/stillbox/pkg/talkgroups"
|
||||
"dynatron.me/x/stillbox/pkg/users"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
@ -63,7 +63,7 @@ type Call struct {
|
|||
Patches []int `json:"patches,omitempty" relayOut:"patches,omitempty"`
|
||||
Source int `json:"source,omitempty" relayOut:"source,omitempty"`
|
||||
System int `json:"system_id,omitempty" relayOut:"system,omitempty"`
|
||||
Submitter *auth.UserID `json:"submitter,omitempty" relayOut:"submitter,omitempty"`
|
||||
Submitter *users.UserID `json:"submitter,omitempty" relayOut:"submitter,omitempty"`
|
||||
SystemLabel string `json:"system_name,omitempty" relayOut:"systemLabel,omitempty"`
|
||||
Talkgroup int `json:"tgid,omitempty" relayOut:"talkgroup,omitempty"`
|
||||
TalkgroupGroup *string `json:"talkgroupGroup,omitempty" relayOut:"talkgroupGroup,omitempty"`
|
||||
|
|
|
@ -44,6 +44,7 @@ const createIncident = `-- name: CreateIncident :one
|
|||
INSERT INTO incidents (
|
||||
id,
|
||||
name,
|
||||
owner,
|
||||
description,
|
||||
start_time,
|
||||
end_time,
|
||||
|
@ -56,14 +57,16 @@ INSERT INTO incidents (
|
|||
$4,
|
||||
$5,
|
||||
$6,
|
||||
$7
|
||||
$7,
|
||||
$8
|
||||
)
|
||||
RETURNING id, name, description, start_time, end_time, location, metadata
|
||||
RETURNING id, name, owner, description, start_time, end_time, location, metadata
|
||||
`
|
||||
|
||||
type CreateIncidentParams struct {
|
||||
ID uuid.UUID `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Owner int `json:"owner"`
|
||||
Description *string `json:"description"`
|
||||
StartTime pgtype.Timestamptz `json:"start_time"`
|
||||
EndTime pgtype.Timestamptz `json:"end_time"`
|
||||
|
@ -75,6 +78,7 @@ func (q *Queries) CreateIncident(ctx context.Context, arg CreateIncidentParams)
|
|||
row := q.db.QueryRow(ctx, createIncident,
|
||||
arg.ID,
|
||||
arg.Name,
|
||||
arg.Owner,
|
||||
arg.Description,
|
||||
arg.StartTime,
|
||||
arg.EndTime,
|
||||
|
@ -85,6 +89,7 @@ func (q *Queries) CreateIncident(ctx context.Context, arg CreateIncidentParams)
|
|||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.Name,
|
||||
&i.Owner,
|
||||
&i.Description,
|
||||
&i.StartTime,
|
||||
&i.EndTime,
|
||||
|
@ -107,6 +112,7 @@ const getIncident = `-- name: GetIncident :one
|
|||
SELECT
|
||||
i.id,
|
||||
i.name,
|
||||
i.owner,
|
||||
i.description,
|
||||
i.start_time,
|
||||
i.end_time,
|
||||
|
@ -122,6 +128,7 @@ func (q *Queries) GetIncident(ctx context.Context, id uuid.UUID) (Incident, erro
|
|||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.Name,
|
||||
&i.Owner,
|
||||
&i.Description,
|
||||
&i.StartTime,
|
||||
&i.EndTime,
|
||||
|
@ -262,6 +269,7 @@ const listIncidentsP = `-- name: ListIncidentsP :many
|
|||
SELECT
|
||||
i.id,
|
||||
i.name,
|
||||
i.owner,
|
||||
i.description,
|
||||
i.start_time,
|
||||
i.end_time,
|
||||
|
@ -299,6 +307,7 @@ type ListIncidentsPParams struct {
|
|||
type ListIncidentsPRow struct {
|
||||
ID uuid.UUID `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Owner int `json:"owner"`
|
||||
Description *string `json:"description"`
|
||||
StartTime pgtype.Timestamptz `json:"start_time"`
|
||||
EndTime pgtype.Timestamptz `json:"end_time"`
|
||||
|
@ -326,6 +335,7 @@ func (q *Queries) ListIncidentsP(ctx context.Context, arg ListIncidentsPParams)
|
|||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.Name,
|
||||
&i.Owner,
|
||||
&i.Description,
|
||||
&i.StartTime,
|
||||
&i.EndTime,
|
||||
|
@ -375,7 +385,7 @@ SET
|
|||
metadata = COALESCE($6, metadata)
|
||||
WHERE
|
||||
id = $7
|
||||
RETURNING id, name, description, start_time, end_time, location, metadata
|
||||
RETURNING id, name, owner, description, start_time, end_time, location, metadata
|
||||
`
|
||||
|
||||
type UpdateIncidentParams struct {
|
||||
|
@ -402,6 +412,7 @@ func (q *Queries) UpdateIncident(ctx context.Context, arg UpdateIncidentParams)
|
|||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.Name,
|
||||
&i.Owner,
|
||||
&i.Description,
|
||||
&i.StartTime,
|
||||
&i.EndTime,
|
||||
|
|
|
@ -58,6 +58,7 @@ type Call struct {
|
|||
type Incident struct {
|
||||
ID uuid.UUID `json:"id,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Owner int `json:"owner,omitempty"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
StartTime pgtype.Timestamptz `json:"start_time,omitempty"`
|
||||
EndTime pgtype.Timestamptz `json:"end_time,omitempty"`
|
||||
|
|
|
@ -10,6 +10,8 @@ import (
|
|||
"dynatron.me/x/stillbox/pkg/calls"
|
||||
"dynatron.me/x/stillbox/pkg/database"
|
||||
"dynatron.me/x/stillbox/pkg/incidents"
|
||||
"dynatron.me/x/stillbox/pkg/rbac"
|
||||
"dynatron.me/x/stillbox/pkg/users"
|
||||
"github.com/google/uuid"
|
||||
"github.com/jackc/pgx/v5"
|
||||
)
|
||||
|
@ -75,12 +77,19 @@ func (s *store) CreateIncident(ctx context.Context, inc incidents.Incident) (*in
|
|||
db := database.FromCtx(ctx)
|
||||
var dbInc database.Incident
|
||||
|
||||
// TODO: replace this with a real RBAC check
|
||||
owner := auth.UIDFrom(ctx)
|
||||
if owner == nil {
|
||||
return nil, rbac.ErrNotAuthorized
|
||||
}
|
||||
|
||||
id := uuid.New()
|
||||
|
||||
txErr := db.InTx(ctx, func(db database.Store) error {
|
||||
var err error
|
||||
dbInc, err = db.CreateIncident(ctx, database.CreateIncidentParams{
|
||||
ID: id,
|
||||
Owner: owner.Int(),
|
||||
Name: inc.Name,
|
||||
Description: inc.Description,
|
||||
StartTime: inc.StartTime.PGTypeTSTZ(),
|
||||
|
@ -228,7 +237,7 @@ func fromDBCalls(d []database.GetIncidentCallsRow) []incidents.IncidentCall {
|
|||
r := make([]incidents.IncidentCall, 0, len(d))
|
||||
for _, v := range d {
|
||||
dur := calls.CallDuration(time.Duration(common.ZeroIfNil(v.Duration)) * time.Millisecond)
|
||||
sub := common.PtrTo(auth.UserID(common.ZeroIfNil(v.Submitter)))
|
||||
sub := common.PtrTo(users.UserID(common.ZeroIfNil(v.Submitter)))
|
||||
r = append(r, incidents.IncidentCall{
|
||||
Call: calls.Call{
|
||||
ID: v.CallID,
|
||||
|
|
|
@ -1 +1,26 @@
|
|||
package rbac
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/el-mike/restrict/v2"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNotAuthorized = errors.New("not authorized")
|
||||
)
|
||||
|
||||
var policy = &restrict.PolicyDefinition{
|
||||
Roles: restrict.Roles{
|
||||
"User": {
|
||||
Grants: restrict.GrantsMap{
|
||||
"Conversation": {
|
||||
&restrict.Permission{Action: "read"},
|
||||
&restrict.Permission{Action: "create"},
|
||||
},
|
||||
},
|
||||
},
|
||||
"Guest": {},
|
||||
"Admin": {},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"net/url"
|
||||
|
||||
"dynatron.me/x/stillbox/internal/common"
|
||||
"dynatron.me/x/stillbox/pkg/rbac"
|
||||
"dynatron.me/x/stillbox/pkg/talkgroups/tgstore"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
|
@ -131,6 +132,7 @@ var statusMapping = map[error]errResponder{
|
|||
ErrBadUID: unauthErrText,
|
||||
ErrBadAppName: unauthErrText,
|
||||
common.ErrPageOutOfRange: badRequestErrText,
|
||||
rbac.ErrNotAuthorized: unauthErrText,
|
||||
}
|
||||
|
||||
func autoError(err error) render.Renderer {
|
||||
|
|
|
@ -47,9 +47,11 @@ func (s *Server) setupRoutes() {
|
|||
})
|
||||
|
||||
r.Group(func(r chi.Router) {
|
||||
// auth routes get rate-limited heavily, but not using middleware
|
||||
// auth/share routes get rate-limited heavily, but not using middleware
|
||||
s.rateLimit(r)
|
||||
r.Use(render.SetContentType(render.ContentTypeJSON))
|
||||
s.auth.PublicRoutes(r)
|
||||
// r.Mount("/share", s.share.ShareRouter(s.rest))
|
||||
})
|
||||
|
||||
r.Group(func(r chi.Router) {
|
||||
|
|
|
@ -30,7 +30,7 @@ func (s *service) Go(ctx context.Context) {
|
|||
|
||||
for {
|
||||
select {
|
||||
case <- tick.C:
|
||||
case <-tick.C:
|
||||
err := s.store.Prune(ctx)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("share prune failed")
|
||||
|
|
|
@ -13,10 +13,10 @@ import (
|
|||
|
||||
"dynatron.me/x/stillbox/internal/common"
|
||||
"dynatron.me/x/stillbox/internal/forms"
|
||||
"dynatron.me/x/stillbox/pkg/auth"
|
||||
"dynatron.me/x/stillbox/pkg/calls"
|
||||
"dynatron.me/x/stillbox/pkg/config"
|
||||
"dynatron.me/x/stillbox/pkg/sources"
|
||||
"dynatron.me/x/stillbox/pkg/users"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
@ -32,16 +32,16 @@ func TestRelay(t *testing.T) {
|
|||
|
||||
tests := []struct {
|
||||
name string
|
||||
submitter auth.UserID
|
||||
submitter users.UserID
|
||||
apiKey string
|
||||
call calls.Call
|
||||
}{
|
||||
{
|
||||
name: "base",
|
||||
submitter: auth.UserID(1),
|
||||
submitter: users.UserID(1),
|
||||
call: calls.Call{
|
||||
ID: uuid.UUID([16]byte{0x52, 0xfd, 0xfc, 0x07, 0x21, 0x82, 0x45, 0x4f, 0x96, 0x3f, 0x5f, 0x0f, 0x9a, 0x62, 0x1d, 0x72}),
|
||||
Submitter: common.PtrTo(auth.UserID(1)),
|
||||
Submitter: common.PtrTo(users.UserID(1)),
|
||||
System: 197,
|
||||
Talkgroup: 10101,
|
||||
DateTime: time.Date(2024, 11, 10, 23, 33, 02, 0, time.Local),
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"dynatron.me/x/stillbox/internal/forms"
|
||||
"dynatron.me/x/stillbox/pkg/auth"
|
||||
"dynatron.me/x/stillbox/pkg/calls"
|
||||
"dynatron.me/x/stillbox/pkg/users"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
@ -70,7 +71,7 @@ func (car *CallUploadRequest) mimeType() string {
|
|||
return ""
|
||||
}
|
||||
|
||||
func (car *CallUploadRequest) ToCall(submitter auth.UserID) (*calls.Call, error) {
|
||||
func (car *CallUploadRequest) ToCall(submitter users.UserID) (*calls.Call, error) {
|
||||
return calls.Make(&calls.Call{
|
||||
Submitter: &submitter,
|
||||
System: car.System,
|
||||
|
|
|
@ -528,7 +528,7 @@ func (t *cache) UpdateTG(ctx context.Context, input database.UpdateTalkgroupPara
|
|||
return oerr
|
||||
}
|
||||
versionBatch := db.StoreTGVersion(ctx, []database.StoreTGVersionParams{{
|
||||
Submitter: auth.UIDFrom(ctx),
|
||||
Submitter: auth.UIDFrom(ctx).Int32Ptr(),
|
||||
TGID: *input.TGID,
|
||||
}})
|
||||
defer versionBatch.Close()
|
||||
|
@ -578,7 +578,7 @@ func (t *cache) DeleteTG(ctx context.Context, id tgsp.ID) error {
|
|||
defer t.Unlock()
|
||||
|
||||
err := database.FromCtx(ctx).InTx(ctx, func(db database.Store) error {
|
||||
err := db.StoreDeletedTGVersion(ctx, common.PtrTo(int32(id.System)), common.PtrTo(int32(id.Talkgroup)), auth.UIDFrom(ctx))
|
||||
err := db.StoreDeletedTGVersion(ctx, common.PtrTo(int32(id.System)), common.PtrTo(int32(id.Talkgroup)), auth.UIDFrom(ctx).Int32Ptr())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -670,7 +670,7 @@ func (t *cache) UpsertTGs(ctx context.Context, system int, input []database.Upse
|
|||
versionParams = append(versionParams, database.StoreTGVersionParams{
|
||||
SystemID: int32(system),
|
||||
TGID: r.TGID,
|
||||
Submitter: auth.UIDFrom(ctx),
|
||||
Submitter: auth.UIDFrom(ctx).Int32Ptr(),
|
||||
})
|
||||
tgs = append(tgs, &tgsp.Talkgroup{
|
||||
Talkgroup: r,
|
||||
|
|
|
@ -14,11 +14,11 @@ type Store interface {
|
|||
SetUserPrefs(ctx context.Context, uid int32, appName string, prefs []byte) error
|
||||
}
|
||||
|
||||
type store struct {
|
||||
type postgresStore struct {
|
||||
}
|
||||
|
||||
func NewStore() *store {
|
||||
return new(store)
|
||||
func NewStore() *postgresStore {
|
||||
return new(postgresStore)
|
||||
}
|
||||
|
||||
type storeCtxKey string
|
||||
|
@ -38,7 +38,7 @@ func FromCtx(ctx context.Context) Store {
|
|||
return s
|
||||
}
|
||||
|
||||
func (s *store) UserPrefs(ctx context.Context, uid int32, appName string) ([]byte, error) {
|
||||
func (s *postgresStore) UserPrefs(ctx context.Context, uid int32, appName string) ([]byte, error) {
|
||||
db := database.FromCtx(ctx)
|
||||
|
||||
prefs, err := db.GetAppPrefs(ctx, appName, int(uid))
|
||||
|
@ -49,8 +49,10 @@ func (s *store) UserPrefs(ctx context.Context, uid int32, appName string) ([]byt
|
|||
return []byte(prefs), err
|
||||
}
|
||||
|
||||
func (s *store) SetUserPrefs(ctx context.Context, uid int32, appName string, prefs []byte) error {
|
||||
func (s *postgresStore) SetUserPrefs(ctx context.Context, uid int32, appName string, prefs []byte) error {
|
||||
db := database.FromCtx(ctx)
|
||||
|
||||
return db.SetAppPrefs(ctx, appName, prefs, int(uid))
|
||||
}
|
||||
|
||||
//func (s *postgresStore)
|
||||
|
|
30
pkg/users/user.go
Normal file
30
pkg/users/user.go
Normal file
|
@ -0,0 +1,30 @@
|
|||
package users
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
type UserID int
|
||||
|
||||
func (u *UserID) Int32Ptr() *int32 {
|
||||
if u == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
i := int32(*u)
|
||||
|
||||
return &i
|
||||
}
|
||||
|
||||
func (u UserID) Int() int {
|
||||
return int(u)
|
||||
}
|
||||
|
||||
type User struct {
|
||||
ID UserID
|
||||
Username string
|
||||
Password string
|
||||
Email string
|
||||
IsAdmin bool
|
||||
Prefs json.RawMessage
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
CREATE TABLE IF NOT EXISTS users(
|
||||
id INTEGER PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
|
||||
id INTEGER PRIMARY KEY GENERATED ALWAYS AS IDENTITY (START WITH 1),
|
||||
username VARCHAR (255) UNIQUE NOT NULL,
|
||||
password TEXT NOT NULL,
|
||||
email TEXT NOT NULL,
|
||||
|
@ -141,6 +141,7 @@ CREATE TABLE IF NOT EXISTS settings(
|
|||
CREATE TABLE IF NOT EXISTS incidents(
|
||||
id UUID PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
owner INTEGER NOT NULL,
|
||||
description TEXT,
|
||||
start_time TIMESTAMPTZ,
|
||||
end_time TIMESTAMPTZ,
|
||||
|
|
|
@ -33,6 +33,7 @@ WHERE incident_id = @incident_id AND call_id = @call_id;
|
|||
INSERT INTO incidents (
|
||||
id,
|
||||
name,
|
||||
owner,
|
||||
description,
|
||||
start_time,
|
||||
end_time,
|
||||
|
@ -41,6 +42,7 @@ INSERT INTO incidents (
|
|||
) VALUES (
|
||||
@id,
|
||||
@name,
|
||||
@owner,
|
||||
sqlc.narg('description'),
|
||||
sqlc.narg('start_time'),
|
||||
sqlc.narg('end_time'),
|
||||
|
@ -54,6 +56,7 @@ RETURNING *;
|
|||
SELECT
|
||||
i.id,
|
||||
i.name,
|
||||
i.owner,
|
||||
i.description,
|
||||
i.start_time,
|
||||
i.end_time,
|
||||
|
@ -148,6 +151,7 @@ ORDER BY ic.call_date ASC;
|
|||
SELECT
|
||||
i.id,
|
||||
i.name,
|
||||
i.owner,
|
||||
i.description,
|
||||
i.start_time,
|
||||
i.end_time,
|
||||
|
|
Loading…
Reference in a new issue