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/cpuguy83/go-md2man/v2 v2.0.5 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // 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/fsnotify/fsnotify v1.8.0 // indirect
github.com/go-audio/audio v1.0.0 // indirect github.com/go-audio/audio v1.0.0 // indirect
github.com/go-audio/riff 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/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 h1:FxWPpzIjnTlhPwqqXc4/vE0f7GvRjuAsbW+HOIe8KnA=
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de/go.mod h1:DCaWoUhZrYW9p1lxo/cm8EmUOOzAPSEZNGF2DK1dJgw= 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 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 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= 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-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 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= 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 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= 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/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 h1:JML/k+t4tpHCpQTCAD62Nu43NUFzHY4CV3uAuvHGC+Y=
github.com/golang-migrate/migrate/v4 v4.18.1/go.mod h1:HAX6m3sQgcdO81tdjn5exv20+3Kb13cmGli1hrD6hks= 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 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 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 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= 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.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 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= 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.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.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.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.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 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 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.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 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= 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 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ=
golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= 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= 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-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 h1:23H/YlmTHfmmvpZ+ajKZL0qLz0+IwFOIqQA0mQbmLeM=
golang.org/x/mobile v0.0.0-20241108191957-fa514ef75a0f/go.mod h1:UbSUP4uu/C9hw9R2CkojhXlAxvayHjBdU9aRvE+c1To= 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 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ=
golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 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-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-20190429190828-d89cdac9e872/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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.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 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug=
golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= 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 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io=
google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= 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= 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/auth"
"dynatron.me/x/stillbox/pkg/calls" "dynatron.me/x/stillbox/pkg/calls"
"dynatron.me/x/stillbox/pkg/sources" "dynatron.me/x/stillbox/pkg/sources"
"dynatron.me/x/stillbox/pkg/users"
"github.com/google/uuid" "github.com/google/uuid"
) )
@ -62,16 +63,16 @@ func TestMarshal(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
submitter auth.UserID submitter users.UserID
apiKey string apiKey string
call calls.Call call calls.Call
}{ }{
{ {
name: "base", name: "base",
submitter: auth.UserID(1), submitter: users.UserID(1),
call: calls.Call{ call: calls.Call{
ID: uuid.UUID([16]byte{0x52, 0xfd, 0xfc, 0x07, 0x21, 0x82, 0x45, 0x4f, 0x96, 0x3f, 0x5f, 0x0f, 0x9a, 0x62, 0x1d, 0x72}), 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, System: 197,
Talkgroup: 10101, Talkgroup: 10101,
DateTime: time.Date(2024, 11, 10, 23, 33, 02, 0, time.Local), DateTime: time.Date(2024, 11, 10, 23, 33, 02, 0, time.Local),

View file

@ -7,18 +7,19 @@ import (
"time" "time"
"dynatron.me/x/stillbox/pkg/database" "dynatron.me/x/stillbox/pkg/database"
"dynatron.me/x/stillbox/pkg/users"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )
type apiKeyAuth interface { 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. // 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) keyUuid, err := uuid.Parse(key)
if err != nil { if err != nil {
log.Error().Str("apikey", key).Msg("cannot parse key") 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 return nil, ErrUnauthorized
} }
owner := UserID(apik.Owner) owner := users.UserID(apik.Owner)
return &owner, nil return &owner, nil
} }

View file

@ -13,18 +13,6 @@ import (
"github.com/go-chi/jwtauth/v5" "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. // Authenticator performs API key and user JWT authentication.
type Authenticator interface { type Authenticator interface {
jwtAuth jwtAuth

View file

@ -11,6 +11,7 @@ import (
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
"dynatron.me/x/stillbox/pkg/database" "dynatron.me/x/stillbox/pkg/database"
"dynatron.me/x/stillbox/pkg/users"
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
"github.com/go-chi/jwtauth/v5" "github.com/go-chi/jwtauth/v5"
@ -44,7 +45,8 @@ type jwtAuth interface {
type claims map[string]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) tok, _, err := jwtauth.FromContext(ctx)
if err != nil { if err != nil {
return nil return nil
@ -56,7 +58,7 @@ func UIDFrom(ctx context.Context) *int32 {
return nil return nil
} }
uid := int32(uidInt) uid := users.UserID(int32(uidInt))
return &uid return &uid
} }

View file

@ -7,9 +7,9 @@ import (
"dynatron.me/x/stillbox/internal/audio" "dynatron.me/x/stillbox/internal/audio"
"dynatron.me/x/stillbox/internal/jsontypes" "dynatron.me/x/stillbox/internal/jsontypes"
"dynatron.me/x/stillbox/pkg/auth"
"dynatron.me/x/stillbox/pkg/pb" "dynatron.me/x/stillbox/pkg/pb"
"dynatron.me/x/stillbox/pkg/talkgroups" "dynatron.me/x/stillbox/pkg/talkgroups"
"dynatron.me/x/stillbox/pkg/users"
"github.com/google/uuid" "github.com/google/uuid"
"google.golang.org/protobuf/types/known/timestamppb" "google.golang.org/protobuf/types/known/timestamppb"
@ -52,23 +52,23 @@ type CallAudio struct {
// further transformation. relayOut exists for compatibility with http // further transformation. relayOut exists for compatibility with http
// source CallUploadRequest as used in the relay sink. // source CallUploadRequest as used in the relay sink.
type Call struct { type Call struct {
ID uuid.UUID `json:"id" relayOut:"id"` ID uuid.UUID `json:"id" relayOut:"id"`
Audio []byte `json:"audio,omitempty" relayOut:"audio,omitempty" filenameField:"AudioName"` Audio []byte `json:"audio,omitempty" relayOut:"audio,omitempty" filenameField:"AudioName"`
AudioName string `json:"audioName,omitempty" relayOut:"audioName,omitempty"` AudioName string `json:"audioName,omitempty" relayOut:"audioName,omitempty"`
AudioType string `json:"audioType,omitempty" relayOut:"audioType,omitempty"` AudioType string `json:"audioType,omitempty" relayOut:"audioType,omitempty"`
Duration CallDuration `json:"duration,omitempty" relayOut:"duration,omitempty"` Duration CallDuration `json:"duration,omitempty" relayOut:"duration,omitempty"`
DateTime time.Time `json:"call_date,omitempty" relayOut:"dateTime,omitempty"` DateTime time.Time `json:"call_date,omitempty" relayOut:"dateTime,omitempty"`
Frequencies []int `json:"frequencies,omitempty" relayOut:"frequencies,omitempty"` Frequencies []int `json:"frequencies,omitempty" relayOut:"frequencies,omitempty"`
Frequency int `json:"frequency,omitempty" relayOut:"frequency,omitempty"` Frequency int `json:"frequency,omitempty" relayOut:"frequency,omitempty"`
Patches []int `json:"patches,omitempty" relayOut:"patches,omitempty"` Patches []int `json:"patches,omitempty" relayOut:"patches,omitempty"`
Source int `json:"source,omitempty" relayOut:"source,omitempty"` Source int `json:"source,omitempty" relayOut:"source,omitempty"`
System int `json:"system_id,omitempty" relayOut:"system,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"` SystemLabel string `json:"system_name,omitempty" relayOut:"systemLabel,omitempty"`
Talkgroup int `json:"tgid,omitempty" relayOut:"talkgroup,omitempty"` Talkgroup int `json:"tgid,omitempty" relayOut:"talkgroup,omitempty"`
TalkgroupGroup *string `json:"talkgroupGroup,omitempty" relayOut:"talkgroupGroup,omitempty"` TalkgroupGroup *string `json:"talkgroupGroup,omitempty" relayOut:"talkgroupGroup,omitempty"`
TalkgroupLabel *string `json:"talkgroupLabel,omitempty" relayOut:"talkgroupLabel,omitempty"` TalkgroupLabel *string `json:"talkgroupLabel,omitempty" relayOut:"talkgroupLabel,omitempty"`
TGAlphaTag *string `json:"tg_name,omitempty" relayOut:"talkgroupTag,omitempty"` TGAlphaTag *string `json:"tg_name,omitempty" relayOut:"talkgroupTag,omitempty"`
shouldStore bool `json:"-"` shouldStore bool `json:"-"`
} }

View file

@ -44,6 +44,7 @@ const createIncident = `-- name: CreateIncident :one
INSERT INTO incidents ( INSERT INTO incidents (
id, id,
name, name,
owner,
description, description,
start_time, start_time,
end_time, end_time,
@ -56,14 +57,16 @@ INSERT INTO incidents (
$4, $4,
$5, $5,
$6, $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 { type CreateIncidentParams struct {
ID uuid.UUID `json:"id"` ID uuid.UUID `json:"id"`
Name string `json:"name"` Name string `json:"name"`
Owner int `json:"owner"`
Description *string `json:"description"` Description *string `json:"description"`
StartTime pgtype.Timestamptz `json:"start_time"` StartTime pgtype.Timestamptz `json:"start_time"`
EndTime pgtype.Timestamptz `json:"end_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, row := q.db.QueryRow(ctx, createIncident,
arg.ID, arg.ID,
arg.Name, arg.Name,
arg.Owner,
arg.Description, arg.Description,
arg.StartTime, arg.StartTime,
arg.EndTime, arg.EndTime,
@ -85,6 +89,7 @@ func (q *Queries) CreateIncident(ctx context.Context, arg CreateIncidentParams)
err := row.Scan( err := row.Scan(
&i.ID, &i.ID,
&i.Name, &i.Name,
&i.Owner,
&i.Description, &i.Description,
&i.StartTime, &i.StartTime,
&i.EndTime, &i.EndTime,
@ -107,6 +112,7 @@ const getIncident = `-- name: GetIncident :one
SELECT SELECT
i.id, i.id,
i.name, i.name,
i.owner,
i.description, i.description,
i.start_time, i.start_time,
i.end_time, i.end_time,
@ -122,6 +128,7 @@ func (q *Queries) GetIncident(ctx context.Context, id uuid.UUID) (Incident, erro
err := row.Scan( err := row.Scan(
&i.ID, &i.ID,
&i.Name, &i.Name,
&i.Owner,
&i.Description, &i.Description,
&i.StartTime, &i.StartTime,
&i.EndTime, &i.EndTime,
@ -262,6 +269,7 @@ const listIncidentsP = `-- name: ListIncidentsP :many
SELECT SELECT
i.id, i.id,
i.name, i.name,
i.owner,
i.description, i.description,
i.start_time, i.start_time,
i.end_time, i.end_time,
@ -299,6 +307,7 @@ type ListIncidentsPParams struct {
type ListIncidentsPRow struct { type ListIncidentsPRow struct {
ID uuid.UUID `json:"id"` ID uuid.UUID `json:"id"`
Name string `json:"name"` Name string `json:"name"`
Owner int `json:"owner"`
Description *string `json:"description"` Description *string `json:"description"`
StartTime pgtype.Timestamptz `json:"start_time"` StartTime pgtype.Timestamptz `json:"start_time"`
EndTime pgtype.Timestamptz `json:"end_time"` EndTime pgtype.Timestamptz `json:"end_time"`
@ -326,6 +335,7 @@ func (q *Queries) ListIncidentsP(ctx context.Context, arg ListIncidentsPParams)
if err := rows.Scan( if err := rows.Scan(
&i.ID, &i.ID,
&i.Name, &i.Name,
&i.Owner,
&i.Description, &i.Description,
&i.StartTime, &i.StartTime,
&i.EndTime, &i.EndTime,
@ -375,7 +385,7 @@ SET
metadata = COALESCE($6, metadata) metadata = COALESCE($6, metadata)
WHERE WHERE
id = $7 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 { type UpdateIncidentParams struct {
@ -402,6 +412,7 @@ func (q *Queries) UpdateIncident(ctx context.Context, arg UpdateIncidentParams)
err := row.Scan( err := row.Scan(
&i.ID, &i.ID,
&i.Name, &i.Name,
&i.Owner,
&i.Description, &i.Description,
&i.StartTime, &i.StartTime,
&i.EndTime, &i.EndTime,

View file

@ -58,6 +58,7 @@ type Call struct {
type Incident struct { type Incident struct {
ID uuid.UUID `json:"id,omitempty"` ID uuid.UUID `json:"id,omitempty"`
Name string `json:"name,omitempty"` Name string `json:"name,omitempty"`
Owner int `json:"owner,omitempty"`
Description *string `json:"description,omitempty"` Description *string `json:"description,omitempty"`
StartTime pgtype.Timestamptz `json:"start_time,omitempty"` StartTime pgtype.Timestamptz `json:"start_time,omitempty"`
EndTime pgtype.Timestamptz `json:"end_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/calls"
"dynatron.me/x/stillbox/pkg/database" "dynatron.me/x/stillbox/pkg/database"
"dynatron.me/x/stillbox/pkg/incidents" "dynatron.me/x/stillbox/pkg/incidents"
"dynatron.me/x/stillbox/pkg/rbac"
"dynatron.me/x/stillbox/pkg/users"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5"
) )
@ -75,12 +77,19 @@ func (s *store) CreateIncident(ctx context.Context, inc incidents.Incident) (*in
db := database.FromCtx(ctx) db := database.FromCtx(ctx)
var dbInc database.Incident 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() id := uuid.New()
txErr := db.InTx(ctx, func(db database.Store) error { txErr := db.InTx(ctx, func(db database.Store) error {
var err error var err error
dbInc, err = db.CreateIncident(ctx, database.CreateIncidentParams{ dbInc, err = db.CreateIncident(ctx, database.CreateIncidentParams{
ID: id, ID: id,
Owner: owner.Int(),
Name: inc.Name, Name: inc.Name,
Description: inc.Description, Description: inc.Description,
StartTime: inc.StartTime.PGTypeTSTZ(), StartTime: inc.StartTime.PGTypeTSTZ(),
@ -228,7 +237,7 @@ func fromDBCalls(d []database.GetIncidentCallsRow) []incidents.IncidentCall {
r := make([]incidents.IncidentCall, 0, len(d)) r := make([]incidents.IncidentCall, 0, len(d))
for _, v := range d { for _, v := range d {
dur := calls.CallDuration(time.Duration(common.ZeroIfNil(v.Duration)) * time.Millisecond) 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{ r = append(r, incidents.IncidentCall{
Call: calls.Call{ Call: calls.Call{
ID: v.CallID, ID: v.CallID,

View file

@ -1 +1,26 @@
package rbac 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" "net/url"
"dynatron.me/x/stillbox/internal/common" "dynatron.me/x/stillbox/internal/common"
"dynatron.me/x/stillbox/pkg/rbac"
"dynatron.me/x/stillbox/pkg/talkgroups/tgstore" "dynatron.me/x/stillbox/pkg/talkgroups/tgstore"
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
@ -131,6 +132,7 @@ var statusMapping = map[error]errResponder{
ErrBadUID: unauthErrText, ErrBadUID: unauthErrText,
ErrBadAppName: unauthErrText, ErrBadAppName: unauthErrText,
common.ErrPageOutOfRange: badRequestErrText, common.ErrPageOutOfRange: badRequestErrText,
rbac.ErrNotAuthorized: unauthErrText,
} }
func autoError(err error) render.Renderer { func autoError(err error) render.Renderer {

View file

@ -47,9 +47,11 @@ func (s *Server) setupRoutes() {
}) })
r.Group(func(r chi.Router) { 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)) r.Use(render.SetContentType(render.ContentTypeJSON))
s.auth.PublicRoutes(r) s.auth.PublicRoutes(r)
// r.Mount("/share", s.share.ShareRouter(s.rest))
}) })
r.Group(func(r chi.Router) { r.Group(func(r chi.Router) {

View file

@ -30,7 +30,7 @@ func (s *service) Go(ctx context.Context) {
for { for {
select { select {
case <- tick.C: case <-tick.C:
err := s.store.Prune(ctx) err := s.store.Prune(ctx)
if err != nil { if err != nil {
log.Error().Err(err).Msg("share prune failed") log.Error().Err(err).Msg("share prune failed")

View file

@ -18,15 +18,15 @@ type EntityType string
const ( const (
EntityIncident EntityType = "incident" EntityIncident EntityType = "incident"
EntityCall EntityType = "call" EntityCall EntityType = "call"
) )
// If an incident is shared, all calls that are part of it must be shared too, but this can be through the incident share (/share/bLaH/callID[.mp3]) // If an incident is shared, all calls that are part of it must be shared too, but this can be through the incident share (/share/bLaH/callID[.mp3])
type Share struct { type Share struct {
ID string `json:"id"` ID string `json:"id"`
Type EntityType `json:"entityType"` Type EntityType `json:"entityType"`
EntityID uuid.UUID `json:"entityID"` EntityID uuid.UUID `json:"entityID"`
Expiration *jsontypes.Time `json:"expiration"` Expiration *jsontypes.Time `json:"expiration"`
} }
@ -46,9 +46,9 @@ func (s *service) NewShare(ctx context.Context, shType EntityType, shID uuid.UUI
} }
share := &Share{ share := &Share{
ID: id, ID: id,
Type: shType, Type: shType,
EntityID: shID, EntityID: shID,
Expiration: expT, Expiration: expT,
} }

View file

@ -26,9 +26,9 @@ type postgresStore struct {
func recToShare(share database.Share) *Share { func recToShare(share database.Share) *Share {
return &Share{ return &Share{
ID: share.ID, ID: share.ID,
Type: EntityType(share.EntityType), Type: EntityType(share.EntityType),
EntityID: share.EntityID, EntityID: share.EntityID,
Expiration: jsontypes.TimePtrFromTSTZ(share.Expiration), Expiration: jsontypes.TimePtrFromTSTZ(share.Expiration),
} }
} }
@ -46,9 +46,9 @@ func (s *postgresStore) Get(ctx context.Context, id string) (*Share, error) {
func (s *postgresStore) Create(ctx context.Context, share *Share) error { func (s *postgresStore) Create(ctx context.Context, share *Share) error {
db := database.FromCtx(ctx) db := database.FromCtx(ctx)
err := db.CreateShare(ctx, database.CreateShareParams{ err := db.CreateShare(ctx, database.CreateShareParams{
ID: share.ID, ID: share.ID,
EntityType: string(share.Type), EntityType: string(share.Type),
EntityID: share.EntityID, EntityID: share.EntityID,
Expiration: share.Expiration.PGTypeTSTZ(), Expiration: share.Expiration.PGTypeTSTZ(),
}) })

View file

@ -13,10 +13,10 @@ import (
"dynatron.me/x/stillbox/internal/common" "dynatron.me/x/stillbox/internal/common"
"dynatron.me/x/stillbox/internal/forms" "dynatron.me/x/stillbox/internal/forms"
"dynatron.me/x/stillbox/pkg/auth"
"dynatron.me/x/stillbox/pkg/calls" "dynatron.me/x/stillbox/pkg/calls"
"dynatron.me/x/stillbox/pkg/config" "dynatron.me/x/stillbox/pkg/config"
"dynatron.me/x/stillbox/pkg/sources" "dynatron.me/x/stillbox/pkg/sources"
"dynatron.me/x/stillbox/pkg/users"
"github.com/google/uuid" "github.com/google/uuid"
) )
@ -32,16 +32,16 @@ func TestRelay(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
submitter auth.UserID submitter users.UserID
apiKey string apiKey string
call calls.Call call calls.Call
}{ }{
{ {
name: "base", name: "base",
submitter: auth.UserID(1), submitter: users.UserID(1),
call: calls.Call{ call: calls.Call{
ID: uuid.UUID([16]byte{0x52, 0xfd, 0xfc, 0x07, 0x21, 0x82, 0x45, 0x4f, 0x96, 0x3f, 0x5f, 0x0f, 0x9a, 0x62, 0x1d, 0x72}), 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, System: 197,
Talkgroup: 10101, Talkgroup: 10101,
DateTime: time.Date(2024, 11, 10, 23, 33, 02, 0, time.Local), 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/internal/forms"
"dynatron.me/x/stillbox/pkg/auth" "dynatron.me/x/stillbox/pkg/auth"
"dynatron.me/x/stillbox/pkg/calls" "dynatron.me/x/stillbox/pkg/calls"
"dynatron.me/x/stillbox/pkg/users"
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )
@ -70,7 +71,7 @@ func (car *CallUploadRequest) mimeType() string {
return "" 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{ return calls.Make(&calls.Call{
Submitter: &submitter, Submitter: &submitter,
System: car.System, System: car.System,

View file

@ -528,7 +528,7 @@ func (t *cache) UpdateTG(ctx context.Context, input database.UpdateTalkgroupPara
return oerr return oerr
} }
versionBatch := db.StoreTGVersion(ctx, []database.StoreTGVersionParams{{ versionBatch := db.StoreTGVersion(ctx, []database.StoreTGVersionParams{{
Submitter: auth.UIDFrom(ctx), Submitter: auth.UIDFrom(ctx).Int32Ptr(),
TGID: *input.TGID, TGID: *input.TGID,
}}) }})
defer versionBatch.Close() defer versionBatch.Close()
@ -578,7 +578,7 @@ func (t *cache) DeleteTG(ctx context.Context, id tgsp.ID) error {
defer t.Unlock() defer t.Unlock()
err := database.FromCtx(ctx).InTx(ctx, func(db database.Store) error { 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 { if err != nil {
return err return err
} }
@ -670,7 +670,7 @@ func (t *cache) UpsertTGs(ctx context.Context, system int, input []database.Upse
versionParams = append(versionParams, database.StoreTGVersionParams{ versionParams = append(versionParams, database.StoreTGVersionParams{
SystemID: int32(system), SystemID: int32(system),
TGID: r.TGID, TGID: r.TGID,
Submitter: auth.UIDFrom(ctx), Submitter: auth.UIDFrom(ctx).Int32Ptr(),
}) })
tgs = append(tgs, &tgsp.Talkgroup{ tgs = append(tgs, &tgsp.Talkgroup{
Talkgroup: r, Talkgroup: r,

View file

@ -14,11 +14,11 @@ type Store interface {
SetUserPrefs(ctx context.Context, uid int32, appName string, prefs []byte) error SetUserPrefs(ctx context.Context, uid int32, appName string, prefs []byte) error
} }
type store struct { type postgresStore struct {
} }
func NewStore() *store { func NewStore() *postgresStore {
return new(store) return new(postgresStore)
} }
type storeCtxKey string type storeCtxKey string
@ -38,7 +38,7 @@ func FromCtx(ctx context.Context) Store {
return s 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) db := database.FromCtx(ctx)
prefs, err := db.GetAppPrefs(ctx, appName, int(uid)) 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 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) db := database.FromCtx(ctx)
return db.SetAppPrefs(ctx, appName, prefs, int(uid)) 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( 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, username VARCHAR (255) UNIQUE NOT NULL,
password TEXT NOT NULL, password TEXT NOT NULL,
email TEXT NOT NULL, email TEXT NOT NULL,
@ -141,6 +141,7 @@ CREATE TABLE IF NOT EXISTS settings(
CREATE TABLE IF NOT EXISTS incidents( CREATE TABLE IF NOT EXISTS incidents(
id UUID PRIMARY KEY, id UUID PRIMARY KEY,
name TEXT NOT NULL, name TEXT NOT NULL,
owner INTEGER NOT NULL,
description TEXT, description TEXT,
start_time TIMESTAMPTZ, start_time TIMESTAMPTZ,
end_time TIMESTAMPTZ, end_time TIMESTAMPTZ,

View file

@ -33,6 +33,7 @@ WHERE incident_id = @incident_id AND call_id = @call_id;
INSERT INTO incidents ( INSERT INTO incidents (
id, id,
name, name,
owner,
description, description,
start_time, start_time,
end_time, end_time,
@ -41,6 +42,7 @@ INSERT INTO incidents (
) VALUES ( ) VALUES (
@id, @id,
@name, @name,
@owner,
sqlc.narg('description'), sqlc.narg('description'),
sqlc.narg('start_time'), sqlc.narg('start_time'),
sqlc.narg('end_time'), sqlc.narg('end_time'),
@ -54,6 +56,7 @@ RETURNING *;
SELECT SELECT
i.id, i.id,
i.name, i.name,
i.owner,
i.description, i.description,
i.start_time, i.start_time,
i.end_time, i.end_time,
@ -148,6 +151,7 @@ ORDER BY ic.call_date ASC;
SELECT SELECT
i.id, i.id,
i.name, i.name,
i.owner,
i.description, i.description,
i.start_time, i.start_time,
i.end_time, i.end_time,