Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Admin api changes #1974

Merged
merged 14 commits into from
Dec 3, 2024
Merged
15 changes: 15 additions & 0 deletions backend/dto/admin/otp.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package admin

import (
"github.com/gofrs/uuid"
"time"
)

type GetOTPRequestDto struct {
UserID string `param:"user_id" validate:"required,uuid4"`
}

type OTPDto struct {
ID uuid.UUID `json:"id"`
CreatedAt time.Time `json:"created_at"`
}
9 changes: 9 additions & 0 deletions backend/dto/admin/password.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,12 @@ type PasswordCredential struct {
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}

type GetPasswordCredentialRequestDto struct {
UserID string `param:"user_id" validate:"required,uuid4"`
}

type CreateOrUpdatePasswordCredentialRequestDto struct {
GetPasswordCredentialRequestDto
Password string `json:"password" validate:"required"`
}
9 changes: 9 additions & 0 deletions backend/dto/admin/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,12 @@ type CreateSessionTokenDto struct {
type CreateSessionTokenResponse struct {
SessionToken string `json:"session_token"`
}

type ListSessionsRequestDto struct {
UserID string `param:"user_id" validate:"required,uuid4"`
}

type DeleteSessionRequestDto struct {
ListSessionsRequestDto
SessionID string `param:"session_id" validate:"required,uuid4"`
}
10 changes: 10 additions & 0 deletions backend/dto/admin/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ type User struct {
UpdatedAt time.Time `json:"updated_at"`
Password *PasswordCredential `json:"password,omitempty"`
Identities []Identity `json:"identities,omitempty"`
OTP *OTPDto `json:"otp"`
}

// FromUserModel Converts the DB model to a DTO object
Expand Down Expand Up @@ -46,6 +47,14 @@ func FromUserModel(model models.User) User {
}
}

var otp *OTPDto = nil
if model.OTPSecret != nil {
otp = &OTPDto{
ID: model.OTPSecret.ID,
CreatedAt: model.OTPSecret.CreatedAt,
}
}

return User{
ID: model.ID,
WebauthnCredentials: credentials,
Expand All @@ -55,6 +64,7 @@ func FromUserModel(model models.User) User {
UpdatedAt: model.UpdatedAt,
Password: passwordCredential,
Identities: identities,
OTP: otp,
}
}

Expand Down
10 changes: 10 additions & 0 deletions backend/dto/admin/webauthn.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package admin

type ListWebauthnCredentialsRequestDto struct {
UserID string `param:"user_id" validate:"required,uuid"`
}

type GetWebauthnCredentialRequestDto struct {
ListWebauthnCredentialsRequestDto
WebauthnCredentialID string `param:"credential_id" validate:"required"`
}
2 changes: 2 additions & 0 deletions backend/dto/webauthn.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ type WebauthnCredentialResponse struct {
Transports []string `json:"transports"`
BackupEligible bool `json:"backup_eligible"`
BackupState bool `json:"backup_state"`
MFAOnly bool `json:"mfa_only"`
}

