Skip to content

Commit

Permalink
Merge pull request #32 from daystram/dev
Browse files Browse the repository at this point in the history
  • Loading branch information
daystram authored Jan 24, 2021
2 parents 32e2001 + 6e49f4a commit 2936d36
Show file tree
Hide file tree
Showing 31 changed files with 890 additions and 137 deletions.
1 change: 1 addition & 0 deletions ratify-be/constants/log.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const (
LogScopeOAuthAuthorize = "oauth::authorize"
LogScopeUserProfile = "user::profile"
LogScopeUserPassword = "user::password"
LogScopeUserSession = "user::session"
LogScopeUserMFA = "user::mfa"
LogScopeApplicationDetail = "application::detail"
LogScopeApplicationCreate = "application::create"
Expand Down
1 change: 1 addition & 0 deletions ratify-be/controllers/oauth/token.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ func POSTToken(c *gin.Context) {
c.JSON(http.StatusUnauthorized, datatransfers.APIResponse{Error: "failed spawning child session"})
return
}
handlers.Handler.LogInsertAuthorize(application, true, datatransfers.LogDetail{})
c.JSON(http.StatusOK, datatransfers.TokenResponse{
AccessToken: accessToken,
RefreshToken: refreshToken,
Expand Down
23 changes: 13 additions & 10 deletions ratify-be/controllers/v1/application.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,20 +31,23 @@ func GETOneApplicationDetail(c *gin.Context) {
// check superuser
if !c.GetBool(constants.IsSuperuserKey) {
c.JSON(http.StatusOK, datatransfers.APIResponse{Data: datatransfers.ApplicationInfo{
Name: application.Name,
Name: application.Name,
CallbackURL: application.CallbackURL,
}})
return
}
c.JSON(http.StatusOK, datatransfers.APIResponse{Data: datatransfers.ApplicationInfo{
ClientID: application.ClientID,
Name: application.Name,
Description: application.Description,
LoginURL: application.LoginURL,
CallbackURL: application.CallbackURL,
LogoutURL: application.LogoutURL,
Metadata: application.Metadata,
Locked: &application.Locked,
CreatedAt: application.CreatedAt,
ClientID: application.ClientID,
Name: application.Name,
Description: application.Description,
LoginURL: application.LoginURL,
CallbackURL: application.CallbackURL,
LogoutURL: application.LogoutURL,
Metadata: application.Metadata,
Locked: &application.Locked,
CreatedAt: application.CreatedAt,
LastAuthorize: &application.LastAuthorize,
AuthorizeCount: &application.AuthorizeCount,
}})
return
}
Expand Down
40 changes: 40 additions & 0 deletions ratify-be/controllers/v1/dashboard.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package v1

import (
"net/http"

"github.com/gin-gonic/gin"

"github.com/daystram/ratify/ratify-be/constants"
"github.com/daystram/ratify/ratify-be/datatransfers"
"github.com/daystram/ratify/ratify-be/handlers"
"github.com/daystram/ratify/ratify-be/models"
)

// @Summary Get dashboard info
// @Tags dashboard
// @Security BearerAuth
// @Success 200 "OK"
// @Router /api/v1/dashboard [GET]
func GETDashboardInfo(c *gin.Context) {
var err error
// retrieve user
var user models.User
if user, err = handlers.Handler.UserGetOneBySubject(c.GetString(constants.UserSubjectKey)); err != nil {
c.JSON(http.StatusNotFound, datatransfers.APIResponse{Error: "user not found"})
return
}
// retrieve all sessions
var activeSessions []*datatransfers.SessionInfo
if activeSessions, err = handlers.Handler.SessionGetAllActive(c.GetString(constants.UserSubjectKey)); err != nil {
c.JSON(http.StatusInternalServerError, datatransfers.APIResponse{Error: "cannot retrieve active sessions"})
return
}
dashboardInfo := datatransfers.DashboardInfo{
SignInCount: user.LoginCount,
LastSignIn: user.LastLogin,
SessionCount: len(activeSessions),
}
c.JSON(http.StatusOK, datatransfers.APIResponse{Data: dashboardInfo})
return
}
10 changes: 10 additions & 0 deletions ratify-be/controllers/v1/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/daystram/ratify/ratify-be/constants"
"github.com/daystram/ratify/ratify-be/datatransfers"
"github.com/daystram/ratify/ratify-be/handlers"
"github.com/daystram/ratify/ratify-be/models"
)

// @Summary Get all active sessions of current user
Expand Down Expand Up @@ -39,15 +40,24 @@ func GETSessionActive(c *gin.Context) {
// @Router /api/v1/session [POST]
func POSTSessionRevoke(c *gin.Context) {
var err error
// parse request
var session datatransfers.SessionInfo
if err = c.ShouldBindJSON(&session); err != nil {
c.JSON(http.StatusBadRequest, datatransfers.APIResponse{Error: err.Error()})
return
}
// retrieve user
var user models.User
if user, err = handlers.Handler.UserGetOneBySubject(c.GetString(constants.UserSubjectKey)); err != nil {
c.JSON(http.StatusNotFound, datatransfers.APIResponse{Error: "user not found"})
return
}
// revoke session
if err = handlers.Handler.SessionRevoke(session.SessionID); err != nil {
c.JSON(http.StatusInternalServerError, datatransfers.APIResponse{Error: "failed clearing session"})
return
}
handlers.Handler.LogInsertUser(user, true, datatransfers.LogDetail{Scope: constants.LogScopeUserSession})
c.JSON(http.StatusOK, datatransfers.APIResponse{})
return
}
49 changes: 44 additions & 5 deletions ratify-be/controllers/v1/user.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package v1

import (
"github.com/daystram/ratify/ratify-be/utils"
"net/http"
"strings"

"github.com/gin-gonic/gin"

Expand All @@ -11,34 +11,73 @@ import (
"github.com/daystram/ratify/ratify-be/errors"
"github.com/daystram/ratify/ratify-be/handlers"
"github.com/daystram/ratify/ratify-be/models"
"github.com/daystram/ratify/ratify-be/utils"
)

// @Summary Get user detail
// @Tags user
// @Security BearerAuth
// @Param user body datatransfers.UserSignup true "User signup info"
// @Success 200 "OK"
// @Router /api/v1/user [GET]
func GETUser(c *gin.Context) {
// @Router /api/v1/user/{subject} [GET]
func GETUserDetail(c *gin.Context) {
var err error
// check superuser
var user models.User
if user, err = handlers.Handler.UserGetOneBySubject(c.GetString(constants.UserSubjectKey)); err != nil {
user.Subject = strings.TrimPrefix(c.Param("subject"), "/")
if !c.GetBool(constants.IsSuperuserKey) && (user.Subject != c.GetString(constants.UserSubjectKey)) {
c.JSON(http.StatusUnauthorized, datatransfers.APIResponse{Error: "access unauthorized"})
return
}
// retrieve user
if user, err = handlers.Handler.UserGetOneBySubject(user.Subject); err != nil {
c.JSON(http.StatusNotFound, datatransfers.APIResponse{Error: "user not found"})
return
}
c.JSON(http.StatusOK, datatransfers.APIResponse{Data: datatransfers.UserInfo{
FamilyName: user.FamilyName,
GivenName: user.GivenName,
FamilyName: user.FamilyName,
Subject: user.Subject,
Username: user.Username,
Email: user.Email,
EmailVerified: user.EmailVerified,
MFAEnabled: user.EnabledTOTP(),
CreatedAt: user.CreatedAt,
SignInCount: user.LoginCount,
LastSignIn: user.LastLogin,
}})
return
}

// @Summary Get all users
// @Tags user
// @Security BearerAuth
// @Success 200 "OK"
// @Router /api/v1/user [GET]
func GETUserList(c *gin.Context) {
var err error
// get all users
var users []models.User
if users, err = handlers.Handler.UserGetAll(); err != nil {
c.JSON(http.StatusNotFound, datatransfers.APIResponse{Error: "cannot retrieve users"})
return
}
var usersResponse []datatransfers.UserInfo
for _, user := range users {
usersResponse = append(usersResponse, datatransfers.UserInfo{
Subject: user.Subject,
GivenName: user.GivenName,
FamilyName: user.FamilyName,
Username: user.Username,
EmailVerified: user.EmailVerified,
MFAEnabled: user.EnabledTOTP(),
CreatedAt: user.CreatedAt,
})
}
c.JSON(http.StatusOK, datatransfers.APIResponse{Data: usersResponse})
return
}

// @Summary Register user
// @Tags user
// @Param user body datatransfers.UserSignup true "User signup info"
Expand Down
22 changes: 12 additions & 10 deletions ratify-be/datatransfers/application.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
package datatransfers

type ApplicationInfo struct {
ClientID string `json:"client_id,omitempty" binding:"-"`
Name string `json:"name" binding:"required"`
Description string `json:"description,omitempty" binding:"-"`
LoginURL string `json:"login_url,omitempty" binding:"required"`
CallbackURL string `json:"callback_url,omitempty" binding:"required"`
LogoutURL string `json:"logout_url,omitempty" binding:"required"`
Metadata string `json:"metadata" binding:"-"`
Locked *bool `json:"locked,omitempty" binding:"-"`
CreatedAt int64 `json:"created_at,omitempty" binding:"-"`
UpdatedAt int64 `json:"updated_at,omitempty" binding:"-"`
ClientID string `json:"client_id,omitempty" binding:"-"`
Name string `json:"name" binding:"required"`
Description string `json:"description,omitempty" binding:"-"`
LoginURL string `json:"login_url,omitempty" binding:"required"`
CallbackURL string `json:"callback_url,omitempty" binding:"required"`
LogoutURL string `json:"logout_url,omitempty" binding:"required"`
Metadata string `json:"metadata" binding:"-"`
Locked *bool `json:"locked,omitempty" binding:"-"` // using pointers tackles omitempty for 'false' or '0' values
CreatedAt int64 `json:"created_at,omitempty" binding:"-"`
UpdatedAt int64 `json:"updated_at,omitempty" binding:"-"`
LastAuthorize *int64 `json:"last_authorize,omitempty" binding:"-"`
AuthorizeCount *int `json:"authorize_count,omitempty" binding:"-"`
}
7 changes: 7 additions & 0 deletions ratify-be/datatransfers/dashboard.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package datatransfers

type DashboardInfo struct {
SignInCount int `json:"signin_count"`
LastSignIn int64 `json:"last_signin"`
SessionCount int `json:"session_count"`
}
2 changes: 2 additions & 0 deletions ratify-be/datatransfers/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,6 @@ type UserInfo struct {
EmailVerified bool `json:"email_verified"`
MFAEnabled bool `json:"mfa_enabled"`
CreatedAt int64 `json:"created_at"`
SignInCount int `json:"signin_count"`
LastSignIn int64 `json:"last_signin"`
}
2 changes: 2 additions & 0 deletions ratify-be/handlers/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ type handlerFunc interface {
UserGetOneBySubject(subject string) (user models.User, err error)
UserGetOneByUsername(username string) (user models.User, err error)
UserGetOneByEmail(email string) (user models.User, err error)
UserGetAll() (users []models.User, err error)
UserUpdate(subject string, user datatransfers.UserUpdate) (err error)
UserUpdatePassword(subject, oldPassword, newPassword string) (err error)

Expand Down Expand Up @@ -71,6 +72,7 @@ type handlerFunc interface {
LogGetAllActivity(subject string) (logs []models.Log, err error)
LogGetAllAdmin() (logs []models.Log, err error)
LogInsertLogin(user models.User, application models.Application, success bool, detail datatransfers.LogDetail)
LogInsertAuthorize(application models.Application, action bool, detail datatransfers.LogDetail)
LogInsertUser(user models.User, success bool, detail datatransfers.LogDetail)
LogInsertApplication(user models.User, application models.Application, action bool, detail datatransfers.LogDetail)
}
Expand Down
5 changes: 5 additions & 0 deletions ratify-be/handlers/log.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ func (m *module) LogGetAllAdmin() (logs []models.Log, err error) {

func (m *module) LogInsertLogin(user models.User, application models.Application, success bool, detail datatransfers.LogDetail) {
description, _ := json.Marshal(detail)
m.db.userOrmer.IncrementLoginCount(user)
m.logEntry(models.Log{
UserSubject: sql.NullString{String: user.Subject, Valid: true},
ApplicationClientID: sql.NullString{String: application.ClientID, Valid: true},
Expand All @@ -36,6 +37,10 @@ func (m *module) LogInsertLogin(user models.User, application models.Application
})
}

func (m *module) LogInsertAuthorize(application models.Application, _ bool, _ datatransfers.LogDetail) {
m.db.applicationOrmer.IncrementAuthorizeCount(application)
}

func (m *module) LogInsertUser(user models.User, success bool, detail datatransfers.LogDetail) {
description, _ := json.Marshal(detail)
m.logEntry(models.Log{
Expand Down
7 changes: 7 additions & 0 deletions ratify-be/handlers/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,13 @@ func (m *module) UserGetOneByEmail(email string) (user models.User, err error) {
return
}

func (m *module) UserGetAll() (users []models.User, err error) {
if users, err = m.db.userOrmer.GetAll(); err != nil {
return []models.User{}, errors.New("cannot retrieve users")
}
return
}

func (m *module) UserUpdate(subject string, user datatransfers.UserUpdate) (err error) {
if err = m.db.userOrmer.UpdateUser(models.User{
Subject: subject,
Expand Down
42 changes: 28 additions & 14 deletions ratify-be/models/application.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package models

import (
"time"

"gorm.io/gorm"
)

Expand All @@ -11,20 +13,22 @@ type applicationOrm struct {
// CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
// Owner field is populated only when .Preload("Owner") is appended to the query pipeline
type Application struct {
ID string `gorm:"column:id;primaryKey;type:uuid;default:uuid_generate_v4()" json:"-"`
Owner User `gorm:"foreignKey:OwnerSubject;references:Subject;constraint:OnUpdate:CASCADE,OnDelete:SET NULL"`
OwnerSubject string
ClientID string `gorm:"column:client_id;uniqueIndex;type:char(32);not null" json:"-"`
ClientSecret string `gorm:"column:client_secret;type:varchar(100);not null" json:"-"`
Name string `gorm:"column:name;type:varchar(20);not null" json:"-"`
Description string `gorm:"column:description;type:varchar(50)" json:"-"`
LoginURL string `gorm:"column:login_url;type:text" json:"-"`
CallbackURL string `gorm:"column:callback_url;type:text" json:"-"`
LogoutURL string `gorm:"column:logout_url;type:text" json:"-"`
Metadata string `gorm:"column:metadata;type:text" json:"-"`
Locked bool `gorm:"column:locked;default:false" json:"-"`
CreatedAt int64 `gorm:"column:created_at;autoCreateTime" json:"-"`
UpdatedAt int64 `gorm:"column:updated_at;autoUpdateTime" json:"-"`
ID string `gorm:"column:id;primaryKey;type:uuid;default:uuid_generate_v4()" json:"-"`
Owner User `gorm:"foreignKey:OwnerSubject;references:Subject;constraint:OnUpdate:CASCADE,OnDelete:SET NULL"`
OwnerSubject string
ClientID string `gorm:"column:client_id;uniqueIndex;type:char(32);not null" json:"-"`
ClientSecret string `gorm:"column:client_secret;type:varchar(100);not null" json:"-"`
Name string `gorm:"column:name;type:varchar(20);not null" json:"-"`
Description string `gorm:"column:description;type:varchar(50)" json:"-"`
LoginURL string `gorm:"column:login_url;type:text" json:"-"`
CallbackURL string `gorm:"column:callback_url;type:text" json:"-"`
LogoutURL string `gorm:"column:logout_url;type:text" json:"-"`
Metadata string `gorm:"column:metadata;type:text" json:"-"`
Locked bool `gorm:"column:locked;default:false" json:"-"`
CreatedAt int64 `gorm:"column:created_at;autoCreateTime" json:"-"`
UpdatedAt int64 `gorm:"column:updated_at;autoUpdateTime" json:"-"`
LastAuthorize int64 `gorm:"column:last_authorize;default:0" json:"-"`
AuthorizeCount int `gorm:"column:authorize_count;default:0" json:"-"`
}

type ApplicationOrmer interface {
Expand All @@ -33,6 +37,7 @@ type ApplicationOrmer interface {
GetAll() (applications []Application, err error)
InsertApplication(application Application) (clientID string, err error)
UpdateApplication(application Application) (err error)
IncrementAuthorizeCount(application Application) (err error)
DeleteApplication(clientID string) (err error)
}

Expand Down Expand Up @@ -66,6 +71,15 @@ func (o *applicationOrm) UpdateApplication(application Application) (err error)
return result.Error
}

func (o *applicationOrm) IncrementAuthorizeCount(application Application) (err error) {
result := o.db.Model(&Application{}).Where("client_id = ?", application.ClientID).
Updates(map[string]interface{}{
"last_authorize": gorm.Expr("?", time.Now().Unix()),
"authorize_count": gorm.Expr("authorize_count + 1"),
})
return result.Error
}

func (o *applicationOrm) DeleteApplication(clientID string) (err error) {
result := o.db.Model(&Application{}).Where("client_id = ?", clientID).Delete(Application{})
return result.Error
Expand Down
Loading

0 comments on commit 2936d36

Please sign in to comment.