(WIP) Refresh & Access
This commit is contained in:
parent
cd97b6262f
commit
377903f7a1
BIN
cmd/imagini.db
BIN
cmd/imagini.db
Binary file not shown.
@ -20,15 +20,15 @@ type Server struct {
|
||||
}
|
||||
|
||||
func NewServer() *Server {
|
||||
config := config.Load()
|
||||
db := db.NewMgr(config)
|
||||
auth := auth.NewMgr(db)
|
||||
c := config.Load()
|
||||
db := db.NewMgr(c)
|
||||
auth := auth.NewMgr(db, c)
|
||||
api := api.NewApi(db, auth)
|
||||
|
||||
return &Server{
|
||||
API: api,
|
||||
Auth: auth,
|
||||
Config: config,
|
||||
Config: c,
|
||||
Database: db,
|
||||
}
|
||||
}
|
||||
|
2
go.mod
2
go.mod
@ -5,6 +5,8 @@ go 1.15
|
||||
require (
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
||||
github.com/disintegration/imaging v1.6.2 // indirect
|
||||
github.com/google/uuid v1.1.5
|
||||
github.com/lestrrat-go/jwx v1.0.8
|
||||
github.com/mattn/go-sqlite3 v1.14.6
|
||||
github.com/sirupsen/logrus v1.7.0
|
||||
github.com/tus/tusd v1.4.0
|
||||
|
30
go.sum
30
go.sum
@ -48,6 +48,8 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/uuid v1.1.5 h1:kxhtnfFVi+rYdOALN0B3k9UT86zVJKfBimRaciULW4I=
|
||||
github.com/google/uuid v1.1.5/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
@ -72,6 +74,17 @@ github.com/klauspost/compress v1.10.3 h1:OP96hzwJVBIHYU52pVTI6CczrxPvrGfgqF9N5eT
|
||||
github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/lestrrat-go/backoff/v2 v2.0.3 h1:2ABaTa5ifB1L90aoRMjaPa97p0WzzVe93Vggv8oZftw=
|
||||
github.com/lestrrat-go/backoff/v2 v2.0.3/go.mod h1:mU93bMXuG27/Y5erI5E9weqavpTX5qiVFZI4uXAX0xk=
|
||||
github.com/lestrrat-go/httpcc v0.0.0-20210101035852-e7e8fea419e3 h1:e52qvXxpJPV/Kb2ovtuYgcRFjNmf9ntcn8BPIbpRM4k=
|
||||
github.com/lestrrat-go/httpcc v0.0.0-20210101035852-e7e8fea419e3/go.mod h1:tGS/u00Vh5N6FHNkExqGGNId8e0Big+++0Gf8MBnAvE=
|
||||
github.com/lestrrat-go/iter v0.0.0-20200422075355-fc1769541911 h1:FvnrqecqX4zT0wOIbYK1gNgTm0677INEWiFY8UEYggY=
|
||||
github.com/lestrrat-go/iter v0.0.0-20200422075355-fc1769541911/go.mod h1:zIdgO1mRKhn8l9vrZJZz9TUMMFbQbLeTsbqPDrJ/OJc=
|
||||
github.com/lestrrat-go/jwx v1.0.8 h1:Mj/2Ey9rkGx4w5IMQ2Q+9KLZn4cZoMgKrnMxi9eXE3k=
|
||||
github.com/lestrrat-go/jwx v1.0.8/go.mod h1:6XJ5sxHF5U116AxYxeHfTnfsZRMgmeKY214zwZDdvho=
|
||||
github.com/lestrrat-go/option v0.0.0-20210103042652-6f1ecfceda35 h1:lea8Wt+1ePkVrI2/WD+NgQT5r/XsLAzxeqtyFLcEs10=
|
||||
github.com/lestrrat-go/option v0.0.0-20210103042652-6f1ecfceda35/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
|
||||
github.com/lestrrat-go/pdebug/v3 v3.0.0-20210111091911-ec4f5c88c087/go.mod h1:za+m+Ve24yCxTEhR59N7UlnJomWwCiIqbJRmKeiADU4=
|
||||
github.com/mattn/go-sqlite3 v1.14.5/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI=
|
||||
github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg=
|
||||
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||
@ -83,6 +96,8 @@ github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uY
|
||||
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
@ -112,6 +127,8 @@ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/tus/tusd v1.4.0 h1:8+TmY/Ip2mxO3UlYzrqUkQWMd/FmSSdcIrlJHrr+3HQ=
|
||||
github.com/tus/tusd v1.4.0/go.mod h1:ygrT4B9ZSb27dx3uTnobX5nOFDnutBL6iWKLH4+KpA0=
|
||||
github.com/unknwon/com v0.0.0-20190804042917-757f69c95f3e h1:GSGeB9EAKY2spCABz6xOX5DbxZEXolK+nBSvmsQwRjM=
|
||||
@ -121,6 +138,7 @@ github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtX
|
||||
github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M=
|
||||
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
|
||||
github.com/vimeo/go-util v1.2.0/go.mod h1:s13SMDTSO7AjH1nbgp707mfN5JFIWUFDU5MDDuRRtKs=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
@ -130,6 +148,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90Pveol
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc=
|
||||
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20201217014255-9d1352758620/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY=
|
||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
@ -142,6 +162,7 @@ golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvx
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@ -150,6 +171,8 @@ golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73r
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@ -158,6 +181,7 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@ -182,6 +206,11 @@ golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200417140056-c07e33ef3290/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.6.0/go.mod h1:btoxGiFvQNVUZQ8W08zLtrVS08CNpINPEfxXxgJL1Q4=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
@ -210,6 +239,7 @@ gopkg.in/macaron.v1 v1.4.0/go.mod h1:uMZCFccv9yr5TipIalVOyAyZQuOH3OkmXvgcWwhJuP4
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gorm.io/driver/sqlite v1.1.4 h1:PDzwYE+sI6De2+mxAneV9Xs11+ZyKV6oxD3wDGkaNvM=
|
||||
gorm.io/driver/sqlite v1.1.4/go.mod h1:mJCeTFr7+crvS+TRnWc5Z3UvwxUN1BGBLMrf5LA9DYw=
|
||||
gorm.io/gorm v1.20.7/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
|
||||
|
@ -16,8 +16,8 @@ type API struct {
|
||||
func NewApi(db *db.DBManager, auth *auth.AuthManager) *API {
|
||||
api := &API{
|
||||
Router: http.NewServeMux(),
|
||||
DB: db,
|
||||
Auth: auth,
|
||||
DB: db,
|
||||
}
|
||||
api.registerRoutes()
|
||||
return api
|
||||
|
@ -35,22 +35,25 @@ func (api *API) loginHandler(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: Is user already logged in? If so refresh token, if different user, kill session and log in new user?
|
||||
|
||||
// Do login
|
||||
resp := api.Auth.AuthenticateUser(creds)
|
||||
if resp == true {
|
||||
// Return Success
|
||||
cookie := http.Cookie{
|
||||
Name: "Token",
|
||||
Value: "testToken",
|
||||
}
|
||||
http.SetCookie(w, &cookie)
|
||||
successJSON(w, "Login success.", http.StatusOK)
|
||||
}else {
|
||||
// Return Failure
|
||||
if !resp {
|
||||
errorJSON(w, "Invalid credentials.", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
// Create tokens
|
||||
accessToken := api.Auth.CreateJWTAccessToken()
|
||||
refreshToken := api.Auth.CreateRefreshToken()
|
||||
|
||||
// Set appropriate cookies
|
||||
accessCookie := http.Cookie{Name: "AccessToken", Value: accessToken}
|
||||
refreshCookie := http.Cookie{Name: "RefreshToken", Value: refreshToken}
|
||||
http.SetCookie(w, &accessCookie)
|
||||
http.SetCookie(w, &refreshCookie)
|
||||
|
||||
// Response success
|
||||
successJSON(w, "Login success.", http.StatusOK)
|
||||
}
|
||||
|
||||
func (api *API) logoutHandler(w http.ResponseWriter, r *http.Request) {
|
||||
@ -73,3 +76,20 @@ func (api *API) logoutHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
http.SetCookie(w, cookie)
|
||||
}
|
||||
|
||||
func (api *API) refreshLoginHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ok := api.Auth.ValidateRefreshToken()
|
||||
if !ok {
|
||||
// TODO: Clear Access & Refresh Cookies
|
||||
errorJSON(w, "Invalid credentials.", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
// Update token
|
||||
accessToken := api.Auth.CreateJWTAccessToken()
|
||||
accessCookie := http.Cookie{Name: "AccessToken", Value: accessToken}
|
||||
http.SetCookie(w, &accessCookie)
|
||||
|
||||
// Response success
|
||||
successJSON(w, "Refresh success.", http.StatusOK)
|
||||
}
|
||||
|
@ -2,13 +2,14 @@ package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type Middleware func(http.Handler) http.Handler
|
||||
type Middleware func(http.Handler) http.HandlerFunc
|
||||
|
||||
func MultipleMiddleware(h http.Handler, m ...Middleware) http.Handler {
|
||||
func multipleMiddleware(h http.HandlerFunc, m ...Middleware) http.HandlerFunc {
|
||||
if len(m) < 1 {
|
||||
return h
|
||||
}
|
||||
@ -19,19 +20,33 @@ func MultipleMiddleware(h http.Handler, m ...Middleware) http.Handler {
|
||||
return wrapped
|
||||
}
|
||||
|
||||
// func authMiddleware(h http.Handler) http.Handler {
|
||||
// return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// _, ok := ValidateUserToken(r)
|
||||
//
|
||||
// if ok {
|
||||
func (api *API) authMiddleware(next http.Handler) http.HandlerFunc {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
cookie, err := r.Cookie("Token")
|
||||
if err != nil {
|
||||
log.Warn("[middleware] Cookie not found")
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
// Validate cookie.Value JWT with
|
||||
api.Auth.ValidateJWTToken(cookie.Value)
|
||||
|
||||
|
||||
log.Info("[middleware] Cookie Name: ", cookie.Name)
|
||||
log.Info("[middleware] Cookie Value: ", cookie.Value)
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
|
||||
// if true {
|
||||
// next.ServeHTTP(w, r)
|
||||
// } else {
|
||||
// w.WriteHeader(http.StatusUnauthorized)
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
})
|
||||
}
|
||||
|
||||
func logMiddleware(h http.Handler) http.Handler {
|
||||
func (api *API) logMiddleware(h http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
log.SetOutput(os.Stdout)
|
||||
log.Println(r.Method, r.URL)
|
||||
|
@ -6,15 +6,38 @@ import (
|
||||
)
|
||||
|
||||
func (api *API) registerRoutes() {
|
||||
api.Router.HandleFunc("/MediaItems", api.mediaItemsHandler)
|
||||
api.Router.HandleFunc("/Upload", api.uploadHandler)
|
||||
api.Router.HandleFunc("/Albums", api.albumsHandler)
|
||||
api.Router.HandleFunc("/MediaItems", multipleMiddleware(
|
||||
api.mediaItemsHandler,
|
||||
api.authMiddleware,
|
||||
))
|
||||
api.Router.HandleFunc("/Upload", multipleMiddleware(
|
||||
api.uploadHandler,
|
||||
api.authMiddleware,
|
||||
))
|
||||
api.Router.HandleFunc("/Albums", multipleMiddleware(
|
||||
api.albumsHandler,
|
||||
api.authMiddleware,
|
||||
))
|
||||
api.Router.HandleFunc("/Users", multipleMiddleware(
|
||||
api.usersHandler,
|
||||
api.authMiddleware,
|
||||
))
|
||||
api.Router.HandleFunc("/Tags", multipleMiddleware(
|
||||
api.tagsHandler,
|
||||
api.authMiddleware,
|
||||
))
|
||||
api.Router.HandleFunc("/Info", multipleMiddleware(
|
||||
api.infoHandler,
|
||||
api.authMiddleware,
|
||||
))
|
||||
api.Router.HandleFunc("/Me", multipleMiddleware(
|
||||
api.meHandler,
|
||||
api.authMiddleware,
|
||||
))
|
||||
|
||||
api.Router.HandleFunc("/Logout", api.logoutHandler)
|
||||
api.Router.HandleFunc("/Login", api.loginHandler)
|
||||
api.Router.HandleFunc("/Users", api.usersHandler)
|
||||
api.Router.HandleFunc("/Tags", api.tagsHandler)
|
||||
api.Router.HandleFunc("/Info", api.infoHandler)
|
||||
api.Router.HandleFunc("/Me", api.meHandler)
|
||||
api.Router.HandleFunc("/RefreshLogin", api.refreshLoginHandler)
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/a/59764037
|
||||
|
@ -31,9 +31,9 @@ func (api *API) meHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// Get Authenticated User & Return Object
|
||||
authCookie, err := r.Cookie("Token")
|
||||
if err != nil {
|
||||
log.Error("[routes] ", err)
|
||||
log.Error("[api] ", err)
|
||||
return
|
||||
}
|
||||
|
||||
log.Info("[routes] INFO: ", authCookie)
|
||||
log.Info("[api] Auth Cookie: ", authCookie)
|
||||
}
|
||||
|
@ -2,19 +2,36 @@ package auth
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"gorm.io/gorm"
|
||||
"reichard.io/imagini/internal/db"
|
||||
"reichard.io/imagini/internal/config"
|
||||
"reichard.io/imagini/internal/models"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"reichard.io/imagini/internal/session"
|
||||
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/lestrrat-go/jwx/jwa"
|
||||
"github.com/lestrrat-go/jwx/jwt"
|
||||
)
|
||||
|
||||
type AuthManager struct {
|
||||
DB *db.DBManager
|
||||
Config *config.Config
|
||||
Session *session.SessionManager
|
||||
}
|
||||
|
||||
func NewMgr(db *db.DBManager) *AuthManager {
|
||||
func NewMgr(db *db.DBManager, c *config.Config) *AuthManager {
|
||||
session := session.NewMgr()
|
||||
return &AuthManager{
|
||||
DB: db,
|
||||
Config: c,
|
||||
Session: session,
|
||||
}
|
||||
}
|
||||
|
||||
@ -46,3 +63,123 @@ func (auth *AuthManager) AuthenticateUser(creds models.APICredentials) bool {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (auth *AuthManager) ValidateJWTToken(userJWT string) bool {
|
||||
byteUserJWT := []byte(userJWT)
|
||||
|
||||
serverToken, err := jwt.ParseBytes(byteUserJWT, jwt.WithVerify(jwa.HS256, auth.Config.JWTSecret))
|
||||
if err != nil {
|
||||
fmt.Println("failed to parse payload: ", err)
|
||||
}
|
||||
|
||||
uid, ok := serverToken.Get("uid");
|
||||
if !ok {
|
||||
fmt.Println("failed to acquire uid")
|
||||
}
|
||||
|
||||
userID := fmt.Sprintf("%v", uid)
|
||||
userKey := auth.Session.Get(userID)
|
||||
|
||||
userToken, err := jwt.ParseBytes(byteUserJWT, jwt.WithVerify(jwa.HS256, userKey))
|
||||
if err != nil {
|
||||
fmt.Println("failed to parse payload: ", err)
|
||||
}
|
||||
_ = userToken
|
||||
|
||||
// TODO:
|
||||
// - Get User ID from UNVALIDATED token
|
||||
// - Lookup user key, concat with server key
|
||||
// - Validate with concatted user & server key
|
||||
// validatedToken, err := jwt.ParseBytes(byteUserJWT, jwt.WithVerify(jwa.HS256, concatKey))
|
||||
// if err != nil {
|
||||
// fmt.Printf("failed to parse payload: %s\n", err)
|
||||
// }
|
||||
|
||||
// userToken := auth.Session.Get(userID)
|
||||
// log.Info("[auth] DEBUG: ", userToken)
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (auth *AuthManager) RevokeRefreshToken() {
|
||||
|
||||
}
|
||||
|
||||
func (auth *AuthManager) ValidateRefreshToken(refreshToken, deviceID string) bool {
|
||||
// Acquire Device
|
||||
deviceUUID, err := uuid.Parse(deviceID)
|
||||
device := models.Device{Base: models.Base{UUID: deviceUUID}}
|
||||
foundDevice, err := auth.DB.Device(device)
|
||||
|
||||
// Validate Expiration
|
||||
expTime, err := time.Parse(time.RFC3339, foundDevice.RefreshExp)
|
||||
if expTime.Before(time.Now()) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Validate Token
|
||||
bRefreshToken :=[]byte(refreshToken)
|
||||
err = bcrypt.CompareHashAndPassword([]byte(foundDevice.RefreshToken), bRefreshToken)
|
||||
if err == nil {
|
||||
log.Info("[auth] Refresh Token validation succeeded: ", foundDevice.UUID)
|
||||
return true
|
||||
}
|
||||
log.Warn("[auth] Refresh Token validation failed: ", foundDevice.UUID)
|
||||
return false
|
||||
}
|
||||
|
||||
func (auth *AuthManager) UpdateRefreshToken(deviceID string) error {
|
||||
// TODO:
|
||||
// - Remove Refresh token from Session AND DB
|
||||
// - Call CreateRefreshToken
|
||||
return nil
|
||||
}
|
||||
|
||||
func (auth *AuthManager) CreateRefreshToken(deviceID string) (string, error) {
|
||||
// TODO:
|
||||
// - Create regular bcrypt password
|
||||
// - Create Expiration (Depends on Device Type)
|
||||
// - Store in DB: DeviceID, ValidUntil
|
||||
|
||||
generatedToken := uuid.New().String()
|
||||
hashedRefreshToken, err := bcrypt.GenerateFromPassword([]byte(generatedToken), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return "", err
|
||||
}
|
||||
_ = string(hashedRefreshToken)
|
||||
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (auth *AuthManager) CreateJWTAccessToken(user, role, deviceID string) (string, error) {
|
||||
// Create New Token
|
||||
tm := time.Now()
|
||||
t := jwt.New()
|
||||
t.Set(`did`, deviceID) // Device ID
|
||||
t.Set(`role`, role) // User Role (Admin / User)
|
||||
t.Set(jwt.SubjectKey, user) // User ID
|
||||
t.Set(jwt.AudienceKey, `imagini`) // App ID
|
||||
t.Set(jwt.IssuedAtKey, tm) // Issued At
|
||||
t.Set(jwt.ExpirationKey, tm.Add(time.Minute * 30)) // 30 Minute Access Key
|
||||
|
||||
// Validate Token Creation
|
||||
_, err := json.MarshalIndent(t, "", " ")
|
||||
if err != nil {
|
||||
fmt.Printf("failed to generate JSON: %s\n", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Use Server Key
|
||||
byteKey := []byte(auth.Config.JWTSecret)
|
||||
|
||||
// Sign Token
|
||||
signed, err := jwt.Sign(t, jwa.HS256, byteKey)
|
||||
if err != nil {
|
||||
log.Printf("failed to sign token: %s", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Return Token
|
||||
return string(signed), nil
|
||||
}
|
||||
|
@ -34,6 +34,7 @@ func NewMgr(c *config.Config) *DBManager {
|
||||
|
||||
// Initialize database
|
||||
dbm.db.AutoMigrate(&models.ServerSetting{})
|
||||
dbm.db.AutoMigrate(&models.Device{})
|
||||
dbm.db.AutoMigrate(&models.User{})
|
||||
dbm.db.AutoMigrate(&models.MediaItem{})
|
||||
dbm.db.AutoMigrate(&models.Tag{})
|
||||
@ -83,71 +84,3 @@ func (dbm *DBManager) ItemsFromAlbum(user models.User, album models.Album) []mod
|
||||
// INNER JOIN MediaItems ON MediaAlbums.mediaID = MediaItems.mediaID
|
||||
// WHERE MediaAlbums.albumID = ? AND MediaItems.userID = ?`, albumID, userID)
|
||||
}
|
||||
|
||||
// func ItemsFromTags(userID int, tagID int) []MediaItem {
|
||||
// return nil
|
||||
// }
|
||||
//
|
||||
// func IndexMediaItems(newItems []MediaItem) {
|
||||
// }
|
||||
|
||||
// func PopulateTestData() {
|
||||
// user1 := User{Username: "Evan", Email: "evan@reichard.io", FirstName: "Evan", LastName: "Reichard", AuthType: "LDAP", Salt: "1234", HashedPWSalt: "1234"}
|
||||
// user2 := User{Username: "Ryan", Email: "ryan@example.com", FirstName: "Ryan", LastName: "Dunfrey", AuthType: "Local", Salt: "2345", HashedPWSalt: "2345"}
|
||||
// user3 := User{Username: "Bill", Email: "bill@example.com", FirstName: "Bill", LastName: "Smith", AuthType: "LDAP", Salt: "3456", HashedPWSalt: "3456"}
|
||||
//
|
||||
// mi1 := MediaItem{
|
||||
// User: user1,
|
||||
// EXIFDate: time.Now(),
|
||||
// Latitude: "1234",
|
||||
// Longitude: "1234",
|
||||
// RelPath: "./1234.jpg",
|
||||
// Tags: []Tag{
|
||||
// {Name: "Tag1"},
|
||||
// {Name: "Tag2"},
|
||||
// },
|
||||
// Albums: []Album{
|
||||
// {Name: "Album1"},
|
||||
// {Name: "Album2"},
|
||||
// },
|
||||
// }
|
||||
//
|
||||
// mi2 := MediaItem{
|
||||
// User: user2,
|
||||
// EXIFDate: time.Now(),
|
||||
// Latitude: "1234",
|
||||
// Longitude: "1234",
|
||||
// RelPath: "./1234.jpg",
|
||||
// Tags: []Tag{
|
||||
// {Name: "Tag3"},
|
||||
// {Name: "Tag4"},
|
||||
// },
|
||||
// Albums: []Album{
|
||||
// {Name: "Album3"},
|
||||
// {Name: "Album4"},
|
||||
// },
|
||||
// }
|
||||
//
|
||||
// mi3 := MediaItem{
|
||||
// User: user3,
|
||||
// EXIFDate: time.Now(),
|
||||
// Latitude: "1234",
|
||||
// Longitude: "1234",
|
||||
// RelPath: "./1234.jpg",
|
||||
// Tags: []Tag{
|
||||
// {Name: "Tag4"},
|
||||
// {Name: "Tag5"},
|
||||
// },
|
||||
// Albums: []Album{
|
||||
// {Name: "Album1"},
|
||||
// {Name: "Album7"},
|
||||
// },
|
||||
// }
|
||||
//
|
||||
// // db.Create(&user1)
|
||||
// // db.Create(&user2)
|
||||
// // db.Create(&user3)
|
||||
// db.Create(&mi1)
|
||||
// db.Create(&mi2)
|
||||
// db.Create(&mi3)
|
||||
// }
|
||||
|
44
internal/db/devices.go
Normal file
44
internal/db/devices.go
Normal file
@ -0,0 +1,44 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"gorm.io/gorm"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"reichard.io/imagini/internal/models"
|
||||
)
|
||||
|
||||
func (dbm *DBManager) CreateDevice(device models.Device) error {
|
||||
log.Info("[query] Creating device: ", device.Name)
|
||||
_, err := dbm.Device(device)
|
||||
if !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
log.Warn("[query] Device already exists: ", device.Name)
|
||||
return errors.New("Device already exists")
|
||||
}
|
||||
|
||||
// Generate random password
|
||||
refreshToken := "asd123"
|
||||
hashedToken, err := bcrypt.GenerateFromPassword([]byte(refreshToken), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
device.RefreshToken = string(hashedToken)
|
||||
return dbm.db.Create(&device).Error
|
||||
}
|
||||
|
||||
func (dbm *DBManager) Device (device models.Device) (models.Device, error) {
|
||||
var foundDevice models.Device
|
||||
var count int64
|
||||
err := dbm.db.Where(&device).First(&foundDevice).Count(&count).Error
|
||||
return foundDevice, err
|
||||
}
|
||||
|
||||
func (dbm *DBManager) DeleteDevice (user models.Device) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dbm *DBManager) UpdateRefreshToken (device models.Device, refreshToken string) error {
|
||||
return nil
|
||||
}
|
@ -1,40 +1,53 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Might not even need this
|
||||
// Base contains common columns for all tables.
|
||||
type Base struct {
|
||||
UUID uuid.UUID `gorm:"type:uuid;default:default:uuid_generate_v4();primarykey"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
DeletedAt gorm.DeletedAt `gorm:"index"`
|
||||
}
|
||||
|
||||
func (base *Base) BeforeCreate(tx *gorm.DB) (err error) {
|
||||
base.UUID = uuid.New()
|
||||
return
|
||||
}
|
||||
|
||||
type ServerSetting struct {
|
||||
gorm.Model
|
||||
Base
|
||||
Name string `json:"name" gorm:"not null"`
|
||||
Description string `json:"description" gorm:"not null"`
|
||||
Value string `json:"value" gorm:"not null"`
|
||||
}
|
||||
|
||||
type Device struct {
|
||||
gorm.Model
|
||||
User User `json:"user" gorm:"ForeignKey:ID"`
|
||||
DeviceName string `json:"name"`
|
||||
Base
|
||||
User User `json:"user" gorm:"ForeignKey:UUID"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"` // Android, iOS, Chrome, FireFox, Edge, etc
|
||||
RefreshExp string `json:"refresh_exp"`
|
||||
RefreshToken string `json:"-"`
|
||||
}
|
||||
|
||||
// TODO: ID -> UUID?
|
||||
type User struct {
|
||||
gorm.Model
|
||||
Base
|
||||
Email string `json:"email" gorm:"unique"`
|
||||
Username string `json:"username" gorm:"unique"`
|
||||
FirstName string `json:"first_name"`
|
||||
LastName string `json:"last_name"`
|
||||
AuthType string `json:"auth_type" gorm:"default:Local;not null"`
|
||||
Password string `json:"-"`
|
||||
JWTSecret string `json:"-" gorm:"unique;not null"` // TODO: Auto Generate UUID
|
||||
}
|
||||
|
||||
type MediaItem struct {
|
||||
gorm.Model
|
||||
User User `json:"user" gorm:"ForeignKey:ID;not null"`
|
||||
Base
|
||||
User User `json:"user" gorm:"ForeignKey:UUID;not null"`
|
||||
EXIFDate time.Time `json:"exif_date"`
|
||||
Latitude string `json:"latitude"`
|
||||
Longitude string `json:"longitude"`
|
||||
@ -45,11 +58,11 @@ type MediaItem struct {
|
||||
}
|
||||
|
||||
type Tag struct {
|
||||
gorm.Model
|
||||
Base
|
||||
Name string `json:"name" gorm:"not null"`
|
||||
}
|
||||
|
||||
type Album struct {
|
||||
gorm.Model
|
||||
Base
|
||||
Name string `json:"name" gorm:"not null"`
|
||||
}
|
||||
|
@ -4,8 +4,11 @@ import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Used to maintain a cache of user specific jwt secrets
|
||||
// This will prevent DB lookups on every request
|
||||
type SessionManager struct {
|
||||
Mutex sync.Mutex
|
||||
mutex sync.Mutex
|
||||
values map[string]string
|
||||
}
|
||||
|
||||
func NewMgr() *SessionManager {
|
||||
@ -13,13 +16,23 @@ func NewMgr() *SessionManager {
|
||||
}
|
||||
|
||||
func (sm *SessionManager) Set(key, value string) {
|
||||
|
||||
sm.mutex.Lock()
|
||||
sm.values[key] = value
|
||||
sm.mutex.Unlock()
|
||||
}
|
||||
|
||||
func (sm *SessionManager) Get(key string) string {
|
||||
return ""
|
||||
sm.mutex.Lock()
|
||||
defer sm.mutex.Unlock()
|
||||
return sm.values[key]
|
||||
}
|
||||
|
||||
func (sm *SessionManager) Delete(key string) {
|
||||
|
||||
sm.mutex.Lock()
|
||||
defer sm.mutex.Unlock()
|
||||
_, exists := sm.values[key]
|
||||
if !exists {
|
||||
return
|
||||
}
|
||||
delete(sm.values, key)
|
||||
}
|
||||
|
Reference in New Issue
Block a user