(WIP) Refresh & Access

This commit is contained in:
Evan Reichard 2021-01-17 23:56:56 -05:00
parent cd97b6262f
commit 377903f7a1
14 changed files with 366 additions and 136 deletions

Binary file not shown.

View File

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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