// FromWebauthnCredentialModel Converts the DB model to a DTO object
Expand All @@ -36,5 +37,6 @@ func FromWebauthnCredentialModel(c *models.WebauthnCredential) *WebauthnCredenti
Transports: c.Transports.GetNames(),
BackupEligible: c.BackupEligible,
BackupState: c.BackupState,
MFAOnly: c.MFAOnly,
}
}
5 changes: 1 addition & 4 deletions backend/flow_api/services/password.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"fmt"
"github.com/gobuffalo/pop/v6"
"github.com/gofrs/uuid"
"github.com/teamhanko/hanko/backend/config"
"github.com/teamhanko/hanko/backend/persistence"
"github.com/teamhanko/hanko/backend/persistence/models"
"golang.org/x/crypto/bcrypt"
Expand All @@ -25,13 +24,11 @@ type Password interface {

type password struct {
persister persistence.Persister
cfg config.Config
}

func NewPasswordService(cfg config.Config, persister persistence.Persister) Password {
func NewPasswordService(persister persistence.Persister) Password {
return &password{
persister,
cfg,
}
}

Expand Down
24 changes: 23 additions & 1 deletion backend/handler/admin_router.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ func NewAdminRouter(cfg *config.Config, persister persistence.Persister, prometh

userHandler := NewUserHandlerAdmin(persister)
emailHandler := NewEmailAdminHandler(cfg, persister)
sessionsHandler := NewSessionAdminHandler(cfg, persister, sessionManager, auditLogger)

user := g.Group("/users")
user.GET("", userHandler.List)
Expand All @@ -74,6 +75,28 @@ func NewAdminRouter(cfg *config.Config, persister persistence.Persister, prometh
email.DELETE("/:email_id", emailHandler.Delete)
email.POST("/:email_id/set_primary", emailHandler.SetPrimaryEmail)

webauthnCredentialHandler := NewWebauthnCredentialAdminHandler(persister)
webauthnCredentials := user.Group("/:user_id/webauthn_credentials")
webauthnCredentials.GET("", webauthnCredentialHandler.List)
webauthnCredentials.GET("/:credential_id", webauthnCredentialHandler.Get)
webauthnCredentials.DELETE("/:credential_id", webauthnCredentialHandler.Delete)

passwordCredentialHandler := NewPasswordAdminHandler(persister)
passwordCredentials := user.Group("/:user_id/password")
passwordCredentials.GET("", passwordCredentialHandler.Get)
passwordCredentials.POST("", passwordCredentialHandler.Create)
passwordCredentials.PUT("", passwordCredentialHandler.Update)
passwordCredentials.DELETE("", passwordCredentialHandler.Delete)

userSessions := user.Group("/:user_id/sessions")
userSessions.GET("", sessionsHandler.List)
userSessions.DELETE("/:session_id", sessionsHandler.Delete)

otpHandler := NewOTPAdminHandler(persister)
otp := user.Group("/:user_id/otp")
otp.GET("", otpHandler.Get)
otp.DELETE("", otpHandler.Delete)

auditLogHandler := NewAuditLogHandler(persister)

auditLogs := g.Group("/audit_logs")
Expand All @@ -87,7 +110,6 @@ func NewAdminRouter(cfg *config.Config, persister persistence.Persister, prometh
webhooks.DELETE("/:id", webhookHandler.Delete)
webhooks.PUT("/:id", webhookHandler.Update)

sessionsHandler := NewSessionAdminHandler(cfg, persister, sessionManager, auditLogger)
sessions := g.Group("/sessions")
sessions.POST("", sessionsHandler.Generate)

Expand Down
17 changes: 0 additions & 17 deletions backend/handler/email_admin.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,23 +42,6 @@ func NewEmailAdminHandler(cfg *config.Config, persister persistence.Persister) E
}
}

func loadDto[I admin.EmailRequests](ctx echo.Context) (*I, error) {
var adminDto I
err := ctx.Bind(&adminDto)
if err != nil {
ctx.Logger().Error(err)
return nil, echo.NewHTTPError(http.StatusBadRequest, err)
}

err = ctx.Validate(adminDto)
if err != nil {
ctx.Logger().Error(err)
return nil, echo.NewHTTPError(http.StatusBadRequest, err)
}

return &adminDto, nil
}

func (h *emailAdminHandler) List(ctx echo.Context) error {
listDto, err := loadDto[admin.ListEmailRequestDto](ctx)
if err != nil {
Expand Down
77 changes: 77 additions & 0 deletions backend/handler/otp_admin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package handler

import (
"fmt"
"github.com/gofrs/uuid"
"github.com/labstack/echo/v4"
"github.com/teamhanko/hanko/backend/dto/admin"
"github.com/teamhanko/hanko/backend/persistence"
"net/http"
)

type OTPAdminHandler interface {
Get(ctx echo.Context) error
Delete(ctx echo.Context) error
}

type otpAdminHandler struct {
persister persistence.Persister
}

func NewOTPAdminHandler(persister persistence.Persister) OTPAdminHandler {
return &otpAdminHandler{persister: persister}
}

func (h *otpAdminHandler) Get(ctx echo.Context) error {
getDto, err := loadDto[admin.GetOTPRequestDto](ctx)
if err != nil {
return err
}

userID, err := uuid.FromString(getDto.UserID)
if err != nil {
return fmt.Errorf(parseUserUuidFailureMessage, err)
}

userModel, err := h.persister.GetUserPersister().Get(userID)
if err != nil {
return err
}

if userModel == nil || userModel.OTPSecret == nil {
return echo.NewHTTPError(http.StatusNotFound)
}

return ctx.JSON(http.StatusOK, admin.OTPDto{
ID: userModel.OTPSecret.ID,
CreatedAt: userModel.OTPSecret.CreatedAt,
})
}

func (h *otpAdminHandler) Delete(ctx echo.Context) error {
deleteDto, err := loadDto[admin.GetOTPRequestDto](ctx)
if err != nil {
return err
}

userID, err := uuid.FromString(deleteDto.UserID)
if err != nil {
return fmt.Errorf(parseUserUuidFailureMessage, err)
}

userModel, err := h.persister.GetUserPersister().Get(userID)
if err != nil {
return err
}

if userModel == nil || userModel.OTPSecret == nil {
return echo.NewHTTPError(http.StatusNotFound)
}

err = h.persister.GetOTPSecretPersister().Delete(userModel.OTPSecret)
if err != nil {
return err
}

return ctx.NoContent(http.StatusNoContent)
}
Loading
Loading