feat: add option to disable self-account editing

This commit is contained in:
Elias Schneider
2024-10-28 18:45:27 +01:00
parent 7bfc3f43a5
commit 8304065652
9 changed files with 65 additions and 30 deletions

View File

@@ -57,7 +57,7 @@ func initRouter(db *gorm.DB, appConfigService *service.AppConfigService) {
apiGroup := r.Group("/api") apiGroup := r.Group("/api")
controller.NewWebauthnController(apiGroup, jwtAuthMiddleware, middleware.NewRateLimitMiddleware(), webauthnService) controller.NewWebauthnController(apiGroup, jwtAuthMiddleware, middleware.NewRateLimitMiddleware(), webauthnService)
controller.NewOidcController(apiGroup, jwtAuthMiddleware, fileSizeLimitMiddleware, oidcService, jwtService) controller.NewOidcController(apiGroup, jwtAuthMiddleware, fileSizeLimitMiddleware, oidcService, jwtService)
controller.NewUserController(apiGroup, jwtAuthMiddleware, middleware.NewRateLimitMiddleware(), userService) controller.NewUserController(apiGroup, jwtAuthMiddleware, middleware.NewRateLimitMiddleware(), userService, appConfigService)
controller.NewAppConfigController(apiGroup, jwtAuthMiddleware, appConfigService) controller.NewAppConfigController(apiGroup, jwtAuthMiddleware, appConfigService)
controller.NewAuditLogController(apiGroup, auditLogService, jwtAuthMiddleware) controller.NewAuditLogController(apiGroup, auditLogService, jwtAuthMiddleware)
controller.NewUserGroupController(apiGroup, jwtAuthMiddleware, userGroupService) controller.NewUserGroupController(apiGroup, jwtAuthMiddleware, userGroupService)

View File

@@ -104,7 +104,6 @@ type ClientIdOrSecretNotProvidedError struct{}
func (e *ClientIdOrSecretNotProvidedError) Error() string { func (e *ClientIdOrSecretNotProvidedError) Error() string {
return "Client id and secret not provided" return "Client id and secret not provided"
} }
func (e *ClientIdOrSecretNotProvidedError) HttpStatusCode() int { return http.StatusBadRequest } func (e *ClientIdOrSecretNotProvidedError) HttpStatusCode() int { return http.StatusBadRequest }
type WrongFileTypeError struct { type WrongFileTypeError struct {
@@ -114,7 +113,6 @@ type WrongFileTypeError struct {
func (e *WrongFileTypeError) Error() string { func (e *WrongFileTypeError) Error() string {
return fmt.Sprintf("File must be of type %s", e.ExpectedFileType) return fmt.Sprintf("File must be of type %s", e.ExpectedFileType)
} }
func (e *WrongFileTypeError) HttpStatusCode() int { return http.StatusBadRequest } func (e *WrongFileTypeError) HttpStatusCode() int { return http.StatusBadRequest }
type MissingSessionIdError struct{} type MissingSessionIdError struct{}
@@ -122,7 +120,6 @@ type MissingSessionIdError struct{}
func (e *MissingSessionIdError) Error() string { func (e *MissingSessionIdError) Error() string {
return "Missing session id" return "Missing session id"
} }
func (e *MissingSessionIdError) HttpStatusCode() int { return http.StatusBadRequest } func (e *MissingSessionIdError) HttpStatusCode() int { return http.StatusBadRequest }
type ReservedClaimError struct { type ReservedClaimError struct {
@@ -132,7 +129,6 @@ type ReservedClaimError struct {
func (e *ReservedClaimError) Error() string { func (e *ReservedClaimError) Error() string {
return fmt.Sprintf("Claim %s is reserved and can't be used", e.Key) return fmt.Sprintf("Claim %s is reserved and can't be used", e.Key)
} }
func (e *ReservedClaimError) HttpStatusCode() int { return http.StatusBadRequest } func (e *ReservedClaimError) HttpStatusCode() int { return http.StatusBadRequest }
type DuplicateClaimError struct { type DuplicateClaimError struct {
@@ -142,5 +138,11 @@ type DuplicateClaimError struct {
func (e *DuplicateClaimError) Error() string { func (e *DuplicateClaimError) Error() string {
return fmt.Sprintf("Claim %s is already defined", e.Key) return fmt.Sprintf("Claim %s is already defined", e.Key)
} }
func (e *DuplicateClaimError) HttpStatusCode() int { return http.StatusBadRequest } func (e *DuplicateClaimError) HttpStatusCode() int { return http.StatusBadRequest }
type AccountEditNotAllowedError struct{}
func (e *AccountEditNotAllowedError) Error() string {
return "You are not allowed to edit your account"
}
func (e *AccountEditNotAllowedError) HttpStatusCode() int { return http.StatusForbidden }

View File

@@ -2,6 +2,7 @@ package controller
import ( import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/stonith404/pocket-id/backend/internal/common"
"github.com/stonith404/pocket-id/backend/internal/dto" "github.com/stonith404/pocket-id/backend/internal/dto"
"github.com/stonith404/pocket-id/backend/internal/middleware" "github.com/stonith404/pocket-id/backend/internal/middleware"
"github.com/stonith404/pocket-id/backend/internal/service" "github.com/stonith404/pocket-id/backend/internal/service"
@@ -11,9 +12,10 @@ import (
"time" "time"
) )
func NewUserController(group *gin.RouterGroup, jwtAuthMiddleware *middleware.JwtAuthMiddleware, rateLimitMiddleware *middleware.RateLimitMiddleware, userService *service.UserService) { func NewUserController(group *gin.RouterGroup, jwtAuthMiddleware *middleware.JwtAuthMiddleware, rateLimitMiddleware *middleware.RateLimitMiddleware, userService *service.UserService, appConfigService *service.AppConfigService) {
uc := UserController{ uc := UserController{
UserService: userService, UserService: userService,
AppConfigService: appConfigService,
} }
group.GET("/users", jwtAuthMiddleware.Add(true), uc.listUsersHandler) group.GET("/users", jwtAuthMiddleware.Add(true), uc.listUsersHandler)
@@ -30,7 +32,8 @@ func NewUserController(group *gin.RouterGroup, jwtAuthMiddleware *middleware.Jwt
} }
type UserController struct { type UserController struct {
UserService *service.UserService UserService *service.UserService
AppConfigService *service.AppConfigService
} }
func (uc *UserController) listUsersHandler(c *gin.Context) { func (uc *UserController) listUsersHandler(c *gin.Context) {
@@ -124,6 +127,10 @@ func (uc *UserController) updateUserHandler(c *gin.Context) {
} }
func (uc *UserController) updateCurrentUserHandler(c *gin.Context) { func (uc *UserController) updateCurrentUserHandler(c *gin.Context) {
if uc.AppConfigService.DbConfig.AllowOwnAccountEdit.Value != "true" {
c.Error(&common.AccountEditNotAllowedError{})
return
}
uc.updateUser(c, true) uc.updateUser(c, true)
} }

View File

@@ -12,13 +12,14 @@ type AppConfigVariableDto struct {
} }
type AppConfigUpdateDto struct { type AppConfigUpdateDto struct {
AppName string `json:"appName" binding:"required,min=1,max=30"` AppName string `json:"appName" binding:"required,min=1,max=30"`
SessionDuration string `json:"sessionDuration" binding:"required"` SessionDuration string `json:"sessionDuration" binding:"required"`
EmailsVerified string `json:"emailsVerified" binding:"required"` EmailsVerified string `json:"emailsVerified" binding:"required"`
EmailEnabled string `json:"emailEnabled" binding:"required"` AllowOwnAccountEdit string `json:"allowOwnAccountEdit" binding:"required"`
SmtHost string `json:"smtpHost"` EmailEnabled string `json:"emailEnabled" binding:"required"`
SmtpPort string `json:"smtpPort"` SmtHost string `json:"smtpHost"`
SmtpFrom string `json:"smtpFrom" binding:"omitempty,email"` SmtpPort string `json:"smtpPort"`
SmtpUser string `json:"smtpUser"` SmtpFrom string `json:"smtpFrom" binding:"omitempty,email"`
SmtpPassword string `json:"smtpPassword"` SmtpUser string `json:"smtpUser"`
SmtpPassword string `json:"smtpPassword"`
} }

View File

@@ -11,11 +11,13 @@ type AppConfigVariable struct {
type AppConfig struct { type AppConfig struct {
AppName AppConfigVariable AppName AppConfigVariable
SessionDuration AppConfigVariable
EmailsVerified AppConfigVariable
AllowOwnAccountEdit AppConfigVariable
BackgroundImageType AppConfigVariable BackgroundImageType AppConfigVariable
LogoLightImageType AppConfigVariable LogoLightImageType AppConfigVariable
LogoDarkImageType AppConfigVariable LogoDarkImageType AppConfigVariable
SessionDuration AppConfigVariable
EmailsVerified AppConfigVariable
EmailEnabled AppConfigVariable EmailEnabled AppConfigVariable
SmtpHost AppConfigVariable SmtpHost AppConfigVariable

View File

@@ -46,6 +46,12 @@ var defaultDbConfig = model.AppConfig{
Type: "bool", Type: "bool",
DefaultValue: "false", DefaultValue: "false",
}, },
AllowOwnAccountEdit: model.AppConfigVariable{
Key: "allowOwnAccountEdit",
Type: "bool",
IsPublic: true,
DefaultValue: "true",
},
BackgroundImageType: model.AppConfigVariable{ BackgroundImageType: model.AppConfigVariable{
Key: "backgroundImageType", Key: "backgroundImageType",
Type: "string", Type: "string",

View File

@@ -1,5 +1,6 @@
export type AppConfig = { export type AppConfig = {
appName: string; appName: string;
allowOwnAccountEdit: boolean;
}; };
export type AllAppConfig = AppConfig & { export type AllAppConfig = AppConfig & {

View File

@@ -3,6 +3,7 @@
import * as Card from '$lib/components/ui/card'; import * as Card from '$lib/components/ui/card';
import UserService from '$lib/services/user-service'; import UserService from '$lib/services/user-service';
import WebAuthnService from '$lib/services/webauthn-service'; import WebAuthnService from '$lib/services/webauthn-service';
import appConfigStore from '$lib/stores/application-configuration-store';
import type { Passkey } from '$lib/types/passkey.type'; import type { Passkey } from '$lib/types/passkey.type';
import type { UserCreate } from '$lib/types/user.type'; import type { UserCreate } from '$lib/types/user.type';
import { axiosErrorToast, getWebauthnErrorMessage } from '$lib/utils/error-util'; import { axiosErrorToast, getWebauthnErrorMessage } from '$lib/utils/error-util';
@@ -51,14 +52,16 @@
<title>Account Settings</title> <title>Account Settings</title>
</svelte:head> </svelte:head>
<Card.Root> {#if $appConfigStore.allowOwnAccountEdit}
<Card.Header> <Card.Root>
<Card.Title>Account Details</Card.Title> <Card.Header>
</Card.Header> <Card.Title>Account Details</Card.Title>
<Card.Content> </Card.Header>
<AccountForm {account} callback={updateAccount} /> <Card.Content>
</Card.Content> <AccountForm {account} callback={updateAccount} />
</Card.Root> </Card.Content>
</Card.Root>
{/if}
<Card.Root> <Card.Root>
<Card.Header> <Card.Header>

View File

@@ -21,13 +21,15 @@
const updatedAppConfig = { const updatedAppConfig = {
appName: appConfig.appName, appName: appConfig.appName,
sessionDuration: appConfig.sessionDuration, sessionDuration: appConfig.sessionDuration,
emailsVerified: appConfig.emailsVerified emailsVerified: appConfig.emailsVerified,
allowOwnAccountEdit: appConfig.allowOwnAccountEdit
}; };
const formSchema = z.object({ const formSchema = z.object({
appName: z.string().min(2).max(30), appName: z.string().min(2).max(30),
sessionDuration: z.number().min(1).max(43200), sessionDuration: z.number().min(1).max(43200),
emailsVerified: z.boolean() emailsVerified: z.boolean(),
allowOwnAccountEdit: z.boolean()
}); });
const { inputs, ...form } = createForm<typeof formSchema>(formSchema, updatedAppConfig); const { inputs, ...form } = createForm<typeof formSchema>(formSchema, updatedAppConfig);
@@ -49,6 +51,17 @@
description="The duration of a session in minutes before the user has to sign in again." description="The duration of a session in minutes before the user has to sign in again."
bind:input={$inputs.sessionDuration} bind:input={$inputs.sessionDuration}
/> />
<div class="items-top mt-5 flex space-x-2">
<Checkbox id="admin-privileges" bind:checked={$inputs.allowOwnAccountEdit.value} />
<div class="grid gap-1.5 leading-none">
<Label for="admin-privileges" class="mb-0 text-sm font-medium leading-none">
Enable Self-Account Editing
</Label>
<p class="text-muted-foreground text-[0.8rem]">
Whether the user should be able to edit their own account details.
</p>
</div>
</div>
<div class="items-top mt-5 flex space-x-2"> <div class="items-top mt-5 flex space-x-2">
<Checkbox id="admin-privileges" bind:checked={$inputs.emailsVerified.value} /> <Checkbox id="admin-privileges" bind:checked={$inputs.emailsVerified.value} />
<div class="grid gap-1.5 leading-none"> <div class="grid gap-1.5 leading-none">