mirror of
https://github.com/pocket-id/pocket-id.git
synced 2025-12-18 03:03:00 +03:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dbacdb5bf0 | ||
|
|
f4c6cff461 | ||
|
|
0b9cbf47e3 | ||
|
|
bda178c2bb | ||
|
|
6bd6cefaa6 | ||
|
|
83be1e0b49 | ||
|
|
cf3fe0be84 | ||
|
|
ec76e1c111 |
@@ -1,3 +1,12 @@
|
|||||||
|
## [](https://github.com/pocket-id/pocket-id/compare/v0.51.0...v) (2025-05-03)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* allow LDAP users to update their locale ([0b9cbf4](https://github.com/pocket-id/pocket-id/commit/0b9cbf47e36a332cfd854aa92e761264fb3e4795))
|
||||||
|
* last name still showing as required on account form ([#492](https://github.com/pocket-id/pocket-id/issues/492)) ([cf3fe0b](https://github.com/pocket-id/pocket-id/commit/cf3fe0be84f6365f5d4eb08c1b47905962a48a0d))
|
||||||
|
* non admin users weren't able to call the end session endpoint ([6bd6cef](https://github.com/pocket-id/pocket-id/commit/6bd6cefaa6dc571a319a6a1c2b2facc2404eadd3))
|
||||||
|
|
||||||
## [](https://github.com/pocket-id/pocket-id/compare/v0.50.0...v) (2025-04-28)
|
## [](https://github.com/pocket-id/pocket-id/compare/v0.50.0...v) (2025-04-28)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
"github.com/pocket-id/pocket-id/backend/internal/bootstrap"
|
"github.com/pocket-id/pocket-id/backend/internal/bootstrap"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -9,5 +11,8 @@ import (
|
|||||||
// @description.markdown
|
// @description.markdown
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
bootstrap.Bootstrap()
|
err := bootstrap.Bootstrap()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err.Error())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,25 +2,69 @@ package bootstrap
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
_ "github.com/golang-migrate/migrate/v4/source/file"
|
_ "github.com/golang-migrate/migrate/v4/source/file"
|
||||||
|
|
||||||
"github.com/pocket-id/pocket-id/backend/internal/service"
|
"github.com/pocket-id/pocket-id/backend/internal/job"
|
||||||
|
"github.com/pocket-id/pocket-id/backend/internal/utils"
|
||||||
"github.com/pocket-id/pocket-id/backend/internal/utils/signals"
|
"github.com/pocket-id/pocket-id/backend/internal/utils/signals"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Bootstrap() {
|
func Bootstrap() error {
|
||||||
// Get a context that is canceled when the application is stopping
|
// Get a context that is canceled when the application is stopping
|
||||||
ctx := signals.SignalContext(context.Background())
|
ctx := signals.SignalContext(context.Background())
|
||||||
|
|
||||||
initApplicationImages()
|
initApplicationImages()
|
||||||
|
|
||||||
|
// Perform migrations for changes
|
||||||
migrateConfigDBConnstring()
|
migrateConfigDBConnstring()
|
||||||
|
|
||||||
db := newDatabase()
|
|
||||||
appConfigService := service.NewAppConfigService(ctx, db)
|
|
||||||
|
|
||||||
migrateKey()
|
migrateKey()
|
||||||
|
|
||||||
initRouter(ctx, db, appConfigService)
|
// Connect to the database
|
||||||
|
db := newDatabase()
|
||||||
|
|
||||||
|
// Create all services
|
||||||
|
svc, err := initServices(ctx, db)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to initialize services: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init the job scheduler
|
||||||
|
scheduler, err := job.NewScheduler()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create job scheduler: %w", err)
|
||||||
|
}
|
||||||
|
err = registerScheduledJobs(ctx, db, svc, scheduler)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to register scheduled jobs: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init the router
|
||||||
|
router := initRouter(db, svc)
|
||||||
|
|
||||||
|
// Run all background serivces
|
||||||
|
// This call blocks until the context is canceled
|
||||||
|
err = utils.
|
||||||
|
NewServiceRunner(router, scheduler.Run).
|
||||||
|
Run(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to run services: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invoke all shutdown functions
|
||||||
|
// We give these a timeout of 5s
|
||||||
|
shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer shutdownCancel()
|
||||||
|
err = utils.
|
||||||
|
// TODO: Add shutdown services here
|
||||||
|
NewServiceRunner().
|
||||||
|
Run(shutdownCtx)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error shutting down services: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,9 +12,9 @@ import (
|
|||||||
|
|
||||||
// When building for E2E tests, add the e2etest controller
|
// When building for E2E tests, add the e2etest controller
|
||||||
func init() {
|
func init() {
|
||||||
registerTestControllers = []func(apiGroup *gin.RouterGroup, db *gorm.DB, appConfigService *service.AppConfigService, jwtService *service.JwtService){
|
registerTestControllers = []func(apiGroup *gin.RouterGroup, db *gorm.DB, svc *services){
|
||||||
func(apiGroup *gin.RouterGroup, db *gorm.DB, appConfigService *service.AppConfigService, jwtService *service.JwtService) {
|
func(apiGroup *gin.RouterGroup, db *gorm.DB, svc *services) {
|
||||||
testService := service.NewTestService(db, appConfigService, jwtService)
|
testService := service.NewTestService(db, svc.appConfigService, svc.jwtService)
|
||||||
controller.NewTestController(apiGroup, testService)
|
controller.NewTestController(apiGroup, testService)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,27 +9,28 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/pocket-id/pocket-id/backend/internal/common"
|
|
||||||
"github.com/pocket-id/pocket-id/backend/internal/controller"
|
|
||||||
"github.com/pocket-id/pocket-id/backend/internal/job"
|
|
||||||
"github.com/pocket-id/pocket-id/backend/internal/middleware"
|
|
||||||
"github.com/pocket-id/pocket-id/backend/internal/service"
|
|
||||||
"github.com/pocket-id/pocket-id/backend/internal/utils/systemd"
|
|
||||||
"golang.org/x/time/rate"
|
"golang.org/x/time/rate"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
|
|
||||||
|
"github.com/pocket-id/pocket-id/backend/internal/common"
|
||||||
|
"github.com/pocket-id/pocket-id/backend/internal/controller"
|
||||||
|
"github.com/pocket-id/pocket-id/backend/internal/middleware"
|
||||||
|
"github.com/pocket-id/pocket-id/backend/internal/utils"
|
||||||
|
"github.com/pocket-id/pocket-id/backend/internal/utils/systemd"
|
||||||
)
|
)
|
||||||
|
|
||||||
// This is used to register additional controllers for tests
|
// This is used to register additional controllers for tests
|
||||||
var registerTestControllers []func(apiGroup *gin.RouterGroup, db *gorm.DB, appConfigService *service.AppConfigService, jwtService *service.JwtService)
|
var registerTestControllers []func(apiGroup *gin.RouterGroup, db *gorm.DB, svc *services)
|
||||||
|
|
||||||
func initRouter(ctx context.Context, db *gorm.DB, appConfigService *service.AppConfigService) {
|
func initRouter(db *gorm.DB, svc *services) utils.Service {
|
||||||
err := initRouterInternal(ctx, db, appConfigService)
|
runner, err := initRouterInternal(db, svc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("failed to init router: %v", err)
|
log.Fatalf("failed to init router: %v", err)
|
||||||
}
|
}
|
||||||
|
return runner
|
||||||
}
|
}
|
||||||
|
|
||||||
func initRouterInternal(ctx context.Context, db *gorm.DB, appConfigService *service.AppConfigService) error {
|
func initRouterInternal(db *gorm.DB, svc *services) (utils.Service, error) {
|
||||||
// Set the appropriate Gin mode based on the environment
|
// Set the appropriate Gin mode based on the environment
|
||||||
switch common.EnvConfig.AppEnv {
|
switch common.EnvConfig.AppEnv {
|
||||||
case "production":
|
case "production":
|
||||||
@@ -43,23 +44,6 @@ func initRouterInternal(ctx context.Context, db *gorm.DB, appConfigService *serv
|
|||||||
r := gin.Default()
|
r := gin.Default()
|
||||||
r.Use(gin.Logger())
|
r.Use(gin.Logger())
|
||||||
|
|
||||||
// Initialize services
|
|
||||||
emailService, err := service.NewEmailService(appConfigService, db)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to create email service: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
geoLiteService := service.NewGeoLiteService(ctx)
|
|
||||||
auditLogService := service.NewAuditLogService(db, appConfigService, emailService, geoLiteService)
|
|
||||||
jwtService := service.NewJwtService(appConfigService)
|
|
||||||
webauthnService := service.NewWebAuthnService(db, jwtService, auditLogService, appConfigService)
|
|
||||||
userService := service.NewUserService(db, jwtService, auditLogService, emailService, appConfigService)
|
|
||||||
customClaimService := service.NewCustomClaimService(db)
|
|
||||||
oidcService := service.NewOidcService(db, jwtService, appConfigService, auditLogService, customClaimService)
|
|
||||||
userGroupService := service.NewUserGroupService(db, appConfigService)
|
|
||||||
ldapService := service.NewLdapService(db, appConfigService, userService, userGroupService)
|
|
||||||
apiKeyService := service.NewApiKeyService(db, emailService)
|
|
||||||
|
|
||||||
rateLimitMiddleware := middleware.NewRateLimitMiddleware()
|
rateLimitMiddleware := middleware.NewRateLimitMiddleware()
|
||||||
|
|
||||||
// Setup global middleware
|
// Setup global middleware
|
||||||
@@ -67,56 +51,31 @@ func initRouterInternal(ctx context.Context, db *gorm.DB, appConfigService *serv
|
|||||||
r.Use(middleware.NewErrorHandlerMiddleware().Add())
|
r.Use(middleware.NewErrorHandlerMiddleware().Add())
|
||||||
r.Use(rateLimitMiddleware.Add(rate.Every(time.Second), 60))
|
r.Use(rateLimitMiddleware.Add(rate.Every(time.Second), 60))
|
||||||
|
|
||||||
scheduler, err := job.NewScheduler()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to create job scheduler: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = scheduler.RegisterLdapJobs(ctx, ldapService, appConfigService)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to register LDAP jobs in scheduler: %w", err)
|
|
||||||
}
|
|
||||||
err = scheduler.RegisterDbCleanupJobs(ctx, db)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to register DB cleanup jobs in scheduler: %w", err)
|
|
||||||
}
|
|
||||||
err = scheduler.RegisterFileCleanupJobs(ctx, db)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to register file cleanup jobs in scheduler: %w", err)
|
|
||||||
}
|
|
||||||
err = scheduler.RegisterApiKeyExpiryJob(ctx, apiKeyService, appConfigService)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to register API key expiration jobs in scheduler: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run the scheduler in a background goroutine, until the context is canceled
|
|
||||||
go scheduler.Run(ctx)
|
|
||||||
|
|
||||||
// Initialize middleware for specific routes
|
// Initialize middleware for specific routes
|
||||||
authMiddleware := middleware.NewAuthMiddleware(apiKeyService, userService, jwtService)
|
authMiddleware := middleware.NewAuthMiddleware(svc.apiKeyService, svc.userService, svc.jwtService)
|
||||||
fileSizeLimitMiddleware := middleware.NewFileSizeLimitMiddleware()
|
fileSizeLimitMiddleware := middleware.NewFileSizeLimitMiddleware()
|
||||||
|
|
||||||
// Set up API routes
|
// Set up API routes
|
||||||
apiGroup := r.Group("/api")
|
apiGroup := r.Group("/api")
|
||||||
controller.NewApiKeyController(apiGroup, authMiddleware, apiKeyService)
|
controller.NewApiKeyController(apiGroup, authMiddleware, svc.apiKeyService)
|
||||||
controller.NewWebauthnController(apiGroup, authMiddleware, middleware.NewRateLimitMiddleware(), webauthnService, appConfigService)
|
controller.NewWebauthnController(apiGroup, authMiddleware, middleware.NewRateLimitMiddleware(), svc.webauthnService, svc.appConfigService)
|
||||||
controller.NewOidcController(apiGroup, authMiddleware, fileSizeLimitMiddleware, oidcService, jwtService)
|
controller.NewOidcController(apiGroup, authMiddleware, fileSizeLimitMiddleware, svc.oidcService, svc.jwtService)
|
||||||
controller.NewUserController(apiGroup, authMiddleware, middleware.NewRateLimitMiddleware(), userService, appConfigService)
|
controller.NewUserController(apiGroup, authMiddleware, middleware.NewRateLimitMiddleware(), svc.userService, svc.appConfigService)
|
||||||
controller.NewAppConfigController(apiGroup, authMiddleware, appConfigService, emailService, ldapService)
|
controller.NewAppConfigController(apiGroup, authMiddleware, svc.appConfigService, svc.emailService, svc.ldapService)
|
||||||
controller.NewAuditLogController(apiGroup, auditLogService, authMiddleware)
|
controller.NewAuditLogController(apiGroup, svc.auditLogService, authMiddleware)
|
||||||
controller.NewUserGroupController(apiGroup, authMiddleware, userGroupService)
|
controller.NewUserGroupController(apiGroup, authMiddleware, svc.userGroupService)
|
||||||
controller.NewCustomClaimController(apiGroup, authMiddleware, customClaimService)
|
controller.NewCustomClaimController(apiGroup, authMiddleware, svc.customClaimService)
|
||||||
|
|
||||||
// Add test controller in non-production environments
|
// Add test controller in non-production environments
|
||||||
if common.EnvConfig.AppEnv != "production" {
|
if common.EnvConfig.AppEnv != "production" {
|
||||||
for _, f := range registerTestControllers {
|
for _, f := range registerTestControllers {
|
||||||
f(apiGroup, db, appConfigService, jwtService)
|
f(apiGroup, db, svc)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set up base routes
|
// Set up base routes
|
||||||
baseGroup := r.Group("/")
|
baseGroup := r.Group("/")
|
||||||
controller.NewWellKnownController(baseGroup, jwtService)
|
controller.NewWellKnownController(baseGroup, svc.jwtService)
|
||||||
|
|
||||||
// Set up the server
|
// Set up the server
|
||||||
srv := &http.Server{
|
srv := &http.Server{
|
||||||
@@ -129,18 +88,13 @@ func initRouterInternal(ctx context.Context, db *gorm.DB, appConfigService *serv
|
|||||||
// Set up the listener
|
// Set up the listener
|
||||||
listener, err := net.Listen("tcp", srv.Addr)
|
listener, err := net.Listen("tcp", srv.Addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create TCP listener: %w", err)
|
return nil, fmt.Errorf("failed to create TCP listener: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Service runner function
|
||||||
|
runFn := func(ctx context.Context) error {
|
||||||
log.Printf("Server listening on %s", srv.Addr)
|
log.Printf("Server listening on %s", srv.Addr)
|
||||||
|
|
||||||
// Notify systemd that we are ready
|
|
||||||
err = systemd.SdNotifyReady()
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("[WARN] Unable to notify systemd that the service is ready: %v", err)
|
|
||||||
// continue to serve anyway since it's not that important
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start the server in a background goroutine
|
// Start the server in a background goroutine
|
||||||
go func() {
|
go func() {
|
||||||
defer listener.Close()
|
defer listener.Close()
|
||||||
@@ -152,6 +106,13 @@ func initRouterInternal(ctx context.Context, db *gorm.DB, appConfigService *serv
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
// Notify systemd that we are ready
|
||||||
|
err = systemd.SdNotifyReady()
|
||||||
|
if err != nil {
|
||||||
|
// Log the error only
|
||||||
|
log.Printf("[WARN] Unable to notify systemd that the service is ready: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
// Block until the context is canceled
|
// Block until the context is canceled
|
||||||
<-ctx.Done()
|
<-ctx.Done()
|
||||||
|
|
||||||
@@ -167,3 +128,6 @@ func initRouterInternal(ctx context.Context, db *gorm.DB, appConfigService *serv
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return runFn, nil
|
||||||
|
}
|
||||||
|
|||||||
35
backend/internal/bootstrap/scheduler_bootstrap.go
Normal file
35
backend/internal/bootstrap/scheduler_bootstrap.go
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
package bootstrap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
|
||||||
|
"github.com/pocket-id/pocket-id/backend/internal/job"
|
||||||
|
)
|
||||||
|
|
||||||
|
func registerScheduledJobs(ctx context.Context, db *gorm.DB, svc *services, scheduler *job.Scheduler) error {
|
||||||
|
err := scheduler.RegisterLdapJobs(ctx, svc.ldapService, svc.appConfigService)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to register LDAP jobs in scheduler: %w", err)
|
||||||
|
}
|
||||||
|
err = scheduler.RegisterGeoLiteUpdateJobs(ctx, svc.geoLiteService)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to register GeoLite DB update service: %w", err)
|
||||||
|
}
|
||||||
|
err = scheduler.RegisterDbCleanupJobs(ctx, db)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to register DB cleanup jobs in scheduler: %w", err)
|
||||||
|
}
|
||||||
|
err = scheduler.RegisterFileCleanupJobs(ctx, db)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to register file cleanup jobs in scheduler: %w", err)
|
||||||
|
}
|
||||||
|
err = scheduler.RegisterApiKeyExpiryJob(ctx, svc.apiKeyService, svc.appConfigService)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to register API key expiration jobs in scheduler: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
51
backend/internal/bootstrap/services_bootstrap.go
Normal file
51
backend/internal/bootstrap/services_bootstrap.go
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
package bootstrap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
|
||||||
|
"github.com/pocket-id/pocket-id/backend/internal/service"
|
||||||
|
)
|
||||||
|
|
||||||
|
type services struct {
|
||||||
|
appConfigService *service.AppConfigService
|
||||||
|
emailService *service.EmailService
|
||||||
|
geoLiteService *service.GeoLiteService
|
||||||
|
auditLogService *service.AuditLogService
|
||||||
|
jwtService *service.JwtService
|
||||||
|
webauthnService *service.WebAuthnService
|
||||||
|
userService *service.UserService
|
||||||
|
customClaimService *service.CustomClaimService
|
||||||
|
oidcService *service.OidcService
|
||||||
|
userGroupService *service.UserGroupService
|
||||||
|
ldapService *service.LdapService
|
||||||
|
apiKeyService *service.ApiKeyService
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initializes all services
|
||||||
|
// The context should be used by services only for initialization, and not for running
|
||||||
|
func initServices(initCtx context.Context, db *gorm.DB) (svc *services, err error) {
|
||||||
|
svc = &services{}
|
||||||
|
|
||||||
|
svc.appConfigService = service.NewAppConfigService(initCtx, db)
|
||||||
|
|
||||||
|
svc.emailService, err = service.NewEmailService(db, svc.appConfigService)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to create email service: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
svc.geoLiteService = service.NewGeoLiteService()
|
||||||
|
svc.auditLogService = service.NewAuditLogService(db, svc.appConfigService, svc.emailService, svc.geoLiteService)
|
||||||
|
svc.jwtService = service.NewJwtService(svc.appConfigService)
|
||||||
|
svc.userService = service.NewUserService(db, svc.jwtService, svc.auditLogService, svc.emailService, svc.appConfigService)
|
||||||
|
svc.customClaimService = service.NewCustomClaimService(db)
|
||||||
|
svc.oidcService = service.NewOidcService(db, svc.jwtService, svc.appConfigService, svc.auditLogService, svc.customClaimService)
|
||||||
|
svc.userGroupService = service.NewUserGroupService(db, svc.appConfigService)
|
||||||
|
svc.ldapService = service.NewLdapService(db, svc.appConfigService, svc.userService, svc.userGroupService)
|
||||||
|
svc.apiKeyService = service.NewApiKeyService(db, svc.emailService)
|
||||||
|
svc.webauthnService = service.NewWebAuthnService(db, svc.jwtService, svc.auditLogService, svc.appConfigService)
|
||||||
|
|
||||||
|
return svc, nil
|
||||||
|
}
|
||||||
@@ -30,8 +30,8 @@ func NewOidcController(group *gin.RouterGroup, authMiddleware *middleware.AuthMi
|
|||||||
group.POST("/oidc/token", oc.createTokensHandler)
|
group.POST("/oidc/token", oc.createTokensHandler)
|
||||||
group.GET("/oidc/userinfo", oc.userInfoHandler)
|
group.GET("/oidc/userinfo", oc.userInfoHandler)
|
||||||
group.POST("/oidc/userinfo", oc.userInfoHandler)
|
group.POST("/oidc/userinfo", oc.userInfoHandler)
|
||||||
group.POST("/oidc/end-session", authMiddleware.WithSuccessOptional().Add(), oc.EndSessionHandler)
|
group.POST("/oidc/end-session", authMiddleware.WithAdminNotRequired().WithSuccessOptional().Add(), oc.EndSessionHandler)
|
||||||
group.GET("/oidc/end-session", authMiddleware.WithSuccessOptional().Add(), oc.EndSessionHandler)
|
group.GET("/oidc/end-session", authMiddleware.WithAdminNotRequired().WithSuccessOptional().Add(), oc.EndSessionHandler)
|
||||||
group.POST("/oidc/introspect", oc.introspectTokenHandler)
|
group.POST("/oidc/introspect", oc.introspectTokenHandler)
|
||||||
|
|
||||||
group.GET("/oidc/clients", authMiddleware.Add(), oc.listClientsHandler)
|
group.GET("/oidc/clients", authMiddleware.Add(), oc.listClientsHandler)
|
||||||
|
|||||||
45
backend/internal/job/geoloite_update_job.go
Normal file
45
backend/internal/job/geoloite_update_job.go
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
package job
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/pocket-id/pocket-id/backend/internal/service"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GeoLiteUpdateJobs struct {
|
||||||
|
geoLiteService *service.GeoLiteService
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Scheduler) RegisterGeoLiteUpdateJobs(ctx context.Context, geoLiteService *service.GeoLiteService) error {
|
||||||
|
// Check if the service needs periodic updating
|
||||||
|
if geoLiteService.DisableUpdater() {
|
||||||
|
// Nothing to do
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
jobs := &GeoLiteUpdateJobs{geoLiteService: geoLiteService}
|
||||||
|
|
||||||
|
// Register the job to run every day, at 5 minutes past midnight
|
||||||
|
err := s.registerJob(ctx, "UpdateGeoLiteDB", "5 * */1 * *", jobs.updateGoeLiteDB)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the job immediately on startup, with a 1s delay
|
||||||
|
go func() {
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
err = jobs.updateGoeLiteDB(ctx)
|
||||||
|
if err != nil {
|
||||||
|
// Log the error only, but don't return it
|
||||||
|
log.Printf("Failed to Update GeoLite database: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *GeoLiteUpdateJobs) updateGoeLiteDB(ctx context.Context) error {
|
||||||
|
return j.geoLiteService.UpdateDatabase(ctx)
|
||||||
|
}
|
||||||
@@ -26,7 +26,7 @@ func NewScheduler() (*Scheduler, error) {
|
|||||||
|
|
||||||
// Run the scheduler.
|
// Run the scheduler.
|
||||||
// This function blocks until the context is canceled.
|
// This function blocks until the context is canceled.
|
||||||
func (s *Scheduler) Run(ctx context.Context) {
|
func (s *Scheduler) Run(ctx context.Context) error {
|
||||||
log.Println("Starting job scheduler")
|
log.Println("Starting job scheduler")
|
||||||
s.scheduler.Start()
|
s.scheduler.Start()
|
||||||
|
|
||||||
@@ -39,6 +39,8 @@ func (s *Scheduler) Run(ctx context.Context) {
|
|||||||
} else {
|
} else {
|
||||||
log.Println("Job scheduler shut down")
|
log.Println("Job scheduler shut down")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Scheduler) registerJob(ctx context.Context, name string, interval string, job func(ctx context.Context) error) error {
|
func (s *Scheduler) registerJob(ctx context.Context, name string, interval string, job func(ctx context.Context) error) error {
|
||||||
|
|||||||
@@ -26,12 +26,12 @@ type AppConfigService struct {
|
|||||||
db *gorm.DB
|
db *gorm.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAppConfigService(ctx context.Context, db *gorm.DB) *AppConfigService {
|
func NewAppConfigService(initCtx context.Context, db *gorm.DB) *AppConfigService {
|
||||||
service := &AppConfigService{
|
service := &AppConfigService{
|
||||||
db: db,
|
db: db,
|
||||||
}
|
}
|
||||||
|
|
||||||
err := service.LoadDbConfig(ctx)
|
err := service.LoadDbConfig(initCtx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to initialize app config service: %v", err)
|
log.Fatalf("Failed to initialize app config service: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ type EmailService struct {
|
|||||||
textTemplates map[string]*ttemplate.Template
|
textTemplates map[string]*ttemplate.Template
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewEmailService(appConfigService *AppConfigService, db *gorm.DB) (*EmailService, error) {
|
func NewEmailService(db *gorm.DB, appConfigService *AppConfigService) (*EmailService, error) {
|
||||||
htmlTemplates, err := email.PrepareHTMLTemplates(emailTemplatesPaths)
|
htmlTemplates, err := email.PrepareHTMLTemplates(emailTemplatesPaths)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("prepare html templates: %w", err)
|
return nil, fmt.Errorf("prepare html templates: %w", err)
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import (
|
|||||||
|
|
||||||
type GeoLiteService struct {
|
type GeoLiteService struct {
|
||||||
disableUpdater bool
|
disableUpdater bool
|
||||||
mutex sync.Mutex
|
mutex sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
var localhostIPNets = []*net.IPNet{
|
var localhostIPNets = []*net.IPNet{
|
||||||
@@ -42,25 +42,22 @@ var tailscaleIPNets = []*net.IPNet{
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewGeoLiteService initializes a new GeoLiteService instance and starts a goroutine to update the GeoLite2 City database.
|
// NewGeoLiteService initializes a new GeoLiteService instance and starts a goroutine to update the GeoLite2 City database.
|
||||||
func NewGeoLiteService(ctx context.Context) *GeoLiteService {
|
func NewGeoLiteService() *GeoLiteService {
|
||||||
service := &GeoLiteService{}
|
service := &GeoLiteService{}
|
||||||
|
|
||||||
if common.EnvConfig.MaxMindLicenseKey == "" && common.EnvConfig.GeoLiteDBUrl == common.MaxMindGeoLiteCityUrl {
|
if common.EnvConfig.MaxMindLicenseKey == "" && common.EnvConfig.GeoLiteDBUrl == common.MaxMindGeoLiteCityUrl {
|
||||||
// Warn the user, and disable the updater.
|
// Warn the user, and disable the periodic updater
|
||||||
log.Println("MAXMIND_LICENSE_KEY environment variable is empty. The GeoLite2 City database won't be updated.")
|
log.Println("MAXMIND_LICENSE_KEY environment variable is empty. The GeoLite2 City database won't be updated.")
|
||||||
service.disableUpdater = true
|
service.disableUpdater = true
|
||||||
}
|
}
|
||||||
|
|
||||||
go func() {
|
|
||||||
err := service.updateDatabase(ctx)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Failed to update GeoLite2 City database: %v", err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
return service
|
return service
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *GeoLiteService) DisableUpdater() bool {
|
||||||
|
return s.disableUpdater
|
||||||
|
}
|
||||||
|
|
||||||
// GetLocationByIP returns the country and city of the given IP address.
|
// GetLocationByIP returns the country and city of the given IP address.
|
||||||
func (s *GeoLiteService) GetLocationByIP(ipAddress string) (country, city string, err error) {
|
func (s *GeoLiteService) GetLocationByIP(ipAddress string) (country, city string, err error) {
|
||||||
// Check the IP address against known private IP ranges
|
// Check the IP address against known private IP ranges
|
||||||
@@ -83,8 +80,8 @@ func (s *GeoLiteService) GetLocationByIP(ipAddress string) (country, city string
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Race condition between reading and writing the database.
|
// Race condition between reading and writing the database.
|
||||||
s.mutex.Lock()
|
s.mutex.RLock()
|
||||||
defer s.mutex.Unlock()
|
defer s.mutex.RUnlock()
|
||||||
|
|
||||||
db, err := maxminddb.Open(common.EnvConfig.GeoLiteDBPath)
|
db, err := maxminddb.Open(common.EnvConfig.GeoLiteDBPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -92,7 +89,10 @@ func (s *GeoLiteService) GetLocationByIP(ipAddress string) (country, city string
|
|||||||
}
|
}
|
||||||
defer db.Close()
|
defer db.Close()
|
||||||
|
|
||||||
addr := netip.MustParseAddr(ipAddress)
|
addr, err := netip.ParseAddr(ipAddress)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", fmt.Errorf("failed to parse IP address: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
var record struct {
|
var record struct {
|
||||||
City struct {
|
City struct {
|
||||||
@@ -112,18 +112,13 @@ func (s *GeoLiteService) GetLocationByIP(ipAddress string) (country, city string
|
|||||||
}
|
}
|
||||||
|
|
||||||
// UpdateDatabase checks the age of the database and updates it if it's older than 14 days.
|
// UpdateDatabase checks the age of the database and updates it if it's older than 14 days.
|
||||||
func (s *GeoLiteService) updateDatabase(parentCtx context.Context) error {
|
func (s *GeoLiteService) UpdateDatabase(parentCtx context.Context) error {
|
||||||
if s.disableUpdater {
|
|
||||||
// Avoid updating the GeoLite2 City database.
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.isDatabaseUpToDate() {
|
if s.isDatabaseUpToDate() {
|
||||||
log.Println("GeoLite2 City database is up-to-date.")
|
log.Println("GeoLite2 City database is up-to-date")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Println("Updating GeoLite2 City database...")
|
log.Println("Updating GeoLite2 City database")
|
||||||
downloadUrl := fmt.Sprintf(common.EnvConfig.GeoLiteDBUrl, common.EnvConfig.MaxMindLicenseKey)
|
downloadUrl := fmt.Sprintf(common.EnvConfig.GeoLiteDBUrl, common.EnvConfig.MaxMindLicenseKey)
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(parentCtx, 10*time.Minute)
|
ctx, cancel := context.WithTimeout(parentCtx, 10*time.Minute)
|
||||||
@@ -145,7 +140,8 @@ func (s *GeoLiteService) updateDatabase(parentCtx context.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Extract the database file directly to the target path
|
// Extract the database file directly to the target path
|
||||||
if err := s.extractDatabase(resp.Body); err != nil {
|
err = s.extractDatabase(resp.Body)
|
||||||
|
if err != nil {
|
||||||
return fmt.Errorf("failed to extract database: %w", err)
|
return fmt.Errorf("failed to extract database: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,10 +175,9 @@ func (s *GeoLiteService) extractDatabase(reader io.Reader) error {
|
|||||||
// Iterate over the files in the tar archive
|
// Iterate over the files in the tar archive
|
||||||
for {
|
for {
|
||||||
header, err := tarReader.Next()
|
header, err := tarReader.Next()
|
||||||
if err == io.EOF {
|
if errors.Is(err, io.EOF) {
|
||||||
break
|
break
|
||||||
}
|
} else if err != nil {
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to read tar archive: %w", err)
|
return fmt.Errorf("failed to read tar archive: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -262,13 +262,13 @@ func (s *UserService) createUserInternal(ctx context.Context, input dto.UserCrea
|
|||||||
return user, nil
|
return user, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *UserService) UpdateUser(ctx context.Context, userID string, updatedUser dto.UserCreateDto, updateOwnUser bool, allowLdapUpdate bool) (model.User, error) {
|
func (s *UserService) UpdateUser(ctx context.Context, userID string, updatedUser dto.UserCreateDto, updateOwnUser bool, isLdapSync bool) (model.User, error) {
|
||||||
tx := s.db.Begin()
|
tx := s.db.Begin()
|
||||||
defer func() {
|
defer func() {
|
||||||
tx.Rollback()
|
tx.Rollback()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
user, err := s.updateUserInternal(ctx, userID, updatedUser, updateOwnUser, allowLdapUpdate, tx)
|
user, err := s.updateUserInternal(ctx, userID, updatedUser, updateOwnUser, isLdapSync, tx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return model.User{}, err
|
return model.User{}, err
|
||||||
}
|
}
|
||||||
@@ -292,11 +292,14 @@ func (s *UserService) updateUserInternal(ctx context.Context, userID string, upd
|
|||||||
return model.User{}, err
|
return model.User{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disallow updating the user if it is an LDAP group and LDAP is enabled
|
// Check if this is an LDAP user and LDAP is enabled
|
||||||
if !isLdapSync && user.LdapID != nil && s.appConfigService.GetDbConfig().LdapEnabled.IsTrue() {
|
isLdapUser := user.LdapID != nil && s.appConfigService.GetDbConfig().LdapEnabled.IsTrue()
|
||||||
return model.User{}, &common.LdapUserUpdateError{}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// For LDAP users, only allow updating the locale unless it's an LDAP sync
|
||||||
|
if !isLdapSync && isLdapUser {
|
||||||
|
// Only update the locale for LDAP users
|
||||||
|
user.Locale = updatedUser.Locale
|
||||||
|
} else {
|
||||||
user.FirstName = updatedUser.FirstName
|
user.FirstName = updatedUser.FirstName
|
||||||
user.LastName = updatedUser.LastName
|
user.LastName = updatedUser.LastName
|
||||||
user.Email = updatedUser.Email
|
user.Email = updatedUser.Email
|
||||||
@@ -306,6 +309,7 @@ func (s *UserService) updateUserInternal(ctx context.Context, userID string, upd
|
|||||||
user.IsAdmin = updatedUser.IsAdmin
|
user.IsAdmin = updatedUser.IsAdmin
|
||||||
user.Disabled = updatedUser.Disabled
|
user.Disabled = updatedUser.Disabled
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
err = tx.
|
err = tx.
|
||||||
WithContext(ctx).
|
WithContext(ctx).
|
||||||
|
|||||||
58
backend/internal/utils/servicerunner.go
Normal file
58
backend/internal/utils/servicerunner.go
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Source:
|
||||||
|
// https://github.com/ItalyPaleAle/traefik-forward-auth/blob/v3.5.1/pkg/utils/servicerunner.go
|
||||||
|
// Copyright (c) 2018, Thom Seddon & Contributors Copyright (c) 2023, Alessandro Segala & Contributors
|
||||||
|
// License: MIT (https://github.com/ItalyPaleAle/traefik-forward-auth/blob/v3.5.1/LICENSE.md)
|
||||||
|
|
||||||
|
// Service is a background service
|
||||||
|
type Service func(ctx context.Context) error
|
||||||
|
|
||||||
|
// ServiceRunner oversees a number of services running in background
|
||||||
|
type ServiceRunner struct {
|
||||||
|
services []Service
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewServiceRunner creates a new ServiceRunner
|
||||||
|
func NewServiceRunner(services ...Service) *ServiceRunner {
|
||||||
|
return &ServiceRunner{
|
||||||
|
services: services,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run all background services
|
||||||
|
func (r *ServiceRunner) Run(ctx context.Context) error {
|
||||||
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
errCh := make(chan error)
|
||||||
|
for _, service := range r.services {
|
||||||
|
go func(service Service) {
|
||||||
|
// Run the service
|
||||||
|
rErr := service(ctx)
|
||||||
|
|
||||||
|
// Ignore context canceled errors here as they generally indicate that the service is stopping
|
||||||
|
if rErr != nil && !errors.Is(rErr, context.Canceled) {
|
||||||
|
errCh <- rErr
|
||||||
|
return
|
||||||
|
}
|
||||||
|
errCh <- nil
|
||||||
|
}(service)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for all services to return
|
||||||
|
errs := make([]error, 0)
|
||||||
|
for range len(r.services) {
|
||||||
|
err := <-errCh
|
||||||
|
if err != nil {
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors.Join(errs...)
|
||||||
|
}
|
||||||
125
backend/internal/utils/servicerunner_test.go
Normal file
125
backend/internal/utils/servicerunner_test.go
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Source:
|
||||||
|
// https://github.com/ItalyPaleAle/traefik-forward-auth/blob/v3.5.1/pkg/utils/servicerunner.go
|
||||||
|
// Copyright (c) 2018, Thom Seddon & Contributors Copyright (c) 2023, Alessandro Segala & Contributors
|
||||||
|
// License: MIT (https://github.com/ItalyPaleAle/traefik-forward-auth/blob/v3.5.1/LICENSE.md)
|
||||||
|
|
||||||
|
func TestServiceRunner_Run(t *testing.T) {
|
||||||
|
t.Run("successful services", func(t *testing.T) {
|
||||||
|
// Create a service that just returns no error after 0.2s
|
||||||
|
successService := func(ctx context.Context) error {
|
||||||
|
time.Sleep(200 * time.Millisecond)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a service runner with two success services
|
||||||
|
runner := NewServiceRunner(successService, successService)
|
||||||
|
|
||||||
|
// Run the services with a timeout to avoid hanging if something goes wrong
|
||||||
|
ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
// Run should return nil when all services succeed
|
||||||
|
err := runner.Run(ctx)
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("service with error", func(t *testing.T) {
|
||||||
|
// Create a service that returns an error
|
||||||
|
expectedErr := errors.New("service failed")
|
||||||
|
errorService := func(ctx context.Context) error {
|
||||||
|
return expectedErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a service runner with one error service and one success service
|
||||||
|
successService := func(ctx context.Context) error {
|
||||||
|
time.Sleep(200 * time.Millisecond)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
runner := NewServiceRunner(errorService, successService)
|
||||||
|
|
||||||
|
// Run the services with a timeout
|
||||||
|
ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
// Run should return the error
|
||||||
|
err := runner.Run(ctx)
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
// The error should contain our expected error
|
||||||
|
require.ErrorIs(t, err, expectedErr)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("context canceled", func(t *testing.T) {
|
||||||
|
// Create a service that waits until context is canceled
|
||||||
|
waitingService := func(ctx context.Context) error {
|
||||||
|
<-ctx.Done()
|
||||||
|
return ctx.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create another service that returns no error quickly
|
||||||
|
quickService := func(ctx context.Context) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
runner := NewServiceRunner(waitingService, quickService)
|
||||||
|
|
||||||
|
// Create a context that we can cancel
|
||||||
|
ctx, cancel := context.WithCancel(t.Context())
|
||||||
|
|
||||||
|
// Run the runner in a goroutine
|
||||||
|
errCh := make(chan error)
|
||||||
|
go func() {
|
||||||
|
errCh <- runner.Run(ctx)
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Cancel the context to trigger service shutdown
|
||||||
|
cancel()
|
||||||
|
|
||||||
|
// Wait for the runner to finish with a timeout
|
||||||
|
select {
|
||||||
|
case err := <-errCh:
|
||||||
|
require.NoError(t, err, "expected nil error (context.Canceled should be ignored)")
|
||||||
|
case <-time.After(5 * time.Second):
|
||||||
|
t.Fatal("test timed out waiting for runner to finish")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("multiple errors", func(t *testing.T) {
|
||||||
|
// Create two services that return different errors
|
||||||
|
err1 := errors.New("error 1")
|
||||||
|
err2 := errors.New("error 2")
|
||||||
|
|
||||||
|
service1 := func(ctx context.Context) error {
|
||||||
|
return err1
|
||||||
|
}
|
||||||
|
service2 := func(ctx context.Context) error {
|
||||||
|
return err2
|
||||||
|
}
|
||||||
|
|
||||||
|
runner := NewServiceRunner(service1, service2)
|
||||||
|
|
||||||
|
// Run the services
|
||||||
|
ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
// Run should join all errors
|
||||||
|
err := runner.Run(ctx)
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
// Check that both errors are included
|
||||||
|
require.ErrorIs(t, err, err1)
|
||||||
|
require.ErrorIs(t, err, err2)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -93,7 +93,7 @@
|
|||||||
"application_configuration": "Configurazione dell'applicazione",
|
"application_configuration": "Configurazione dell'applicazione",
|
||||||
"settings": "Impostazioni",
|
"settings": "Impostazioni",
|
||||||
"update_pocket_id": "Aggiorna Pocket ID",
|
"update_pocket_id": "Aggiorna Pocket ID",
|
||||||
"powered_by": "Alimentato da",
|
"powered_by": "Powered by",
|
||||||
"see_your_account_activities_from_the_last_3_months": "Visualizza le attività del tuo account degli ultimi 3 mesi.",
|
"see_your_account_activities_from_the_last_3_months": "Visualizza le attività del tuo account degli ultimi 3 mesi.",
|
||||||
"time": "Ora",
|
"time": "Ora",
|
||||||
"event": "Evento",
|
"event": "Evento",
|
||||||
|
|||||||
@@ -343,8 +343,8 @@
|
|||||||
"callback_url_description": "URL-адреса, предоставленные клиентом. Поддерживаются wildcard-адреса (*), но лучше всего избегать их для лучшей безопасности.",
|
"callback_url_description": "URL-адреса, предоставленные клиентом. Поддерживаются wildcard-адреса (*), но лучше всего избегать их для лучшей безопасности.",
|
||||||
"api_key_expiration": "Истечение срока действия API ключа",
|
"api_key_expiration": "Истечение срока действия API ключа",
|
||||||
"send_an_email_to_the_user_when_their_api_key_is_about_to_expire": "Отправлять пользователю письмо, когда истечет срок действия API ключа.",
|
"send_an_email_to_the_user_when_their_api_key_is_about_to_expire": "Отправлять пользователю письмо, когда истечет срок действия API ключа.",
|
||||||
"authorize_device": "Authorize Device",
|
"authorize_device": "Авторизовать устройство",
|
||||||
"the_device_has_been_authorized": "The device has been authorized.",
|
"the_device_has_been_authorized": "Устройство авторизовано.",
|
||||||
"enter_code_displayed_in_previous_step": "Enter the code that was displayed in the previous step.",
|
"enter_code_displayed_in_previous_step": "Введите код, который был отображен на предыдущем шаге.",
|
||||||
"authorize": "Авторизируйте"
|
"authorize": "Авторизируйте"
|
||||||
}
|
}
|
||||||
|
|||||||
243
frontend/package-lock.json
generated
243
frontend/package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "pocket-id-frontend",
|
"name": "pocket-id-frontend",
|
||||||
"version": "0.46.0",
|
"version": "0.51.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "pocket-id-frontend",
|
"name": "pocket-id-frontend",
|
||||||
"version": "0.46.0",
|
"version": "0.51.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@simplewebauthn/browser": "^13.1.0",
|
"@simplewebauthn/browser": "^13.1.0",
|
||||||
"@tailwindcss/vite": "^4.0.0",
|
"@tailwindcss/vite": "^4.0.0",
|
||||||
@@ -52,7 +52,7 @@
|
|||||||
"tslib": "^2.8.1",
|
"tslib": "^2.8.1",
|
||||||
"typescript": "^5.7.3",
|
"typescript": "^5.7.3",
|
||||||
"typescript-eslint": "^8.21.0",
|
"typescript-eslint": "^8.21.0",
|
||||||
"vite": "^6.2.6"
|
"vite": "^6.3.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@ampproject/remapping": {
|
"node_modules/@ampproject/remapping": {
|
||||||
@@ -1112,228 +1112,260 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||||
"version": "4.32.0",
|
"version": "4.40.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.32.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.40.1.tgz",
|
||||||
"integrity": "sha512-G2fUQQANtBPsNwiVFg4zKiPQyjVKZCUdQUol53R8E71J7AsheRMV/Yv/nB8giOcOVqP7//eB5xPqieBYZe9bGg==",
|
"integrity": "sha512-kxz0YeeCrRUHz3zyqvd7n+TVRlNyTifBsmnmNPtk3hQURUyG9eAB+usz6DAwagMusjx/zb3AjvDUvhFGDAexGw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"android"
|
"android"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-android-arm64": {
|
"node_modules/@rollup/rollup-android-arm64": {
|
||||||
"version": "4.32.0",
|
"version": "4.40.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.32.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.40.1.tgz",
|
||||||
"integrity": "sha512-qhFwQ+ljoymC+j5lXRv8DlaJYY/+8vyvYmVx074zrLsu5ZGWYsJNLjPPVJJjhZQpyAKUGPydOq9hRLLNvh1s3A==",
|
"integrity": "sha512-PPkxTOisoNC6TpnDKatjKkjRMsdaWIhyuMkA4UsBXT9WEZY4uHezBTjs6Vl4PbqQQeu6oION1w2voYZv9yquCw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"android"
|
"android"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-darwin-arm64": {
|
"node_modules/@rollup/rollup-darwin-arm64": {
|
||||||
"version": "4.32.0",
|
"version": "4.40.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.32.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.40.1.tgz",
|
||||||
"integrity": "sha512-44n/X3lAlWsEY6vF8CzgCx+LQaoqWGN7TzUfbJDiTIOjJm4+L2Yq+r5a8ytQRGyPqgJDs3Rgyo8eVL7n9iW6AQ==",
|
"integrity": "sha512-VWXGISWFY18v/0JyNUy4A46KCFCb9NVsH+1100XP31lud+TzlezBbz24CYzbnA4x6w4hx+NYCXDfnvDVO6lcAA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"darwin"
|
"darwin"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-darwin-x64": {
|
"node_modules/@rollup/rollup-darwin-x64": {
|
||||||
"version": "4.32.0",
|
"version": "4.40.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.32.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.40.1.tgz",
|
||||||
"integrity": "sha512-F9ct0+ZX5Np6+ZDztxiGCIvlCaW87HBdHcozUfsHnj1WCUTBUubAoanhHUfnUHZABlElyRikI0mgcw/qdEm2VQ==",
|
"integrity": "sha512-nIwkXafAI1/QCS7pxSpv/ZtFW6TXcNUEHAIA9EIyw5OzxJZQ1YDrX+CL6JAIQgZ33CInl1R6mHet9Y/UZTg2Bw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"darwin"
|
"darwin"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-freebsd-arm64": {
|
"node_modules/@rollup/rollup-freebsd-arm64": {
|
||||||
"version": "4.32.0",
|
"version": "4.40.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.32.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.40.1.tgz",
|
||||||
"integrity": "sha512-JpsGxLBB2EFXBsTLHfkZDsXSpSmKD3VxXCgBQtlPcuAqB8TlqtLcbeMhxXQkCDv1avgwNjF8uEIbq5p+Cee0PA==",
|
"integrity": "sha512-BdrLJ2mHTrIYdaS2I99mriyJfGGenSaP+UwGi1kB9BLOCu9SR8ZpbkmmalKIALnRw24kM7qCN0IOm6L0S44iWw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"freebsd"
|
"freebsd"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-freebsd-x64": {
|
"node_modules/@rollup/rollup-freebsd-x64": {
|
||||||
"version": "4.32.0",
|
"version": "4.40.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.32.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.40.1.tgz",
|
||||||
"integrity": "sha512-wegiyBT6rawdpvnD9lmbOpx5Sph+yVZKHbhnSP9MqUEDX08G4UzMU+D87jrazGE7lRSyTRs6NEYHtzfkJ3FjjQ==",
|
"integrity": "sha512-VXeo/puqvCG8JBPNZXZf5Dqq7BzElNJzHRRw3vjBE27WujdzuOPecDPc/+1DcdcTptNBep3861jNq0mYkT8Z6Q==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"freebsd"
|
"freebsd"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
||||||
"version": "4.32.0",
|
"version": "4.40.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.32.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.40.1.tgz",
|
||||||
"integrity": "sha512-3pA7xecItbgOs1A5H58dDvOUEboG5UfpTq3WzAdF54acBbUM+olDJAPkgj1GRJ4ZqE12DZ9/hNS2QZk166v92A==",
|
"integrity": "sha512-ehSKrewwsESPt1TgSE/na9nIhWCosfGSFqv7vwEtjyAqZcvbGIg4JAcV7ZEh2tfj/IlfBeZjgOXm35iOOjadcg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
||||||
"version": "4.32.0",
|
"version": "4.40.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.32.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.40.1.tgz",
|
||||||
"integrity": "sha512-Y7XUZEVISGyge51QbYyYAEHwpGgmRrAxQXO3siyYo2kmaj72USSG8LtlQQgAtlGfxYiOwu+2BdbPjzEpcOpRmQ==",
|
"integrity": "sha512-m39iO/aaurh5FVIu/F4/Zsl8xppd76S4qoID8E+dSRQvTyZTOI2gVk3T4oqzfq1PtcvOfAVlwLMK3KRQMaR8lg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
||||||
"version": "4.32.0",
|
"version": "4.40.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.32.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.40.1.tgz",
|
||||||
"integrity": "sha512-r7/OTF5MqeBrZo5omPXcTnjvv1GsrdH8a8RerARvDFiDwFpDVDnJyByYM/nX+mvks8XXsgPUxkwe/ltaX2VH7w==",
|
"integrity": "sha512-Y+GHnGaku4aVLSgrT0uWe2o2Rq8te9hi+MwqGF9r9ORgXhmHK5Q71N757u0F8yU1OIwUIFy6YiJtKjtyktk5hg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
||||||
"version": "4.32.0",
|
"version": "4.40.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.32.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.40.1.tgz",
|
||||||
"integrity": "sha512-HJbifC9vex9NqnlodV2BHVFNuzKL5OnsV2dvTw6e1dpZKkNjPG6WUq+nhEYV6Hv2Bv++BXkwcyoGlXnPrjAKXw==",
|
"integrity": "sha512-jEwjn3jCA+tQGswK3aEWcD09/7M5wGwc6+flhva7dsQNRZZTe30vkalgIzV4tjkopsTS9Jd7Y1Bsj6a4lzz8gQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-loongarch64-gnu": {
|
"node_modules/@rollup/rollup-linux-loongarch64-gnu": {
|
||||||
"version": "4.32.0",
|
"version": "4.40.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.32.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.40.1.tgz",
|
||||||
"integrity": "sha512-VAEzZTD63YglFlWwRj3taofmkV1V3xhebDXffon7msNz4b14xKsz7utO6F8F4cqt8K/ktTl9rm88yryvDpsfOw==",
|
"integrity": "sha512-ySyWikVhNzv+BV/IDCsrraOAZ3UaC8SZB67FZlqVwXwnFhPihOso9rPOxzZbjp81suB1O2Topw+6Ug3JNegejQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"loong64"
|
"loong64"
|
||||||
],
|
],
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
|
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
|
||||||
"version": "4.32.0",
|
"version": "4.40.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.32.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.40.1.tgz",
|
||||||
"integrity": "sha512-Sts5DST1jXAc9YH/iik1C9QRsLcCoOScf3dfbY5i4kH9RJpKxiTBXqm7qU5O6zTXBTEZry69bGszr3SMgYmMcQ==",
|
"integrity": "sha512-BvvA64QxZlh7WZWqDPPdt0GH4bznuL6uOO1pmgPnnv86rpUpc8ZxgZwcEgXvo02GRIZX1hQ0j0pAnhwkhwPqWg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
||||||
"version": "4.32.0",
|
"version": "4.40.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.32.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.40.1.tgz",
|
||||||
"integrity": "sha512-qhlXeV9AqxIyY9/R1h1hBD6eMvQCO34ZmdYvry/K+/MBs6d1nRFLm6BOiITLVI+nFAAB9kUB6sdJRKyVHXnqZw==",
|
"integrity": "sha512-EQSP+8+1VuSulm9RKSMKitTav89fKbHymTf25n5+Yr6gAPZxYWpj3DzAsQqoaHAk9YX2lwEyAf9S4W8F4l3VBQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@rollup/rollup-linux-riscv64-musl": {
|
||||||
|
"version": "4.40.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.40.1.tgz",
|
||||||
|
"integrity": "sha512-n/vQ4xRZXKuIpqukkMXZt9RWdl+2zgGNx7Uda8NtmLJ06NL8jiHxUawbwC+hdSq1rrw/9CghCpEONor+l1e2gA==",
|
||||||
|
"cpu": [
|
||||||
|
"riscv64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
||||||
"version": "4.32.0",
|
"version": "4.40.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.32.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.40.1.tgz",
|
||||||
"integrity": "sha512-8ZGN7ExnV0qjXa155Rsfi6H8M4iBBwNLBM9lcVS+4NcSzOFaNqmt7djlox8pN1lWrRPMRRQ8NeDlozIGx3Omsw==",
|
"integrity": "sha512-h8d28xzYb98fMQKUz0w2fMc1XuGzLLjdyxVIbhbil4ELfk5/orZlSTpF/xdI9C8K0I8lCkq+1En2RJsawZekkg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"s390x"
|
"s390x"
|
||||||
],
|
],
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
||||||
"version": "4.32.0",
|
"version": "4.40.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.32.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.40.1.tgz",
|
||||||
"integrity": "sha512-VDzNHtLLI5s7xd/VubyS10mq6TxvZBp+4NRWoW+Hi3tgV05RtVm4qK99+dClwTN1McA6PHwob6DEJ6PlXbY83A==",
|
"integrity": "sha512-XiK5z70PEFEFqcNj3/zRSz/qX4bp4QIraTy9QjwJAb/Z8GM7kVUsD0Uk8maIPeTyPCP03ChdI+VVmJriKYbRHQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-x64-musl": {
|
"node_modules/@rollup/rollup-linux-x64-musl": {
|
||||||
"version": "4.32.0",
|
"version": "4.40.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.32.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.40.1.tgz",
|
||||||
"integrity": "sha512-qcb9qYDlkxz9DxJo7SDhWxTWV1gFuwznjbTiov289pASxlfGbaOD54mgbs9+z94VwrXtKTu+2RqwlSTbiOqxGg==",
|
"integrity": "sha512-2BRORitq5rQ4Da9blVovzNCMaUlyKrzMSvkVR0D4qPuOy/+pMCrh1d7o01RATwVy+6Fa1WBw+da7QPeLWU/1mQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
||||||
"version": "4.32.0",
|
"version": "4.40.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.32.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.40.1.tgz",
|
||||||
"integrity": "sha512-pFDdotFDMXW2AXVbfdUEfidPAk/OtwE/Hd4eYMTNVVaCQ6Yl8et0meDaKNL63L44Haxv4UExpv9ydSf3aSayDg==",
|
"integrity": "sha512-b2bcNm9Kbde03H+q+Jjw9tSfhYkzrDUf2d5MAd1bOJuVplXvFhWz7tRtWvD8/ORZi7qSCy0idW6tf2HgxSXQSg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"win32"
|
"win32"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
||||||
"version": "4.32.0",
|
"version": "4.40.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.32.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.40.1.tgz",
|
||||||
"integrity": "sha512-/TG7WfrCAjeRNDvI4+0AAMoHxea/USWhAzf9PVDFHbcqrQ7hMMKp4jZIy4VEjk72AAfN5k4TiSMRXRKf/0akSw==",
|
"integrity": "sha512-DfcogW8N7Zg7llVEfpqWMZcaErKfsj9VvmfSyRjCyo4BI3wPEfrzTtJkZG6gKP/Z92wFm6rz2aDO7/JfiR/whA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ia32"
|
"ia32"
|
||||||
],
|
],
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"win32"
|
"win32"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
||||||
"version": "4.32.0",
|
"version": "4.40.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.32.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.40.1.tgz",
|
||||||
"integrity": "sha512-5hqO5S3PTEO2E5VjCePxv40gIgyS2KvO7E7/vvC/NbIW4SIRamkMr1hqj+5Y67fbBWv/bQLB6KelBQmXlyCjWA==",
|
"integrity": "sha512-ECyOuDeH3C1I8jH2MK1RtBJW+YPMvSfT0a5NN0nHfQYnDSJ6tUiZH3gzwVP5/Kfh/+Tt7tpWVF9LXNTnhTJ3kA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"win32"
|
"win32"
|
||||||
@@ -1709,9 +1741,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/estree": {
|
"node_modules/@types/estree": {
|
||||||
"version": "1.0.6",
|
"version": "1.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz",
|
||||||
"integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw=="
|
"integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==",
|
||||||
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/json-schema": {
|
"node_modules/@types/json-schema": {
|
||||||
"version": "7.0.15",
|
"version": "7.0.15",
|
||||||
@@ -2970,10 +3003,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/fdir": {
|
"node_modules/fdir": {
|
||||||
"version": "6.4.3",
|
"version": "6.4.4",
|
||||||
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz",
|
||||||
"integrity": "sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==",
|
"integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==",
|
||||||
"dev": true,
|
"license": "MIT",
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"picomatch": "^3 || ^4"
|
"picomatch": "^3 || ^4"
|
||||||
},
|
},
|
||||||
@@ -3979,7 +4012,6 @@
|
|||||||
"version": "4.0.2",
|
"version": "4.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
|
||||||
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
|
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
},
|
},
|
||||||
@@ -4432,11 +4464,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/rollup": {
|
"node_modules/rollup": {
|
||||||
"version": "4.32.0",
|
"version": "4.40.1",
|
||||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.32.0.tgz",
|
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.40.1.tgz",
|
||||||
"integrity": "sha512-JmrhfQR31Q4AuNBjjAX4s+a/Pu/Q8Q9iwjWBsjRH1q52SPFE2NqRMK6fUZKKnvKO6id+h7JIRf0oYsph53eATg==",
|
"integrity": "sha512-C5VvvgCCyfyotVITIAv+4efVytl5F7wt+/I2i9q9GZcEXW9BP52YYOXC58igUi+LFZVHukErIIqQSWwv/M3WRw==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/estree": "1.0.6"
|
"@types/estree": "1.0.7"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"rollup": "dist/bin/rollup"
|
"rollup": "dist/bin/rollup"
|
||||||
@@ -4446,25 +4479,26 @@
|
|||||||
"npm": ">=8.0.0"
|
"npm": ">=8.0.0"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@rollup/rollup-android-arm-eabi": "4.32.0",
|
"@rollup/rollup-android-arm-eabi": "4.40.1",
|
||||||
"@rollup/rollup-android-arm64": "4.32.0",
|
"@rollup/rollup-android-arm64": "4.40.1",
|
||||||
"@rollup/rollup-darwin-arm64": "4.32.0",
|
"@rollup/rollup-darwin-arm64": "4.40.1",
|
||||||
"@rollup/rollup-darwin-x64": "4.32.0",
|
"@rollup/rollup-darwin-x64": "4.40.1",
|
||||||
"@rollup/rollup-freebsd-arm64": "4.32.0",
|
"@rollup/rollup-freebsd-arm64": "4.40.1",
|
||||||
"@rollup/rollup-freebsd-x64": "4.32.0",
|
"@rollup/rollup-freebsd-x64": "4.40.1",
|
||||||
"@rollup/rollup-linux-arm-gnueabihf": "4.32.0",
|
"@rollup/rollup-linux-arm-gnueabihf": "4.40.1",
|
||||||
"@rollup/rollup-linux-arm-musleabihf": "4.32.0",
|
"@rollup/rollup-linux-arm-musleabihf": "4.40.1",
|
||||||
"@rollup/rollup-linux-arm64-gnu": "4.32.0",
|
"@rollup/rollup-linux-arm64-gnu": "4.40.1",
|
||||||
"@rollup/rollup-linux-arm64-musl": "4.32.0",
|
"@rollup/rollup-linux-arm64-musl": "4.40.1",
|
||||||
"@rollup/rollup-linux-loongarch64-gnu": "4.32.0",
|
"@rollup/rollup-linux-loongarch64-gnu": "4.40.1",
|
||||||
"@rollup/rollup-linux-powerpc64le-gnu": "4.32.0",
|
"@rollup/rollup-linux-powerpc64le-gnu": "4.40.1",
|
||||||
"@rollup/rollup-linux-riscv64-gnu": "4.32.0",
|
"@rollup/rollup-linux-riscv64-gnu": "4.40.1",
|
||||||
"@rollup/rollup-linux-s390x-gnu": "4.32.0",
|
"@rollup/rollup-linux-riscv64-musl": "4.40.1",
|
||||||
"@rollup/rollup-linux-x64-gnu": "4.32.0",
|
"@rollup/rollup-linux-s390x-gnu": "4.40.1",
|
||||||
"@rollup/rollup-linux-x64-musl": "4.32.0",
|
"@rollup/rollup-linux-x64-gnu": "4.40.1",
|
||||||
"@rollup/rollup-win32-arm64-msvc": "4.32.0",
|
"@rollup/rollup-linux-x64-musl": "4.40.1",
|
||||||
"@rollup/rollup-win32-ia32-msvc": "4.32.0",
|
"@rollup/rollup-win32-arm64-msvc": "4.40.1",
|
||||||
"@rollup/rollup-win32-x64-msvc": "4.32.0",
|
"@rollup/rollup-win32-ia32-msvc": "4.40.1",
|
||||||
|
"@rollup/rollup-win32-x64-msvc": "4.40.1",
|
||||||
"fsevents": "~2.3.2"
|
"fsevents": "~2.3.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -4957,6 +4991,22 @@
|
|||||||
"integrity": "sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==",
|
"integrity": "sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
|
"node_modules/tinyglobby": {
|
||||||
|
"version": "0.2.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz",
|
||||||
|
"integrity": "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"fdir": "^6.4.4",
|
||||||
|
"picomatch": "^4.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/SuperchupuDev"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/to-regex-range": {
|
"node_modules/to-regex-range": {
|
||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||||
@@ -5154,14 +5204,17 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite": {
|
"node_modules/vite": {
|
||||||
"version": "6.2.6",
|
"version": "6.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-6.2.6.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-6.3.4.tgz",
|
||||||
"integrity": "sha512-9xpjNl3kR4rVDZgPNdTL0/c6ao4km69a/2ihNQbcANz8RuCOK3hQBmLSJf3bRKVQjVMda+YvizNE8AwvogcPbw==",
|
"integrity": "sha512-BiReIiMS2fyFqbqNT/Qqt4CVITDU9M9vE+DKcVAsB+ZV0wvTKd+3hMbkpxz1b+NmEDMegpVbisKiAZOnvO92Sw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "^0.25.0",
|
"esbuild": "^0.25.0",
|
||||||
|
"fdir": "^6.4.4",
|
||||||
|
"picomatch": "^4.0.2",
|
||||||
"postcss": "^8.5.3",
|
"postcss": "^8.5.3",
|
||||||
"rollup": "^4.30.1"
|
"rollup": "^4.34.9",
|
||||||
|
"tinyglobby": "^0.2.13"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"vite": "bin/vite.js"
|
"vite": "bin/vite.js"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "pocket-id-frontend",
|
"name": "pocket-id-frontend",
|
||||||
"version": "0.51.0",
|
"version": "0.51.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -57,6 +57,6 @@
|
|||||||
"tslib": "^2.8.1",
|
"tslib": "^2.8.1",
|
||||||
"typescript": "^5.7.3",
|
"typescript": "^5.7.3",
|
||||||
"typescript-eslint": "^8.21.0",
|
"typescript-eslint": "^8.21.0",
|
||||||
"vite": "^6.2.6"
|
"vite": "^6.3.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
onInput,
|
onInput,
|
||||||
...restProps
|
...restProps
|
||||||
}: HTMLAttributes<HTMLDivElement> & {
|
}: HTMLAttributes<HTMLDivElement> & {
|
||||||
input?: FormInput<string | boolean | number | Date>;
|
input?: FormInput<string | boolean | number | Date | undefined>;
|
||||||
label?: string;
|
label?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
import type { UserCreate } from '$lib/types/user.type';
|
import type { UserCreate } from '$lib/types/user.type';
|
||||||
import { axiosErrorToast } from '$lib/utils/error-util';
|
import { axiosErrorToast } from '$lib/utils/error-util';
|
||||||
import { createForm } from '$lib/utils/form-util';
|
import { createForm } from '$lib/utils/form-util';
|
||||||
import { BookUser } from 'lucide-svelte';
|
|
||||||
import { toast } from 'svelte-sonner';
|
import { toast } from 'svelte-sonner';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
@@ -29,7 +28,7 @@
|
|||||||
|
|
||||||
const formSchema = z.object({
|
const formSchema = z.object({
|
||||||
firstName: z.string().min(1).max(50),
|
firstName: z.string().min(1).max(50),
|
||||||
lastName: z.string().min(1).max(50),
|
lastName: z.string().max(50).optional(),
|
||||||
username: z
|
username: z
|
||||||
.string()
|
.string()
|
||||||
.min(2)
|
.min(2)
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
import { Button } from '$lib/components/ui/button';
|
import { Button } from '$lib/components/ui/button';
|
||||||
import * as Card from '$lib/components/ui/card';
|
import * as Card from '$lib/components/ui/card';
|
||||||
import UserGroupSelection from '$lib/components/user-group-selection.svelte';
|
import UserGroupSelection from '$lib/components/user-group-selection.svelte';
|
||||||
|
import { m } from '$lib/paraglide/messages';
|
||||||
import CustomClaimService from '$lib/services/custom-claim-service';
|
import CustomClaimService from '$lib/services/custom-claim-service';
|
||||||
import UserService from '$lib/services/user-service';
|
import UserService from '$lib/services/user-service';
|
||||||
import appConfigStore from '$lib/stores/application-configuration-store';
|
import appConfigStore from '$lib/stores/application-configuration-store';
|
||||||
@@ -14,7 +15,6 @@
|
|||||||
import { LucideChevronLeft } from 'lucide-svelte';
|
import { LucideChevronLeft } from 'lucide-svelte';
|
||||||
import { toast } from 'svelte-sonner';
|
import { toast } from 'svelte-sonner';
|
||||||
import UserForm from '../user-form.svelte';
|
import UserForm from '../user-form.svelte';
|
||||||
import { m } from '$lib/paraglide/messages';
|
|
||||||
|
|
||||||
let { data } = $props();
|
let { data } = $props();
|
||||||
let user = $state({
|
let user = $state({
|
||||||
@@ -75,7 +75,7 @@
|
|||||||
<title
|
<title
|
||||||
>{m.user_details_firstname_lastname({
|
>{m.user_details_firstname_lastname({
|
||||||
firstName: user.firstName,
|
firstName: user.firstName,
|
||||||
lastName: user.lastName
|
lastName: user.lastName ?? ''
|
||||||
})}</title
|
})}</title
|
||||||
>
|
>
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
|
|
||||||
async function deleteUser(user: User) {
|
async function deleteUser(user: User) {
|
||||||
openConfirmDialog({
|
openConfirmDialog({
|
||||||
title: m.delete_firstname_lastname({ firstName: user.firstName, lastName: user.lastName }),
|
title: m.delete_firstname_lastname({ firstName: user.firstName, lastName: user.lastName ?? "" }),
|
||||||
message: m.are_you_sure_you_want_to_delete_this_user(),
|
message: m.are_you_sure_you_want_to_delete_this_user(),
|
||||||
confirm: {
|
confirm: {
|
||||||
label: m.delete(),
|
label: m.delete(),
|
||||||
@@ -67,7 +67,7 @@
|
|||||||
|
|
||||||
async function disableUser(user: User) {
|
async function disableUser(user: User) {
|
||||||
openConfirmDialog({
|
openConfirmDialog({
|
||||||
title: m.disable_firstname_lastname({ firstName: user.firstName, lastName: user.lastName }),
|
title: m.disable_firstname_lastname({ firstName: user.firstName, lastName: user.lastName ?? "" }),
|
||||||
message: m.are_you_sure_you_want_to_disable_this_user(),
|
message: m.are_you_sure_you_want_to_disable_this_user(),
|
||||||
confirm: {
|
confirm: {
|
||||||
label: m.disable(),
|
label: m.disable(),
|
||||||
|
|||||||
Reference in New Issue
Block a user