RBAC #102

Merged
amigan merged 6 commits from rbac into trunk 2025-01-18 17:22:09 -05:00
23 changed files with 170 additions and 71 deletions
Showing only changes of commit 40f10ce514 - Show all commits

1
go.mod
View file

@ -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
View file

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

View file

@ -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),

View file

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

View file

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

View file

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

View file

@ -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"`

View file

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

View file

@ -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"`

View file

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

View file

@ -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": {},
},
}

View file

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

View file

@ -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) {

View file

@ -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")

View file

@ -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),

View file

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

View file

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

View file

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

View file

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

View file

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