From 45d7adbd8b1dae23c6cfd0fdbf672d64c0ad661f Mon Sep 17 00:00:00 2001 From: Evan Reichard Date: Tue, 19 Jan 2021 15:50:48 -0500 Subject: [PATCH] More --- cmd/imagini.db | Bin 102400 -> 102400 bytes cmd/server/server.go | 3 +- internal/api/auth.go | 151 ++++++++++++++++++++++++++----------- internal/api/devices.go | 9 +++ internal/api/routes.go | 26 ++++--- internal/api/users.go | 2 +- internal/auth/auth.go | 63 +++++++--------- internal/db/albums.go | 0 internal/db/db.go | 22 ------ internal/db/devices.go | 4 +- internal/db/media_items.go | 35 +++++++++ internal/db/tags.go | 0 internal/db/users.go | 2 +- internal/models/db.go | 7 +- 14 files changed, 203 insertions(+), 121 deletions(-) create mode 100644 internal/api/devices.go create mode 100644 internal/db/albums.go create mode 100644 internal/db/media_items.go create mode 100644 internal/db/tags.go diff --git a/cmd/imagini.db b/cmd/imagini.db index ecb2d8fff709e96159d544b2c21dca03b33b4249..9320e9aa619a772e3444778d0d3f4649c3b99df4 100644 GIT binary patch delta 1048 zcma))!AlfT9LHyO#&vgfW;XY*m4yWhO7zzEe)DGLz0EF)N~LAlVnUZUZ)QN!R$L>I zVBHA2bTIZGFuTdN!6xz$Xe82EM-|zRgvYuF3Ppoyv zx*nexI^U%fF0JY2PvvNQOEKP>Zw%V`$Vlm*^hRf2`di1Z^u6R(dN?`VUQ3>BKh}0X z@mYJ-HmzNcZ)rU*pYVA7OZ<8LlR8#^-I1xA=|ugZI?y_ZjY6vn9ZL9ktaC6YyrM@*S%P=#|GzT zh9_^>mC1!W3v_X0e%2{34PF>63=LMMmai^(Q{J5Iz^%){=;p-7{(LLEn=4u-ts4D` zDUJ6=e-!+*QLSawWHWfSL?olq@dOHe3lJr?iy9_EPG(CGNFPF9A|J4es7)CX zR_H~00QeFM0XFQ%vUt&suw2)&51-@a~^Q!OG?vDm%n#AdOJ=oCEyY?H zSlui E4L^1Up#T5? delta 591 zcma*iziSg=7zgmX^paddE_dQ^g^KNPo#ef~KkvN@bgCs3Q;d~pyLs<>?;9gE5!7J2 zn1X|gQVED-)zK|Cxheh)ZbiuC;^5%m;NW#Ff*qdW;rqbz{fv(*{dmZyNK?Hz%*t+OK-K_S~M;ZrG~&!j7ve<(HKo)~B+v?v!TM zmHdLYQJQ=$eVBZo;}bR4%P(6)dw#*H`o3>Ib0@FsUvlNV#a7F3TgCO7`}8m}>)ov_ z!|@%*KP*!%5Y|!vMMVN=Bq>NR!9degD@_=oI>itp01-g(GK!iIHgO!roZ%z_kQMv? z@-(~OAGC)u-3xFt4G;|U_HOiOXSdZKLMTVK_C;^?fUBst9=AiG9!J@TZMRby?xCyf zpnJQwe{b|8oOVuDgY)#AjxDcZy25ZpVcsdsi>BS(+%#4$b_JuEWJwByq6komD2U?} zgP3JZXM%IZ*clIL5*{X;vwv#Oq#L7j@Nl``xjP)F{`H#?!-y@Bh=5GS#Xms^NI2v` hO0JcRv0y|`ADp= 2 { + if s[0] == '"' && s[len(s)-1] == '"' { + return s[1 : len(s)-1] + } + } + return s +} diff --git a/internal/api/devices.go b/internal/api/devices.go new file mode 100644 index 0000000..70a718e --- /dev/null +++ b/internal/api/devices.go @@ -0,0 +1,9 @@ +package api + +import ( + "net/http" +) + +func (api *API) devicesHandler(w http.ResponseWriter, r *http.Request) { + +} diff --git a/internal/api/routes.go b/internal/api/routes.go index 53817bf..918eae5 100644 --- a/internal/api/routes.go +++ b/internal/api/routes.go @@ -6,40 +6,46 @@ import ( ) func (api *API) registerRoutes() { - api.Router.HandleFunc("/MediaItems", multipleMiddleware( + api.Router.HandleFunc("/api/v1/MediaItems", multipleMiddleware( api.mediaItemsHandler, api.authMiddleware, )) - api.Router.HandleFunc("/Upload", multipleMiddleware( + api.Router.HandleFunc("/api/v1/Devices", multipleMiddleware( + api.devicesHandler, + api.authMiddleware, + )) + api.Router.HandleFunc("/api/v1/Upload", multipleMiddleware( api.uploadHandler, api.authMiddleware, )) - api.Router.HandleFunc("/Albums", multipleMiddleware( + api.Router.HandleFunc("/api/v1/Albums", multipleMiddleware( api.albumsHandler, api.authMiddleware, )) - api.Router.HandleFunc("/Users", multipleMiddleware( + api.Router.HandleFunc("/api/v1/Users", multipleMiddleware( api.usersHandler, api.authMiddleware, )) - api.Router.HandleFunc("/Tags", multipleMiddleware( + api.Router.HandleFunc("/api/v1/Tags", multipleMiddleware( api.tagsHandler, api.authMiddleware, )) - api.Router.HandleFunc("/Info", multipleMiddleware( + api.Router.HandleFunc("/api/v1/Info", multipleMiddleware( api.infoHandler, api.authMiddleware, )) - api.Router.HandleFunc("/Me", multipleMiddleware( + api.Router.HandleFunc("/api/v1/Me", multipleMiddleware( api.meHandler, api.authMiddleware, )) - api.Router.HandleFunc("/Logout", api.logoutHandler) - api.Router.HandleFunc("/Login", api.loginHandler) - api.Router.HandleFunc("/RefreshLogin", api.refreshLoginHandler) + api.Router.HandleFunc("/api/v1/Logout", api.logoutHandler) + api.Router.HandleFunc("/api/v1/Login", api.loginHandler) + api.Router.HandleFunc("/api/v1/RefreshLogin", api.refreshLoginHandler) } + + // https://stackoverflow.com/a/59764037 func errorJSON(w http.ResponseWriter, err string, code int) { w.Header().Set("Content-Type", "application/json; charset=utf-8") diff --git a/internal/api/users.go b/internal/api/users.go index 0aa6758..4c28349 100644 --- a/internal/api/users.go +++ b/internal/api/users.go @@ -2,7 +2,7 @@ package api import ( "net/http" - log "github.com/sirupsen/logrus" + // log "github.com/sirupsen/logrus" ) func (api *API) usersHandler(w http.ResponseWriter, r *http.Request) { diff --git a/internal/auth/auth.go b/internal/auth/auth.go index d26e84c..720aa2d 100644 --- a/internal/auth/auth.go +++ b/internal/auth/auth.go @@ -62,26 +62,11 @@ func (auth *AuthManager) AuthenticateUser(creds models.APICredentials) (bool, mo } } - func (auth *AuthManager) getRole(user models.User) string { // TODO: Lookup role of user return "User" } -func (auth *AuthManager) ValidateJWTAccessToken(accessJWT string) (jwt.Token, bool) { - byteAccessJWT := []byte(accessJWT) - verifiedToken, err := jwt.ParseBytes(byteAccessJWT, jwt.WithVerify(jwa.HS256, []byte(auth.Config.JWTSecret))) - if err != nil { - fmt.Println("failed to parse payload: ", err) - return nil, false - } - return verifiedToken, true -} - -func (auth *AuthManager) RevokeRefreshToken() { - -} - func (auth *AuthManager) ValidateJWTRefreshToken(refreshJWT string) (jwt.Token, bool) { byteRefreshJWT := []byte(refreshJWT) @@ -100,8 +85,11 @@ func (auth *AuthManager) ValidateJWTRefreshToken(refreshJWT string) (jwt.Token, return nil, false } - // Verify Token - verifiedToken, err := jwt.ParseBytes(byteRefreshJWT, jwt.WithVerify(jwa.HS256, []byte(device.RefreshKey))) + // Verify & Validate Token + verifiedToken, err := jwt.ParseBytes(byteRefreshJWT, + jwt.WithValidate(true), + jwt.WithVerify(jwa.HS256, []byte(device.RefreshKey)), + ) if err != nil { fmt.Println("failed to parse payload: ", err) return nil, false @@ -109,11 +97,17 @@ func (auth *AuthManager) ValidateJWTRefreshToken(refreshJWT string) (jwt.Token, return verifiedToken, true } -func (auth *AuthManager) UpdateRefreshToken(deviceID string) error { - // TODO: - // - Remove Refresh token from Session AND DB - // - Call CreateRefreshToken - return nil +func (auth *AuthManager) ValidateJWTAccessToken(accessJWT string) (jwt.Token, bool) { + byteAccessJWT := []byte(accessJWT) + verifiedToken, err := jwt.ParseBytes(byteAccessJWT, + jwt.WithValidate(true), + jwt.WithVerify(jwa.HS256, []byte(auth.Config.JWTSecret)), + ) + if err != nil { + fmt.Println("failed to parse payload: ", err) + return nil, false + } + return verifiedToken, true } func (auth *AuthManager) CreateJWTRefreshToken(user models.User, device models.Device) (string, error) { @@ -123,13 +117,15 @@ func (auth *AuthManager) CreateJWTRefreshToken(user models.User, device models.D // Create New Token tm := time.Now() t := jwt.New() - t.Set(`did`, device.UUID) // Device ID - t.Set(jwt.SubjectKey, user.UUID) // User ID - t.Set(jwt.AudienceKey, `imagini`) // App ID - t.Set(jwt.IssuedAtKey, tm) // Issued At + t.Set(`did`, device.UUID.String()) // Device ID + t.Set(jwt.SubjectKey, user.UUID.String()) // User ID + t.Set(jwt.AudienceKey, `imagini`) // App ID + t.Set(jwt.IssuedAtKey, tm) // Issued At - // TODO: Depends on Device - t.Set(jwt.ExpirationKey, tm.Add(time.Hour * 24)) // 1 Day Access Key + // iOS & Android = Never Expiring Refresh Token + if device.Type != "iOS" && device.Type != "Android" { + t.Set(jwt.ExpirationKey, tm.Add(time.Hour * 24)) // 1 Day Access Key + } // Validate Token Creation _, err := json.MarshalIndent(t, "", " ") @@ -150,18 +146,15 @@ func (auth *AuthManager) CreateJWTRefreshToken(user models.User, device models.D } func (auth *AuthManager) CreateJWTAccessToken(user models.User, device models.Device) (string, error) { - // Acquire Role - role := auth.getRole(user) - // Create New Token tm := time.Now() t := jwt.New() - t.Set(`did`, device.UUID) // Device ID - t.Set(`role`, role) // User Role (Admin / User) - t.Set(jwt.SubjectKey, user.UUID) // User ID + t.Set(`did`, device.UUID.String()) // Device ID + t.Set(`role`, auth.getRole(user)) // User Role (Admin / User) + t.Set(jwt.SubjectKey, user.UUID.String()) // 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 + t.Set(jwt.ExpirationKey, tm.Add(time.Hour * 2)) // 2 Hour Access Key // Validate Token Creation _, err := json.MarshalIndent(t, "", " ") diff --git a/internal/db/albums.go b/internal/db/albums.go new file mode 100644 index 0000000..e69de29 diff --git a/internal/db/db.go b/internal/db/db.go index 214ef08..23245c2 100644 --- a/internal/db/db.go +++ b/internal/db/db.go @@ -62,25 +62,3 @@ func (dbm *DBManager) bootstrapDatabase() { log.Fatal("[query] Unable to bootstrap database.") } } - -func (dbm *DBManager) ItemsFromAlbum(user models.User, album models.Album) []models.MediaItem { - var mediaItems []models.MediaItem - // db.Table("media_albums"). - // Select("media_item.*"). - // Joins("INNER JOIN media_items ON media_albums.ID = media_items.Albums"). - // Where("media_albums.album_id = ? AND media_items.User = ?", albumID, userID). - - - dbm.db. - //Where("album = ? AND user = ?", albumID, userID). - Find(&mediaItems) - return mediaItems - - // db.Raw(` - // SELECT - // MediaItems.* - // FROM - // MediaAlbums - // INNER JOIN MediaItems ON MediaAlbums.mediaID = MediaItems.mediaID - // WHERE MediaAlbums.albumID = ? AND MediaItems.userID = ?`, albumID, userID) -} diff --git a/internal/db/devices.go b/internal/db/devices.go index 1a00bb2..a99f7bf 100644 --- a/internal/db/devices.go +++ b/internal/db/devices.go @@ -7,8 +7,8 @@ import ( "reichard.io/imagini/internal/models" ) -func (dbm *DBManager) CreateDevice(device models.Device) (models.Device, error) { - log.Info("[query] Creating device: ", device.Name) +func (dbm *DBManager) CreateDevice (device models.Device) (models.Device, error) { + log.Info("[db] Creating device: ", device.Name) device.RefreshKey = uuid.New().String() err := dbm.db.Create(&device).Error return device, err diff --git a/internal/db/media_items.go b/internal/db/media_items.go new file mode 100644 index 0000000..26b025b --- /dev/null +++ b/internal/db/media_items.go @@ -0,0 +1,35 @@ +package db + +import ( + log "github.com/sirupsen/logrus" + + "reichard.io/imagini/internal/models" +) + +func (dbm *DBManager) CreateMediaItem (mediaItem models.MediaItem) (models.MediaItem, error) { + log.Info("[db] Creating media item: ", mediaItem.RelPath) + err := dbm.db.Create(&mediaItem).Error + return mediaItem, err +} + +func (dbm *DBManager) MediaItemsFromAlbum(user models.User, album models.Album) ([]models.MediaItem, error) { + var mediaItems []models.MediaItem + // db.Table("media_albums"). + // Select("media_item.*"). + // Joins("INNER JOIN media_items ON media_albums.ID = media_items.Albums"). + // Where("media_albums.album_id = ? AND media_items.User = ?", albumID, userID). + + + err := dbm.db. + //Where("album = ? AND user = ?", albumID, userID). + Find(&mediaItems).Error + return mediaItems, err + + // db.Raw(` + // SELECT + // MediaItems.* + // FROM + // MediaAlbums + // INNER JOIN MediaItems ON MediaAlbums.mediaID = MediaItems.mediaID + // WHERE MediaAlbums.albumID = ? AND MediaItems.userID = ?`, albumID, userID) +} diff --git a/internal/db/tags.go b/internal/db/tags.go new file mode 100644 index 0000000..e69de29 diff --git a/internal/db/users.go b/internal/db/users.go index 6952ba3..0215f50 100644 --- a/internal/db/users.go +++ b/internal/db/users.go @@ -8,7 +8,7 @@ import ( ) func (dbm *DBManager) CreateUser(user models.User) (models.User, error) { - log.Info("[query] Creating user: ", user.Username) + log.Info("[db] Creating user: ", user.Username) hashedPassword, err := bcrypt.GenerateFromPassword([]byte(user.Password), bcrypt.DefaultCost) if err != nil { log.Error(err) diff --git a/internal/models/db.go b/internal/models/db.go index 46e5463..1a396d4 100644 --- a/internal/models/db.go +++ b/internal/models/db.go @@ -28,9 +28,9 @@ type ServerSetting struct { type Device struct { Base - User User `json:"user" gorm:"ForeignKey:UUID"` - Name string `json:"name"` - Type string `json:"type"` // Android, iOS, Chrome, FireFox, Edge, etc + User User `json:"user" gorm:"ForeignKey:UUID;not null"` + Name string `json:"name" gorm:"not null"` + Type string `json:"type" gorm:"not null"` // Android, iOS, Chrome, FireFox, Edge, etc RefreshKey string `json:"-"` } @@ -40,6 +40,7 @@ type User struct { Username string `json:"username" gorm:"unique"` FirstName string `json:"first_name"` LastName string `json:"last_name"` + Role string `json:"role"` AuthType string `json:"auth_type" gorm:"default:Local;not null"` Password string `json:"-"` }